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