@xviewer.js/debug 1.0.0-alpha.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/dist/module.js ADDED
@@ -0,0 +1,2805 @@
1
+ import { MeshBasicMaterial, MeshStandardMaterial, ShaderMaterial, Material } from 'three';
2
+ import { property, Plugin, PropertyManager, Component, ObjectInstance } from '@xviewer/core';
3
+
4
+ var e=[],t=[];function n(n,r){if(n&&"undefined"!=typeof document){var a,s=!0===r.prepend?"prepend":"append",d=!0===r.singleTag,i="string"==typeof r.container?document.querySelector(r.container):document.getElementsByTagName("head")[0];if(d){var u=e.indexOf(i);-1===u&&(u=e.push(i)-1,t[u]={}),a=t[u]&&t[u][s]?t[u][s]:t[u][s]=c();}else a=c();65279===n.charCodeAt(0)&&(n=n.substring(1)),a.styleSheet?a.styleSheet.cssText+=n:a.appendChild(document.createTextNode(n));}function c(){var e=document.createElement("style");if(e.setAttribute("type","text/css"),r.attributes)for(var t=Object.keys(r.attributes),n=0;n<t.length;n++)e.setAttribute(t[n],r.attributes[t[n]]);var a="prepend"===s?"afterbegin":"beforeend";return i.insertAdjacentElement(a,e),e}}
5
+
6
+ var css = "@charset \"UTF-8\";\n.lil-gui {\n font-family: var(--font-family);\n font-size: var(--font-size);\n line-height: 1;\n font-weight: normal;\n font-style: normal;\n text-align: left;\n background-color: var(--background-color);\n color: var(--text-color);\n user-select: none;\n -webkit-user-select: none;\n touch-action: manipulation;\n --background-color: #1f1f1f;\n --text-color: #ebebeb;\n --title-background-color: #111111;\n --title-text-color: #ebebeb;\n --widget-color: #424242;\n --hover-color: #4f4f4f;\n --focus-color: #595959;\n --number-color: #2cc9ff;\n --string-color: #a2db3c;\n --font-size: 11px;\n --input-font-size: 11px;\n --font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif;\n --font-family-mono: Menlo, Monaco, Consolas, \"Droid Sans Mono\", monospace;\n --padding: 4px;\n --spacing: 4px;\n --widget-height: 20px;\n --title-height: calc(var(--widget-height) + var(--spacing) * 1.25);\n --name-width: 40%;\n --slider-knob-width: 2px;\n --slider-input-width: 27%;\n --color-input-width: 27%;\n --slider-input-min-width: 45px;\n --color-input-min-width: 45px;\n --folder-indent: 7px;\n --widget-padding: 0 0 0 3px;\n --widget-border-radius: 2px;\n --checkbox-size: calc(0.75 * var(--widget-height));\n --scrollbar-width: 5px;\n}\n.lil-gui, .lil-gui * {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\n.lil-gui.root {\n width: var(--width, 245px);\n display: flex;\n flex-direction: column;\n}\n.lil-gui.root > .title {\n background: var(--title-background-color);\n color: var(--title-text-color);\n}\n.lil-gui.root > .children {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.lil-gui.root > .children::-webkit-scrollbar {\n width: var(--scrollbar-width);\n height: var(--scrollbar-width);\n background: var(--background-color);\n}\n.lil-gui.root > .children::-webkit-scrollbar-thumb {\n border-radius: var(--scrollbar-width);\n background: var(--focus-color);\n}\n@media (pointer: coarse) {\n .lil-gui.allow-touch-styles, .lil-gui.allow-touch-styles .lil-gui {\n --widget-height: 28px;\n --padding: 6px;\n --spacing: 6px;\n --font-size: 13px;\n --input-font-size: 16px;\n --folder-indent: 10px;\n --scrollbar-width: 7px;\n --slider-input-min-width: 50px;\n --color-input-min-width: 65px;\n }\n}\n.lil-gui.force-touch-styles, .lil-gui.force-touch-styles .lil-gui {\n --widget-height: 28px;\n --padding: 6px;\n --spacing: 6px;\n --font-size: 13px;\n --input-font-size: 16px;\n --folder-indent: 10px;\n --scrollbar-width: 7px;\n --slider-input-min-width: 50px;\n --color-input-min-width: 65px;\n}\n.lil-gui.autoPlace {\n max-height: 100%;\n position: fixed;\n top: 0;\n right: 15px;\n z-index: 1001;\n}\n\n.lil-gui .controller {\n display: flex;\n align-items: center;\n padding: 0 var(--padding);\n margin: var(--spacing) 0;\n}\n.lil-gui .controller.disabled {\n opacity: 0.5;\n}\n.lil-gui .controller.disabled, .lil-gui .controller.disabled * {\n pointer-events: none !important;\n}\n.lil-gui .controller > .name {\n min-width: var(--name-width);\n flex-shrink: 0;\n white-space: pre;\n padding-right: var(--spacing);\n line-height: var(--widget-height);\n}\n.lil-gui .controller .widget {\n position: relative;\n display: flex;\n align-items: center;\n width: 100%;\n min-height: var(--widget-height);\n}\n.lil-gui .controller.string input {\n color: var(--string-color);\n}\n.lil-gui .controller.boolean .widget {\n cursor: pointer;\n}\n.lil-gui .controller.color .display {\n width: 100%;\n height: var(--widget-height);\n border-radius: var(--widget-border-radius);\n position: relative;\n}\n@media (hover: hover) {\n .lil-gui .controller.color .display:hover:before {\n content: \" \";\n display: block;\n position: absolute;\n border-radius: var(--widget-border-radius);\n border: 1px solid rgba(255, 255, 255, 0.6);\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n }\n}\n.lil-gui .controller.color input[type=color] {\n opacity: 0;\n width: 100%;\n height: 100%;\n cursor: pointer;\n}\n.lil-gui .controller.color input[type=text] {\n margin-left: var(--spacing);\n font-family: var(--font-family-mono);\n min-width: var(--color-input-min-width);\n width: var(--color-input-width);\n flex-shrink: 0;\n}\n.lil-gui .controller.option select {\n opacity: 0;\n position: absolute;\n width: 100%;\n max-width: 100%;\n}\n.lil-gui .controller.option .display {\n position: relative;\n pointer-events: none;\n border-radius: var(--widget-border-radius);\n height: var(--widget-height);\n line-height: var(--widget-height);\n max-width: 100%;\n overflow: hidden;\n word-break: break-all;\n padding-left: 0.55em;\n padding-right: 1.75em;\n background: var(--widget-color);\n}\n@media (hover: hover) {\n .lil-gui .controller.option .display.focus {\n background: var(--focus-color);\n }\n}\n.lil-gui .controller.option .display.active {\n background: var(--focus-color);\n}\n.lil-gui .controller.option .display:after {\n font-family: \"lil-gui\";\n content: \"↕\";\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n padding-right: 0.375em;\n}\n.lil-gui .controller.option .widget,\n.lil-gui .controller.option select {\n cursor: pointer;\n}\n@media (hover: hover) {\n .lil-gui .controller.option .widget:hover .display {\n background: var(--hover-color);\n }\n}\n.lil-gui .controller.number input {\n color: var(--number-color);\n}\n.lil-gui .controller.number.hasSlider input {\n margin-left: var(--spacing);\n width: var(--slider-input-width);\n min-width: var(--slider-input-min-width);\n flex-shrink: 0;\n}\n.lil-gui .controller.number .slider {\n width: 100%;\n height: var(--widget-height);\n background-color: var(--widget-color);\n border-radius: var(--widget-border-radius);\n padding-right: var(--slider-knob-width);\n overflow: hidden;\n cursor: ew-resize;\n touch-action: pan-y;\n}\n@media (hover: hover) {\n .lil-gui .controller.number .slider:hover {\n background-color: var(--hover-color);\n }\n}\n.lil-gui .controller.number .slider.active {\n background-color: var(--focus-color);\n}\n.lil-gui .controller.number .slider.active .fill {\n opacity: 0.95;\n}\n.lil-gui .controller.number .fill {\n height: 100%;\n border-right: var(--slider-knob-width) solid var(--number-color);\n box-sizing: content-box;\n}\n.lil-gui .controller.image img {\n max-width: 100%;\n}\n.lil-gui .controller.image canvas {\n max-width: 100%;\n min-height: 1px;\n}\n.lil-gui .controller.vector input {\n color: var(--number-color);\n}\n.lil-gui .controller.vector .fill {\n height: 100%;\n margin-left: var(--spacing);\n box-sizing: content-box;\n}\n\n.lil-gui-dragging .lil-gui {\n --hover-color: var(--widget-color);\n}\n.lil-gui-dragging * {\n cursor: ew-resize !important;\n}\n\n.lil-gui-dragging.lil-gui-vertical * {\n cursor: ns-resize !important;\n}\n\n.lil-gui .title {\n height: var(--title-height);\n line-height: calc(var(--title-height) - 4px);\n font-weight: 600;\n padding: 0 var(--padding);\n -webkit-tap-highlight-color: transparent;\n cursor: pointer;\n outline: none;\n text-decoration-skip: objects;\n}\n.lil-gui .title:before {\n font-family: \"lil-gui\";\n content: \"▾\";\n padding-right: 2px;\n display: inline-block;\n}\n.lil-gui .title:active {\n background: var(--title-background-color);\n opacity: 0.75;\n}\n@media (hover: hover) {\n body:not(.lil-gui-dragging) .lil-gui .title:hover {\n background: var(--title-background-color);\n opacity: 0.85;\n }\n .lil-gui .title:focus {\n text-decoration: underline var(--focus-color);\n }\n}\n.lil-gui.root > .title:focus {\n text-decoration: none !important;\n}\n.lil-gui.closed > .title:before {\n content: \"▸\";\n}\n.lil-gui.closed > .children {\n transform: translateY(-7px);\n opacity: 0;\n}\n.lil-gui.closed:not(.transition) > .children {\n display: none;\n}\n.lil-gui.transition > .children {\n transition-duration: 300ms;\n transition-property: height, opacity, transform;\n transition-timing-function: cubic-bezier(0.2, 0.6, 0.35, 1);\n overflow: hidden;\n pointer-events: none;\n}\n.lil-gui .children:empty:before {\n content: \"Empty\";\n padding: 0 var(--padding);\n margin: var(--spacing) 0;\n display: block;\n height: var(--widget-height);\n font-style: italic;\n line-height: var(--widget-height);\n opacity: 0.5;\n}\n.lil-gui.root > .children > .lil-gui > .title {\n border: 0 solid var(--widget-color);\n border-width: 1px 0;\n transition: border-color 300ms;\n}\n.lil-gui.root > .children > .lil-gui.closed > .title {\n border-bottom-color: transparent;\n}\n.lil-gui + .controller {\n border-top: 1px solid var(--widget-color);\n margin-top: 0;\n padding-top: var(--spacing);\n}\n.lil-gui .lil-gui .lil-gui > .title {\n border: none;\n}\n.lil-gui .lil-gui .lil-gui > .children {\n border: none;\n margin-left: var(--folder-indent);\n border-left: 2px solid var(--widget-color);\n}\n.lil-gui .lil-gui .controller {\n border: none;\n}\n\n.lil-gui input {\n -webkit-tap-highlight-color: transparent;\n border: 0;\n outline: none;\n font-family: var(--font-family);\n font-size: var(--input-font-size);\n border-radius: var(--widget-border-radius);\n height: var(--widget-height);\n background: var(--widget-color);\n color: var(--text-color);\n width: 100%;\n}\n@media (hover: hover) {\n .lil-gui input:hover {\n background: var(--hover-color);\n }\n .lil-gui input:active {\n background: var(--focus-color);\n }\n}\n.lil-gui input:disabled {\n opacity: 1;\n}\n.lil-gui input[type=text],\n.lil-gui input[type=number] {\n padding: var(--widget-padding);\n}\n.lil-gui input[type=text]:focus,\n.lil-gui input[type=number]:focus {\n background: var(--focus-color);\n}\n.lil-gui input::-webkit-outer-spin-button,\n.lil-gui input::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n.lil-gui input[type=number] {\n -moz-appearance: textfield;\n}\n.lil-gui input[type=checkbox] {\n appearance: none;\n -webkit-appearance: none;\n height: var(--checkbox-size);\n width: var(--checkbox-size);\n border-radius: var(--widget-border-radius);\n text-align: center;\n cursor: pointer;\n}\n.lil-gui input[type=checkbox]:checked:before {\n font-family: \"lil-gui\";\n content: \"✓\";\n font-size: var(--checkbox-size);\n line-height: var(--checkbox-size);\n}\n@media (hover: hover) {\n .lil-gui input[type=checkbox]:focus {\n box-shadow: inset 0 0 0 1px var(--focus-color);\n }\n}\n.lil-gui button {\n -webkit-tap-highlight-color: transparent;\n outline: none;\n cursor: pointer;\n font-family: var(--font-family);\n font-size: var(--font-size);\n color: var(--text-color);\n width: 100%;\n height: var(--widget-height);\n text-transform: none;\n background: var(--widget-color);\n border-radius: var(--widget-border-radius);\n border: 1px solid var(--widget-color);\n text-align: center;\n line-height: calc(var(--widget-height) - 4px);\n}\n@media (hover: hover) {\n .lil-gui button:hover {\n background: var(--hover-color);\n border-color: var(--hover-color);\n }\n .lil-gui button:focus {\n border-color: var(--focus-color);\n }\n}\n.lil-gui button:active {\n background: var(--focus-color);\n}\n\n@font-face {\n font-family: \"lil-gui\";\n src: url(\"data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAUsAAsAAAAACJwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAAH4AAADAImwmYE9TLzIAAAGIAAAAPwAAAGBKqH5SY21hcAAAAcgAAAD0AAACrukyyJBnbHlmAAACvAAAAF8AAACEIZpWH2hlYWQAAAMcAAAAJwAAADZfcj2zaGhlYQAAA0QAAAAYAAAAJAC5AHhobXR4AAADXAAAABAAAABMAZAAAGxvY2EAAANsAAAAFAAAACgCEgIybWF4cAAAA4AAAAAeAAAAIAEfABJuYW1lAAADoAAAASIAAAIK9SUU/XBvc3QAAATEAAAAZgAAAJCTcMc2eJxVjbEOgjAURU+hFRBK1dGRL+ALnAiToyMLEzFpnPz/eAshwSa97517c/MwwJmeB9kwPl+0cf5+uGPZXsqPu4nvZabcSZldZ6kfyWnomFY/eScKqZNWupKJO6kXN3K9uCVoL7iInPr1X5baXs3tjuMqCtzEuagm/AAlzQgPAAB4nGNgYRBlnMDAysDAYM/gBiT5oLQBAwuDJAMDEwMrMwNWEJDmmsJwgCFeXZghBcjlZMgFCzOiKOIFAB71Bb8AeJy1kjFuwkAQRZ+DwRAwBtNQRUGKQ8OdKCAWUhAgKLhIuAsVSpWz5Bbkj3dEgYiUIszqWdpZe+Z7/wB1oCYmIoboiwiLT2WjKl/jscrHfGg/pKdMkyklC5Zs2LEfHYpjcRoPzme9MWWmk3dWbK9ObkWkikOetJ554fWyoEsmdSlt+uR0pCJR34b6t/TVg1SY3sYvdf8vuiKrpyaDXDISiegp17p7579Gp3p++y7HPAiY9pmTibljrr85qSidtlg4+l25GLCaS8e6rRxNBmsnERunKbaOObRz7N72ju5vdAjYpBXHgJylOAVsMseDAPEP8LYoUHicY2BiAAEfhiAGJgZWBgZ7RnFRdnVJELCQlBSRlATJMoLV2DK4glSYs6ubq5vbKrJLSbGrgEmovDuDJVhe3VzcXFwNLCOILB/C4IuQ1xTn5FPilBTj5FPmBAB4WwoqAHicY2BkYGAA4sk1sR/j+W2+MnAzpDBgAyEMQUCSg4EJxAEAwUgFHgB4nGNgZGBgSGFggJMhDIwMqEAYAByHATJ4nGNgAIIUNEwmAABl3AGReJxjYAACIQYlBiMGJ3wQAEcQBEV4nGNgZGBgEGZgY2BiAAEQyQWEDAz/wXwGAAsPATIAAHicXdBNSsNAHAXwl35iA0UQXYnMShfS9GPZA7T7LgIu03SSpkwzYTIt1BN4Ak/gKTyAeCxfw39jZkjymzcvAwmAW/wgwHUEGDb36+jQQ3GXGot79L24jxCP4gHzF/EIr4jEIe7wxhOC3g2TMYy4Q7+Lu/SHuEd/ivt4wJd4wPxbPEKMX3GI5+DJFGaSn4qNzk8mcbKSR6xdXdhSzaOZJGtdapd4vVPbi6rP+cL7TGXOHtXKll4bY1Xl7EGnPtp7Xy2n00zyKLVHfkHBa4IcJ2oD3cgggWvt/V/FbDrUlEUJhTn/0azVWbNTNr0Ens8de1tceK9xZmfB1CPjOmPH4kitmvOubcNpmVTN3oFJyjzCvnmrwhJTzqzVj9jiSX911FjeAAB4nG3HMRKCMBBA0f0giiKi4DU8k0V2GWbIZDOh4PoWWvq6J5V8If9NVNQcaDhyouXMhY4rPTcG7jwYmXhKq8Wz+p762aNaeYXom2n3m2dLTVgsrCgFJ7OTmIkYbwIbC6vIB7WmFfAAAA==\") format(\"woff\");\n}\n.curve-editor {\n display: flex;\n flex-direction: column;\n gap: 5px;\n background-color: #303030;\n outline: 1px solid #000000;\n padding: 5px;\n width: 100%;\n}\n.curve-editor .button-medium {\n background-color: #545454;\n min-width: var(--widget-height);\n min-height: var(--widget-height);\n height: 100%;\n outline: 1px solid #3c3c3c;\n display: flex;\n justify-content: center;\n align-items: center;\n color: #fff;\n font-size: var(--input-font-size, 11px);\n}\n.curve-editor .button-medium.selected {\n background-color: #4772b3;\n pointer-events: none;\n}\n.curve-editor .button-medium:hover {\n background-color: #656565;\n cursor: default;\n}\n.curve-editor .curve-top {\n display: flex;\n width: 100%;\n}\n.curve-editor .curve-panel {\n width: 100%;\n height: 90px;\n outline: 1px solid #646464;\n}\n.curve-editor .curve-point-panel {\n display: flex;\n align-items: center;\n width: 100%;\n height: var(--widget-height);\n}\n.curve-editor .curve-point-panel input {\n background-color: #545454;\n height: 100%;\n width: 100%;\n color: #fff;\n border: 0;\n outline: 1px solid #3c3c3c;\n text-align: center;\n font-size: var(--input-font-size, 11px);\n}";
7
+ n(css,{});
8
+
9
+ class Controller {
10
+ /**
11
+ * Sets the name of the controller and its label in the GUI.
12
+ * @param {string} name
13
+ * @returns {this}
14
+ */ name(name) {
15
+ this._name = name;
16
+ this.$name.innerHTML = name;
17
+ return this;
18
+ }
19
+ /**
20
+ * Pass a function to be called whenever the value is modified by this controller.
21
+ * The function receives the new value as its first parameter. The value of `this` will be the
22
+ * controller.
23
+ *
24
+ * For function controllers, the `onChange` callback will be fired on click, after the function
25
+ * executes.
26
+ * @param {Function} callback
27
+ * @returns {this}
28
+ * @example
29
+ * const controller = gui.add( object, 'property' );
30
+ *
31
+ * controller.onChange( function( v ) {
32
+ * console.log( 'The value is now ' + v );
33
+ * console.assert( this === controller );
34
+ * } );
35
+ */ onChange(callback) {
36
+ this._onChange = callback;
37
+ return this;
38
+ }
39
+ /**
40
+ * Calls the onChange methods of this controller and its parent GUI.
41
+ */ _callOnChange() {
42
+ this.parent._callOnChange(this);
43
+ if (this._onChange !== undefined) {
44
+ this._onChange.call(this, this.getValue());
45
+ }
46
+ this._changed = true;
47
+ }
48
+ /**
49
+ * Pass a function to be called after this controller has been modified and loses focus.
50
+ * @param {Function} callback
51
+ * @returns {this}
52
+ * @example
53
+ * const controller = gui.add( object, 'property' );
54
+ *
55
+ * controller.onFinishChange( function( v ) {
56
+ * console.log( 'Changes complete: ' + v );
57
+ * console.assert( this === controller );
58
+ * } );
59
+ */ onFinishChange(callback) {
60
+ this._onFinishChange = callback;
61
+ return this;
62
+ }
63
+ /**
64
+ * Should be called by Controller when its widgets lose focus.
65
+ */ _callOnFinishChange() {
66
+ if (this._changed) {
67
+ this.parent._callOnFinishChange(this);
68
+ if (this._onFinishChange !== undefined) {
69
+ this._onFinishChange.call(this, this.getValue());
70
+ }
71
+ }
72
+ this._changed = false;
73
+ }
74
+ /**
75
+ * Sets the controller back to its initial value.
76
+ * @returns {this}
77
+ */ reset() {
78
+ this.setValue(this.initialValue);
79
+ this._callOnFinishChange();
80
+ return this;
81
+ }
82
+ /**
83
+ * Enables this controller.
84
+ * @param {boolean} enabled
85
+ * @returns {this}
86
+ * @example
87
+ * controller.enable();
88
+ * controller.enable( false ); // disable
89
+ * controller.enable( controller._disabled ); // toggle
90
+ */ enable(enabled = true) {
91
+ return this.disable(!enabled);
92
+ }
93
+ /**
94
+ * Disables this controller.
95
+ * @param {boolean} disabled
96
+ * @returns {this}
97
+ * @example
98
+ * controller.disable();
99
+ * controller.disable( false ); // enable
100
+ * controller.disable( !controller._disabled ); // toggle
101
+ */ disable(disabled = true) {
102
+ if (disabled === this._disabled) return this;
103
+ this._disabled = disabled;
104
+ this.domElement.classList.toggle('disabled', disabled);
105
+ this.$disable.toggleAttribute('disabled', disabled);
106
+ return this;
107
+ }
108
+ /**
109
+ * Shows the Controller after it's been hidden.
110
+ * @param {boolean} show
111
+ * @returns {this}
112
+ * @example
113
+ * controller.show();
114
+ * controller.show( false ); // hide
115
+ * controller.show( controller._hidden ); // toggle
116
+ */ show(show = true) {
117
+ this._hidden = !show;
118
+ this.domElement.style.display = this._hidden ? 'none' : '';
119
+ return this;
120
+ }
121
+ /**
122
+ * Hides the Controller.
123
+ * @returns {this}
124
+ */ hide() {
125
+ return this.show(false);
126
+ }
127
+ options(options) {
128
+ return this;
129
+ }
130
+ /**
131
+ * Sets the minimum value. Only works on number controllers.
132
+ * @param {number} min
133
+ * @returns {this}
134
+ */ // eslint-disable-next-line no-unused-vars
135
+ min(min) {
136
+ return this;
137
+ }
138
+ /**
139
+ * Sets the maximum value. Only works on number controllers.
140
+ * @param {number} max
141
+ * @returns {this}
142
+ */ // eslint-disable-next-line no-unused-vars
143
+ max(max) {
144
+ return this;
145
+ }
146
+ /**
147
+ * Values set by this controller will be rounded to multiples of `step`. Only works on number
148
+ * controllers.
149
+ * @param {number} step
150
+ * @returns {this}
151
+ */ // eslint-disable-next-line no-unused-vars
152
+ step(step) {
153
+ return this;
154
+ }
155
+ /**
156
+ * Rounds the displayed value to a fixed number of decimals, without affecting the actual value
157
+ * like `step()`. Only works on number controllers.
158
+ * @example
159
+ * gui.add( object, 'property' ).listen().decimals( 4 );
160
+ * @param {number} decimals
161
+ * @returns {this}
162
+ */ // eslint-disable-next-line no-unused-vars
163
+ decimals(decimals) {
164
+ return this;
165
+ }
166
+ /**
167
+ * Calls `updateDisplay()` every animation frame. Pass `false` to stop listening.
168
+ * @param {boolean} listen
169
+ * @returns {this}
170
+ */ listen(listen = true) {
171
+ this._listening = listen;
172
+ if (this._listenCallbackID !== undefined) {
173
+ cancelAnimationFrame(this._listenCallbackID);
174
+ this._listenCallbackID = undefined;
175
+ }
176
+ if (this._listening) {
177
+ this._listenCallback();
178
+ }
179
+ return this;
180
+ }
181
+ needsUpdateDisplay() {
182
+ if (this.parent._closed) {
183
+ return false;
184
+ }
185
+ let value = this.save();
186
+ if (this._listenPrevValue !== value) {
187
+ this._listenPrevValue = value;
188
+ return true;
189
+ }
190
+ return false;
191
+ }
192
+ _listenCallback() {
193
+ this._listenCallbackID = requestAnimationFrame(this._listenCallback);
194
+ // To prevent framerate loss, make sure the value has changed before updating the display.
195
+ // Note: save() is used here instead of getValue() only because of ColorController. The !== operator
196
+ // won't work for color objects or arrays, but ColorController.save() always returns a string.
197
+ if (this.needsUpdateDisplay()) {
198
+ this.updateDisplay();
199
+ }
200
+ }
201
+ /**
202
+ * Returns `object[ property ]`.
203
+ * @returns {any}
204
+ */ getValue() {
205
+ return this.object[this.property];
206
+ }
207
+ /**
208
+ * Sets the value of `object[ property ]`, invokes any `onChange` handlers and updates the display.
209
+ * @param {any} value
210
+ * @param {boolean} callOnChange
211
+ * @returns {this}
212
+ */ setValue(value) {
213
+ this.object[this.property] = value;
214
+ this._callOnChange();
215
+ this.updateDisplay();
216
+ return this;
217
+ }
218
+ update() {
219
+ if (this.needsUpdateDisplay()) {
220
+ this.updateDisplay();
221
+ }
222
+ }
223
+ /**
224
+ * Updates the display to keep it in sync with the current value. Useful for updating your
225
+ * controllers when their values have been modified outside of the GUI.
226
+ * @returns {this}
227
+ */ updateDisplay() {
228
+ return this;
229
+ }
230
+ load(value) {
231
+ this.setValue(value);
232
+ this._callOnFinishChange();
233
+ return this;
234
+ }
235
+ save() {
236
+ return this.getValue();
237
+ }
238
+ /**
239
+ * Destroys this controller and removes it from the parent GUI.
240
+ */ destroy() {
241
+ this.listen(false);
242
+ this.parent.children.splice(this.parent.children.indexOf(this), 1);
243
+ this.parent.controllers.splice(this.parent.controllers.indexOf(this), 1);
244
+ this.parent.$children.removeChild(this.domElement);
245
+ }
246
+ constructor(parent, object, property, className, widgetTag = 'div'){
247
+ /**
248
+ * Used to determine if the controller is disabled.
249
+ * Use `controller.disable( true|false )` to modify this value
250
+ */ this._disabled = false;
251
+ /**
252
+ * Used to determine if the Controller is hidden.
253
+ * Use `controller.show()` or `controller.hide()` to change this.
254
+ */ this._hidden = false;
255
+ this._changed = false;
256
+ /**
257
+ * Used to determine if the controller is currently listening. Don't modify this value
258
+ * directly. Use the `controller.listen( true|false )` method instead.
259
+ */ this._listening = false;
260
+ this._listenCallbackID = -1;
261
+ /**
262
+ * The controller's name. Use `controller.name( 'Name' )` to modify this value.
263
+ */ this._name = "";
264
+ this.parent = parent;
265
+ this.object = object;
266
+ this.property = property;
267
+ this.initialValue = this.getValue();
268
+ this.domElement = document.createElement('div');
269
+ this.domElement.classList.add('controller');
270
+ this.domElement.classList.add(className);
271
+ this.$name = document.createElement('div');
272
+ this.$name.classList.add('name');
273
+ Controller.nextNameID = Controller.nextNameID || 0;
274
+ this.$name.id = `lil-gui-name-${++Controller.nextNameID}`;
275
+ /**
276
+ * The DOM element that contains the controller's "widget" (which differs by controller type).
277
+ * @type {HTMLElement}
278
+ */ this.$widget = document.createElement(widgetTag);
279
+ this.$widget.classList.add('widget');
280
+ this.$disable = this.$widget;
281
+ this.domElement.appendChild(this.$name);
282
+ this.domElement.appendChild(this.$widget);
283
+ // Don't fire global key events while typing in a controller
284
+ this.domElement.addEventListener('keydown', (e)=>e.stopPropagation());
285
+ this.domElement.addEventListener('keyup', (e)=>e.stopPropagation());
286
+ this.parent.children.push(this);
287
+ this.parent.controllers.push(this);
288
+ this.parent.$children.appendChild(this.domElement);
289
+ this._listenCallback = this._listenCallback.bind(this);
290
+ this.name(property);
291
+ }
292
+ }
293
+ Controller.nextNameID = 0;
294
+
295
+ class OptionController extends Controller {
296
+ updateDisplay() {
297
+ const value = this.getValue();
298
+ const index = this._values.indexOf(value);
299
+ this.$select.selectedIndex = index;
300
+ this.$display.innerHTML = index === -1 ? value : this._names[index];
301
+ return this;
302
+ }
303
+ options(options) {
304
+ this.$select.innerHTML = "";
305
+ this._values = Array.isArray(options) ? options : Object.values(options);
306
+ this._names = Array.isArray(options) ? options : Object.keys(options);
307
+ this._names.forEach((name)=>{
308
+ const $option = document.createElement('option');
309
+ $option.innerHTML = name;
310
+ this.$select.appendChild($option);
311
+ });
312
+ return this;
313
+ }
314
+ constructor(parent, object, property, options){
315
+ super(parent, object, property, 'option');
316
+ this.$select = document.createElement('select');
317
+ this.$select.setAttribute('aria-labelledby', this.$name.id);
318
+ this.$display = document.createElement('div');
319
+ this.$display.classList.add('display');
320
+ this._values = Array.isArray(options) ? options : Object.values(options);
321
+ this._names = Array.isArray(options) ? options : Object.keys(options);
322
+ this._names.forEach((name)=>{
323
+ const $option = document.createElement('option');
324
+ $option.innerHTML = name;
325
+ this.$select.appendChild($option);
326
+ });
327
+ this.$select.addEventListener('change', ()=>{
328
+ this.setValue(this._values[this.$select.selectedIndex]);
329
+ this._callOnFinishChange();
330
+ });
331
+ this.$select.addEventListener('focus', ()=>{
332
+ this.$display.classList.add('focus');
333
+ });
334
+ this.$select.addEventListener('blur', ()=>{
335
+ this.$display.classList.remove('focus');
336
+ });
337
+ this.$widget.appendChild(this.$select);
338
+ this.$widget.appendChild(this.$display);
339
+ this.$disable = this.$select;
340
+ }
341
+ }
342
+
343
+ /* eslint-disable no-cond-assign */ function normalizeColorString(string) {
344
+ let match, result;
345
+ if (match = string.match(/(#|0x)?([a-f0-9]{6})/i)) {
346
+ result = match[2];
347
+ } else if (match = string.match(/rgb\(\s*(\d*)\s*,\s*(\d*)\s*,\s*(\d*)\s*\)/)) {
348
+ result = parseInt(match[1]).toString(16).padStart(2, "0") + parseInt(match[2]).toString(16).padStart(2, "0") + parseInt(match[3]).toString(16).padStart(2, "0");
349
+ } else if (match = string.match(/^#?([a-f0-9])([a-f0-9])([a-f0-9])$/i)) {
350
+ result = match[1] + match[1] + match[2] + match[2] + match[3] + match[3];
351
+ }
352
+ if (result) {
353
+ return '#' + result;
354
+ }
355
+ return false;
356
+ }
357
+
358
+ const STRING = {
359
+ isPrimitive: true,
360
+ match: (v)=>typeof v === 'string',
361
+ fromHexString: normalizeColorString,
362
+ toHexString: normalizeColorString
363
+ };
364
+ const INT = {
365
+ isPrimitive: true,
366
+ match: (v)=>typeof v === 'number',
367
+ fromHexString: (string)=>parseInt(string.substring(1), 16),
368
+ toHexString: (value)=>'#' + value.toString(16).padStart(6, 0)
369
+ };
370
+ const ARRAY = {
371
+ isPrimitive: false,
372
+ // The arrow function is here to appease tree shakers like esbuild or webpack.
373
+ // See https://esbuild.github.io/api/#tree-shaking
374
+ match: (v)=>Array.isArray(v),
375
+ fromHexString (string, target, rgbScale = 1) {
376
+ const int = INT.fromHexString(string);
377
+ target[0] = (int >> 16 & 255) / 255 * rgbScale;
378
+ target[1] = (int >> 8 & 255) / 255 * rgbScale;
379
+ target[2] = (int & 255) / 255 * rgbScale;
380
+ },
381
+ toHexString ([r, g, b], rgbScale = 1) {
382
+ rgbScale = 255 / rgbScale;
383
+ const int = r * rgbScale << 16 ^ g * rgbScale << 8 ^ b * rgbScale << 0;
384
+ return INT.toHexString(int);
385
+ }
386
+ };
387
+ const OBJECT = {
388
+ isPrimitive: false,
389
+ match: (v)=>Object(v) === v,
390
+ fromHexString (string, target, rgbScale = 1) {
391
+ const int = INT.fromHexString(string);
392
+ target.r = (int >> 16 & 255) / 255 * rgbScale;
393
+ target.g = (int >> 8 & 255) / 255 * rgbScale;
394
+ target.b = (int & 255) / 255 * rgbScale;
395
+ },
396
+ toHexString ({ r, g, b }, rgbScale = 1) {
397
+ rgbScale = 255 / rgbScale;
398
+ const int = r * rgbScale << 16 ^ g * rgbScale << 8 ^ b * rgbScale << 0;
399
+ return INT.toHexString(int);
400
+ }
401
+ };
402
+ const FORMATS = [
403
+ STRING,
404
+ INT,
405
+ ARRAY,
406
+ OBJECT
407
+ ];
408
+ function getColorFormat(value) {
409
+ return FORMATS.find((format)=>format.match(value));
410
+ }
411
+
412
+ function getPrecision(value) {
413
+ let str = value.toString();
414
+ let index = str.indexOf('.');
415
+ if (index > 0) {
416
+ return str.length - index - 1;
417
+ }
418
+ return 0;
419
+ }
420
+ function hasDecimal(value) {
421
+ return value % 1 !== 0;
422
+ }
423
+
424
+ class NumberController extends Controller {
425
+ min(min) {
426
+ this._min = min;
427
+ this._onUpdateMinMax();
428
+ return this;
429
+ }
430
+ max(max) {
431
+ this._max = max;
432
+ this._onUpdateMinMax();
433
+ return this;
434
+ }
435
+ step(step, explicit = true) {
436
+ this._step = step;
437
+ this._stepExplicit = explicit;
438
+ return this;
439
+ }
440
+ needsUpdateDisplay() {
441
+ if (this.parent._closed) {
442
+ return false;
443
+ }
444
+ let value = this.getValue();
445
+ if (this._listenPrevValue === undefined || Math.abs(this._listenPrevValue - value) >= this._step) {
446
+ this._listenPrevValue = value;
447
+ return true;
448
+ }
449
+ return false;
450
+ }
451
+ updateDisplay() {
452
+ const value = this.getValue();
453
+ if (this._hasSlider) {
454
+ let percent = (value - this._min) / (this._max - this._min);
455
+ percent = Math.max(0, Math.min(percent, 1));
456
+ this.$fill.style.width = percent * 100 + '%';
457
+ }
458
+ if (!this._inputFocused) {
459
+ this.$input.value = value;
460
+ }
461
+ return this;
462
+ }
463
+ _initInput() {
464
+ this.$input = document.createElement('input');
465
+ this.$input.setAttribute('type', 'text');
466
+ this.$input.setAttribute('aria-labelledby', this.$name.id);
467
+ // On touch devices only, use input[type=number] to force a numeric keyboard.
468
+ // Ideally we could use one input type everywhere, but [type=number] has quirks
469
+ // on desktop, and [inputmode=decimal] has quirks on iOS.
470
+ // See https://github.com/georgealways/lil-gui/pull/16
471
+ const isTouch = window.matchMedia('(pointer: coarse)').matches;
472
+ if (isTouch) {
473
+ this.$input.setAttribute('type', 'number');
474
+ this.$input.setAttribute('step', 'any');
475
+ }
476
+ this.$widget.appendChild(this.$input);
477
+ this.$disable = this.$input;
478
+ const onInput = ()=>{
479
+ let value = parseFloat(this.$input.value);
480
+ if (isNaN(value)) return;
481
+ if (this._stepExplicit) {
482
+ value = this._snap(value);
483
+ }
484
+ this.setValue(this._clamp(value));
485
+ };
486
+ // Keys & mouse wheel
487
+ // ---------------------------------------------------------------------
488
+ const increment = (delta)=>{
489
+ const value = parseFloat(this.$input.value);
490
+ if (isNaN(value)) return;
491
+ this._snapClampSetValue(value + delta);
492
+ // Force the input to updateDisplay when it's focused
493
+ this.$input.value = this.getValue();
494
+ };
495
+ const onKeyDown = (e)=>{
496
+ // Using `e.key` instead of `e.code` also catches NumpadEnter
497
+ if (e.key === 'Enter') {
498
+ this.$input.blur();
499
+ }
500
+ if (e.code === 'ArrowUp') {
501
+ e.preventDefault();
502
+ increment(this._step * this._arrowKeyMultiplier(e));
503
+ }
504
+ if (e.code === 'ArrowDown') {
505
+ e.preventDefault();
506
+ increment(this._step * this._arrowKeyMultiplier(e) * -1);
507
+ }
508
+ };
509
+ const onWheel = (e)=>{
510
+ if (this._inputFocused) {
511
+ e.preventDefault();
512
+ increment(this._step * this._normalizeMouseWheel(e));
513
+ }
514
+ };
515
+ // Vertical drag
516
+ // ---------------------------------------------------------------------
517
+ let testingForVerticalDrag = false, initClientX, initClientY, prevClientY, initValue, dragDelta;
518
+ // Once the mouse is dragged more than DRAG_THRESH px on any axis, we decide
519
+ // on the user's intent: horizontal means highlight, vertical means drag.
520
+ const DRAG_THRESH = 5;
521
+ const onMouseDown = (e)=>{
522
+ initClientX = e.clientX;
523
+ initClientY = prevClientY = e.clientY;
524
+ testingForVerticalDrag = true;
525
+ initValue = this.getValue();
526
+ dragDelta = 0;
527
+ window.addEventListener('mousemove', onMouseMove);
528
+ window.addEventListener('mouseup', onMouseUp);
529
+ };
530
+ const onMouseMove = (e)=>{
531
+ if (testingForVerticalDrag) {
532
+ const dx = e.clientX - initClientX;
533
+ const dy = e.clientY - initClientY;
534
+ if (Math.abs(dy) > DRAG_THRESH) {
535
+ e.preventDefault();
536
+ this.$input.blur();
537
+ testingForVerticalDrag = false;
538
+ this._setDraggingStyle(true, 'vertical');
539
+ } else if (Math.abs(dx) > DRAG_THRESH) {
540
+ onMouseUp();
541
+ }
542
+ }
543
+ // This isn't an else so that the first move counts towards dragDelta
544
+ if (!testingForVerticalDrag) {
545
+ const dy = e.clientY - prevClientY;
546
+ dragDelta -= dy * this._step * this._arrowKeyMultiplier(e);
547
+ // Clamp dragDelta so we don't have 'dead space' after dragging past bounds.
548
+ // We're okay with the fact that bounds can be undefined here.
549
+ if (initValue + dragDelta > this._max) {
550
+ dragDelta = this._max - initValue;
551
+ } else if (initValue + dragDelta < this._min) {
552
+ dragDelta = this._min - initValue;
553
+ }
554
+ this._snapClampSetValue(initValue + dragDelta);
555
+ }
556
+ prevClientY = e.clientY;
557
+ };
558
+ const onMouseUp = ()=>{
559
+ this._setDraggingStyle(false, 'vertical');
560
+ this._callOnFinishChange();
561
+ window.removeEventListener('mousemove', onMouseMove);
562
+ window.removeEventListener('mouseup', onMouseUp);
563
+ };
564
+ // Focus state & onFinishChange
565
+ // ---------------------------------------------------------------------
566
+ const onFocus = ()=>{
567
+ this._inputFocused = true;
568
+ };
569
+ const onBlur = ()=>{
570
+ this._inputFocused = false;
571
+ this.updateDisplay();
572
+ this._callOnFinishChange();
573
+ };
574
+ this.$input.addEventListener('input', onInput);
575
+ this.$input.addEventListener('keydown', onKeyDown);
576
+ this.$input.addEventListener('wheel', onWheel, {
577
+ passive: false
578
+ });
579
+ this.$input.addEventListener('mousedown', onMouseDown);
580
+ this.$input.addEventListener('focus', onFocus);
581
+ this.$input.addEventListener('blur', onBlur);
582
+ }
583
+ _initSlider() {
584
+ this._hasSlider = true;
585
+ // Build DOM
586
+ // ---------------------------------------------------------------------
587
+ this.$slider = document.createElement('div');
588
+ this.$slider.classList.add('slider');
589
+ this.$fill = document.createElement('div');
590
+ this.$fill.classList.add('fill');
591
+ this.$slider.appendChild(this.$fill);
592
+ this.$widget.insertBefore(this.$slider, this.$input);
593
+ this.domElement.classList.add('hasSlider');
594
+ // Map clientX to value
595
+ // ---------------------------------------------------------------------
596
+ const map = (v, a, b, c, d)=>{
597
+ return (v - a) / (b - a) * (d - c) + c;
598
+ };
599
+ const setValueFromX = (clientX)=>{
600
+ const rect = this.$slider.getBoundingClientRect();
601
+ let value = map(clientX, rect.left, rect.right, this._min, this._max);
602
+ this._snapClampSetValue(value);
603
+ };
604
+ // Mouse drag
605
+ // ---------------------------------------------------------------------
606
+ const mouseDown = (e)=>{
607
+ this._setDraggingStyle(true);
608
+ setValueFromX(e.clientX);
609
+ window.addEventListener('mousemove', mouseMove);
610
+ window.addEventListener('mouseup', mouseUp);
611
+ };
612
+ const mouseMove = (e)=>{
613
+ setValueFromX(e.clientX);
614
+ };
615
+ const mouseUp = ()=>{
616
+ this._callOnFinishChange();
617
+ this._setDraggingStyle(false);
618
+ window.removeEventListener('mousemove', mouseMove);
619
+ window.removeEventListener('mouseup', mouseUp);
620
+ };
621
+ // Touch drag
622
+ // ---------------------------------------------------------------------
623
+ let testingForScroll = false, prevClientX, prevClientY;
624
+ const beginTouchDrag = (e)=>{
625
+ e.preventDefault();
626
+ this._setDraggingStyle(true);
627
+ setValueFromX(e.touches[0].clientX);
628
+ testingForScroll = false;
629
+ };
630
+ const onTouchStart = (e)=>{
631
+ if (e.touches.length > 1) return;
632
+ // If we're in a scrollable container, we should wait for the first
633
+ // touchmove to see if the user is trying to slide or scroll.
634
+ if (this._hasScrollBar) {
635
+ prevClientX = e.touches[0].clientX;
636
+ prevClientY = e.touches[0].clientY;
637
+ testingForScroll = true;
638
+ } else {
639
+ // Otherwise, we can set the value straight away on touchstart.
640
+ beginTouchDrag(e);
641
+ }
642
+ window.addEventListener('touchmove', onTouchMove, {
643
+ passive: false
644
+ });
645
+ window.addEventListener('touchend', onTouchEnd);
646
+ };
647
+ const onTouchMove = (e)=>{
648
+ if (testingForScroll) {
649
+ const dx = e.touches[0].clientX - prevClientX;
650
+ const dy = e.touches[0].clientY - prevClientY;
651
+ if (Math.abs(dx) > Math.abs(dy)) {
652
+ // We moved horizontally, set the value and stop checking.
653
+ beginTouchDrag(e);
654
+ } else {
655
+ // This was, in fact, an attempt to scroll. Abort.
656
+ window.removeEventListener('touchmove', onTouchMove);
657
+ window.removeEventListener('touchend', onTouchEnd);
658
+ }
659
+ } else {
660
+ e.preventDefault();
661
+ setValueFromX(e.touches[0].clientX);
662
+ }
663
+ };
664
+ const onTouchEnd = ()=>{
665
+ this._callOnFinishChange();
666
+ this._setDraggingStyle(false);
667
+ window.removeEventListener('touchmove', onTouchMove);
668
+ window.removeEventListener('touchend', onTouchEnd);
669
+ };
670
+ // Mouse wheel
671
+ // ---------------------------------------------------------------------
672
+ // We have to use a debounced function to call onFinishChange because
673
+ // there's no way to tell when the user is "done" mouse-wheeling.
674
+ const callOnFinishChange = this._callOnFinishChange.bind(this);
675
+ const WHEEL_DEBOUNCE_TIME = 400;
676
+ let wheelFinishChangeTimeout;
677
+ const onWheel = (e)=>{
678
+ // ignore vertical wheels if there's a scrollbar
679
+ const isVertical = Math.abs(e.deltaX) < Math.abs(e.deltaY);
680
+ if (isVertical && this._hasScrollBar) return;
681
+ e.preventDefault();
682
+ // set value
683
+ const delta = this._normalizeMouseWheel(e) * this._step;
684
+ this._snapClampSetValue(this.getValue() + delta);
685
+ // force the input to updateDisplay when it's focused
686
+ this.$input.value = this.getValue();
687
+ // debounce onFinishChange
688
+ clearTimeout(wheelFinishChangeTimeout);
689
+ wheelFinishChangeTimeout = setTimeout(callOnFinishChange, WHEEL_DEBOUNCE_TIME);
690
+ };
691
+ this.$slider.addEventListener('mousedown', mouseDown);
692
+ this.$slider.addEventListener('touchstart', onTouchStart, {
693
+ passive: false
694
+ });
695
+ this.$slider.addEventListener('wheel', onWheel, {
696
+ passive: false
697
+ });
698
+ }
699
+ _setDraggingStyle(active, axis = 'horizontal') {
700
+ if (this.$slider) {
701
+ this.$slider.classList.toggle('active', active);
702
+ }
703
+ document.body.classList.toggle('lil-gui-dragging', active);
704
+ document.body.classList.toggle(`lil-gui-${axis}`, active);
705
+ }
706
+ _getImplicitStep() {
707
+ const value = this.getValue();
708
+ if (this._hasMin && this._hasMax) {
709
+ return (this._max - this._min) / 100;
710
+ } else if (hasDecimal(value)) {
711
+ return Math.pow(0.1, getPrecision(value));
712
+ }
713
+ return 0.1;
714
+ }
715
+ _onUpdateMinMax() {
716
+ if (!this._hasSlider && this._hasMin && this._hasMax) {
717
+ // If this is the first time we're hearing about min and max
718
+ // and we haven't explicitly stated what our step is, let's
719
+ // update that too.
720
+ if (!this._stepExplicit) {
721
+ this.step(this._getImplicitStep(), false);
722
+ }
723
+ this._initSlider();
724
+ this.updateDisplay();
725
+ }
726
+ }
727
+ _normalizeMouseWheel(e) {
728
+ let { deltaX, deltaY } = e;
729
+ // Safari and Chrome report weird non-integral values for a notched wheel,
730
+ // but still expose actual lines scrolled via wheelDelta. Notched wheels
731
+ // should behave the same way as arrow keys.
732
+ if (Math.floor(e.deltaY) !== e.deltaY && e.wheelDelta) {
733
+ deltaX = 0;
734
+ deltaY = -e.wheelDelta / 120;
735
+ deltaY *= this._stepExplicit ? 1 : 10;
736
+ }
737
+ const wheel = deltaX + -deltaY;
738
+ return wheel;
739
+ }
740
+ _arrowKeyMultiplier(e) {
741
+ let mult = this._stepExplicit ? 1 : 10;
742
+ if (e.shiftKey) {
743
+ mult *= 10;
744
+ } else if (e.altKey) {
745
+ mult /= 10;
746
+ }
747
+ return mult;
748
+ }
749
+ _snap(value) {
750
+ // This would be the logical way to do things, but floating point errors.
751
+ // return Math.round( value / this._step ) * this._step;
752
+ // Using inverse step solves a lot of them, but not all
753
+ // const inverseStep = 1 / this._step;
754
+ // return Math.round( value * inverseStep ) / inverseStep;
755
+ // Not happy about this, but haven't seen it break.
756
+ const r = Math.round(value / this._step) * this._step;
757
+ return parseFloat(r.toPrecision(15));
758
+ }
759
+ _clamp(value) {
760
+ // either condition is false if min or max is undefined
761
+ if (value < this._min) value = this._min;
762
+ if (value > this._max) value = this._max;
763
+ return value;
764
+ }
765
+ _snapClampSetValue(value) {
766
+ this.setValue(this._clamp(this._snap(value)));
767
+ }
768
+ get _hasScrollBar() {
769
+ const root = this.parent.root.$children;
770
+ return root.scrollHeight > root.clientHeight;
771
+ }
772
+ get _hasMin() {
773
+ return this._min !== undefined;
774
+ }
775
+ get _hasMax() {
776
+ return this._max !== undefined;
777
+ }
778
+ constructor(parent, object, property, min, max, step){
779
+ super(parent, object, property, 'number');
780
+ this._initInput();
781
+ this.min(min);
782
+ this.max(max);
783
+ const stepExplicit = step !== undefined;
784
+ this.step(stepExplicit ? step : this._getImplicitStep(), stepExplicit);
785
+ }
786
+ }
787
+
788
+ class BooleanController extends Controller {
789
+ updateDisplay() {
790
+ this.$input.checked = this.getValue();
791
+ return this;
792
+ }
793
+ constructor(parent, object, property){
794
+ super(parent, object, property, 'boolean', 'label');
795
+ this.$input = document.createElement('input');
796
+ this.$input.setAttribute('type', 'checkbox');
797
+ this.$input.setAttribute('aria-labelledby', this.$name.id);
798
+ this.$widget.appendChild(this.$input);
799
+ this.$input.addEventListener('change', ()=>{
800
+ this.setValue(this.$input.checked);
801
+ this._callOnFinishChange();
802
+ });
803
+ this.$disable = this.$input;
804
+ }
805
+ }
806
+
807
+ class StringController extends Controller {
808
+ updateDisplay() {
809
+ this.$input.value = this.getValue();
810
+ return this;
811
+ }
812
+ constructor(parent, object, property){
813
+ super(parent, object, property, 'string');
814
+ this.$input = document.createElement('input');
815
+ this.$input.setAttribute('type', 'text');
816
+ this.$input.setAttribute('aria-labelledby', this.$name.id);
817
+ this.$input.addEventListener('input', ()=>{
818
+ this.setValue(this.$input.value);
819
+ });
820
+ this.$input.addEventListener('keydown', (e)=>{
821
+ if (e.code === 'Enter') {
822
+ this.$input.blur();
823
+ }
824
+ });
825
+ this.$input.addEventListener('blur', ()=>{
826
+ this._callOnFinishChange();
827
+ });
828
+ this.$widget.appendChild(this.$input);
829
+ this.$disable = this.$input;
830
+ }
831
+ }
832
+
833
+ class FunctionController extends Controller {
834
+ constructor(parent, object, property){
835
+ super(parent, object, property, 'function');
836
+ // Buttons are the only case where widget contains name
837
+ this.$button = document.createElement('button');
838
+ this.$button.appendChild(this.$name);
839
+ this.$widget.appendChild(this.$button);
840
+ this.$button.addEventListener('click', (e)=>{
841
+ e.preventDefault();
842
+ this.getValue().call(this.object);
843
+ this._callOnChange();
844
+ });
845
+ // enables :active pseudo class on mobile
846
+ this.$button.addEventListener('touchstart', ()=>{}, {
847
+ passive: true
848
+ });
849
+ this.$disable = this.$button;
850
+ }
851
+ }
852
+
853
+ class ColorController extends Controller {
854
+ reset() {
855
+ this._setValueFromHexString(this._initialValueHexString);
856
+ return this;
857
+ }
858
+ _setValueFromHexString(value) {
859
+ if (this._format.isPrimitive) {
860
+ const newValue = this._format.fromHexString(value);
861
+ this.setValue(newValue);
862
+ } else {
863
+ this._format.fromHexString(value, this.getValue(), this._rgbScale);
864
+ this._callOnChange();
865
+ this.updateDisplay();
866
+ }
867
+ }
868
+ save() {
869
+ return this._format.toHexString(this.getValue(), this._rgbScale);
870
+ }
871
+ load(value) {
872
+ this._setValueFromHexString(value);
873
+ this._callOnFinishChange();
874
+ return this;
875
+ }
876
+ updateDisplay() {
877
+ this.$input.value = this._format.toHexString(this.getValue(), this._rgbScale);
878
+ if (!this._textFocused) {
879
+ this.$text.value = this.$input.value.substring(1);
880
+ }
881
+ this.$display.style.backgroundColor = this.$input.value;
882
+ return this;
883
+ }
884
+ constructor(parent, object, property, rgbScale){
885
+ super(parent, object, property, 'color');
886
+ this._textFocused = false;
887
+ this._initialValueHexString = "";
888
+ this._rgbScale = 1;
889
+ this.$input = document.createElement('input');
890
+ this.$input.setAttribute('type', 'color');
891
+ this.$input.setAttribute('tabindex', "-1");
892
+ this.$input.setAttribute('aria-labelledby', this.$name.id);
893
+ this.$text = document.createElement('input');
894
+ this.$text.setAttribute('type', 'text');
895
+ this.$text.setAttribute('spellcheck', 'false');
896
+ this.$text.setAttribute('aria-labelledby', this.$name.id);
897
+ this.$display = document.createElement('div');
898
+ this.$display.classList.add('display');
899
+ this.$display.appendChild(this.$input);
900
+ this.$widget.appendChild(this.$display);
901
+ this.$widget.appendChild(this.$text);
902
+ this._format = getColorFormat(this.initialValue);
903
+ this._rgbScale = rgbScale;
904
+ this._initialValueHexString = this.save();
905
+ this._textFocused = false;
906
+ this.$input.addEventListener('input', ()=>{
907
+ this._setValueFromHexString(this.$input.value);
908
+ });
909
+ this.$input.addEventListener('blur', ()=>{
910
+ this._callOnFinishChange();
911
+ });
912
+ this.$text.addEventListener('input', ()=>{
913
+ const tryParse = normalizeColorString(this.$text.value);
914
+ if (tryParse) {
915
+ this._setValueFromHexString(tryParse);
916
+ }
917
+ });
918
+ this.$text.addEventListener('focus', ()=>{
919
+ this._textFocused = true;
920
+ this.$text.select();
921
+ });
922
+ this.$text.addEventListener('blur', ()=>{
923
+ this._textFocused = false;
924
+ this.updateDisplay();
925
+ this._callOnFinishChange();
926
+ });
927
+ this.$disable = this.$text;
928
+ this.listen();
929
+ }
930
+ }
931
+
932
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
933
+ try {
934
+ var info = gen[key](arg);
935
+ var value = info.value;
936
+ } catch (error) {
937
+ reject(error);
938
+ return;
939
+ }
940
+ if (info.done) resolve(value);
941
+ else Promise.resolve(value).then(_next, _throw);
942
+ }
943
+ function _async_to_generator(fn) {
944
+ return function() {
945
+ var self = this, args = arguments;
946
+
947
+ return new Promise(function(resolve, reject) {
948
+ var gen = fn.apply(self, args);
949
+
950
+ function _next(value) {
951
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
952
+ }
953
+
954
+ function _throw(err) {
955
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
956
+ }
957
+
958
+ _next(undefined);
959
+ });
960
+ };
961
+ }
962
+
963
+ class ImageController extends Controller {
964
+ static toDataURL(img) {
965
+ return _async_to_generator(function*() {
966
+ if (img instanceof HTMLImageElement) {
967
+ return img.src;
968
+ } else {
969
+ if (img) {
970
+ let url = ImageController._imageMap.get(img);
971
+ if (url) return url;
972
+ const w = Math.min(img.width, 256);
973
+ const h = Math.min(img.height, 256);
974
+ if (ImageController._context == null) {
975
+ ImageController._context = document.createElement('canvas').getContext("2d");
976
+ }
977
+ ImageController._context.canvas.width = w;
978
+ ImageController._context.canvas.height = h;
979
+ if (img.data) {
980
+ let imageData = ImageController._context.createImageData(img.width, img.height);
981
+ imageData.data.set(img.data);
982
+ img = yield createImageBitmap(imageData);
983
+ }
984
+ if (img instanceof ImageBitmap) {
985
+ ImageController._context.drawImage(img, 0, 0, img.width, img.height, 0, 0, w, h);
986
+ ImageController._imageMap.set(img, url = ImageController._context.canvas.toDataURL());
987
+ return url;
988
+ }
989
+ }
990
+ return "";
991
+ }
992
+ })();
993
+ }
994
+ updateDisplay() {
995
+ ImageController.toDataURL(this.getValue()).then((url)=>this.$img.src = url);
996
+ return this;
997
+ }
998
+ constructor(parent, object, property){
999
+ super(parent, object, property, "image");
1000
+ this.$img = document.createElement("img");
1001
+ this.$widget.appendChild(this.$img);
1002
+ this.updateDisplay();
1003
+ }
1004
+ }
1005
+ ImageController._context = null;
1006
+ ImageController._imageMap = new WeakMap();
1007
+
1008
+ class UIElement {
1009
+ add(...args) {
1010
+ for(let i = 0; i < args.length; i++){
1011
+ const argument = args[i];
1012
+ if (argument instanceof UIElement) {
1013
+ this.dom.appendChild(argument.dom);
1014
+ } else {
1015
+ console.error('UIElement:', argument, 'is not an instance of UIElement.');
1016
+ }
1017
+ }
1018
+ return this;
1019
+ }
1020
+ remove(...args) {
1021
+ for(let i = 0; i < args.length; i++){
1022
+ const argument = args[i];
1023
+ if (argument instanceof UIElement) {
1024
+ this.dom.removeChild(argument.dom);
1025
+ } else {
1026
+ console.error('UIElement:', argument, 'is not an instance of UIElement.');
1027
+ }
1028
+ }
1029
+ return this;
1030
+ }
1031
+ clear() {
1032
+ while(this.dom.children.length){
1033
+ this.dom.removeChild(this.dom.lastChild);
1034
+ }
1035
+ }
1036
+ setId(id) {
1037
+ this.dom.id = id;
1038
+ return this;
1039
+ }
1040
+ getId() {
1041
+ return this.dom.id;
1042
+ }
1043
+ setClass(name) {
1044
+ this.dom.className = name;
1045
+ return this;
1046
+ }
1047
+ addClass(name) {
1048
+ this.dom.classList.add(name);
1049
+ return this;
1050
+ }
1051
+ removeClass(name) {
1052
+ this.dom.classList.remove(name);
1053
+ return this;
1054
+ }
1055
+ setStyle(style, array) {
1056
+ for(let i = 0; i < array.length; i++){
1057
+ this.dom.style[style] = array[i];
1058
+ }
1059
+ return this;
1060
+ }
1061
+ setDisabled(value) {
1062
+ this.dom.disabled = value;
1063
+ return this;
1064
+ }
1065
+ setTextContent(value) {
1066
+ this.dom.textContent = value;
1067
+ return this;
1068
+ }
1069
+ getIndexOfChild(element) {
1070
+ return Array.prototype.indexOf.call(this.dom.children, element.dom);
1071
+ }
1072
+ constructor(dom){
1073
+ this.dom = dom;
1074
+ }
1075
+ }
1076
+
1077
+ class UINumber extends UIElement {
1078
+ equals(v) {
1079
+ return Math.abs(this.value - v) <= this.step;
1080
+ }
1081
+ getValue() {
1082
+ return this.value;
1083
+ }
1084
+ setValue(value) {
1085
+ if (value !== undefined) {
1086
+ if (value < this.min) value = this.min;
1087
+ if (value > this.max) value = this.max;
1088
+ this.value = value;
1089
+ this.updateDisplay();
1090
+ this._onChangeCallback && this._onChangeCallback(value);
1091
+ }
1092
+ return this;
1093
+ }
1094
+ updateDisplay() {
1095
+ let value = this.getValue();
1096
+ this.dom.value = "" + this._snap(value);
1097
+ if (this.unit !== '') this.dom.value += ' ' + this.unit;
1098
+ }
1099
+ _getImplicitStep(value) {
1100
+ if (this.max !== Infinity && this.min !== -Infinity) {
1101
+ return (this.max - this.min) / 100;
1102
+ } else if (hasDecimal(value)) {
1103
+ return 1 / Math.pow(10, getPrecision(value));
1104
+ }
1105
+ return 0.1;
1106
+ }
1107
+ _snap(value) {
1108
+ const r = Math.round(value / this.step) * this.step;
1109
+ return parseFloat(r.toPrecision(15));
1110
+ }
1111
+ onChange(callback) {
1112
+ this._onChangeCallback = callback;
1113
+ return this;
1114
+ }
1115
+ setUnit(unit) {
1116
+ this.unit = unit;
1117
+ return this;
1118
+ }
1119
+ constructor(value, min, max, step){
1120
+ super(document.createElement('input'));
1121
+ this.value = 0;
1122
+ this.min = -Infinity;
1123
+ this.max = Infinity;
1124
+ this.step = 1;
1125
+ this.nudge = 1;
1126
+ this.unit = "";
1127
+ this.dom.style.cursor = 'ns-resize';
1128
+ this.dom.value = '0.00';
1129
+ this.min = min != null ? min : this.min;
1130
+ this.max = max != null ? max : this.max;
1131
+ this.step = step != null ? step : this._getImplicitStep(value);
1132
+ this.setValue(value);
1133
+ const scope = this;
1134
+ const changeEvent = document.createEvent('HTMLEvents');
1135
+ changeEvent.initEvent('change', true, true);
1136
+ let distance = 0;
1137
+ let onMouseDownValue = 0;
1138
+ const pointer = {
1139
+ x: 0,
1140
+ y: 0
1141
+ };
1142
+ const prevPointer = {
1143
+ x: 0,
1144
+ y: 0
1145
+ };
1146
+ function onMouseDown(event) {
1147
+ event.preventDefault();
1148
+ distance = 0;
1149
+ onMouseDownValue = scope.value;
1150
+ prevPointer.x = event.clientX;
1151
+ prevPointer.y = event.clientY;
1152
+ document.addEventListener('mousemove', onMouseMove, false);
1153
+ document.addEventListener('mouseup', onMouseUp, false);
1154
+ }
1155
+ function onMouseMove(event) {
1156
+ const currentValue = scope.value;
1157
+ pointer.x = event.clientX;
1158
+ pointer.y = event.clientY;
1159
+ distance += pointer.x - prevPointer.x - (pointer.y - prevPointer.y);
1160
+ let value = onMouseDownValue + distance * 10 / (event.shiftKey ? 5 : 50) * scope.step;
1161
+ value = Math.min(scope.max, Math.max(scope.min, value));
1162
+ if (currentValue !== value) {
1163
+ scope.setValue(value);
1164
+ scope.dom.dispatchEvent(changeEvent);
1165
+ }
1166
+ prevPointer.x = event.clientX;
1167
+ prevPointer.y = event.clientY;
1168
+ }
1169
+ function onMouseUp() {
1170
+ document.removeEventListener('mousemove', onMouseMove, false);
1171
+ document.removeEventListener('mouseup', onMouseUp, false);
1172
+ if (Math.abs(distance) < 2) {
1173
+ scope.dom.focus();
1174
+ scope.dom.select();
1175
+ }
1176
+ }
1177
+ function onTouchStart(event) {
1178
+ if (event.touches.length === 1) {
1179
+ distance = 0;
1180
+ onMouseDownValue = scope.value;
1181
+ prevPointer.x = event.touches[0].pageX;
1182
+ prevPointer.y = event.touches[0].pageY;
1183
+ document.addEventListener('touchmove', onTouchMove, false);
1184
+ document.addEventListener('touchend', onTouchEnd, false);
1185
+ }
1186
+ }
1187
+ function onTouchMove(event) {
1188
+ const currentValue = scope.value;
1189
+ pointer.x = event.touches[0].pageX;
1190
+ pointer.y = event.touches[0].pageY;
1191
+ distance += pointer.x - prevPointer.x - (pointer.y - prevPointer.y);
1192
+ let value = onMouseDownValue + distance / (event.shiftKey ? 5 : 50) * scope.step;
1193
+ value = Math.min(scope.max, Math.max(scope.min, value));
1194
+ if (currentValue !== value) {
1195
+ scope.setValue(value);
1196
+ scope.dom.dispatchEvent(changeEvent);
1197
+ }
1198
+ prevPointer.x = event.touches[0].pageX;
1199
+ prevPointer.y = event.touches[0].pageY;
1200
+ }
1201
+ function onTouchEnd(event) {
1202
+ if (event.touches.length === 0) {
1203
+ document.removeEventListener('touchmove', onTouchMove, false);
1204
+ document.removeEventListener('touchend', onTouchEnd, false);
1205
+ }
1206
+ }
1207
+ function onChange() {
1208
+ scope.setValue(parseFloat(scope.dom.value));
1209
+ }
1210
+ function onFocus() {
1211
+ scope.dom.style.backgroundColor = '';
1212
+ scope.dom.style.cursor = '';
1213
+ }
1214
+ function onBlur() {
1215
+ // scope.dom.style.backgroundColor = 'transparent';
1216
+ scope.dom.style.cursor = 'ns-resize';
1217
+ }
1218
+ function onKeyDown(event) {
1219
+ event.stopPropagation();
1220
+ switch(event.keyCode){
1221
+ case 13:
1222
+ scope.dom.blur();
1223
+ break;
1224
+ case 38:
1225
+ event.preventDefault();
1226
+ scope.setValue(scope.getValue() + scope.step);
1227
+ scope.dom.dispatchEvent(changeEvent);
1228
+ break;
1229
+ case 40:
1230
+ event.preventDefault();
1231
+ scope.setValue(scope.getValue() - scope.step);
1232
+ scope.dom.dispatchEvent(changeEvent);
1233
+ break;
1234
+ }
1235
+ }
1236
+ onBlur();
1237
+ this.dom.addEventListener('keydown', onKeyDown, false);
1238
+ this.dom.addEventListener('mousedown', onMouseDown, false);
1239
+ this.dom.addEventListener('touchstart', onTouchStart, {
1240
+ passive: true
1241
+ });
1242
+ this.dom.addEventListener('change', onChange, false);
1243
+ this.dom.addEventListener('focus', onFocus, false);
1244
+ this.dom.addEventListener('blur', onBlur, false);
1245
+ }
1246
+ }
1247
+
1248
+ class VectorController extends Controller {
1249
+ createElement(fill, value, min, max, step, onChange) {
1250
+ let uiNumber = new UINumber(value, min, max, step);
1251
+ uiNumber.dom.setAttribute('type', 'number');
1252
+ uiNumber.dom.setAttribute('aria-labelledby', this.$name.id);
1253
+ uiNumber.onChange(onChange);
1254
+ this.$widget.appendChild(uiNumber.dom);
1255
+ if (fill) {
1256
+ let $fill = document.createElement('div');
1257
+ $fill.classList.add('fill');
1258
+ this.$widget.appendChild($fill);
1259
+ }
1260
+ return uiNumber;
1261
+ }
1262
+ setValue(value) {
1263
+ let target = this.getValue();
1264
+ if (value.x !== undefined) target.x = value.x;
1265
+ if (value.y !== undefined) target.y = value.y;
1266
+ if (value.z !== undefined) target.z = value.z;
1267
+ if (value.w !== undefined) target.w = value.w;
1268
+ this._callOnChange();
1269
+ return this;
1270
+ }
1271
+ needsUpdateDisplay() {
1272
+ return !this.parent._closed;
1273
+ }
1274
+ updateDisplay() {
1275
+ let value = this.getValue();
1276
+ if (value.x !== undefined && this.xElement && !this.xElement.equals(value.x)) {
1277
+ this.xElement.value = value.x;
1278
+ this.xElement.updateDisplay();
1279
+ }
1280
+ if (value.y !== undefined && this.yElement && !this.yElement.equals(value.y)) {
1281
+ this.yElement.value = value.y;
1282
+ this.yElement.updateDisplay();
1283
+ }
1284
+ if (value.z !== undefined && this.zElement && !this.zElement.equals(value.z)) {
1285
+ this.zElement.value = value.z;
1286
+ this.zElement.updateDisplay();
1287
+ }
1288
+ if (value.w !== undefined && this.wElement && !this.wElement.equals(value.w)) {
1289
+ this.wElement.value = value.w;
1290
+ this.wElement.updateDisplay();
1291
+ }
1292
+ return this;
1293
+ }
1294
+ constructor(parent, object, property, min, max, step){
1295
+ super(parent, object, property, 'vector');
1296
+ const value = this.getValue();
1297
+ this.initialValue = value.clone && value.clone() || Object.assign({}, value);
1298
+ if (this.initialValue.x !== undefined) this.xElement = this.createElement(true, this.initialValue.x, min, max, step, (v)=>{
1299
+ value.x = v;
1300
+ this._callOnChange();
1301
+ });
1302
+ if (this.initialValue.y !== undefined) this.yElement = this.createElement(true, this.initialValue.y, min, max, step, (v)=>{
1303
+ value.y = v;
1304
+ this._callOnChange();
1305
+ });
1306
+ if (this.initialValue.z !== undefined) this.zElement = this.createElement(true, this.initialValue.z, min, max, step, (v)=>{
1307
+ value.z = v;
1308
+ this._callOnChange();
1309
+ });
1310
+ if (this.initialValue.w !== undefined) this.wElement = this.createElement(false, this.initialValue.w, min, max, step, (v)=>{
1311
+ value.w = v;
1312
+ this._callOnChange();
1313
+ });
1314
+ }
1315
+ }
1316
+
1317
+ function clamp(v, minv, maxv) {
1318
+ return Math.max(Math.min(v, maxv), minv);
1319
+ }
1320
+ function hitTest(x, y, cx, cy, hw, hh) {
1321
+ if (x < cx - hw || x > cx + hw || y < cy - hh || y > cy + hh) {
1322
+ return false;
1323
+ }
1324
+ return true;
1325
+ }
1326
+ const theme = {
1327
+ BACKGROUND: "#545454",
1328
+ GRID: "#646464",
1329
+ LINE: "#000000",
1330
+ COVER: "#19191980",
1331
+ POINT: "#878787",
1332
+ SELECTED: "#ffffff"
1333
+ };
1334
+ class CurvePanel {
1335
+ get dom() {
1336
+ return this._canvas;
1337
+ }
1338
+ get curve() {
1339
+ return this._curve;
1340
+ }
1341
+ resize(width, height) {
1342
+ const canvas = this._canvas;
1343
+ const DPR = window.devicePixelRatio || 1;
1344
+ const xRes = (width != null ? width : canvas.offsetWidth) * DPR;
1345
+ const yRes = (height != null ? height : canvas.offsetHeight) * DPR;
1346
+ if (Math.hypot(xRes - this._xRes, yRes - this._yRes) > 0.001) {
1347
+ this._xRes = canvas.width = xRes;
1348
+ this._yRes = canvas.height = yRes;
1349
+ }
1350
+ }
1351
+ onCurveChanged(callback) {
1352
+ this._onCurveChanged = callback;
1353
+ return this;
1354
+ }
1355
+ onPointChanged(callback) {
1356
+ this._onPointChanged = callback;
1357
+ return this;
1358
+ }
1359
+ onPointRemoved(callback) {
1360
+ this._onPointRemoved = callback;
1361
+ return this;
1362
+ }
1363
+ onPointAdded(callback) {
1364
+ this._onPointAdded = callback;
1365
+ return this;
1366
+ }
1367
+ _onDblClick(e) {
1368
+ e.preventDefault();
1369
+ if (e.button === 0) {
1370
+ this._tryAddingPoint = true;
1371
+ }
1372
+ }
1373
+ _onMouseDown(e) {
1374
+ e.preventDefault();
1375
+ if (e.button == 0) {
1376
+ this._cxRef = this._cx;
1377
+ this._cyRef = this._cy;
1378
+ this._offsetXRef = e.offsetX;
1379
+ this._offsetYRef = e.offsetY;
1380
+ this._trySelectingPoint = true;
1381
+ }
1382
+ this._button = e.button;
1383
+ this._mouse.x = e.offsetX;
1384
+ this._mouse.y = e.offsetY;
1385
+ this._selectedPoint = null;
1386
+ }
1387
+ _onMouseUp(e) {
1388
+ e.preventDefault();
1389
+ this._button = -1;
1390
+ }
1391
+ _onMouseMove(e) {
1392
+ e.preventDefault();
1393
+ const cxRes = this._canvas.offsetWidth;
1394
+ const cyRes = this._canvas.offsetHeight;
1395
+ if (this._button == 0) {
1396
+ if (this._selectedPoint) {
1397
+ const rx = this._ra;
1398
+ const ry = this._fixed ? this._ra * cyRes / cxRes : this._ra;
1399
+ const x = this._pxRef + (e.offsetX - this._offsetXRef) / cxRes * rx / 2;
1400
+ const y = this._pyRef - (e.offsetY - this._offsetYRef) / cyRes * ry / 2;
1401
+ this.setPointPos(x, y);
1402
+ } else {
1403
+ this._cx = this._cxRef - (e.offsetX - this._offsetXRef) * 2 * this._ra / cxRes;
1404
+ this._cy = this._cyRef + (e.offsetY - this._offsetYRef) * 2 * this._ra / cyRes;
1405
+ }
1406
+ }
1407
+ this._mouse.x = e.offsetX;
1408
+ this._mouse.y = e.offsetY;
1409
+ }
1410
+ _onWheel(e) {
1411
+ e.preventDefault();
1412
+ const sfactor = 1.1;
1413
+ this._ra *= e.deltaY < 0 || e.wheelDelta > 0 ? 1.0 / sfactor : sfactor;
1414
+ this._ra = clamp(this._ra, 1, 2);
1415
+ }
1416
+ render() {
1417
+ if (this._autoResize) {
1418
+ this.resize();
1419
+ }
1420
+ const rx = this._ra;
1421
+ const ry = this._fixed ? this._ra * this._yRes / this._xRes : this._ra;
1422
+ const a = 2 - this._ra;
1423
+ this._cx = clamp(this._cx, -a, a);
1424
+ this._cy = clamp(this._cy, -a, a);
1425
+ const ctx = this._context;
1426
+ ctx.setTransform(1, 0.0, 0.0, 1, 0.5, 0.5);
1427
+ ctx.fillStyle = theme.BACKGROUND;
1428
+ ctx.fillRect(0, 0, this._xRes, this._yRes);
1429
+ this._drawGrid(ctx, theme.GRID, rx, ry);
1430
+ // this._drawAxis(ctx, theme.GRID, rx, ry);
1431
+ this._drawCurve(ctx, theme.LINE, rx, ry);
1432
+ }
1433
+ _drawGrid(ctx, color, rx, ry) {
1434
+ const iax = Math.floor(this._cx - rx);
1435
+ const ibx = Math.floor(this._cx + rx);
1436
+ const iay = Math.floor(this._cy - ry);
1437
+ const iby = Math.floor(this._cy + ry);
1438
+ ctx.lineWidth = 2.0;
1439
+ ctx.strokeStyle = color;
1440
+ ctx.beginPath();
1441
+ for(let i = iax; i <= ibx; i++){
1442
+ let ix = this._xRes * (0.5 + (i - this._cx) / (2 * rx));
1443
+ ctx.moveTo(ix, this._yRes);
1444
+ ctx.lineTo(ix, 0);
1445
+ }
1446
+ for(let i = iay; i <= iby; i++){
1447
+ let iy = this._yRes * (0.5 - (i - this._cy) / (2 * ry));
1448
+ ctx.moveTo(this._xRes, iy);
1449
+ ctx.lineTo(0, iy);
1450
+ }
1451
+ ctx.stroke();
1452
+ }
1453
+ _drawAxis(ctx, color, rx, ry) {
1454
+ const xPos = this._xRes * (0.5 - this._cx / (2.0 * rx));
1455
+ const yPos = this._yRes * (0.5 + this._cy / (2.0 * ry));
1456
+ ctx.strokeStyle = color;
1457
+ ctx.lineWidth = 4;
1458
+ ctx.beginPath();
1459
+ ctx.moveTo(xPos, 0);
1460
+ ctx.lineTo(xPos, this._yRes);
1461
+ ctx.moveTo(0, yPos);
1462
+ ctx.lineTo(this._xRes, yPos);
1463
+ ctx.stroke();
1464
+ }
1465
+ _drawCurve(ctx, color, rx, ry) {
1466
+ if (!this._curve) return;
1467
+ const ax = 2 / rx; //缩放
1468
+ const ay = 2 / ry;
1469
+ const bx = (1 - ax) / 2; //偏移,保持居中
1470
+ const by = (1 - ay) / 2;
1471
+ const cx = this._cx / 4; //缩小4倍至单位1
1472
+ const cy = this._cy / 4;
1473
+ const SAMPLES = Math.min(100, this._xRes);
1474
+ const STEP = 1 / (SAMPLES - 1);
1475
+ const point = this._curve.createCurvePoint();
1476
+ const DPR = window.devicePixelRatio || 1;
1477
+ const mx = this._mouse.x * DPR;
1478
+ const my = this._mouse.y * DPR;
1479
+ let hitCurve = null;
1480
+ let hitPoint = null;
1481
+ let isFirstPoint = true;
1482
+ //配置参数
1483
+ ctx.lineWidth = 2;
1484
+ ctx.strokeStyle = color;
1485
+ ctx.fillStyle = color;
1486
+ //绘制曲线
1487
+ ctx.beginPath();
1488
+ for(let i = 0; i < SAMPLES; i++){
1489
+ let p = this._curve.getPoint(i * STEP, point);
1490
+ let x = this._xRes * ((p.x - cx) * ax + bx);
1491
+ let y = this._yRes * (1 + (cy - p.y) * ay - by);
1492
+ if (hitCurve === null && hitTest(mx, my, x, y, 15, 15)) {
1493
+ hitCurve = p;
1494
+ }
1495
+ if (isFirstPoint) ctx.moveTo(x, y);
1496
+ else ctx.lineTo(x, y);
1497
+ isFirstPoint = false;
1498
+ }
1499
+ ctx.stroke();
1500
+ ctx.lineTo(this._xRes, this._yRes);
1501
+ ctx.closePath();
1502
+ ctx.fillStyle = theme.COVER;
1503
+ ctx.fill();
1504
+ //绘制控制点
1505
+ const points = this._curve.points;
1506
+ for(let i = 1; i < points.length - 1; i++){
1507
+ let p = points[i];
1508
+ let x = this._xRes * ((p.x - cx) * ax + bx);
1509
+ let y = this._yRes * (1 + (cy - p.y) * ay - by);
1510
+ if (hitPoint === null && hitTest(mx, my, x, y, 20, 20)) {
1511
+ hitPoint = p;
1512
+ }
1513
+ ctx.fillStyle = p === hitPoint || p === this._selectedPoint ? theme.SELECTED : theme.POINT;
1514
+ ctx.fillRect(x - 2.5, y - 2.5, 5, 5);
1515
+ }
1516
+ //状态检查
1517
+ if (this._tryAddingPoint && hitPoint) {
1518
+ this._tryAddingPoint = false;
1519
+ points.splice(points.indexOf(hitPoint), 1);
1520
+ this._onPointRemoved && this._onPointRemoved(hitPoint);
1521
+ }
1522
+ if (this._tryAddingPoint && hitCurve) {
1523
+ this._tryAddingPoint = false;
1524
+ let px = (mx / this._xRes - bx) / ax + cx;
1525
+ let py = cy - (my / this._yRes + by - 1) / ay;
1526
+ let p = this._curve.createCurvePoint(px, py);
1527
+ points.push(p);
1528
+ points.sort((a, b)=>a.x - b.x);
1529
+ this._onPointAdded && this._onPointAdded(p);
1530
+ }
1531
+ if (this._trySelectingPoint && hitPoint) {
1532
+ this._trySelectingPoint = false;
1533
+ this.selectPoint(hitPoint);
1534
+ }
1535
+ this._tryAddingPoint = false;
1536
+ this._trySelectingPoint = false;
1537
+ }
1538
+ setCurve(curve) {
1539
+ this._curve = curve;
1540
+ this._selectedPoint = null;
1541
+ this._onCurveChanged && this._onCurveChanged(curve);
1542
+ return this;
1543
+ }
1544
+ setPointMode(mode) {
1545
+ if (this._selectedPoint) {
1546
+ this._selectedPoint.mode = mode;
1547
+ this._onPointChanged && this._onPointChanged(this._selectedPoint);
1548
+ }
1549
+ return this;
1550
+ }
1551
+ setPointPos(x, y) {
1552
+ if (this._selectedPoint) {
1553
+ this._selectedPoint.set(clamp(x, 0, 1), clamp(y, 0, 1));
1554
+ this._curve.points.sort((a, b)=>a.x - b.x);
1555
+ this._onPointChanged && this._onPointChanged(this._selectedPoint);
1556
+ }
1557
+ return this;
1558
+ }
1559
+ selectPoint(point) {
1560
+ if (point) {
1561
+ this._selectedPoint = point;
1562
+ this._pxRef = point.x;
1563
+ this._pyRef = point.y;
1564
+ this._onPointChanged && this._onPointChanged(point);
1565
+ } else {
1566
+ this._selectedPoint = null;
1567
+ }
1568
+ return this;
1569
+ }
1570
+ constructor(){
1571
+ this._xRes = 1;
1572
+ this._yRes = 1;
1573
+ this._cx = 0;
1574
+ this._cy = 0;
1575
+ this._ra = 2;
1576
+ this._fixed = false;
1577
+ this._autoResize = true;
1578
+ this._mouse = {
1579
+ x: 0,
1580
+ y: 0
1581
+ };
1582
+ this._offsetXRef = 0;
1583
+ this._offsetYRef = 0;
1584
+ this._cxRef = 0;
1585
+ this._cyRef = 0;
1586
+ this._pxRef = 0;
1587
+ this._pyRef = 0;
1588
+ this._button = -1;
1589
+ this._curve = null;
1590
+ this._selectedPoint = null;
1591
+ this._tryAddingPoint = false;
1592
+ this._trySelectingPoint = false;
1593
+ this._canvas = document.createElement("canvas");
1594
+ this._context = this._canvas.getContext("2d");
1595
+ const canvas = this._canvas;
1596
+ canvas.addEventListener("mousedown", (e)=>this._onMouseDown(e));
1597
+ canvas.addEventListener("mouseup", (e)=>this._onMouseUp(e));
1598
+ canvas.addEventListener("mouseleave", (e)=>this._onMouseUp(e));
1599
+ canvas.addEventListener("mousemove", (e)=>this._onMouseMove(e));
1600
+ canvas.addEventListener("wheel", (e)=>this._onWheel(e));
1601
+ canvas.addEventListener("dblclick", (e)=>this._onDblClick(e));
1602
+ }
1603
+ }
1604
+ function copyToClipboard(text) {
1605
+ const tempInput = document.createElement('textarea');
1606
+ tempInput.value = text;
1607
+ document.body.appendChild(tempInput);
1608
+ tempInput.select();
1609
+ tempInput.setSelectionRange(0, 99999); // 兼容移动设备
1610
+ document.execCommand('copy');
1611
+ document.body.removeChild(tempInput);
1612
+ }
1613
+ function toFixedFloat(v, fractionDigits) {
1614
+ return parseFloat(v.toFixed(fractionDigits));
1615
+ }
1616
+ class CurveEditor {
1617
+ get dom() {
1618
+ return this._dom;
1619
+ }
1620
+ onChange(callback) {
1621
+ this._onChange = callback;
1622
+ return this;
1623
+ }
1624
+ _onToggleTab(tabInex) {
1625
+ for(let i = 0; i < this._tabs.length; i++){
1626
+ if (i === tabInex) this._tabs[i].classList.add("selected");
1627
+ else this._tabs[i].classList.remove("selected");
1628
+ }
1629
+ this._curvePanel.setCurve(this._curves[tabInex]);
1630
+ }
1631
+ _onTogglePointTab(tab) {
1632
+ if (tab === 0) {
1633
+ this._smoothTab.classList.add("selected");
1634
+ this._sharpTab.classList.remove("selected");
1635
+ } else {
1636
+ this._smoothTab.classList.remove("selected");
1637
+ this._sharpTab.classList.add("selected");
1638
+ }
1639
+ }
1640
+ _onCurveChanged(curve) {
1641
+ const points = this._curvePanel.curve.points;
1642
+ this._curvePointPanel.style.display = points.length > 2 ? "flex" : "none";
1643
+ this._curvePanel.selectPoint(points.length > 2 ? points[1] : null);
1644
+ }
1645
+ _onPosChanged() {
1646
+ this._curvePanel.setPointPos(parseFloat(this._xPosInput.value), parseFloat(this._yPosInput.value));
1647
+ }
1648
+ _onPointAdded(point) {
1649
+ const points = this._curvePanel.curve.points;
1650
+ this._curvePointPanel.style.display = points.length > 2 ? "flex" : "none";
1651
+ this._curvePanel.selectPoint(point);
1652
+ }
1653
+ _onPointRemoved(point) {
1654
+ const points = this._curvePanel.curve.points;
1655
+ this._curvePointPanel.style.display = points.length > 2 ? "flex" : "none";
1656
+ this._curvePanel.selectPoint(points.length > 2 ? points[1] : null);
1657
+ }
1658
+ _onPointChanged(point) {
1659
+ let isChanged = false;
1660
+ let layoutPoint = this._layoutPoint;
1661
+ if (layoutPoint.mode !== point.mode) {
1662
+ layoutPoint.mode = point.mode;
1663
+ this._onTogglePointTab(point.mode);
1664
+ isChanged = true;
1665
+ }
1666
+ if (Math.hypot(layoutPoint.x - point.x, layoutPoint.y - point.y) > 0.001) {
1667
+ layoutPoint.x = point.x;
1668
+ layoutPoint.y = point.y;
1669
+ this._xPosInput.value = parseFloat(point.x.toFixed(3)).toString();
1670
+ this._yPosInput.value = parseFloat(point.y.toFixed(3)).toString();
1671
+ isChanged = true;
1672
+ }
1673
+ if (isChanged) {
1674
+ this._curvePanel.curve.needsUpdate = true;
1675
+ this._onChange && this._onChange(this._target);
1676
+ }
1677
+ }
1678
+ render() {
1679
+ this._curvePanel.render();
1680
+ }
1681
+ constructor(target){
1682
+ this._tabs = [];
1683
+ this._dom = document.createElement("div");
1684
+ this._dom.classList.add("curve-editor");
1685
+ document.body.appendChild(this._dom);
1686
+ let tabGroup = document.createElement("div");
1687
+ tabGroup.classList.add("curve-top");
1688
+ tabGroup.ondblclick = (e)=>{
1689
+ e.preventDefault();
1690
+ let text = this._curves.map((v)=>v.points.length + "," + v.points.map((p)=>`${toFixedFloat(p.x, 3)},${toFixedFloat(p.y, 3)},${p.mode}`).join(",")).join(",");
1691
+ copyToClipboard(text);
1692
+ alert("Copyed curve points");
1693
+ };
1694
+ this._dom.appendChild(tabGroup);
1695
+ let curves = Array.isArray(target) ? target : [
1696
+ target
1697
+ ];
1698
+ if (curves.length > 1) {
1699
+ for(let i = 0; i < curves.length; i++){
1700
+ let c = curves[i];
1701
+ let tab = document.createElement("div");
1702
+ tab.classList.add("button-medium");
1703
+ tab.textContent = c.name || i.toString();
1704
+ tab.onclick = ()=>this._onToggleTab(i);
1705
+ tabGroup.appendChild(tab);
1706
+ this._tabs.push(tab);
1707
+ }
1708
+ }
1709
+ this._target = target;
1710
+ this._curves = curves;
1711
+ this._layoutPoint = curves[0].createCurvePoint(0, 0, -1);
1712
+ this._curvePanel = new CurvePanel();
1713
+ this._curvePanel.dom.classList.add("curve-panel");
1714
+ this._curvePanel.onCurveChanged((c)=>this._onCurveChanged(c));
1715
+ this._curvePanel.onPointChanged((p)=>this._onPointChanged(p));
1716
+ this._curvePanel.onPointAdded((p)=>this._onPointAdded(p));
1717
+ this._curvePanel.onPointRemoved((p)=>this._onPointRemoved(p));
1718
+ this._dom.appendChild(this._curvePanel.dom);
1719
+ this._curvePointPanel = document.createElement("div");
1720
+ this._curvePointPanel.classList.add("curve-point-panel");
1721
+ this._curvePointPanel.style.display = "none";
1722
+ this._dom.appendChild(this._curvePointPanel);
1723
+ this._smoothTab = document.createElement("div");
1724
+ this._smoothTab.classList.add("button-medium");
1725
+ this._smoothTab.innerHTML = /*html*/ `
1726
+ <svg viewBox="351.367 172.836 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:bx="https://boxy-svg.com">
1727
+ <path style="fill: none; stroke-width: 2px; stroke: rgb(240, 240, 240);" d="M 356 200 C 356 200 355.7892846963982 195.12801796288684 356 193 C 356.17984169835074 191.18377517899805 356.2627725586097 189.58207992830177 357 188 C 357.82614089255884 186.22711303095053 359.32609311863524 184.16719383631644 361 183 C 362.6636054267734 181.83998923607413 364.99999999999994 180.99999999999997 367 181 C 368.99999999999994 180.99999999999997 371.3363945732266 181.83998923607413 373 183 C 374.6739068813647 184.16719383631644 376.1738591074412 186.22711303095053 377 188 C 377.73722744139036 189.58207992830177 377.8201583016493 191.18377517899808 378 193 C 378.2107153036018 195.12801796288684 378 200 378 200" transform="matrix(1, 0, 0, 1, -5.684341886080802e-14, 0)" bx:d="M 356 200 R 356 193 R 357 188 R 361 183 R 367 181 R 373 183 R 377 188 R 378 193 R 378 200 1@f911f19e"/>
1728
+ <rect x="368" y="176.021" width="6" height="5.979" style="fill: rgb(255, 255, 255);" transform="matrix(1, 0, 0, 1, -5.684341886080802e-14, 0)"/>
1729
+ </svg>
1730
+ `;
1731
+ this._curvePointPanel.appendChild(this._smoothTab);
1732
+ this._sharpTab = document.createElement("div");
1733
+ this._sharpTab.classList.add("button-medium");
1734
+ this._sharpTab.innerHTML = /*html*/ `
1735
+ <svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
1736
+ <polyline style="fill: none; stroke: rgb(240, 240, 240); stroke-width: 2px;" points="6 26 17 7 28 26"/>
1737
+ <rect x="14" y="4" width="6" height="6" style="fill: rgb(255, 255, 255);"/>
1738
+ </svg>
1739
+ `;
1740
+ this._curvePointPanel.appendChild(this._sharpTab);
1741
+ this._smoothTab.onclick = ()=>this._curvePanel.setPointMode(0);
1742
+ this._sharpTab.onclick = ()=>this._curvePanel.setPointMode(1);
1743
+ this._xPosInput = document.createElement("input");
1744
+ this._xPosInput.onchange = (e)=>this._onPosChanged();
1745
+ this._curvePointPanel.appendChild(this._xPosInput);
1746
+ this._yPosInput = document.createElement("input");
1747
+ this._yPosInput.onchange = (e)=>this._onPosChanged();
1748
+ this._curvePointPanel.appendChild(this._yPosInput);
1749
+ this._onToggleTab(0);
1750
+ }
1751
+ }
1752
+
1753
+ class CurveController extends Controller {
1754
+ needsUpdateDisplay() {
1755
+ return !this.parent._closed;
1756
+ }
1757
+ updateDisplay() {
1758
+ this._curveEditor.render();
1759
+ return this;
1760
+ }
1761
+ constructor(parent, object, property){
1762
+ super(parent, object, property, "label");
1763
+ this._curveEditor = new CurveEditor(this.getValue());
1764
+ this._curveEditor.onChange((v)=>this.setValue(v));
1765
+ this.$widget.appendChild(this._curveEditor.dom);
1766
+ }
1767
+ }
1768
+
1769
+ class TextureController extends Controller {
1770
+ needsUpdateDisplay() {
1771
+ if (this.parent._closed) {
1772
+ return false;
1773
+ }
1774
+ let texture = this.getValue();
1775
+ if (texture && this._version !== texture.version) {
1776
+ this._version = texture.version;
1777
+ return true;
1778
+ }
1779
+ return false;
1780
+ }
1781
+ updateDisplay() {
1782
+ const canvas = this.$canvas;
1783
+ const context = canvas.getContext("2d");
1784
+ const texture = this.getValue();
1785
+ const image = texture.image;
1786
+ // Seems like context can be null if the canvas is not visible
1787
+ if (context) {
1788
+ // Always clear the context before set new texture, because new texture may has transparency
1789
+ context.clearRect(0, 0, canvas.width, canvas.height);
1790
+ }
1791
+ if (image && image.width > 0) {
1792
+ const scale = canvas.width / image.width;
1793
+ canvas.height = Math.max(2, image.height / image.width * canvas.width);
1794
+ const width = Math.max(image.width * scale, 2);
1795
+ const height = Math.max(image.height * scale, 2);
1796
+ if (texture.__getImage) {
1797
+ context.drawImage(texture.__getImage(), 0, 0, width, height);
1798
+ } else {
1799
+ context.drawImage(image, 0, 0, width, height);
1800
+ }
1801
+ }
1802
+ return this;
1803
+ }
1804
+ constructor(parent, object, property){
1805
+ super(parent, object, property, "image");
1806
+ this._version = 0;
1807
+ let group = document.createElement("div");
1808
+ group.style.display = "flex";
1809
+ group.style.flexDirection = "column";
1810
+ group.style.width = "100%";
1811
+ this.$widget.appendChild(group);
1812
+ this.$canvas = document.createElement("canvas");
1813
+ group.appendChild(this.$canvas);
1814
+ const texture = this.getValue();
1815
+ if (texture.isRenderTargetTexture) {
1816
+ let button = document.createElement("button");
1817
+ button.textContent = "capture";
1818
+ button.onclick = ()=>this.updateDisplay();
1819
+ group.appendChild(button);
1820
+ }
1821
+ }
1822
+ }
1823
+
1824
+ class GUI {
1825
+ /**
1826
+ * Adds a controller to the GUI, inferring controller type using the `typeof` operator.
1827
+ * @example
1828
+ * gui.add( object, 'property' );
1829
+ * gui.add( object, 'number', 0, 100, 1 );
1830
+ * gui.add( object, 'options', [ 1, 2, 3 ] );
1831
+ *
1832
+ * @param {object} object The object the controller will modify.
1833
+ * @param {string} property Name of the property to control.
1834
+ * @param {number|object|Array} [$1] Minimum value for number controllers, or the set of
1835
+ * selectable values for a dropdown.
1836
+ * @param {number} [max] Maximum value for number controllers.
1837
+ * @param {number} [step] Step value for number controllers.
1838
+ * @returns {Controller}
1839
+ */ add(object, property, $1, max, step) {
1840
+ if (Object($1) === $1) {
1841
+ return new OptionController(this, object, property, $1);
1842
+ }
1843
+ const initialValue = object[property];
1844
+ switch(typeof initialValue){
1845
+ case 'number':
1846
+ return new NumberController(this, object, property, $1, max, step);
1847
+ case 'boolean':
1848
+ return new BooleanController(this, object, property);
1849
+ case 'string':
1850
+ return new StringController(this, object, property);
1851
+ case 'function':
1852
+ return new FunctionController(this, object, property);
1853
+ }
1854
+ // eslint-disable-next-line no-console
1855
+ console.error(`gui.add failed
1856
+ property:`, property, `
1857
+ object:`, object, `
1858
+ value:`, initialValue);
1859
+ }
1860
+ /**
1861
+ * Adds a color controller to the GUI.
1862
+ * @example
1863
+ * params = {
1864
+ * cssColor: '#ff00ff',
1865
+ * rgbColor: { r: 0, g: 0.2, b: 0.4 },
1866
+ * customRange: [ 0, 127, 255 ],
1867
+ * };
1868
+ *
1869
+ * gui.addColor( params, 'cssColor' );
1870
+ * gui.addColor( params, 'rgbColor' );
1871
+ * gui.addColor( params, 'customRange', 255 );
1872
+ *
1873
+ * @param {object} object The object the controller will modify.
1874
+ * @param {string} property Name of the property to control.
1875
+ * @param {number} rgbScale Maximum value for a color channel when using an RGB color. You may
1876
+ * need to set this to 255 if your colors are too bright.
1877
+ * @returns {Controller}
1878
+ */ addColor(object, property, rgbScale = 1) {
1879
+ return new ColorController(this, object, property, rgbScale);
1880
+ }
1881
+ /**
1882
+ *
1883
+ * @param {object} object
1884
+ * @param {string} property Name of the property to control.
1885
+ * @returns {Controller}
1886
+ */ addImage(object, property) {
1887
+ return new ImageController(this, object, property);
1888
+ }
1889
+ addTexture(object, property) {
1890
+ return new TextureController(this, object, property);
1891
+ }
1892
+ /**
1893
+ *
1894
+ * @param {object} object
1895
+ * @param {string} property
1896
+ * @param min
1897
+ * @param max
1898
+ * @param step
1899
+ * @returns {Controller}
1900
+ */ addVector(object, property, min, max, step) {
1901
+ return new VectorController(this, object, property, min, max, step);
1902
+ }
1903
+ addCurve(object, property) {
1904
+ return new CurveController(this, object, property);
1905
+ }
1906
+ /**
1907
+ * Adds a folder to the GUI, which is just another GUI. This method returns
1908
+ * the nested GUI so you can add controllers to it.
1909
+ * @example
1910
+ * const folder = gui.addFolder( 'Position' );
1911
+ * folder.add( position, 'x' );
1912
+ * folder.add( position, 'y' );
1913
+ * folder.add( position, 'z' );
1914
+ *
1915
+ * @param {string} title Name to display in the folder's title bar.
1916
+ * @param {string} id Id
1917
+ * @returns {GUI}
1918
+ */ addFolder(title, id = title) {
1919
+ const folder = new GUI({
1920
+ parent: this,
1921
+ title,
1922
+ id
1923
+ });
1924
+ if (this.root._closeFolders) folder.close();
1925
+ return folder;
1926
+ }
1927
+ /**
1928
+ *
1929
+ * @param {string} id
1930
+ * @returns {this}
1931
+ */ removeFolder(id) {
1932
+ let folder = this.getFolder(id);
1933
+ if (folder) folder.destroy();
1934
+ return this;
1935
+ }
1936
+ /**
1937
+ *
1938
+ * @param {string} id
1939
+ * @returns {GUI}
1940
+ */ getFolder(id) {
1941
+ return this.folders.find((v)=>v.id === id);
1942
+ }
1943
+ /**
1944
+ * Recalls values that were saved with `gui.save()`.
1945
+ * @param {object} obj
1946
+ * @param {boolean} recursive Pass false to exclude folders descending from this GUI.
1947
+ * @returns {this}
1948
+ */ load(obj, recursive = true) {
1949
+ if (obj.controllers) {
1950
+ this.controllers.forEach((c)=>{
1951
+ if (c instanceof FunctionController) return;
1952
+ if (c._name in obj.controllers) {
1953
+ c.load(obj.controllers[c._name]);
1954
+ }
1955
+ });
1956
+ }
1957
+ if (recursive && obj.folders) {
1958
+ this.folders.forEach((f)=>{
1959
+ if (f._title in obj.folders) {
1960
+ f.load(obj.folders[f._title]);
1961
+ }
1962
+ });
1963
+ }
1964
+ return this;
1965
+ }
1966
+ /**
1967
+ * Returns an object mapping controller names to values. The object can be passed to `gui.load()` to
1968
+ * recall these values.
1969
+ * @example
1970
+ * {
1971
+ * controllers: {
1972
+ * prop1: 1,
1973
+ * prop2: 'value',
1974
+ * ...
1975
+ * },
1976
+ * folders: {
1977
+ * folderName1: { controllers, folders },
1978
+ * folderName2: { controllers, folders }
1979
+ * ...
1980
+ * }
1981
+ * }
1982
+ *
1983
+ * @param {boolean} recursive Pass false to exclude folders descending from this GUI.
1984
+ * @returns {object}
1985
+ */ save(recursive = true) {
1986
+ const obj = {
1987
+ controllers: {},
1988
+ folders: {}
1989
+ };
1990
+ this.controllers.forEach((c)=>{
1991
+ if (c instanceof FunctionController) return;
1992
+ if (c._name in obj.controllers) {
1993
+ throw new Error(`Cannot save GUI with duplicate property "${c._name}"`);
1994
+ }
1995
+ obj.controllers[c._name] = c.save();
1996
+ });
1997
+ if (recursive) {
1998
+ this.folders.forEach((f)=>{
1999
+ if (f._title in obj.folders) {
2000
+ throw new Error(`Cannot save GUI with duplicate folder "${f._title}"`);
2001
+ }
2002
+ obj.folders[f._title] = f.save();
2003
+ });
2004
+ }
2005
+ return obj;
2006
+ }
2007
+ /**
2008
+ * Opens a GUI or folder. GUI and folders are open by default.
2009
+ * @param {boolean} open Pass false to close
2010
+ * @returns {this}
2011
+ * @example
2012
+ * gui.open(); // open
2013
+ * gui.open( false ); // close
2014
+ * gui.open( gui._closed ); // toggle
2015
+ */ open(open = true) {
2016
+ this._setClosed(!open);
2017
+ this.$title.setAttribute('aria-expanded', !this._closed);
2018
+ this.domElement.classList.toggle('closed', this._closed);
2019
+ return this;
2020
+ }
2021
+ /**
2022
+ * Closes the GUI.
2023
+ * @returns {this}
2024
+ */ close() {
2025
+ return this.open(false);
2026
+ }
2027
+ _setClosed(closed) {
2028
+ if (this._closed === closed) return;
2029
+ this._closed = closed;
2030
+ this._callOnOpenClose(this);
2031
+ }
2032
+ /**
2033
+ * Shows the GUI after it's been hidden.
2034
+ * @param {boolean} show
2035
+ * @returns {this}
2036
+ * @example
2037
+ * gui.show();
2038
+ * gui.show( false ); // hide
2039
+ * gui.show( gui._hidden ); // toggle
2040
+ */ show(show = true) {
2041
+ this._hidden = !show;
2042
+ this.domElement.style.display = this._hidden ? 'none' : '';
2043
+ return this;
2044
+ }
2045
+ /**
2046
+ * Hides the GUI.
2047
+ * @returns {this}
2048
+ */ hide() {
2049
+ return this.show(false);
2050
+ }
2051
+ openAnimated(open = true) {
2052
+ // set state immediately
2053
+ this._setClosed(!open);
2054
+ this.$title.setAttribute('aria-expanded', !this._closed);
2055
+ // wait for next frame to measure $children
2056
+ requestAnimationFrame(()=>{
2057
+ // explicitly set initial height for transition
2058
+ const initialHeight = this.$children.clientHeight;
2059
+ this.$children.style.height = initialHeight + 'px';
2060
+ this.domElement.classList.add('transition');
2061
+ const onTransitionEnd = (e)=>{
2062
+ if (e.target !== this.$children) return;
2063
+ this.$children.style.height = '';
2064
+ this.domElement.classList.remove('transition');
2065
+ this.$children.removeEventListener('transitionend', onTransitionEnd);
2066
+ };
2067
+ this.$children.addEventListener('transitionend', onTransitionEnd);
2068
+ // todo: this is wrong if children's scrollHeight makes for a gui taller than maxHeight
2069
+ const targetHeight = !open ? 0 : this.$children.scrollHeight;
2070
+ this.domElement.classList.toggle('closed', !open);
2071
+ requestAnimationFrame(()=>{
2072
+ this.$children.style.height = targetHeight + 'px';
2073
+ });
2074
+ });
2075
+ return this;
2076
+ }
2077
+ /**
2078
+ * Change the title of this GUI.
2079
+ * @param {string} title
2080
+ * @returns {this}
2081
+ */ title(title) {
2082
+ this._title = title;
2083
+ this.$title.innerHTML = title;
2084
+ return this;
2085
+ }
2086
+ /**
2087
+ * Resets all controllers to their initial values.
2088
+ * @param {boolean} recursive Pass false to exclude folders descending from this GUI.
2089
+ * @returns {this}
2090
+ */ reset(recursive = true) {
2091
+ const controllers = recursive ? this.controllersRecursive() : this.controllers;
2092
+ controllers.forEach((c)=>c.reset());
2093
+ return this;
2094
+ }
2095
+ /**
2096
+ * Pass a function to be called whenever a controller in this GUI changes.
2097
+ * @param {function({object:object, property:string, value:any, controller:Controller})} callback
2098
+ * @returns {this}
2099
+ * @example
2100
+ * gui.onChange( event => {
2101
+ * event.object // object that was modified
2102
+ * event.property // string, name of property
2103
+ * event.value // new value of controller
2104
+ * event.controller // controller that was modified
2105
+ * } );
2106
+ */ onChange(callback) {
2107
+ /**
2108
+ * Used to access the function bound to `onChange` events. Don't modify this value
2109
+ * directly. Use the `gui.onChange( callback )` method instead.
2110
+ * @type {Function}
2111
+ */ this._onChange = callback;
2112
+ return this;
2113
+ }
2114
+ _callOnChange(controller) {
2115
+ if (this.parent) {
2116
+ this.parent._callOnChange(controller);
2117
+ }
2118
+ if (this._onChange !== undefined) {
2119
+ this._onChange.call(this, {
2120
+ object: controller.object,
2121
+ property: controller.property,
2122
+ value: controller.getValue(),
2123
+ controller
2124
+ });
2125
+ }
2126
+ }
2127
+ /**
2128
+ * Pass a function to be called whenever a controller in this GUI has finished changing.
2129
+ * @param {function({object:object, property:string, value:any, controller:Controller})} callback
2130
+ * @returns {this}
2131
+ * @example
2132
+ * gui.onFinishChange( event => {
2133
+ * event.object // object that was modified
2134
+ * event.property // string, name of property
2135
+ * event.value // new value of controller
2136
+ * event.controller // controller that was modified
2137
+ * } );
2138
+ */ onFinishChange(callback) {
2139
+ this._onFinishChange = callback;
2140
+ return this;
2141
+ }
2142
+ _callOnFinishChange(controller) {
2143
+ if (this.parent) {
2144
+ this.parent._callOnFinishChange(controller);
2145
+ }
2146
+ if (this._onFinishChange !== undefined) {
2147
+ this._onFinishChange.call(this, {
2148
+ object: controller.object,
2149
+ property: controller.property,
2150
+ value: controller.getValue(),
2151
+ controller
2152
+ });
2153
+ }
2154
+ }
2155
+ /**
2156
+ * Pass a function to be called when this GUI or its descendants are opened or closed.
2157
+ * @param {function(GUI)} callback
2158
+ * @returns {this}
2159
+ * @example
2160
+ * gui.onOpenClose( changedGUI => {
2161
+ * console.log( changedGUI._closed );
2162
+ * } );
2163
+ */ onOpenClose(callback) {
2164
+ this._onOpenClose = callback;
2165
+ return this;
2166
+ }
2167
+ _callOnOpenClose(changedGUI) {
2168
+ if (this.parent) {
2169
+ this.parent._callOnOpenClose(changedGUI);
2170
+ }
2171
+ if (this._onOpenClose !== undefined) {
2172
+ this._onOpenClose.call(this, changedGUI);
2173
+ }
2174
+ }
2175
+ /**
2176
+ * Destroys all DOM elements and event listeners associated with this GUI
2177
+ */ destroy() {
2178
+ if (this.parent) {
2179
+ this.parent.children.splice(this.parent.children.indexOf(this), 1);
2180
+ this.parent.folders.splice(this.parent.folders.indexOf(this), 1);
2181
+ }
2182
+ if (this.domElement.parentElement) {
2183
+ this.domElement.parentElement.removeChild(this.domElement);
2184
+ }
2185
+ Array.from(this.children).forEach((c)=>c.destroy());
2186
+ }
2187
+ /**
2188
+ * Returns an array of controllers contained by this GUI and its descendents.
2189
+ * @returns {Controller[]}
2190
+ */ controllersRecursive() {
2191
+ let controllers = Array.from(this.controllers);
2192
+ this.folders.forEach((f)=>{
2193
+ controllers = controllers.concat(f.controllersRecursive());
2194
+ });
2195
+ return controllers;
2196
+ }
2197
+ /**
2198
+ * Returns an array of folders contained by this GUI and its descendents.
2199
+ * @returns {GUI[]}
2200
+ */ foldersRecursive() {
2201
+ let folders = Array.from(this.folders);
2202
+ this.folders.forEach((f)=>{
2203
+ folders = folders.concat(f.foldersRecursive());
2204
+ });
2205
+ return folders;
2206
+ }
2207
+ update() {
2208
+ for (let child of this.children){
2209
+ child.update();
2210
+ }
2211
+ }
2212
+ constructor({ parent, container, width, autoPlace = parent === undefined, title = 'Controls', id = title, closeFolders = false, touchStyles = true } = {}){
2213
+ this.children = [];
2214
+ this.controllers = [];
2215
+ this.folders = [];
2216
+ /**
2217
+ * Current title of the GUI. Use `gui.title( 'Title' )` to modify this value.
2218
+ */ this._title = "";
2219
+ this._closed = false;
2220
+ this._hidden = false;
2221
+ this._closeFolders = false;
2222
+ this.id = id;
2223
+ this.parent = parent;
2224
+ this.root = parent ? parent.root : this;
2225
+ this.domElement = document.createElement("div");
2226
+ this.domElement.classList.add("lil-gui");
2227
+ this.$title = document.createElement('div');
2228
+ this.$title.classList.add('title');
2229
+ this.$title.setAttribute('role', 'button');
2230
+ this.$title.setAttribute('aria-expanded', "true");
2231
+ this.$title.setAttribute('tabindex', "0");
2232
+ this.$title.addEventListener('click', ()=>this.openAnimated(this._closed));
2233
+ this.$title.addEventListener('keydown', (e)=>{
2234
+ if (e.code === 'Enter' || e.code === 'Space') {
2235
+ e.preventDefault();
2236
+ this.$title.click();
2237
+ }
2238
+ });
2239
+ // enables :active pseudo class on mobile
2240
+ this.$title.addEventListener('touchstart', ()=>{}, {
2241
+ passive: true
2242
+ });
2243
+ /**
2244
+ * The DOM element that contains children.
2245
+ * @type {HTMLElement}
2246
+ */ this.$children = document.createElement('div');
2247
+ this.$children.classList.add('children');
2248
+ this.domElement.appendChild(this.$title);
2249
+ this.domElement.appendChild(this.$children);
2250
+ this.title(title);
2251
+ if (this.parent) {
2252
+ this.parent.children.push(this);
2253
+ this.parent.folders.push(this);
2254
+ this.parent.$children.appendChild(this.domElement);
2255
+ // Stop the constructor early, everything onward only applies to root GUI's
2256
+ return;
2257
+ }
2258
+ this.domElement.classList.add('root');
2259
+ if (touchStyles) {
2260
+ this.domElement.classList.add('allow-touch-styles');
2261
+ }
2262
+ if (container) {
2263
+ container.appendChild(this.domElement);
2264
+ } else if (autoPlace) {
2265
+ this.domElement.classList.add('autoPlace');
2266
+ document.body.appendChild(this.domElement);
2267
+ }
2268
+ if (width) {
2269
+ this.domElement.style.setProperty('--width', width + 'px');
2270
+ }
2271
+ this._closeFolders = closeFolders;
2272
+ }
2273
+ }
2274
+
2275
+ /******************************************************************************
2276
+ Copyright (c) Microsoft Corporation.
2277
+
2278
+ Permission to use, copy, modify, and/or distribute this software for any
2279
+ purpose with or without fee is hereby granted.
2280
+
2281
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
2282
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
2283
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
2284
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
2285
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
2286
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
2287
+ PERFORMANCE OF THIS SOFTWARE.
2288
+ ***************************************************************************** */
2289
+ /* global Reflect, Promise, SuppressedError, Symbol */
2290
+
2291
+
2292
+ function __decorate(decorators, target, key, desc) {
2293
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2294
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2295
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2296
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2297
+ }
2298
+
2299
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
2300
+ var e = new Error(message);
2301
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
2302
+ };
2303
+
2304
+ class InspectorPlugin extends Plugin {
2305
+ static _getTargetName(target) {
2306
+ return target.name || target.constructor.name || target.type;
2307
+ }
2308
+ onUpdate(dt) {
2309
+ this._gui.update();
2310
+ this._updateFolders();
2311
+ }
2312
+ _initFolders() {
2313
+ const gui = this._gui;
2314
+ gui.addFolder("Viewer").close().hide();
2315
+ gui.addFolder("Plugin").close().hide();
2316
+ gui.addFolder("Component").close().hide();
2317
+ gui.addFolder("Material").close().hide();
2318
+ gui.addFolder("Other").close().hide();
2319
+ }
2320
+ _initViewer() {
2321
+ this.inspect(new ViewerExtension(this.viewer));
2322
+ }
2323
+ _updateFolders() {
2324
+ const statesMap = this._statesMap;
2325
+ const setState = (v)=>statesMap.set(v, statesMap.has(v) ? 0 : 1);
2326
+ statesMap.forEach((_, k, map)=>map.set(k, -1));
2327
+ this.viewer.traversePlugins(setState);
2328
+ this.viewer.traverseComponents(setState);
2329
+ this.viewer.traverseMaterials(setState);
2330
+ statesMap.forEach((v, k, map)=>{
2331
+ if (v === 1) {
2332
+ this.inspect(k);
2333
+ }
2334
+ if (v === -1) {
2335
+ this.uninspect(k);
2336
+ map.delete(k);
2337
+ }
2338
+ });
2339
+ }
2340
+ _getPropertiesList(target) {
2341
+ const list = [];
2342
+ let props = PropertyManager._getMergedProperties(target);
2343
+ if (props) {
2344
+ list.push(props);
2345
+ this._targetMap.set(props, target);
2346
+ }
2347
+ if (target instanceof ShaderMaterial) {
2348
+ props = Object.create(null);
2349
+ const uniforms = Object.create(null);
2350
+ this._targetMap.set(props, uniforms);
2351
+ const entries = Object.entries(target.uniforms);
2352
+ entries.forEach(([name])=>{
2353
+ Object.defineProperty(uniforms, name, {
2354
+ get: ()=>target.uniforms[name].value,
2355
+ set: (v)=>target.uniforms[name].value = v
2356
+ });
2357
+ props[name] = {
2358
+ dir: "uniforms"
2359
+ };
2360
+ });
2361
+ list.push(props);
2362
+ }
2363
+ return list;
2364
+ }
2365
+ inspect(target) {
2366
+ let propsList = this._getPropertiesList(target);
2367
+ if (propsList.length === 0) return;
2368
+ let gui = this._gui;
2369
+ if (target instanceof ViewerExtension) {
2370
+ gui = gui.getFolder("Viewer").show();
2371
+ } else if (target instanceof Plugin) {
2372
+ gui = gui.getFolder("Plugin").show();
2373
+ } else if (target instanceof Component) {
2374
+ gui = gui.getFolder("Component").show();
2375
+ } else if (target instanceof Material) {
2376
+ gui = gui.getFolder("Material").show();
2377
+ } else {
2378
+ gui = gui.getFolder("Other").show();
2379
+ }
2380
+ if (gui._title !== "Viewer") {
2381
+ gui = gui.getFolder(target.uuid) || gui.addFolder(InspectorPlugin._getTargetName(target), target.uuid).close();
2382
+ }
2383
+ this._addPropsListGUI(gui, propsList, target);
2384
+ return target;
2385
+ }
2386
+ _addPropsListGUI(gui, list, target, dir = "") {
2387
+ let source = target;
2388
+ for (let props of list){
2389
+ if (dir) {
2390
+ gui = gui.getFolder(dir) || gui.addFolder(dir).close();
2391
+ }
2392
+ target = this._targetMap.get(props) || source;
2393
+ for(let k in props){
2394
+ let value = target[k];
2395
+ if (value == null) continue;
2396
+ const prop = props[k];
2397
+ let folder = gui;
2398
+ if (prop.dir) {
2399
+ folder = folder.getFolder(prop.dir) || folder.addFolder(prop.dir).close();
2400
+ }
2401
+ if (PropertyManager._hasProperties(value)) {
2402
+ this._addPropsListGUI(folder, this._getPropertiesList(value), target, k);
2403
+ }
2404
+ const type = typeof value;
2405
+ let ins = null;
2406
+ if (value.isColor) {
2407
+ ins = folder.addColor(target, k);
2408
+ } else if (value.isAnimationCurve) {
2409
+ ins = folder.addCurve(target, k);
2410
+ } else if (value.isVector2 || value.isVector3 || value.isVector4 || value.isEuler) {
2411
+ ins = folder.addVector(target, k, prop.min, prop.max, prop.step);
2412
+ } else if (value.isTexture) ; else if (type === "number" || type === "boolean" || type === "string" || type === "function") {
2413
+ ins = folder.add(target, k, prop.value || prop.min, prop.max, prop.step);
2414
+ } else ;
2415
+ if (ins && prop.name) {
2416
+ ins.name(prop.name);
2417
+ }
2418
+ }
2419
+ }
2420
+ }
2421
+ uninspect(target) {
2422
+ let gui = this._gui;
2423
+ if (target instanceof Plugin) {
2424
+ gui = gui.getFolder("Plugin");
2425
+ } else if (target instanceof Component) {
2426
+ gui = gui.getFolder("Component");
2427
+ } else if (target instanceof Material) {
2428
+ gui = gui.getFolder("Material");
2429
+ } else {
2430
+ gui = gui.getFolder("Other");
2431
+ }
2432
+ gui.removeFolder(target.uuid);
2433
+ if (gui.children.length === 0) {
2434
+ gui.hide();
2435
+ }
2436
+ }
2437
+ constructor(){
2438
+ super();
2439
+ this._statesMap = new Map();
2440
+ this._targetMap = new Map();
2441
+ this.install = ()=>{
2442
+ this._gui = new GUI({
2443
+ width: 310,
2444
+ title: "Inspector"
2445
+ }).close();
2446
+ this._initFolders();
2447
+ this._initViewer();
2448
+ };
2449
+ this.uninstall = ()=>{
2450
+ this._gui.destroy();
2451
+ this._statesMap.clear();
2452
+ this._targetMap.clear();
2453
+ };
2454
+ }
2455
+ }
2456
+ class ViewerExtension extends ObjectInstance {
2457
+ get outputColorSpace() {
2458
+ return this._renderer.outputColorSpace;
2459
+ }
2460
+ set outputColorSpace(v) {
2461
+ this._renderer.outputColorSpace = v;
2462
+ }
2463
+ get toneMapping() {
2464
+ return this._renderer.toneMapping;
2465
+ }
2466
+ set toneMapping(v) {
2467
+ this._renderer.toneMapping = v;
2468
+ }
2469
+ get toneMappingExposure() {
2470
+ return this._renderer.toneMappingExposure;
2471
+ }
2472
+ set toneMappingExposure(v) {
2473
+ this._renderer.toneMappingExposure = v;
2474
+ }
2475
+ get shadows() {
2476
+ return this._renderer.shadowMap.enabled;
2477
+ }
2478
+ set shadows(v) {
2479
+ this._renderer.shadowMap.enabled = v;
2480
+ }
2481
+ get backgroundBlurriness() {
2482
+ return this._scene.backgroundBlurriness;
2483
+ }
2484
+ set backgroundBlurriness(v) {
2485
+ this._scene.backgroundBlurriness = v;
2486
+ }
2487
+ constructor(viewer){
2488
+ super();
2489
+ this._scene = viewer.scene;
2490
+ this._renderer = viewer.renderer;
2491
+ }
2492
+ }
2493
+ __decorate([
2494
+ property({
2495
+ value: {
2496
+ SRGBColorSpace: "srgb",
2497
+ LinearSRGBColorSpace: "srgb-linear",
2498
+ DisplayP3ColorSpace: "display-p3",
2499
+ LinearDisplayP3ColorSpace: "display-p3-linear"
2500
+ }
2501
+ })
2502
+ ], ViewerExtension.prototype, "outputColorSpace", null);
2503
+ __decorate([
2504
+ property({
2505
+ value: {
2506
+ NoToneMapping: 0,
2507
+ LinearToneMapping: 1,
2508
+ ReinhardToneMapping: 2,
2509
+ CineonToneMapping: 3,
2510
+ ACESFilmicToneMapping: 4
2511
+ }
2512
+ })
2513
+ ], ViewerExtension.prototype, "toneMapping", null);
2514
+ __decorate([
2515
+ property({
2516
+ min: 0,
2517
+ max: 10,
2518
+ step: 0.01
2519
+ })
2520
+ ], ViewerExtension.prototype, "toneMappingExposure", null);
2521
+ __decorate([
2522
+ property
2523
+ ], ViewerExtension.prototype, "shadows", null);
2524
+ __decorate([
2525
+ property({
2526
+ min: 0,
2527
+ max: 1,
2528
+ step: 0.01
2529
+ })
2530
+ ], ViewerExtension.prototype, "backgroundBlurriness", null);
2531
+ const meshBasicMaterislProperties = {
2532
+ map: {
2533
+ dir: "diffuse"
2534
+ },
2535
+ lightMap: {
2536
+ dir: "lightMap"
2537
+ },
2538
+ lightMapIntensity: {
2539
+ dir: "lightMap",
2540
+ min: 0,
2541
+ max: 1,
2542
+ step: 0.01,
2543
+ parent: "lightMap"
2544
+ },
2545
+ aoMap: {
2546
+ dir: "ao"
2547
+ },
2548
+ aoMapIntensity: {
2549
+ dir: "ao",
2550
+ min: 0,
2551
+ max: 1,
2552
+ step: 0.01,
2553
+ parent: "aoMap"
2554
+ },
2555
+ specularMap: {},
2556
+ alphaMap: {},
2557
+ envMap: {}
2558
+ };
2559
+ for(let k in meshBasicMaterislProperties){
2560
+ property(meshBasicMaterislProperties[k])(MeshBasicMaterial.prototype, k);
2561
+ }
2562
+ const meshStandardMaterialProperties = {
2563
+ visible: {},
2564
+ transparent: {},
2565
+ side: {
2566
+ value: {
2567
+ FrontSide: 0,
2568
+ BackSide: 1,
2569
+ DoubleSide: 2
2570
+ }
2571
+ },
2572
+ color: {
2573
+ dir: "diffuse"
2574
+ },
2575
+ opacity: {
2576
+ dir: "diffuse",
2577
+ min: 0,
2578
+ max: 1,
2579
+ step: 0.01
2580
+ },
2581
+ map: {
2582
+ dir: "diffuse"
2583
+ },
2584
+ roughnessMap: {
2585
+ dir: "roughness"
2586
+ },
2587
+ roughness: {
2588
+ dir: "roughness",
2589
+ min: 0,
2590
+ max: 1,
2591
+ step: 0.01
2592
+ },
2593
+ metalnessMap: {
2594
+ dir: "metalness"
2595
+ },
2596
+ metalness: {
2597
+ dir: "metalness",
2598
+ min: 0,
2599
+ max: 1,
2600
+ step: 0.01
2601
+ },
2602
+ aoMap: {
2603
+ dir: "ao"
2604
+ },
2605
+ aoMapIntensity: {
2606
+ dir: "ao",
2607
+ min: 0,
2608
+ max: 1,
2609
+ step: 0.01,
2610
+ parent: "aoMap"
2611
+ },
2612
+ emissive: {
2613
+ dir: "emissive"
2614
+ },
2615
+ emissiveMap: {
2616
+ dir: "emissive"
2617
+ },
2618
+ emissiveIntensity: {
2619
+ dir: "emissive",
2620
+ min: 0,
2621
+ max: 1,
2622
+ step: 0.01,
2623
+ parent: "emissiveMap"
2624
+ },
2625
+ normalMap: {
2626
+ dir: "normal"
2627
+ },
2628
+ normalScale: {
2629
+ dir: "normal",
2630
+ parent: "normalMap"
2631
+ },
2632
+ displacementScale: {
2633
+ dir: "displacement",
2634
+ min: 0,
2635
+ max: 1,
2636
+ step: 0.01,
2637
+ parent: "displacementMap"
2638
+ },
2639
+ displacementBias: {
2640
+ dir: "displacement",
2641
+ parent: "displacementMap"
2642
+ },
2643
+ envMap: {
2644
+ dir: "envMap"
2645
+ },
2646
+ envMapIntensity: {
2647
+ dir: "envMap",
2648
+ min: 0,
2649
+ max: 1,
2650
+ step: 0.01
2651
+ },
2652
+ lightMap: {
2653
+ dir: "lightMap"
2654
+ },
2655
+ lightMapIntensity: {
2656
+ dir: "lightMap",
2657
+ min: 0,
2658
+ max: 1,
2659
+ step: 0.01,
2660
+ parent: "lightMap"
2661
+ }
2662
+ };
2663
+ for(let k in meshStandardMaterialProperties){
2664
+ property(meshStandardMaterialProperties[k])(MeshStandardMaterial.prototype, k);
2665
+ }
2666
+
2667
+ /**
2668
+ * @author mrdoob / http://mrdoob.com/
2669
+ */ function format(v) {
2670
+ if (v > 1000) {
2671
+ return Math.floor(v * 0.001) + "k";
2672
+ } else if (v > 1000000) {
2673
+ return Math.floor(v * 0.000001) + "m";
2674
+ } else {
2675
+ return v;
2676
+ }
2677
+ }
2678
+ var Panel = function(name, fg, bg) {
2679
+ var min = Infinity, max = 0, round = Math.round;
2680
+ var PR = round(window.devicePixelRatio || 1);
2681
+ var WIDTH = 80 * PR, HEIGHT = 48 * PR, TEXT_X = 3 * PR, TEXT_Y = 2 * PR, GRAPH_X = 3 * PR, GRAPH_Y = 15 * PR, GRAPH_WIDTH = 74 * PR, GRAPH_HEIGHT = 30 * PR;
2682
+ var canvas = document.createElement('canvas');
2683
+ canvas.width = WIDTH;
2684
+ canvas.height = HEIGHT;
2685
+ canvas.style.cssText = 'width:80px;height:48px';
2686
+ var context = canvas.getContext('2d');
2687
+ context.font = 'bold ' + 9 * PR + 'px Helvetica,Arial,sans-serif';
2688
+ context.textBaseline = 'top';
2689
+ context.fillStyle = bg;
2690
+ context.fillRect(0, 0, WIDTH, HEIGHT);
2691
+ context.fillStyle = fg;
2692
+ context.fillText(name, TEXT_X, TEXT_Y);
2693
+ context.fillRect(GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT);
2694
+ context.fillStyle = bg;
2695
+ context.globalAlpha = 0.9;
2696
+ context.fillRect(GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT);
2697
+ return {
2698
+ dom: canvas,
2699
+ update: function(value, maxValue) {
2700
+ min = Math.min(min, value);
2701
+ max = Math.max(max, value);
2702
+ context.fillStyle = bg;
2703
+ context.globalAlpha = 1;
2704
+ context.fillRect(0, 0, WIDTH, GRAPH_Y);
2705
+ context.fillStyle = fg;
2706
+ context.fillText(format(round(value)) + ' ' + name + ' (' + format(round(min)) + '-' + format(round(max)) + ')', TEXT_X, TEXT_Y);
2707
+ context.drawImage(canvas, GRAPH_X + PR, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT, GRAPH_X, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT);
2708
+ context.fillRect(GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, GRAPH_HEIGHT);
2709
+ context.fillStyle = bg;
2710
+ context.globalAlpha = 0.9;
2711
+ context.fillRect(GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, round((1 - value / maxValue) * GRAPH_HEIGHT));
2712
+ }
2713
+ };
2714
+ };
2715
+ var Stats = function() {
2716
+ var mode = 0;
2717
+ var container = document.createElement('div');
2718
+ container.style.cssText = 'position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000';
2719
+ container.addEventListener('click', function(event) {
2720
+ event.preventDefault();
2721
+ showPanel(++mode % container.children.length);
2722
+ }, false);
2723
+ //
2724
+ function addPanel(panel) {
2725
+ container.appendChild(panel.dom);
2726
+ return panel;
2727
+ }
2728
+ function showPanel(id) {
2729
+ for(var i = 0; i < container.children.length; i++){
2730
+ container.children[i].style.display = i === id ? 'block' : 'none';
2731
+ }
2732
+ mode = id;
2733
+ }
2734
+ //
2735
+ var beginTime = (performance || Date).now(), prevTime = beginTime, frames = 0;
2736
+ var fpsPanel = addPanel(new Panel('FPS', '#0ff', '#002'));
2737
+ var msPanel = addPanel(new Panel('MS', '#0f0', '#020'));
2738
+ if (self.performance && self.performance.memory) {
2739
+ var memPanel = addPanel(new Panel('MB', '#f08', '#201'));
2740
+ }
2741
+ showPanel(0);
2742
+ return {
2743
+ REVISION: 16,
2744
+ dom: container,
2745
+ addPanel: addPanel,
2746
+ showPanel: showPanel,
2747
+ begin: function() {
2748
+ beginTime = (performance || Date).now();
2749
+ },
2750
+ end: function() {
2751
+ frames++;
2752
+ var time = (performance || Date).now();
2753
+ msPanel.update(time - beginTime, 200);
2754
+ if (time >= prevTime + 1000) {
2755
+ fpsPanel.update(frames * 1000 / (time - prevTime), 100);
2756
+ prevTime = time;
2757
+ frames = 0;
2758
+ if (memPanel) {
2759
+ var memory = performance.memory;
2760
+ memPanel.update(memory.usedJSHeapSize / 1048576, memory.jsHeapSizeLimit / 1048576);
2761
+ }
2762
+ }
2763
+ return time;
2764
+ },
2765
+ update: function() {
2766
+ beginTime = this.end();
2767
+ },
2768
+ // Backwards Compatibility
2769
+ domElement: container,
2770
+ setMode: showPanel
2771
+ };
2772
+ };
2773
+
2774
+ class StatsPlugin extends Plugin {
2775
+ onUpdate(dt) {
2776
+ const info = this.viewer.renderer.info;
2777
+ this._dcPanel.update(info.render.calls, 300);
2778
+ this._triPanel.update(info.render.triangles, 500000);
2779
+ this._texPanel.update(info.memory.textures, 200);
2780
+ this._prgPanel.update(info.programs.length, 50);
2781
+ this._stats.update();
2782
+ }
2783
+ constructor(){
2784
+ super();
2785
+ this._stats = new Stats();
2786
+ this._dcPanel = new Panel('DC', '#ff8', '#221');
2787
+ this._triPanel = new Panel("TRI", '#ff8', '#221');
2788
+ this._texPanel = new Panel('TEX', '#ff8', '#221');
2789
+ this._prgPanel = new Panel('PRG', '#ff8', '#221');
2790
+ this.install = ()=>{
2791
+ this._stats.addPanel(this._dcPanel);
2792
+ this._stats.addPanel(this._texPanel);
2793
+ this._stats.addPanel(this._triPanel);
2794
+ this._stats.addPanel(this._prgPanel);
2795
+ this._stats.showPanel(0);
2796
+ document.body.appendChild(this._stats.dom);
2797
+ };
2798
+ this.uninstall = ()=>{
2799
+ document.body.removeChild(this._stats.dom);
2800
+ };
2801
+ }
2802
+ }
2803
+
2804
+ export { GUI, InspectorPlugin, StatsPlugin };
2805
+ //# sourceMappingURL=module.js.map