├── .editorconfig
├── .gitignore
├── .nojekyll
├── .npmignore
├── CHANGELOG.md
├── GUIControl.js
├── LICENSE.md
├── README.md
├── examples
├── all-controls.js
├── assets
│ ├── noise.png
│ ├── palette-hsl.png
│ ├── palette.jpg
│ ├── pex.png
│ ├── pisa
│ │ ├── pisa.txt
│ │ ├── pisa_negx.jpg
│ │ ├── pisa_negy.jpg
│ │ ├── pisa_negz.jpg
│ │ ├── pisa_posx.jpg
│ │ ├── pisa_posy.jpg
│ │ └── pisa_posz.jpg
│ ├── plask.png
│ └── rainbow.jpg
├── canvas-2d.js
├── debug.js
├── options.js
├── pex-context.js
└── theme.js
├── index.html
├── index.js
├── package-lock.json
├── package.json
├── renderers
├── CanvasRenderer.js
├── DebugRenderer.js
├── PexContextRenderer.js
└── index.js
├── screenshot.png
├── shaders
├── chunks
│ ├── encode-decode.glsl.js
│ ├── gamma.glsl.js
│ └── rgbm.glsl.js
├── main.vert.js
├── texture-2d.frag.js
└── texture-cube.frag.js
├── theme.js
├── types.js
└── web_modules
├── _chunks
├── _commonjsHelpers-BFTU3MAI.js
├── avec3-CX_9gCVx.js
├── mat4-DkKmQtAV.js
├── polyfills-BwRuO6W0.js
├── ray-bLshgWN6.js
└── vec3-DW1VLBq6.js
├── es-module-shims.js
├── es-module-shims
├── debug.js
├── typescript-transform.js
└── wasm.js
├── import-map.json
├── pex-cam.js
├── pex-color.js
├── pex-context.js
├── pex-geom.js
├── pex-io.js
├── pex-math.js
└── primitive-geometry.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | /types
4 | /lib
5 |
--------------------------------------------------------------------------------
/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pex-gl/pex-gui/93ee94a82801cd78cc36077dc3e0fd194223a592/.nojekyll
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /web_modules
2 | /examples
3 | /docs
4 | /coverage
5 | /test
6 | /.github
7 | screenshot.*
8 | index.html
9 | tsconfig.json
10 | .editorconfig
11 | .nojekyll
12 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
4 |
5 | ## [3.1.3](https://github.com/pex-gl/pex-gui/compare/v3.1.2...v3.1.3) (2025-04-11)
6 |
7 |
8 | ### Bug Fixes
9 |
10 | * add fallback font to theme ([be96f71](https://github.com/pex-gl/pex-gui/commit/be96f715ea9f755631ea69a86604480484b537be)), closes [#44](https://github.com/pex-gl/pex-gui/issues/44)
11 |
12 |
13 |
14 | ## [3.1.2](https://github.com/pex-gl/pex-gui/compare/v3.1.1...v3.1.2) (2025-03-24)
15 |
16 |
17 | ### Bug Fixes
18 |
19 | * ensure graph values don't overflow render width ([570dcaf](https://github.com/pex-gl/pex-gui/commit/570dcaf35149b356b5a8438864cf876a5b7c9e50))
20 |
21 |
22 |
23 | ## [3.1.1](https://github.com/pex-gl/pex-gui/compare/v3.1.0...v3.1.1) (2024-11-26)
24 |
25 |
26 | ### Bug Fixes
27 |
28 | * allow update interval of 0 for stats and graph controls ([154220f](https://github.com/pex-gl/pex-gui/commit/154220ffec80027e4ed88d16f416ce098fed5b9a))
29 |
30 |
31 |
32 | # [3.1.0](https://github.com/pex-gl/pex-gui/compare/v3.0.1...v3.1.0) (2024-11-22)
33 |
34 |
35 | ### Bug Fixes
36 |
37 | * check for nullish when rendering graph control value ([6e5c7bb](https://github.com/pex-gl/pex-gui/commit/6e5c7bb0c946312075de03e35120224163707412))
38 | * use round instead of floor in fps graph redraw ([1373b6b](https://github.com/pex-gl/pex-gui/commit/1373b6b894539396262e67e978d32c8ad9f1ba05))
39 |
40 |
41 | ### Features
42 |
43 | * add format support to graph controls ([506553a](https://github.com/pex-gl/pex-gui/commit/506553aaa04bf526adf4e6c13a3ceba96de59ec8))
44 | * handle multiline and empty string title in control stats ([285e223](https://github.com/pex-gl/pex-gui/commit/285e223447a4f3e6fde422f990939576809de54a))
45 |
46 |
47 |
48 | ## [3.0.1](https://github.com/pex-gl/pex-gui/compare/v3.0.0...v3.0.1) (2024-07-09)
49 |
50 |
51 |
52 | # [3.0.0](https://github.com/pex-gl/pex-gui/compare/v3.0.0-alpha.3...v3.0.0) (2024-02-05)
53 |
54 |
55 |
56 | # [3.0.0-alpha.3](https://github.com/pex-gl/pex-gui/compare/v3.0.0-alpha.2...v3.0.0-alpha.3) (2023-02-27)
57 |
58 |
59 | ### Features
60 |
61 | * add title for gui.addStats ([68e7f5f](https://github.com/pex-gl/pex-gui/commit/68e7f5f251da1c1afb265873e0e5255b983985ff))
62 |
63 |
64 |
65 | # [3.0.0-alpha.2](https://github.com/pex-gl/pex-gui/compare/v3.0.0-alpha.1...v3.0.0-alpha.2) (2022-09-09)
66 |
67 |
68 | ### Bug Fixes
69 |
70 | * don't assume pixel ratio from pex-context ([b8a6d9f](https://github.com/pex-gl/pex-gui/commit/b8a6d9f3b502643a1a189e509113510886928c3d))
71 |
72 |
73 | ### Features
74 |
75 | * make gui render at the size regardless of the canvas size or pixel ratio unless it overflows the canvas viewport ([17eab56](https://github.com/pex-gl/pex-gui/commit/17eab565705b031fc96826dee434a60b46534e08))
76 |
77 |
78 |
79 | # [3.0.0-alpha.1](https://github.com/pex-gl/pex-gui/compare/v3.0.0-alpha.0...v3.0.0-alpha.1) (2022-07-26)
80 |
81 |
82 |
83 | # [3.0.0-alpha.0](https://github.com/pex-gl/pex-gui/compare/v2.4.0...v3.0.0-alpha.0) (2022-07-26)
84 |
85 |
86 | ### Bug Fixes
87 |
88 | * add missing GUIControl.setPosition ([b9bf584](https://github.com/pex-gl/pex-gui/commit/b9bf5840fecd0144a6b8639ffcbd0a940bbcd4f6))
89 | * allow hardcoded GUIControl position x/y ([278c787](https://github.com/pex-gl/pex-gui/commit/278c7870a9f5b5c5c0f08e824102dfe135860333))
90 | * handle nullish options in addParam ([fbb6599](https://github.com/pex-gl/pex-gui/commit/fbb6599eadbe2647b5f1e6cc4c7f9f86c794e9c9))
91 |
92 |
93 | ### Code Refactoring
94 |
95 | * use ES modules ([ca261fa](https://github.com/pex-gl/pex-gui/commit/ca261faa2568dad81ca8c0467da26339df0063e8))
96 |
97 |
98 | ### Features
99 |
100 | * add flipY option for 2D textures ([368dabb](https://github.com/pex-gl/pex-gui/commit/368dabbe60645bcc3ec55f0789bf8b82a55b2c7f)), closes [#38](https://github.com/pex-gl/pex-gui/issues/38)
101 | * allow gui.pixelRatio to be updated ([e9169db](https://github.com/pex-gl/pex-gui/commit/e9169db7f0c46fda741eeedbfd2b00eee2a64e28))
102 | * allow scale and responsive as options ([e27ca9c](https://github.com/pex-gl/pex-gui/commit/e27ca9ccc53cdd4dc86241cdfa5ca4a7088fef15))
103 | * move to pointer events ([e64c6d1](https://github.com/pex-gl/pex-gui/commit/e64c6d12d1742be7a88885559b96c6c41438b55f))
104 | * update pex-color to latest api ([9faa62a](https://github.com/pex-gl/pex-gui/commit/9faa62af0e5e48b7c54c1d3db7a52b451dd972ee))
105 |
106 |
107 | ### BREAKING CHANGES
108 |
109 | * switch to type module
110 |
--------------------------------------------------------------------------------
/GUIControl.js:
--------------------------------------------------------------------------------
1 | import { fromHSL, toHSL } from "pex-color";
2 |
3 | class GUIControl {
4 | constructor(options) {
5 | Object.assign(this, options);
6 | }
7 |
8 | setPosition(x, y) {
9 | this.x = x;
10 | this.y = y;
11 | }
12 |
13 | getNormalizedValue(idx) {
14 | if (!this.contextObject) return 0;
15 |
16 | let val = this.contextObject[this.attributeName];
17 | if (
18 | this.options &&
19 | this.options.min !== undefined &&
20 | this.options.max !== undefined
21 | ) {
22 | if (this.type === "multislider") {
23 | val =
24 | (val[idx] - this.options.min) / (this.options.max - this.options.min);
25 | } else if (this.type === "color") {
26 | const hsl = toHSL(val);
27 | if (idx === 0) val = hsl[0];
28 | if (idx === 1) val = hsl[1];
29 | if (idx === 2) val = hsl[2];
30 | if (idx === 3) val = val[3];
31 | } else {
32 | val = (val - this.options.min) / (this.options.max - this.options.min);
33 | }
34 | }
35 | return val;
36 | }
37 |
38 | setNormalizedValue(val, idx) {
39 | if (!this.contextObject) return;
40 |
41 | if (
42 | this.options &&
43 | this.options.min !== undefined &&
44 | this.options.max !== undefined
45 | ) {
46 | if (this.type === "multislider") {
47 | const a = this.contextObject[this.attributeName];
48 | if (idx >= a.length) {
49 | return;
50 | }
51 | a[idx] = this.options.min + val * (this.options.max - this.options.min);
52 | val = a;
53 | } else if (this.type === "color") {
54 | const c = this.contextObject[this.attributeName];
55 | if (idx === 3) {
56 | c[3] = val;
57 | } else {
58 | const hsl = toHSL(c);
59 | if (idx === 0) hsl[0] = val;
60 | if (idx === 1) hsl[1] = val;
61 | if (idx === 2) hsl[2] = val;
62 | fromHSL(c, ...hsl);
63 | }
64 |
65 | val = c;
66 | } else {
67 | val = this.options.min + val * (this.options.max - this.options.min);
68 | }
69 | if (this.options && this.options.step) {
70 | val = val - (val % this.options.step);
71 | }
72 | }
73 | this.contextObject[this.attributeName] = val;
74 | }
75 |
76 | getSerializedValue() {
77 | return this.contextObject ? this.contextObject[this.attributeName] : "";
78 | }
79 |
80 | setSerializedValue(value) {
81 | if (this.type === "slider") {
82 | this.contextObject[this.attributeName] = value;
83 | } else if (this.type === "multislider") {
84 | this.contextObject[this.attributeName] = value;
85 | } else if (this.type === "color") {
86 | this.contextObject[this.attributeName].r = value.r;
87 | this.contextObject[this.attributeName].g = value.g;
88 | this.contextObject[this.attributeName].b = value.b;
89 | this.contextObject[this.attributeName].a = value.a;
90 | } else if (this.type === "toggle") {
91 | this.contextObject[this.attributeName] = value;
92 | } else if (this.type === "radiolist") {
93 | this.contextObject[this.attributeName] = value;
94 | }
95 | }
96 |
97 | getValue() {
98 | if (this.type === "slider") {
99 | return this.contextObject[this.attributeName];
100 | } else if (this.type === "multislider") {
101 | return this.contextObject[this.attributeName];
102 | } else if (this.type === "color") {
103 | return this.contextObject[this.attributeName];
104 | } else if (this.type === "toggle") {
105 | return this.contextObject[this.attributeName];
106 | } else {
107 | return 0;
108 | }
109 | }
110 |
111 | getStrValue() {
112 | if (this.type === "slider") {
113 | const str = `${this.contextObject[this.attributeName]}`;
114 | let dotPos = str.indexOf(".") + 1;
115 |
116 | if (dotPos === 0) return `${str}.0`;
117 |
118 | while (str.charAt(dotPos) === "0") {
119 | dotPos++;
120 | }
121 | return str.substr(0, dotPos + 2);
122 | } else if (this.type === "color") {
123 | return this.options.alpha ? "HSLA" : "HSL";
124 | } else if (this.type === "toggle") {
125 | return this.contextObject[this.attributeName];
126 | } else {
127 | return "";
128 | }
129 | }
130 | }
131 |
132 | export default GUIControl;
133 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012-2014 Marcin Ignac
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pex-gui
2 |
3 | [](https://www.npmjs.com/package/pex-gui)
4 | [](https://www.npmjs.com/package/pex-gui)
5 | [](https://bundlephobia.com/package/pex-gui)
6 | [](https://github.com/pex-gl/pex-gui/blob/main/package.json)
7 | [](https://github.com/microsoft/TypeScript)
8 | [](https://conventionalcommits.org)
9 | [](https://github.com/prettier/prettier)
10 | [](https://github.com/eslint/eslint)
11 | [](https://github.com/pex-gl/pex-gui/blob/main/LICENSE.md)
12 |
13 | GUI controls for [PEX](https://pex.gl).
14 |
15 | 
16 |
17 | ## Installation
18 |
19 | ```bash
20 | npm install pex-gui
21 | ```
22 |
23 | ## Usage
24 |
25 | ```js
26 | import createGUI from "pex-gui";
27 | import createContext from "pex-context";
28 | import { loadImage } from "pex-io";
29 |
30 | const ctx = createContext({ pixelRatio: 2 });
31 | const gui = createGUI(ctx);
32 |
33 | const res = await load({
34 | palette: { image: `examples/assets/palette.jpg` },
35 | paletteHsl: { image: `examples/assets/palette-hsl.png` },
36 | plask: { image: `examples/assets/plask.png` },
37 | pex: { image: `examples/assets/pex.png` },
38 | noise: { image: `examples/assets/noise.png` },
39 | posx: { image: `examples/assets/pisa/pisa_posx.jpg` },
40 | negx: { image: `examples/assets/pisa/pisa_negx.jpg` },
41 | posy: { image: `examples/assets/pisa/pisa_posy.jpg` },
42 | negy: { image: `examples/assets/pisa/pisa_negy.jpg` },
43 | posz: { image: `examples/assets/pisa/pisa_posz.jpg` },
44 | negz: { image: `examples/assets/pisa/pisa_negz.jpg` },
45 | });
46 |
47 | const images = [res.plask, res.pex, res.noise];
48 |
49 | const State = {
50 | currentRadioListChoice: 0,
51 | radioListChoices: ["Choice 1", "Choice 2", "Choice 3"].map((name, value) => ({
52 | name,
53 | value,
54 | })),
55 | checkboxValue: false,
56 | message: "Message",
57 | range: 0,
58 | position: [2, 0],
59 | rgb: [0.92, 0.2, 0.2],
60 | rgba: [0.2, 0.92, 0.2, 1.0],
61 | palette: Float32Array.of(0.2, 0.2, 0.92, 1.0),
62 | paletteHsl: Float32Array.of(0.92, 0.2, 0.92, 1.0),
63 | cubeTexture: ctx.textureCube({
64 | data: [res.posx, res.negx, res.posy, res.negy, res.posz, res.negz],
65 | width: 64,
66 | height: 64,
67 | }),
68 | currentTexture: 0,
69 | textures: images.map((image) =>
70 | ctx.texture2D({
71 | data: image,
72 | width: image.width,
73 | height: image.height,
74 | flipY: true,
75 | wrap: ctx.Wrap.Repeat,
76 | encoding: ctx.Encoding.SRGB,
77 | mipmap: true,
78 | min: ctx.Filter.LinearMipmapLinear,
79 | aniso: 16,
80 | })
81 | ),
82 | };
83 |
84 | // Controls
85 | gui.addTab("Controls");
86 | gui.addColumn("Inputs");
87 | gui.addLabel("Special Parameters");
88 | gui.addLabel("Multiline\nLabel");
89 | gui.addButton("Button", () => {
90 | console.log("Called back");
91 | });
92 | gui.addRadioList(
93 | "Radio list",
94 | State,
95 | "currentRadioListChoice",
96 | State.radioListChoices
97 | );
98 |
99 | gui.addSeparator();
100 | gui.addLabel("Smart Parameters");
101 | gui.addParam("Checkbox", State, "checkboxValue");
102 | gui.addParam("Text message", State, "message", {}, (value) => {
103 | console.log(value);
104 | });
105 | gui.addParam("Slider", State, "range", {
106 | min: -Math.PI / 2,
107 | max: Math.PI / 2,
108 | });
109 | gui.addParam("Multi Slider", State, "position", {
110 | min: 0,
111 | max: 10,
112 | });
113 |
114 | gui.addColumn("Colors");
115 | gui.addParam("Color", State, "rgb", {
116 | type: "color",
117 | });
118 | gui.addParam("Color alpha", State, "rgba", {
119 | type: "color",
120 | alpha: true,
121 | });
122 | gui.addParam("Palette", State, "palette", {
123 | type: "color",
124 | palette: res.palette,
125 | });
126 | gui.addParam("Palette HSL", State, "paletteHsl", {
127 | type: "color",
128 | palette: res.paletteHsl,
129 | });
130 |
131 | gui.addColumn("Textures");
132 | gui.addTexture2D("Single", State.textures[0]);
133 | gui.addTexture2DList(
134 | "List",
135 | State,
136 | "currentTexture",
137 | State.textures.map((texture, value) => ({
138 | texture,
139 | value,
140 | }))
141 | );
142 | gui.addTextureCube("Cube", State.cubeTexture, { level: 2 });
143 |
144 | gui.addColumn("Graphs");
145 | gui.addGraph("Sin", {
146 | interval: 500,
147 | t: 0,
148 | update(item) {
149 | item.options.t += 0.01;
150 | },
151 | redraw(item) {
152 | item.values.push(+Math.sin(item.options.t).toFixed(3));
153 | },
154 | });
155 | gui.addFPSMeeter();
156 | gui.addHeader("Stats");
157 | gui.addStats();
158 | gui.addStats("Object stats", {
159 | update(item) {
160 | Object.assign(item.stats, {
161 | r: State.rgb[0],
162 | g: State.rgb[1],
163 | b: State.rgb[2],
164 | });
165 | },
166 | });
167 | ```
168 |
169 | ## API
170 |
171 |
172 |
173 | ## Modules
174 |
175 |
176 | - pex-gui
177 |
178 |
179 |
180 | ## Classes
181 |
182 |
183 | - GUI
184 | GUI controls for PEX.
185 |
186 |
187 |
188 | ## Typedefs
189 |
190 |
191 | - GUIControlOptions :
object
192 |
193 | - GUIOptions :
object
194 |
195 | - ctx :
module:pex-context~ctx
196 |
197 |
198 |
199 |
200 |
201 | ## pex-gui
202 |
203 | **Summary**: Export a factory function for creating a GUI instance.
204 |
205 |
206 | ### pex-gui.default(ctx, opts) ⇒ [GUI
](#GUI)
207 |
208 | **Kind**: static method of [pex-gui
](#module_pex-gui)
209 |
210 | | Param | Type |
211 | | ----- | ----------------------------------------------------------------- |
212 | | ctx | [ctx
](#ctx) \| CanvasRenderingContext2D
|
213 | | opts | [GUIOptions
](#GUIOptions) |
214 |
215 |
216 |
217 | ## GUI
218 |
219 | GUI controls for PEX.
220 |
221 | **Kind**: global class
222 | **Properties**
223 |
224 | | Name | Type | Default | Description |
225 | | --------- | -------------------- | ----------------- | ----------------------------------------------- |
226 | | [enabled] | boolean
| true
| Enable/disable pointer interaction and drawing. |
227 |
228 | - [GUI](#GUI)
229 | - [new GUI(ctx, opts)](#new_GUI_new)
230 | - [.addTab(title, contextObject, attributeName, [options], onChange)](#GUI+addTab) ⇒ GUIControl
231 | - [.addColumn(title, [width])](#GUI+addColumn) ⇒ GUIControl
232 | - [.addHeader(title)](#GUI+addHeader) ⇒ GUIControl
233 | - [.addSeparator()](#GUI+addSeparator) ⇒ GUIControl
234 | - [.addLabel(title)](#GUI+addLabel) ⇒ GUIControl
235 | - [.addParam(title, contextObject, attributeName, [options], onChange)](#GUI+addParam) ⇒ GUIControl
236 | - [.addButton(title, onClick)](#GUI+addButton) ⇒ GUIControl
237 | - [.addRadioList(title, contextObject, attributeName, items, onChange)](#GUI+addRadioList) ⇒ GUIControl
238 | - [.addTexture2DList(title, contextObject, attributeName, items, [itemsPerRow], onChange)](#GUI+addTexture2DList) ⇒ GUIControl
239 | - [.addTexture2D(title, texture, options)](#GUI+addTexture2D) ⇒ GUIControl
240 | - [.addTextureCube(title, texture, options)](#GUI+addTextureCube) ⇒ GUIControl
241 | - [.addGraph(title, options)](#GUI+addGraph) ⇒ GUIControl
242 | - [.addFPSMeeter()](#GUI+addFPSMeeter) ⇒ GUIControl
243 | - [.addStats(title, [options])](#GUI+addStats) ⇒ GUIControl
244 | - [.draw()](#GUI+draw)
245 | - [.serialize()](#GUI+serialize) ⇒ object
246 | - [.deserialize(data)](#GUI+deserialize)
247 | - [.dispose()](#GUI+dispose)
248 |
249 |
250 |
251 | ### new GUI(ctx, opts)
252 |
253 | Creates an instance of GUI.
254 |
255 | | Param | Type |
256 | | ----- | ----------------------------------------------------------------- |
257 | | ctx | [ctx
](#ctx) \| CanvasRenderingContext2D
|
258 | | opts | [GUIOptions
](#GUIOptions) |
259 |
260 |
261 |
262 | ### guI.addTab(title, contextObject, attributeName, [options], onChange) ⇒ GUIControl
263 |
264 | Add a tab control.
265 |
266 | **Kind**: instance method of [GUI
](#GUI)
267 |
268 | | Param | Type | Default |
269 | | ------------- | ---------------------------------------------------- | --------------- |
270 | | title | string
| |
271 | | contextObject | object
| |
272 | | attributeName | string
| |
273 | | [options] | [GUIControlOptions
](#GUIControlOptions) | {}
|
274 | | onChange | function
| |
275 |
276 |
277 |
278 | ### guI.addColumn(title, [width]) ⇒ GUIControl
279 |
280 | Add a column control with a header.
281 |
282 | **Kind**: instance method of [GUI
](#GUI)
283 |
284 | | Param | Type | Default |
285 | | ------- | ------------------- | ----------------------------------- |
286 | | title | string
| |
287 | | [width] | number
| this.theme.columnWidth
|
288 |
289 |
290 |
291 | ### guI.addHeader(title) ⇒ GUIControl
292 |
293 | Add a header control.
294 |
295 | **Kind**: instance method of [GUI
](#GUI)
296 |
297 | | Param | Type |
298 | | ----- | ------------------- |
299 | | title | string
|
300 |
301 |
302 |
303 | ### guI.addSeparator() ⇒ GUIControl
304 |
305 | Add some breathing space between controls.
306 |
307 | **Kind**: instance method of [GUI
](#GUI)
308 |
309 |
310 | ### guI.addLabel(title) ⇒ GUIControl
311 |
312 | Add a text label. Can be multiple line.
313 |
314 | **Kind**: instance method of [GUI
](#GUI)
315 |
316 | | Param | Type |
317 | | ----- | ------------------- |
318 | | title | string
|
319 |
320 | **Example**
321 |
322 | ```js
323 | gui.addLabel("Multiline\nLabel");
324 | ```
325 |
326 |
327 |
328 | ### guI.addParam(title, contextObject, attributeName, [options], onChange) ⇒ GUIControl
329 |
330 | Add a generic parameter control.
331 |
332 | **Kind**: instance method of [GUI
](#GUI)
333 |
334 | | Param | Type | Default |
335 | | ------------- | ---------------------------------------------------- | --------------- |
336 | | title | string
| |
337 | | contextObject | object
| |
338 | | attributeName | string
| |
339 | | [options] | [GUIControlOptions
](#GUIControlOptions) | {}
|
340 | | onChange | function
| |
341 |
342 | **Example**
343 |
344 | ```js
345 | gui.addParam("Checkbox", State, "rotate");
346 |
347 | gui.addParam("Text message", State, "text", {}, function (value) {
348 | console.log(value);
349 | });
350 |
351 | gui.addParam("Slider", State, "range", {
352 | min: -Math.PI / 2,
353 | max: Math.PI / 2,
354 | });
355 |
356 | gui.addParam("Multi Slider", State, "position", {
357 | min: 0,
358 | max: 10,
359 | });
360 |
361 | gui.addParam("Color [RGBA]", State, "color");
362 |
363 | gui.addParam("Texture", State, "texture");
364 | gui.addParam("Texture Cube", State, "textureCube");
365 | ```
366 |
367 |
368 |
369 | ### guI.addButton(title, onClick) ⇒ GUIControl
370 |
371 | Add a clickable button.
372 |
373 | **Kind**: instance method of [GUI
](#GUI)
374 |
375 | | Param | Type |
376 | | ------- | --------------------- |
377 | | title | string
|
378 | | onClick | function
|
379 |
380 | **Example**
381 |
382 | ```js
383 | gui.addButton("Button", () => {
384 | console.log("Called back");
385 | });
386 | ```
387 |
388 |
389 |
390 | ### guI.addRadioList(title, contextObject, attributeName, items, onChange) ⇒ GUIControl
391 |
392 | Add a radio list with options.
393 |
394 | **Kind**: instance method of [GUI
](#GUI)
395 |
396 | | Param | Type |
397 | | ------------- | -------------------------------------------------------- |
398 | | title | string
|
399 | | contextObject | object
|
400 | | attributeName | string
|
401 | | items | Array.<{name: string, value: number}>
|
402 | | onChange | function
|
403 |
404 | **Example**
405 |
406 | ```js
407 | gui.addRadioList(
408 | "Radio list",
409 | State,
410 | "currentRadioListChoice",
411 | ["Choice 1", "Choice 2", "Choice 3"].map((name, value) => ({
412 | name,
413 | value,
414 | })),
415 | );
416 | ```
417 |
418 |
419 |
420 | ### guI.addTexture2DList(title, contextObject, attributeName, items, [itemsPerRow], onChange) ⇒ GUIControl
421 |
422 | Add a texture visualiser and selector for multiple textures (from pex-context) or images.
423 |
424 | **Kind**: instance method of [GUI
](#GUI)
425 |
426 | | Param | Type | Default |
427 | | ------------- | ---------------------------------------------------------------------------------------------------- | -------------- |
428 | | title | string
| |
429 | | contextObject | object
| |
430 | | attributeName | string
| |
431 | | items | Array.<{texture: (module:pex-context~texture\|CanvasImageSource), value: number}>
| |
432 | | [itemsPerRow] | number
| 4
|
433 | | onChange | function
| |
434 |
435 | **Example**
436 |
437 | ```js
438 | gui.addTexture2DList("List", State, "currentTexture", textures.map((texture, value) = > ({ texture, value })));
439 | ```
440 |
441 |
442 |
443 | ### guI.addTexture2D(title, texture, options) ⇒ GUIControl
444 |
445 | Add a texture (from pex-context) or image visualiser.
446 | Notes: texture cannot be updated once created.
447 |
448 | **Kind**: instance method of [GUI
](#GUI)
449 |
450 | | Param | Type |
451 | | ------- | ------------------------------------------------------------------------- |
452 | | title | string
|
453 | | texture | module:pex-context~texture
\| CanvasImageSource
|
454 | | options | [GUIControlOptions
](#GUIControlOptions) |
455 |
456 | **Example**
457 |
458 | ```js
459 | gui.addTexture2D("Single", image);
460 | ```
461 |
462 |
463 |
464 | ### guI.addTextureCube(title, texture, options) ⇒ GUIControl
465 |
466 | Add a cube texture visualiser (from pex-context).
467 | Notes: texture cannot be updated once created.
468 |
469 | **Kind**: instance method of [GUI
](#GUI)
470 |
471 | | Param | Type |
472 | | ------- | ------------------------------------------- |
473 | | title | string
|
474 | | texture | module:pex-context~textureCube
|
475 | | options | Object
|
476 |
477 | **Example**
478 |
479 | ```js
480 | gui.addTextureCube("Cube", State.cubeTexture, { level: 2 });
481 | ```
482 |
483 |
484 |
485 | ### guI.addGraph(title, options) ⇒ GUIControl
486 |
487 | Add a XY graph visualiser from the control values.
488 |
489 | **Kind**: instance method of [GUI
](#GUI)
490 |
491 | | Param | Type |
492 | | ------- | ---------------------------------------------------- |
493 | | title | string
|
494 | | options | [GUIControlOptions
](#GUIControlOptions) |
495 |
496 | **Example**
497 |
498 | ```js
499 | gui.addGraph("Sin", {
500 | interval: 500,
501 | t: 0,
502 | update(item) {
503 | item.options.t += 0.01;
504 | },
505 | redraw(item) {
506 | item.values.push(+Math.sin(item.options.t).toFixed(3));
507 | },
508 | });
509 | ```
510 |
511 |
512 |
513 | ### guI.addFPSMeeter() ⇒ GUIControl
514 |
515 | Add a FPS counter. Need "gui.draw()" to be called on frame.
516 |
517 | **Kind**: instance method of [GUI
](#GUI)
518 |
519 |
520 | ### guI.addStats(title, [options]) ⇒ GUIControl
521 |
522 | Add an updatable object stats visualiser.
523 |
524 | **Kind**: instance method of [GUI
](#GUI)
525 |
526 | | Param | Type | Description |
527 | | --------- | ------------------- | ------------------------------------------------------------ |
528 | | title | string
| |
529 | | [options] | object
| An object with an update() function to update control.stats. |
530 |
531 |
532 |
533 | ### guI.draw()
534 |
535 | Renders the GUI. Should be called at the end of the frame.
536 |
537 | **Kind**: instance method of [GUI
](#GUI)
538 |
539 |
540 | ### guI.serialize() ⇒ object
541 |
542 | Retrieve a serialized value of the current GUI's state.
543 |
544 | **Kind**: instance method of [GUI
](#GUI)
545 |
546 |
547 | ### guI.deserialize(data)
548 |
549 | Deserialize a previously serialized data state GUI's state.
550 |
551 | **Kind**: instance method of [GUI
](#GUI)
552 |
553 | | Param | Type |
554 | | ----- | ------------------- |
555 | | data | object
|
556 |
557 |
558 |
559 | ### guI.dispose()
560 |
561 | Remove events listeners, empty list of controls and dispose of the gui's resources.
562 |
563 | **Kind**: instance method of [GUI
](#GUI)
564 |
565 |
566 | ## GUIControlOptions : object
567 |
568 | **Kind**: global typedef
569 | **Properties**
570 |
571 | | Name | Type | Default | Description |
572 | | ------------ | ------------------------------ | -------------- | ----------------------------------------------------------------------------------- |
573 | | [min] | number
| 0
| |
574 | | [max] | number
| 0
| |
575 | | [type] | "color"
| | Interpret an array as color. |
576 | | [colorSpace] | string
| | Display a color as values of a pex-color color space. |
577 | | [alpha] | boolean
| | Add a 4th slider for colors. |
578 | | [palette] | HTMLImageElement
| | Draw a palette image as color picker. |
579 | | [flipEnvMap] | boolean
| | Should be 1 for dynamic cubemaps and -1 for cubemaps from file with X axis flipped. |
580 | | [flipY] | boolean
| | Flip texture 2D vertically. |
581 | | [level] | number
| | Level of detail for cube textures. |
582 |
583 |
584 |
585 | ## GUIOptions : object
586 |
587 | **Kind**: global typedef
588 | **Properties**
589 |
590 | | Name | Type | Default | Description |
591 | | ------------ | -------------------- | ------------------------------------ | --------------------------------------------------------------------------------------- |
592 | | [pixelRatio] | boolean
| window.devicePixelRatio
| |
593 | | [theme] | boolean
| {}
| See [theme file](https://github.com/pex-gl/pex-gui/blob/main/theme.js) for all options. |
594 | | [scale] | number
| 1
| |
595 | | [responsive] | boolean
| true
| Adapts to canvas dimension. |
596 |
597 |
598 |
599 | ## ctx : module:pex-context~ctx
600 |
601 | **Kind**: global typedef
602 |
603 |
604 |
605 | ## License
606 |
607 | MIT. See [license file](https://github.com/pex-gl/pex-gui/blob/main/LICENSE.md).
608 |
--------------------------------------------------------------------------------
/examples/all-controls.js:
--------------------------------------------------------------------------------
1 | import { load } from "pex-io";
2 |
3 | export default async function addAllControls(gui, ctx) {
4 | const res = await load({
5 | palette: { image: `examples/assets/palette.jpg` },
6 | paletteHsl: { image: `examples/assets/palette-hsl.png` },
7 | plask: { image: `examples/assets/plask.png` },
8 | pex: { image: `examples/assets/pex.png` },
9 | noise: { image: `examples/assets/noise.png` },
10 | posx: { image: `examples/assets/pisa/pisa_posx.jpg` },
11 | negx: { image: `examples/assets/pisa/pisa_negx.jpg` },
12 | posy: { image: `examples/assets/pisa/pisa_posy.jpg` },
13 | negy: { image: `examples/assets/pisa/pisa_negy.jpg` },
14 | posz: { image: `examples/assets/pisa/pisa_posz.jpg` },
15 | negz: { image: `examples/assets/pisa/pisa_negz.jpg` },
16 | });
17 |
18 | const isPexGl = ctx.gl;
19 |
20 | const images = [res.plask, res.pex, res.noise];
21 |
22 | const State = {
23 | currentRadioListChoice: 0,
24 | radioListChoices: ["Choice 1", "Choice 2", "Choice 3"].map(
25 | (name, value) => ({
26 | name,
27 | value,
28 | }),
29 | ),
30 | checkboxValue: false,
31 | message: "Message",
32 | range: 0,
33 | position: [2, 0],
34 | rgb: [0.92, 0.2, 0.2],
35 | rgba: [0.2, 0.92, 0.2, 1.0],
36 | palette: Float32Array.of(0.2, 0.2, 0.92, 1.0),
37 | paletteHsl: Float32Array.of(0.92, 0.2, 0.92, 1.0),
38 | cubeTexture: isPexGl
39 | ? ctx.textureCube({
40 | data: [res.posx, res.negx, res.posy, res.negy, res.posz, res.negz],
41 | width: 64,
42 | height: 64,
43 | })
44 | : null,
45 | currentTexture: 0,
46 | textures: isPexGl
47 | ? images.map((image) =>
48 | ctx.texture2D({
49 | data: image,
50 | width: image.width,
51 | height: image.height,
52 | flipY: true,
53 | wrap: ctx.Wrap.Repeat,
54 | encoding: ctx.Encoding.SRGB,
55 | mipmap: true,
56 | min: ctx.Filter.LinearMipmapLinear,
57 | aniso: 16,
58 | }),
59 | )
60 | : images,
61 | };
62 |
63 | // Controls
64 | gui.addTab("Controls");
65 | gui.addColumn("Inputs");
66 | gui.addLabel("Special Parameters");
67 | gui.addLabel("Multiline\nLabel");
68 | gui.addButton("Button", () => {
69 | console.log("Called back");
70 | });
71 | gui.addRadioList(
72 | "Radio list",
73 | State,
74 | "currentRadioListChoice",
75 | State.radioListChoices,
76 | );
77 |
78 | gui.addSeparator();
79 | gui.addLabel("Smart Parameters");
80 | gui.addParam("Checkbox", State, "checkboxValue");
81 | gui.addParam("Text message", State, "message", {}, (value) => {
82 | console.log(value);
83 | });
84 | gui.addParam("Slider", State, "range", {
85 | min: -Math.PI / 2,
86 | max: Math.PI / 2,
87 | });
88 | gui.addParam("Multi Slider", State, "position", {
89 | min: 0,
90 | max: 10,
91 | });
92 |
93 | gui.addColumn("Colors");
94 | gui.addParam("Color", State, "rgb", {
95 | type: "color",
96 | });
97 | gui.addParam("Color alpha", State, "rgba", {
98 | type: "color",
99 | alpha: true,
100 | });
101 | gui.addParam("Palette", State, "palette", {
102 | type: "color",
103 | palette: res.palette,
104 | });
105 | gui.addParam("Palette HSL", State, "paletteHsl", {
106 | type: "color",
107 | palette: res.paletteHsl,
108 | });
109 |
110 | gui.addColumn("Textures");
111 | gui.addTexture2D("Single", State.textures[0]); // or gui.addParam("Single", State, "texture");
112 | gui.addTexture2D("Single flipped", State.textures[0], { flipY: true });
113 | gui.addTexture2DList(
114 | "List",
115 | State,
116 | "currentTexture",
117 | State.textures.map((texture, value) => ({
118 | texture,
119 | value,
120 | })),
121 | );
122 | if (isPexGl) gui.addTextureCube("Cube", State.cubeTexture, { level: 2 }); // gui.addParam("Cube", State, "cubeTexture", { level: 2 });
123 |
124 | gui.addColumn("Graphs");
125 | gui.addGraph("Sin", {
126 | interval: 500,
127 | t: 0,
128 | update(item) {
129 | item.options.t += 0.01;
130 | },
131 | redraw(item) {
132 | item.values.push(Math.sin(item.options.t));
133 | },
134 | format: (value) => value?.toFixed(3) || "",
135 | });
136 | gui.addFPSMeeter();
137 | gui.addHeader("Stats");
138 | gui.addStats();
139 | gui.addStats("Object stats", {
140 | update(item) {
141 | Object.assign(item.stats, {
142 | r: State.rgb[0],
143 | g: State.rgb[1],
144 | b: State.rgb[2],
145 | });
146 | },
147 | });
148 |
149 | return { State, res };
150 | }
151 |
--------------------------------------------------------------------------------
/examples/assets/noise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pex-gl/pex-gui/93ee94a82801cd78cc36077dc3e0fd194223a592/examples/assets/noise.png
--------------------------------------------------------------------------------
/examples/assets/palette-hsl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pex-gl/pex-gui/93ee94a82801cd78cc36077dc3e0fd194223a592/examples/assets/palette-hsl.png
--------------------------------------------------------------------------------
/examples/assets/palette.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pex-gl/pex-gui/93ee94a82801cd78cc36077dc3e0fd194223a592/examples/assets/palette.jpg
--------------------------------------------------------------------------------
/examples/assets/pex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pex-gl/pex-gui/93ee94a82801cd78cc36077dc3e0fd194223a592/examples/assets/pex.png
--------------------------------------------------------------------------------
/examples/assets/pisa/pisa.txt:
--------------------------------------------------------------------------------
1 | http://gl.ict.usc.edu/Data/HighResProbes/
2 |
3 | Google Maps location
4 | https://www.google.com/maps/@43.7223952,10.3982855,3a,75y,94.86t/data=!3m6!1e1!3m4!1sMbQ_X8y3hq31NwsRCx7T7Q!2e0!7i13312!8i6656
--------------------------------------------------------------------------------
/examples/assets/pisa/pisa_negx.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pex-gl/pex-gui/93ee94a82801cd78cc36077dc3e0fd194223a592/examples/assets/pisa/pisa_negx.jpg
--------------------------------------------------------------------------------
/examples/assets/pisa/pisa_negy.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pex-gl/pex-gui/93ee94a82801cd78cc36077dc3e0fd194223a592/examples/assets/pisa/pisa_negy.jpg
--------------------------------------------------------------------------------
/examples/assets/pisa/pisa_negz.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pex-gl/pex-gui/93ee94a82801cd78cc36077dc3e0fd194223a592/examples/assets/pisa/pisa_negz.jpg
--------------------------------------------------------------------------------
/examples/assets/pisa/pisa_posx.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pex-gl/pex-gui/93ee94a82801cd78cc36077dc3e0fd194223a592/examples/assets/pisa/pisa_posx.jpg
--------------------------------------------------------------------------------
/examples/assets/pisa/pisa_posy.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pex-gl/pex-gui/93ee94a82801cd78cc36077dc3e0fd194223a592/examples/assets/pisa/pisa_posy.jpg
--------------------------------------------------------------------------------
/examples/assets/pisa/pisa_posz.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pex-gl/pex-gui/93ee94a82801cd78cc36077dc3e0fd194223a592/examples/assets/pisa/pisa_posz.jpg
--------------------------------------------------------------------------------
/examples/assets/plask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pex-gl/pex-gui/93ee94a82801cd78cc36077dc3e0fd194223a592/examples/assets/plask.png
--------------------------------------------------------------------------------
/examples/assets/rainbow.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pex-gl/pex-gui/93ee94a82801cd78cc36077dc3e0fd194223a592/examples/assets/rainbow.jpg
--------------------------------------------------------------------------------
/examples/canvas-2d.js:
--------------------------------------------------------------------------------
1 | import { toHex } from "pex-color";
2 | import createGUI from "../index.js";
3 | import addAllControls from "./all-controls.js";
4 |
5 | const canvas = document.createElement("canvas");
6 |
7 | const ctx = canvas.getContext("2d");
8 | document.querySelector("main").appendChild(canvas);
9 |
10 | const gui = createGUI(ctx);
11 |
12 | window.gui = gui;
13 |
14 | const { State } = await addAllControls(gui, ctx);
15 |
16 | window.State = State;
17 |
18 | requestAnimationFrame(function frame() {
19 | const W = ctx.canvas.width;
20 | const H = ctx.canvas.height;
21 | ctx.clearRect(0, 0, W, H);
22 | ctx.fillStyle = "#333";
23 | ctx.fillRect(0, 0, W, H);
24 |
25 | ctx.fillStyle = toHex(State.rgb);
26 | ctx.fillRect(W * 0.5 - 100, H * 0.5 - 100, 200, 200);
27 | gui.draw();
28 | requestAnimationFrame(frame);
29 | });
30 |
31 | const onResize = () => {
32 | const W = window.innerWidth;
33 | const H = window.innerHeight;
34 | canvas.width = W * devicePixelRatio;
35 | canvas.height = H * devicePixelRatio;
36 | canvas.style.width = `${W}px`;
37 | canvas.style.height = `${H}px`;
38 | };
39 |
40 | window.addEventListener("resize", onResize);
41 |
42 | onResize();
43 |
--------------------------------------------------------------------------------
/examples/debug.js:
--------------------------------------------------------------------------------
1 | import createGUI, { DEFAULT_THEME, Renderers } from "../index.js";
2 | import addAllControls from "./all-controls.js";
3 |
4 | const canvas = document.createElement("canvas");
5 |
6 | const ctx = canvas.getContext("2d");
7 | document.querySelector("main").appendChild(canvas);
8 |
9 | const gui = createGUI(ctx, {
10 | renderer: new Renderers.DebugRenderer({
11 | width: canvas.width / 3,
12 | height: canvas.height / 3,
13 | theme: DEFAULT_THEME,
14 | }),
15 | });
16 |
17 | await addAllControls(gui, ctx);
18 |
19 | requestAnimationFrame(function frame() {
20 | ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
21 | ctx.fillStyle = "#333";
22 | ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
23 | gui.draw();
24 | requestAnimationFrame(frame);
25 | });
26 |
27 | const onResize = () => {
28 | const W = window.innerWidth;
29 | const H = window.innerHeight;
30 | canvas.width = W * devicePixelRatio;
31 | canvas.height = H * devicePixelRatio;
32 | canvas.style.width = `${W}px`;
33 | canvas.style.height = `${H}px`;
34 | };
35 |
36 | window.addEventListener("resize", onResize);
37 |
38 | onResize();
39 |
--------------------------------------------------------------------------------
/examples/options.js:
--------------------------------------------------------------------------------
1 | import createGUI from "../index.js";
2 | import addAllControls from "./all-controls.js";
3 |
4 | const guis = new Map();
5 |
6 | Object.assign(document.querySelector("main").style, {
7 | position: "relative",
8 | padding: "0 20px",
9 | });
10 |
11 | const DEFAULT_WIDTH = 800;
12 | const DEFAULT_HEIGHT = 600;
13 | const mainElement = document.querySelector("main");
14 |
15 | // Helpers
16 | const addSeparator = () =>
17 | mainElement.appendChild(document.createElement("hr"));
18 |
19 | const addButton = (title, cb) => {
20 | const buttonElement = document.createElement("button");
21 | buttonElement.innerText = title;
22 | buttonElement.addEventListener("click", cb);
23 | mainElement.appendChild(buttonElement);
24 | };
25 | const addText = (title, tagName = "h3") => {
26 | const titleElement = document.createElement(tagName);
27 | titleElement.innerHTML = title;
28 | mainElement.appendChild(titleElement);
29 | };
30 |
31 | const addGUI = async (
32 | title,
33 | options = {},
34 | width = DEFAULT_WIDTH,
35 | height = DEFAULT_HEIGHT,
36 | pixelRatio = devicePixelRatio
37 | ) => {
38 | addText(title, "p");
39 |
40 | const canvas = document.createElement("canvas");
41 | mainElement.appendChild(canvas);
42 |
43 | const ctx = canvas.getContext("2d");
44 | canvas.width = width * pixelRatio;
45 | canvas.height = height * pixelRatio;
46 | canvas.style.width = `${width}px`;
47 | canvas.style.height = `${height}px`;
48 |
49 | const gui = createGUI(ctx, options);
50 | guis.set(ctx, gui);
51 |
52 | await addAllControls(gui, ctx);
53 | };
54 |
55 | // Content
56 | addText("Change all canvas dimensions (canvas.width and canvas.height)", "h3");
57 | addButton("Divide all by two", () => {
58 | Array.from(guis.values()).forEach((ctx) => {
59 | ctx.canvas.width /= 2;
60 | ctx.canvas.height /= 2;
61 | });
62 | });
63 | addButton("Multiply all by two", () => {
64 | Array.from(guis.values()).forEach((ctx) => {
65 | ctx.canvas.width *= 2;
66 | ctx.canvas.height *= 2;
67 | });
68 | });
69 |
70 | addSeparator();
71 | addText(
72 | "Default render at the same size regardless of the canvas size or pixel ratio unless it overflows the canvas viewport"
73 | );
74 | await addGUI(
75 | "Default ({ responsive: true, scale: 1, pixelRatio: devicePixelRatio })"
76 | );
77 | await addGUI(
78 | "Default with canvas at double size",
79 | undefined,
80 | DEFAULT_WIDTH * 2,
81 | DEFAULT_HEIGHT * 2
82 | );
83 | await addGUI(
84 | "Default with canvas at half size",
85 | undefined,
86 | DEFAULT_WIDTH / 2,
87 | DEFAULT_HEIGHT / 2
88 | );
89 | await addGUI(
90 | "Default with canvas dimensions at multiplied by a ratio of 1",
91 | undefined,
92 | undefined,
93 | undefined,
94 | 1
95 | );
96 | await addGUI(
97 | "Default with canvas dimensions at multiplied by a ratio of 0.5",
98 | undefined,
99 | undefined,
100 | undefined,
101 | 0.5
102 | );
103 |
104 | addSeparator();
105 | addText("How big is the GUI");
106 |
107 | await addGUI("Hardcoded scale at 2", { scale: 2 });
108 | await addGUI("Hardcoded scale at 0.5", { scale: 0.5 });
109 |
110 | addSeparator();
111 | addText("How sharp things are");
112 |
113 | await addGUI("Renderer at pixelRatio 1", { pixelRatio: 1 });
114 | await addGUI("Renderer at pixelRatio 0.5", { pixelRatio: 0.5 });
115 |
116 | addSeparator();
117 | addText("No responsive doesn't change rendering size");
118 |
119 | await addGUI("No responsive", { responsive: false });
120 | await addGUI("No responsive and renderer at pixelRatio 0.5", {
121 | responsive: false,
122 | pixelRatio: 0.5,
123 | });
124 |
125 | addSeparator();
126 | addText("No responsive on small canvas will make the content overflow");
127 |
128 | await addGUI(
129 | "No responsive overflowing",
130 | { responsive: false },
131 | DEFAULT_WIDTH / 2,
132 | DEFAULT_HEIGHT / 2
133 | );
134 | await addGUI(
135 | "No responsive overflowing and renderer at pixelRatio 0.5",
136 | {
137 | responsive: false,
138 | pixelRatio: 0.5,
139 | },
140 | DEFAULT_WIDTH / 2,
141 | DEFAULT_HEIGHT / 2
142 | );
143 |
144 | addSeparator();
145 | addText("Scale can compensate for non-responsive GUI");
146 |
147 | await addGUI(
148 | "No responsive, hardcoded scale at 0.5",
149 | { responsive: false, scale: 0.5 },
150 | DEFAULT_WIDTH / 2,
151 | DEFAULT_HEIGHT / 2
152 | );
153 | await addGUI(
154 | "No responsive, hardcoded scale at 0.5 and renderer at pixelRatio 0.5",
155 | {
156 | responsive: false,
157 | scale: 0.5,
158 | pixelRatio: 0.5,
159 | },
160 | DEFAULT_WIDTH / 2,
161 | DEFAULT_HEIGHT / 2
162 | );
163 |
164 | requestAnimationFrame(function frame() {
165 | Array.from(guis.entries()).forEach(([ctx, gui]) => {
166 | ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
167 | ctx.fillStyle = "#333";
168 | ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
169 | gui.draw();
170 | });
171 | requestAnimationFrame(frame);
172 | });
173 |
--------------------------------------------------------------------------------
/examples/pex-context.js:
--------------------------------------------------------------------------------
1 | import createContext from "pex-context";
2 | import { perspective as createCamera, orbiter as createOrbiter } from "pex-cam";
3 | import { mat4, quat } from "pex-math";
4 | import { torus as createTorus, cube as createCube } from "primitive-geometry";
5 |
6 | import createGUI from "../index.js";
7 |
8 | import allControls from "./all-controls.js";
9 |
10 | const ExampleState = {
11 | scale: 1,
12 | rotate: false,
13 | time: 0,
14 | size: [1, 0.2],
15 | rotation: [0, 0, 0],
16 | bgColor: [0.2, 0.2, 0.2, 1.0],
17 | currentGeometry: 0,
18 | geometries: [],
19 | };
20 |
21 | const vert = /* glsl */ `
22 | attribute vec2 aTexCoord;
23 | attribute vec3 aPosition;
24 | uniform mat4 uProjectionMatrix;
25 | uniform mat4 uViewMatrix;
26 | uniform mat4 uModelMatrix;
27 | varying vec2 vTexCoord;
28 | void main() {
29 | vTexCoord = aTexCoord;
30 | gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aPosition, 1.0);
31 | }
32 | `;
33 |
34 | const frag = /* glsl */ `
35 | #ifdef GL_ES
36 | precision mediump float;
37 | #endif
38 |
39 | varying vec2 vTexCoord;
40 | uniform sampler2D uTexture;
41 | uniform vec2 uRepeat;
42 | void main() {
43 | gl_FragColor = texture2D(uTexture, vTexCoord * uRepeat);
44 | }`;
45 |
46 | const ctx = createContext({ pixelRatio: devicePixelRatio });
47 |
48 | const gui = createGUI(ctx);
49 |
50 | const camera = createCamera({
51 | fov: Math.PI / 4,
52 | aspect: ctx.gl.drawingBufferWidth / ctx.gl.drawingBufferHeight,
53 | near: 0.1,
54 | far: 100,
55 | position: [3, 3, -3],
56 | target: [0, 0, 0],
57 | });
58 |
59 | const orbiter = createOrbiter({ camera });
60 |
61 | const { State, res } = await allControls(gui, ctx);
62 |
63 | // Example
64 | gui.addTab("Example");
65 | gui.addColumn("Settings");
66 | gui.addParam("Scale", ExampleState, "scale", { min: 0.1, max: 2 });
67 | gui.addParam("Rotate camera", ExampleState, "rotate");
68 | gui.addParam("Rotation", ExampleState, "rotation", {
69 | min: -Math.PI / 2,
70 | max: Math.PI / 2,
71 | });
72 | gui.addHeader("Color");
73 | gui.addParam("BG Color [RGBA]", ExampleState, "bgColor");
74 | gui.addParam("BG Color [HSB]", ExampleState, "bgColor", {
75 | type: "color",
76 | palette: res.paletteHsl,
77 | });
78 |
79 | gui.addColumn("Geometry");
80 | gui.addRadioList(
81 | "Type",
82 | ExampleState,
83 | "currentGeometry",
84 | ExampleState.geometries
85 | );
86 | gui.addParam("Torus Size", ExampleState, "size", { min: 0.1, max: 2 }, () => {
87 | const torus = createTorus({
88 | minorRadius: ExampleState.size[1],
89 | radius: ExampleState.size[0],
90 | });
91 | ctx.update(ExampleState.geometries[1].attributes.aPosition, {
92 | data: torus.positions,
93 | });
94 | });
95 |
96 | gui.addColumn("Texture");
97 | gui.addTexture2D("Default", State.textures[1]);
98 | gui.addTexture2DList(
99 | "Default",
100 | State,
101 | "currentTexture",
102 | State.textures.map((tex, index) => {
103 | return { texture: tex, value: index };
104 | })
105 | );
106 |
107 | const clearCmd = {
108 | pass: ctx.pass({
109 | clearColor: ExampleState.bgColor,
110 | clearDepth: 1,
111 | }),
112 | };
113 |
114 | const cube = createCube();
115 | ExampleState.geometries.push({
116 | name: "Cube",
117 | value: 0,
118 | attributes: {
119 | aPosition: ctx.vertexBuffer(cube.positions),
120 | aTexCoord: ctx.vertexBuffer(cube.uvs),
121 | },
122 | indices: ctx.indexBuffer(cube.cells),
123 | });
124 |
125 | const torus = createTorus();
126 | ExampleState.geometries.push({
127 | name: "Torus",
128 | value: 1,
129 | attributes: {
130 | aPosition: ctx.vertexBuffer(torus.positions),
131 | aTexCoord: ctx.vertexBuffer(torus.uvs),
132 | },
133 | indices: ctx.indexBuffer(torus.cells),
134 | });
135 |
136 | const modelMatrix = mat4.create();
137 | const rotationQuat = quat.create();
138 |
139 | const drawCmd = {
140 | name: "drawCmd",
141 | pipeline: ctx.pipeline({
142 | vert,
143 | frag,
144 | depthTest: true,
145 | }),
146 | uniforms: {
147 | uProjectionMatrix: camera.projectionMatrix,
148 | uViewMatrix: camera.viewMatrix,
149 | uModelMatrix: modelMatrix,
150 | uRepeat: [1, 1],
151 | },
152 | };
153 |
154 | let frameNumber = 0;
155 | ctx.frame(() => {
156 | ctx.debug(frameNumber++ === 10);
157 | ctx.submit(clearCmd);
158 |
159 | quat.fromEuler(rotationQuat, ExampleState.rotation);
160 | mat4.fromQuat(modelMatrix, rotationQuat);
161 | mat4.scale(modelMatrix, [
162 | ExampleState.scale,
163 | ExampleState.scale,
164 | ExampleState.scale,
165 | ]);
166 |
167 | if (ExampleState.rotate) {
168 | ExampleState.time += 1 / 60;
169 | camera.set({
170 | position: [
171 | Math.cos(ExampleState.time * Math.PI) * 5,
172 | Math.sin(ExampleState.time * 0.5) * 3,
173 | Math.sin(ExampleState.time * Math.PI) * 5,
174 | ],
175 | });
176 | orbiter.set({ camera });
177 | }
178 |
179 | if (State.textures.length > 0) {
180 | ctx.submit(drawCmd, {
181 | attributes:
182 | ExampleState.geometries[ExampleState.currentGeometry].attributes,
183 | indices: ExampleState.geometries[ExampleState.currentGeometry].indices,
184 | uniforms: {
185 | uTexture: State.textures[State.currentTexture],
186 | },
187 | });
188 | }
189 |
190 | gui.draw();
191 | });
192 |
193 | const onResize = () => {
194 | const W = window.innerWidth;
195 | const H = window.innerHeight;
196 | ctx.set({
197 | width: W,
198 | height: H,
199 | });
200 | camera.set({
201 | aspect: W / H,
202 | });
203 | };
204 |
205 | window.addEventListener("resize", onResize);
206 |
207 | onResize();
208 |
--------------------------------------------------------------------------------
/examples/theme.js:
--------------------------------------------------------------------------------
1 | import createGUI from "../index.js";
2 | import addAllControls from "./all-controls.js";
3 |
4 | const canvas = document.createElement("canvas");
5 | canvas.width = window.innerWidth * devicePixelRatio;
6 | canvas.height = window.innerHeight * devicePixelRatio;
7 | canvas.style.width = `${window.innerWidth}px`;
8 | canvas.style.height = `${window.innerHeight}px`;
9 |
10 | const ctx = canvas.getContext("2d");
11 | document.querySelector("main").appendChild(canvas);
12 |
13 | const gui = createGUI(ctx, {
14 | theme: {
15 | fontFamily: "Courier New",
16 | fontSize: 12,
17 | capHeight: 0.115,
18 |
19 | leftOffset: 0,
20 | topOffset: 0,
21 |
22 | columnWidth: 200,
23 |
24 | tabHeight: 26,
25 | headerSize: 22,
26 |
27 | titleHeight: 24,
28 | itemHeight: 30,
29 | graphHeight: 60,
30 |
31 | padding: 1,
32 | textPadding: 1,
33 | accent: "rgba(255, 0, 0, 1)",
34 | tabColorActive: "rgba(255, 255, 255, 1)",
35 | tabBackgroundActive: "rgba(0, 0, 255, 1)",
36 | },
37 | });
38 |
39 | gui.scale = 1.2;
40 | gui.x = 100;
41 | gui.y = 100;
42 |
43 | await addAllControls(gui, ctx);
44 |
45 | requestAnimationFrame(function frame() {
46 | ctx.fillStyle = "#333";
47 | ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
48 | gui.draw();
49 | requestAnimationFrame(frame);
50 | });
51 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | pex-gui by pex-gl (https://github.com/pex-gl)
8 |
56 |
57 |
58 |
59 |
69 |
70 |
71 |
72 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pex-gui",
3 | "version": "3.1.3",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "pex-gui",
9 | "version": "3.1.3",
10 | "license": "MIT",
11 | "dependencies": {
12 | "pex-color": "^2.2.0",
13 | "pex-geom": "^3.0.2",
14 | "pex-math": "^4.1.1"
15 | },
16 | "devDependencies": {
17 | "es-module-shims": "2.0.10",
18 | "pex-cam": "^3.0.0-alpha.1",
19 | "pex-context": "^3.2.0",
20 | "pex-io": "^3.0.2",
21 | "primitive-geometry": "^2.10.1"
22 | },
23 | "engines": {
24 | "node": ">=22.0.0",
25 | "npm": ">=10.5.1",
26 | "snowdev": ">=2.3.x"
27 | }
28 | },
29 | "node_modules/es-module-shims": {
30 | "version": "2.0.10",
31 | "resolved": "https://registry.npmjs.org/es-module-shims/-/es-module-shims-2.0.10.tgz",
32 | "integrity": "sha512-Ub1xUe9v97lKsSnTMfCJvnX6dSmJ3nRVWHpH1szP0RqmLliiBGQiUXLPkz1b0uh6LnPjUdTtND1YjBT+VIuozw==",
33 | "dev": true,
34 | "license": "MIT"
35 | },
36 | "node_modules/interpolate-angle": {
37 | "version": "1.0.2",
38 | "resolved": "https://registry.npmjs.org/interpolate-angle/-/interpolate-angle-1.0.2.tgz",
39 | "integrity": "sha1-q6mSFyJy4ucJOMlTC0HcNcV+5do=",
40 | "dev": true,
41 | "dependencies": {
42 | "lerp": "^1.0.3"
43 | }
44 | },
45 | "node_modules/latlon-to-xyz": {
46 | "version": "1.0.1",
47 | "resolved": "https://registry.npmjs.org/latlon-to-xyz/-/latlon-to-xyz-1.0.1.tgz",
48 | "integrity": "sha512-OF65aE60Y9aMOibxWFC2Vl7EN2BOtMKabm0t97+VaVgR9LoStZHbNNMc8dS/LG6iE1BpEXmnoH85wWr1drp/Tw==",
49 | "dev": true
50 | },
51 | "node_modules/lerp": {
52 | "version": "1.0.3",
53 | "resolved": "https://registry.npmjs.org/lerp/-/lerp-1.0.3.tgz",
54 | "integrity": "sha1-oYyJaPkXiW3hXM/MKNVaa3Med24=",
55 | "dev": true
56 | },
57 | "node_modules/mouse-event-offset": {
58 | "version": "3.0.2",
59 | "resolved": "https://registry.npmjs.org/mouse-event-offset/-/mouse-event-offset-3.0.2.tgz",
60 | "integrity": "sha1-39hqbiSMa6jK1TuQXVA3ogY+mYQ=",
61 | "dev": true
62 | },
63 | "node_modules/pex-cam": {
64 | "version": "3.0.0-alpha.1",
65 | "resolved": "https://registry.npmjs.org/pex-cam/-/pex-cam-3.0.0-alpha.1.tgz",
66 | "integrity": "sha512-mVDMzz5cyGA2em7k/fq9Yq9inO1hRputz4poeiXKdzydUh+EHXVvx3gzNyeXez2BTfV0TLpuoCgoLYLc5b4Ifw==",
67 | "dev": true,
68 | "dependencies": {
69 | "interpolate-angle": "^1.0.2",
70 | "latlon-to-xyz": "^1.0.1",
71 | "mouse-event-offset": "^3.0.2",
72 | "pex-geom": "^3.0.0-alpha.0",
73 | "pex-math": "^4.0.0-alpha.1",
74 | "xyz-to-latlon": "^1.0.2"
75 | },
76 | "engines": {
77 | "node": ">=16.0.0",
78 | "npm": ">=7.0.0"
79 | }
80 | },
81 | "node_modules/pex-color": {
82 | "version": "2.2.0",
83 | "resolved": "https://registry.npmjs.org/pex-color/-/pex-color-2.2.0.tgz",
84 | "integrity": "sha512-eZgxo99JIvcTTrmvgXwRJgGNdkDTIgs03m2y1Q/aMA2TVD9fK2tyCetVQmcbAlwt94cmg5go59gzNjjnPF/01A==",
85 | "license": "MIT",
86 | "engines": {
87 | "node": ">=22.0.0",
88 | "npm": ">=10.5.1",
89 | "snowdev": ">=2.2.x"
90 | }
91 | },
92 | "node_modules/pex-context": {
93 | "version": "3.2.0",
94 | "resolved": "https://registry.npmjs.org/pex-context/-/pex-context-3.2.0.tgz",
95 | "integrity": "sha512-yM7XS0SCbWWM/QbonhNnwVxD58ZDjcOJ1VwONeJGj0kFoMHkJskoAFSJH3i4fGAEte6YiHt361LX9D/waP1IAw==",
96 | "dev": true,
97 | "license": "MIT",
98 | "dependencies": {
99 | "pex-gl": "^3.0.2"
100 | },
101 | "engines": {
102 | "node": ">=22.0.0",
103 | "npm": ">=10.5.1",
104 | "snowdev": ">=2.3.x"
105 | }
106 | },
107 | "node_modules/pex-geom": {
108 | "version": "3.0.2",
109 | "resolved": "https://registry.npmjs.org/pex-geom/-/pex-geom-3.0.2.tgz",
110 | "integrity": "sha512-jLx3ISMMrVuDouPvkmN1hLTqeJE/45otdNVIuoMHKxcz7LZb8k9+XuV7VmsAPA+/TySJyvO+gVvFopUVVG40jg==",
111 | "license": "MIT",
112 | "dependencies": {
113 | "pex-math": "^4.1.1"
114 | },
115 | "engines": {
116 | "node": ">=22.0.0",
117 | "npm": ">=10.5.1",
118 | "snowdev": ">=2.2.x"
119 | }
120 | },
121 | "node_modules/pex-gl": {
122 | "version": "3.0.2",
123 | "resolved": "https://registry.npmjs.org/pex-gl/-/pex-gl-3.0.2.tgz",
124 | "integrity": "sha512-q0MEUXyRlXEa7A/QGQn7GvrkLf+Yc+tRpTRFW0gmbG1RnJpKC0U504WYaB9O47OoMmy9LlKGCaolP2cJJUw5gQ==",
125 | "dev": true,
126 | "license": "MIT",
127 | "engines": {
128 | "node": ">=22.0.0",
129 | "npm": ">=10.5.1",
130 | "snowdev": ">=2.2.x"
131 | }
132 | },
133 | "node_modules/pex-io": {
134 | "version": "3.0.2",
135 | "resolved": "https://registry.npmjs.org/pex-io/-/pex-io-3.0.2.tgz",
136 | "integrity": "sha512-9lVsogyCEVqUZ/qQ2Yv1Yr6ATRMpgsuUF4UGsOA6vKVkekOHCFlQ71B6m3zQll5HgZmPUw8QM9yrQkcmjE05EA==",
137 | "dev": true,
138 | "license": "MIT",
139 | "engines": {
140 | "node": ">=22.0.0",
141 | "npm": ">=10.5.1",
142 | "snowdev": ">=2.2.x"
143 | }
144 | },
145 | "node_modules/pex-math": {
146 | "version": "4.1.1",
147 | "resolved": "https://registry.npmjs.org/pex-math/-/pex-math-4.1.1.tgz",
148 | "integrity": "sha512-jhFPZfgdhUczx8EJsV7PwFTDjvhDMhNiabtK+rOLsfkWl33LNV97hUYB0URlMiTf9UR4nNpPcTLuha96aniyQg==",
149 | "license": "MIT",
150 | "engines": {
151 | "node": ">=22.0.0",
152 | "npm": ">=10.5.1",
153 | "snowdev": ">=2.2.x"
154 | }
155 | },
156 | "node_modules/primitive-geometry": {
157 | "version": "2.10.1",
158 | "resolved": "https://registry.npmjs.org/primitive-geometry/-/primitive-geometry-2.10.1.tgz",
159 | "integrity": "sha512-nfWIrVugpis6O9f/itBAo0qS69/VBGyZtxyyCpjgWeOWshAUAkmr/hEl80AlSK4WFnaKhdratnKCxhaq8ImPUQ==",
160 | "dev": true,
161 | "funding": [
162 | {
163 | "type": "individual",
164 | "url": "https://paypal.me/dmnsgn"
165 | },
166 | {
167 | "type": "individual",
168 | "url": "https://commerce.coinbase.com/checkout/56cbdf28-e323-48d8-9c98-7019e72c97f3"
169 | }
170 | ],
171 | "license": "MIT",
172 | "engines": {
173 | "node": ">=15.0.0",
174 | "npm": ">=7.0.0"
175 | }
176 | },
177 | "node_modules/xyz-to-latlon": {
178 | "version": "1.0.2",
179 | "resolved": "https://registry.npmjs.org/xyz-to-latlon/-/xyz-to-latlon-1.0.2.tgz",
180 | "integrity": "sha512-iTQe5HIzAE0r2L2u2abs17s/SekCCUAlIHzYnHHo10aR0B1iKRSGye33z7a52IMJoUUMTpPwNUTNIy+OhZVRIg==",
181 | "dev": true
182 | }
183 | },
184 | "dependencies": {
185 | "es-module-shims": {
186 | "version": "2.0.10",
187 | "resolved": "https://registry.npmjs.org/es-module-shims/-/es-module-shims-2.0.10.tgz",
188 | "integrity": "sha512-Ub1xUe9v97lKsSnTMfCJvnX6dSmJ3nRVWHpH1szP0RqmLliiBGQiUXLPkz1b0uh6LnPjUdTtND1YjBT+VIuozw==",
189 | "dev": true
190 | },
191 | "interpolate-angle": {
192 | "version": "1.0.2",
193 | "resolved": "https://registry.npmjs.org/interpolate-angle/-/interpolate-angle-1.0.2.tgz",
194 | "integrity": "sha1-q6mSFyJy4ucJOMlTC0HcNcV+5do=",
195 | "dev": true,
196 | "requires": {
197 | "lerp": "^1.0.3"
198 | }
199 | },
200 | "latlon-to-xyz": {
201 | "version": "1.0.1",
202 | "resolved": "https://registry.npmjs.org/latlon-to-xyz/-/latlon-to-xyz-1.0.1.tgz",
203 | "integrity": "sha512-OF65aE60Y9aMOibxWFC2Vl7EN2BOtMKabm0t97+VaVgR9LoStZHbNNMc8dS/LG6iE1BpEXmnoH85wWr1drp/Tw==",
204 | "dev": true
205 | },
206 | "lerp": {
207 | "version": "1.0.3",
208 | "resolved": "https://registry.npmjs.org/lerp/-/lerp-1.0.3.tgz",
209 | "integrity": "sha1-oYyJaPkXiW3hXM/MKNVaa3Med24=",
210 | "dev": true
211 | },
212 | "mouse-event-offset": {
213 | "version": "3.0.2",
214 | "resolved": "https://registry.npmjs.org/mouse-event-offset/-/mouse-event-offset-3.0.2.tgz",
215 | "integrity": "sha1-39hqbiSMa6jK1TuQXVA3ogY+mYQ=",
216 | "dev": true
217 | },
218 | "pex-cam": {
219 | "version": "3.0.0-alpha.1",
220 | "resolved": "https://registry.npmjs.org/pex-cam/-/pex-cam-3.0.0-alpha.1.tgz",
221 | "integrity": "sha512-mVDMzz5cyGA2em7k/fq9Yq9inO1hRputz4poeiXKdzydUh+EHXVvx3gzNyeXez2BTfV0TLpuoCgoLYLc5b4Ifw==",
222 | "dev": true,
223 | "requires": {
224 | "interpolate-angle": "^1.0.2",
225 | "latlon-to-xyz": "^1.0.1",
226 | "mouse-event-offset": "^3.0.2",
227 | "pex-geom": "^3.0.0-alpha.0",
228 | "pex-math": "^4.0.0-alpha.1",
229 | "xyz-to-latlon": "^1.0.2"
230 | }
231 | },
232 | "pex-color": {
233 | "version": "2.2.0",
234 | "resolved": "https://registry.npmjs.org/pex-color/-/pex-color-2.2.0.tgz",
235 | "integrity": "sha512-eZgxo99JIvcTTrmvgXwRJgGNdkDTIgs03m2y1Q/aMA2TVD9fK2tyCetVQmcbAlwt94cmg5go59gzNjjnPF/01A=="
236 | },
237 | "pex-context": {
238 | "version": "3.2.0",
239 | "resolved": "https://registry.npmjs.org/pex-context/-/pex-context-3.2.0.tgz",
240 | "integrity": "sha512-yM7XS0SCbWWM/QbonhNnwVxD58ZDjcOJ1VwONeJGj0kFoMHkJskoAFSJH3i4fGAEte6YiHt361LX9D/waP1IAw==",
241 | "dev": true,
242 | "requires": {
243 | "pex-gl": "^3.0.2"
244 | }
245 | },
246 | "pex-geom": {
247 | "version": "3.0.2",
248 | "resolved": "https://registry.npmjs.org/pex-geom/-/pex-geom-3.0.2.tgz",
249 | "integrity": "sha512-jLx3ISMMrVuDouPvkmN1hLTqeJE/45otdNVIuoMHKxcz7LZb8k9+XuV7VmsAPA+/TySJyvO+gVvFopUVVG40jg==",
250 | "requires": {
251 | "pex-math": "^4.1.1"
252 | }
253 | },
254 | "pex-gl": {
255 | "version": "3.0.2",
256 | "resolved": "https://registry.npmjs.org/pex-gl/-/pex-gl-3.0.2.tgz",
257 | "integrity": "sha512-q0MEUXyRlXEa7A/QGQn7GvrkLf+Yc+tRpTRFW0gmbG1RnJpKC0U504WYaB9O47OoMmy9LlKGCaolP2cJJUw5gQ==",
258 | "dev": true
259 | },
260 | "pex-io": {
261 | "version": "3.0.2",
262 | "resolved": "https://registry.npmjs.org/pex-io/-/pex-io-3.0.2.tgz",
263 | "integrity": "sha512-9lVsogyCEVqUZ/qQ2Yv1Yr6ATRMpgsuUF4UGsOA6vKVkekOHCFlQ71B6m3zQll5HgZmPUw8QM9yrQkcmjE05EA==",
264 | "dev": true
265 | },
266 | "pex-math": {
267 | "version": "4.1.1",
268 | "resolved": "https://registry.npmjs.org/pex-math/-/pex-math-4.1.1.tgz",
269 | "integrity": "sha512-jhFPZfgdhUczx8EJsV7PwFTDjvhDMhNiabtK+rOLsfkWl33LNV97hUYB0URlMiTf9UR4nNpPcTLuha96aniyQg=="
270 | },
271 | "primitive-geometry": {
272 | "version": "2.10.1",
273 | "resolved": "https://registry.npmjs.org/primitive-geometry/-/primitive-geometry-2.10.1.tgz",
274 | "integrity": "sha512-nfWIrVugpis6O9f/itBAo0qS69/VBGyZtxyyCpjgWeOWshAUAkmr/hEl80AlSK4WFnaKhdratnKCxhaq8ImPUQ==",
275 | "dev": true
276 | },
277 | "xyz-to-latlon": {
278 | "version": "1.0.2",
279 | "resolved": "https://registry.npmjs.org/xyz-to-latlon/-/xyz-to-latlon-1.0.2.tgz",
280 | "integrity": "sha512-iTQe5HIzAE0r2L2u2abs17s/SekCCUAlIHzYnHHo10aR0B1iKRSGye33z7a52IMJoUUMTpPwNUTNIy+OhZVRIg==",
281 | "dev": true
282 | }
283 | }
284 | }
285 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pex-gui",
3 | "version": "3.1.3",
4 | "description": "GUI controls for PEX.",
5 | "keywords": [
6 | "pex",
7 | "gui",
8 | "webgl",
9 | "3d",
10 | "graphics",
11 | "ui",
12 | "controls"
13 | ],
14 | "homepage": "https://github.com/pex-gl/pex-gui",
15 | "bugs": "https://github.com/pex-gl/pex-gui/issues",
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/pex-gl/pex-gui.git"
19 | },
20 | "license": "MIT",
21 | "author": "Marcin Ignac (http://marcinignac.com)",
22 | "contributors": [
23 | "Damien Seguin (https://github.com/dmnsgn)"
24 | ],
25 | "type": "module",
26 | "exports": {
27 | ".": {
28 | "types": "./types/index.d.ts",
29 | "default": "./index.js"
30 | }
31 | },
32 | "main": "index.js",
33 | "types": "types/index.d.ts",
34 | "scripts": {
35 | "build": "snowdev build",
36 | "dev": "snowdev dev",
37 | "release": "snowdev release"
38 | },
39 | "dependencies": {
40 | "pex-color": "^2.2.0",
41 | "pex-geom": "^3.0.2",
42 | "pex-math": "^4.1.1"
43 | },
44 | "devDependencies": {
45 | "es-module-shims": "2.0.10",
46 | "pex-cam": "^3.0.0-alpha.1",
47 | "pex-context": "^3.2.0",
48 | "pex-io": "^3.0.2",
49 | "primitive-geometry": "^2.10.1"
50 | },
51 | "engines": {
52 | "node": ">=22.0.0",
53 | "npm": ">=10.5.1",
54 | "snowdev": ">=2.3.x"
55 | },
56 | "snowdev": {
57 | "files": "{*.js,*(renderers|shaders)/**/*.js}"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/renderers/CanvasRenderer.js:
--------------------------------------------------------------------------------
1 | import { toHex } from "pex-color";
2 | import { utils } from "pex-math";
3 |
4 | function rectSet4(a, x, y, w, h) {
5 | a[0][0] = x;
6 | a[0][1] = y;
7 | a[1][0] = x + w;
8 | a[1][1] = y + h;
9 | return a;
10 | }
11 |
12 | function makePaletteImage(item, w, img) {
13 | const canvas = document.createElement("canvas");
14 | canvas.width = w;
15 | canvas.height = (w * img.height) / img.width;
16 | const ctx = canvas.getContext("2d");
17 | ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
18 | item.options.paletteImage = canvas;
19 | item.options.paletteImage.data = ctx.getImageData(
20 | 0,
21 | 0,
22 | canvas.width,
23 | canvas.height,
24 | ).data;
25 | item.options.paletteImage.aspectRatio = canvas.height / canvas.width;
26 | item.dirty = true;
27 | }
28 |
29 | class CanvasRenderer {
30 | constructor({ width, height, pixelRatio = devicePixelRatio, theme }) {
31 | this.pixelRatio = pixelRatio;
32 | this.theme = theme;
33 |
34 | this.canvas = document.createElement("canvas");
35 | this.canvas.width = width * this.pixelRatio;
36 | this.canvas.height = height * this.pixelRatio;
37 | this.ctx = this.canvas.getContext("2d");
38 | this.dirty = true;
39 | }
40 |
41 | draw(items) {
42 | this.dirty = false;
43 |
44 | const {
45 | fontFamily,
46 | fontSize,
47 | capHeight,
48 | leftOffset,
49 | topOffset,
50 | columnWidth,
51 | tabHeight,
52 | headerSize,
53 | titleHeight,
54 | itemHeight,
55 | graphHeight,
56 | padding,
57 | textPadding,
58 | } = this.theme;
59 |
60 | const sliderHeight = 0.7 * itemHeight;
61 | const buttonHeight = 1.2 * itemHeight;
62 |
63 | const ctx = this.ctx;
64 |
65 | ctx.save();
66 | ctx.scale(this.pixelRatio, this.pixelRatio);
67 | ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
68 |
69 | const fontCapOffset = capHeight * fontSize;
70 | ctx.font = `${fontSize}px ${fontFamily}`;
71 | ctx.textBaseline = "middle";
72 |
73 | let dx = leftOffset;
74 | let dy = topOffset;
75 | let w = columnWidth;
76 | const gap = padding;
77 |
78 | let cellSize = 0;
79 | let numRows = 0;
80 | let columnIndex = 0;
81 | const tabs = items.filter(({ type }) => type === "tab");
82 | const defaultDy = tabs.length
83 | ? topOffset + tabHeight + padding * 3
84 | : topOffset;
85 |
86 | tabs.forEach((tab) => {
87 | ctx.fillStyle = this.theme.background;
88 | ctx.fillRect(dx, dy, w, tabHeight + padding * 2);
89 |
90 | ctx.fillStyle = tab.current
91 | ? this.theme.tabBackgroundActive
92 | : this.theme.tabBackground;
93 |
94 | const x = dx + padding;
95 | const y = dy + padding;
96 | const width = w - padding * 2;
97 | ctx.fillRect(x, y, width, tabHeight);
98 |
99 | if (!tab.current) {
100 | ctx.fillStyle = this.theme.background;
101 | ctx.fillRect(x, dy + tabHeight + padding / 2, width, padding / 2);
102 | }
103 |
104 | ctx.fillStyle = tab.current
105 | ? this.theme.tabColorActive
106 | : this.theme.tabColor;
107 | ctx.fillText(
108 | tab.title,
109 | x + textPadding,
110 | y + tabHeight / 2 + fontCapOffset,
111 | );
112 |
113 | rectSet4(tab.activeArea, x, y, width, tabHeight);
114 |
115 | dx += w + gap;
116 | });
117 |
118 | dx = leftOffset;
119 |
120 | let maxWidth = 0;
121 | let maxHeight = 0;
122 | let needInitialDy = true;
123 | for (let i = 0; i < items.length; i++) {
124 | const item = items[i];
125 |
126 | if (Number.isFinite(item.x)) dx = item.x;
127 | if (Number.isFinite(item.y)) dy = item.y;
128 |
129 | let eh = itemHeight;
130 | if (item.type === "tab") continue;
131 |
132 | if (tabs.length > 0) {
133 | const prevTabs = items.filter(
134 | ({ type }, index) => index < i && type === "tab",
135 | );
136 | const parentTab = prevTabs[prevTabs.length - 1];
137 | if (parentTab && !parentTab.current) {
138 | continue;
139 | } else {
140 | if (needInitialDy && item.type !== "column") {
141 | needInitialDy = false;
142 | dy += tabHeight;
143 | }
144 | }
145 | needInitialDy = false;
146 | }
147 |
148 | const x = dx + padding;
149 | const width = w - padding * 2;
150 | const textY = titleHeight / 2 + fontCapOffset;
151 |
152 | // Compute item height
153 | if (item.type === "column") {
154 | dx = leftOffset + columnIndex * (w + gap);
155 | dy = defaultDy;
156 | w = item.width;
157 | columnIndex++;
158 | continue;
159 | } else if (item.type === "slider") {
160 | eh = titleHeight + sliderHeight;
161 | } else if (item.type === "toggle") {
162 | eh = padding + itemHeight;
163 | } else if (item.type === "multislider") {
164 | const numSliders = item.getValue().length;
165 | eh =
166 | titleHeight + numSliders * sliderHeight + (numSliders - 1) * padding;
167 | } else if (item.type === "color") {
168 | const numSliders = item.options.alpha ? 4 : 3;
169 | const sliderGap = item.options.paletteImage ? 0 : 1;
170 | eh =
171 | titleHeight +
172 | numSliders * sliderHeight +
173 | (numSliders - sliderGap) * padding;
174 | if (item.options.paletteImage) {
175 | eh += width * item.options.paletteImage.aspectRatio;
176 | }
177 | } else if (item.type === "button") {
178 | eh = padding + buttonHeight;
179 | } else if (item.type === "texture2D") {
180 | eh = titleHeight + (item.texture.height * width) / item.texture.width;
181 | } else if (item.type === "textureCube") {
182 | eh = titleHeight + width / 2;
183 | } else if (item.type === "radiolist") {
184 | eh =
185 | titleHeight +
186 | item.items.length * itemHeight +
187 | (item.items.length - 1) * padding * 2;
188 | } else if (item.type === "texturelist") {
189 | cellSize = Math.floor(width / item.itemsPerRow);
190 | numRows = Math.ceil(item.items.length / item.itemsPerRow);
191 | eh = titleHeight + numRows * cellSize;
192 | } else if (item.type === "header") {
193 | eh = padding + headerSize;
194 | } else if (item.type === "text") {
195 | eh = titleHeight + buttonHeight;
196 | } else if (item.type === "graph") {
197 | eh = titleHeight + graphHeight;
198 | } else if (item.type === "stats") {
199 | eh =
200 | Object.values(item.stats)
201 | .map((value) => String(value).split("\n").length)
202 | .reduce((a, b) => a + b, 0) * titleHeight;
203 | if (item.title !== "") eh += titleHeight;
204 | } else if (item.type === "label") {
205 | eh = item.title.split("\n").length * titleHeight;
206 | }
207 |
208 | const needsPadding = !["column", "label"].includes(item.type);
209 |
210 | // Draw background
211 | if (item.type === "separator") {
212 | eh /= 2;
213 | } else {
214 | ctx.fillStyle = this.theme.background;
215 | ctx.fillRect(dx, dy, w, eh + (needsPadding ? padding : 0));
216 | }
217 |
218 | // Draw item
219 | if (item.type === "slider") {
220 | const y = dy + titleHeight;
221 | const height = eh - titleHeight;
222 |
223 | ctx.fillStyle = this.theme.color;
224 | ctx.fillText(
225 | `${item.title}: ${item.getStrValue()}`,
226 | x + textPadding,
227 | dy + textY,
228 | );
229 |
230 | ctx.fillStyle = this.theme.input;
231 | ctx.fillRect(x, y, width, height);
232 |
233 | ctx.fillStyle = this.theme.accent;
234 | ctx.fillRect(x, y, width * item.getNormalizedValue(), height);
235 |
236 | rectSet4(item.activeArea, x, y, width, height);
237 | } else if (item.type === "multislider" || item.type === "color") {
238 | const isColor = item.type === "color";
239 | const y = dy + titleHeight;
240 | const height = eh - titleHeight;
241 | const numSliders = isColor
242 | ? item.options.alpha
243 | ? 4
244 | : 3
245 | : item.getValue().length;
246 |
247 | ctx.fillStyle = this.theme.color;
248 | ctx.fillText(
249 | `${item.title}: ${item.getStrValue()}`,
250 | x + textPadding,
251 | dy + textY,
252 | );
253 |
254 | for (let j = 0; j < numSliders; j++) {
255 | const sliderY = y + j * (sliderHeight + padding);
256 | ctx.fillStyle = this.theme.input;
257 | ctx.fillRect(x, sliderY, width, sliderHeight);
258 |
259 | ctx.fillStyle = this.theme.accent;
260 | ctx.fillRect(
261 | x,
262 | sliderY,
263 | width * item.getNormalizedValue(j),
264 | sliderHeight,
265 | );
266 | }
267 |
268 | if (isColor) {
269 | const sqSize = titleHeight * 0.6;
270 |
271 | ctx.fillStyle = toHex(item.contextObject[item.attributeName]);
272 | ctx.fillRect(
273 | dx + w - sqSize - padding,
274 | dy + titleHeight * 0.2,
275 | sqSize,
276 | sqSize,
277 | );
278 |
279 | if (item.options?.palette && !item.options.paletteImage) {
280 | if (item.options.palette.width) {
281 | makePaletteImage(item, w, item.options.palette);
282 | } else {
283 | const img = new Image();
284 | img.onload = () => {
285 | makePaletteImage(item, w, img);
286 | };
287 | img.src = item.options.palette;
288 | }
289 | }
290 |
291 | if (item.options.paletteImage) {
292 | ctx.drawImage(
293 | item.options.paletteImage,
294 | x,
295 | y + (sliderHeight + padding) * numSliders,
296 | width,
297 | width * item.options.paletteImage.aspectRatio,
298 | );
299 | }
300 | }
301 |
302 | rectSet4(item.activeArea, x, y, width, height);
303 | } else if (item.type === "button") {
304 | const y = dy + padding;
305 | const height = buttonHeight;
306 |
307 | ctx.fillStyle = item.active ? this.theme.accent : this.theme.input;
308 | ctx.fillRect(x, y, width, height);
309 |
310 | ctx.fillStyle = item.active ? this.theme.input : this.theme.color;
311 | ctx.fillText(
312 | item.title,
313 | x + textPadding * 2,
314 | y + height / 2 + fontCapOffset,
315 | );
316 |
317 | rectSet4(item.activeArea, x, y, width, height);
318 | } else if (item.type === "toggle") {
319 | const y = dy + padding;
320 | const height = itemHeight;
321 | ctx.fillStyle = item.contextObject[item.attributeName]
322 | ? this.theme.accent
323 | : this.theme.input;
324 | ctx.fillRect(x, y, height, height);
325 |
326 | ctx.fillStyle = this.theme.color;
327 | ctx.fillText(
328 | item.title,
329 | x + itemHeight + textPadding * 2,
330 | dy + padding + itemHeight / 2 + fontCapOffset,
331 | );
332 |
333 | rectSet4(item.activeArea, x, y, height, height);
334 | } else if (item.type === "radiolist") {
335 | const y = dy + titleHeight;
336 | const height =
337 | item.items.length * itemHeight +
338 | (item.items.length - 1) * 2 * padding;
339 |
340 | ctx.fillStyle = this.theme.color;
341 | ctx.fillText(item.title, x + textPadding, dy + textY);
342 |
343 | for (let j = 0; j < item.items.length; j++) {
344 | const i = item.items[j];
345 | const radioY = j * (itemHeight + padding * 2);
346 |
347 | ctx.fillStyle =
348 | item.contextObject[item.attributeName] === i.value
349 | ? this.theme.accent
350 | : this.theme.input;
351 | ctx.fillRect(x, y + radioY, itemHeight, itemHeight);
352 |
353 | ctx.fillStyle = this.theme.color;
354 | ctx.fillText(
355 | i.name,
356 | x + itemHeight + textPadding * 2,
357 | titleHeight + radioY + dy + itemHeight / 2 + fontCapOffset,
358 | );
359 | }
360 |
361 | rectSet4(item.activeArea, x, y, itemHeight, height);
362 | } else if (item.type === "texturelist") {
363 | const y = dy + titleHeight;
364 | ctx.fillStyle = this.theme.color;
365 | ctx.fillText(item.title, x + textPadding, dy + textY);
366 |
367 | for (let j = 0; j < item.items.length; j++) {
368 | const col = j % item.itemsPerRow;
369 | const row = Math.floor(j / item.itemsPerRow);
370 | const itemX = x + col * cellSize;
371 | const itemY = dy + titleHeight + row * cellSize;
372 | let shrink = 0;
373 | if (item.items[j].value === item.contextObject[item.attributeName]) {
374 | ctx.fillStyle = "none";
375 | ctx.strokeStyle = this.theme.accent;
376 | ctx.lineWidth = padding;
377 | ctx.strokeRect(
378 | itemX + padding * 0.5,
379 | itemY + padding * 0.5,
380 | cellSize - 1 - padding,
381 | cellSize - 1 - padding,
382 | );
383 | ctx.lineWidth = 1;
384 | shrink = padding;
385 | }
386 | if (!item.items[j].activeArea) {
387 | item.items[j].activeArea = [
388 | [0, 0],
389 | [0, 0],
390 | ];
391 | }
392 | rectSet4(
393 | item.items[j].activeArea,
394 | itemX + shrink,
395 | itemY + shrink,
396 | cellSize - 1 - 2 * shrink,
397 | cellSize - 1 - 2 * shrink,
398 | );
399 | }
400 |
401 | rectSet4(item.activeArea, x, y, width, cellSize * numRows);
402 | } else if (item.type === "texture2D") {
403 | const y = dy + titleHeight;
404 | const height = eh - titleHeight;
405 |
406 | ctx.fillStyle = this.theme.color;
407 | ctx.fillText(item.title, x + textPadding, dy + textY);
408 |
409 | rectSet4(item.activeArea, x, y, width, height);
410 | } else if (item.type === "textureCube") {
411 | const y = dy + titleHeight;
412 | const height = eh - titleHeight;
413 | ctx.fillStyle = this.theme.color;
414 | ctx.fillText(item.title, x + textPadding, dy + textY);
415 |
416 | rectSet4(item.activeArea, x, y, width, height);
417 | } else if (item.type === "header") {
418 | ctx.fillStyle = this.theme.headerBackground;
419 | ctx.fillRect(x, dy + padding, width, eh - padding);
420 |
421 | ctx.fillStyle = this.theme.headerColor;
422 | ctx.fillText(
423 | item.title,
424 | x + textPadding,
425 | dy + padding + headerSize / 2 + fontCapOffset,
426 | );
427 | } else if (item.type === "text") {
428 | const y = dy + titleHeight;
429 | const height = eh - titleHeight;
430 |
431 | ctx.fillStyle = this.theme.color;
432 | ctx.fillText(item.title, x + textPadding, dy + textY);
433 |
434 | ctx.fillStyle = this.theme.input;
435 | ctx.fillRect(
436 | x,
437 | y,
438 | item.activeArea[1][0] - item.activeArea[0][0],
439 | item.activeArea[1][1] - item.activeArea[0][1],
440 | );
441 |
442 | ctx.fillStyle = this.theme.color;
443 | ctx.fillText(
444 | item.contextObject[item.attributeName],
445 | x + textPadding * 2,
446 | y + buttonHeight / 2 + fontCapOffset,
447 | );
448 | if (item.focus) {
449 | ctx.strokeStyle = this.theme.accent;
450 | ctx.strokeRect(
451 | item.activeArea[0][0] - 0.5,
452 | item.activeArea[0][1] - 0.5,
453 | item.activeArea[1][0] - item.activeArea[0][0],
454 | item.activeArea[1][1] - item.activeArea[0][1],
455 | );
456 | }
457 |
458 | rectSet4(item.activeArea, x, y, width, height);
459 | } else if (item.type === "graph") {
460 | const y = dy + titleHeight;
461 | const height = eh - titleHeight;
462 |
463 | if (item.values.length > width) item.values = item.values.slice(-width);
464 | if (item.values.length) {
465 | item.max = item.options.max ?? Math.max(...item.values);
466 | }
467 | if (item.values.length) {
468 | item.min = item.options.min ?? Math.min(...item.values);
469 | }
470 |
471 | ctx.fillStyle = this.theme.graphBackground;
472 | ctx.fillRect(x, y, width, height);
473 |
474 | ctx.strokeStyle = this.theme.background;
475 | ctx.beginPath();
476 | ctx.moveTo(x, y + padding);
477 | ctx.lineTo(x + width, y + padding);
478 | ctx.closePath();
479 | ctx.stroke();
480 |
481 | ctx.beginPath();
482 | ctx.moveTo(x, y + height - padding);
483 | ctx.lineTo(x + width, y + height - padding);
484 | ctx.closePath();
485 | ctx.stroke();
486 |
487 | ctx.fillStyle = this.theme.color;
488 | ctx.save();
489 | ctx.font = `${fontSize * 0.5}px ${fontFamily}`;
490 | ctx.textAlign = "right";
491 | const textX = x + width - padding;
492 | if (item.max !== undefined) {
493 | ctx.fillText(item.options.format(item.max), textX, y + padding * 2.5);
494 | }
495 | if (item.min !== undefined) {
496 | ctx.fillText(
497 | item.options.format(item.min),
498 | textX,
499 | y + height - padding * 2.5,
500 | );
501 | }
502 | ctx.restore();
503 |
504 | ctx.strokeStyle = this.theme.color;
505 | ctx.beginPath();
506 | for (let j = 0; j < item.values.length; j++) {
507 | const v = utils.remap(item.values[j], item.min, item.max, 0, 1);
508 | ctx[j === 0 ? "moveTo" : "lineTo"](
509 | x + j,
510 | y + height - v * (height - padding * 2) - padding,
511 | );
512 | }
513 | ctx.stroke();
514 |
515 | ctx.fillText(
516 | `${item.title}: ${item.options.format(item.values[item.values.length - 1])}`,
517 | x + textPadding,
518 | dy + textY,
519 | );
520 | } else if (item.type === "stats") {
521 | ctx.fillStyle = this.theme.color;
522 |
523 | let lineIndex = 0;
524 | if (item.title) {
525 | ctx.fillText(item.title, x + textPadding, dy + textY);
526 | lineIndex++;
527 | }
528 |
529 | for (let [name, value] of Object.entries(item.stats)) {
530 | const lines = String(value).split("\n");
531 |
532 | for (let j = 0; j < lines.length; j++) {
533 | const lineValue = lines[j];
534 |
535 | ctx.fillText(
536 | j === 0 ? `${name}: ${lineValue}` : lineValue,
537 | x + textPadding * 2,
538 | dy + textY + titleHeight * lineIndex,
539 | );
540 | lineIndex++;
541 | }
542 | }
543 | } else if (item.type === "label") {
544 | ctx.fillStyle = this.theme.color;
545 | const lines = item.title.split("\n");
546 | for (let i = 0; i < lines.length; i++) {
547 | ctx.fillText(lines[i], x + textPadding, dy + textY + titleHeight * i);
548 | }
549 | } else if (item.type === "separator") {
550 | // Nothing to draw, just increase the gap.
551 | } else {
552 | ctx.fillStyle = this.theme.color;
553 | ctx.fillText(item.title, x + textPadding, dy + textY);
554 | }
555 | dy += eh + (needsPadding ? padding : 0) + gap;
556 | maxWidth = Math.max(maxWidth, dx + w + leftOffset);
557 | maxHeight = Math.max(maxHeight, dy + topOffset);
558 | }
559 |
560 | this.afterDraw();
561 | ctx.restore();
562 |
563 | maxWidth = Math.max(maxWidth, tabs.length * (w + gap));
564 |
565 | if (maxWidth && maxHeight) {
566 | maxWidth = (maxWidth * this.pixelRatio) | 0;
567 | maxHeight = (maxHeight * this.pixelRatio) | 0;
568 | if (this.canvas.width !== maxWidth) {
569 | this.canvas.width = maxWidth;
570 | this.dirty = true;
571 | }
572 | if (this.canvas.height !== maxHeight) {
573 | this.canvas.height = maxHeight;
574 | this.dirty = true;
575 | }
576 | if (this.dirty) {
577 | this.draw(items);
578 | }
579 | }
580 | }
581 |
582 | afterDraw() {}
583 |
584 | getTexture() {
585 | return this.canvas;
586 | }
587 |
588 | dispose() {
589 | this.canvas.remove();
590 | }
591 | }
592 |
593 | export default CanvasRenderer;
594 |
--------------------------------------------------------------------------------
/renderers/DebugRenderer.js:
--------------------------------------------------------------------------------
1 | import { rect } from "pex-geom";
2 |
3 | import CanvasRenderer from "./CanvasRenderer.js";
4 |
5 | class DebugRenderer extends CanvasRenderer {
6 | constructor(opts) {
7 | super(opts);
8 | }
9 |
10 | draw(items) {
11 | this.items = items;
12 | super.draw(items);
13 | }
14 |
15 | afterDraw() {
16 | this.items.forEach((item) => {
17 | this.ctx.strokeStyle = "rgba(255, 0, 0, 0.5)";
18 | this.ctx.strokeRect(
19 | item.activeArea[0][0],
20 | item.activeArea[0][1],
21 | rect.width(item.activeArea),
22 | rect.height(item.activeArea),
23 | );
24 | });
25 | }
26 | }
27 |
28 | export default DebugRenderer;
29 |
--------------------------------------------------------------------------------
/renderers/PexContextRenderer.js:
--------------------------------------------------------------------------------
1 | import CanvasRenderer from "./CanvasRenderer.js";
2 |
3 | class PexContextRenderer extends CanvasRenderer {
4 | #ctx;
5 |
6 | constructor(opts) {
7 | super(opts);
8 |
9 | const { ctx } = opts;
10 |
11 | this.#ctx = ctx;
12 |
13 | this.rendererTexture = ctx.texture2D({
14 | width: opts[0],
15 | height: opts[1],
16 | pixelFormat: ctx.PixelFormat.RGBA8,
17 | encoding: ctx.Encoding.SRGB,
18 | });
19 | }
20 |
21 | draw(items) {
22 | super.draw(items);
23 | }
24 |
25 | afterDraw() {
26 | this.#ctx.update(this.rendererTexture, {
27 | data: this.canvas,
28 | width: this.canvas.width,
29 | height: this.canvas.height,
30 | flipY: true,
31 | });
32 | }
33 |
34 | getTexture() {
35 | return this.rendererTexture;
36 | }
37 |
38 | dispose() {
39 | super.dispose();
40 |
41 | this.#ctx.dispose(this.rendererTexture);
42 | }
43 | }
44 |
45 | export default PexContextRenderer;
46 |
--------------------------------------------------------------------------------
/renderers/index.js:
--------------------------------------------------------------------------------
1 | export { default as CanvasRenderer } from "./CanvasRenderer.js";
2 | export { default as PexContextRenderer } from "./PexContextRenderer.js";
3 | export { default as DebugRenderer } from "./DebugRenderer.js";
4 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pex-gl/pex-gui/93ee94a82801cd78cc36077dc3e0fd194223a592/screenshot.png
--------------------------------------------------------------------------------
/shaders/chunks/encode-decode.glsl.js:
--------------------------------------------------------------------------------
1 | export default /* glsl */ `
2 | #define LINEAR 1
3 | #define GAMMA 2
4 | #define SRGB 3
5 | #define RGBM 4
6 |
7 | vec4 decode(vec4 pixel, int encoding) {
8 | if (encoding == LINEAR) return pixel;
9 | if (encoding == GAMMA) return toLinear(pixel);
10 | if (encoding == SRGB) return toLinear(pixel);
11 | if (encoding == RGBM) return vec4(decodeRGBM(pixel), 1.0);
12 | return pixel;
13 | }
14 |
15 | vec4 encode(vec4 pixel, int encoding) {
16 | if (encoding == LINEAR) return pixel;
17 | if (encoding == GAMMA) return toGamma(pixel);
18 | if (encoding == SRGB) return toGamma(pixel);
19 | if (encoding == RGBM) return encodeRGBM(pixel.rgb);
20 | return pixel;
21 | }
22 | `;
23 |
--------------------------------------------------------------------------------
/shaders/chunks/gamma.glsl.js:
--------------------------------------------------------------------------------
1 | export default /* glsl */ `
2 | const float gamma = 2.2;
3 |
4 | // Linear
5 | float toLinear(float v) {
6 | return pow(v, gamma);
7 | }
8 |
9 | vec2 toLinear(vec2 v) {
10 | return pow(v, vec2(gamma));
11 | }
12 |
13 | vec3 toLinear(vec3 v) {
14 | return pow(v, vec3(gamma));
15 | }
16 |
17 | vec4 toLinear(vec4 v) {
18 | return vec4(toLinear(v.rgb), v.a);
19 | }
20 |
21 | // Gamma
22 | float toGamma(float v) {
23 | return pow(v, 1.0 / gamma);
24 | }
25 |
26 | vec2 toGamma(vec2 v) {
27 | return pow(v, vec2(1.0 / gamma));
28 | }
29 |
30 | vec3 toGamma(vec3 v) {
31 | return pow(v, vec3(1.0 / gamma));
32 | }
33 |
34 | vec4 toGamma(vec4 v) {
35 | return vec4(toGamma(v.rgb), v.a);
36 | }
37 | `;
38 |
--------------------------------------------------------------------------------
/shaders/chunks/rgbm.glsl.js:
--------------------------------------------------------------------------------
1 | export default /* glsl */ `
2 | vec3 decodeRGBM (vec4 rgbm) {
3 | vec3 r = rgbm.rgb * (7.0 * rgbm.a);
4 | return r * r;
5 | }
6 |
7 | vec4 encodeRGBM (vec3 rgb_0) {
8 | vec4 r;
9 | r.xyz = (1.0 / 7.0) * sqrt(rgb_0);
10 | r.a = max(max(r.x, r.y), r.z);
11 | r.a = clamp(r.a, 1.0 / 255.0, 1.0);
12 | r.a = ceil(r.a * 255.0) / 255.0;
13 | r.xyz /= r.a;
14 | return r;
15 | }
16 | `;
17 |
--------------------------------------------------------------------------------
/shaders/main.vert.js:
--------------------------------------------------------------------------------
1 | export default /* glsl */ `
2 | attribute vec2 aPosition;
3 | attribute vec2 aTexCoord0;
4 |
5 | uniform vec4 uViewport;
6 | uniform vec4 uRect;
7 |
8 | varying vec2 vTexCoord0;
9 |
10 | void main() {
11 | vTexCoord0 = vec2(aTexCoord0.x, 1.0 - aTexCoord0.y);
12 | vec2 vertexPos = aPosition * 0.5 + 0.5;
13 |
14 | vec2 pos = vec2(0.0, 0.0); // window pos
15 | vec2 windowSize = vec2(uViewport.z - uViewport.x, uViewport.w - uViewport.y);
16 | pos.x = uRect.x / windowSize.x + vertexPos.x * (uRect.z - uRect.x) / windowSize.x;
17 | pos.y = uRect.y / windowSize.y + vertexPos.y * (uRect.w - uRect.y) / windowSize.y;
18 | pos.y = 1.0 - pos.y;
19 | pos = pos * 2.0 - 1.0;
20 |
21 | gl_Position = vec4(pos, 0.0, 1.0);
22 | }`;
23 |
--------------------------------------------------------------------------------
/shaders/texture-2d.frag.js:
--------------------------------------------------------------------------------
1 | import GAMMA from "./chunks/gamma.glsl.js";
2 | import RGBM from "./chunks/rgbm.glsl.js";
3 | import DECODE_ENCODE from "./chunks/encode-decode.glsl.js";
4 |
5 | export default /* glsl */ `#version 100
6 | precision highp float;
7 |
8 | ${GAMMA}
9 | ${RGBM}
10 | ${DECODE_ENCODE}
11 |
12 | uniform sampler2D uTexture;
13 | uniform int uTextureEncoding;
14 | varying vec2 vTexCoord0;
15 |
16 | void main() {
17 | vec4 color = texture2D(uTexture, vTexCoord0);
18 | color = decode(color, uTextureEncoding);
19 | // if LINEAR || RGBM then tonemap
20 | if (uTextureEncoding == LINEAR || uTextureEncoding == RGBM) {
21 | color.rgb = color.rgb / (color.rgb + 1.0);
22 | }
23 | gl_FragColor = encode(color, 2); // to gamma
24 | }`;
25 |
--------------------------------------------------------------------------------
/shaders/texture-cube.frag.js:
--------------------------------------------------------------------------------
1 | import GAMMA from "./chunks/gamma.glsl.js";
2 | import RGBM from "./chunks/rgbm.glsl.js";
3 | import DECODE_ENCODE from "./chunks/encode-decode.glsl.js";
4 |
5 | export default /* glsl */ `#version 100
6 | precision highp float;
7 |
8 | ${GAMMA}
9 | ${RGBM}
10 | ${DECODE_ENCODE}
11 |
12 | const float PI = 3.1415926;
13 |
14 | varying vec2 vTexCoord0;
15 |
16 | uniform samplerCube uTexture;
17 | uniform int uTextureEncoding;
18 | uniform float uLevel;
19 | uniform float uFlipEnvMap;
20 |
21 | void main() {
22 | float theta = PI * (vTexCoord0.x * 2.0);
23 | float phi = PI * (1.0 - vTexCoord0.y);
24 |
25 | float x = sin(phi) * sin(theta);
26 | float y = -cos(phi);
27 | float z = -sin(phi) * cos(theta);
28 |
29 | vec3 N = normalize(vec3(uFlipEnvMap * x, y, z));
30 | vec4 color = textureCube(uTexture, N, uLevel);
31 | color = decode(color, uTextureEncoding);
32 | // if LINEAR || RGBM then tonemap
33 | if (uTextureEncoding == LINEAR || uTextureEncoding == RGBM) {
34 | color.rgb = color.rgb / (color.rgb + 1.0);
35 | }
36 | gl_FragColor = encode(color, 2); // to gamma
37 | }`;
38 |
--------------------------------------------------------------------------------
/theme.js:
--------------------------------------------------------------------------------
1 | export default {
2 | fontFamily: `"Monaco", monospace`,
3 | fontSize: 10,
4 | capHeight: 0.09,
5 |
6 | leftOffset: 10,
7 | topOffset: 10,
8 |
9 | columnWidth: 160,
10 |
11 | tabHeight: 22,
12 | headerSize: 18,
13 |
14 | titleHeight: 20,
15 | itemHeight: 16,
16 | graphHeight: 40,
17 |
18 | padding: 3,
19 | textPadding: 2,
20 |
21 | color: "rgba(255, 255, 255, 1)",
22 | background: "rgba(0, 0, 0, 0.56)",
23 | input: "rgba(150, 150, 150, 1)",
24 | accent: "rgba(255, 255, 0, 1)",
25 |
26 | tabBackground: "rgba(75, 75, 75, 1)",
27 | tabColor: "rgba(175, 175, 175, 1)",
28 | tabBackgroundActive: "rgba(46, 204, 113, 1)",
29 | tabColorActive: "rgba(0, 0, 0, 1)",
30 |
31 | headerBackground: "rgba(255, 255, 255, 1)",
32 | headerColor: "rgba(0, 0, 0, 1)",
33 |
34 | graphBackground: "rgba(50, 50, 50, 1)",
35 | };
36 |
--------------------------------------------------------------------------------
/types.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @typedef {object} GUIControlOptions
3 | * @property {number} [min=0]
4 | * @property {number} [max=0]
5 | * @property {"color"} [type] Interpret an array as color.
6 | * @property {string} [colorSpace] Display a color as values of a pex-color color space.
7 | * @property {boolean} [alpha] Add a 4th slider for colors.
8 | * @property {HTMLImageElement} [palette] Draw a palette image as color picker.
9 | * @property {boolean} [flipEnvMap] Should be 1 for dynamic cubemaps and -1 for cubemaps from file with X axis flipped.
10 | * @property {boolean} [flipY] Flip texture 2D vertically.
11 | * @property {number} [level] Level of detail for cube textures.
12 | */
13 | /**
14 | * @typedef {object} GUIOptions
15 | * @property {boolean} [pixelRatio=window.devicePixelRatio]
16 | * @property {boolean} [theme={}] See [theme file]{@link https://github.com/pex-gl/pex-gui/blob/main/theme.js} for all options.
17 | * @property {number} [scale=1]
18 | * @property {boolean} [responsive=true] Adapts to canvas dimension.
19 | */
20 |
21 | /** @typedef {import("pex-context").ctx} ctx */
22 |
23 | export {};
24 |
--------------------------------------------------------------------------------
/web_modules/_chunks/_commonjsHelpers-BFTU3MAI.js:
--------------------------------------------------------------------------------
1 | var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
2 |
3 | function getDefaultExportFromCjs (x) {
4 | return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
5 | }
6 |
7 | export { commonjsGlobal as c, getDefaultExportFromCjs as g };
8 |
--------------------------------------------------------------------------------
/web_modules/_chunks/avec3-CX_9gCVx.js:
--------------------------------------------------------------------------------
1 | import { h as create } from './vec3-DW1VLBq6.js';
2 |
3 | const TEMP_VEC3 = create();
4 | /**
5 | * Sets a vector components.
6 | * @param {import("./types.js").avec3} a
7 | * @param {number} i
8 | * @param {number} x
9 | * @param {number} y
10 | * @param {number} z
11 | */ function set3(a, i, x, y, z) {
12 | a[i * 3] = x;
13 | a[i * 3 + 1] = y;
14 | a[i * 3 + 2] = z;
15 | }
16 | /**
17 | * Sets a vector to another vector.
18 | * @param {import("./types.js").avec3} a
19 | * @param {number} i
20 | * @param {import("./types.js").avec3} b
21 | * @param {number} j
22 | */ function set(a, i, b, j) {
23 | a[i * 3] = b[j * 3];
24 | a[i * 3 + 1] = b[j * 3 + 1];
25 | a[i * 3 + 2] = b[j * 3 + 2];
26 | }
27 | /**
28 | * Compares two vectors.
29 | * @param {import("./types.js").avec3} a
30 | * @param {number} i
31 | * @param {import("./types.js").avec3} b
32 | * @param {number} j
33 | * @returns {boolean}
34 | */ function equals(a, i, b, j) {
35 | return a[i * 3] === b[j * 3] && a[i * 3 + 1] === b[j * 3 + 1] && a[i * 3 + 2] === b[j * 3 + 2];
36 | }
37 | /**
38 | * Adds a vector to another.
39 | * @param {import("./types.js").avec3} a
40 | * @param {number} i
41 | * @param {import("./types.js").avec3} b
42 | * @param {number} j
43 | */ function add(a, i, b, j) {
44 | a[i * 3] += b[j * 3];
45 | a[i * 3 + 1] += b[j * 3 + 1];
46 | a[i * 3 + 2] += b[j * 3 + 2];
47 | }
48 | /**
49 | * Subtracts a vector from another.
50 | * @param {import("./types.js").avec3} a
51 | * @param {number} i
52 | * @param {import("./types.js").avec3} b
53 | * @param {number} j
54 | */ function sub(a, i, b, j) {
55 | a[i * 3] -= b[j * 3];
56 | a[i * 3 + 1] -= b[j * 3 + 1];
57 | a[i * 3 + 2] -= b[j * 3 + 2];
58 | }
59 | /**
60 | * Scales a vector by a number.
61 | * @param {import("./types.js").avec3} a
62 | * @param {number} i
63 | * @param {number} s
64 | */ function scale(a, i, s) {
65 | a[i * 3] *= s;
66 | a[i * 3 + 1] *= s;
67 | a[i * 3 + 2] *= s;
68 | }
69 | /**
70 | * Adds two vectors after scaling the second one.
71 | * @param {import("./types.js").avec3} a
72 | * @param {number} i
73 | * @param {import("./types.js").avec3} b
74 | * @param {number} j
75 | * @param {number} s
76 | */ function addScaled(a, i, b, j, s) {
77 | a[i * 3] += b[j * 3] * s;
78 | a[i * 3 + 1] += b[j * 3 + 1] * s;
79 | a[i * 3 + 2] += b[j * 3 + 2] * s;
80 | }
81 | /**
82 | * Multiplies a vector by a matrix.
83 | * @param {import("./types.js").avec3} a
84 | * @param {number} i
85 | * @param {import("./types.js").amat4} m
86 | * @param {number} j
87 | */ function multMat4(a, i, m, j) {
88 | const x = a[i * 3];
89 | const y = a[i * 3 + 1];
90 | const z = a[i * 3 + 2];
91 | a[i * 3] = m[j * 16] * x + m[j * 16 + 4] * y + m[j * 16 + 8] * z + m[j * 16 + 12];
92 | a[i * 3 + 1] = m[j * 16 + 1] * x + m[j * 16 + 5] * y + m[j * 16 + 9] * z + m[j * 16 + 13];
93 | a[i * 3 + 2] = m[j * 16 + 2] * x + m[j * 16 + 6] * y + m[j * 16 + 10] * z + m[j * 16 + 14];
94 | }
95 | /**
96 | * Multiplies a vector by a quaternion.
97 | * @param {import("./types.js").avec3} a
98 | * @param {number} i
99 | * @param {import("./types.js").aquat} q
100 | * @param {number} j
101 | */ function multQuat(a, i, q, j) {
102 | const x = a[i * 3];
103 | const y = a[i * 3 + 1];
104 | const z = a[i * 3 + 2];
105 | const qx = q[j * 4];
106 | const qy = q[j * 4 + 1];
107 | const qz = q[j * 4 + 2];
108 | const qw = q[j * 4 + 3];
109 | const ix = qw * x + qy * z - qz * y;
110 | const iy = qw * y + qz * x - qx * z;
111 | const iz = qw * z + qx * y - qy * x;
112 | const iw = -qx * x - qy * y - qz * z;
113 | a[i * 3] = ix * qw + iw * -qx + iy * -qz - iz * -qy;
114 | a[i * 3 + 1] = iy * qw + iw * -qy + iz * -qx - ix * -qz;
115 | a[i * 3 + 2] = iz * qw + iw * -qz + ix * -qy - iy * -qx;
116 | }
117 | /**
118 | * Calculates the dot product of two vectors.
119 | * @param {import("./types.js").avec3} a
120 | * @param {number} i
121 | * @param {import("./types.js").avec3} b
122 | * @param {number} j
123 | * @returns {number}
124 | */ function dot(a, i, b, j) {
125 | return a[i * 3] * b[j * 3] + a[i * 3 + 1] * b[j * 3 + 1] + a[i * 3 + 2] * b[j * 3 + 2];
126 | }
127 | /**
128 | * Calculates the cross product of two vectors.
129 | * @param {import("./types.js").avec3} a
130 | * @param {number} i
131 | * @param {import("./types.js").avec3} b
132 | * @param {number} j
133 | */ function cross(a, i, b, j) {
134 | const x = a[i * 3];
135 | const y = a[i * 3 + 1];
136 | const z = a[i * 3 + 2];
137 | const vx = b[j * 3];
138 | const vy = b[j * 3 + 1];
139 | const vz = b[j * 3 + 2];
140 | a[i * 3] = y * vz - vy * z;
141 | a[i * 3 + 1] = z * vx - vz * x;
142 | a[i * 3 + 2] = x * vy - vx * y;
143 | }
144 | /**
145 | * Calculates the length of a vector.
146 | * @param {import("./types.js").avec3} a
147 | * @param {number} i
148 | * @returns {number}
149 | */ function length(a, i) {
150 | const x = a[i * 3];
151 | const y = a[i * 3 + 1];
152 | const z = a[i * 3 + 2];
153 | return Math.sqrt(x * x + y * y + z * z);
154 | }
155 | /**
156 | * Calculates the squared length of a vector.
157 | * @param {import("./types.js").avec3} a
158 | * @param {number} i
159 | * @returns {number}
160 | */ function lengthSq(a, i) {
161 | const x = a[i * 3];
162 | const y = a[i * 3 + 1];
163 | const z = a[i * 3 + 2];
164 | return x * x + y * y + z * z;
165 | }
166 | /**
167 | * Normalises a vector.
168 | * @param {import("./types.js").avec3} a
169 | * @param {number} i
170 | */ function normalize(a, i) {
171 | const lenSq = a[i * 3] * a[i * 3] + a[i * 3 + 1] * a[i * 3 + 1] + a[i * 3 + 2] * a[i * 3 + 2];
172 | if (lenSq > 0) {
173 | const len = Math.sqrt(lenSq);
174 | a[i * 3] /= len;
175 | a[i * 3 + 1] /= len;
176 | a[i * 3 + 2] /= len;
177 | }
178 | }
179 | /**
180 | * Calculates the distance between two vectors.
181 | * @param {import("./types.js").avec3} a
182 | * @param {number} i
183 | * @param {import("./types.js").avec3} b
184 | * @param {number} j
185 | * @returns {number}
186 | */ function distance(a, i, b, j) {
187 | const dx = b[j * 3] - a[i * 3];
188 | const dy = b[j * 3 + 1] - a[i * 3 + 1];
189 | const dz = b[j * 3 + 2] - a[i * 3 + 2];
190 | return Math.sqrt(dx * dx + dy * dy + dz * dz);
191 | }
192 | /**
193 | * Calculates the squared distance between two vectors.
194 | * @param {import("./types.js").avec3} a
195 | * @param {number} i
196 | * @param {import("./types.js").avec3} b
197 | * @param {number} j
198 | * @returns {number}
199 | */ function distanceSq(a, i, b, j) {
200 | const dx = b[j * 3] - a[i * 3];
201 | const dy = b[j * 3 + 1] - a[i * 3 + 1];
202 | const dz = b[j * 3 + 2] - a[i * 3 + 2];
203 | return dx * dx + dy * dy + dz * dz;
204 | }
205 | /**
206 | * Limits a vector to a length.
207 | * @param {import("./types.js").avec3} a
208 | * @param {number} i
209 | * @param {number} len
210 | */ function limit(a, i, len) {
211 | const x = a[i * 3];
212 | const y = a[i * 3 + 1];
213 | const z = a[i * 3 + 2];
214 | const dsq = x * x + y * y + z * z;
215 | const lsq = len * len;
216 | if (lsq > 0 && dsq > lsq) {
217 | const nd = len / Math.sqrt(dsq);
218 | a[i * 3] *= nd;
219 | a[i * 3 + 1] *= nd;
220 | a[i * 3 + 2] *= nd;
221 | }
222 | }
223 | /**
224 | * Linearly interpolates between two vectors.
225 | * @param {import("./types.js").avec3} a
226 | * @param {number} i
227 | * @param {import("./types.js").avec3} b
228 | * @param {number} j
229 | * @param {number} t
230 | */ function lerp(a, i, b, j, t) {
231 | const x = a[i * 3];
232 | const y = a[i * 3 + 1];
233 | const z = a[i * 3 + 2];
234 | a[i * 3] = x + (b[j * 3] - x) * t;
235 | a[i * 3 + 1] = y + (b[j * 3 + 1] - y) * t;
236 | a[i * 3 + 2] = z + (b[j * 3 + 2] - z) * t;
237 | }
238 | /**
239 | * Executes a function once for each array element.
240 | * @param {import("./types.js").avec3} a
241 | * @param {import("./types.js").iterativeCallback} callbackFn
242 | */ function forEach(a, callbackFn) {
243 | for(let i = 0; i < a.length / 3; i++){
244 | TEMP_VEC3[0] = a[i * 3];
245 | TEMP_VEC3[1] = a[i * 3 + 1];
246 | TEMP_VEC3[2] = a[i * 3 + 2];
247 | callbackFn(TEMP_VEC3, i, a);
248 | a[i * 3] = TEMP_VEC3[0];
249 | a[i * 3 + 1] = TEMP_VEC3[1];
250 | a[i * 3 + 2] = TEMP_VEC3[2];
251 | }
252 | }
253 | /**
254 | * Creates a new array populated with the results of calling a provided function on every element in the calling array.
255 | * @param {import("./types.js").avec3} a
256 | * @param {import("./types.js").iterativeCallback} callbackFn
257 | * @returns {import("./types.js").avec3}
258 | */ function map(a, callbackFn) {
259 | const b = new a.constructor(a.length);
260 | const element = new a.constructor(3);
261 | for(let i = 0; i < a.length / 3; i++){
262 | element[0] = a[i * 3];
263 | element[1] = a[i * 3 + 1];
264 | element[2] = a[i * 3 + 2];
265 | const returnValue = callbackFn(element, i, a);
266 | b[i * 3] = returnValue[0];
267 | b[i * 3 + 1] = returnValue[1];
268 | b[i * 3 + 2] = returnValue[2];
269 | }
270 | return b;
271 | }
272 | /**
273 | * Prints a vector to a string.
274 | * @param {import("./types.js").avec3} a
275 | * @param {number} i
276 | * @param {number} [precision=4]
277 | * @returns {string}
278 | */ function toString(a, i, precision) {
279 | if (precision === undefined) precision = 4;
280 | const scale = 10 ** precision;
281 | // prettier-ignore
282 | return `[${Math.floor(a[i * 3] * scale) / scale}, ${Math.floor(a[i * 3 + 1] * scale) / scale}, ${Math.floor(a[i * 3 + 2] * scale) / scale}]`;
283 | }
284 |
285 | var avec3 = /*#__PURE__*/Object.freeze({
286 | __proto__: null,
287 | add: add,
288 | addScaled: addScaled,
289 | cross: cross,
290 | distance: distance,
291 | distanceSq: distanceSq,
292 | dot: dot,
293 | equals: equals,
294 | forEach: forEach,
295 | length: length,
296 | lengthSq: lengthSq,
297 | lerp: lerp,
298 | limit: limit,
299 | map: map,
300 | multMat4: multMat4,
301 | multQuat: multQuat,
302 | normalize: normalize,
303 | scale: scale,
304 | set: set,
305 | set3: set3,
306 | sub: sub,
307 | toString: toString
308 | });
309 |
310 | export { avec3 as a, set3 as s };
311 |
--------------------------------------------------------------------------------
/web_modules/_chunks/mat4-DkKmQtAV.js:
--------------------------------------------------------------------------------
1 | /** @module utils */ /**
2 | * @constant {number}
3 | */ const EPSILON = 0.000001;
4 | /**
5 | * @constant {import("./types.js").vec3}
6 | */ const Y_UP = Object.freeze([
7 | 0,
8 | 1,
9 | 0
10 | ]);
11 | /**
12 | * Linear interpolation between two numbers.
13 | * @param {number} a
14 | * @param {number} b
15 | * @param {number} t
16 | * @returns {number}
17 | */ function lerp(a, b, t) {
18 | return a + (b - a) * t;
19 | }
20 | /**
21 | * Clamps a number between two numbers.
22 | * @param {number} n
23 | * @param {number} min
24 | * @param {number} max
25 | * @returns {number}
26 | */ function clamp(n, min, max) {
27 | return Math.max(min, Math.min(n, max));
28 | }
29 | /**
30 | * Smooth Hermite interpolation between 0 and 1
31 | * @param {number} n
32 | * @param {number} min
33 | * @param {number} max
34 | * @returns {number}
35 | */ function smoothstep(n, min, max) {
36 | n = clamp((n - min) / (max - min), 0, 1);
37 | return n * n * (3 - 2 * n);
38 | }
39 | /**
40 | * Maps a number from one range to another.
41 | * @param {number} n
42 | * @param {number} inStart
43 | * @param {number} inEnd
44 | * @param {number} outStart
45 | * @param {number} outEnd
46 | * @returns {number}
47 | */ function remap(n, inStart, inEnd, outStart, outEnd) {
48 | return outStart + (outEnd - outStart) * (n - inStart) / (inEnd - inStart);
49 | }
50 | /**
51 | * @deprecated Use "remap()"
52 | * @ignore
53 | */ function map() {
54 | for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
55 | args[_key] = arguments[_key];
56 | }
57 | return remap(...args);
58 | }
59 | /**
60 | * Transforms degrees into radians.
61 | * @param {import("./types.js").Degrees} degrees
62 | * @returns {import("./types.js").Radians}
63 | */ function toRadians(degrees) {
64 | return degrees * Math.PI / 180;
65 | }
66 | /**
67 | * Transforms radians into degrees.
68 | * @param {import("./types.js").Radians} radians
69 | * @returns {import("./types.js").Degrees}
70 | */ function toDegrees(radians) {
71 | return radians * 180 / Math.PI;
72 | }
73 | /**
74 | * Check if a number is a power of two
75 | * @param {number} a
76 | * @returns {boolean}
77 | */ function isPowerOfTwo(a) {
78 | return (a & a - 1) === 0;
79 | }
80 | /**
81 | * Returns the next highest power of two.
82 | * @param {number} n
83 | * @returns {number}
84 | */ function nextPowerOfTwo(n) {
85 | if (n === 0) return 1;
86 | n--;
87 | n |= n >> 1;
88 | n |= n >> 2;
89 | n |= n >> 4;
90 | n |= n >> 8;
91 | n |= n >> 16;
92 | return n + 1;
93 | }
94 | /**
95 | * Returns the previous power of two.
96 | * @param {number} n
97 | * @returns {number}
98 | */ function prevPowerOfTwo(n) {
99 | if (n <= 1) return 0;
100 | return nextPowerOfTwo(n) / 2;
101 | }
102 |
103 | var utils = /*#__PURE__*/Object.freeze({
104 | __proto__: null,
105 | EPSILON: EPSILON,
106 | Y_UP: Y_UP,
107 | clamp: clamp,
108 | isPowerOfTwo: isPowerOfTwo,
109 | lerp: lerp,
110 | map: map,
111 | nextPowerOfTwo: nextPowerOfTwo,
112 | prevPowerOfTwo: prevPowerOfTwo,
113 | remap: remap,
114 | smoothstep: smoothstep,
115 | toDegrees: toDegrees,
116 | toRadians: toRadians
117 | });
118 |
119 | /**
120 | * Returns a 4x4 identity matrix.
121 | *
122 | * Row major memory layout:
123 | *
124 | * ```
125 | * 0 1 2 3
126 | * 4 5 6 7
127 | * 8 9 10 11
128 | * 12 13 14 15
129 | * ```
130 | *
131 | * Equivalent to the column major OpenGL spec:
132 | *
133 | * ```
134 | * 0 4 8 12
135 | * 1 5 9 13
136 | * 2 6 10 14
137 | * 3 7 11 15
138 | *
139 | * m00 m10 m20 m30
140 | * m01 m11 m21 m31
141 | * m02 m12 m22 m32
142 | * m03 m13 m23 m33
143 | * ```
144 | * @returns {import("./types.js").mat4}
145 | */ function create() {
146 | // prettier-ignore
147 | return [
148 | 1,
149 | 0,
150 | 0,
151 | 0,
152 | 0,
153 | 1,
154 | 0,
155 | 0,
156 | 0,
157 | 0,
158 | 1,
159 | 0,
160 | 0,
161 | 0,
162 | 0,
163 | 1
164 | ];
165 | }
166 | /**
167 | * Sets a matrix to the identity matrix.
168 | * @param {import("./types.js").mat4} a
169 | * @returns {import("./types.js").mat4}
170 | */ function identity(a) {
171 | a[0] = a[5] = a[10] = a[15] = 1;
172 | a[1] = a[2] = a[3] = a[4] = a[6] = a[7] = a[8] = a[9] = a[11] = a[12] = a[13] = a[14] = 0;
173 | return a;
174 | }
175 | /**
176 | * Returns a copy of a matrix.
177 | * @param {import("./types.js").mat4} a
178 | * @returns {import("./types.js").mat4}
179 | */ function copy(a) {
180 | return a.slice();
181 | }
182 | /**
183 | * Sets a matrix from another matrix.
184 | * @param {import("./types.js").mat4} a
185 | * @param {import("./types.js").mat4} b
186 | * @returns {import("./types.js").mat4}
187 | */ function set(a, b) {
188 | a[0] = b[0];
189 | a[1] = b[1];
190 | a[2] = b[2];
191 | a[3] = b[3];
192 | a[4] = b[4];
193 | a[5] = b[5];
194 | a[6] = b[6];
195 | a[7] = b[7];
196 | a[8] = b[8];
197 | a[9] = b[9];
198 | a[10] = b[10];
199 | a[11] = b[11];
200 | a[12] = b[12];
201 | a[13] = b[13];
202 | a[14] = b[14];
203 | a[15] = b[15];
204 | return a;
205 | }
206 | /**
207 | * Compares two matrices.
208 | * @param {import("./types.js").mat4} a
209 | * @param {import("./types.js").mat4} b
210 | * @returns {boolean}
211 | */ function equals(a, b) {
212 | return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5] && a[6] === b[6] && a[7] === b[7] && a[8] === b[8] && a[9] === b[9] && a[10] === b[10] && a[11] === b[11] && a[12] === b[12] && a[13] === b[13] && a[14] === b[14] && a[15] === b[15];
213 | }
214 | /**
215 | * Multiplies two matrices.
216 | * @param {import("./types.js").mat4} a
217 | * @param {import("./types.js").mat4} b
218 | * @returns {import("./types.js").mat4}
219 | */ function mult(a, b) {
220 | const a00 = a[0];
221 | const a01 = a[1];
222 | const a02 = a[2];
223 | const a03 = a[3];
224 | const a10 = a[4];
225 | const a11 = a[5];
226 | const a12 = a[6];
227 | const a13 = a[7];
228 | const a20 = a[8];
229 | const a21 = a[9];
230 | const a22 = a[10];
231 | const a23 = a[11];
232 | const a30 = a[12];
233 | const a31 = a[13];
234 | const a32 = a[14];
235 | const a33 = a[15];
236 | let b0 = b[0];
237 | let b1 = b[1];
238 | let b2 = b[2];
239 | let b3 = b[3];
240 | a[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
241 | a[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
242 | a[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
243 | a[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
244 | b0 = b[4];
245 | b1 = b[5];
246 | b2 = b[6];
247 | b3 = b[7];
248 | a[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
249 | a[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
250 | a[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
251 | a[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
252 | b0 = b[8];
253 | b1 = b[9];
254 | b2 = b[10];
255 | b3 = b[11];
256 | a[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
257 | a[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
258 | a[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
259 | a[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
260 | b0 = b[12];
261 | b1 = b[13];
262 | b2 = b[14];
263 | b3 = b[15];
264 | a[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
265 | a[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
266 | a[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
267 | a[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
268 | return a;
269 | }
270 | /**
271 | * Inverts a matrix.
272 | * @param {import("./types.js").mat4} a
273 | * @returns {import("./types.js").mat4}
274 | */ function invert(a) {
275 | const a00 = a[0];
276 | const a10 = a[1];
277 | const a20 = a[2];
278 | const a30 = a[3];
279 | const a01 = a[4];
280 | const a11 = a[5];
281 | const a21 = a[6];
282 | const a31 = a[7];
283 | const a02 = a[8];
284 | const a12 = a[9];
285 | const a22 = a[10];
286 | const a32 = a[11];
287 | const a03 = a[12];
288 | const a13 = a[13];
289 | const a23 = a[14];
290 | const a33 = a[15];
291 | const a22a33 = a22 * a33;
292 | const a32a23 = a32 * a23;
293 | const a21a33 = a21 * a33;
294 | const a31a23 = a31 * a23;
295 | const a21a32 = a21 * a32;
296 | const a31a22 = a31 * a22;
297 | const a20a33 = a20 * a33;
298 | const a30a23 = a30 * a23;
299 | const a20a32 = a20 * a32;
300 | const a30a22 = a30 * a22;
301 | const a20a31 = a20 * a31;
302 | const a30a21 = a30 * a21;
303 | const a00a11 = a00 * a11;
304 | const a01a10 = a01 * a10;
305 | const a02a10 = a02 * a10;
306 | const a03a10 = a03 * a10;
307 | const a12a33 = a12 * a33;
308 | const a32a13 = a32 * a13;
309 | const a00a31 = a00 * a31;
310 | const a03a11 = a03 * a11;
311 | const a12a23 = a12 * a23;
312 | const a22a13 = a22 * a13;
313 | const a02a11 = a02 * a11;
314 | const a21a13 = a21 * a13;
315 | const a21a12 = a21 * a12;
316 | const a02a30 = a02 * a30;
317 | const a03a30 = a03 * a30;
318 | const a02a20 = a02 * a20;
319 | const a03a20 = a03 * a20;
320 | const a01a30 = a01 * a30;
321 | const a01a20 = a01 * a20;
322 | a[0] = a11 * a22a33 - a11 * a32a23 - a12 * a21a33 + a12 * a31a23 + a13 * a21a32 - a13 * a31a22;
323 | a[4] = -a01 * a22a33 + a01 * a32a23 + a02 * a21a33 - a02 * a31a23 - a03 * a21a32 + a03 * a31a22;
324 | a[8] = a01 * a12a33 - a01 * a32a13 - a02a11 * a33 + a02 * a31 * a13 + a03a11 * a32 - a03 * a31 * a12;
325 | a[12] = -a01 * a12a23 + a01 * a22a13 + a02a11 * a23 - a02 * a21a13 - a03a11 * a22 + a03 * a21a12;
326 | a[1] = -a10 * a22a33 + a10 * a32a23 + a12 * a20a33 - a12 * a30a23 - a13 * a20a32 + a13 * a30a22;
327 | a[5] = a00 * a22a33 - a00 * a32a23 - a02 * a20a33 + a02 * a30a23 + a03 * a20a32 - a03 * a30a22;
328 | a[9] = -a00 * a12a33 + a00 * a32a13 + a02a10 * a33 - a02a30 * a13 - a03a10 * a32 + a03a30 * a12;
329 | a[13] = a00 * a12a23 - a00 * a22a13 - a02a10 * a23 + a02a20 * a13 + a03a10 * a22 - a03a20 * a12;
330 | a[2] = a10 * a21a33 - a10 * a31a23 - a11 * a20a33 + a11 * a30a23 + a13 * a20a31 - a13 * a30a21;
331 | a[6] = -a00 * a21a33 + a00 * a31a23 + a01 * a20a33 - a01 * a30a23 - a03 * a20a31 + a03 * a30a21;
332 | a[10] = a00a11 * a33 - a00a31 * a13 - a01a10 * a33 + a01a30 * a13 + a03a10 * a31 - a03a30 * a11;
333 | a[14] = -a00a11 * a23 + a00 * a21a13 + a01a10 * a23 - a01a20 * a13 - a03a10 * a21 + a03a20 * a11;
334 | a[3] = -a10 * a21a32 + a10 * a31a22 + a11 * a20a32 - a11 * a30a22 - a12 * a20a31 + a12 * a30a21;
335 | a[7] = a00 * a21a32 - a00 * a31a22 - a01 * a20a32 + a01 * a30a22 + a02 * a20a31 - a02 * a30a21;
336 | a[11] = -a00a11 * a32 + a00a31 * a12 + a01a10 * a32 - a01a30 * a12 - a02a10 * a31 + a02a30 * a11;
337 | a[15] = a00a11 * a22 - a00 * a21a12 - a01a10 * a22 + a01a20 * a12 + a02a10 * a21 - a02a20 * a11;
338 | let det = a00 * a[0] + a10 * a[4] + a20 * a[8] + a30 * a[12];
339 | if (!det) return null;
340 | det = 1 / det;
341 | a[0] *= det;
342 | a[1] *= det;
343 | a[2] *= det;
344 | a[3] *= det;
345 | a[4] *= det;
346 | a[5] *= det;
347 | a[6] *= det;
348 | a[7] *= det;
349 | a[8] *= det;
350 | a[9] *= det;
351 | a[10] *= det;
352 | a[11] *= det;
353 | a[12] *= det;
354 | a[13] *= det;
355 | a[14] *= det;
356 | a[15] *= det;
357 | return a;
358 | }
359 | /**
360 | * Transposes a matrix.
361 | * @param {import("./types.js").mat4} a
362 | * @returns {import("./types.js").mat4}
363 | */ function transpose(a) {
364 | const a01 = a[1];
365 | const a02 = a[2];
366 | const a03 = a[3];
367 | const a12 = a[6];
368 | const a13 = a[7];
369 | const a20 = a[8];
370 | const a21 = a[9];
371 | const a23 = a[11];
372 | const a30 = a[12];
373 | const a31 = a[13];
374 | const a32 = a[14];
375 | // 1st row - keeping a00
376 | a[1] = a[4];
377 | a[2] = a20;
378 | a[3] = a30;
379 | // 2nd row - keeping a11
380 | a[4] = a01;
381 | a[6] = a21;
382 | a[7] = a31;
383 | // 3rd row - keeping a22
384 | a[8] = a02;
385 | a[9] = a12;
386 | a[11] = a32;
387 | // 4th row - keeping a33
388 | a[12] = a03;
389 | a[13] = a13;
390 | a[14] = a23;
391 | return a;
392 | }
393 | /**
394 | * Translates a matrix by a vector.
395 | * @param {import("./types.js").mat4} a
396 | * @param {import("./types.js").vec3} v
397 | * @returns {import("./types.js").mat4}
398 | */ function translate(a, param) {
399 | let [x, y, z] = param;
400 | a[12] += a[0] * x + a[4] * y + a[8] * z;
401 | a[13] += a[1] * x + a[5] * y + a[9] * z;
402 | a[14] += a[2] * x + a[6] * y + a[10] * z;
403 | a[15] += a[3] * x + a[7] * y + a[11] * z;
404 | return a;
405 | }
406 | /**
407 | * Rotates a matrix by an angle at an axis.
408 | * @param {import("./types.js").mat4} a
409 | * @param {import("./types.js").Radians} r
410 | * @param {import("./types.js").vec3} v
411 | * @returns {import("./types.js").mat4}
412 | */ function rotate(a, r, param) {
413 | let [x, y, z] = param;
414 | let len = Math.sqrt(x * x + y * y + z * z);
415 | if (len < EPSILON) {
416 | return null;
417 | }
418 | len = 1 / len;
419 | x *= len;
420 | y *= len;
421 | z *= len;
422 | const s = Math.sin(r);
423 | const c = Math.cos(r);
424 | const t = 1 - c;
425 | const a00 = a[0];
426 | const a01 = a[1];
427 | const a02 = a[2];
428 | const a03 = a[3];
429 | const a10 = a[4];
430 | const a11 = a[5];
431 | const a12 = a[6];
432 | const a13 = a[7];
433 | const a20 = a[8];
434 | const a21 = a[9];
435 | const a22 = a[10];
436 | const a23 = a[11];
437 | const b00 = x * x * t + c;
438 | const b01 = y * x * t + z * s;
439 | const b02 = z * x * t - y * s;
440 | const b10 = x * y * t - z * s;
441 | const b11 = y * y * t + c;
442 | const b12 = z * y * t + x * s;
443 | const b20 = x * z * t + y * s;
444 | const b21 = y * z * t - x * s;
445 | const b22 = z * z * t + c;
446 | a[0] = a00 * b00 + a10 * b01 + a20 * b02;
447 | a[1] = a01 * b00 + a11 * b01 + a21 * b02;
448 | a[2] = a02 * b00 + a12 * b01 + a22 * b02;
449 | a[3] = a03 * b00 + a13 * b01 + a23 * b02;
450 | a[4] = a00 * b10 + a10 * b11 + a20 * b12;
451 | a[5] = a01 * b10 + a11 * b11 + a21 * b12;
452 | a[6] = a02 * b10 + a12 * b11 + a22 * b12;
453 | a[7] = a03 * b10 + a13 * b11 + a23 * b12;
454 | a[8] = a00 * b20 + a10 * b21 + a20 * b22;
455 | a[9] = a01 * b20 + a11 * b21 + a21 * b22;
456 | a[10] = a02 * b20 + a12 * b21 + a22 * b22;
457 | a[11] = a03 * b20 + a13 * b21 + a23 * b22;
458 | return a;
459 | }
460 | /**
461 | * Scales a matrix by a vector.
462 | * @param {import("./types.js").mat4} a
463 | * @param {import("./types.js").vec3} v
464 | * @returns {import("./types.js").mat4}
465 | */ function scale(a, param) {
466 | let [x, y, z] = param;
467 | a[0] *= x;
468 | a[1] *= x;
469 | a[2] *= x;
470 | a[3] *= x;
471 | a[4] *= y;
472 | a[5] *= y;
473 | a[6] *= y;
474 | a[7] *= y;
475 | a[8] *= z;
476 | a[9] *= z;
477 | a[10] *= z;
478 | a[11] *= z;
479 | return a;
480 | }
481 | /**
482 | * Sets a matrix to a quaternion.
483 | * @param {import("./types.js").mat4} a
484 | * @param {import("./types.js").quat} q
485 | * @returns {import("./types.js").mat4}
486 | */ function fromQuat(a, q) {
487 | const x = q[0];
488 | const y = q[1];
489 | const z = q[2];
490 | const w = q[3];
491 | const x2 = x + x;
492 | const y2 = y + y;
493 | const z2 = z + z;
494 | const xx = x * x2;
495 | const xy = x * y2;
496 | const xz = x * z2;
497 | const yy = y * y2;
498 | const yz = y * z2;
499 | const zz = z * z2;
500 | const wx = w * x2;
501 | const wy = w * y2;
502 | const wz = w * z2;
503 | a[0] = 1 - (yy + zz);
504 | a[4] = xy - wz;
505 | a[8] = xz + wy;
506 | a[1] = xy + wz;
507 | a[5] = 1 - (xx + zz);
508 | a[9] = yz - wx;
509 | a[2] = xz - wy;
510 | a[6] = yz + wx;
511 | a[10] = 1 - (xx + yy);
512 | a[3] = a[7] = a[11] = a[12] = a[13] = a[14] = 0;
513 | a[15] = 1;
514 | return a;
515 | }
516 | /**
517 | * Sets a matrix to the TRS matrix.
518 | * @param {import("./types.js").mat4} a
519 | * @param {import("./types.js").vec3} v
520 | * @param {import("./types.js").quat} q
521 | * @param {import("./types.js").vec3} s
522 | * @returns {import("./types.js").mat4}
523 | */ function fromTranslationRotationScale(a, v, q, s) {
524 | // const TEMP_0 = create();
525 | // identity(a);
526 | // translate(a, translation);
527 | // mult(a, fromQuat(TEMP_0, rotation));
528 | // scale(a, scaling);
529 | const x = q[0];
530 | const y = q[1];
531 | const z = q[2];
532 | const w = q[3];
533 | const x2 = x + x;
534 | const y2 = y + y;
535 | const z2 = z + z;
536 | const xx = x * x2;
537 | const xy = x * y2;
538 | const xz = x * z2;
539 | const yy = y * y2;
540 | const yz = y * z2;
541 | const zz = z * z2;
542 | const wx = w * x2;
543 | const wy = w * y2;
544 | const wz = w * z2;
545 | a[3] = a[7] = a[11] = 0;
546 | a[15] = 1;
547 | a[0] = (1 - (yy + zz)) * s[0];
548 | a[1] = (xy + wz) * s[0];
549 | a[2] = (xz - wy) * s[0];
550 | a[4] = (xy - wz) * s[1];
551 | a[5] = (1 - (xx + zz)) * s[1];
552 | a[6] = (yz + wx) * s[1];
553 | a[8] = (xz + wy) * s[2];
554 | a[9] = (yz - wx) * s[2];
555 | a[10] = (1 - (xx + yy)) * s[2];
556 | a[12] = v[0];
557 | a[13] = v[1];
558 | a[14] = v[2];
559 | return a;
560 | }
561 | /**
562 | * Sets a 4x4 matrix to a 3x3 matrix.
563 | * @param {import("./types.js").mat4} a
564 | * @param {import("./types.js").mat3} b
565 | * @returns {import("./types.js").mat4}
566 | */ function fromMat3(a, b) {
567 | a[0] = b[0];
568 | a[1] = b[1];
569 | a[2] = b[2];
570 | a[4] = b[3];
571 | a[5] = b[4];
572 | a[6] = b[5];
573 | a[8] = b[6];
574 | a[9] = b[7];
575 | a[10] = b[8];
576 | a[3] = a[7] = a[11] = a[12] = a[13] = a[14] = 0;
577 | a[15] = 1;
578 | return a;
579 | }
580 | /**
581 | * Creates a frustum matrix.
582 | * @param {import("./types.js").mat4} a
583 | * @param {number} left
584 | * @param {number} right
585 | * @param {number} bottom
586 | * @param {number} top
587 | * @param {number} near
588 | * @param {number} far
589 | * @returns {import("./types.js").mat4}
590 | */ function frustum(a, left, right, bottom, top, near, far) {
591 | const rl = 1 / (right - left);
592 | const tb = 1 / (top - bottom);
593 | const nf = 1 / (near - far);
594 | const near2 = near * 2;
595 | a[0] = near2 * rl;
596 | a[1] = a[2] = 0;
597 | a[3] = 0;
598 | a[4] = 0;
599 | a[5] = near2 * tb;
600 | a[6] = 0;
601 | a[7] = 0;
602 | a[8] = (right + left) * rl;
603 | a[9] = (top + bottom) * tb;
604 | a[10] = (far + near) * nf;
605 | a[11] = -1;
606 | a[12] = 0;
607 | a[13] = 0;
608 | a[14] = far * near2 * nf;
609 | a[15] = 0;
610 | return a;
611 | }
612 | /**
613 | * Creates a perspective matrix.
614 | * @param {import("./types.js").mat4} a
615 | * @param {import("./types.js").Radians} fovy
616 | * @param {number} aspectRatio
617 | * @param {number} near
618 | * @param {number} far
619 | * @returns {import("./types.js").mat4}
620 | */ function perspective(a, fovy, aspectRatio, near, far) {
621 | const f = 1 / Math.tan(fovy / 2);
622 | const nf = 1 / (near - far);
623 | a[1] = a[2] = a[3] = a[4] = a[6] = a[7] = a[8] = a[9] = a[12] = a[13] = a[15] = 0;
624 | a[0] = f / aspectRatio;
625 | a[5] = f;
626 | a[10] = (far + near) * nf;
627 | a[11] = -1;
628 | a[14] = 2 * far * near * nf;
629 | return a;
630 | }
631 | /**
632 | * Creates an orthographic matrix.
633 | * @param {import("./types.js").mat4} a
634 | * @param {number} left
635 | * @param {number} right
636 | * @param {number} bottom
637 | * @param {number} top
638 | * @param {number} near
639 | * @param {number} far
640 | * @returns {import("./types.js").mat4}
641 | */ function ortho(a, left, right, bottom, top, near, far) {
642 | const lr = left - right;
643 | const bt = bottom - top;
644 | const nf = near - far;
645 | a[1] = a[2] = a[3] = a[4] = a[6] = a[7] = a[8] = a[9] = a[11] = 0;
646 | a[0] = -2 / lr;
647 | a[5] = -2 / bt;
648 | a[10] = 2 / nf;
649 | a[12] = (left + right) / lr;
650 | a[13] = (top + bottom) / bt;
651 | a[14] = (far + near) / nf;
652 | a[15] = 1;
653 | return a;
654 | }
655 | /**
656 | * Calculates a lookAt matrix from position, target and up vectors.
657 | * @param {import("./types.js").mat4} a
658 | * @param {import("./types.js").vec3} from
659 | * @param {import("./types.js").vec3} to
660 | * @param {import("./types.js").vec3} [up=Y_UP]
661 | * @returns {import("./types.js").mat4}
662 | */ function lookAt(a, param, param1, param2) {
663 | let [eyex, eyey, eyez] = param;
664 | let [targetx, targety, targetz] = param1;
665 | let [upx, upy, upz] = param2 === undefined ? Y_UP : param2;
666 | if (Math.abs(eyex - targetx) < EPSILON && Math.abs(eyey - targety) < EPSILON && Math.abs(eyez - targetz) < EPSILON) {
667 | return identity(a);
668 | }
669 | let z0 = eyex - targetx;
670 | let z1 = eyey - targety;
671 | let z2 = eyez - targetz;
672 | let len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2);
673 | z0 *= len;
674 | z1 *= len;
675 | z2 *= len;
676 | let x0 = upy * z2 - upz * z1;
677 | let x1 = upz * z0 - upx * z2;
678 | let x2 = upx * z1 - upy * z0;
679 | len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2);
680 | if (len) {
681 | len = 1 / len;
682 | x0 *= len;
683 | x1 *= len;
684 | x2 *= len;
685 | }
686 | let y0 = z1 * x2 - z2 * x1;
687 | let y1 = z2 * x0 - z0 * x2;
688 | let y2 = z0 * x1 - z1 * x0;
689 | len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2);
690 | if (len) {
691 | len = 1 / len;
692 | x0 *= len;
693 | x1 *= len;
694 | x2 *= len;
695 | }
696 | a[0] = x0;
697 | a[1] = y0;
698 | a[2] = z0;
699 | a[3] = 0;
700 | a[4] = x1;
701 | a[5] = y1;
702 | a[6] = z1;
703 | a[7] = 0;
704 | a[8] = x2;
705 | a[9] = y2;
706 | a[10] = z2;
707 | a[11] = 0;
708 | a[12] = -(x0 * eyex + x1 * eyey + x2 * eyez);
709 | a[13] = -(y0 * eyex + y1 * eyey + y2 * eyez);
710 | a[14] = -(z0 * eyex + z1 * eyey + z2 * eyez);
711 | a[15] = 1;
712 | return a;
713 | }
714 | /**
715 | * Sets a matrix from a direction.
716 | * Note: we assume +Z facing models.
717 | * @param {import("./types.js").mat4} a
718 | * @param {import("./types.js").vec3} direction
719 | * @param {import("./types.js").vec3} [up=Y_UP]
720 | * @returns {import("./types.js").mat4}
721 | */ function fromDirection(a, param, param1) {
722 | let [z0, z1, z2] = param;
723 | let [upx, upy, upz] = param1 === undefined ? Y_UP : param1;
724 | let len = z0 * z0 + z1 * z1 + z2 * z2;
725 | if (len > 0) {
726 | len = 1 / Math.sqrt(len);
727 | z0 *= len;
728 | z1 *= len;
729 | z2 *= len;
730 | }
731 | let x0 = upy * z2 - upz * z1;
732 | let x1 = upz * z0 - upx * z2;
733 | let x2 = upx * z1 - upy * z0;
734 | len = x0 * x0 + x1 * x1 + x2 * x2;
735 | if (len > 0) {
736 | len = 1 / Math.sqrt(len);
737 | x0 *= len;
738 | x1 *= len;
739 | x2 *= len;
740 | }
741 | upx = z1 * x2 - z2 * x1;
742 | upy = z2 * x0 - z0 * x2;
743 | upz = z0 * x1 - z1 * x0;
744 | len = upx * upx + upy * upy + upz * upz;
745 | if (len > 0) {
746 | len = 1 / Math.sqrt(len);
747 | upx *= len;
748 | upy *= len;
749 | upz *= len;
750 | }
751 | a[0] = x0;
752 | a[1] = x1;
753 | a[2] = x2;
754 | a[3] = 0;
755 | a[4] = upx;
756 | a[5] = upy;
757 | a[6] = upz;
758 | a[7] = 0;
759 | a[8] = z0;
760 | a[9] = z1;
761 | a[10] = z2;
762 | a[11] = 0;
763 | a[12] = 0;
764 | a[13] = 0;
765 | a[14] = 0;
766 | a[15] = 1;
767 | return a;
768 | }
769 | /**
770 | * Sets a matrix from a point to another.
771 | * @param {import("./types.js").mat4} a
772 | * @param {import("./types.js").vec3} from
773 | * @param {import("./types.js").vec3} to
774 | * @param {import("./types.js").vec3} [up=Y_UP]
775 | * @returns {import("./types.js").mat4}
776 | */ function fromPointToPoint(a, param, param1, up) {
777 | let [fromX, fromY, fromZ] = param;
778 | let [toX, toY, toZ] = param1;
779 | return fromDirection(a, [
780 | toX - fromX,
781 | toY - fromY,
782 | toZ - fromZ
783 | ], up);
784 | }
785 |
786 | var mat4 = /*#__PURE__*/Object.freeze({
787 | __proto__: null,
788 | copy: copy,
789 | create: create,
790 | equals: equals,
791 | fromDirection: fromDirection,
792 | fromMat3: fromMat3,
793 | fromPointToPoint: fromPointToPoint,
794 | fromQuat: fromQuat,
795 | fromTranslationRotationScale: fromTranslationRotationScale,
796 | frustum: frustum,
797 | identity: identity,
798 | invert: invert,
799 | lookAt: lookAt,
800 | mult: mult,
801 | ortho: ortho,
802 | perspective: perspective,
803 | rotate: rotate,
804 | scale: scale,
805 | set: set,
806 | translate: translate,
807 | transpose: transpose
808 | });
809 |
810 | export { EPSILON as E, clamp as a, toRadians as b, create as c, lerp as d, fromDirection as e, frustum as f, fromPointToPoint as g, invert as i, lookAt as l, mat4 as m, ortho as o, perspective as p, set as s, toDegrees as t, utils as u };
811 |
--------------------------------------------------------------------------------
/web_modules/_chunks/ray-bLshgWN6.js:
--------------------------------------------------------------------------------
1 | import { f as set, g as dot, s as sub, b as add, a as scale, j as cross, l as length, t as toString$1, h as create$1 } from './vec3-DW1VLBq6.js';
2 |
3 | /**
4 | * Enum for different intersections values
5 | * @readonly
6 | * @enum {number}
7 | */ const Intersections = Object.freeze({
8 | Intersect: 1,
9 | NoIntersect: 0,
10 | SamePlane: -1,
11 | Parallel: -2,
12 | TriangleDegenerate: -2
13 | });
14 | const TEMP_0 = create$1();
15 | const TEMP_1 = create$1();
16 | const TEMP_2 = create$1();
17 | const TEMP_3 = create$1();
18 | const TEMP_4 = create$1();
19 | const TEMP_5 = create$1();
20 | const TEMP_6 = create$1();
21 | const TEMP_7 = create$1();
22 | const EPSILON = 1e-6;
23 | /**
24 | * Creates a new ray
25 | * @returns {import("./types.js").ray}
26 | */ function create() {
27 | return [
28 | [
29 | 0,
30 | 0,
31 | 0
32 | ],
33 | [
34 | 0,
35 | 0,
36 | 1
37 | ]
38 | ];
39 | }
40 | /**
41 | * Determines if a ray intersect a plane and set intersection point
42 | * @see {@link https://www.cs.princeton.edu/courses/archive/fall00/cs426/lectures/raycast/sld017.htm}
43 | * @param {import("./types.js").ray} ray
44 | * @param {import("./types.js").plane} plane
45 | * @param {import("./types.js").vec3} out
46 | * @returns {number}
47 | */ function hitTestPlane(param, param1, out) {
48 | let [origin, direction] = param;
49 | let [point, normal] = param1;
50 | if (out === undefined) out = create$1();
51 | set(TEMP_0, origin);
52 | set(TEMP_1, direction);
53 | const dotDirectionNormal = dot(TEMP_1, normal);
54 | if (dotDirectionNormal === 0) return Intersections.SamePlane;
55 | set(TEMP_2, point);
56 | const t = dot(sub(TEMP_2, TEMP_0), normal) / dotDirectionNormal;
57 | if (t < 0) return Intersections.Parallel;
58 | set(out, add(TEMP_0, scale(TEMP_1, t)));
59 | return Intersections.Intersect;
60 | }
61 | /**
62 | * Determines if a ray intersect a triangle and set intersection point
63 | * @see {@link http://geomalgorithms.com/a06-_intersect-2.html#intersect3D_RayTriangle()}
64 | * @param {import("./types.js").ray} ray
65 | * @param {import("./types.js").triangle} triangle
66 | * @param {import("./types.js").vec3} out
67 | * @returns {number}
68 | */ function hitTestTriangle(param, param1, out) {
69 | let [origin, direction] = param;
70 | let [p0, p1, p2] = param1;
71 | if (out === undefined) out = create$1();
72 | // get triangle edge vectors and plane normal
73 | const u = sub(set(TEMP_0, p1), p0);
74 | const v = sub(set(TEMP_1, p2), p0);
75 | const n = cross(set(TEMP_2, u), v);
76 | if (length(n) < EPSILON) return Intersections.TriangleDegenerate;
77 | // ray vectors
78 | const w0 = sub(set(TEMP_3, origin), p0);
79 | // params to calc ray-plane intersect
80 | const a = -dot(n, w0);
81 | const b = dot(n, direction);
82 | if (Math.abs(b) < EPSILON) {
83 | if (a === 0) return Intersections.SamePlane;
84 | return Intersections.NoIntersect;
85 | }
86 | // get intersect point of ray with triangle plane
87 | const r = a / b;
88 | // ray goes away from triangle
89 | if (r < -1e-6) return Intersections.NoIntersect;
90 | // for a segment, also test if (r > 1.0) => no intersect
91 | // intersect point of ray and plane
92 | const I = add(set(TEMP_4, origin), scale(set(TEMP_5, direction), r));
93 | const uu = dot(u, u);
94 | const uv = dot(u, v);
95 | const vv = dot(v, v);
96 | const w = sub(set(TEMP_6, I), p0);
97 | const wu = dot(w, u);
98 | const wv = dot(w, v);
99 | const D = uv * uv - uu * vv;
100 | // get and test parametric coords
101 | const s = (uv * wv - vv * wu) / D;
102 | if (s < -1e-6 || s > 1.0 + EPSILON) return Intersections.NoIntersect;
103 | const t = (uv * wu - uu * wv) / D;
104 | if (t < -1e-6 || s + t > 1.0 + EPSILON) return Intersections.NoIntersect;
105 | set(out, u);
106 | scale(out, s);
107 | add(out, scale(set(TEMP_7, v), t));
108 | add(out, p0);
109 | return Intersections.Intersect;
110 | }
111 | /**
112 | * Determines if a ray intersect an AABB bounding box
113 | * @see {@link http://gamedev.stackexchange.com/questions/18436/most-efficient-aabb-vs-ray-collision-algorithms}
114 | * @param {import("./types.js").ray} ray
115 | * @param {import("./types.js").aabb} aabb
116 | * @returns {boolean}
117 | */ function hitTestAABB(param, aabb) {
118 | let [origin, direction] = param;
119 | const dirFracx = 1.0 / direction[0];
120 | const dirFracy = 1.0 / direction[1];
121 | const dirFracz = 1.0 / direction[2];
122 | const min = aabb[0];
123 | const max = aabb[1];
124 | const minx = min[0];
125 | const miny = min[1];
126 | const minz = min[2];
127 | const maxx = max[0];
128 | const maxy = max[1];
129 | const maxz = max[2];
130 | const t1 = (minx - origin[0]) * dirFracx;
131 | const t2 = (maxx - origin[0]) * dirFracx;
132 | const t3 = (miny - origin[1]) * dirFracy;
133 | const t4 = (maxy - origin[1]) * dirFracy;
134 | const t5 = (minz - origin[2]) * dirFracz;
135 | const t6 = (maxz - origin[2]) * dirFracz;
136 | const tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)), Math.min(t5, t6));
137 | const tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)), Math.max(t5, t6));
138 | return !(tmax < 0 || tmin > tmax);
139 | }
140 | /**
141 | * Alias for {@link hitTestAABB}
142 | * @function
143 | */ const intersectsAABB = hitTestAABB;
144 | /**
145 | * Prints a plane to a string.
146 | * @param {import("./types.js").ray} a
147 | * @param {number} [precision=4]
148 | * @returns {string}
149 | */ function toString(a, precision) {
150 | if (precision === undefined) precision = 4;
151 | // prettier-ignore
152 | return `[${toString$1(a[0], precision)}, ${toString$1(a[1], precision)}]`;
153 | }
154 |
155 | var ray = /*#__PURE__*/Object.freeze({
156 | __proto__: null,
157 | Intersections: Intersections,
158 | create: create,
159 | hitTestAABB: hitTestAABB,
160 | hitTestPlane: hitTestPlane,
161 | hitTestTriangle: hitTestTriangle,
162 | intersectsAABB: intersectsAABB,
163 | toString: toString
164 | });
165 |
166 | export { hitTestPlane as h, ray as r };
167 |
--------------------------------------------------------------------------------
/web_modules/_chunks/vec3-DW1VLBq6.js:
--------------------------------------------------------------------------------
1 | /** @module vec2 */ /**
2 | * Returns a new vec2 at 0, 0.
3 | * @returns {import("./types.js").vec2}
4 | */ function create$1() {
5 | return [
6 | 0,
7 | 0
8 | ];
9 | }
10 | /**
11 | * Returns a copy of a vector.
12 | * @param {import("./types.js").vec2} a
13 | * @returns {import("./types.js").vec2}
14 | */ function copy$1(a) {
15 | return a.slice();
16 | }
17 | /**
18 | * Sets a vector to another vector.
19 | * @param {import("./types.js").vec2} a
20 | * @param {import("./types.js").vec2} b
21 | * @returns {import("./types.js").vec2}
22 | */ function set$1(a, b) {
23 | a[0] = b[0];
24 | a[1] = b[1];
25 | return a;
26 | }
27 | /**
28 | * Compares two vectors.
29 | * @param {import("./types.js").vec2} a
30 | * @param {import("./types.js").vec2} b
31 | * @returns {boolean}
32 | */ function equals$1(a, b) {
33 | return a[0] === b[0] && a[1] === b[1];
34 | }
35 | /**
36 | * Add a vector to another.
37 | * @param {import("./types.js").vec2} a
38 | * @param {import("./types.js").vec2} b
39 | * @returns {import("./types.js").vec2}
40 | */ function add$1(a, b) {
41 | a[0] += b[0];
42 | a[1] += b[1];
43 | return a;
44 | }
45 | /**
46 | * Subtracts a vector from another.
47 | * @param {import("./types.js").vec2} a
48 | * @param {import("./types.js").vec2} b
49 | * @returns {import("./types.js").vec2}
50 | */ function sub$1(a, b) {
51 | a[0] -= b[0];
52 | a[1] -= b[1];
53 | return a;
54 | }
55 | /**
56 | * Scales a vector by a number.
57 | * @param {import("./types.js").vec2} a
58 | * @param {number} s
59 | * @returns {import("./types.js").vec2}
60 | */ function scale$1(a, s) {
61 | a[0] *= s;
62 | a[1] *= s;
63 | return a;
64 | }
65 | /**
66 | * Adds two vectors after scaling the second one.
67 | * @param {import("./types.js").vec2} a
68 | * @param {import("./types.js").vec2} b
69 | * @param {number} s
70 | * @returns {import("./types.js").vec2}
71 | */ function addScaled$1(a, b, s) {
72 | a[0] += b[0] * s;
73 | a[1] += b[1] * s;
74 | return a;
75 | }
76 | /**
77 | * Calculates the dot product of two vectors.
78 | * @param {import("./types.js").vec2} a
79 | * @param {import("./types.js").vec2} b
80 | * @returns {number}
81 | */ function dot$1(a, b) {
82 | return a[0] * b[0] + a[1] * b[1];
83 | }
84 | /**
85 | * Calculates the length of a vector.
86 | * @param {import("./types.js").vec2} a
87 | * @returns {number}
88 | */ function length$1(a) {
89 | const x = a[0];
90 | const y = a[1];
91 | return Math.sqrt(x * x + y * y);
92 | }
93 | /**
94 | * Calculates the squared length of a vector.
95 | * @param {import("./types.js").vec2} a
96 | * @returns {number}
97 | */ function lengthSq$1(a) {
98 | const x = a[0];
99 | const y = a[1];
100 | return x * x + y * y;
101 | }
102 | /**
103 | * Normalises a vector.
104 | * @param {import("./types.js").vec2} a
105 | * @returns {import("./types.js").vec2}
106 | */ function normalize$1(a) {
107 | const x = a[0];
108 | const y = a[1];
109 | let l = Math.sqrt(x * x + y * y);
110 | l = 1 / (l || 1);
111 | a[0] *= l;
112 | a[1] *= l;
113 | return a;
114 | }
115 | /**
116 | * Calculates the distance between two vectors.
117 | * @param {import("./types.js").vec2} a
118 | * @param {import("./types.js").vec2} b
119 | * @returns {number}
120 | */ function distance$1(a, b) {
121 | const dx = b[0] - a[0];
122 | const dy = b[1] - a[1];
123 | return Math.sqrt(dx * dx + dy * dy);
124 | }
125 | /**
126 | * Calculates the squared distance between two vectors.
127 | * @param {import("./types.js").vec2} a
128 | * @param {import("./types.js").vec2} b
129 | * @returns {number}
130 | */ function distanceSq$1(a, b) {
131 | const dx = b[0] - a[0];
132 | const dy = b[1] - a[1];
133 | return dx * dx + dy * dy;
134 | }
135 | /**
136 | * Limits a vector to a length.
137 | * @param {import("./types.js").vec2} a
138 | * @param {number} len
139 | * @returns {import("./types.js").vec2}
140 | */ function limit$1(a, len) {
141 | const x = a[0];
142 | const y = a[1];
143 | const dsq = x * x + y * y;
144 | const lsq = len * len;
145 | if (lsq > 0 && dsq > lsq) {
146 | const nd = len / Math.sqrt(dsq);
147 | a[0] *= nd;
148 | a[1] *= nd;
149 | }
150 | return a;
151 | }
152 | /**
153 | * Linearly interpolates between two vectors.
154 | * @param {import("./types.js").vec2} a
155 | * @param {import("./types.js").vec2} b
156 | * @param {number} t
157 | * @returns {import("./types.js").vec2}
158 | */ function lerp$1(a, b, t) {
159 | const x = a[0];
160 | const y = a[1];
161 | a[0] = x + (b[0] - x) * t;
162 | a[1] = y + (b[1] - y) * t;
163 | return a;
164 | }
165 | /**
166 | * Prints a vector to a string.
167 | * @param {import("./types.js").vec2} a
168 | * @param {number} [precision=4]
169 | * @returns {string}
170 | */ function toString$1(a, precision) {
171 | if (precision === undefined) precision = 4;
172 | const scale = 10 ** precision;
173 | // prettier-ignore
174 | return `[${Math.floor(a[0] * scale) / scale}, ${Math.floor(a[1] * scale) / scale}]`;
175 | }
176 |
177 | var vec2 = /*#__PURE__*/Object.freeze({
178 | __proto__: null,
179 | add: add$1,
180 | addScaled: addScaled$1,
181 | copy: copy$1,
182 | create: create$1,
183 | distance: distance$1,
184 | distanceSq: distanceSq$1,
185 | dot: dot$1,
186 | equals: equals$1,
187 | length: length$1,
188 | lengthSq: lengthSq$1,
189 | lerp: lerp$1,
190 | limit: limit$1,
191 | normalize: normalize$1,
192 | scale: scale$1,
193 | set: set$1,
194 | sub: sub$1,
195 | toString: toString$1
196 | });
197 |
198 | /** @module vec3 */ /**
199 | * Returns a new vec3 at 0, 0, 0.
200 | * @returns {import("./types.js").vec3}
201 | */ function create() {
202 | return [
203 | 0,
204 | 0,
205 | 0
206 | ];
207 | }
208 | /**
209 | * Returns a copy of a vector.
210 | * @param {import("./types.js").vec3} a
211 | * @returns {import("./types.js").vec3}
212 | */ function copy(a) {
213 | return a.slice();
214 | }
215 | /**
216 | * Sets a vector to another vector.
217 | * @param {import("./types.js").vec3} a
218 | * @param {import("./types.js").vec3} b
219 | * @returns {import("./types.js").vec3}
220 | */ function set(a, b) {
221 | a[0] = b[0];
222 | a[1] = b[1];
223 | a[2] = b[2];
224 | return a;
225 | }
226 | /**
227 | * Compares two vectors.
228 | * @param {import("./types.js").vec3} a
229 | * @param {import("./types.js").vec3} b
230 | * @returns {boolean}
231 | */ function equals(a, b) {
232 | return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
233 | }
234 | /**
235 | * Adds a vector to another.
236 | * @param {import("./types.js").vec3} a
237 | * @param {import("./types.js").vec3} b
238 | * @returns {import("./types.js").vec3}
239 | */ function add(a, b) {
240 | a[0] += b[0];
241 | a[1] += b[1];
242 | a[2] += b[2];
243 | return a;
244 | }
245 | /**
246 | * Subtracts a vector from another.
247 | * @param {import("./types.js").vec3} a
248 | * @param {import("./types.js").vec3} b
249 | * @returns {import("./types.js").vec3}
250 | */ function sub(a, b) {
251 | a[0] -= b[0];
252 | a[1] -= b[1];
253 | a[2] -= b[2];
254 | return a;
255 | }
256 | /**
257 | * Scales a vector by a number.
258 | * @param {import("./types.js").vec3} a
259 | * @param {number} s
260 | * @returns {import("./types.js").vec3}
261 | */ function scale(a, s) {
262 | a[0] *= s;
263 | a[1] *= s;
264 | a[2] *= s;
265 | return a;
266 | }
267 | /**
268 | * Adds two vectors after scaling the second one.
269 | * @param {import("./types.js").vec3} a
270 | * @param {import("./types.js").vec3} b
271 | * @param {number} s
272 | * @returns {import("./types.js").vec3}
273 | */ function addScaled(a, b, s) {
274 | a[0] += b[0] * s;
275 | a[1] += b[1] * s;
276 | a[2] += b[2] * s;
277 | return a;
278 | }
279 | /**
280 | * Multiplies a vector by a matrix.
281 | * @param {import("./types.js").vec3} a
282 | * @param {import("./types.js").mat4} m
283 | * @returns {import("./types.js").vec3}
284 | */ function multMat4(a, m) {
285 | const x = a[0];
286 | const y = a[1];
287 | const z = a[2];
288 | a[0] = m[0] * x + m[4] * y + m[8] * z + m[12];
289 | a[1] = m[1] * x + m[5] * y + m[9] * z + m[13];
290 | a[2] = m[2] * x + m[6] * y + m[10] * z + m[14];
291 | return a;
292 | }
293 | /**
294 | * Multiplies a vector by a quaternion.
295 | * @param {import("./types.js").vec3} a
296 | * @param {import("./types.js").quat} q
297 | * @returns {import("./types.js").vec3}
298 | */ function multQuat(a, q) {
299 | const x = a[0];
300 | const y = a[1];
301 | const z = a[2];
302 | const qx = q[0];
303 | const qy = q[1];
304 | const qz = q[2];
305 | const qw = q[3];
306 | const ix = qw * x + qy * z - qz * y;
307 | const iy = qw * y + qz * x - qx * z;
308 | const iz = qw * z + qx * y - qy * x;
309 | const iw = -qx * x - qy * y - qz * z;
310 | a[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy;
311 | a[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz;
312 | a[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx;
313 | return a;
314 | }
315 | /**
316 | * Calculates the dot product of two vectors.
317 | * @param {import("./types.js").vec3} a
318 | * @param {import("./types.js").vec3} b
319 | * @returns {number}
320 | */ function dot(a, b) {
321 | return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
322 | }
323 | /**
324 | * Calculates the cross product of two vectors.
325 | * @param {import("./types.js").vec3} a
326 | * @param {import("./types.js").vec3} b
327 | * @returns {import("./types.js").vec3}
328 | */ function cross(a, b) {
329 | const x = a[0];
330 | const y = a[1];
331 | const z = a[2];
332 | const vx = b[0];
333 | const vy = b[1];
334 | const vz = b[2];
335 | a[0] = y * vz - vy * z;
336 | a[1] = z * vx - vz * x;
337 | a[2] = x * vy - vx * y;
338 | return a;
339 | }
340 | /**
341 | * Calculates the length of a vector.
342 | * @param {import("./types.js").vec3} a
343 | * @returns {number}
344 | */ function length(a) {
345 | const x = a[0];
346 | const y = a[1];
347 | const z = a[2];
348 | return Math.sqrt(x * x + y * y + z * z);
349 | }
350 | /**
351 | * Calculates the squared length of a vector.
352 | * @param {import("./types.js").vec3} a
353 | * @returns {number}
354 | */ function lengthSq(a) {
355 | const x = a[0];
356 | const y = a[1];
357 | const z = a[2];
358 | return x * x + y * y + z * z;
359 | }
360 | /**
361 | * Normalises a vector.
362 | * @param {import("./types.js").vec3} a
363 | * @returns {import("./types.js").vec3}
364 | */ function normalize(a) {
365 | const x = a[0];
366 | const y = a[1];
367 | const z = a[2];
368 | let l = Math.sqrt(x * x + y * y + z * z);
369 | l = 1 / (l || 1);
370 | a[0] *= l;
371 | a[1] *= l;
372 | a[2] *= l;
373 | return a;
374 | }
375 | /**
376 | * Calculates the distance between two vectors.
377 | * @param {import("./types.js").vec3} a
378 | * @param {import("./types.js").vec3} b
379 | * @returns {number}
380 | */ function distance(a, b) {
381 | const dx = b[0] - a[0];
382 | const dy = b[1] - a[1];
383 | const dz = b[2] - a[2];
384 | return Math.sqrt(dx * dx + dy * dy + dz * dz);
385 | }
386 | /**
387 | * Calculates the squared distance between two vectors.
388 | * @param {import("./types.js").vec3} a
389 | * @param {import("./types.js").vec3} b
390 | * @returns {number}
391 | */ function distanceSq(a, b) {
392 | const dx = b[0] - a[0];
393 | const dy = b[1] - a[1];
394 | const dz = b[2] - a[2];
395 | return dx * dx + dy * dy + dz * dz;
396 | }
397 | /**
398 | * Limits a vector to a length.
399 | * @param {import("./types.js").vec3} a
400 | * @param {number} len
401 | * @returns {import("./types.js").vec3}
402 | */ function limit(a, len) {
403 | const x = a[0];
404 | const y = a[1];
405 | const z = a[2];
406 | const dsq = x * x + y * y + z * z;
407 | const lsq = len * len;
408 | if (lsq > 0 && dsq > lsq) {
409 | const nd = len / Math.sqrt(dsq);
410 | a[0] *= nd;
411 | a[1] *= nd;
412 | a[2] *= nd;
413 | }
414 | return a;
415 | }
416 | /**
417 | * Linearly interpolates between two vectors.
418 | * @param {import("./types.js").vec3} a
419 | * @param {import("./types.js").vec3} b
420 | * @param {number} t
421 | * @returns {import("./types.js").vec3}
422 | */ function lerp(a, b, t) {
423 | const x = a[0];
424 | const y = a[1];
425 | const z = a[2];
426 | a[0] = x + (b[0] - x) * t;
427 | a[1] = y + (b[1] - y) * t;
428 | a[2] = z + (b[2] - z) * t;
429 | return a;
430 | }
431 | /**
432 | * Prints a vector to a string.
433 | * @param {import("./types.js").vec3} a
434 | * @param {number} [precision=4]
435 | * @returns {string}
436 | */ function toString(a, precision) {
437 | if (precision === undefined) precision = 4;
438 | const scale = 10 ** precision;
439 | // prettier-ignore
440 | return `[${Math.floor(a[0] * scale) / scale}, ${Math.floor(a[1] * scale) / scale}, ${Math.floor(a[2] * scale) / scale}]`;
441 | }
442 |
443 | var vec3 = /*#__PURE__*/Object.freeze({
444 | __proto__: null,
445 | add: add,
446 | addScaled: addScaled,
447 | copy: copy,
448 | create: create,
449 | cross: cross,
450 | distance: distance,
451 | distanceSq: distanceSq,
452 | dot: dot,
453 | equals: equals,
454 | length: length,
455 | lengthSq: lengthSq,
456 | lerp: lerp,
457 | limit: limit,
458 | multMat4: multMat4,
459 | multQuat: multQuat,
460 | normalize: normalize,
461 | scale: scale,
462 | set: set,
463 | sub: sub,
464 | toString: toString
465 | });
466 |
467 | export { scale as a, add as b, copy as c, distance as d, distance$1 as e, set as f, dot as g, create as h, toString$1 as i, cross as j, create$1 as k, length as l, multMat4 as m, normalize as n, vec3 as o, sub as s, toString as t, vec2 as v };
468 |
--------------------------------------------------------------------------------
/web_modules/import-map.json:
--------------------------------------------------------------------------------
1 | {
2 | "imports": {
3 | "es-module-shims": "./es-module-shims.js",
4 | "es-module-shims/debug": "./es-module-shims/debug.js",
5 | "es-module-shims/wasm": "./es-module-shims/wasm.js",
6 | "es-module-shims/typescript-transform": "./es-module-shims/typescript-transform.js",
7 | "pex-cam": "./pex-cam.js",
8 | "pex-color": "./pex-color.js",
9 | "pex-context": "./pex-context.js",
10 | "pex-geom": "./pex-geom.js",
11 | "pex-io": "./pex-io.js",
12 | "pex-math": "./pex-math.js",
13 | "primitive-geometry": "./primitive-geometry.js"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/web_modules/pex-cam.js:
--------------------------------------------------------------------------------
1 | import { c as create, l as lookAt, s as set, i as invert, f as frustum, p as perspective$1, o as ortho, a as clamp, t as toDegrees, b as toRadians, d as lerp$2 } from './_chunks/mat4-DkKmQtAV.js';
2 | import { n as normalize, m as multMat4, s as sub, c as copy, d as distance, a as scale, b as add, l as length, e as distance$1, f as set$1 } from './_chunks/vec3-DW1VLBq6.js';
3 | import { g as getDefaultExportFromCjs } from './_chunks/_commonjsHelpers-BFTU3MAI.js';
4 | import { h as hitTestPlane } from './_chunks/ray-bLshgWN6.js';
5 |
6 | /**
7 | * An interface for cameras to extend
8 | */ class Camera {
9 | // Static getter to get different mat for each instances
10 | static get DEFAULT_OPTIONS() {
11 | return {
12 | projectionMatrix: create(),
13 | invViewMatrix: create(),
14 | viewMatrix: create(),
15 | position: [
16 | 0,
17 | 0,
18 | 3
19 | ],
20 | target: [
21 | 0,
22 | 0,
23 | 0
24 | ],
25 | up: [
26 | 0,
27 | 1,
28 | 0
29 | ],
30 | aspect: 1,
31 | near: 0.1,
32 | far: 100,
33 | view: null
34 | };
35 | }
36 | /**
37 | * Update the camera
38 | * @param {import("./types.js").CameraOptions} opts
39 | */ set(opts) {
40 | Object.assign(this, opts);
41 | if (opts.position || opts.target || opts.up) {
42 | lookAt(this.viewMatrix, this.position, this.target, this.up);
43 | set(this.invViewMatrix, this.viewMatrix);
44 | invert(this.invViewMatrix);
45 | }
46 | }
47 | }
48 |
49 | /**
50 | * A class to create a perspective camera
51 | * @extends Camera
52 | */ class PerspectiveCamera extends Camera {
53 | static get DEFAULT_OPTIONS() {
54 | return {
55 | fov: Math.PI / 3
56 | };
57 | }
58 | /**
59 | * Create an instance of PerspectiveCamera
60 | * @param {import("./types.js").CameraOptions & import("./types.js").PerspectiveCameraOptions} opts
61 | */ constructor(opts = {}){
62 | super();
63 | this.set({
64 | ...Camera.DEFAULT_OPTIONS,
65 | ...PerspectiveCamera.DEFAULT_OPTIONS,
66 | ...opts
67 | });
68 | }
69 | /**
70 | * Update the camera
71 | * @param {import("./types.js").CameraOptions & import("./types.js").PerspectiveCameraOptions} opts
72 | */ set(opts) {
73 | super.set(opts);
74 | if (opts.fov || opts.aspect || opts.near || opts.far || opts.view) {
75 | if (this.view) {
76 | const aspectRatio = this.view.totalSize[0] / this.view.totalSize[1];
77 | const top = Math.tan(this.fov * 0.5) * this.near;
78 | const bottom = -top;
79 | const left = aspectRatio * bottom;
80 | const right = aspectRatio * top;
81 | const width = Math.abs(right - left);
82 | const height = Math.abs(top - bottom);
83 | const widthNormalized = width / this.view.totalSize[0];
84 | const heightNormalized = height / this.view.totalSize[1];
85 | const l = left + this.view.offset[0] * widthNormalized;
86 | const r = left + (this.view.offset[0] + this.view.size[0]) * widthNormalized;
87 | const b = top - (this.view.offset[1] + this.view.size[1]) * heightNormalized;
88 | const t = top - this.view.offset[1] * heightNormalized;
89 | frustum(this.projectionMatrix, l, r, b, t, this.near, this.far);
90 | } else {
91 | perspective$1(this.projectionMatrix, this.fov, this.aspect, this.near, this.far);
92 | }
93 | }
94 | }
95 | /**
96 | * Create a picking ray in view (camera) coordinates
97 | * @param {number} x mouse x
98 | * @param {number} y mouse y
99 | * @param {number} windowWidth
100 | * @param {number} windowHeight
101 | * @returns {import("pex-geom").ray}
102 | */ getViewRay(x, y, windowWidth, windowHeight) {
103 | if (this.view) {
104 | x += this.view.offset[0];
105 | y += this.view.offset[1];
106 | windowWidth = this.view.totalSize[0];
107 | windowHeight = this.view.totalSize[1];
108 | }
109 | let nx = 2 * x / windowWidth - 1;
110 | let ny = 1 - 2 * y / windowHeight;
111 | const hNear = 2 * Math.tan(this.fov / 2) * this.near;
112 | const wNear = hNear * this.aspect;
113 | nx *= wNear * 0.5;
114 | ny *= hNear * 0.5;
115 | // [origin, direction]
116 | return [
117 | [
118 | 0,
119 | 0,
120 | 0
121 | ],
122 | normalize([
123 | nx,
124 | ny,
125 | -this.near
126 | ])
127 | ];
128 | }
129 | /**
130 | * Create a picking ray in world coordinates
131 | * @param {number} x
132 | * @param {number} y
133 | * @param {number} windowWidth
134 | * @param {number} windowHeight
135 | * @returns {import("pex-geom").ray}
136 | */ getWorldRay(x, y, windowWidth, windowHeight) {
137 | let ray = this.getViewRay(x, y, windowWidth, windowHeight);
138 | const origin = ray[0];
139 | const direction = ray[1];
140 | multMat4(origin, this.invViewMatrix);
141 | // this is correct as origin is [0, 0, 0] so direction is also a point
142 | multMat4(direction, this.invViewMatrix);
143 | // TODO: is this necessary?
144 | normalize(sub(direction, origin));
145 | return ray;
146 | }
147 | }
148 |
149 | /**
150 | * A class to create an orthographic camera
151 | * @extends Camera
152 | */ class OrthographicCamera extends Camera {
153 | static get DEFAULT_OPTIONS() {
154 | return {
155 | left: -1,
156 | right: 1,
157 | bottom: -1,
158 | top: 1,
159 | zoom: 1
160 | };
161 | }
162 | /**
163 | * Create an instance of PerspectiveCamera
164 | * @param {import("./types.js").CameraOptions & import("./types.js").OrthographicCameraOptions} opts
165 | */ constructor(opts = {}){
166 | super();
167 | this.set({
168 | ...Camera.DEFAULT_OPTIONS,
169 | ...OrthographicCamera.DEFAULT_OPTIONS,
170 | ...opts
171 | });
172 | }
173 | /**
174 | * Update the camera
175 | * @param {import("./types.js").CameraOptions & import("./types.js").OrthographicCameraOptions} opts
176 | */ set(opts) {
177 | super.set(opts);
178 | if (opts.left || opts.right || opts.bottom || opts.top || opts.zoom || opts.near || opts.far || opts.view) {
179 | const dx = (this.right - this.left) / (2 / this.zoom);
180 | const dy = (this.top - this.bottom) / (2 / this.zoom);
181 | const cx = (this.right + this.left) / 2;
182 | const cy = (this.top + this.bottom) / 2;
183 | let left = cx - dx;
184 | let right = cx + dx;
185 | let top = cy + dy;
186 | let bottom = cy - dy;
187 | if (this.view) {
188 | const zoomW = 1 / this.zoom / (this.view.size[0] / this.view.totalSize[0]);
189 | const zoomH = 1 / this.zoom / (this.view.size[1] / this.view.totalSize[1]);
190 | const scaleW = (this.right - this.left) / this.view.size[0];
191 | const scaleH = (this.top - this.bottom) / this.view.size[1];
192 | left += scaleW * (this.view.offset[0] / zoomW);
193 | right = left + scaleW * (this.view.size[0] / zoomW);
194 | top -= scaleH * (this.view.offset[1] / zoomH);
195 | bottom = top - scaleH * (this.view.size[1] / zoomH);
196 | }
197 | ortho(this.projectionMatrix, left, right, bottom, top, this.near, this.far);
198 | }
199 | }
200 | getViewRay(x, y, windowWidth, windowHeight) {
201 | if (this.view) {
202 | x += this.view.offset[0];
203 | y += this.view.offset[1];
204 | windowWidth = this.view.totalSize[0];
205 | windowHeight = this.view.totalSize[1];
206 | }
207 | // [origin, direction]
208 | return [
209 | [
210 | 0,
211 | 0,
212 | 0
213 | ],
214 | normalize([
215 | x * (this.right - this.left) / this.zoom / windowWidth,
216 | (1 - y) * (this.top - this.bottom) / this.zoom / windowHeight,
217 | -this.near
218 | ])
219 | ];
220 | }
221 | }
222 |
223 | function lerp$1(v0, v1, t) {
224 | return v0 * (1 - t) + v1 * t;
225 | }
226 | var lerp_1 = lerp$1;
227 |
228 | var lerp = lerp_1;
229 | var PI = Math.PI;
230 | var TWO_PI = Math.PI * 2;
231 | function interpolateAngle(fromAngle, toAngle, t) {
232 | fromAngle = (fromAngle + TWO_PI) % TWO_PI;
233 | toAngle = (toAngle + TWO_PI) % TWO_PI;
234 | var diff = Math.abs(fromAngle - toAngle);
235 | if (diff < PI) {
236 | return lerp(fromAngle, toAngle, t);
237 | } else {
238 | if (fromAngle > toAngle) {
239 | fromAngle = fromAngle - TWO_PI;
240 | return lerp(fromAngle, toAngle, t);
241 | } else if (toAngle > fromAngle) {
242 | toAngle = toAngle - TWO_PI;
243 | return lerp(fromAngle, toAngle, t);
244 | }
245 | }
246 | }
247 | var interpolateAngle_1 = interpolateAngle;
248 | var interpolateAngle$1 = /*@__PURE__*/ getDefaultExportFromCjs(interpolateAngle_1);
249 |
250 | function latLonToXyz(lat, lon, out) {
251 | out = out || [
252 | 0,
253 | 0,
254 | 0
255 | ];
256 | const phi = (lon + 90) / 180 * Math.PI;
257 | const theta = (90 - lat) / 180 * Math.PI;
258 | out[0] = Math.sin(theta) * Math.sin(phi);
259 | out[1] = Math.cos(theta);
260 | out[2] = Math.sin(theta) * Math.cos(phi);
261 | return out;
262 | }
263 | var latlonToXyz = latLonToXyz;
264 | var latLonToXyz$1 = /*@__PURE__*/ getDefaultExportFromCjs(latlonToXyz);
265 |
266 | function xyzToLatLon(normalizedPosition, out) {
267 | out = out || [
268 | 0,
269 | 0
270 | ];
271 | out[0] = 90 - Math.acos(normalizedPosition[1]) / Math.PI * 180;
272 | out[1] = -Math.atan2(normalizedPosition[2], normalizedPosition[0]) / Math.PI * 180;
273 | return out;
274 | }
275 | var xyzToLatlon = xyzToLatLon;
276 | var xyzToLatLon$1 = /*@__PURE__*/ getDefaultExportFromCjs(xyzToLatlon);
277 |
278 | var rootPosition = {
279 | left: 0,
280 | top: 0
281 | };
282 | var mouseEventOffset_1 = mouseEventOffset;
283 | function mouseEventOffset(ev, target, out) {
284 | target = target || ev.currentTarget || ev.srcElement;
285 | if (!Array.isArray(out)) {
286 | out = [
287 | 0,
288 | 0
289 | ];
290 | }
291 | var cx = ev.clientX || 0;
292 | var cy = ev.clientY || 0;
293 | var rect = getBoundingClientOffset(target);
294 | out[0] = cx - rect.left;
295 | out[1] = cy - rect.top;
296 | return out;
297 | }
298 | function getBoundingClientOffset(element) {
299 | if (element === window || element === document || element === document.body) {
300 | return rootPosition;
301 | } else {
302 | return element.getBoundingClientRect();
303 | }
304 | }
305 | var eventOffset = /*@__PURE__*/ getDefaultExportFromCjs(mouseEventOffset_1);
306 |
307 | /**
308 | * Camera controls to orbit around a target
309 | */ class OrbiterControls {
310 | static get DEFAULT_OPTIONS() {
311 | return {
312 | element: document,
313 | easing: 0.1,
314 | zoom: true,
315 | pan: true,
316 | drag: true,
317 | minDistance: 0.01,
318 | maxDistance: Infinity,
319 | minLat: -89.5,
320 | maxLat: 89.5,
321 | minLon: -Infinity,
322 | maxLon: Infinity,
323 | panSlowdown: 4,
324 | zoomSlowdown: 400,
325 | dragSlowdown: 4,
326 | autoUpdate: true
327 | };
328 | }
329 | get domElement() {
330 | return this.element === document ? this.element.body : this.element;
331 | }
332 | /**
333 | * Create an instance of OrbiterControls
334 | * @param {import("./types.js").OrbiterControlsOptions} opts
335 | */ constructor(opts){
336 | // Internals
337 | // Set initially by .set
338 | this.lat = null; // Y
339 | this.lon = null; // XZ
340 | this.currentLat = null;
341 | this.currentLon = null;
342 | this.distance = null;
343 | this.currentDistance = null;
344 | // Updated by user interaction
345 | this.panning = false;
346 | this.dragging = false;
347 | this.zooming = false;
348 | this.width = 0;
349 | this.height = 0;
350 | this.zoomTouchDistance = null;
351 | this.panPlane = null;
352 | this.clickTarget = [
353 | 0,
354 | 0,
355 | 0
356 | ];
357 | this.clickPosWorld = [
358 | 0,
359 | 0,
360 | 0
361 | ];
362 | this.clickPosPlane = [
363 | 0,
364 | 0,
365 | 0
366 | ];
367 | this.dragPos = [
368 | 0,
369 | 0,
370 | 0
371 | ];
372 | this.dragPosWorld = [
373 | 0,
374 | 0,
375 | 0
376 | ];
377 | this.dragPosPlane = [
378 | 0,
379 | 0,
380 | 0
381 | ];
382 | // TODO: add ability to set lat/lng instead of position/target
383 | this.set({
384 | ...OrbiterControls.DEFAULT_OPTIONS,
385 | ...opts
386 | });
387 | this.setup();
388 | }
389 | /**
390 | * Update the control
391 | * @param {import("./types.js").OrbiterOptions} opts
392 | */ set(opts) {
393 | Object.assign(this, opts);
394 | if (opts.camera) {
395 | const latLon = xyzToLatLon$1(normalize(sub(copy(opts.camera.position), opts.camera.target)));
396 | const distance$1 = opts.distance || distance(opts.camera.position, opts.camera.target);
397 | this.lat = latLon[0];
398 | this.lon = latLon[1];
399 | this.currentLat = this.lat;
400 | this.currentLon = this.lon;
401 | this.distance = distance$1;
402 | this.currentDistance = this.distance;
403 | }
404 | if (Object.getOwnPropertyDescriptor(opts, "autoUpdate")) {
405 | if (this.autoUpdate) {
406 | const self = this;
407 | this.rafHandle = requestAnimationFrame(function tick() {
408 | self.updateCamera();
409 | if (self.autoUpdate) self.rafHandle = requestAnimationFrame(tick);
410 | });
411 | } else if (this.rafHandle) {
412 | cancelAnimationFrame(this.rafHandle);
413 | }
414 | }
415 | }
416 | updateCamera() {
417 | // instad of rotating the object we want to move camera around it
418 | if (!this.camera) return;
419 | const position = this.camera.position;
420 | const target = this.camera.target;
421 | this.lat = clamp(this.lat, this.minLat, this.maxLat);
422 | if (this.minLon !== -Infinity && this.maxLon !== Infinity) {
423 | this.lon = clamp(this.lon, this.minLon, this.maxLon) % 360;
424 | }
425 | this.currentLat = toDegrees(interpolateAngle$1((toRadians(this.currentLat) + 2 * Math.PI) % (2 * Math.PI), (toRadians(this.lat) + 2 * Math.PI) % (2 * Math.PI), this.easing));
426 | this.currentLon += (this.lon - this.currentLon) * this.easing;
427 | this.currentDistance = lerp$2(this.currentDistance, this.distance, this.easing);
428 | // Set position from lat/lon
429 | latLonToXyz$1(this.currentLat, this.currentLon, position);
430 | // Move position according to distance and target
431 | scale(position, this.currentDistance);
432 | add(position, target);
433 | if (this.camera.zoom) this.camera.set({
434 | zoom: length(position)
435 | });
436 | this.camera.set({
437 | position
438 | });
439 | }
440 | updateWindowSize() {
441 | const width = this.domElement.clientWidth || this.domElement.innerWidth;
442 | const height = this.domElement.clientHeight || this.domElement.innerHeight;
443 | if (width !== this.width) this.width = width;
444 | if (height !== this.height) this.height = height;
445 | }
446 | handleDragStart(position) {
447 | this.dragging = true;
448 | this.dragPos = position;
449 | }
450 | handlePanZoomStart(touch0, touch1) {
451 | this.dragging = false;
452 | if (this.zoom && touch1) {
453 | this.zooming = true;
454 | this.zoomTouchDistance = distance$1(touch1, touch0);
455 | }
456 | const camera = this.camera;
457 | if (this.pan && camera) {
458 | this.panning = true;
459 | this.updateWindowSize();
460 | // TODO: use dragPos?
461 | const clickPosWindow = touch1 ? [
462 | (touch0[0] + touch1[0]) * 0.5,
463 | (touch0[1] + touch1[1]) * 0.5
464 | ] : touch0;
465 | set$1(this.clickTarget, camera.target);
466 | const targetInViewSpace = multMat4(copy(this.clickTarget), camera.viewMatrix);
467 | this.panPlane = [
468 | targetInViewSpace,
469 | [
470 | 0,
471 | 0,
472 | 1
473 | ]
474 | ];
475 | hitTestPlane(camera.getViewRay(clickPosWindow[0], clickPosWindow[1], this.width, this.height), this.panPlane, this.clickPosPlane);
476 | }
477 | }
478 | handleDragMove(position) {
479 | const dx = position[0] - this.dragPos[0];
480 | const dy = position[1] - this.dragPos[1];
481 | this.lat += dy / this.dragSlowdown;
482 | this.lon -= dx / this.dragSlowdown;
483 | this.dragPos = position;
484 | }
485 | handlePanZoomMove(touch0, touch1) {
486 | if (this.zoom && touch1) {
487 | const distance = distance$1(touch1, touch0);
488 | this.handleZoom(this.zoomTouchDistance - distance);
489 | this.zoomTouchDistance = distance;
490 | }
491 | const camera = this.camera;
492 | if (this.pan && camera && this.panPlane) {
493 | const dragPosWindow = touch1 ? [
494 | (touch0[0] + touch1[0]) * 0.5,
495 | (touch0[1] + touch1[1]) * 0.5
496 | ] : touch0;
497 | hitTestPlane(camera.getViewRay(dragPosWindow[0], dragPosWindow[1], this.width, this.height), this.panPlane, this.dragPosPlane);
498 | multMat4(set$1(this.clickPosWorld, this.clickPosPlane), camera.invViewMatrix);
499 | multMat4(set$1(this.dragPosWorld, this.dragPosPlane), camera.invViewMatrix);
500 | const diffWorld = sub(copy(this.dragPosWorld), this.clickPosWorld);
501 | camera.set({
502 | distance: this.distance,
503 | target: sub(copy(this.clickTarget), diffWorld)
504 | });
505 | }
506 | }
507 | handleZoom(dy) {
508 | this.distance *= 1 + dy / this.zoomSlowdown;
509 | this.distance = clamp(this.distance, this.minDistance, this.maxDistance);
510 | }
511 | handleEnd() {
512 | this.dragging = false;
513 | this.panning = false;
514 | this.zooming = false;
515 | this.panPlane = null;
516 | }
517 | setup() {
518 | this.onPointerDown = (event)=>{
519 | const pan = event.ctrlKey || event.metaKey || event.shiftKey || event.touches && event.touches.length === 2;
520 | const touch0 = eventOffset(event.touches ? event.touches[0] : event, this.domElement);
521 | if (this.drag && !pan) {
522 | this.handleDragStart(touch0);
523 | } else if ((this.pan || this.zoom) && pan) {
524 | const touch1 = event.touches && eventOffset(event.touches[1], this.domElement);
525 | this.handlePanZoomStart(touch0, touch1);
526 | }
527 | };
528 | this.onPointerMove = (event)=>{
529 | const touch0 = eventOffset(event.touches ? event.touches[0] : event, this.domElement);
530 | if (this.dragging) {
531 | this.handleDragMove(touch0);
532 | } else if (this.panning || this.zooming) {
533 | if (event.touches && !event.touches[1]) return;
534 | const touch1 = event.touches && eventOffset(event.touches[1], this.domElement);
535 | this.handlePanZoomMove(touch0, touch1);
536 | }
537 | };
538 | this.onPointerUp = ()=>{
539 | this.handleEnd();
540 | };
541 | this.onTouchStart = (event)=>{
542 | event.preventDefault();
543 | if (event.touches.length <= 2) this.onPointerDown(event);
544 | };
545 | this.onTouchMove = (event)=>{
546 | !!event.cancelable && event.preventDefault();
547 | if (event.touches.length <= 2) this.onPointerMove(event);
548 | };
549 | this.onWheel = (event)=>{
550 | if (!this.zoom) return;
551 | event.preventDefault();
552 | this.handleZoom(event.deltaY);
553 | };
554 | this.element.addEventListener("pointerdown", this.onPointerDown);
555 | this.element.addEventListener("wheel", this.onWheel, {
556 | passive: false
557 | });
558 | document.addEventListener("pointermove", this.onPointerMove);
559 | document.addEventListener("pointerup", this.onPointerUp);
560 | this.domElement.style.touchAction = "none";
561 | }
562 | /**
563 | * Remove all event listeners
564 | */ dispose() {
565 | if (this.rafHandle) cancelAnimationFrame(this.rafHandle);
566 | this.element.removeEventListener("pointerdown", this.onPointerDown);
567 | this.element.removeEventListener("wheel", this.onWheel);
568 | document.removeEventListener("pointermove", this.onPointerMove);
569 | document.removeEventListener("pointerup", this.onPointerUp);
570 | }
571 | }
572 |
573 | /**
574 | * Factory function for perspective camera
575 | * @param {import("./types.js").CameraOptions & import("./types.js").PerspectiveCameraOptions} opts
576 | * @returns {PerspectiveCamera}
577 | */ const perspective = (opts)=>new PerspectiveCamera(opts);
578 | /**
579 | * Factory function for orthographic camera
580 | * @param {import("./types.js").CameraOptions & import("./types.js").OrthographicCameraOptions} opts
581 | * @returns {OrthographicCamera}
582 | */ const orthographic = (opts)=>new OrthographicCamera(opts);
583 | /**
584 | * Factory function for orbiter controls
585 | * @param {import("./types.js").OrbiterControlsOptions} opts
586 | * @returns {OrbiterControls}
587 | */ const orbiter = (opts)=>new OrbiterControls(opts);
588 |
589 | export { orbiter, orthographic, perspective };
590 |
--------------------------------------------------------------------------------
/web_modules/pex-geom.js:
--------------------------------------------------------------------------------
1 | import { t as toString$3, f as set$2, s as sub, n as normalize, g as dot, h as create$3, i as toString$4 } from './_chunks/vec3-DW1VLBq6.js';
2 | import { s as set3 } from './_chunks/avec3-CX_9gCVx.js';
3 | export { r as ray } from './_chunks/ray-bLshgWN6.js';
4 |
5 | /**
6 | * Creates a new bounding box.
7 | * @returns {import("./types.js").aabb}
8 | */ function create$2() {
9 | // [min, max]
10 | return [
11 | [
12 | Infinity,
13 | Infinity,
14 | Infinity
15 | ],
16 | [
17 | -Infinity,
18 | -Infinity,
19 | -Infinity
20 | ]
21 | ];
22 | }
23 | /**
24 | * Reset a bounding box.
25 | * @param {import("./types.js").aabb} a
26 | * @returns {import("./types.js").rect}
27 | */ function empty$1(a) {
28 | a[0][0] = Infinity;
29 | a[0][1] = Infinity;
30 | a[0][2] = Infinity;
31 | a[1][0] = -Infinity;
32 | a[1][1] = -Infinity;
33 | a[1][2] = -Infinity;
34 | return a;
35 | }
36 | /**
37 | * Copies a bounding box.
38 | * @param {import("./types.js").aabb} a
39 | * @returns {import("./types.js").aabb}
40 | */ function copy$1(a) {
41 | return [
42 | a[0].slice(),
43 | a[1].slice()
44 | ];
45 | }
46 | /**
47 | * Sets a bounding box to another.
48 | * @param {import("./types.js").aabb} a
49 | * @param {import("./types.js").aabb} b
50 | * @returns {import("./types.js").aabb}
51 | */ function set$1(a, b) {
52 | a[0][0] = b[0][0];
53 | a[0][1] = b[0][1];
54 | a[0][2] = b[0][2];
55 | a[1][0] = b[1][0];
56 | a[1][1] = b[1][1];
57 | a[1][2] = b[1][2];
58 | return a;
59 | }
60 | /**
61 | * Checks if a bounding box is empty.
62 | * @param {import("./types.js").aabb} a
63 | * @returns {boolean}
64 | */ function isEmpty$1(a) {
65 | return a[0][0] > a[1][0] || a[0][1] > a[1][1] || a[0][2] > a[1][2];
66 | }
67 | /**
68 | * Updates a bounding box from a list of points.
69 | * @param {import("./types.js").aabb} a
70 | * @param {import("./types.js").vec3[] | import("./types.js").TypedArray} points
71 | * @returns {import("./types.js").aabb}
72 | */ function fromPoints$1(a, points) {
73 | const isFlatArray = !points[0]?.length;
74 | const l = points.length / (isFlatArray ? 3 : 1);
75 | for(let i = 0; i < l; i++){
76 | if (isFlatArray) {
77 | includePoint$1(a, points, i * 3);
78 | } else {
79 | includePoint$1(a, points[i]);
80 | }
81 | }
82 | return a;
83 | }
84 | /**
85 | * Returns a list of 8 points from a bounding box.
86 | * @param {import("./types.js").aabb} a
87 | * @param {import("./types.js").vec3[]} [points]
88 | * @returns {import("./types.js").vec3[]}
89 | */ function getCorners$1(a, points) {
90 | if (points === undefined) points = Array.from({
91 | length: 8
92 | }, ()=>[]);
93 | set3(points[0], 0, a[0][0], a[0][1], a[0][2]);
94 | set3(points[1], 0, a[1][0], a[0][1], a[0][2]);
95 | set3(points[2], 0, a[1][0], a[0][1], a[1][2]);
96 | set3(points[3], 0, a[0][0], a[0][1], a[1][2]);
97 | set3(points[4], 0, a[0][0], a[1][1], a[0][2]);
98 | set3(points[5], 0, a[1][0], a[1][1], a[0][2]);
99 | set3(points[6], 0, a[1][0], a[1][1], a[1][2]);
100 | set3(points[7], 0, a[0][0], a[1][1], a[1][2]);
101 | return points;
102 | }
103 | /**
104 | * Returns the center of a bounding box.
105 | * @param {import("./types.js").aabb} a
106 | * @param {import("./types.js").vec3} out
107 | * @returns {import("./types.js").vec3}
108 | */ function center$1(a, out) {
109 | if (out === undefined) out = [
110 | 0,
111 | 0,
112 | 0
113 | ];
114 | out[0] = (a[0][0] + a[1][0]) / 2;
115 | out[1] = (a[0][1] + a[1][1]) / 2;
116 | out[2] = (a[0][2] + a[1][2]) / 2;
117 | return out;
118 | }
119 | /**
120 | * Returns the size of a bounding box.
121 | * @param {import("./types.js").aabb} a
122 | * @param {import("./types.js").vec3} out
123 | * @returns {import("./types.js").vec3}
124 | */ function size$1(a, out) {
125 | if (out === undefined) out = [
126 | 0,
127 | 0,
128 | 0
129 | ];
130 | out[0] = Math.abs(a[1][0] - a[0][0]);
131 | out[1] = Math.abs(a[1][1] - a[0][1]);
132 | out[2] = Math.abs(a[1][2] - a[0][2]);
133 | return out;
134 | }
135 | /**
136 | * Checks if a point is inside a bounding box.
137 | * @param {import("./types.js").aabb} a
138 | * @param {import("./types.js").vec3} p
139 | * @returns {boolean}
140 | */ function containsPoint$1(a, param) {
141 | let [x, y, z] = param;
142 | return x >= a[0][0] && x <= a[1][0] && y >= a[0][1] && y <= a[1][1] && z >= a[0][2] && z <= a[1][2];
143 | }
144 | /**
145 | * Includes a bounding box in another.
146 | * @param {import("./types.js").aabb} a
147 | * @param {import("./types.js").aabb} b
148 | * @returns {import("./types.js").aabb}
149 | */ function includeAABB(a, b) {
150 | if (isEmpty$1(a)) {
151 | set$1(a, b);
152 | } else if (isEmpty$1(b)) ; else {
153 | a[0][0] = Math.min(a[0][0], b[0][0]);
154 | a[0][1] = Math.min(a[0][1], b[0][1]);
155 | a[0][2] = Math.min(a[0][2], b[0][2]);
156 | a[1][0] = Math.max(a[1][0], b[1][0]);
157 | a[1][1] = Math.max(a[1][1], b[1][1]);
158 | a[1][2] = Math.max(a[1][2], b[1][2]);
159 | }
160 | return a;
161 | }
162 | /**
163 | * Includes a point in a bounding box.
164 | * @param {import("./types.js").aabb} a
165 | * @param {import("./types.js").vec3} p
166 | * @param {number} [i=0] offset in the point array
167 | * @returns {import("./types.js").vec3}
168 | */ function includePoint$1(a, p, i) {
169 | if (i === undefined) i = 0;
170 | a[0][0] = Math.min(a[0][0], p[i + 0]);
171 | a[0][1] = Math.min(a[0][1], p[i + 1]);
172 | a[0][2] = Math.min(a[0][2], p[i + 2]);
173 | a[1][0] = Math.max(a[1][0], p[i + 0]);
174 | a[1][1] = Math.max(a[1][1], p[i + 1]);
175 | a[1][2] = Math.max(a[1][2], p[i + 2]);
176 | return a;
177 | }
178 | /**
179 | * Prints a bounding box to a string.
180 | * @param {import("./types.js").aabb} a
181 | * @param {number} [precision=4]
182 | * @returns {string}
183 | */ function toString$2(a, precision) {
184 | if (precision === undefined) precision = 4;
185 | // prettier-ignore
186 | return `[${toString$3(a[0], precision)}, ${toString$3(a[1], precision)}]`;
187 | }
188 |
189 | var aabb = /*#__PURE__*/Object.freeze({
190 | __proto__: null,
191 | center: center$1,
192 | containsPoint: containsPoint$1,
193 | copy: copy$1,
194 | create: create$2,
195 | empty: empty$1,
196 | fromPoints: fromPoints$1,
197 | getCorners: getCorners$1,
198 | includeAABB: includeAABB,
199 | includePoint: includePoint$1,
200 | isEmpty: isEmpty$1,
201 | set: set$1,
202 | size: size$1,
203 | toString: toString$2
204 | });
205 |
206 | /**
207 | * Enum for different side values
208 | * @readonly
209 | * @enum {number}
210 | */ const Side = Object.freeze({
211 | OnPlane: 0,
212 | Same: -1,
213 | Opposite: 1
214 | });
215 | const TEMP_0 = create$3();
216 | /**
217 | * Creates a new plane
218 | * @returns {import("./types.js").plane}
219 | */ function create$1() {
220 | return [
221 | [
222 | 0,
223 | 0,
224 | 0
225 | ],
226 | [
227 | 0,
228 | 1,
229 | 0
230 | ]
231 | ];
232 | }
233 | /**
234 | * Returns on which side a point is.
235 | * @param {import("./types.js").plane} plane
236 | * @param {import("./types.js").vec3} point
237 | * @returns {number}
238 | */ function side(param, point) {
239 | let [planePoint, planeNormal] = param;
240 | set$2(TEMP_0, planePoint);
241 | sub(TEMP_0, point);
242 | normalize(TEMP_0);
243 | const dot$1 = dot(TEMP_0, planeNormal);
244 | if (dot$1 > 0) return Side.Opposite;
245 | if (dot$1 < 0) return Side.Same;
246 | return Side.OnPlane;
247 | }
248 | /**
249 | * Prints a plane to a string.
250 | * @param {import("./types.js").plane} a
251 | * @param {number} [precision=4]
252 | * @returns {string}
253 | */ function toString$1(a, precision) {
254 | if (precision === undefined) precision = 4;
255 | // prettier-ignore
256 | return `[${toString$3(a[0], precision)}, ${toString$3(a[1], precision)}]`;
257 | }
258 |
259 | var plane = /*#__PURE__*/Object.freeze({
260 | __proto__: null,
261 | Side: Side,
262 | create: create$1,
263 | side: side,
264 | toString: toString$1
265 | });
266 |
267 | /**
268 | * Creates a new rectangle.
269 | * @returns {import("./types.js").rect}
270 | */ function create() {
271 | return [
272 | [
273 | Infinity,
274 | Infinity
275 | ],
276 | [
277 | -Infinity,
278 | -Infinity
279 | ]
280 | ];
281 | }
282 | /**
283 | * Reset a rectangle.
284 | * @param {import("./types.js").rect} a
285 | * @returns {import("./types.js").rect}
286 | */ function empty(a) {
287 | a[0][0] = a[0][1] = Infinity;
288 | a[1][0] = a[1][1] = -Infinity;
289 | return a;
290 | }
291 | /**
292 | * Copies a rectangle.
293 | * @param {import("./types.js").rect} a
294 | * @returns {import("./types.js").rect}
295 | */ function copy(a) {
296 | return [
297 | a[0].slice(),
298 | a[1].slice()
299 | ];
300 | }
301 | /**
302 | * Sets a rectangle to another.
303 | * @param {import("./types.js").rect} a
304 | * @param {import("./types.js").rect} b
305 | * @returns {import("./types.js").rect}
306 | */ function set(a, b) {
307 | a[0][0] = b[0][0];
308 | a[0][1] = b[0][1];
309 | a[1][0] = b[1][0];
310 | a[1][1] = b[1][1];
311 | return a;
312 | }
313 | /**
314 | * Checks if a rectangle is empty.
315 | * @param {import("./types.js").rect} a
316 | * @returns {boolean}
317 | */ function isEmpty(a) {
318 | return a[0][0] > a[1][0] || a[0][1] > a[1][1];
319 | }
320 | /**
321 | * Updates a rectangle from a list of points.
322 | * @param {import("./types.js").rect} a
323 | * @param {import("./types.js").vec2[] | import("./types.js").TypedArray} points
324 | * @returns {import("./types.js").rect}
325 | */ function fromPoints(a, points) {
326 | const isTypedArray = !Array.isArray(points);
327 | for(let i = 0; i < points.length / (isTypedArray ? 2 : 1); i++){
328 | includePoint(a, isTypedArray ? points.slice(i * 2) : points[i]);
329 | }
330 | return a;
331 | }
332 | /**
333 | * Returns a list of 4 points from a rectangle.
334 | * @param {import("./types.js").rect} a
335 | * @param {import("./types.js").vec2[]} points
336 | * @returns {import("./types.js").vec2[]}
337 | */ function getCorners(a, points) {
338 | if (points === undefined) points = [];
339 | points[0] = a[0].slice();
340 | points[1] = [
341 | a[0][1],
342 | a[1][0]
343 | ];
344 | points[2] = a[1].slice();
345 | points[3] = [
346 | a[1][0],
347 | a[0][1]
348 | ];
349 | return points;
350 | }
351 | /**
352 | * Scales a rectangle.
353 | * @param {import("./types.js").rect} a
354 | * @param {number} n
355 | * @returns {import("./types.js").rect}
356 | */ function scale(a, n) {
357 | a[0][0] *= n;
358 | a[0][1] *= n;
359 | a[1][0] *= n;
360 | a[1][1] *= n;
361 | return a;
362 | }
363 | /**
364 | * Sets the size of a rectangle using width and height.
365 | * @param {import("./types.js").rect} a
366 | * @param {import("./types.js").vec2} size
367 | * @returns {import("./types.js").rect}
368 | */ function setSize(a, size) {
369 | a[1][0] = a[0][0] + size[0];
370 | a[1][1] = a[0][1] + size[1];
371 | return a;
372 | }
373 | /**
374 | * Returns the size of a rectangle.
375 | * @param {import("./types.js").rect} a
376 | * @param {import("./types.js").vec2} out
377 | * @returns {import("./types.js").vec2}
378 | */ function size(a, out) {
379 | if (out === undefined) out = [];
380 | out[0] = width(a);
381 | out[1] = height(a);
382 | return out;
383 | }
384 | /**
385 | * Returns the width of a rectangle.
386 | * @param {import("./types.js").rect} a
387 | * @returns {number}
388 | */ function width(a) {
389 | return a[1][0] - a[0][0];
390 | }
391 | /**
392 | * Returns the height of a rectangle.
393 | * @param {import("./types.js").rect} a
394 | * @returns {number}
395 | */ function height(a) {
396 | return a[1][1] - a[0][1];
397 | }
398 | /**
399 | * Returns the aspect ratio of a rectangle.
400 | * @param {import("./types.js").rect} a
401 | * @returns {number}
402 | */ function aspectRatio(a) {
403 | return width(a) / height(a);
404 | }
405 | /**
406 | * Sets the position of a rectangle.
407 | * @param {import("./types.js").rect} a
408 | * @param {import("./types.js").vec2} p
409 | * @returns {import("./types.js").rect}
410 | */ function setPosition(a, param) {
411 | let [x, y] = param;
412 | const w = width(a);
413 | const h = height(a);
414 | a[0][0] = x;
415 | a[0][1] = y;
416 | a[1][0] = x + w;
417 | a[1][1] = y + h;
418 | return a;
419 | }
420 | /**
421 | * Returns the center of a rectangle.
422 | * @param {import("./types.js").rect} a
423 | * @param {import("./types.js").vec2} out
424 | * @returns {import("./types.js").rect}
425 | */ function center(a, out) {
426 | if (out === undefined) out = [];
427 | out[0] = a[0][0] + width(a) * 0.5;
428 | out[1] = a[0][1] + height(a) * 0.5;
429 | return out;
430 | }
431 | /**
432 | * Checks if a point is inside a rectangle.
433 | * @param {import("./types.js").rect} a
434 | * @param {import("./types.js").vec2} p
435 | * @returns {boolean}
436 | */ function containsPoint(a, param) {
437 | let [x, y] = param;
438 | return x >= a[0][0] && x <= a[1][0] && y >= a[0][1] && y <= a[1][1];
439 | }
440 | /**
441 | * Checks if a rectangle is inside another rectangle.
442 | * @param {import("./types.js").rect} a
443 | * @param {import("./types.js").rect} b
444 | * @returns {boolean}
445 | */ function containsRect(a, b) {
446 | return containsPoint(a, b[0]) && containsPoint(a, b[1]);
447 | }
448 | /**
449 | * Includes a point in a rectangle.
450 | * @param {import("./types.js").rect} a
451 | * @param {import("./types.js").vec2} p
452 | * @returns {import("./types.js").rect}
453 | */ function includePoint(a, param) {
454 | let [x, y] = param;
455 | const minx = a[0][0];
456 | const miny = a[0][1];
457 | const maxx = a[1][0];
458 | const maxy = a[1][1];
459 | a[0][0] = minx > x ? x : minx;
460 | a[0][1] = miny > y ? y : miny;
461 | a[1][0] = maxx < x ? x : maxx;
462 | a[1][1] = maxy < y ? y : maxy;
463 | return a;
464 | }
465 | /**
466 | * Includes a rectangle in another rectangle.
467 | * @param {import("./types.js").rect} a
468 | * @param {import("./types.js").rect} b
469 | * @returns {import("./types.js").rect}
470 | */ function includeRect(a, b) {
471 | includePoint(a, b[0]);
472 | includePoint(a, b[1]);
473 | return a;
474 | }
475 | /**
476 | * Maps a point into the dimensions of a rectangle.
477 | * @param {import("./types.js").rect} a
478 | * @param {import("./types.js").vec2} p
479 | * @returns {import("./types.js").vec2}
480 | */ function mapPoint(a, p) {
481 | const minx = a[0][0];
482 | const miny = a[0][1];
483 | const maxx = a[1][0];
484 | const maxy = a[1][1];
485 | p[0] = Math.max(minx, Math.min(p[0], maxx)) - minx;
486 | p[1] = Math.max(miny, Math.min(p[1], maxy)) - miny;
487 | return p;
488 | }
489 | /**
490 | * Clamps a point into the dimensions of a rectangle.
491 | * @param {import("./types.js").rect} a
492 | * @param {import("./types.js").vec2} p
493 | * @returns {import("./types.js").vec2}
494 | */ function clampPoint(a, p) {
495 | const minx = a[0][0];
496 | const miny = a[0][1];
497 | const maxx = a[1][0];
498 | const maxy = a[1][1];
499 | p[0] = Math.max(minx, Math.min(p[0], maxx));
500 | p[1] = Math.max(miny, Math.min(p[1], maxy));
501 | return p;
502 | }
503 | /**
504 | * Prints a rect to a string.
505 | * @param {import("./types.js").rect} a
506 | * @param {number} [precision=4]
507 | * @returns {string}
508 | */ function toString(a, precision) {
509 | if (precision === undefined) precision = 4;
510 | // prettier-ignore
511 | return `[${toString$4(a[0], precision)}, ${toString$4(a[1], precision)}]`;
512 | }
513 |
514 | var rect = /*#__PURE__*/Object.freeze({
515 | __proto__: null,
516 | aspectRatio: aspectRatio,
517 | center: center,
518 | clampPoint: clampPoint,
519 | containsPoint: containsPoint,
520 | containsRect: containsRect,
521 | copy: copy,
522 | create: create,
523 | empty: empty,
524 | fromPoints: fromPoints,
525 | getCorners: getCorners,
526 | height: height,
527 | includePoint: includePoint,
528 | includeRect: includeRect,
529 | isEmpty: isEmpty,
530 | mapPoint: mapPoint,
531 | scale: scale,
532 | set: set,
533 | setPosition: setPosition,
534 | setSize: setSize,
535 | size: size,
536 | toString: toString,
537 | width: width
538 | });
539 |
540 | export { aabb, plane, rect };
541 |
--------------------------------------------------------------------------------
/web_modules/pex-io.js:
--------------------------------------------------------------------------------
1 | /** @module pex-io */ const ok = async (response)=>response.ok ? response : Promise.reject(new Error(`GET ${response.url} ${response.status} (${response.statusText})`));
2 | /**
3 | * Load an item and parse the Response as text.
4 | * @function
5 | * @param {RequestInfo} url
6 | * @param {RequestInit} options
7 | * @returns {Promise}
8 | */ const loadText = async (url, options)=>{
9 | if (options === undefined) options = {};
10 | return await (await ok(await fetch(url, options))).text();
11 | };
12 | /**
13 | * Load an item and parse the Response as json.
14 | * @function
15 | * @param {RequestInfo} url
16 | * @param {RequestInit} options
17 | * @returns {Promise}
18 | */ const loadJson = async (url, options)=>{
19 | if (options === undefined) options = {};
20 | return await (await ok(await fetch(url, options))).json();
21 | };
22 | /**
23 | * Load an item and parse the Response as arrayBuffer.
24 | * @function
25 | * @param {RequestInfo} url
26 | * @param {RequestInit} options
27 | * @returns {Promise}
28 | */ const loadArrayBuffer = async (url, options)=>{
29 | if (options === undefined) options = {};
30 | return await (await ok(await fetch(url, options))).arrayBuffer();
31 | };
32 | /**
33 | * Load an item and parse the Response as blob.
34 | * @function
35 | * @param {RequestInfo} url
36 | * @param {RequestInit} options
37 | * @returns {Promise}
38 | */ const loadBlob = async (url, options)=>{
39 | if (options === undefined) options = {};
40 | return await (await ok(await fetch(url, options))).blob();
41 | };
42 | /**
43 | * Load an item, parse the Response as blob and create a HTML Image.
44 | * @function
45 | * @param {string | import("./types.js").ImageOptions} urlOrOpts
46 | * @param {RequestInit} options
47 | * @returns {Promise}
48 | */ const loadImage = async (urlOrOpts, options)=>{
49 | if (options === undefined) options = {};
50 | const img = new Image();
51 | let src = urlOrOpts;
52 | if (urlOrOpts.url) {
53 | const { url, ...rest } = urlOrOpts;
54 | src = url;
55 | try {
56 | Object.assign(img, rest);
57 | } catch (error) {
58 | return Promise.reject(new Error(error));
59 | }
60 | }
61 | const data = await loadBlob(src, options);
62 | return await new Promise((resolve, reject)=>{
63 | img.addEventListener("load", function load() {
64 | img.removeEventListener("load", load);
65 | resolve(img);
66 | });
67 | img.addEventListener("error", function error() {
68 | img.removeEventListener("error", error);
69 | reject(img);
70 | });
71 | img.src = URL.createObjectURL(data);
72 | });
73 | };
74 | /**
75 | * @private
76 | */ const LOADERS_MAP = {
77 | text: loadText,
78 | json: loadJson,
79 | image: loadImage,
80 | blob: loadBlob,
81 | arrayBuffer: loadArrayBuffer
82 | };
83 | const LOADERS_MAP_KEYS = Object.keys(LOADERS_MAP);
84 | /**
85 | * Loads resources from a named map.
86 | * @function
87 | * @param {Object.} resources
88 | * @returns {Promise>}
89 | * @example
90 | * const resources = {
91 | * hello: { text: "assets/hello.txt" },
92 | * data: { json: "assets/data.json" },
93 | * img: { image: "assets/tex.jpg" },
94 | * blob: { blob: "assets/blob" },
95 | * hdrImg: { arrayBuffer: "assets/tex.hdr", options: { mode: "no-cors" } },
96 | * };
97 | *
98 | * const res = await io.load(resources);
99 | * res.hello; // => string
100 | * res.data; // => Object
101 | * res.img; // => HTMLImageElement
102 | * res.blob; // => Blob
103 | * res.hdrImg; // => ArrayBuffer
104 | */ const load = (resources)=>{
105 | const names = Object.keys(resources);
106 | return Promise.allSettled(names.map(async (name)=>{
107 | const res = resources[name];
108 | const loader = LOADERS_MAP_KEYS.find((loader)=>res[loader]);
109 | if (loader) return await LOADERS_MAP[loader](res[loader], res.options);
110 | return Promise.reject(new Error(`io.load: unknown resource type "${Object.keys(res)}".
111 | Resource needs one of ${LOADERS_MAP_KEYS.join("|")} set to an url.`));
112 | })).then((values)=>Object.fromEntries(Array.from(values.map((v)=>v.value || v.reason), (v, i)=>[
113 | names[i],
114 | v
115 | ])));
116 | };
117 |
118 | export { load, loadArrayBuffer, loadBlob, loadImage, loadJson, loadText };
119 |
--------------------------------------------------------------------------------