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