├── .editorconfig
├── .gitignore
├── .nojekyll
├── .npmignore
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── examples
├── elliptical-mapping.js
├── elliptical-mapping
│ ├── all.jpg
│ ├── concentric.png
│ ├── cornerificTapered2.png
│ ├── elliptical-mapping.md
│ ├── elliptical.png
│ ├── fgSquircular.png
│ ├── lamé.png
│ ├── nonAxial2Pinch.png
│ ├── nonAxialHalfPinch.png
│ ├── radial.png
│ ├── rectangular.png
│ ├── squelched.png
│ ├── squelchedHorizontal.png
│ ├── squelchedVertical.png
│ ├── tapered4.png
│ ├── threeSquircular.png
│ └── twoSquircular.png
├── index.js
├── mode
│ ├── 00-texture.png
│ ├── 01-normal.png
│ ├── 02-flat-shaded.png
│ ├── 03-uv.png
│ ├── 04-wireframe.png
│ └── 05-bbox.png
├── render.js
└── uv.jpg
├── index.html
├── index.js
├── package-lock.json
├── package.json
├── screenshot.gif
├── src
├── annulus.js
├── box.js
├── capsule.js
├── circle.js
├── cone.js
├── cube.js
├── cylinder.js
├── disc.js
├── ellipse.js
├── ellipsoid.js
├── icosahedron.js
├── icosphere.js
├── mappings.js
├── plane.js
├── quad.js
├── reuleux.js
├── rounded-cube.js
├── rounded-rectangle.js
├── sphere.js
├── squircle.js
├── stadium.js
├── superellipse.js
├── tetrahedron.js
├── torus.js
└── utils.js
├── types.js
└── web_modules
├── _chunks
├── _commonjsHelpers-BFTU3MAI.js
├── polyfills-BwRuO6W0.js
└── vec2-BUEubpJQ.js
├── async-preloader.js
├── cameras.js
├── es-module-shims.js
├── es-module-shims
├── debug.js
├── typescript-transform.js
└── wasm.js
├── gl-matrix.js
├── import-map.json
├── pex-context.js
├── pex-geom.js
├── tweakpane.js
└── typed-array-interleave.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/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/.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 | # [2.11.0](https://github.com/dmnsgn/primitive-geometry/compare/v2.10.1...v2.11.0) (2025-05-24)
6 |
7 |
8 | ### Features
9 |
10 | * add mergeCentroid option for ellipse, disc, reuleux, squircle and superellipse ([e590786](https://github.com/dmnsgn/primitive-geometry/commit/e5907861c48aa6aa39d53b792b468bda8fb297ac))
11 | * add polar uv mapping + add safer division ([fa8e850](https://github.com/dmnsgn/primitive-geometry/commit/fa8e850633e42351ede778f34fd28c81a9fb42a7))
12 | * use ellipse for annulus + add support for elliptical annulus ([e8b2640](https://github.com/dmnsgn/primitive-geometry/commit/e8b2640d53025abb1c95a1ca1d32b90ea1dab5b6))
13 |
14 |
15 |
16 | ## [2.10.1](https://github.com/dmnsgn/primitive-geometry/compare/v2.10.0...v2.10.1) (2024-06-20)
17 |
18 |
19 | ### Bug Fixes
20 |
21 | * Update types export in package.json ([4c18ff1](https://github.com/dmnsgn/primitive-geometry/commit/4c18ff141e398ea276919408b9f5d94d73780d41)), closes [#18](https://github.com/dmnsgn/primitive-geometry/issues/18)
22 |
23 |
24 |
25 | # [2.10.0](https://github.com/dmnsgn/primitive-geometry/compare/v2.9.1...v2.10.0) (2024-02-04)
26 |
27 |
28 | ### Features
29 |
30 | * use 3D positions for circle ([5bb9e3b](https://github.com/dmnsgn/primitive-geometry/commit/5bb9e3b68b4de45ceec0864465a0edf4fffe1d7e)), closes [#15](https://github.com/dmnsgn/primitive-geometry/issues/15)
31 |
32 |
33 |
34 | ## [2.9.1](https://github.com/dmnsgn/primitive-geometry/compare/v2.9.0...v2.9.1) (2022-10-26)
35 |
36 |
37 | ### Bug Fixes
38 |
39 | * ccw triangle order for rounded rectangle edges ([c04dc96](https://github.com/dmnsgn/primitive-geometry/commit/c04dc9698eba752b10d73c438a1daeadb474228d))
40 |
41 |
42 |
43 | # [2.9.0](https://github.com/dmnsgn/primitive-geometry/compare/v2.8.0...v2.9.0) (2022-09-22)
44 |
45 |
46 | ### Features
47 |
48 | * add phi/thetaOffset ([1ea22da](https://github.com/dmnsgn/primitive-geometry/commit/1ea22da7b69384c0d5fc32d9613d68d8a1a4c2ec))
49 | * add rounded rectangle and stadium ([0dd9d9e](https://github.com/dmnsgn/primitive-geometry/commit/0dd9d9ed70194d1e78f85df6db8096589fd626df))
50 |
51 |
52 |
53 | # [2.8.0](https://github.com/dmnsgn/primitive-geometry/compare/v2.7.0...v2.8.0) (2022-09-14)
54 |
55 |
56 | ### Features
57 |
58 | * add elliptical geometries (ellipse, superellipse, squircle, reuleux) ([ba5b72f](https://github.com/dmnsgn/primitive-geometry/commit/ba5b72f6fed5748cde1dc62b8b9580f33f481cc1))
59 |
60 |
61 |
62 | # [2.7.0](https://github.com/dmnsgn/primitive-geometry/compare/v2.6.0...v2.7.0) (2022-06-15)
63 |
64 |
65 | ### Features
66 |
67 | * **plane:** add quads option ([3b9cb02](https://github.com/dmnsgn/primitive-geometry/commit/3b9cb02db95882faceb20049a3edbd9ceeec776a)), closes [#11](https://github.com/dmnsgn/primitive-geometry/issues/11)
68 |
69 |
70 |
71 | # [2.6.0](https://github.com/dmnsgn/primitive-geometry/compare/v2.5.0...v2.6.0) (2022-04-15)
72 |
73 |
74 | ### Features
75 |
76 | * **capsule:** add roundSegments ([4a0ca75](https://github.com/dmnsgn/primitive-geometry/commit/4a0ca7586c1426169d63bec62f466647df424e9c))
77 | * **rounded-cube:** add roundSegments and edgeSegments ([98741b9](https://github.com/dmnsgn/primitive-geometry/commit/98741b90e5660e1555e7c998265f5b88badda19f))
78 |
79 |
80 |
81 | # [2.5.0](https://github.com/dmnsgn/primitive-geometry/compare/v2.4.0...v2.5.0) (2022-04-11)
82 |
83 |
84 | ### Bug Fixes
85 |
86 | * **plane:** hardcode normals from direction ([18e2423](https://github.com/dmnsgn/primitive-geometry/commit/18e2423beb133c84ab35372dfc50388477bb5d49))
87 | * **torus:** default radius and minorSegments ([14c5256](https://github.com/dmnsgn/primitive-geometry/commit/14c5256d36b125bc116b2f8e7c91de93723e3d23))
88 |
89 |
90 | ### Features
91 |
92 | * add disc and annulus ([6328e9f](https://github.com/dmnsgn/primitive-geometry/commit/6328e9ffbe82955f8302e49b7d1ba67769f21f08))
93 | * **quad:** make normal face z ([246f7de](https://github.com/dmnsgn/primitive-geometry/commit/246f7de131db1463c829c4bbbcb901b2ea892c82))
94 |
95 |
96 | ### Performance Improvements
97 |
98 | * move iterator in increment expression + remove assignment to 0 (default with TypedArrays) ([d0dbfbc](https://github.com/dmnsgn/primitive-geometry/commit/d0dbfbc7c8acd19b1c1b032e7f7dfa3412ea78fd))
99 | * **circle:** extract t value for cos/sin ([5d1c7dd](https://github.com/dmnsgn/primitive-geometry/commit/5d1c7dd8f6e55ec76b7c5b02ec6f938924120045))
100 |
101 |
102 |
103 | # [2.4.0](https://github.com/dmnsgn/primitive-geometry/compare/v2.3.0...v2.4.0) (2022-04-06)
104 |
105 |
106 | ### Bug Fixes
107 |
108 | * **capsule:** wrong cell order ([0aa7efb](https://github.com/dmnsgn/primitive-geometry/commit/0aa7efb8c5e1f6e00c9a7c21d465c4048e8d983d))
109 |
110 |
111 | ### Features
112 |
113 | * add phi/theta for all geometries ([b3afdf8](https://github.com/dmnsgn/primitive-geometry/commit/b3afdf8e320b0056f7f1274e908cac97d78df632))
114 |
115 |
116 |
117 | # [2.3.0](https://github.com/dmnsgn/primitive-geometry/compare/v2.2.0...v2.3.0) (2022-04-06)
118 |
119 |
120 | ### Features
121 |
122 | * add tetrahedron + add icosahedron ([cd341d2](https://github.com/dmnsgn/primitive-geometry/commit/cd341d203274cec347242f58d373f69501caee13))
123 | * **cylinder:** add capBaseSegments option ([92dc090](https://github.com/dmnsgn/primitive-geometry/commit/92dc090880ab1de82f0d68c5becddb10b90ab22e))
124 | * add plane direction + refactor computePlane for use in both plane and cube ([a5392c3](https://github.com/dmnsgn/primitive-geometry/commit/a5392c30de99db8e2190d041451c9c963d1bd549))
125 | * **circle:** add theta and closed options ([cf34b04](https://github.com/dmnsgn/primitive-geometry/commit/cf34b047c837efb6514b74da9848dab7d97c8eca))
126 |
127 |
128 |
129 | # [2.2.0](https://github.com/dmnsgn/primitive-geometry/compare/v2.1.0...v2.2.0) (2021-10-02)
130 |
131 |
132 | ### Features
133 |
134 | * add exports field to package.json ([09700e2](https://github.com/dmnsgn/primitive-geometry/commit/09700e218efe30b29e5d8b3bf71a44864adf2f32))
135 |
136 |
137 |
138 | # [2.1.0](https://github.com/dmnsgn/primitive-geometry/compare/v2.0.1...v2.1.0) (2021-08-16)
139 |
140 |
141 | ### Features
142 |
143 | * add arguments check ([797003b](https://github.com/dmnsgn/primitive-geometry/commit/797003bee9de62b8f7a03ccec5d34a0730605f1b)), closes [#10](https://github.com/dmnsgn/primitive-geometry/issues/10)
144 |
145 |
146 |
147 | ## [2.0.1](https://github.com/dmnsgn/primitive-geometry/compare/v2.0.0...v2.0.1) (2021-06-15)
148 |
149 |
150 |
151 | # [2.0.0](https://github.com/dmnsgn/primitive-geometry/compare/v1.2.1...v2.0.0) (2021-04-27)
152 |
153 |
154 | ### Code Refactoring
155 |
156 | * use ES modules and move to typed arrays ([d6f2aed](https://github.com/dmnsgn/primitive-geometry/commit/d6f2aedf1805b8506e2baf1ffc4190e6952158c5))
157 |
158 |
159 | ### BREAKING CHANGES
160 |
161 | * switch to type module and move to typed arrays
162 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (C) 2018 Damien Seguin
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/examples/elliptical-mapping.js:
--------------------------------------------------------------------------------
1 | import * as Primitives from "../index.js";
2 |
3 | import { setGeometries, pane, controls, CONFIG } from "./render.js";
4 |
5 | const params = new URLSearchParams(window.location.search);
6 |
7 | const update = (options) => {
8 | setGeometries(
9 | params.has("screenshot")
10 | ? [Primitives.disc({ segments: 128, innerSegments: 128, ...options })]
11 | : [
12 | Primitives.ellipse(options),
13 | Primitives.disc(options),
14 | Primitives.superellipse(options),
15 | null,
16 | Primitives.annulus(options),
17 | // Elliptical annulus
18 | Primitives.annulus({ sy: 0.5, options }),
19 | null,
20 | Primitives.squircle(options),
21 | Primitives.reuleux(options),
22 | null,
23 | Primitives.superellipse({
24 | m: 4,
25 | sx: 1,
26 | sy: 1,
27 | ...options,
28 | }), // Lamé special quartic (Squircle)
29 | Primitives.superellipse({
30 | m: 4,
31 | sx: 1,
32 | sy: 0.5,
33 | ...options,
34 | }), // Rectellipse
35 | Primitives.superellipse({ m: 2 / 3, sy: 1, ...options }), // Astroid
36 | null,
37 | Primitives.superellipse({ m: 1, sy: 1, ...options }), // Diamond
38 | Primitives.superellipse({
39 | m: 5 / 2,
40 | sx: 6 / 6,
41 | sy: 5 / 6,
42 | ...options,
43 | }), // Piet Hein 6 / 5
44 | Primitives.superellipse({
45 | m: 5 / 2,
46 | sx: 3 / 3,
47 | sy: 2 / 3,
48 | ...options,
49 | }), // Piet Hein 6 / 5
50 | ],
51 | );
52 | };
53 |
54 | const mappingOptions = Object.keys(Primitives.mappings);
55 | if (params.has("screenshot")) window.screenshotItems = mappingOptions;
56 |
57 | CONFIG.mapping = "";
58 | CONFIG.theta = Primitives.utils.TAU;
59 | CONFIG.thetaOffset = 0;
60 | CONFIG.mergeCentroid = false;
61 |
62 | const getGeometryOptions = () => ({
63 | // segments: 4,
64 | // innerSegments: 4,
65 | mapping: Primitives.mappings[CONFIG.mapping],
66 | theta: CONFIG.theta,
67 | thetaOffset: CONFIG.thetaOffset,
68 | mergeCentroid: CONFIG.mergeCentroid,
69 | });
70 |
71 | pane.addBlade({ view: "separator" });
72 | pane
73 | .addBinding(CONFIG, "mapping", {
74 | options: [
75 | { text: "", value: "" },
76 | ...mappingOptions.map((value) => ({
77 | text: value,
78 | value,
79 | })),
80 | ],
81 | })
82 | .on("change", (event) => {
83 | update(getGeometryOptions());
84 | if (params.has("screenshot")) {
85 | window.dispatchEvent(new CustomEvent("screenshot"));
86 | }
87 | });
88 | pane
89 | .addBinding(CONFIG, "theta", {
90 | min: 0,
91 | max: Primitives.utils.TAU,
92 | })
93 | .on("change", () => {
94 | update(getGeometryOptions());
95 | });
96 | pane
97 | .addBinding(CONFIG, "thetaOffset", {
98 | min: 0,
99 | max: Primitives.utils.TAU,
100 | })
101 | .on("change", () => {
102 | update(getGeometryOptions());
103 | });
104 | pane.addBinding(CONFIG, "mergeCentroid").on("change", () => {
105 | update(getGeometryOptions());
106 | });
107 |
108 | CONFIG.cycleMapping = false;
109 | pane.addBinding(CONFIG, "cycleMapping");
110 |
111 | CONFIG.mapping = params.get("mapping");
112 | if (params.has("screenshot")) {
113 | CONFIG.axes = false;
114 | document.querySelector("main h1").innerHTML = ``;
115 | update();
116 | pane.refresh();
117 | pane.dispose();
118 |
119 | controls.damping = 0;
120 | controls.sphericalTarget = [0, Math.PI / 2, 1.5];
121 | controls.update();
122 | controls.damping = 0.9;
123 | } else {
124 | update();
125 | pane.refresh();
126 | }
127 |
128 | setInterval(() => {
129 | if (CONFIG.cycleMapping) {
130 | CONFIG.mapping =
131 | mappingOptions[
132 | (mappingOptions.findIndex((m) => m === CONFIG.mapping) + 1) %
133 | mappingOptions.length
134 | ];
135 |
136 | pane.refresh();
137 | }
138 | }, 2000);
139 |
--------------------------------------------------------------------------------
/examples/elliptical-mapping/all.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/elliptical-mapping/all.jpg
--------------------------------------------------------------------------------
/examples/elliptical-mapping/concentric.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/elliptical-mapping/concentric.png
--------------------------------------------------------------------------------
/examples/elliptical-mapping/cornerificTapered2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/elliptical-mapping/cornerificTapered2.png
--------------------------------------------------------------------------------
/examples/elliptical-mapping/elliptical-mapping.md:
--------------------------------------------------------------------------------
1 | # Elliptical mapping
2 |
3 | Based on the paper [Elliptification of Rectangular Imagery](https://arxiv.org/pdf/1709.07875.pdf) from Chamberlain Fong, I have implemented the mappings that can be used for the following geometries UVs generation: ellipse, disc, superellipse, squircle, annulus and reuleux geometries.
4 |
5 | ## All Mappings
6 |
7 | 
8 |
9 | ## Rectangular
10 |
11 | 
12 |
13 | ## Radial
14 |
15 | 
16 |
17 | ## Concentric
18 |
19 | 
20 |
21 | ## Lamé
22 |
23 | 
24 |
25 | ## Elliptical
26 |
27 | 
28 |
29 | ## Fg Squircular
30 |
31 | 
32 |
33 | ## Two Squircular
34 |
35 | 
36 |
37 | ## Three Squircular
38 |
39 | 
40 |
41 | ## Cornerific Tapered2
42 |
43 | 
44 |
45 | ## Tapered4
46 |
47 | 
48 |
49 | ## Non Axial2 Pinch
50 |
51 | 
52 |
53 | ## Non Axial Half Pinch
54 |
55 | 
56 |
57 | ## Squelched
58 |
59 | 
60 |
61 | ## Squelched Vertical
62 |
63 | 
64 |
65 | ## Squelched Horizontal
66 |
67 | 
68 |
--------------------------------------------------------------------------------
/examples/elliptical-mapping/elliptical.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/elliptical-mapping/elliptical.png
--------------------------------------------------------------------------------
/examples/elliptical-mapping/fgSquircular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/elliptical-mapping/fgSquircular.png
--------------------------------------------------------------------------------
/examples/elliptical-mapping/lamé.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/elliptical-mapping/lamé.png
--------------------------------------------------------------------------------
/examples/elliptical-mapping/nonAxial2Pinch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/elliptical-mapping/nonAxial2Pinch.png
--------------------------------------------------------------------------------
/examples/elliptical-mapping/nonAxialHalfPinch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/elliptical-mapping/nonAxialHalfPinch.png
--------------------------------------------------------------------------------
/examples/elliptical-mapping/radial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/elliptical-mapping/radial.png
--------------------------------------------------------------------------------
/examples/elliptical-mapping/rectangular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/elliptical-mapping/rectangular.png
--------------------------------------------------------------------------------
/examples/elliptical-mapping/squelched.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/elliptical-mapping/squelched.png
--------------------------------------------------------------------------------
/examples/elliptical-mapping/squelchedHorizontal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/elliptical-mapping/squelchedHorizontal.png
--------------------------------------------------------------------------------
/examples/elliptical-mapping/squelchedVertical.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/elliptical-mapping/squelchedVertical.png
--------------------------------------------------------------------------------
/examples/elliptical-mapping/tapered4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/elliptical-mapping/tapered4.png
--------------------------------------------------------------------------------
/examples/elliptical-mapping/threeSquircular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/elliptical-mapping/threeSquircular.png
--------------------------------------------------------------------------------
/examples/elliptical-mapping/twoSquircular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/elliptical-mapping/twoSquircular.png
--------------------------------------------------------------------------------
/examples/index.js:
--------------------------------------------------------------------------------
1 | import * as Primitives from "../index.js";
2 |
3 | import { modeOptions, setGeometries, computeEdges } from "./render.js";
4 |
5 | const params = new URLSearchParams(window.location.search);
6 |
7 | // I don't like performances, just give me the biggest you've got
8 | // Primitives.utils.setTypedArrayType(Uint32Array);
9 |
10 | const box = Primitives.box();
11 | box.edges = computeEdges(box.positions, box.cells, 4);
12 |
13 | const quadsPlane = Primitives.plane({ nx: 10, quads: true });
14 | quadsPlane.edges = computeEdges(quadsPlane.positions, quadsPlane.cells, 4);
15 | quadsPlane.quads = true;
16 |
17 | const circle = Primitives.circle({ closed: true });
18 | circle.edges = circle.cells;
19 |
20 | // Box and plane of quads are rendered as lines
21 | const geometries =
22 | params.has("geometry") && Primitives[params.get("geometry")]
23 | ? [Primitives[params.get("geometry")]()]
24 | : [
25 | box,
26 | circle,
27 | quadsPlane,
28 | Primitives.quad(),
29 | null,
30 | Primitives.plane(),
31 | Primitives.roundedRectangle(),
32 | Primitives.stadium(),
33 | null,
34 | Primitives.ellipse(),
35 | Primitives.disc(),
36 | Primitives.superellipse(),
37 | Primitives.squircle(),
38 | Primitives.annulus(),
39 | Primitives.reuleux(),
40 | null,
41 | Primitives.cube(),
42 | Primitives.roundedCube(),
43 | null,
44 | Primitives.sphere(),
45 | Primitives.icosphere(),
46 | Primitives.ellipsoid(),
47 | null,
48 | Primitives.cylinder(),
49 | Primitives.cone(),
50 | Primitives.capsule(),
51 | Primitives.torus(),
52 | null,
53 | Primitives.tetrahedron(),
54 | Primitives.icosahedron(),
55 | ];
56 |
57 | setGeometries(geometries);
58 |
59 | if (params.has("screenshot")) {
60 | window.screenshotItems = [...modeOptions, "bbox"];
61 | window.dispatchEvent(new CustomEvent("screenshot"));
62 | }
63 |
--------------------------------------------------------------------------------
/examples/mode/00-texture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/mode/00-texture.png
--------------------------------------------------------------------------------
/examples/mode/01-normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/mode/01-normal.png
--------------------------------------------------------------------------------
/examples/mode/02-flat-shaded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/mode/02-flat-shaded.png
--------------------------------------------------------------------------------
/examples/mode/03-uv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/mode/03-uv.png
--------------------------------------------------------------------------------
/examples/mode/04-wireframe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/mode/04-wireframe.png
--------------------------------------------------------------------------------
/examples/mode/05-bbox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/mode/05-bbox.png
--------------------------------------------------------------------------------
/examples/render.js:
--------------------------------------------------------------------------------
1 | import * as Primitives from "../index.js";
2 |
3 | import { mat3, mat4 } from "gl-matrix";
4 | import createContext from "pex-context";
5 | import { aabb } from "pex-geom";
6 | import AsyncPreloader from "async-preloader";
7 | import typedArrayInterleave from "typed-array-interleave";
8 | import { PerspectiveCamera, Controls } from "cameras";
9 | import { Pane } from "tweakpane";
10 |
11 | const params = new URLSearchParams(window.location.search);
12 |
13 | // Setup
14 | const canvas = document.createElement("canvas");
15 | document.querySelector("main").appendChild(canvas);
16 | const ctx = createContext({
17 | canvas,
18 | pixelRatio: devicePixelRatio,
19 | });
20 |
21 | const camera = new PerspectiveCamera({
22 | fov: Math.PI / 4,
23 | near: 0.1,
24 | far: 100,
25 | viewport: [0, 0, window.innerWidth, window.innerHeight],
26 | });
27 | const controls = new Controls({
28 | ...(params.has("geometry") && Primitives[params.get("geometry")]
29 | ? {
30 | position: [0, 0, 2],
31 | }
32 | : {
33 | phi: Math.PI / 3,
34 | theta: Math.PI / 4,
35 | distance: 15 * (window.innerHeight / window.innerWidth),
36 | }),
37 | element: ctx.gl.canvas,
38 | camera,
39 | distanceBounds: [1, 100],
40 | });
41 | controls.updatePosition();
42 | controls.target = [0, 0, 0];
43 |
44 | // GUI
45 | const modeOptions = ["texture", "normal", "flat-shaded", "uv", "wireframe"];
46 |
47 | const CONFIG = {
48 | mode: params.get("mode") || "texture",
49 | cycle: false,
50 | axes: true,
51 | bbox: params.get("mode") === "bbox",
52 | normals: true,
53 | };
54 | const pane = new Pane();
55 | pane.addBinding(CONFIG, "mode", {
56 | options: modeOptions.map((value) => ({
57 | text: value.toUpperCase(),
58 | value,
59 | })),
60 | });
61 | pane.addBinding(CONFIG, "cycle");
62 | pane.addBinding(CONFIG, "bbox");
63 | pane.addBinding(CONFIG, "normals");
64 |
65 | setInterval(() => {
66 | if (CONFIG.cycle) {
67 | CONFIG.mode =
68 | modeOptions[
69 | (modeOptions.findIndex((m) => m === CONFIG.mode) + 1) %
70 | modeOptions.length
71 | ];
72 | pane.refresh();
73 | }
74 | }, 2000);
75 |
76 | // Assets
77 | const colorMap = ctx.texture2D({
78 | data: await AsyncPreloader.loadImage({ src: "examples/uv.jpg" }),
79 | flipY: true,
80 | wrap: ctx.Wrap.Repeat,
81 | });
82 |
83 | // Loop
84 | const clearCmd = {
85 | pass: ctx.pass({
86 | clearColor: [0, 0, 0, 1],
87 | clearDepth: 1,
88 | }),
89 | };
90 |
91 | const drawCmd = {
92 | pipeline: ctx.pipeline({
93 | depthTest: true,
94 | cullFace: true,
95 | vert: /* glsl */ `#version 300 es
96 | precision mediump float;
97 |
98 | uniform mat4 uProjectionMatrix;
99 | uniform mat4 uModelMatrix;
100 | uniform mat4 uViewMatrix;
101 | uniform mat4 uInverseViewMatrix;
102 | uniform mat3 uNormalMatrix;
103 |
104 | in vec3 aPosition;
105 | in vec2 aUv;
106 | in vec3 aNormal;
107 |
108 | out vec3 vPositionWorld;
109 | out vec3 vPositionView;
110 | out vec3 vNormalView;
111 | out vec3 vNormal;
112 | out vec3 vNormalWorld;
113 | out vec2 vUv;
114 |
115 | void main() {
116 | vNormal = aNormal;
117 | vUv = aUv;
118 |
119 | vPositionWorld = (uModelMatrix * vec4(aPosition, 1.0)).xyz;
120 | vPositionView = (uViewMatrix * vec4(vPositionWorld, 1.0)).xyz;
121 |
122 | vNormalView = uNormalMatrix * aNormal;
123 | vNormalWorld = normalize((uInverseViewMatrix * vec4(vNormalView, 0.0)).xyz);
124 |
125 | gl_Position = uProjectionMatrix * vec4(vPositionView, 1.0);
126 | }`,
127 | frag: /* glsl */ `#version 300 es
128 | precision mediump float;
129 |
130 | uniform sampler2D uColorMap;
131 | uniform float uMode;
132 |
133 | in vec3 vPositionWorld;
134 | in vec3 vPositionView;
135 | in vec3 vNormal;
136 | in vec2 vUv;
137 |
138 | out vec4 fragColor;
139 |
140 | void main () {
141 | if (uMode == 0.0) fragColor = texture(uColorMap, vUv);
142 | if (uMode == 1.0) fragColor = vec4(vNormal * 0.5 + 0.5, 1.0);
143 | if (uMode == 2.0) {
144 | vec3 fdx = vec3(dFdx(vPositionWorld.x), dFdx(vPositionWorld.y), dFdx(vPositionWorld.z));
145 | vec3 fdy = vec3(dFdy(vPositionWorld.x), dFdy(vPositionWorld.y), dFdy(vPositionWorld.z));
146 | vec3 normal = normalize(cross(fdx, fdy));
147 | fragColor = vec4(normal * 0.5 + 0.5, 1.0);
148 | }
149 | if (uMode == 3.0) fragColor = vec4(vUv.xy, 0.0, 1.0);
150 | if (vUv.x > 1.0 || vUv.x < 0.0 || vUv.y > 1.0 || vUv.y < 0.0) fragColor.a = 0.1;
151 | }`,
152 | }),
153 | uniforms: {
154 | uColorMap: colorMap,
155 | },
156 | };
157 | const drawLinesCmd = {
158 | pipeline: ctx.pipeline({
159 | depthTest: true,
160 | primitive: ctx.Primitive.Lines,
161 | blend: true,
162 | blendSrcRGBFactor: ctx.BlendFactor.SrcAlpha,
163 | blendSrcAlphaFactor: ctx.BlendFactor.One,
164 | blendDstRGBFactor: ctx.BlendFactor.OneMinusSrcAlpha,
165 | blendDstAlphaFactor: ctx.BlendFactor.One,
166 | vert: /* glsl */ `#version 300 es
167 | precision mediump float;
168 |
169 | uniform mat4 uProjectionMatrix;
170 | uniform mat4 uModelMatrix;
171 | uniform mat4 uViewMatrix;
172 |
173 | in vec3 aPosition;
174 | in vec3 aColor;
175 |
176 | out vec3 vPositionWorld;
177 | out vec3 vPositionView;
178 | out vec3 vColor;
179 |
180 | void main() {
181 | vPositionWorld = (uModelMatrix * vec4(aPosition, 1.0)).xyz;
182 | vPositionView = (uViewMatrix * vec4(vPositionWorld, 1.0)).xyz;
183 | vColor = aColor;
184 |
185 | gl_Position = uProjectionMatrix * vec4(vPositionView, 1.0);
186 | }`,
187 | frag: /* glsl */ `#version 300 es
188 | precision mediump float;
189 |
190 | uniform float uOpacity;
191 |
192 | in vec3 vColor;
193 |
194 | out vec4 fragColor;
195 |
196 | void main () {
197 | fragColor = vec4(vColor, uOpacity);
198 | }`,
199 | }),
200 | uniforms: { uOpacity: 1 },
201 | };
202 |
203 | const bboxCells = ctx.indexBuffer(
204 | // prettier-ignore
205 | Uint8Array.of(
206 | 0, 1, 1, 2, 2, 3, 3, 0,
207 | 4, 5, 5, 6, 6, 7, 7, 4,
208 | 0, 4, 1, 5, 2, 6, 3, 7
209 | ),
210 | );
211 | const unitBox = Primitives.box();
212 | unitBox.edges = computeEdges(unitBox.positions, unitBox.cells, 4);
213 |
214 | const drawAxesCmd = {
215 | ...drawLinesCmd,
216 | attributes: {
217 | aPosition: ctx.vertexBuffer(
218 | // prettier-ignore
219 | Float32Array.of(
220 | 0, 0, 0,
221 | 1, 0, 0,
222 | 0, 0, 0,
223 | 0, 1, 0,
224 | 0, 0, 0,
225 | 0, 0, 1,
226 | ),
227 | ),
228 | aColor: ctx.vertexBuffer(
229 | // prettier-ignore
230 | Float32Array.of(
231 | 1, 0, 0,
232 | 1, 0.5, 0.5,
233 | 0, 1, 0,
234 | 0.5, 1, 0.5,
235 | 0, 0, 1,
236 | 0.5, 0.5, 1,
237 | ),
238 | ),
239 | },
240 | indices: ctx.indexBuffer(Uint8Array.of(0, 1, 2, 3, 4, 5)),
241 | uniforms: { uModelMatrix: mat4.create() },
242 | };
243 |
244 | // Events
245 | const onResize = () => {
246 | const width = window.innerWidth;
247 | const height = window.innerHeight;
248 | ctx.set({ width, height });
249 |
250 | canvas.width = width * devicePixelRatio;
251 | canvas.height = height * devicePixelRatio;
252 |
253 | camera.aspect = width / height;
254 | camera.updateProjectionMatrix();
255 | };
256 | window.addEventListener("resize", onResize);
257 | onResize();
258 |
259 | const inverseModelViewMatrix = mat4.create();
260 |
261 | let meshes = [];
262 |
263 | // Render
264 | ctx.frame(() => {
265 | controls.update();
266 | camera.position = controls.position;
267 | camera.target = controls.target;
268 | camera.update();
269 |
270 | ctx.submit(clearCmd);
271 |
272 | if (CONFIG.axes) {
273 | ctx.submit(drawAxesCmd, {
274 | uniforms: {
275 | uProjectionMatrix: camera.projectionMatrix,
276 | uViewMatrix: camera.viewMatrix,
277 | },
278 | });
279 | }
280 |
281 | meshes.filter(Boolean).forEach((mesh) => {
282 | mat4.fromRotationTranslationScale(
283 | mesh.modelMatrix,
284 | mesh.rotation,
285 | mesh.translation,
286 | mesh.scale,
287 | );
288 |
289 | mat4.multiply(mesh.modelViewMatrix, camera.viewMatrix, mesh.modelMatrix);
290 |
291 | mat4.invert(inverseModelViewMatrix, mesh.modelViewMatrix);
292 | mat3.fromMat4(mesh.normalMatrix, inverseModelViewMatrix);
293 | mat3.transpose(mesh.normalMatrix, mesh.normalMatrix);
294 |
295 | const isLine =
296 | !mesh.geometry.normals || mesh.quads || CONFIG.mode === "wireframe";
297 | ctx.submit(!isLine ? drawCmd : drawLinesCmd, {
298 | attributes: mesh.attributes,
299 | indices: isLine ? mesh.edges : mesh.indices,
300 | uniforms: {
301 | uMode: modeOptions.findIndex((o) => o === CONFIG.mode),
302 | uProjectionMatrix: camera.projectionMatrix,
303 | uViewMatrix: camera.viewMatrix,
304 | uInverseViewMatrix: camera.inverseViewMatrix,
305 | uNormalMatrix: mesh.normalMatrix,
306 | uModelMatrix: mesh.modelMatrix,
307 | },
308 | });
309 |
310 | if (CONFIG.bbox) {
311 | unitBox.positionsBuffer ||= ctx.vertexBuffer(unitBox.positions);
312 | unitBox.colorsBuffer ||= ctx.vertexBuffer(unitBox.positions.map(() => 1));
313 | unitBox.indicesBuffer ||= ctx.indexBuffer(unitBox.edges);
314 | ctx.submit(drawLinesCmd, {
315 | attributes: {
316 | aPosition: unitBox.positionsBuffer,
317 | aColor: unitBox.colorsBuffer,
318 | },
319 | indices: unitBox.indicesBuffer,
320 | uniforms: {
321 | uOpacity: 0.2,
322 | uMode: modeOptions.findIndex((o) => o === CONFIG.mode),
323 | uProjectionMatrix: camera.projectionMatrix,
324 | uViewMatrix: camera.viewMatrix,
325 | uInverseViewMatrix: camera.inverseViewMatrix,
326 | uNormalMatrix: mesh.normalMatrix,
327 | uModelMatrix: mesh.modelMatrix,
328 | },
329 | });
330 |
331 | mesh.bboxPositions ||= ctx.vertexBuffer(mesh.bbox);
332 | mesh.bboxColors ||= ctx.vertexBuffer(mesh.bbox.map((p) => p * 0.5 + 0.5));
333 |
334 | ctx.submit(drawLinesCmd, {
335 | attributes: {
336 | aPosition: mesh.bboxPositions,
337 | aColor: mesh.bboxColors,
338 | },
339 | indices: bboxCells,
340 | uniforms: {
341 | uMode: modeOptions.findIndex((o) => o === CONFIG.mode),
342 | uProjectionMatrix: camera.projectionMatrix,
343 | uViewMatrix: camera.viewMatrix,
344 | uInverseViewMatrix: camera.inverseViewMatrix,
345 | uNormalMatrix: mesh.normalMatrix,
346 | uModelMatrix: mesh.modelMatrix,
347 | },
348 | });
349 | }
350 |
351 | if (CONFIG.normals && mesh.geometry.normals && !mesh.quads) {
352 | mesh.normalsAttributes ||= {
353 | aPosition: ctx.vertexBuffer(
354 | typedArrayInterleave(
355 | Float32Array,
356 | [3, 3],
357 | mesh.geometry.positions,
358 | mesh.geometry.positions.map(
359 | (p, i) => p + mesh.geometry.normals[i] * 0.1,
360 | ),
361 | ),
362 | ),
363 | aColor: ctx.vertexBuffer(
364 | typedArrayInterleave(
365 | Float32Array,
366 | [3, 3],
367 | mesh.geometry.normals.map((p) => p * 0.5 + 0.5),
368 | mesh.geometry.normals.map((p) => p * 0.5 + 0.5),
369 | ),
370 | ),
371 | };
372 |
373 | mesh.normalsIndices ||= ctx.indexBuffer(
374 | new Uint32Array((mesh.geometry.positions.length / 3) * 2).map(
375 | (_, i) => i,
376 | ),
377 | );
378 |
379 | ctx.submit(drawLinesCmd, {
380 | attributes: mesh.normalsAttributes,
381 | indices: mesh.normalsIndices,
382 | uniforms: {
383 | uMode: modeOptions.findIndex((o) => o === CONFIG.mode),
384 | uProjectionMatrix: camera.projectionMatrix,
385 | uViewMatrix: camera.viewMatrix,
386 | uInverseViewMatrix: camera.inverseViewMatrix,
387 | uNormalMatrix: mesh.normalMatrix,
388 | uModelMatrix: mesh.modelMatrix,
389 | },
390 | });
391 | }
392 |
393 | ctx.submit(!isLine ? drawCmd : drawLinesCmd, {
394 | attributes: mesh.attributes,
395 | indices: isLine ? mesh.edges : mesh.indices,
396 | uniforms: {
397 | uMode: modeOptions.findIndex((o) => o === CONFIG.mode),
398 | uProjectionMatrix: camera.projectionMatrix,
399 | uViewMatrix: camera.viewMatrix,
400 | uInverseViewMatrix: camera.inverseViewMatrix,
401 | uNormalMatrix: mesh.normalMatrix,
402 | uModelMatrix: mesh.modelMatrix,
403 | },
404 | });
405 | });
406 | });
407 |
408 | function computeEdges(positions, cells, stride = 3) {
409 | const edges = new (Primitives.utils.getCellsTypedArray(positions.length / 3))(
410 | cells.length * 2,
411 | );
412 |
413 | let cellIndex = 0;
414 |
415 | for (let i = 0; i < cells.length; i += stride) {
416 | for (let j = 0; j < stride; j++) {
417 | const a = cells[i + j];
418 | const b = cells[i + ((j + 1) % stride)];
419 | edges[cellIndex] = Math.min(a, b);
420 | edges[cellIndex + 1] = Math.max(a, b);
421 | cellIndex += 2;
422 | }
423 | }
424 | return edges;
425 | }
426 |
427 | const setGeometries = (geometries) => {
428 | console.table(geometries);
429 |
430 | // Create the meshes for rendering
431 | meshes = geometries.map(
432 | (geometry) =>
433 | geometry && {
434 | modelMatrix: mat4.create(),
435 | modelViewMatrix: mat4.create(),
436 | normalMatrix: mat3.create(),
437 | rotation: [0, 0, 0, 1],
438 | translation: [0, 0, 0],
439 | scale: [1, 1, 1],
440 | geometry,
441 | quads: geometry.quads,
442 | bbox: aabb
443 | .getCorners(
444 | aabb.fromPoints(
445 | aabb.create(),
446 | Array.from(
447 | { length: geometry.positions.length / 3 },
448 | (_, index) =>
449 | geometry.positions.slice(index * 3, index * 3 + 3),
450 | ),
451 | ),
452 | )
453 | .flat(),
454 | edges: ctx.indexBuffer(
455 | geometry.edges || computeEdges(geometry.positions, geometry.cells),
456 | ),
457 | attributes: geometry.normals
458 | ? {
459 | aPosition: ctx.vertexBuffer(geometry.positions),
460 | aNormal: ctx.vertexBuffer(geometry.normals),
461 | aUv: ctx.vertexBuffer(geometry.uvs),
462 | aColor: ctx.vertexBuffer(
463 | geometry.normals.map((p) => p * 0.5 + 0.5),
464 | ),
465 | }
466 | : {
467 | aPosition: ctx.vertexBuffer(geometry.positions),
468 | aColor: ctx.vertexBuffer(
469 | geometry.positions.map((p) => p * 0.5 + 0.5),
470 | ),
471 | },
472 | indices: ctx.indexBuffer(geometry.cells),
473 | },
474 | );
475 | console.log(meshes);
476 |
477 | // Position them
478 | const offset = 1.5;
479 | const { gridSize } = meshes.reduce(
480 | (current, mesh) => {
481 | if (mesh) {
482 | current.count++;
483 | } else {
484 | current.count = 0;
485 | }
486 | current.gridSize = Math.max(current.gridSize, current.count);
487 | return current;
488 | },
489 | { gridSize: 0, count: 0 },
490 | );
491 |
492 | const halfSize = (gridSize - 1) * 0.5;
493 | let i = 0;
494 | meshes.forEach((mesh) => {
495 | if (!mesh) {
496 | if (i % gridSize !== 0) i += gridSize - (i % gridSize);
497 | return;
498 | }
499 | mesh.translation = [
500 | (i % gridSize) * offset - halfSize * offset,
501 | 0,
502 | ~~(i / gridSize) * offset,
503 | ];
504 | i++;
505 | });
506 | const halfGridSize = meshes.at(-1).translation[2] * 0.5;
507 | meshes.forEach((mesh) => mesh && (mesh.translation[2] -= halfGridSize));
508 | };
509 |
510 | export {
511 | CONFIG,
512 | setGeometries,
513 | computeEdges,
514 | modeOptions,
515 | pane,
516 | controls,
517 | camera,
518 | };
519 |
--------------------------------------------------------------------------------
/examples/uv.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/examples/uv.jpg
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | primitive-geometry by Damien Seguin (https://github.com/dmnsgn)
9 |
10 |
37 |
38 |
39 |
40 | primitive-geometry
41 |
42 |
43 |
44 |
45 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Re-export all geometries, UV mappings functions and utils.
3 | * @module index
4 | */
5 |
6 | export { default as box } from "./src/box.js";
7 | export { default as circle } from "./src/circle.js";
8 |
9 | export { default as quad } from "./src/quad.js";
10 | export { default as plane } from "./src/plane.js";
11 | export { default as roundedRectangle } from "./src/rounded-rectangle.js";
12 | export { default as stadium } from "./src/stadium.js";
13 |
14 | export { default as ellipse } from "./src/ellipse.js";
15 | export { default as disc } from "./src/disc.js";
16 | export { default as superellipse } from "./src/superellipse.js";
17 | export { default as squircle } from "./src/squircle.js";
18 | export { default as annulus } from "./src/annulus.js";
19 | export { default as reuleux } from "./src/reuleux.js";
20 |
21 | export { default as cube } from "./src/cube.js";
22 | export { default as roundedCube } from "./src/rounded-cube.js";
23 |
24 | export { default as sphere } from "./src/sphere.js";
25 | export { default as icosphere } from "./src/icosphere.js";
26 | export { default as ellipsoid } from "./src/ellipsoid.js";
27 |
28 | export { default as cylinder } from "./src/cylinder.js";
29 | export { default as cone } from "./src/cone.js";
30 | export { default as capsule } from "./src/capsule.js";
31 | export { default as torus } from "./src/torus.js";
32 |
33 | export { default as tetrahedron } from "./src/tetrahedron.js";
34 | export { default as icosahedron } from "./src/icosahedron.js";
35 |
36 | export * as mappings from "./src/mappings.js";
37 |
38 | export * as utils from "./src/utils.js";
39 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "primitive-geometry",
3 | "version": "2.11.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "primitive-geometry",
9 | "version": "2.11.0",
10 | "funding": [
11 | {
12 | "type": "individual",
13 | "url": "https://paypal.me/dmnsgn"
14 | },
15 | {
16 | "type": "individual",
17 | "url": "https://commerce.coinbase.com/checkout/56cbdf28-e323-48d8-9c98-7019e72c97f3"
18 | }
19 | ],
20 | "license": "MIT",
21 | "devDependencies": {
22 | "async-preloader": "^8.0.4",
23 | "cameras": "^3.1.1",
24 | "es-module-shims": "^2.5.1",
25 | "gl-matrix": "^3.4.3",
26 | "pex-context": "^3.2.0",
27 | "pex-geom": "^3.0.2",
28 | "tweakpane": "^4.0.5",
29 | "typed-array-interleave": "^2.0.1"
30 | },
31 | "engines": {
32 | "node": ">=22.0.0",
33 | "npm": ">=10.5.1",
34 | "snowdev": ">=2.3.x"
35 | }
36 | },
37 | "node_modules/async-preloader": {
38 | "version": "8.0.4",
39 | "resolved": "https://registry.npmjs.org/async-preloader/-/async-preloader-8.0.4.tgz",
40 | "integrity": "sha512-7RXL6f/aFQ4/sVZP3WXW9VroKb2QmDEHfJPRyZXllDXp349IoPrFqHvc+L1u4+sk9Asr6U1WA2xVRYTbqwtJxw==",
41 | "dev": true,
42 | "funding": [
43 | {
44 | "type": "individual",
45 | "url": "https://paypal.me/dmnsgn"
46 | },
47 | {
48 | "type": "individual",
49 | "url": "https://commerce.coinbase.com/checkout/56cbdf28-e323-48d8-9c98-7019e72c97f3"
50 | }
51 | ],
52 | "license": "MIT",
53 | "dependencies": {
54 | "fontfaceobserver-es": "^3.3.3",
55 | "tslib": "^2.6.3"
56 | },
57 | "engines": {
58 | "node": ">=22.0.0",
59 | "npm": ">=10.5.1",
60 | "snowdev": ">=2.2.x"
61 | }
62 | },
63 | "node_modules/cameras": {
64 | "version": "3.1.1",
65 | "resolved": "https://registry.npmjs.org/cameras/-/cameras-3.1.1.tgz",
66 | "integrity": "sha512-hm8z4IIMgz8ygz6PGVb9l+29qBuXHahzlw2CNsVKq8i09RnylhsrlLwoCZRuik3vbl0i1w2hJFfS0OBVh1WDNQ==",
67 | "dev": true,
68 | "funding": [
69 | {
70 | "type": "individual",
71 | "url": "https://paypal.me/dmnsgn"
72 | },
73 | {
74 | "type": "individual",
75 | "url": "https://commerce.coinbase.com/checkout/56cbdf28-e323-48d8-9c98-7019e72c97f3"
76 | }
77 | ],
78 | "dependencies": {
79 | "clamp": "^1.0.1",
80 | "gl-matrix": "^3.3.0",
81 | "normalize-wheel": "^1.0.1"
82 | },
83 | "engines": {
84 | "node": ">=15.0.0",
85 | "npm": ">=7.0.0"
86 | }
87 | },
88 | "node_modules/clamp": {
89 | "version": "1.0.1",
90 | "resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz",
91 | "integrity": "sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ=",
92 | "dev": true
93 | },
94 | "node_modules/es-module-shims": {
95 | "version": "2.5.1",
96 | "resolved": "https://registry.npmjs.org/es-module-shims/-/es-module-shims-2.5.1.tgz",
97 | "integrity": "sha512-rx57z9aYi2XWJggkIoR0knHT2yr9kQ1vyXkva+oMNlplJr46Aj/1CI6c+GHGxEH6ljzoEhtAYxk080U5NpUpmg==",
98 | "dev": true,
99 | "license": "MIT"
100 | },
101 | "node_modules/fontfaceobserver-es": {
102 | "version": "3.3.3",
103 | "resolved": "https://registry.npmjs.org/fontfaceobserver-es/-/fontfaceobserver-es-3.3.3.tgz",
104 | "integrity": "sha512-1EyweL1ryy0koZWk80Kr0UgWq9KhbOl2cD6x1O8BtFu0gNqCWIXK1lVFSUb37DI5FMn1m86I6X7N1JRBy8wVfw==",
105 | "dev": true
106 | },
107 | "node_modules/gl-matrix": {
108 | "version": "3.4.3",
109 | "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
110 | "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==",
111 | "dev": true
112 | },
113 | "node_modules/normalize-wheel": {
114 | "version": "1.0.1",
115 | "resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
116 | "integrity": "sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU=",
117 | "dev": true
118 | },
119 | "node_modules/pex-context": {
120 | "version": "3.2.0",
121 | "resolved": "https://registry.npmjs.org/pex-context/-/pex-context-3.2.0.tgz",
122 | "integrity": "sha512-yM7XS0SCbWWM/QbonhNnwVxD58ZDjcOJ1VwONeJGj0kFoMHkJskoAFSJH3i4fGAEte6YiHt361LX9D/waP1IAw==",
123 | "dev": true,
124 | "license": "MIT",
125 | "dependencies": {
126 | "pex-gl": "^3.0.2"
127 | },
128 | "engines": {
129 | "node": ">=22.0.0",
130 | "npm": ">=10.5.1",
131 | "snowdev": ">=2.3.x"
132 | }
133 | },
134 | "node_modules/pex-geom": {
135 | "version": "3.0.2",
136 | "resolved": "https://registry.npmjs.org/pex-geom/-/pex-geom-3.0.2.tgz",
137 | "integrity": "sha512-jLx3ISMMrVuDouPvkmN1hLTqeJE/45otdNVIuoMHKxcz7LZb8k9+XuV7VmsAPA+/TySJyvO+gVvFopUVVG40jg==",
138 | "dev": true,
139 | "license": "MIT",
140 | "dependencies": {
141 | "pex-math": "^4.1.1"
142 | },
143 | "engines": {
144 | "node": ">=22.0.0",
145 | "npm": ">=10.5.1",
146 | "snowdev": ">=2.2.x"
147 | }
148 | },
149 | "node_modules/pex-gl": {
150 | "version": "3.0.2",
151 | "resolved": "https://registry.npmjs.org/pex-gl/-/pex-gl-3.0.2.tgz",
152 | "integrity": "sha512-q0MEUXyRlXEa7A/QGQn7GvrkLf+Yc+tRpTRFW0gmbG1RnJpKC0U504WYaB9O47OoMmy9LlKGCaolP2cJJUw5gQ==",
153 | "dev": true,
154 | "license": "MIT",
155 | "engines": {
156 | "node": ">=22.0.0",
157 | "npm": ">=10.5.1",
158 | "snowdev": ">=2.2.x"
159 | }
160 | },
161 | "node_modules/pex-math": {
162 | "version": "4.1.1",
163 | "resolved": "https://registry.npmjs.org/pex-math/-/pex-math-4.1.1.tgz",
164 | "integrity": "sha512-jhFPZfgdhUczx8EJsV7PwFTDjvhDMhNiabtK+rOLsfkWl33LNV97hUYB0URlMiTf9UR4nNpPcTLuha96aniyQg==",
165 | "dev": true,
166 | "license": "MIT",
167 | "engines": {
168 | "node": ">=22.0.0",
169 | "npm": ">=10.5.1",
170 | "snowdev": ">=2.2.x"
171 | }
172 | },
173 | "node_modules/tslib": {
174 | "version": "2.6.3",
175 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
176 | "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
177 | "dev": true,
178 | "license": "0BSD"
179 | },
180 | "node_modules/tweakpane": {
181 | "version": "4.0.5",
182 | "resolved": "https://registry.npmjs.org/tweakpane/-/tweakpane-4.0.5.tgz",
183 | "integrity": "sha512-rxEXdSI+ArlG1RyO6FghC4ZUX8JkEfz8F3v1JuteXSV0pEtHJzyo07fcDG+NsJfN5L39kSbCYbB9cBGHyuI/tQ==",
184 | "dev": true,
185 | "license": "MIT",
186 | "funding": {
187 | "url": "https://github.com/sponsors/cocopon"
188 | }
189 | },
190 | "node_modules/typed-array-interleave": {
191 | "version": "2.0.1",
192 | "resolved": "https://registry.npmjs.org/typed-array-interleave/-/typed-array-interleave-2.0.1.tgz",
193 | "integrity": "sha512-RTidx5Oq8GSiRPhNs57IU6fhlD9XU6uQGqPLSZCS5tHnAuIBxYsQI/NjKe2D07n0QIq6A+hURNgOVG4ECSFokQ==",
194 | "dev": true,
195 | "funding": [
196 | {
197 | "type": "individual",
198 | "url": "https://paypal.me/dmnsgn"
199 | },
200 | {
201 | "type": "individual",
202 | "url": "https://commerce.coinbase.com/checkout/56cbdf28-e323-48d8-9c98-7019e72c97f3"
203 | }
204 | ],
205 | "license": "MIT",
206 | "engines": {
207 | "node": ">=22.0.0",
208 | "npm": ">=10.5.1",
209 | "snowdev": ">=2.2.x"
210 | }
211 | }
212 | },
213 | "dependencies": {
214 | "async-preloader": {
215 | "version": "8.0.4",
216 | "resolved": "https://registry.npmjs.org/async-preloader/-/async-preloader-8.0.4.tgz",
217 | "integrity": "sha512-7RXL6f/aFQ4/sVZP3WXW9VroKb2QmDEHfJPRyZXllDXp349IoPrFqHvc+L1u4+sk9Asr6U1WA2xVRYTbqwtJxw==",
218 | "dev": true,
219 | "requires": {
220 | "fontfaceobserver-es": "^3.3.3",
221 | "tslib": "^2.6.3"
222 | }
223 | },
224 | "cameras": {
225 | "version": "3.1.1",
226 | "resolved": "https://registry.npmjs.org/cameras/-/cameras-3.1.1.tgz",
227 | "integrity": "sha512-hm8z4IIMgz8ygz6PGVb9l+29qBuXHahzlw2CNsVKq8i09RnylhsrlLwoCZRuik3vbl0i1w2hJFfS0OBVh1WDNQ==",
228 | "dev": true,
229 | "requires": {
230 | "clamp": "^1.0.1",
231 | "gl-matrix": "^3.3.0",
232 | "normalize-wheel": "^1.0.1"
233 | }
234 | },
235 | "clamp": {
236 | "version": "1.0.1",
237 | "resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz",
238 | "integrity": "sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ=",
239 | "dev": true
240 | },
241 | "es-module-shims": {
242 | "version": "2.5.1",
243 | "resolved": "https://registry.npmjs.org/es-module-shims/-/es-module-shims-2.5.1.tgz",
244 | "integrity": "sha512-rx57z9aYi2XWJggkIoR0knHT2yr9kQ1vyXkva+oMNlplJr46Aj/1CI6c+GHGxEH6ljzoEhtAYxk080U5NpUpmg==",
245 | "dev": true
246 | },
247 | "fontfaceobserver-es": {
248 | "version": "3.3.3",
249 | "resolved": "https://registry.npmjs.org/fontfaceobserver-es/-/fontfaceobserver-es-3.3.3.tgz",
250 | "integrity": "sha512-1EyweL1ryy0koZWk80Kr0UgWq9KhbOl2cD6x1O8BtFu0gNqCWIXK1lVFSUb37DI5FMn1m86I6X7N1JRBy8wVfw==",
251 | "dev": true
252 | },
253 | "gl-matrix": {
254 | "version": "3.4.3",
255 | "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
256 | "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==",
257 | "dev": true
258 | },
259 | "normalize-wheel": {
260 | "version": "1.0.1",
261 | "resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
262 | "integrity": "sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU=",
263 | "dev": true
264 | },
265 | "pex-context": {
266 | "version": "3.2.0",
267 | "resolved": "https://registry.npmjs.org/pex-context/-/pex-context-3.2.0.tgz",
268 | "integrity": "sha512-yM7XS0SCbWWM/QbonhNnwVxD58ZDjcOJ1VwONeJGj0kFoMHkJskoAFSJH3i4fGAEte6YiHt361LX9D/waP1IAw==",
269 | "dev": true,
270 | "requires": {
271 | "pex-gl": "^3.0.2"
272 | }
273 | },
274 | "pex-geom": {
275 | "version": "3.0.2",
276 | "resolved": "https://registry.npmjs.org/pex-geom/-/pex-geom-3.0.2.tgz",
277 | "integrity": "sha512-jLx3ISMMrVuDouPvkmN1hLTqeJE/45otdNVIuoMHKxcz7LZb8k9+XuV7VmsAPA+/TySJyvO+gVvFopUVVG40jg==",
278 | "dev": true,
279 | "requires": {
280 | "pex-math": "^4.1.1"
281 | }
282 | },
283 | "pex-gl": {
284 | "version": "3.0.2",
285 | "resolved": "https://registry.npmjs.org/pex-gl/-/pex-gl-3.0.2.tgz",
286 | "integrity": "sha512-q0MEUXyRlXEa7A/QGQn7GvrkLf+Yc+tRpTRFW0gmbG1RnJpKC0U504WYaB9O47OoMmy9LlKGCaolP2cJJUw5gQ==",
287 | "dev": true
288 | },
289 | "pex-math": {
290 | "version": "4.1.1",
291 | "resolved": "https://registry.npmjs.org/pex-math/-/pex-math-4.1.1.tgz",
292 | "integrity": "sha512-jhFPZfgdhUczx8EJsV7PwFTDjvhDMhNiabtK+rOLsfkWl33LNV97hUYB0URlMiTf9UR4nNpPcTLuha96aniyQg==",
293 | "dev": true
294 | },
295 | "tslib": {
296 | "version": "2.6.3",
297 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
298 | "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
299 | "dev": true
300 | },
301 | "tweakpane": {
302 | "version": "4.0.5",
303 | "resolved": "https://registry.npmjs.org/tweakpane/-/tweakpane-4.0.5.tgz",
304 | "integrity": "sha512-rxEXdSI+ArlG1RyO6FghC4ZUX8JkEfz8F3v1JuteXSV0pEtHJzyo07fcDG+NsJfN5L39kSbCYbB9cBGHyuI/tQ==",
305 | "dev": true
306 | },
307 | "typed-array-interleave": {
308 | "version": "2.0.1",
309 | "resolved": "https://registry.npmjs.org/typed-array-interleave/-/typed-array-interleave-2.0.1.tgz",
310 | "integrity": "sha512-RTidx5Oq8GSiRPhNs57IU6fhlD9XU6uQGqPLSZCS5tHnAuIBxYsQI/NjKe2D07n0QIq6A+hURNgOVG4ECSFokQ==",
311 | "dev": true
312 | }
313 | }
314 | }
315 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "primitive-geometry",
3 | "version": "2.11.0",
4 | "description": "Geometries for 3D rendering, including normals, UVs and cell indices (faces). Perfect if you want to supercharge your dependency folder... with 30KB of geometries.",
5 | "keywords": [
6 | "simplicial",
7 | "complex",
8 | "mesh",
9 | "geom",
10 | "geometry",
11 | "primitive",
12 | "stackgl",
13 | "glsl",
14 | "webgl",
15 | "gl",
16 | "quad",
17 | "plane",
18 | "cube",
19 | "rounded-cube",
20 | "cylinder",
21 | "cone",
22 | "capsule",
23 | "sphere",
24 | "icosphere",
25 | "ellipsoid",
26 | "torus",
27 | "platonic-solid",
28 | "tetrahedron",
29 | "icosahedron",
30 | "ellipse",
31 | "disc",
32 | "squircle",
33 | "superellipse",
34 | "reuleux",
35 | "box",
36 | "circle"
37 | ],
38 | "homepage": "https://github.com/dmnsgn/primitive-geometry",
39 | "bugs": "https://github.com/dmnsgn/primitive-geometry/issues",
40 | "repository": {
41 | "type": "git",
42 | "url": "git+https://github.com/dmnsgn/primitive-geometry.git"
43 | },
44 | "funding": [
45 | {
46 | "type": "individual",
47 | "url": "https://paypal.me/dmnsgn"
48 | },
49 | {
50 | "type": "individual",
51 | "url": "https://commerce.coinbase.com/checkout/56cbdf28-e323-48d8-9c98-7019e72c97f3"
52 | }
53 | ],
54 | "license": "MIT",
55 | "author": "Damien Seguin (https://github.com/dmnsgn)",
56 | "type": "module",
57 | "exports": {
58 | ".": {
59 | "types": "./types/index.d.ts",
60 | "default": "./index.js"
61 | }
62 | },
63 | "main": "index.js",
64 | "types": "types/index.d.ts",
65 | "scripts": {
66 | "build": "npx snowdev build",
67 | "deps": "npx snowdev install",
68 | "dev": "npx snowdev dev"
69 | },
70 | "devDependencies": {
71 | "async-preloader": "^8.0.4",
72 | "cameras": "^3.1.1",
73 | "es-module-shims": "^2.5.1",
74 | "gl-matrix": "^3.4.3",
75 | "pex-context": "^3.2.0",
76 | "pex-geom": "^3.0.2",
77 | "tweakpane": "^4.0.5",
78 | "typed-array-interleave": "^2.0.1"
79 | },
80 | "engines": {
81 | "node": ">=22.0.0",
82 | "npm": ">=10.5.1",
83 | "snowdev": ">=2.3.x"
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/primitive-geometry/36966fdd65921cb6dae505c5be99ff69bce238cb/screenshot.gif
--------------------------------------------------------------------------------
/src/annulus.js:
--------------------------------------------------------------------------------
1 | /** @module annulus */
2 | import ellipse from "./ellipse.js";
3 | import { concentric } from "./mappings.js";
4 | import { checkArguments, TAU } from "./utils.js";
5 |
6 | /**
7 | * @typedef {object} AnnulusOptions
8 | * @property {number} [sx=1]
9 | * @property {number} [sy=1]
10 | * @property {number} [radius=0.5]
11 | * @property {number} [segments=32]
12 | * @property {number} [innerSegments=16]
13 | * @property {number} [theta=TAU]
14 | * @property {number} [thetaOffset=0]
15 | * @property {number} [innerRadius=radius * 0.5]
16 | * @property {Function} [mapping=mappings.concentric]
17 | */
18 |
19 | /**
20 | * @alias module:annulus
21 | * @param {AnnulusOptions} [options={}]
22 | * @returns {import("../types.js").SimplicialComplex}
23 | */
24 | function annulus({
25 | sx = 1,
26 | sy = 1,
27 | radius = 0.5,
28 | segments = 32,
29 | innerSegments = 16,
30 | theta = TAU,
31 | thetaOffset = 0,
32 | innerRadius = radius * 0.5,
33 | mapping = concentric,
34 | } = {}) {
35 | checkArguments(arguments);
36 |
37 | return ellipse({
38 | sx,
39 | sy,
40 | radius,
41 | segments,
42 | innerSegments,
43 | theta,
44 | thetaOffset,
45 | innerRadius,
46 | mergeCentroid: false,
47 | mapping,
48 | });
49 | }
50 |
51 | export default annulus;
52 |
--------------------------------------------------------------------------------
/src/box.js:
--------------------------------------------------------------------------------
1 | /** @module box */
2 | import { checkArguments } from "./utils.js";
3 |
4 | /**
5 | * @typedef {object} BoxOptions
6 | * @property {number} [sx=1]
7 | * @property {number} [sy=sx]
8 | * @property {number} [sz=sx]
9 | */
10 |
11 | /**
12 | * @alias module:box
13 | * @param {BoxOptions} [options={}]
14 | * @returns {import("../types.js").BasicSimplicialComplex}
15 | */
16 | function box({ sx = 1, sy = sx, sz = sx } = {}) {
17 | checkArguments(arguments);
18 |
19 | const x = sx / 2;
20 | const y = sy / 2;
21 | const z = sz / 2;
22 |
23 | return {
24 | // prettier-ignore
25 | positions: Float32Array.of(
26 | -x, y, z,
27 | -x, -y, z,
28 | x, -y, z,
29 | x, y, z,
30 |
31 | // -z
32 | x, y, -z,
33 | x, -y, -z,
34 | -x, -y, -z,
35 | -x, y, -z,
36 | ),
37 | // prettier-ignore
38 | cells: Uint8Array.of(
39 | 0, 1, 2, 3, // +z
40 | 3, 2, 5, 4, // +x
41 | 4, 5, 6, 7, // -z
42 | 7, 6, 1, 0, // -x
43 | 7, 0, 3, 4, // +y
44 | 1, 6, 5, 2, // -y
45 | ),
46 | };
47 | }
48 |
49 | export default box;
50 |
--------------------------------------------------------------------------------
/src/capsule.js:
--------------------------------------------------------------------------------
1 | /** @module capsule */
2 | import { checkArguments, getCellsTypedArray, TAU } from "./utils.js";
3 |
4 | /**
5 | * @typedef {object} CapsuleOptions
6 | * @property {number} [height=0.5]
7 | * @property {number} [radius=0.25]
8 | * @property {number} [nx=16]
9 | * @property {number} [ny=1]
10 | * @property {number} [roundSegments=32]
11 | * @property {number} [phi=TAU]
12 | */
13 |
14 | /**
15 | * @alias module:capsule
16 | * @param {CapsuleOptions} [options={}]
17 | * @returns {import("../types.js").SimplicialComplex}
18 | */
19 |
20 | function capsule({
21 | height = 0.5,
22 | radius = 0.25,
23 | nx = 16,
24 | ny = 1,
25 | roundSegments = 16,
26 | phi = TAU,
27 | } = {}) {
28 | checkArguments(arguments);
29 |
30 | const ringsBody = ny + 1;
31 | const ringsCap = roundSegments * 2;
32 | const ringsTotal = ringsCap + ringsBody;
33 |
34 | const size = ringsTotal * nx;
35 |
36 | const positions = new Float32Array(size * 3);
37 | const normals = new Float32Array(size * 3);
38 | const uvs = new Float32Array(size * 2);
39 | const cells = new (getCellsTypedArray(size))((ringsTotal - 1) * (nx - 1) * 6);
40 |
41 | let vertexIndex = 0;
42 | let cellIndex = 0;
43 |
44 | const segmentIncrement = 1 / (nx - 1);
45 | const ringIncrement = 1 / (ringsCap - 1);
46 | const bodyIncrement = 1 / (ringsBody - 1);
47 |
48 | function computeRing(r, y, dy) {
49 | for (let s = 0; s < nx; s++, vertexIndex++) {
50 | const x = -Math.cos(s * segmentIncrement * phi) * r;
51 | const z = Math.sin(s * segmentIncrement * phi) * r;
52 |
53 | const py = radius * y + height * dy;
54 |
55 | positions[vertexIndex * 3] = radius * x;
56 | positions[vertexIndex * 3 + 1] = py;
57 | positions[vertexIndex * 3 + 2] = radius * z;
58 |
59 | normals[vertexIndex * 3] = x;
60 | normals[vertexIndex * 3 + 1] = y;
61 | normals[vertexIndex * 3 + 2] = z;
62 |
63 | uvs[vertexIndex * 2] = s * segmentIncrement;
64 | uvs[vertexIndex * 2 + 1] = 1 - (0.5 - py / (2 * radius + height));
65 | }
66 | }
67 |
68 | for (let r = 0; r < roundSegments; r++) {
69 | computeRing(
70 | Math.sin(Math.PI * r * ringIncrement),
71 | Math.sin(Math.PI * (r * ringIncrement - 0.5)),
72 | -0.5,
73 | );
74 | }
75 |
76 | for (let r = 0; r < ringsBody; r++) {
77 | computeRing(1, 0, r * bodyIncrement - 0.5);
78 | }
79 |
80 | for (let r = roundSegments; r < ringsCap; r++) {
81 | computeRing(
82 | Math.sin(Math.PI * r * ringIncrement),
83 | Math.sin(Math.PI * (r * ringIncrement - 0.5)),
84 | 0.5,
85 | );
86 | }
87 |
88 | for (let r = 0; r < ringsTotal - 1; r++) {
89 | for (let s = 0; s < nx - 1; s++) {
90 | const a = r * nx;
91 | const b = (r + 1) * nx;
92 | const s1 = s + 1;
93 | cells[cellIndex] = a + s;
94 | cells[cellIndex + 1] = a + s1;
95 | cells[cellIndex + 2] = b + s1;
96 |
97 | cells[cellIndex + 3] = a + s;
98 | cells[cellIndex + 4] = b + s1;
99 | cells[cellIndex + 5] = b + s;
100 |
101 | cellIndex += 6;
102 | }
103 | }
104 |
105 | return {
106 | positions,
107 | normals,
108 | uvs,
109 | cells,
110 | };
111 | }
112 |
113 | export default capsule;
114 |
--------------------------------------------------------------------------------
/src/circle.js:
--------------------------------------------------------------------------------
1 | /** @module circle */
2 | import { checkArguments, getCellsTypedArray, TAU } from "./utils.js";
3 |
4 | /**
5 | * @typedef {object} CircleOptions
6 | * @property {number} [radius=0.5]
7 | * @property {number} [segments=32]
8 | * @property {number} [theta=TAU]
9 | * @property {number} [thetaOffset=0]
10 | * @property {boolean} [closed=false]
11 | */
12 |
13 | /**
14 | * @alias module:circle
15 | * @param {CircleOptions} [options={}]
16 | * @returns {import("../types.js").BasicSimplicialComplex}
17 | */
18 | function circle({
19 | radius = 0.5,
20 | segments = 32,
21 | theta = TAU,
22 | thetaOffset = 0,
23 | closed = false,
24 | } = {}) {
25 | checkArguments(arguments);
26 |
27 | const positions = new Float32Array(segments * 3);
28 | const cells = new (getCellsTypedArray(segments))(
29 | (segments - (closed ? 0 : 1)) * 2,
30 | );
31 |
32 | for (let i = 0; i < segments; i++) {
33 | const t = (i / segments) * theta + thetaOffset;
34 | positions[i * 3] = radius * Math.cos(t);
35 | positions[i * 3 + 1] = radius * Math.sin(t);
36 |
37 | if (i > 0) {
38 | cells[(i - 1) * 2] = i - 1;
39 | cells[(i - 1) * 2 + 1] = i;
40 | }
41 | }
42 |
43 | if (closed) {
44 | cells[(segments - 1) * 2] = segments - 1;
45 | cells[(segments - 1) * 2 + 1] = 0;
46 | }
47 |
48 | return { positions, cells };
49 | }
50 |
51 | export default circle;
52 |
--------------------------------------------------------------------------------
/src/cone.js:
--------------------------------------------------------------------------------
1 | /** @module cone */
2 | import cylinder from "./cylinder.js";
3 | import { checkArguments } from "./utils.js";
4 |
5 | /**
6 | * @typedef {object} ConeOptions
7 | * @property {number} [height=1]
8 | * @property {number} [radius=0.25]
9 | * @property {number} [nx=16]
10 | * @property {number} [ny=1]
11 | * @property {number} [capSegments=1]
12 | * @property {boolean} [capBase=true]
13 | * @property {number} [phi=TAU]
14 | */
15 |
16 | /**
17 | * @alias module:cone
18 | * @param {ConeOptions} [options={}]
19 | * @returns {import("../types.js").SimplicialComplex}
20 | */
21 | function cone({ height, radius, nx, ny, capSegments, capBase, phi } = {}) {
22 | checkArguments(arguments);
23 |
24 | return cylinder({
25 | height,
26 | radius,
27 | nx,
28 | ny,
29 | capSegments,
30 | capBase,
31 | phi,
32 |
33 | radiusApex: 0,
34 | capApex: false,
35 | });
36 | }
37 |
38 | export default cone;
39 |
--------------------------------------------------------------------------------
/src/cube.js:
--------------------------------------------------------------------------------
1 | /** @module cube */
2 |
3 | import { checkArguments, computePlane, getCellsTypedArray } from "./utils.js";
4 |
5 | /**
6 | * @typedef {object} CubeOptions
7 | * @property {number} [sx=1]
8 | * @property {number} [sy=sx]
9 | * @property {number} [sz=sx]
10 | * @property {number} [nx=1]
11 | * @property {number} [ny=nx]
12 | * @property {number} [nz=nx]
13 | */
14 |
15 | /**
16 | * @alias module:cube
17 | * @param {CubeOptions} [options={}]
18 | * @returns {import("../types.js").SimplicialComplex}
19 | */
20 | function cube({ sx = 1, sy = sx, sz = sx, nx = 1, ny = nx, nz = nx } = {}) {
21 | checkArguments(arguments);
22 |
23 | const size =
24 | (nx + 1) * (ny + 1) * 2 + (nx + 1) * (nz + 1) * 2 + (nz + 1) * (ny + 1) * 2;
25 |
26 | const geometry = {
27 | positions: new Float32Array(size * 3),
28 | normals: new Float32Array(size * 3),
29 | uvs: new Float32Array(size * 2),
30 | cells: new (getCellsTypedArray(size))(
31 | (nx * ny * 2 + nx * nz * 2 + nz * ny * 2) * 6,
32 | ),
33 | };
34 |
35 | const halfSX = sx * 0.5;
36 | const halfSY = sy * 0.5;
37 | const halfSZ = sz * 0.5;
38 |
39 | const indices = { vertex: 0, cell: 0 };
40 |
41 | computePlane(geometry, indices, sx, sy, nx, ny, "z", halfSZ);
42 | computePlane(geometry, indices, sx, sy, nx, ny, "-z", -halfSZ);
43 | computePlane(geometry, indices, sz, sy, nz, ny, "-x", -halfSX);
44 | computePlane(geometry, indices, sz, sy, nz, ny, "x", halfSX);
45 | computePlane(geometry, indices, sx, sz, nx, nz, "y", halfSY);
46 | computePlane(geometry, indices, sx, sz, nx, nz, "-y", -halfSY);
47 |
48 | return geometry;
49 | }
50 |
51 | export default cube;
52 |
--------------------------------------------------------------------------------
/src/cylinder.js:
--------------------------------------------------------------------------------
1 | /** @module cylinder */
2 | import {
3 | checkArguments,
4 | getCellsTypedArray,
5 | normalize,
6 | TAU,
7 | TMP,
8 | } from "./utils.js";
9 |
10 | /**
11 | * @typedef {object} CylinderOptions
12 | * @property {number} [height=1]
13 | * @property {number} [radius=0.25]
14 | * @property {number} [nx=16]
15 | * @property {number} [ny=1]
16 | * @property {number} [radiusApex=radius]
17 | * @property {number} [capSegments=1]
18 | * @property {boolean} [capApex=true]
19 | * @property {boolean} [capBase=true]
20 | * @property {number} [phi=TAU]
21 | */
22 |
23 | /**
24 | * @alias module:cylinder
25 | * @param {CylinderOptions} [options={}]
26 | * @returns {import("../types.js").SimplicialComplex}
27 | */
28 | function cylinder({
29 | height = 1,
30 | radius = 0.25,
31 | nx = 16,
32 | ny = 1,
33 |
34 | radiusApex = radius,
35 | capSegments = 1,
36 | capApex = true,
37 | capBase = true,
38 | capBaseSegments = capSegments,
39 | phi = TAU,
40 | } = {}) {
41 | checkArguments(arguments);
42 |
43 | let capCount = 0;
44 | if (capApex) capCount += capSegments;
45 | if (capBase) capCount += capBaseSegments;
46 |
47 | const segments = nx + 1;
48 | const slices = ny + 1;
49 |
50 | const size = segments * slices + segments * 2 * capCount;
51 |
52 | const positions = new Float32Array(size * 3);
53 | const normals = new Float32Array(size * 3);
54 | const uvs = new Float32Array(size * 2);
55 | const cells = new (getCellsTypedArray(size))((nx * ny + nx * capCount) * 6);
56 |
57 | let vertexIndex = 0;
58 | let cellIndex = 0;
59 |
60 | const halfHeight = height / 2;
61 | const segmentIncrement = 1 / (segments - 1);
62 | const ringIncrement = 1 / (slices - 1);
63 |
64 | for (let i = 0; i < segments; i++) {
65 | const u = i * segmentIncrement;
66 |
67 | for (let j = 0; j < slices; j++) {
68 | const v = j * ringIncrement;
69 | const p = u * phi;
70 | const cosPhi = -Math.cos(p);
71 | const sinPhi = Math.sin(p);
72 |
73 | const r = radius * (1 - v) + radiusApex * v;
74 | positions[vertexIndex * 3] = r * cosPhi;
75 | positions[vertexIndex * 3 + 1] = height * v - halfHeight;
76 | positions[vertexIndex * 3 + 2] = r * sinPhi;
77 |
78 | TMP[0] = height * cosPhi;
79 | TMP[1] = radius - radiusApex;
80 | TMP[2] = height * sinPhi;
81 | normalize(TMP);
82 |
83 | normals[vertexIndex * 3] = TMP[0];
84 | normals[vertexIndex * 3 + 1] = TMP[1];
85 | normals[vertexIndex * 3 + 2] = TMP[2];
86 |
87 | uvs[vertexIndex * 2] = u;
88 | uvs[vertexIndex * 2 + 1] = v;
89 |
90 | vertexIndex++;
91 | }
92 | }
93 |
94 | for (let j = 0; j < slices - 1; j++) {
95 | for (let i = 0; i < segments - 1; i++) {
96 | cells[cellIndex + 0] = (i + 0) * slices + (j + 0);
97 | cells[cellIndex + 1] = (i + 1) * slices + (j + 0);
98 | cells[cellIndex + 2] = (i + 1) * slices + (j + 1);
99 |
100 | cells[cellIndex + 3] = (i + 0) * slices + (j + 0);
101 | cells[cellIndex + 4] = (i + 1) * slices + (j + 1);
102 | cells[cellIndex + 5] = (i + 0) * slices + (j + 1);
103 |
104 | cellIndex += 6;
105 | }
106 | }
107 |
108 | function computeCap(flip, height, radius, capSegments) {
109 | const index = vertexIndex;
110 |
111 | const segmentIncrement = 1 / (segments - 1);
112 | for (let r = 0; r < capSegments; r++) {
113 | for (let i = 0; i < segments; i++) {
114 | const p = i * segmentIncrement * phi;
115 | const cosPhi = -Math.cos(p);
116 | const sinPhi = Math.sin(p);
117 |
118 | // inner point
119 | positions[vertexIndex * 3] = (radius * cosPhi * r) / capSegments;
120 | positions[vertexIndex * 3 + 1] = height;
121 | positions[vertexIndex * 3 + 2] = (radius * sinPhi * r) / capSegments;
122 |
123 | normals[vertexIndex * 3 + 1] = -flip;
124 |
125 | uvs[vertexIndex * 2] = (0.5 * cosPhi * r) / capSegments + 0.5;
126 | uvs[vertexIndex * 2 + 1] = (0.5 * sinPhi * r) / capSegments + 0.5;
127 |
128 | vertexIndex++;
129 |
130 | // outer point
131 | positions[vertexIndex * 3] = (radius * cosPhi * (r + 1)) / capSegments;
132 | positions[vertexIndex * 3 + 1] = height;
133 | positions[vertexIndex * 3 + 2] =
134 | (radius * sinPhi * (r + 1)) / capSegments;
135 |
136 | normals[vertexIndex * 3 + 1] = -flip;
137 |
138 | uvs[vertexIndex * 2] = (0.5 * (cosPhi * (r + 1))) / capSegments + 0.5;
139 | uvs[vertexIndex * 2 + 1] =
140 | (0.5 * (sinPhi * (r + 1))) / capSegments + 0.5;
141 |
142 | vertexIndex++;
143 | }
144 | }
145 |
146 | for (let r = 0; r < capSegments; r++) {
147 | for (let i = 0; i < segments - 1; i++) {
148 | const n = index + r * segments * 2 + i * 2;
149 | const a = n + 0;
150 | const b = n + 1;
151 | const c = n + 2;
152 | const d = n + 3;
153 |
154 | if (flip === 1) {
155 | cells[cellIndex] = a;
156 | cells[cellIndex + 1] = c;
157 | cells[cellIndex + 2] = d;
158 |
159 | cells[cellIndex + 3] = a;
160 | cells[cellIndex + 4] = d;
161 | cells[cellIndex + 5] = b;
162 | } else {
163 | cells[cellIndex + 0] = a;
164 | cells[cellIndex + 1] = d;
165 | cells[cellIndex + 2] = c;
166 |
167 | cells[cellIndex + 3] = a;
168 | cells[cellIndex + 4] = b;
169 | cells[cellIndex + 5] = d;
170 | }
171 | cellIndex += 6;
172 | }
173 | }
174 | }
175 |
176 | if (capBase) computeCap(1, -halfHeight, radius, capBaseSegments);
177 | if (capApex) computeCap(-1, halfHeight, radiusApex, capSegments);
178 |
179 | return {
180 | positions,
181 | normals,
182 | uvs,
183 | cells,
184 | };
185 | }
186 |
187 | export default cylinder;
188 |
--------------------------------------------------------------------------------
/src/disc.js:
--------------------------------------------------------------------------------
1 | /** @module disc */
2 | import ellipse from "./ellipse.js";
3 | import { concentric } from "./mappings.js";
4 | import { checkArguments, TAU } from "./utils.js";
5 |
6 | /**
7 | * @typedef {object} DiscOptions
8 | * @property {number} [radius=0.5]
9 | * @property {number} [segments=32]
10 | * @property {number} [innerSegments=16]
11 | * @property {number} [theta=TAU]
12 | * @property {number} [thetaOffset=0]
13 | * @property {boolean} [mergeCentroid=true]
14 | * @property {Function} [mapping=mappings.concentric]
15 | */
16 |
17 | /**
18 | * @alias module:disc
19 | * @param {DiscOptions} [options={}]
20 | * @returns {import("../types.js").SimplicialComplex}
21 | */
22 | function disc({
23 | radius = 0.5,
24 | segments = 32,
25 | innerSegments = 16,
26 | theta = TAU,
27 | thetaOffset = 0,
28 | mergeCentroid = true,
29 | mapping = concentric,
30 | } = {}) {
31 | checkArguments(arguments);
32 |
33 | return ellipse({
34 | sx: 1,
35 | sy: 1,
36 | radius,
37 | segments,
38 | innerSegments,
39 | theta,
40 | thetaOffset,
41 | mergeCentroid,
42 | mapping,
43 | });
44 | }
45 |
46 | export default disc;
47 |
--------------------------------------------------------------------------------
/src/ellipse.js:
--------------------------------------------------------------------------------
1 | /** @module ellipse */
2 | import { elliptical } from "./mappings.js";
3 | import { checkArguments, getCellsTypedArray, TAU } from "./utils.js";
4 |
5 | /**
6 | * @typedef {object} EllipseOptions
7 | * @property {number} [sx=1]
8 | * @property {number} [sy=0.5]
9 | * @property {number} [radius=0.5]
10 | * @property {number} [segments=32]
11 | * @property {number} [innerSegments=16]
12 | * @property {number} [theta=TAU]
13 | * @property {number} [thetaOffset=0]
14 | * @property {boolean} [mergeCentroid=true]
15 | * @property {Function} [mapping=mappings.elliptical]
16 | */
17 |
18 | /**
19 | * @alias module:ellipse
20 | * @param {EllipseOptions} [options={}]
21 | * @returns {import("../types.js").SimplicialComplex}
22 | */
23 | function ellipse({
24 | sx = 1,
25 | sy = 0.5,
26 | radius = 0.5,
27 | segments = 32,
28 | innerSegments = 16,
29 | theta = TAU,
30 | thetaOffset = 0,
31 | innerRadius = 0,
32 | mergeCentroid = true,
33 | mapping = elliptical,
34 | equation = ({ rx, ry, cosTheta, sinTheta }) => [rx * cosTheta, ry * sinTheta],
35 | } = {}) {
36 | checkArguments(arguments);
37 |
38 | const size = mergeCentroid
39 | ? 1 + (segments + 1) + (innerSegments - 1) * (segments + 1)
40 | : (segments + 1) * (innerSegments + 1);
41 |
42 | const positions = new Float32Array(size * 3);
43 | const normals = new Float32Array(size * 3);
44 | const uvs = new Float32Array(size * 2);
45 | const cells = new (getCellsTypedArray(size))(
46 | mergeCentroid
47 | ? segments * 3 + (innerSegments - 1) * segments * 6
48 | : size * 6,
49 | );
50 |
51 | if (mergeCentroid) {
52 | normals[2] = 1;
53 | uvs[0] = 0.5;
54 | uvs[1] = 0.5;
55 | }
56 |
57 | let vertexIndex = mergeCentroid ? 1 : 0;
58 | let cellIndex = 0;
59 |
60 | for (let j = vertexIndex; j <= innerSegments; j++) {
61 | const radiusRatio = j / innerSegments;
62 |
63 | const r = innerRadius + (radius - innerRadius) * radiusRatio;
64 |
65 | for (let i = 0; i <= segments; i++, vertexIndex++) {
66 | const thetaRatio = i / segments;
67 | const t = thetaOffset + thetaRatio * theta;
68 |
69 | const cosTheta = Math.cos(t);
70 | const sinTheta = Math.sin(t);
71 |
72 | const [x, y] = equation({
73 | rx: sx * r,
74 | ry: sy * r,
75 | cosTheta,
76 | sinTheta,
77 | s: radiusRatio,
78 | t,
79 | });
80 |
81 | positions[vertexIndex * 3] = x;
82 | positions[vertexIndex * 3 + 1] = y;
83 |
84 | normals[vertexIndex * 3 + 2] = 1;
85 |
86 | mapping({
87 | uvs,
88 | index: vertexIndex * 2,
89 | u: radiusRatio * cosTheta,
90 | v: radiusRatio * sinTheta,
91 | radius,
92 | radiusRatio,
93 | thetaRatio,
94 | t,
95 | // For rectangular
96 | x,
97 | y,
98 | sx,
99 | sy,
100 | });
101 |
102 | if (i < segments) {
103 | if (mergeCentroid && j === 1) {
104 | cells[cellIndex] = i + 1;
105 | cells[cellIndex + 1] = i + 2;
106 |
107 | cellIndex += 3;
108 | } else {
109 | let a;
110 |
111 | if (mergeCentroid) {
112 | a = 1 + (j - 2) * (segments + 1) + i;
113 | } else if (j < innerSegments) {
114 | a = j * (segments + 1) + i;
115 | }
116 |
117 | if (a !== undefined) {
118 | const b = a + segments + 1;
119 | const c = a + segments + 2;
120 | const d = a + 1;
121 |
122 | cells[cellIndex] = a;
123 | cells[cellIndex + 1] = b;
124 | cells[cellIndex + 2] = d;
125 |
126 | cells[cellIndex + 3] = b;
127 | cells[cellIndex + 4] = c;
128 | cells[cellIndex + 5] = d;
129 |
130 | cellIndex += 6;
131 | }
132 | }
133 | }
134 | }
135 | }
136 |
137 | return { positions, normals, uvs, cells };
138 | }
139 |
140 | export default ellipse;
141 |
--------------------------------------------------------------------------------
/src/ellipsoid.js:
--------------------------------------------------------------------------------
1 | /** @module ellipsoid */
2 | import {
3 | checkArguments,
4 | getCellsTypedArray,
5 | normalize,
6 | TAU,
7 | TMP,
8 | } from "./utils.js";
9 |
10 | /**
11 | * @typedef {object} EllipsoidOptions
12 | * @property {number} [radius=0.5]
13 | * @property {number} [nx=32]
14 | * @property {number} [ny=16]
15 | * @property {number} [rx=1]
16 | * @property {number} [ry=0.5]
17 | * @property {number} [rz=ry]
18 | * @property {number} [theta=Math.PI]
19 | * @property {number} [thetaOffset=0]
20 | * @property {number} [phi=TAU]
21 | * @property {number} [phiOffset=0]
22 | */
23 |
24 | /**
25 | * Default to an oblate spheroid.
26 | * @alias module:ellipsoid
27 | * @param {EllipsoidOptions} [options={}]
28 | * @returns {import("../types.js").SimplicialComplex}
29 | */
30 | function ellipsoid({
31 | radius = 1,
32 | nx = 32,
33 | ny = 16,
34 | rx = 0.5,
35 | ry = 0.25,
36 | rz = ry,
37 | theta = Math.PI,
38 | thetaOffset = 0,
39 | phi = TAU,
40 | phiOffset = 0,
41 | } = {}) {
42 | checkArguments(arguments);
43 |
44 | const size = (ny + 1) * (nx + 1);
45 |
46 | const positions = new Float32Array(size * 3);
47 | const normals = new Float32Array(size * 3);
48 | const uvs = new Float32Array(size * 2);
49 | const cells = new (getCellsTypedArray(size))(ny * nx * 6);
50 |
51 | let vertexIndex = 0;
52 | let cellIndex = 0;
53 |
54 | for (let y = 0; y <= ny; y++) {
55 | const v = y / ny;
56 | const t = v * theta + thetaOffset;
57 | const cosTheta = Math.cos(t);
58 | const sinTheta = Math.sin(t);
59 |
60 | for (let x = 0; x <= nx; x++) {
61 | const u = x / nx;
62 | const p = u * phi + phiOffset;
63 | const cosPhi = Math.cos(p);
64 | const sinPhi = Math.sin(p);
65 |
66 | TMP[0] = -rx * cosPhi * sinTheta;
67 | TMP[1] = -ry * cosTheta;
68 | TMP[2] = rz * sinPhi * sinTheta;
69 |
70 | positions[vertexIndex * 3] = radius * TMP[0];
71 | positions[vertexIndex * 3 + 1] = radius * TMP[1];
72 | positions[vertexIndex * 3 + 2] = radius * TMP[2];
73 |
74 | normalize(TMP);
75 |
76 | normals[vertexIndex * 3] = TMP[0];
77 | normals[vertexIndex * 3 + 1] = TMP[1];
78 | normals[vertexIndex * 3 + 2] = TMP[2];
79 |
80 | uvs[vertexIndex * 2] = u;
81 | uvs[vertexIndex * 2 + 1] = v;
82 |
83 | vertexIndex++;
84 | }
85 |
86 | if (y > 0) {
87 | for (let i = vertexIndex - 2 * (nx + 1); i + nx + 2 < vertexIndex; i++) {
88 | const a = i;
89 | const b = i + 1;
90 | const c = i + nx + 1;
91 | const d = i + nx + 2;
92 | cells[cellIndex] = a;
93 | cells[cellIndex + 1] = b;
94 | cells[cellIndex + 2] = c;
95 |
96 | cells[cellIndex + 3] = c;
97 | cells[cellIndex + 4] = b;
98 | cells[cellIndex + 5] = d;
99 |
100 | cellIndex += 6;
101 | }
102 | }
103 | }
104 |
105 | return {
106 | positions,
107 | normals,
108 | uvs,
109 | cells,
110 | };
111 | }
112 |
113 | export default ellipsoid;
114 |
--------------------------------------------------------------------------------
/src/icosahedron.js:
--------------------------------------------------------------------------------
1 | /** @module icosahedron */
2 | import icosphere from "./icosphere.js";
3 | import { checkArguments } from "./utils.js";
4 |
5 | /**
6 | * @typedef {object} IcosahedronOptions
7 | * @property {number} [radius=0.5]
8 | */
9 |
10 | /**
11 | * @alias module:icosahedron
12 | * @param {IcosahedronOptions} [options={}]
13 | * @returns {import("../types.js").SimplicialComplex}
14 | */
15 | function icosahedron({ radius } = {}) {
16 | checkArguments(arguments);
17 |
18 | return icosphere({ subdivisions: 0, radius });
19 | }
20 |
21 | export default icosahedron;
22 |
--------------------------------------------------------------------------------
/src/icosphere.js:
--------------------------------------------------------------------------------
1 | /** @module icosphere */
2 | import { checkArguments, getCellsTypedArray, TAU } from "./utils.js";
3 |
4 | const f = 0.5 + Math.sqrt(5) / 2;
5 |
6 | /**
7 | * @typedef {object} IcosphereOptions
8 | * @property {number} [radius=0.5]
9 | * @property {number} [subdivisions=2]
10 | */
11 |
12 | /**
13 | * @alias module:icosphere
14 | * @param {IcosphereOptions} [options={}]
15 | * @returns {import("../types.js").SimplicialComplex}
16 | */
17 | function icosphere({ radius = 0.5, subdivisions = 2 } = {}) {
18 | checkArguments(arguments);
19 |
20 | if (subdivisions > 10) throw new Error("Max subdivisions is 10.");
21 |
22 | const T = Math.pow(4, subdivisions);
23 |
24 | const numVertices = 10 * T + 2;
25 | const numDuplicates =
26 | subdivisions === 0 ? 3 : Math.pow(2, subdivisions) * 3 + 9;
27 |
28 | const size = numVertices + numDuplicates;
29 |
30 | const positions = new Float32Array(size * 3);
31 | const uvs = new Float32Array(size * 2);
32 |
33 | // prettier-ignore
34 | positions.set(Float32Array.of(
35 | -1, f, 0,
36 | 1, f, 0,
37 | -1, -f, 0,
38 | 1, -f, 0,
39 |
40 | 0, -1, f,
41 | 0, 1, f,
42 | 0, -1, -f,
43 | 0, 1, -f,
44 |
45 | f, 0, -1,
46 | f, 0, 1,
47 | -f, 0, -1,
48 | -f, 0, 1,
49 | ));
50 | // prettier-ignore
51 | let cells = Uint16Array.of(
52 | 0, 11, 5,
53 | 0, 5, 1,
54 | 0, 1, 7,
55 | 0, 7, 10,
56 | 0, 10, 11,
57 |
58 | 11, 10, 2,
59 | 5, 11, 4,
60 | 1, 5, 9,
61 | 7, 1, 8,
62 | 10, 7, 6,
63 |
64 | 3, 9, 4,
65 | 3, 4, 2,
66 | 3, 2, 6,
67 | 3, 6, 8,
68 | 3, 8, 9,
69 |
70 | 9, 8, 1,
71 | 4, 9, 5,
72 | 2, 4, 11,
73 | 6, 2, 10,
74 | 8, 6, 7,
75 | );
76 |
77 | let vertexIndex = 12;
78 |
79 | const midCache = subdivisions ? {} : null;
80 |
81 | function addMidPoint(a, b) {
82 | // Cantor's pairing function
83 | const key = Math.floor(((a + b) * (a + b + 1)) / 2 + Math.min(a, b));
84 | const i = midCache[key];
85 | if (i !== undefined) {
86 | delete midCache[key];
87 | return i;
88 | }
89 | midCache[key] = vertexIndex;
90 | positions[3 * vertexIndex + 0] =
91 | (positions[3 * a + 0] + positions[3 * b + 0]) * 0.5;
92 | positions[3 * vertexIndex + 1] =
93 | (positions[3 * a + 1] + positions[3 * b + 1]) * 0.5;
94 | positions[3 * vertexIndex + 2] =
95 | (positions[3 * a + 2] + positions[3 * b + 2]) * 0.5;
96 | return vertexIndex++;
97 | }
98 |
99 | let cellsPrev = cells;
100 | const IndexArray = subdivisions > 5 ? Uint32Array : getCellsTypedArray(size);
101 |
102 | // Subdivide
103 | for (let i = 0; i < subdivisions; i++) {
104 | const prevLen = cellsPrev.length;
105 | cells = new IndexArray(prevLen * 4);
106 |
107 | for (let k = 0; k < prevLen; k += 3) {
108 | const v1 = cellsPrev[k + 0];
109 | const v2 = cellsPrev[k + 1];
110 | const v3 = cellsPrev[k + 2];
111 |
112 | const a = addMidPoint(v1, v2);
113 | const b = addMidPoint(v2, v3);
114 | const c = addMidPoint(v3, v1);
115 |
116 | cells[k * 4 + 0] = v1;
117 | cells[k * 4 + 1] = a;
118 | cells[k * 4 + 2] = c;
119 |
120 | cells[k * 4 + 3] = v2;
121 | cells[k * 4 + 4] = b;
122 | cells[k * 4 + 5] = a;
123 |
124 | cells[k * 4 + 6] = v3;
125 | cells[k * 4 + 7] = c;
126 | cells[k * 4 + 8] = b;
127 |
128 | cells[k * 4 + 9] = a;
129 | cells[k * 4 + 10] = b;
130 | cells[k * 4 + 11] = c;
131 | }
132 | cellsPrev = cells;
133 | }
134 |
135 | // Normalize
136 | for (let i = 0; i < numVertices * 3; i += 3) {
137 | const v1 = positions[i + 0];
138 | const v2 = positions[i + 1];
139 | const v3 = positions[i + 2];
140 | const m = 1 / Math.sqrt(v1 * v1 + v2 * v2 + v3 * v3);
141 | positions[i + 0] *= m;
142 | positions[i + 1] *= m;
143 | positions[i + 2] *= m;
144 | }
145 |
146 | for (let i = 0; i < numVertices; i++) {
147 | uvs[2 * i + 0] =
148 | -Math.atan2(positions[3 * i + 2], positions[3 * i]) / TAU + 0.5;
149 | uvs[2 * i + 1] = Math.asin(positions[3 * i + 1]) / Math.PI + 0.5;
150 | }
151 |
152 | const duplicates = {};
153 | function addDuplicate(i, uvx, uvy, cached) {
154 | if (cached) {
155 | const dupe = duplicates[i];
156 | if (dupe !== undefined) return dupe;
157 | }
158 | positions[3 * vertexIndex + 0] = positions[3 * i + 0];
159 | positions[3 * vertexIndex + 1] = positions[3 * i + 1];
160 | positions[3 * vertexIndex + 2] = positions[3 * i + 2];
161 |
162 | uvs[2 * vertexIndex + 0] = uvx;
163 | uvs[2 * vertexIndex + 1] = uvy;
164 |
165 | if (cached) duplicates[i] = vertexIndex;
166 | return vertexIndex++;
167 | }
168 |
169 | for (let i = 0; i < cells.length; i += 3) {
170 | const a = cells[i + 0];
171 | const b = cells[i + 1];
172 | const c = cells[i + 2];
173 |
174 | let ax = uvs[2 * a];
175 | let bx = uvs[2 * b];
176 | let cx = uvs[2 * c];
177 |
178 | const ay = uvs[2 * a + 1];
179 | const by = uvs[2 * b + 1];
180 | const cy = uvs[2 * c + 1];
181 |
182 | if (ax - bx >= 0.5 && ay !== 1) bx += 1;
183 | if (bx - cx > 0.5) cx += 1;
184 | if ((ax < 0.5 && cx - ax > 0.5) || (ax === 1 && cy === 0)) ax += 1;
185 | if (bx < 0.5 && ax - bx > 0.5) bx += 1;
186 |
187 | // Poles
188 | const isPoleA = ay === 0 || ay === 1;
189 | const isPoleB = by === 0 || by === 1;
190 | const isPoleC = cy === 0 || cy === 1;
191 |
192 | if (isPoleA) {
193 | ax = (bx + cx) * 0.5;
194 | if (ay === 1 - bx) {
195 | uvs[2 * a] = ax;
196 | } else {
197 | cells[i + 0] = addDuplicate(a, ax, ay, false);
198 | }
199 | } else if (isPoleB) {
200 | bx = (ax + cx) * 0.5;
201 | if (by === ax) {
202 | uvs[2 * b] = bx;
203 | } else {
204 | cells[i + 1] = addDuplicate(b, bx, by, false);
205 | }
206 | } else if (isPoleC) {
207 | cx = (ax + bx) * 0.5;
208 | if (cy === ax) {
209 | uvs[2 * c] = cx;
210 | } else {
211 | cells[i + 2] = addDuplicate(c, cx, cy, false);
212 | }
213 | }
214 |
215 | // Seam zipper
216 | if (ax !== uvs[2 * a] && !isPoleA) {
217 | cells[i + 0] = addDuplicate(a, ax, ay, true);
218 | }
219 | if (bx !== uvs[2 * b] && !isPoleB) {
220 | cells[i + 1] = addDuplicate(b, bx, by, true);
221 | }
222 | if (cx !== uvs[2 * c] && !isPoleC) {
223 | cells[i + 2] = addDuplicate(c, cx, cy, true);
224 | }
225 | }
226 |
227 | return {
228 | positions: positions.map((v) => v * radius),
229 | normals: positions,
230 | uvs,
231 | cells,
232 | };
233 | }
234 |
235 | export default icosphere;
236 |
--------------------------------------------------------------------------------
/src/mappings.js:
--------------------------------------------------------------------------------
1 | /** @module mappings */
2 | import { HALF_PI, SQRT2, TAU } from "./utils.js";
3 |
4 | const safeSqrt = (x) => Math.sqrt(Math.max(x, 0));
5 | const safeDivide = (x, y) => x / (y + Number.EPSILON);
6 | const isNegligeable = (x) => Math.abs(x) < Number.EPSILON * 2;
7 |
8 | const remapRectangular = (x, radius) => (x / radius + 1) / 2;
9 | const remap = (x) => (x + 1) / 2; // From [-1, 1] to [0, 1]
10 |
11 | export function rectangular({ uvs, index, x, y, radius, sx = 1, sy = 1 }) {
12 | uvs[index] = remapRectangular(x, radius * sx);
13 | uvs[index + 1] = remapRectangular(y, radius * sy);
14 | }
15 |
16 | export function polar({ uvs, index, radiusRatio, thetaRatio }) {
17 | uvs[index] = radiusRatio;
18 | uvs[index + 1] = thetaRatio;
19 | }
20 |
21 | // Basic
22 | export function radial({ uvs, index, u, v, radius }) {
23 | const x = safeDivide(
24 | Math.sqrt(u ** 2 + v ** 2),
25 | Math.max(Math.abs(u), Math.abs(v)),
26 | );
27 |
28 | uvs[index] = remap(x * u, radius);
29 | uvs[index + 1] = remap(x * v, radius);
30 | }
31 |
32 | const FOUR_OVER_PI = 4 / Math.PI;
33 |
34 | export function concentric({ uvs, index, u, v, radius }) {
35 | const u2 = u ** 2;
36 | const v2 = v ** 2;
37 | const x = Math.sqrt(u2 + v2);
38 |
39 | if (u2 > v2) {
40 | uvs[index] = remap(x * Math.sign(u), radius);
41 | uvs[index + 1] = remap(
42 | x * (FOUR_OVER_PI * Math.atan(safeDivide(v, Math.abs(u)))),
43 | radius,
44 | );
45 | } else {
46 | uvs[index] = remap(
47 | x * (FOUR_OVER_PI * Math.atan(safeDivide(u, Math.abs(v)))),
48 | radius,
49 | );
50 | uvs[index + 1] = remap(x * Math.sign(v), radius);
51 | }
52 | }
53 | export function lamé({ uvs, index, u, v, radius }) {
54 | const u2 = u ** 2;
55 | const v2 = v ** 2;
56 | uvs[index] = remap(Math.sign(u) * Math.abs(u) ** (1 - u2 - v2), radius);
57 | uvs[index + 1] = remap(Math.sign(v) * Math.abs(v) ** (1 - u2 - v2), radius);
58 | }
59 | export function elliptical({ uvs, index, u, v, radius }) {
60 | const t = u ** 2 - v ** 2;
61 | const pu1 = 0.5 * safeSqrt(2 + t + 2 * SQRT2 * u);
62 | const pu2 = 0.5 * safeSqrt(2 + t - 2 * SQRT2 * u);
63 | const pv1 = 0.5 * safeSqrt(2 - t + 2 * SQRT2 * v);
64 | const pv2 = 0.5 * safeSqrt(2 - t - 2 * SQRT2 * v);
65 |
66 | uvs[index] = remap(pu1 - pu2, radius);
67 | uvs[index + 1] = remap(pv1 - pv2, radius);
68 | }
69 |
70 | // Radial, all variations of FG squircular:
71 | function fixFGSingularities(uvs, index, u, v, radius) {
72 | if (isNegligeable(u) || isNegligeable(v)) {
73 | uvs[index] = remap(u, radius);
74 | uvs[index + 1] = remap(v, radius);
75 | } else {
76 | return true;
77 | }
78 | }
79 |
80 | export function fgSquircular({ uvs, index, u, v, radius }) {
81 | const ok = fixFGSingularities(uvs, index, u, v, radius);
82 | if (ok) {
83 | const u2 = u ** 2;
84 | const v2 = v ** 2;
85 | const sign = Math.sign(u * v);
86 | const uv2Sum = u2 + v2;
87 | const sqrtUV = Math.sqrt(
88 | uv2Sum - safeSqrt(uv2Sum * (uv2Sum - 4 * u2 * v2)),
89 | );
90 | uvs[index] = remap((sign / (v * SQRT2)) * sqrtUV, radius);
91 | uvs[index + 1] = remap((sign / (u * SQRT2)) * sqrtUV, radius);
92 | }
93 | }
94 | export function twoSquircular({ uvs, index, u, v, radius }) {
95 | const ok = fixFGSingularities(uvs, index, u, v, radius);
96 | if (ok) {
97 | const sign = Math.sign(u * v);
98 | const sqrtUV = Math.sqrt(1 - safeSqrt(1 - 4 * u ** 2 * v ** 2));
99 | uvs[index] = remap((sign / (v * SQRT2)) * sqrtUV, radius);
100 | uvs[index + 1] = remap((sign / (u * SQRT2)) * sqrtUV, radius);
101 | }
102 | }
103 | export function threeSquircular({ uvs, index, u, v, radius }) {
104 | const ok = fixFGSingularities(uvs, index, u, v, radius);
105 | if (ok) {
106 | const u2 = u ** 2;
107 | const v2 = v ** 2;
108 | const sign = Math.sign(u * v);
109 | const sqrtUV = Math.sqrt(
110 | (1 - safeSqrt(1 - 4 * u ** 4 * v2 - 4 * u2 * v ** 4)) / (2 * (u2 + v2)),
111 | );
112 | uvs[index] = remap((sign / v) * sqrtUV, radius);
113 | uvs[index + 1] = remap((sign / u) * sqrtUV, radius);
114 | }
115 | }
116 | export function cornerificTapered2({ uvs, index, u, v, radius }) {
117 | const ok = fixFGSingularities(uvs, index, u, v, radius);
118 | if (ok) {
119 | const u2 = u ** 2;
120 | const v2 = v ** 2;
121 | const sign = Math.sign(u * v);
122 | const uv2Sum = u2 + v2;
123 | const sqrtUV = Math.sqrt(
124 | (uv2Sum - Math.sqrt(uv2Sum * (uv2Sum - 4 * u2 * v2 * (2 - u2 - v2)))) /
125 | (2 * (2 - u2 - v2)),
126 | );
127 | uvs[index] = remap((sign / v) * sqrtUV, radius);
128 | uvs[index + 1] = remap((sign / u) * sqrtUV, radius);
129 | }
130 | }
131 | export function tapered4({ uvs, index, u, v, radius }) {
132 | const ok = fixFGSingularities(uvs, index, u, v, radius);
133 | if (ok) {
134 | const u2 = u ** 2;
135 | const v2 = v ** 2;
136 | const sign = Math.sign(u * v);
137 | const uv2Sum = u2 + v2;
138 | const divider = 3 - u ** 4 - 2 * u2 * v2 - v ** 4;
139 | const sqrtUV = Math.sqrt(
140 | (uv2Sum - safeSqrt(uv2Sum * (uv2Sum - 2 * u2 * v2 * divider))) / divider,
141 | );
142 | uvs[index] = remap((sign / v) * sqrtUV, radius);
143 | uvs[index + 1] = remap((sign / u) * sqrtUV, radius);
144 | }
145 | }
146 |
147 | // Non-axial
148 | const FOURTH_SQRT2 = 2 ** (1 / 4);
149 |
150 | export function nonAxial2Pinch({ uvs, index, u, v, radius }) {
151 | const u2 = u ** 2;
152 | const v2 = v ** 2;
153 | const sign = Math.sign(u * v);
154 | const uv2Sum = u2 + v2;
155 |
156 | const sqrtUV =
157 | (uv2Sum - 2 * u2 * v2 - safeSqrt((uv2Sum - 4 * u2 * v2) * uv2Sum)) **
158 | (1 / 4);
159 |
160 | if (isNegligeable(v)) {
161 | uvs[index] = remap(Math.sign(u) * Math.sqrt(Math.abs(u)), radius);
162 | uvs[index + 1] = remap(safeDivide(sign, u * FOURTH_SQRT2) * sqrtUV, radius);
163 | } else {
164 | uvs[index] = remap(safeDivide(sign, v * FOURTH_SQRT2) * sqrtUV, radius);
165 | uvs[index + 1] = remap(
166 | isNegligeable(u)
167 | ? Math.sign(v) * Math.sqrt(Math.abs(v))
168 | : safeDivide(sign, u * FOURTH_SQRT2) * sqrtUV,
169 | radius,
170 | );
171 | }
172 | }
173 | export function nonAxialHalfPinch({ uvs, index, u, v, radius }) {
174 | const u2 = u ** 2;
175 | const v2 = v ** 2;
176 | const sign = Math.sign(u * v);
177 | const uv2Sum = u2 + v2;
178 |
179 | const sqrtUV = Math.sqrt(
180 | safeDivide(1 - safeSqrt(1 - 4 * u2 * v2 * uv2Sum ** 2), 2 * uv2Sum),
181 | );
182 |
183 | if (isNegligeable(v)) {
184 | uvs[index] = remap(Math.sign(u) * u2, radius);
185 | uvs[index + 1] = remap(safeDivide(sign, u) * sqrtUV, radius);
186 | } else {
187 | uvs[index] = remap(safeDivide(sign, v) * sqrtUV, radius);
188 | uvs[index + 1] = remap(
189 | isNegligeable(u) ? Math.sign(v) * v2 : safeDivide(sign, u) * sqrtUV,
190 | radius,
191 | );
192 | }
193 | }
194 |
195 | // Variations of elliptical
196 | export function squelched({ uvs, index, u, v, radius, t }) {
197 | uvs[index] = [HALF_PI, TAU - HALF_PI].includes(t)
198 | ? 0.5
199 | : remap(u / Math.sqrt(1 - v ** 2), radius);
200 | uvs[index + 1] = [0, TAU, Math.PI].includes(t)
201 | ? 0.5
202 | : remap(v / Math.sqrt(1 - u ** 2), radius);
203 | }
204 | export function squelchedVertical({ uvs, index, u, v, radius, t }) {
205 | uvs[index] = remap(u, radius);
206 | uvs[index + 1] = [0, TAU, Math.PI].includes(t)
207 | ? 0.5
208 | : remap(v / Math.sqrt(1 - u ** 2), radius);
209 | }
210 | export function squelchedHorizontal({ uvs, index, u, v, radius, t }) {
211 | uvs[index] = [HALF_PI, TAU - HALF_PI].includes(t)
212 | ? 0.5
213 | : remap(u / Math.sqrt(1 - v ** 2), radius);
214 | uvs[index + 1] = remap(v, radius);
215 | }
216 |
--------------------------------------------------------------------------------
/src/plane.js:
--------------------------------------------------------------------------------
1 | /** @module plane */
2 |
3 | import { checkArguments, computePlane, getCellsTypedArray } from "./utils.js";
4 |
5 | /**
6 | * @typedef {object} PlaneOptions
7 | * @property {number} [sx=1]
8 | * @property {number} [sy=sx]
9 | * @property {number} [nx=1]
10 | * @property {number} [ny=nx]
11 | * @property {PlaneDirection} [direction="z"]
12 | * @property {boolean} [quads=false]
13 | */
14 |
15 | /**
16 | * @typedef {"x" | "-x" | "y" | "-y" | "z" | "-z"} PlaneDirection
17 | */
18 |
19 | /**
20 | * @alias module:plane
21 | * @param {PlaneOptions} [options={}]
22 | * @returns {import("../types.js").SimplicialComplex}
23 | */
24 | function plane({
25 | sx = 1,
26 | sy = sx,
27 | nx = 1,
28 | ny = nx,
29 | direction = "z",
30 | quads = false,
31 | } = {}) {
32 | checkArguments(arguments);
33 |
34 | const size = (nx + 1) * (ny + 1);
35 |
36 | return computePlane(
37 | {
38 | positions: new Float32Array(size * 3),
39 | normals: new Float32Array(size * 3),
40 | uvs: new Float32Array(size * 2),
41 | cells: new (getCellsTypedArray(size))(nx * ny * (quads ? 4 : 6)),
42 | },
43 | { vertex: 0, cell: 0 },
44 | sx,
45 | sy,
46 | nx,
47 | ny,
48 | direction,
49 | 0,
50 | quads,
51 | );
52 | }
53 |
54 | export default plane;
55 |
--------------------------------------------------------------------------------
/src/quad.js:
--------------------------------------------------------------------------------
1 | /** @module quad */
2 |
3 | import { checkArguments, getCellsTypedArray } from "./utils.js";
4 |
5 | /**
6 | * @typedef {object} QuadOptions
7 | * @property {number} [scale=0.5]
8 | */
9 |
10 | /**
11 | * @alias module:quad
12 | * @param {QuadOptions} [options={}]
13 | * @returns {import("../types.js").SimplicialComplex}
14 | */
15 | function quad({ scale = 0.5 } = {}) {
16 | checkArguments(arguments);
17 |
18 | return {
19 | // prettier-ignore
20 | positions: Float32Array.of(
21 | -scale, -scale, 0,
22 | scale, -scale, 0,
23 | scale, scale, 0,
24 | -scale, scale, 0,
25 | ),
26 | // prettier-ignore
27 | normals: Int8Array.of(
28 | 0, 0, 1,
29 | 0, 0, 1,
30 | 0, 0, 1,
31 | 0, 0, 1,
32 | ),
33 | // prettier-ignore
34 | uvs: Uint8Array.of(
35 | 0, 0,
36 | 1, 0,
37 | 1, 1,
38 | 0, 1
39 | ),
40 | // prettier-ignore
41 | cells: (getCellsTypedArray(12)).of(
42 | 0, 1, 2,
43 | 2, 3, 0
44 | ),
45 | };
46 | }
47 |
48 | export default quad;
49 |
--------------------------------------------------------------------------------
/src/reuleux.js:
--------------------------------------------------------------------------------
1 | /** @module reuleux */
2 | import ellipse from "./ellipse.js";
3 | import { concentric } from "./mappings.js";
4 | import { checkArguments, TAU } from "./utils.js";
5 |
6 | /**
7 | * @typedef {object} ReuleuxOptions
8 | * @property {number} [radius=0.5]
9 | * @property {number} [segments=32]
10 | * @property {number} [innerSegments=16]
11 | * @property {number} [theta=TAU]
12 | * @property {number} [thetaOffset=0]
13 | * @property {boolean} [mergeCentroid=true]
14 | * @property {Function} [mapping=mappings.concentric]
15 | * @property {number} [n=3]
16 | */
17 |
18 | /**
19 | * @see [Parametric equations for regular and Reuleaux polygons]{@link https://tpfto.wordpress.com/2011/09/15/parametric-equations-for-regular-and-reuleaux-polygons/}
20 | *
21 | * @alias module:reuleux
22 | * @param {ReuleuxOptions} [options={}]
23 | * @returns {import("../types.js").SimplicialComplex}
24 | */
25 | function reuleux({
26 | radius = 0.5,
27 | segments = 32,
28 | innerSegments = 16,
29 | theta = TAU,
30 | thetaOffset = 0,
31 | mergeCentroid = true,
32 | mapping = concentric,
33 | n = 3,
34 | } = {}) {
35 | checkArguments(arguments);
36 |
37 | const cosN = 2 * Math.cos(Math.PI / (2 * n));
38 | const PIoverN = Math.PI / n;
39 |
40 | return ellipse({
41 | sx: 1,
42 | sy: 1,
43 | radius,
44 | segments,
45 | innerSegments,
46 | theta,
47 | thetaOffset,
48 | mergeCentroid,
49 | mapping,
50 | equation: ({ rx, ry, t }) => [
51 | rx *
52 | (cosN *
53 | Math.cos(0.5 * (t + PIoverN * (2 * Math.floor((n * t) / TAU) + 1))) -
54 | Math.cos(PIoverN * (2 * Math.floor((n * t) / TAU) + 1))),
55 | ry *
56 | (cosN *
57 | Math.sin(0.5 * (t + PIoverN * (2 * Math.floor((n * t) / TAU) + 1))) -
58 | Math.sin(PIoverN * (2 * Math.floor((n * t) / TAU) + 1))),
59 | ],
60 | });
61 | }
62 |
63 | export default reuleux;
64 |
--------------------------------------------------------------------------------
/src/rounded-cube.js:
--------------------------------------------------------------------------------
1 | /** @module roundedCube */
2 | import {
3 | checkArguments,
4 | computePlane,
5 | getCellsTypedArray,
6 | normalize,
7 | TMP,
8 | } from "./utils.js";
9 |
10 | /**
11 | * @typedef {object} RoundedCubeOptions
12 | * @property {number} [sx=1]
13 | * @property {number} [sy=sx]
14 | * @property {number} [sz=sx]
15 | * @property {number} [nx=1]
16 | * @property {number} [ny=nx]
17 | * @property {number} [nz=nx]
18 | * @property {number} [radius=sx * 0.25]
19 | * @property {number} [roundSegments=8]
20 | * @property {number} [edgeSegments=1]
21 | */
22 |
23 | /**
24 | * @alias module:roundedCube
25 | * @param {RoundedCubeOptions} [options={}]
26 | * @returns {import("../types.js").SimplicialComplex}
27 | */
28 | function roundedCube({
29 | sx = 1,
30 | sy = sx,
31 | sz = sx,
32 | nx = 1,
33 | ny = nx,
34 | nz = nx,
35 | radius = sx * 0.25,
36 | roundSegments = 8,
37 | edgeSegments = 1,
38 | } = {}) {
39 | checkArguments(arguments);
40 |
41 | const size =
42 | (nx + 1) * (ny + 1) * 2 +
43 | (nx + 1) * (nz + 1) * 2 +
44 | (nz + 1) * (ny + 1) * 2 +
45 | (roundSegments + 1) * (roundSegments + 1) * 24 +
46 | (roundSegments + 1) * (edgeSegments + 1) * 24;
47 |
48 | const geometry = {
49 | positions: new Float32Array(size * 3),
50 | normals: new Float32Array(size * 3),
51 | uvs: new Float32Array(size * 2),
52 | cells: new (getCellsTypedArray(size))(
53 | (nx * ny * 2 +
54 | nx * nz * 2 +
55 | nz * ny * 2 +
56 | roundSegments * roundSegments * 24 +
57 | roundSegments * edgeSegments * 24) *
58 | 6,
59 | ),
60 | };
61 |
62 | const halfSX = sx * 0.5;
63 | const halfSY = sy * 0.5;
64 | const halfSZ = sz * 0.5;
65 |
66 | const r2 = radius * 2;
67 | const widthX = sx - r2;
68 | const widthY = sy - r2;
69 | const widthZ = sz - r2;
70 |
71 | const faceSX = widthX / sx;
72 | const faceSY = widthY / sy;
73 | const faceSZ = widthZ / sz;
74 |
75 | const radiusSX = radius / sx;
76 | const radiusSY = radius / sy;
77 | const radiusSZ = radius / sz;
78 |
79 | const indices = { vertex: 0, cell: 0 };
80 |
81 | const PLANES = [
82 | [
83 | widthX,
84 | widthY,
85 | nx,
86 | ny,
87 | "z",
88 | halfSZ,
89 | [faceSX, faceSY],
90 | [radiusSX, radiusSY],
91 | (x, y) => [x, y, 0],
92 | ],
93 | [
94 | widthX,
95 | widthY,
96 | nx,
97 | ny,
98 | "-z",
99 | -halfSZ,
100 | [faceSX, faceSY],
101 | [radiusSX, radiusSY],
102 | (x, y) => [-x, y, 0],
103 | ],
104 | [
105 | widthZ,
106 | widthY,
107 | nz,
108 | ny,
109 | "-x",
110 | -halfSX,
111 | [faceSZ, faceSY],
112 | [radiusSZ, radiusSY],
113 | (x, y) => [0, y, x],
114 | ],
115 | [
116 | widthZ,
117 | widthY,
118 | nz,
119 | ny,
120 | "x",
121 | halfSX,
122 | [faceSZ, faceSY],
123 | [radiusSZ, radiusSY],
124 | (x, y) => [0, y, -x],
125 | ],
126 | [
127 | widthX,
128 | widthZ,
129 | nx,
130 | nz,
131 | "y",
132 | halfSY,
133 | [faceSX, faceSZ],
134 | [radiusSX, radiusSZ],
135 | (x, y) => [x, 0, -y],
136 | ],
137 | [
138 | widthX,
139 | widthZ,
140 | nx,
141 | nz,
142 | "-y",
143 | -halfSY,
144 | [faceSX, faceSZ],
145 | [radiusSX, radiusSZ],
146 | (x, y) => [x, 0, y],
147 | ],
148 | ];
149 |
150 | const uvOffsetCorner = (su, sv) => [
151 | [0, 0],
152 | [1 - radius / (su + r2), 0],
153 | [1 - radius / (su + r2), 1 - radius / (sv + r2)],
154 | [0, 1 - radius / (sv + r2)],
155 | ];
156 | const uvOffsetStart = (_, sv) => [0, radius / (sv + r2)];
157 | const uvOffsetEnd = (su, sv) => [1 - radius / (su + r2), radius / (sv + r2)];
158 |
159 | for (let j = 0; j < PLANES.length; j++) {
160 | const [su, sv, nu, nv, direction, pw, uvScale, uvOffset, center] =
161 | PLANES[j];
162 |
163 | // Cube faces
164 | computePlane(
165 | geometry,
166 | indices,
167 | su,
168 | sv,
169 | nu,
170 | nv,
171 | direction,
172 | pw,
173 | false,
174 | uvScale,
175 | uvOffset,
176 | );
177 |
178 | // Corner order: ccw uv-like order and L/B (0) R/T (2)
179 | // 0,1 -- 1,1
180 | // | -- |
181 | // 0,0 -- 1,0
182 | for (let i = 0; i < 4; i++) {
183 | const ceil = Math.ceil(i / 2) % 2;
184 | const floor = Math.floor(i / 2) % 2;
185 |
186 | const x = (ceil === 0 ? -1 : 1) * (su + radius) * 0.5;
187 | const y = (floor === 0 ? -1 : 1) * (sv + radius) * 0.5;
188 |
189 | // Corners
190 | computePlane(
191 | geometry,
192 | indices,
193 | radius,
194 | radius,
195 | roundSegments,
196 | roundSegments,
197 | direction,
198 | pw,
199 | false,
200 | [radius / (su + r2), radius / (sv + r2)],
201 | uvOffsetCorner(su, sv)[i],
202 | center(x, y),
203 | );
204 |
205 | // Edges
206 | if (i === 0 || i === 2) {
207 | // Left / Right
208 | computePlane(
209 | geometry,
210 | indices,
211 | radius,
212 | sv,
213 | roundSegments,
214 | edgeSegments,
215 | direction,
216 | pw,
217 | false,
218 | [uvOffset[0], uvScale[1]],
219 | ceil === 0 ? uvOffsetStart(su, sv) : uvOffsetEnd(su, sv),
220 | center(x, 0),
221 | );
222 | // Bottom/Top
223 | computePlane(
224 | geometry,
225 | indices,
226 | su,
227 | radius,
228 | edgeSegments,
229 | roundSegments,
230 | direction,
231 | pw,
232 | false,
233 | [uvScale[0], uvOffset[1]],
234 | floor === 0
235 | ? [...uvOffsetStart(sv, su)].reverse()
236 | : [...uvOffsetEnd(sv, su)].reverse(),
237 | center(0, y),
238 | );
239 | }
240 | }
241 | }
242 |
243 | const rx = widthX * 0.5;
244 | const ry = widthY * 0.5;
245 | const rz = widthZ * 0.5;
246 |
247 | for (let i = 0; i < geometry.positions.length; i += 3) {
248 | const position = [
249 | geometry.positions[i],
250 | geometry.positions[i + 1],
251 | geometry.positions[i + 2],
252 | ];
253 | TMP[0] = position[0];
254 | TMP[1] = position[1];
255 | TMP[2] = position[2];
256 |
257 | if (position[0] < -rx) {
258 | position[0] = -rx;
259 | } else if (position[0] > rx) {
260 | position[0] = rx;
261 | }
262 |
263 | if (position[1] < -ry) {
264 | position[1] = -ry;
265 | } else if (position[1] > ry) {
266 | position[1] = ry;
267 | }
268 |
269 | if (position[2] < -rz) {
270 | position[2] = -rz;
271 | } else if (position[2] > rz) {
272 | position[2] = rz;
273 | }
274 |
275 | TMP[0] -= position[0];
276 | TMP[1] -= position[1];
277 | TMP[2] -= position[2];
278 |
279 | normalize(TMP);
280 |
281 | geometry.normals[i] = TMP[0];
282 | geometry.normals[i + 1] = TMP[1];
283 | geometry.normals[i + 2] = TMP[2];
284 |
285 | geometry.positions[i] = position[0] + radius * TMP[0];
286 | geometry.positions[i + 1] = position[1] + radius * TMP[1];
287 | geometry.positions[i + 2] = position[2] + radius * TMP[2];
288 | }
289 |
290 | return geometry;
291 | }
292 |
293 | export default roundedCube;
294 |
--------------------------------------------------------------------------------
/src/rounded-rectangle.js:
--------------------------------------------------------------------------------
1 | /** @module roundedRectangle */
2 | import {
3 | checkArguments,
4 | computePlane,
5 | getCellsTypedArray,
6 | TMP,
7 | } from "./utils.js";
8 |
9 | /**
10 | * @typedef {object} RoundedCubeOptions
11 | * @property {number} [sx=1]
12 | * @property {number} [sy=sx]
13 | * @property {number} [nx=1]
14 | * @property {number} [ny=nx]
15 | * @property {number} [radius=sx * 0.25]
16 | * @property {number} [roundSegments=8]
17 | * @property {number} [edgeSegments=1]
18 | */
19 |
20 | /**
21 | * @alias module:roundedRectangle
22 | * @param {RoundedCubeOptions} [options={}]
23 | * @returns {import("../types.js").SimplicialComplex}
24 | */
25 | function roundedRectangle({
26 | sx = 1,
27 | sy = sx,
28 | nx = 1,
29 | ny = nx,
30 | radius = sx * 0.25,
31 | roundSegments = 8,
32 | edgeSegments = 1,
33 | } = {}) {
34 | checkArguments(arguments);
35 |
36 | const size =
37 | (nx + 1) * (ny + 1) +
38 | (roundSegments + 1) * (roundSegments + 1) * 4 +
39 | (roundSegments + 1) * (edgeSegments + 1) * 4;
40 |
41 | const geometry = {
42 | positions: new Float32Array(size * 3),
43 | normals: new Float32Array(size * 3),
44 | uvs: new Float32Array(size * 2),
45 | cells: new (getCellsTypedArray(size))(
46 | (nx * ny +
47 | roundSegments * roundSegments * 4 +
48 | roundSegments * edgeSegments * 4) *
49 | 6,
50 | ),
51 | };
52 |
53 | const r2 = radius * 2;
54 | const widthX = sx - r2;
55 | const widthY = sy - r2;
56 |
57 | const faceSX = widthX / sx;
58 | const faceSY = widthY / sy;
59 |
60 | const radiusSX = radius / sx;
61 | const radiusSY = radius / sy;
62 |
63 | const indices = { vertex: 0, cell: 0 };
64 |
65 | const uvOffsetCorner = (su, sv) => [
66 | [radius / (su + r2), 0],
67 | [1 - radius / (su + r2), 0],
68 | [1, 1 - radius / (sv + r2)],
69 | [0, 1 - radius / (sv + r2)],
70 | ];
71 | const uvOffsetStart = (_, sv) => [0, radius / (sv + r2)];
72 | const uvOffsetEnd = (su, sv) => [1 - radius / (su + r2), radius / (sv + r2)];
73 |
74 | const [su, sv, nu, nv, direction, pw, uvScale, uvOffset, center] = [
75 | widthX,
76 | widthY,
77 | nx,
78 | ny,
79 | "z",
80 | 0,
81 | [faceSX, faceSY],
82 | [radiusSX, radiusSY],
83 | (x, y) => [x, y, 0],
84 | ];
85 |
86 | // Plane face
87 | computePlane(
88 | geometry,
89 | indices,
90 | su,
91 | sv,
92 | nu,
93 | nv,
94 | direction,
95 | pw,
96 | false,
97 | uvScale,
98 | uvOffset,
99 | );
100 |
101 | // Corner order: ccw uv-like order and L/B (0) R/T (2)
102 | // 0,1 -- 1,1
103 | // | -- |
104 | // 0,0 -- 1,0
105 | for (let i = 0; i < 4; i++) {
106 | const ceil = Math.ceil(i / 2) % 2;
107 | const floor = Math.floor(i / 2) % 2;
108 |
109 | const x = (ceil === 0 ? -1 : 1) * (su + radius) * 0.5;
110 | const y = (floor === 0 ? -1 : 1) * (sv + radius) * 0.5;
111 |
112 | // Flip for quad seams to be radial
113 | const flip = i % 2 === 0;
114 |
115 | // Corners
116 | computePlane(
117 | geometry,
118 | indices,
119 | radius,
120 | radius,
121 | roundSegments,
122 | roundSegments,
123 | flip ? "-z" : "z",
124 | pw,
125 | false,
126 | [(flip ? -1 : 1) * (radius / (su + r2)), radius / (sv + r2)],
127 | uvOffsetCorner(su, sv)[i],
128 | center(x, y),
129 | !flip,
130 | );
131 |
132 | // Edges
133 | if (i === 0 || i === 2) {
134 | // Left / Right
135 | computePlane(
136 | geometry,
137 | indices,
138 | radius,
139 | sv,
140 | roundSegments,
141 | edgeSegments,
142 | direction,
143 | pw,
144 | false,
145 | [uvOffset[0], uvScale[1]],
146 | ceil === 0 ? uvOffsetStart(su, sv) : uvOffsetEnd(su, sv),
147 | center(x, 0),
148 | );
149 | // Bottom/Top
150 | computePlane(
151 | geometry,
152 | indices,
153 | su,
154 | radius,
155 | edgeSegments,
156 | roundSegments,
157 | direction,
158 | pw,
159 | false,
160 | [uvScale[0], uvOffset[1]],
161 | floor === 0
162 | ? [...uvOffsetStart(sv, su)].reverse()
163 | : [...uvOffsetEnd(sv, su)].reverse(),
164 | center(0, y),
165 | );
166 | }
167 | }
168 |
169 | const rx = widthX * 0.5;
170 | const ry = widthY * 0.5;
171 |
172 | for (let i = 0; i < geometry.positions.length; i += 3) {
173 | const position = [
174 | geometry.positions[i],
175 | geometry.positions[i + 1],
176 | geometry.positions[i + 2],
177 | ];
178 | TMP[0] = position[0];
179 | TMP[1] = position[1];
180 | TMP[2] = position[2];
181 |
182 | let needsRounding = false;
183 |
184 | if (position[0] < -rx) {
185 | if (position[1] < -ry) {
186 | position[0] = -rx;
187 | position[1] = -ry;
188 | needsRounding = true;
189 | } else if (position[1] > ry) {
190 | position[0] = -rx;
191 | position[1] = ry;
192 | needsRounding = true;
193 | }
194 | } else if (position[0] > rx) {
195 | if (position[1] < -ry) {
196 | position[0] = rx;
197 | position[1] = -ry;
198 | needsRounding = true;
199 | } else if (position[1] > ry) {
200 | position[0] = rx;
201 | position[1] = ry;
202 | needsRounding = true;
203 | }
204 | }
205 |
206 | TMP[0] -= position[0];
207 | TMP[1] -= position[1];
208 |
209 | geometry.normals[i + 2] = 1;
210 |
211 | if (needsRounding) {
212 | const x =
213 | Math.sqrt(TMP[0] ** 2 + TMP[1] ** 2) /
214 | Math.max(Math.abs(TMP[0]), Math.abs(TMP[1]));
215 |
216 | geometry.positions[i] = position[0] + TMP[0] / x;
217 | geometry.positions[i + 1] = position[1] + TMP[1] / x;
218 | }
219 | }
220 |
221 | return geometry;
222 | }
223 |
224 | export default roundedRectangle;
225 |
--------------------------------------------------------------------------------
/src/sphere.js:
--------------------------------------------------------------------------------
1 | /** @module sphere */
2 | import ellipsoid from "./ellipsoid.js";
3 | import { checkArguments } from "./utils.js";
4 |
5 | /**
6 | * @typedef {object} SphereOptions
7 | * @property {number} [radius=0.5]
8 | * @property {number} [nx=32]
9 | * @property {number} [ny=16]
10 | * @property {number} [theta=Math.PI]
11 | * @property {number} [thetaOffset=0]
12 | * @property {number} [phi=TAU]
13 | * @property {number} [phiOffset=0]
14 | */
15 |
16 | /**
17 | * @alias module:sphere
18 | * @param {SphereOptions} [options={}]
19 | * @returns {import("../types.js").SimplicialComplex}
20 | */
21 | function sphere({
22 | radius = 0.5,
23 | nx = 32,
24 | ny = 16,
25 | theta,
26 | thetaOffset,
27 | phi,
28 | phiOffset,
29 | } = {}) {
30 | checkArguments(arguments);
31 |
32 | return ellipsoid({
33 | radius,
34 | nx,
35 | ny,
36 | theta,
37 | thetaOffset,
38 | phi,
39 | phiOffset,
40 | rx: 1,
41 | ry: 1,
42 | });
43 | }
44 |
45 | export default sphere;
46 |
--------------------------------------------------------------------------------
/src/squircle.js:
--------------------------------------------------------------------------------
1 | /** @module squircle */
2 | import ellipse from "./ellipse.js";
3 | import { fgSquircular } from "./mappings.js";
4 | import { checkArguments, HALF_PI, SQRT2, TAU } from "./utils.js";
5 |
6 | /**
7 | * @typedef {object} SquircleOptions
8 | * @property {number} [sx=1]
9 | * @property {number} [sy=1]
10 | * @property {number} [radius=0.5]
11 | * @property {number} [segments=128]
12 | * @property {number} [innerSegments=16]
13 | * @property {number} [theta=TAU]
14 | * @property {number} [thetaOffset=0]
15 | * @property {boolean} [mergeCentroid=true]
16 | * @property {Function} [mapping=mappings.fgSquircular]
17 | * @property {number} [squareness=0.95] Squareness (0 < s <= 1)
18 | */
19 |
20 | /**
21 | * Fernández-Guasti squircle
22 | * @see [Squircular Calculations – Chamberlain Fong]{@link https://arxiv.org/vc/arxiv/papers/1604/1604.02174v1.pdf}
23 | *
24 | * @alias module:squircle
25 | * @param {SquircleOptions} [options={}]
26 | * @returns {import("../types.js").SimplicialComplex}
27 | */
28 | function squircle({
29 | sx = 1,
30 | sy = 1,
31 | radius = 0.5,
32 | segments = 128,
33 | innerSegments = 16,
34 | theta = TAU,
35 | thetaOffset = 0,
36 | mergeCentroid = true,
37 | mapping = fgSquircular,
38 | squareness = 0.95,
39 | } = {}) {
40 | checkArguments(arguments);
41 |
42 | return ellipse({
43 | sx,
44 | sy,
45 | radius,
46 | segments,
47 | innerSegments,
48 | theta,
49 | thetaOffset,
50 | mergeCentroid,
51 | mapping,
52 | equation: ({ rx, ry, cosTheta, sinTheta, t }) => {
53 | // Fix singularities
54 | // https://codereview.stackexchange.com/questions/233496/handling-singularities-in-squircle-parametric-equations
55 | if (t === 0 || t === TAU) {
56 | return [rx, 0];
57 | } else if (t === HALF_PI) {
58 | return [0, ry];
59 | } else if (t === Math.PI) {
60 | return [-rx, 0];
61 | } else if (t === TAU - HALF_PI) {
62 | return [0, -ry];
63 | } else {
64 | const sqrt = Math.sqrt(
65 | 1 - Math.sqrt(1 - squareness ** 2 * Math.sin(2 * t) ** 2),
66 | );
67 |
68 | return [
69 | ((rx * Math.sign(cosTheta)) /
70 | (squareness * SQRT2 * Math.abs(sinTheta))) *
71 | sqrt,
72 | ((ry * Math.sign(sinTheta)) /
73 | (squareness * SQRT2 * Math.abs(cosTheta))) *
74 | sqrt,
75 | ];
76 | }
77 | },
78 | });
79 | }
80 |
81 | export default squircle;
82 |
--------------------------------------------------------------------------------
/src/stadium.js:
--------------------------------------------------------------------------------
1 | /** @module stadium */
2 | import roundedRectangle from "./rounded-rectangle.js";
3 | import { checkArguments } from "./utils.js";
4 |
5 | /**
6 | * @typedef {object} StadiumOptions
7 | * @property {number} [sx=1]
8 | * @property {number} [sy=sx]
9 | * @property {number} [nx=1]
10 | * @property {number} [ny=nx]
11 | * @property {number} [roundSegments=8]
12 | * @property {number} [edgeSegments=1]
13 | */
14 |
15 | /**
16 | * @alias module:stadium
17 | * @param {StadiumOptions} [options={}]
18 | * @returns {import("../types.js").SimplicialComplex}
19 | */
20 | function stadium({
21 | sx = 1,
22 | sy = 0.5,
23 | nx,
24 | ny,
25 | roundSegments,
26 | edgeSegments,
27 | } = {}) {
28 | checkArguments(arguments);
29 |
30 | return roundedRectangle({
31 | sx,
32 | sy,
33 | nx,
34 | ny,
35 | radius: Math.min(sx, sy) * 0.5,
36 | roundSegments,
37 | edgeSegments,
38 | });
39 | }
40 |
41 | export default stadium;
42 |
--------------------------------------------------------------------------------
/src/superellipse.js:
--------------------------------------------------------------------------------
1 | /** @module superellipse */
2 | import ellipse from "./ellipse.js";
3 | import { lamé } from "./mappings.js";
4 | import { checkArguments, TAU } from "./utils.js";
5 |
6 | /**
7 | * @typedef {object} SuperellipseOptions
8 | * @property {number} [sx=1]
9 | * @property {number} [sy=0.5]
10 | * @property {number} [radius=0.5]
11 | * @property {number} [segments=32]
12 | * @property {number} [innerSegments=16]
13 | * @property {number} [theta=TAU]
14 | * @property {number} [thetaOffset=0]
15 | * @property {boolean} [mergeCentroid=true]
16 | * @property {Function} [mapping=mappings.lamé]
17 | * @property {number} [m=2]
18 | * @property {number} [n=m]
19 | */
20 |
21 | /**
22 | * Lamé curve
23 | * See elliptical-mapping example for a few special cases
24 | * @see [Wolfram MathWorld – Superellipse]{@link https://mathworld.wolfram.com/Superellipse.html}
25 | * @see [Wikipedia – Superellipse]{@link https://en.wikipedia.org/wiki/Superellipse}
26 | * @alias module:superellipse
27 | * @param {SuperellipseOptions} [options={}]
28 | * @returns {import("../types.js").SimplicialComplex}
29 | */
30 | function superellipse({
31 | sx = 1,
32 | sy = 0.5,
33 | radius = 0.5,
34 | segments = 32,
35 | innerSegments = 16,
36 | theta = TAU,
37 | thetaOffset = 0,
38 | mergeCentroid = true,
39 | mapping = lamé,
40 | m = 2,
41 | n = m,
42 | } = {}) {
43 | checkArguments(arguments);
44 |
45 | return ellipse({
46 | sx,
47 | sy,
48 | radius,
49 | segments,
50 | innerSegments,
51 | theta,
52 | thetaOffset,
53 | mergeCentroid,
54 | mapping,
55 | equation: ({ rx, ry, cosTheta, sinTheta }) => [
56 | rx * Math.abs(cosTheta) ** (2 / m) * Math.sign(cosTheta),
57 | ry * Math.abs(sinTheta) ** (2 / n) * Math.sign(sinTheta),
58 | ],
59 | });
60 | }
61 |
62 | export default superellipse;
63 |
--------------------------------------------------------------------------------
/src/tetrahedron.js:
--------------------------------------------------------------------------------
1 | /** @module tetrahedron */
2 | import cylinder from "./cylinder.js";
3 | import { checkArguments } from "./utils.js";
4 |
5 | /**
6 | * @typedef {object} TetrahedronOptions
7 | * @property {number} [radius=0.5]
8 | */
9 |
10 | /**
11 | * @alias module:tetrahedron
12 | * @param {TetrahedronOptions} [options={}]
13 | * @returns {import("../types.js").SimplicialComplex}
14 | */
15 | function tetrahedron({ radius = 0.5 } = {}) {
16 | checkArguments(arguments);
17 |
18 | return cylinder({
19 | height: radius * 1.5,
20 | radius,
21 | nx: 3,
22 | ny: 1,
23 |
24 | radiusApex: 0,
25 | capSegments: 0,
26 | capApex: false,
27 | capBaseSegments: 1,
28 | });
29 | }
30 |
31 | export default tetrahedron;
32 |
--------------------------------------------------------------------------------
/src/torus.js:
--------------------------------------------------------------------------------
1 | /** @module torus */
2 | import {
3 | checkArguments,
4 | getCellsTypedArray,
5 | normalize,
6 | TAU,
7 | TMP,
8 | } from "./utils.js";
9 |
10 | /**
11 | * @typedef {object} TorusOptions
12 | * @property {number} [radius=0.4]
13 | * @property {number} [segments=64]
14 | * @property {number} [minorRadius=0.1]
15 | * @property {number} [minorSegments=32]
16 | * @property {number} [theta=TAU]
17 | * @property {number} [thetaOffset=0]
18 | * @property {number} [phi=TAU]
19 | * @property {number} [phiOffset=0]
20 | */
21 |
22 | /**
23 | * @alias module:torus
24 | * @param {TorusOptions} [options={}]
25 | * @returns {import("../types.js").SimplicialComplex}
26 | */
27 | function torus({
28 | radius = 0.4,
29 | segments = 64,
30 |
31 | minorRadius = 0.1,
32 | minorSegments = 32,
33 | theta = TAU,
34 | thetaOffset = 0,
35 | phi = TAU,
36 | phiOffset = 0,
37 | } = {}) {
38 | checkArguments(arguments);
39 |
40 | const size = (minorSegments + 1) * (segments + 1);
41 |
42 | const positions = new Float32Array(size * 3);
43 | const normals = new Float32Array(size * 3);
44 | const uvs = new Float32Array(size * 2);
45 | const cells = new (getCellsTypedArray(size))(minorSegments * segments * 6);
46 |
47 | let vertexIndex = 0;
48 | let cellIndex = 0;
49 |
50 | for (let j = 0; j <= minorSegments; j++) {
51 | const v = j / minorSegments;
52 |
53 | for (let i = 0; i <= segments; i++, vertexIndex++) {
54 | const u = i / segments;
55 |
56 | const p = u * phi + phiOffset;
57 | const cosPhi = -Math.cos(p);
58 | const sinPhi = Math.sin(p);
59 |
60 | const t = v * theta + thetaOffset;
61 | const cosTheta = -Math.cos(t);
62 | const sinTheta = Math.sin(t);
63 |
64 | TMP[0] = (radius + minorRadius * cosTheta) * cosPhi;
65 | TMP[1] = (radius + minorRadius * cosTheta) * sinPhi;
66 | TMP[2] = minorRadius * sinTheta;
67 |
68 | positions[vertexIndex * 3] = TMP[0];
69 | positions[vertexIndex * 3 + 1] = TMP[1];
70 | positions[vertexIndex * 3 + 2] = TMP[2];
71 |
72 | TMP[0] -= radius * cosPhi;
73 | TMP[1] -= radius * sinPhi;
74 |
75 | normalize(TMP);
76 |
77 | normals[vertexIndex * 3] = TMP[0];
78 | normals[vertexIndex * 3 + 1] = TMP[1];
79 | normals[vertexIndex * 3 + 2] = TMP[2];
80 |
81 | uvs[vertexIndex * 2] = u;
82 | uvs[vertexIndex * 2 + 1] = v;
83 |
84 | if (j > 0 && i > 0) {
85 | const a = (segments + 1) * j + i - 1;
86 | const b = (segments + 1) * (j - 1) + i - 1;
87 | const c = (segments + 1) * (j - 1) + i;
88 | const d = (segments + 1) * j + i;
89 |
90 | cells[cellIndex] = a;
91 | cells[cellIndex + 1] = b;
92 | cells[cellIndex + 2] = d;
93 |
94 | cells[cellIndex + 3] = b;
95 | cells[cellIndex + 4] = c;
96 | cells[cellIndex + 5] = d;
97 |
98 | cellIndex += 6;
99 | }
100 | }
101 | }
102 |
103 | return {
104 | positions,
105 | normals,
106 | uvs,
107 | cells,
108 | };
109 | }
110 |
111 | export default torus;
112 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | /** @module utils */
2 |
3 | /**
4 | * Two times PI.
5 | * @constant {number}
6 | */
7 | export const TAU = Math.PI * 2;
8 |
9 | /**
10 | * Two times PI.
11 | * @constant {number}
12 | */
13 | export const HALF_PI = Math.PI / 2;
14 |
15 | /**
16 | * Square root of 2.
17 | * @constant {number}
18 | */
19 | export const SQRT2 = Math.sqrt(2);
20 |
21 | /**
22 | * Normalize a vector 3.
23 | * @param {number[]} v Vector 3 array
24 | * @returns {number[]} Normalized vector
25 | */
26 | export function normalize(v) {
27 | const l = 1 / (Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) || 1);
28 | v[0] *= l;
29 | v[1] *= l;
30 | v[2] *= l;
31 | return v;
32 | }
33 |
34 | /**
35 | * Ensure first argument passed to the primitive functions is an object
36 | * @param {...*} args
37 | */
38 | export function checkArguments(args) {
39 | const argumentType = typeof args[0];
40 | if (argumentType !== "object" && argumentType !== "undefined") {
41 | console.error("First argument must be an object.");
42 | }
43 | }
44 |
45 | /**
46 | * @private
47 | */
48 | let TYPED_ARRAY_TYPE;
49 |
50 | /**
51 | * Enforce a typed array constructor for cells
52 | * @param {(Class|Class|Class)} type
53 | */
54 | export function setTypedArrayType(type) {
55 | TYPED_ARRAY_TYPE = type;
56 | }
57 |
58 | /**
59 | * Select cells typed array from a size determined by amount of vertices.
60 | *
61 | * @param {number} size The max value expected
62 | * @returns {(Uint8Array|Uint16Array|Uint32Array)}
63 | * @see [MDN TypedArray objects]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray#typedarray_objects}
64 | */
65 | export const getCellsTypedArray = (size) =>
66 | TYPED_ARRAY_TYPE ||
67 | (size <= 255 ? Uint8Array : size <= 65535 ? Uint16Array : Uint32Array);
68 |
69 | /**
70 | * @private
71 | */
72 | export const TMP = [0, 0, 0];
73 |
74 | /**
75 | * @private
76 | */
77 | export const PLANE_DIRECTIONS = {
78 | z: [0, 1, 2, 1, -1, 1],
79 | "-z": [0, 1, 2, -1, -1, -1],
80 | "-x": [2, 1, 0, 1, -1, -1],
81 | x: [2, 1, 0, -1, -1, 1],
82 | y: [0, 2, 1, 1, 1, 1],
83 | "-y": [0, 2, 1, 1, -1, -1],
84 | };
85 |
86 | /**
87 | * @private
88 | */
89 | export function computePlane(
90 | geometry,
91 | indices,
92 | su,
93 | sv,
94 | nu,
95 | nv,
96 | direction = "z",
97 | pw = 0,
98 | quads = false,
99 | uvScale = [1, 1],
100 | uvOffset = [0, 0],
101 | center = [0, 0, 0],
102 | ccw = true,
103 | ) {
104 | const { positions, normals, uvs, cells } = geometry;
105 | const [u, v, w, flipU, flipV, normal] = PLANE_DIRECTIONS[direction];
106 |
107 | const vertexOffset = indices.vertex;
108 |
109 | for (let j = 0; j <= nv; j++) {
110 | for (let i = 0; i <= nu; i++) {
111 | positions[indices.vertex * 3 + u] =
112 | (-su / 2 + (i * su) / nu) * flipU + center[u];
113 | positions[indices.vertex * 3 + v] =
114 | (-sv / 2 + (j * sv) / nv) * flipV + center[v];
115 | positions[indices.vertex * 3 + w] = pw + center[w];
116 |
117 | normals[indices.vertex * 3 + w] = normal;
118 |
119 | uvs[indices.vertex * 2] = (i / nu) * uvScale[0] + uvOffset[0];
120 | uvs[indices.vertex * 2 + 1] = (1 - j / nv) * uvScale[1] + uvOffset[1];
121 |
122 | indices.vertex++;
123 |
124 | if (j < nv && i < nu) {
125 | const n = vertexOffset + j * (nu + 1) + i;
126 | if (quads) {
127 | const o = vertexOffset + (j + 1) * (nu + 1) + i;
128 | cells[indices.cell] = n;
129 | cells[indices.cell + 1] = o;
130 | cells[indices.cell + 2] = o + 1;
131 | cells[indices.cell + 3] = n + 1;
132 | } else {
133 | cells[indices.cell] = n;
134 | cells[indices.cell + (ccw ? 1 : 2)] = n + nu + 1;
135 | cells[indices.cell + (ccw ? 2 : 1)] = n + nu + 2;
136 |
137 | cells[indices.cell + 3] = n;
138 | cells[indices.cell + (ccw ? 4 : 5)] = n + nu + 2;
139 | cells[indices.cell + (ccw ? 5 : 4)] = n + 1;
140 | }
141 | indices.cell += quads ? 4 : 6;
142 | }
143 | }
144 | }
145 |
146 | return geometry;
147 | }
148 |
--------------------------------------------------------------------------------
/types.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @typedef {object} BasicSimplicialComplex Geometry definition without normals and UVs.
3 | * @property {Float32Array} positions
4 | * @property {(Uint8Array|Uint16Array|Uint32Array)} cells
5 | */
6 |
7 | /**
8 | * @typedef {object} SimplicialComplex Geometry definition.
9 | * @property {Float32Array} positions
10 | * @property {Float32Array} normals
11 | * @property {Float32Array} uvs
12 | * @property {(Uint8Array|Uint16Array|Uint32Array)} cells
13 | */
14 |
15 | export {};
16 |
--------------------------------------------------------------------------------
/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/async-preloader.js:
--------------------------------------------------------------------------------
1 | /* Font Face Observer v3.3.1 - © Bram Stein - Damien Seguin. License: BSD-3-Clause */ function _classCallCheck(instance, Constructor) {
2 | if (!(instance instanceof Constructor)) {
3 | throw new TypeError("Cannot call a class as a function");
4 | }
5 | }
6 | function _defineProperties(target, props) {
7 | for(var i = 0; i < props.length; i++){
8 | var descriptor = props[i];
9 | descriptor.enumerable = descriptor.enumerable || false;
10 | descriptor.configurable = true;
11 | if ("value" in descriptor) descriptor.writable = true;
12 | Object.defineProperty(target, descriptor.key, descriptor);
13 | }
14 | }
15 | function _createClass(Constructor, protoProps, staticProps) {
16 | if (protoProps) _defineProperties(Constructor.prototype, protoProps);
17 | if (staticProps) _defineProperties(Constructor, staticProps);
18 | return Constructor;
19 | }
20 | function _defineProperty(obj, key, value) {
21 | if (key in obj) {
22 | Object.defineProperty(obj, key, {
23 | value: value,
24 | enumerable: true,
25 | configurable: true,
26 | writable: true
27 | });
28 | } else {
29 | obj[key] = value;
30 | }
31 | return obj;
32 | }
33 | function _objectSpread(target) {
34 | for(var i = 1; i < arguments.length; i++){
35 | var source = arguments[i] != null ? arguments[i] : {};
36 | var ownKeys = Object.keys(source);
37 | if (typeof Object.getOwnPropertySymbols === 'function') {
38 | ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
39 | return Object.getOwnPropertyDescriptor(source, sym).enumerable;
40 | }));
41 | }
42 | ownKeys.forEach(function(key) {
43 | _defineProperty(target, key, source[key]);
44 | });
45 | }
46 | return target;
47 | }
48 | var styles = {
49 | maxWidth: "none",
50 | display: "inline-block",
51 | position: "absolute",
52 | height: "100%",
53 | width: "100%",
54 | overflow: "scroll",
55 | fontSize: "16px"
56 | };
57 | var collapsibleInnerStyles = {
58 | display: "inline-block",
59 | height: "200%",
60 | width: "200%",
61 | fontSize: "16px",
62 | maxWidth: "none"
63 | };
64 | var fontStyle = {
65 | maxWidth: "none",
66 | minWidth: "20px",
67 | minHeight: "20px",
68 | display: "inline-block",
69 | overflow: "hidden",
70 | position: "absolute",
71 | width: "auto",
72 | margin: "0",
73 | padding: "0",
74 | top: "-999px",
75 | whiteSpace: "nowrap",
76 | fontSynthesis: "none"
77 | };
78 | var Ruler = /*#__PURE__*/ function() {
79 | /**
80 | *
81 | * @param {string} text
82 | */ function Ruler(text) {
83 | _classCallCheck(this, Ruler);
84 | this.element = document.createElement("div");
85 | this.element.setAttribute("aria-hidden", "true");
86 | this.element.appendChild(document.createTextNode(text));
87 | this.collapsible = document.createElement("span");
88 | this.expandable = document.createElement("span");
89 | this.collapsibleInner = document.createElement("span");
90 | this.expandableInner = document.createElement("span");
91 | this.lastOffsetWidth = -1;
92 | Object.assign(this.collapsible.style, styles);
93 | Object.assign(this.expandable.style, styles);
94 | Object.assign(this.expandableInner.style, styles);
95 | Object.assign(this.collapsibleInner.style, collapsibleInnerStyles);
96 | this.collapsible.appendChild(this.collapsibleInner);
97 | this.expandable.appendChild(this.expandableInner);
98 | this.element.appendChild(this.collapsible);
99 | this.element.appendChild(this.expandable);
100 | }
101 | /**
102 | * @return {Element}
103 | */ _createClass(Ruler, [
104 | {
105 | key: "getElement",
106 | value: function getElement() {
107 | return this.element;
108 | }
109 | },
110 | {
111 | key: "setFont",
112 | value: function setFont(font) {
113 | Object.assign(this.element.style, _objectSpread({}, fontStyle, {
114 | font: font
115 | }));
116 | }
117 | },
118 | {
119 | key: "getWidth",
120 | value: function getWidth() {
121 | return this.element.offsetWidth;
122 | }
123 | },
124 | {
125 | key: "setWidth",
126 | value: function setWidth(width) {
127 | this.element.style.width = width + "px";
128 | }
129 | },
130 | {
131 | key: "reset",
132 | value: function reset() {
133 | var offsetWidth = this.getWidth();
134 | var width = offsetWidth + 100;
135 | this.expandableInner.style.width = width + "px";
136 | this.expandable.scrollLeft = width;
137 | this.collapsible.scrollLeft = this.collapsible.scrollWidth + 100;
138 | if (this.lastOffsetWidth !== offsetWidth) {
139 | this.lastOffsetWidth = offsetWidth;
140 | return true;
141 | } else {
142 | return false;
143 | }
144 | }
145 | },
146 | {
147 | key: "onScroll",
148 | value: function onScroll(callback) {
149 | if (this.reset() && this.element.parentNode !== null) {
150 | callback(this.lastOffsetWidth);
151 | }
152 | }
153 | },
154 | {
155 | key: "onResize",
156 | value: function onResize(callback) {
157 | var that = this;
158 | function onScroll() {
159 | that.onScroll(callback);
160 | }
161 | this.collapsible.addEventListener("scroll", onScroll);
162 | this.expandable.addEventListener("scroll", onScroll);
163 | this.reset();
164 | }
165 | }
166 | ]);
167 | return Ruler;
168 | }();
169 | function onReady(callback) {
170 | document.body ? callback() : document.addEventListener ? document.addEventListener("DOMContentLoaded", function c() {
171 | document.removeEventListener("DOMContentLoaded", c);
172 | callback();
173 | }) : document.attachEvent("onreadystatechange", function k() {
174 | if ("interactive" == document.readyState || "complete" == document.readyState) document.detachEvent("onreadystatechange", k), callback();
175 | });
176 | }
177 | /** Class for FontFaceObserver. */ var FontFaceObserver = /*#__PURE__*/ function() {
178 | _createClass(FontFaceObserver, null, [
179 | {
180 | key: "getUserAgent",
181 | /**
182 | * @type {null|boolean}
183 | */ /**
184 | * @type {null|boolean}
185 | */ /**
186 | * @type {null|boolean}
187 | */ /**
188 | * @type {null|boolean}
189 | */ /**
190 | * @type {number}
191 | */ /**
192 | * @return {string}
193 | */ value: function getUserAgent() {
194 | return window.navigator.userAgent;
195 | }
196 | },
197 | {
198 | key: "getNavigatorVendor",
199 | value: function getNavigatorVendor() {
200 | return window.navigator.vendor;
201 | }
202 | },
203 | {
204 | key: "hasWebKitFallbackBug",
205 | value: function hasWebKitFallbackBug() {
206 | if (FontFaceObserver.HAS_WEBKIT_FALLBACK_BUG === null) {
207 | var match = /AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(FontFaceObserver.getUserAgent());
208 | FontFaceObserver.HAS_WEBKIT_FALLBACK_BUG = !!match && (parseInt(match[1], 10) < 536 || parseInt(match[1], 10) === 536 && parseInt(match[2], 10) <= 11);
209 | }
210 | return FontFaceObserver.HAS_WEBKIT_FALLBACK_BUG;
211 | }
212 | },
213 | {
214 | key: "hasSafari10Bug",
215 | value: function hasSafari10Bug() {
216 | if (FontFaceObserver.HAS_SAFARI_10_BUG === null) {
217 | if (FontFaceObserver.supportsNativeFontLoading() && /Apple/.test(FontFaceObserver.getNavigatorVendor())) {
218 | var match = /AppleWebKit\/([0-9]+)(?:\.([0-9]+))(?:\.([0-9]+))/.exec(FontFaceObserver.getUserAgent());
219 | FontFaceObserver.HAS_SAFARI_10_BUG = !!match && parseInt(match[1], 10) < 603;
220 | } else {
221 | FontFaceObserver.HAS_SAFARI_10_BUG = false;
222 | }
223 | }
224 | return FontFaceObserver.HAS_SAFARI_10_BUG;
225 | }
226 | },
227 | {
228 | key: "supportsNativeFontLoading",
229 | value: function supportsNativeFontLoading() {
230 | if (FontFaceObserver.SUPPORTS_NATIVE_FONT_LOADING === null) {
231 | FontFaceObserver.SUPPORTS_NATIVE_FONT_LOADING = !!document["fonts"];
232 | }
233 | return FontFaceObserver.SUPPORTS_NATIVE_FONT_LOADING;
234 | }
235 | },
236 | {
237 | key: "supportStretch",
238 | value: function supportStretch() {
239 | if (FontFaceObserver.SUPPORTS_STRETCH === null) {
240 | var div = document.createElement("div");
241 | try {
242 | div.style.font = "condensed 100px sans-serif";
243 | } catch (e) {}
244 | FontFaceObserver.SUPPORTS_STRETCH = div.style.font !== "";
245 | }
246 | return FontFaceObserver.SUPPORTS_STRETCH;
247 | }
248 | }
249 | ]);
250 | function FontFaceObserver(family) {
251 | var descriptors = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
252 | _classCallCheck(this, FontFaceObserver);
253 | this.family = family;
254 | this.style = descriptors.style || "normal";
255 | this.weight = descriptors.weight || "normal";
256 | this.stretch = descriptors.stretch || "normal";
257 | return this;
258 | }
259 | /**
260 | * @param {string=} text Optional test string to use for detecting if a font
261 | * is available.
262 | * @param {number=} timeout Optional timeout for giving up on font load
263 | * detection and rejecting the promise (defaults to 3 seconds).
264 | * @return {Promise.}
265 | */ _createClass(FontFaceObserver, [
266 | {
267 | key: "load",
268 | value: function load(text, timeout) {
269 | var that = this;
270 | var testString = text || "BESbswy";
271 | var timeoutId = 0;
272 | var timeoutValue = timeout || FontFaceObserver.DEFAULT_TIMEOUT;
273 | var start = that.getTime();
274 | return new Promise(function(resolve, reject) {
275 | if (FontFaceObserver.supportsNativeFontLoading() && !FontFaceObserver.hasSafari10Bug()) {
276 | var loader = new Promise(function(resolve, reject) {
277 | var check = function check() {
278 | var now = that.getTime();
279 | if (now - start >= timeoutValue) {
280 | reject(new Error("" + timeoutValue + "ms timeout exceeded"));
281 | } else {
282 | document.fonts.load(that.getStyle('"' + that["family"] + '"'), testString).then(function(fonts) {
283 | if (fonts.length >= 1) {
284 | resolve();
285 | } else {
286 | setTimeout(check, 25);
287 | }
288 | }, reject);
289 | }
290 | };
291 | check();
292 | });
293 | var timer = new Promise(function(resolve, reject) {
294 | timeoutId = setTimeout(function() {
295 | reject(new Error("" + timeoutValue + "ms timeout exceeded"));
296 | }, timeoutValue);
297 | });
298 | Promise.race([
299 | timer,
300 | loader
301 | ]).then(function() {
302 | clearTimeout(timeoutId);
303 | resolve(that);
304 | }, reject);
305 | } else {
306 | onReady(function() {
307 | var rulerA = new Ruler(testString);
308 | var rulerB = new Ruler(testString);
309 | var rulerC = new Ruler(testString);
310 | var widthA = -1;
311 | var widthB = -1;
312 | var widthC = -1;
313 | var fallbackWidthA = -1;
314 | var fallbackWidthB = -1;
315 | var fallbackWidthC = -1;
316 | var container = document.createElement("div");
317 | /**
318 | * @private
319 | */ function removeContainer() {
320 | if (container.parentNode !== null) {
321 | container.parentNode.removeChild(container);
322 | }
323 | }
324 | /**
325 | * @private
326 | *
327 | * If metric compatible fonts are detected, one of the widths will be
328 | * -1. This is because a metric compatible font won't trigger a scroll
329 | * event. We work around this by considering a font loaded if at least
330 | * two of the widths are the same. Because we have three widths, this
331 | * still prevents false positives.
332 | *
333 | * Cases:
334 | * 1) Font loads: both a, b and c are called and have the same value.
335 | * 2) Font fails to load: resize callback is never called and timeout
336 | * happens.
337 | * 3) WebKit bug: both a, b and c are called and have the same value,
338 | * but the values are equal to one of the last resort fonts, we
339 | * ignore this and continue waiting until we get new values (or a
340 | * timeout).
341 | */ function check() {
342 | if (widthA != -1 && widthB != -1 || widthA != -1 && widthC != -1 || widthB != -1 && widthC != -1) {
343 | if (widthA == widthB || widthA == widthC || widthB == widthC) {
344 | // All values are the same, so the browser has most likely
345 | // loaded the web font
346 | if (FontFaceObserver.hasWebKitFallbackBug()) {
347 | // Except if the browser has the WebKit fallback bug, in which
348 | // case we check to see if all values are set to one of the
349 | // last resort fonts.
350 | if (widthA == fallbackWidthA && widthB == fallbackWidthA && widthC == fallbackWidthA || widthA == fallbackWidthB && widthB == fallbackWidthB && widthC == fallbackWidthB || widthA == fallbackWidthC && widthB == fallbackWidthC && widthC == fallbackWidthC) {
351 | // The width we got matches some of the known last resort
352 | // fonts, so let's assume we're dealing with the last resort
353 | // font.
354 | return;
355 | }
356 | }
357 | removeContainer();
358 | clearTimeout(timeoutId);
359 | resolve(that);
360 | }
361 | }
362 | } // This ensures the scroll direction is correct.
363 | container.dir = "ltr";
364 | rulerA.setFont(that.getStyle("sans-serif"));
365 | rulerB.setFont(that.getStyle("serif"));
366 | rulerC.setFont(that.getStyle("monospace"));
367 | container.appendChild(rulerA.getElement());
368 | container.appendChild(rulerB.getElement());
369 | container.appendChild(rulerC.getElement());
370 | document.body.appendChild(container);
371 | fallbackWidthA = rulerA.getWidth();
372 | fallbackWidthB = rulerB.getWidth();
373 | fallbackWidthC = rulerC.getWidth();
374 | function checkForTimeout() {
375 | var now = that.getTime();
376 | if (now - start >= timeoutValue) {
377 | removeContainer();
378 | reject(new Error("" + timeoutValue + "ms timeout exceeded"));
379 | } else {
380 | var hidden = document["hidden"];
381 | if (hidden === true || hidden === undefined) {
382 | widthA = rulerA.getWidth();
383 | widthB = rulerB.getWidth();
384 | widthC = rulerC.getWidth();
385 | check();
386 | }
387 | timeoutId = setTimeout(checkForTimeout, 50);
388 | }
389 | }
390 | checkForTimeout();
391 | rulerA.onResize(function(width) {
392 | widthA = width;
393 | check();
394 | });
395 | rulerA.setFont(that.getStyle('"' + that["family"] + '",sans-serif'));
396 | rulerB.onResize(function(width) {
397 | widthB = width;
398 | check();
399 | });
400 | rulerB.setFont(that.getStyle('"' + that["family"] + '",serif'));
401 | rulerC.onResize(function(width) {
402 | widthC = width;
403 | check();
404 | });
405 | rulerC.setFont(that.getStyle('"' + that["family"] + '",monospace'));
406 | });
407 | }
408 | });
409 | }
410 | },
411 | {
412 | key: "getStyle",
413 | value: function getStyle(family) {
414 | return [
415 | this.style,
416 | this.weight,
417 | FontFaceObserver.supportStretch() ? this.stretch : "",
418 | "100px",
419 | family
420 | ].join(" ");
421 | }
422 | },
423 | {
424 | key: "getTime",
425 | value: function getTime() {
426 | return new Date().getTime();
427 | }
428 | }
429 | ]);
430 | return FontFaceObserver;
431 | }();
432 | _defineProperty(FontFaceObserver, "Ruler", Ruler);
433 | _defineProperty(FontFaceObserver, "HAS_WEBKIT_FALLBACK_BUG", null);
434 | _defineProperty(FontFaceObserver, "HAS_SAFARI_10_BUG", null);
435 | _defineProperty(FontFaceObserver, "SUPPORTS_STRETCH", null);
436 | _defineProperty(FontFaceObserver, "SUPPORTS_NATIVE_FONT_LOADING", null);
437 | _defineProperty(FontFaceObserver, "DEFAULT_TIMEOUT", 3000);
438 |
439 | /**
440 | * Keys used for the {@link AsyncPreloader.loaders}
441 | */ var LoaderKey;
442 | (function(LoaderKey) {
443 | LoaderKey["Json"] = "Json";
444 | LoaderKey["ArrayBuffer"] = "ArrayBuffer";
445 | LoaderKey["Blob"] = "Blob";
446 | LoaderKey["FormData"] = "FormData";
447 | LoaderKey["Text"] = "Text";
448 | LoaderKey["Image"] = "Image";
449 | LoaderKey["Video"] = "Video";
450 | LoaderKey["Audio"] = "Audio";
451 | LoaderKey["Xml"] = "Xml";
452 | LoaderKey["Font"] = "Font";
453 | })(LoaderKey || (LoaderKey = {}));
454 |
455 | const isSafari = /^((?!chrome|android).)*safari/i.test(globalThis.navigator?.userAgent) === true;
456 | /**
457 | * AsyncPreloader: assets preloader using ES2017 async/await and fetch.
458 | *
459 | * It exports an instance of itself as default so you can:
460 | *
461 | * ```js
462 | * import Preloader from "async-preloader";
463 | *
464 | * await Preloader.loadItems([]);
465 | * ```
466 | *
467 | * to use directly as a singleton or
468 | *
469 | * ```js
470 | * import { AsyncPreloader as Preloader } from "async-preloader";
471 | *
472 | * const preloader = new Preloader();
473 | * await preloader.loadItems([]);
474 | * ```
475 | * if you need more than one instance.
476 | */ class AsyncPreloader {
477 | constructor(){
478 | // Properties
479 | /**
480 | * Object that contains the loaded items
481 | */ this.items = new Map();
482 | /**
483 | * Default body method to be called on the Response from fetch if no body option is specified on the LoadItem
484 | */ this.defaultBodyMethod = "blob";
485 | /**
486 | * Default loader to use if no loader key is specified in the {@link LoadItem} or if the extension doesn't match any of the {@link AsyncPreloader.loaders} extensions
487 | */ this.defaultLoader = LoaderKey.Text;
488 | // API
489 | /**
490 | * Load the specified manifest (array of items)
491 | *
492 | * @param items Items to load
493 | * @returns Resolve when all items are loaded, reject for any error
494 | */ this.loadItems = async (items)=>{
495 | return await Promise.all(items.map(this.loadItem));
496 | };
497 | /**
498 | * Load a single item
499 | *
500 | * @param item Item to load
501 | * @returns Resolve when item is loaded, reject for any error
502 | */ this.loadItem = async (item)=>{
503 | if (typeof item === "string") item = {
504 | src: item
505 | };
506 | const extension = AsyncPreloader.getFileExtension(item.src || "");
507 | const loaderKey = item.loader || AsyncPreloader.getLoaderKey(extension);
508 | const loadedItem = await this[`load` + loaderKey](item);
509 | this.items.set(item.id || item.src, loadedItem);
510 | return loadedItem;
511 | };
512 | // Special loaders
513 | /**
514 | * Load a manifest of items
515 | *
516 | * @param src Manifest src url
517 | * @param key Manifest key in the JSON object containing the array of LoadItem.
518 | * @returns
519 | */ this.loadManifest = async (src, key)=>{
520 | if (key === void 0) key = "items";
521 | const loadedManifest = await this.loadJson({
522 | src
523 | });
524 | const items = AsyncPreloader.getProp(loadedManifest, key);
525 | return await this.loadItems(items);
526 | };
527 | // Text loaders
528 | /**
529 | * Load an item and parse the Response as text
530 | *
531 | * @param item Item to load
532 | * @returns Fulfilled value of parsed Response
533 | */ this.loadText = async (item)=>{
534 | const response = await AsyncPreloader.fetchItem(item);
535 | return await response.text();
536 | };
537 | /**
538 | * Load an item and parse the Response as json
539 | *
540 | * @param item Item to load
541 | * @returns Fulfilled value of parsed Response
542 | */ this.loadJson = async (item)=>{
543 | const response = await AsyncPreloader.fetchItem(item);
544 | return await response.json();
545 | };
546 | /**
547 | * Load an item and parse the Response as arrayBuffer
548 | *
549 | * @param item Item to load
550 | * @returns Fulfilled value of parsed Response
551 | */ this.loadArrayBuffer = async (item)=>{
552 | const response = await AsyncPreloader.fetchItem(item);
553 | return await response.arrayBuffer();
554 | };
555 | /**
556 | * Load an item and parse the Response as blob
557 | *
558 | * @param item Item to load
559 | * @returns Fulfilled value of parsed Response
560 | */ this.loadBlob = async (item)=>{
561 | const response = await AsyncPreloader.fetchItem(item);
562 | return await response.blob();
563 | };
564 | /**
565 | * Load an item and parse the Response as formData
566 | *
567 | * @param item Item to load
568 | * @returns Fulfilled value of parsed Response
569 | */ this.loadFormData = async (item)=>{
570 | const response = await AsyncPreloader.fetchItem(item);
571 | return await response.formData();
572 | };
573 | // Custom loaders
574 | /**
575 | * Load an item in one of the following cases:
576 | * - item's "loader" option set as "Image"
577 | * - item's "src" option extensions matching the loaders Map
578 | * - direct call of the method
579 | *
580 | * @param item Item to load
581 | * @returns Fulfilled value with a decoded HTMLImageElement instance of or a parsed Response according to the "body" option. Defaults to a decoded HTMLImageElement.
582 | */ this.loadImage = async (item)=>{
583 | const image = new Image();
584 | if (item.body) {
585 | const response = await AsyncPreloader.fetchItem(item);
586 | const data = await response[item.body]();
587 | if (item.body !== "blob") return data;
588 | return await new Promise((resolve, reject)=>{
589 | image.addEventListener("load", function load() {
590 | image.removeEventListener("load", load);
591 | resolve(image);
592 | });
593 | image.addEventListener("error", function error(event) {
594 | image.removeEventListener("error", error);
595 | reject(event);
596 | });
597 | image.src = URL.createObjectURL(data);
598 | });
599 | }
600 | image.src = item.src;
601 | if (!item.noDecode) await image.decode();
602 | return image;
603 | };
604 | /**
605 | * Load an item in one of the following cases:
606 | * - item's "loader" option set as "Video"
607 | * - item's "src" option extensions matching the loaders Map
608 | * - direct call of the method
609 | *
610 | * @param item Item to load
611 | * @returns Fulfilled value of parsed Response according to the "body" option. Defaults to an HTMLVideoElement with a blob as srcObject or src.
612 | */ this.loadVideo = async (item)=>{
613 | const response = await AsyncPreloader.fetchItem(item);
614 | const data = await response[item.body || this.defaultBodyMethod]();
615 | if (item.body) return data;
616 | const video = document.createElement("video");
617 | return await new Promise((resolve, reject)=>{
618 | video.addEventListener("canplaythrough", function canplaythrough() {
619 | video.removeEventListener("canplaythrough", canplaythrough);
620 | resolve(video);
621 | });
622 | video.addEventListener("error", function error(event) {
623 | video.removeEventListener("error", error);
624 | reject(event);
625 | });
626 | try {
627 | if (isSafari) throw "";
628 | video.srcObject = data;
629 | } catch (error) {
630 | video.src = URL.createObjectURL(data);
631 | }
632 | video.load();
633 | });
634 | };
635 | /**
636 | * Load an item in one of the following cases:
637 | * - item's "loader" option set as "Audio"
638 | * - item's "src" option extensions matching the loaders Map
639 | * - direct call of the method
640 | *
641 | * @param item Item to load
642 | * @returns Fulfilled value of parsed Response according to the "body" option. Defaults to an HTMLAudioElement with a blob as srcObject or src.
643 | */ this.loadAudio = async (item)=>{
644 | const response = await AsyncPreloader.fetchItem(item);
645 | const data = await response[item.body || this.defaultBodyMethod]();
646 | if (item.body) return data;
647 | const audio = document.createElement("audio");
648 | audio.autoplay = false;
649 | audio.preload = "auto";
650 | return await new Promise((resolve, reject)=>{
651 | audio.addEventListener("canplaythrough", function canplaythrough() {
652 | audio.removeEventListener("canplaythrough", canplaythrough);
653 | resolve(audio);
654 | });
655 | audio.addEventListener("error", function error(event) {
656 | audio.removeEventListener("error", error);
657 | reject(event);
658 | });
659 | try {
660 | if (isSafari) throw "";
661 | audio.srcObject = data;
662 | } catch (error) {
663 | audio.src = URL.createObjectURL(data);
664 | }
665 | audio.load();
666 | });
667 | };
668 | /**
669 | * Load an item in one of the following cases:
670 | * - item's "loader" option set as "Xml"
671 | * - item's "src" option extensions matching the loaders Map
672 | * - direct call of the method
673 | *
674 | * @param item Item to load (need a mimeType specified or default to "application/xml")
675 | * @returns Result of Response parsed as a document.
676 | */ this.loadXml = async (item)=>{
677 | if (!item.mimeType) {
678 | const extension = AsyncPreloader.getFileExtension(item.src);
679 | item = {
680 | ...item,
681 | mimeType: AsyncPreloader.getMimeType(LoaderKey.Xml, extension)
682 | };
683 | }
684 | if (!AsyncPreloader.domParser) {
685 | throw new Error("DomParser is not supported.");
686 | }
687 | const response = await AsyncPreloader.fetchItem(item);
688 | const data = await response.text();
689 | return AsyncPreloader.domParser.parseFromString(data, item.mimeType);
690 | };
691 | /**
692 | * Load a font via FontFace or check a font is loaded via FontFaceObserver instance
693 | *
694 | * @param item Item to load (id correspond to the font family name).
695 | * @returns Fulfilled value with FontFace instance or initial id if no src provided.
696 | */ this.loadFont = async (item)=>{
697 | const fontName = item.id || AsyncPreloader.getFileName(item.src);
698 | const options = item.fontOptions || {};
699 | if (!item.src) {
700 | const font = new FontFaceObserver(fontName, options.variant || {});
701 | await font.load(options.testString, options.timeout);
702 | return fontName;
703 | }
704 | const source = item.body === "arrayBuffer" ? await this.loadArrayBuffer({
705 | src: item.src
706 | }) : `url(${item.src})`;
707 | const font = new FontFace(fontName, source, options.descriptors);
708 | return await font.load().then((font)=>{
709 | document.fonts.add(font);
710 | return font;
711 | });
712 | };
713 | }
714 | // Utils
715 | /**
716 | * Fetch wrapper for LoadItem
717 | *
718 | * @param item Item to fetch
719 | * @returns Fetch response
720 | */ static fetchItem(item) {
721 | return fetch(item.src, item.options || {});
722 | }
723 | /**
724 | * Get an object property by its path in the form 'a[0].b.c' or ['a', '0', 'b', 'c'].
725 | * Similar to [lodash.get](https://lodash.com/docs/4.17.5#get).
726 | *
727 | * @param object Object with nested properties
728 | * @param path Path to the desired property
729 | * @returns The returned object property
730 | */ static getProp(object, path) {
731 | const p = Array.isArray(path) ? path : path.split(".").filter((index)=>index.length);
732 | if (!p.length) return object;
733 | return AsyncPreloader.getProp(object[p.shift()], p);
734 | }
735 | /**
736 | * Get file extension
737 | *
738 | * @param path
739 | * @returns
740 | */ static getFileExtension(path) {
741 | return (path?.match(/[^\\/]\.([^.\\/]+)$/) || [
742 | null
743 | ]).pop();
744 | }
745 | /**
746 | * Get file base name
747 | *
748 | * @param path
749 | * @returns
750 | */ static getFileBaseName(path) {
751 | return path.split(/[\\/]/).pop();
752 | }
753 | /**
754 | * Get file name
755 | *
756 | * @param path
757 | * @returns
758 | */ static getFileName(path) {
759 | return AsyncPreloader.getFileBaseName(path).split(".").slice(0, -1).join(".") || path;
760 | }
761 | /**
762 | * Retrieve loader key from extension (when the loader option isn't specified in the LoadItem)
763 | *
764 | * @param extension
765 | * @returns
766 | */ static getLoaderKey(extension) {
767 | const loader = Array.from(AsyncPreloader.loaders).find((loader)=>loader[1].extensions.includes(extension));
768 | return loader ? loader[0] : LoaderKey.Text;
769 | }
770 | /**
771 | * Retrieve mime type from extension
772 | *
773 | * @param loaderKey
774 | * @param extension
775 | * @returns
776 | */ static getMimeType(loaderKey, extension) {
777 | const loader = AsyncPreloader.loaders.get(loaderKey);
778 | return loader.mimeType[extension] || loader.defaultMimeType;
779 | }
780 | }
781 | /**
782 | * Loader types and the extensions they handle
783 | *
784 | * Allows the omission of the loader key in a {@link LoadItem.loader} for some generic extensions
785 | */ AsyncPreloader.loaders = new Map().set(LoaderKey.Text, {
786 | extensions: [
787 | "txt"
788 | ]
789 | }).set(LoaderKey.Json, {
790 | extensions: [
791 | "json"
792 | ]
793 | }).set(LoaderKey.Image, {
794 | extensions: [
795 | "jpeg",
796 | "jpg",
797 | "gif",
798 | "png",
799 | "webp"
800 | ]
801 | }).set(LoaderKey.Video, {
802 | extensions: [
803 | "webm",
804 | "ogg",
805 | "mp4"
806 | ]
807 | }).set(LoaderKey.Audio, {
808 | extensions: [
809 | "webm",
810 | "ogg",
811 | "mp3",
812 | "wav",
813 | "flac"
814 | ]
815 | }).set(LoaderKey.Xml, {
816 | extensions: [
817 | "xml",
818 | "svg",
819 | "html"
820 | ],
821 | mimeType: {
822 | xml: "text/xml",
823 | svg: "image/svg+xml",
824 | html: "text/html"
825 | },
826 | defaultMimeType: "text/xml"
827 | }).set(LoaderKey.Font, {
828 | extensions: [
829 | "woff2",
830 | "woff",
831 | "ttf",
832 | "otf",
833 | "eot"
834 | ]
835 | });
836 | /**
837 | * DOMParser instance for the XML loader
838 | */ AsyncPreloader.domParser = typeof DOMParser !== "undefined" && new DOMParser();
839 | const AsyncPreloaderInstance = new AsyncPreloader();
840 |
841 | export { AsyncPreloader, AsyncPreloaderInstance as default };
842 |
--------------------------------------------------------------------------------
/web_modules/import-map.json:
--------------------------------------------------------------------------------
1 | {
2 | "imports": {
3 | "async-preloader": "./async-preloader.js",
4 | "cameras": "./cameras.js",
5 | "es-module-shims": "./es-module-shims.js",
6 | "es-module-shims/debug": "./es-module-shims/debug.js",
7 | "es-module-shims/wasm": "./es-module-shims/wasm.js",
8 | "es-module-shims/typescript-transform": "./es-module-shims/typescript-transform.js",
9 | "gl-matrix": "./gl-matrix.js",
10 | "pex-context": "./pex-context.js",
11 | "pex-geom": "./pex-geom.js",
12 | "tweakpane": "./tweakpane.js",
13 | "typed-array-interleave": "./typed-array-interleave.js"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/web_modules/pex-geom.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Prints a vector to a string.
3 | * @param {import("./types.js").vec2} a
4 | * @param {number} [precision=4]
5 | * @returns {string}
6 | */ function toString$5(a, precision) {
7 | if (precision === void 0) precision = 4;
8 | const scale = 10 ** precision;
9 | // prettier-ignore
10 | return `[${Math.floor(a[0] * scale) / scale}, ${Math.floor(a[1] * scale) / scale}]`;
11 | }
12 |
13 | /** @module vec3 */ /**
14 | * Returns a new vec3 at 0, 0, 0.
15 | * @returns {import("./types.js").vec3}
16 | */ function create$4() {
17 | return [
18 | 0,
19 | 0,
20 | 0
21 | ];
22 | }
23 | /**
24 | * Sets a vector to another vector.
25 | * @param {import("./types.js").vec3} a
26 | * @param {import("./types.js").vec3} b
27 | * @returns {import("./types.js").vec3}
28 | */ function set$2(a, b) {
29 | a[0] = b[0];
30 | a[1] = b[1];
31 | a[2] = b[2];
32 | return a;
33 | }
34 | /**
35 | * Adds a vector to another.
36 | * @param {import("./types.js").vec3} a
37 | * @param {import("./types.js").vec3} b
38 | * @returns {import("./types.js").vec3}
39 | */ function add(a, b) {
40 | a[0] += b[0];
41 | a[1] += b[1];
42 | a[2] += b[2];
43 | return a;
44 | }
45 | /**
46 | * Subtracts a vector from another.
47 | * @param {import("./types.js").vec3} a
48 | * @param {import("./types.js").vec3} b
49 | * @returns {import("./types.js").vec3}
50 | */ function sub(a, b) {
51 | a[0] -= b[0];
52 | a[1] -= b[1];
53 | a[2] -= b[2];
54 | return a;
55 | }
56 | /**
57 | * Scales a vector by a number.
58 | * @param {import("./types.js").vec3} a
59 | * @param {number} s
60 | * @returns {import("./types.js").vec3}
61 | */ function scale$1(a, s) {
62 | a[0] *= s;
63 | a[1] *= s;
64 | a[2] *= s;
65 | return a;
66 | }
67 | /**
68 | * Calculates the dot product of two vectors.
69 | * @param {import("./types.js").vec3} a
70 | * @param {import("./types.js").vec3} b
71 | * @returns {number}
72 | */ function dot(a, b) {
73 | return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
74 | }
75 | /**
76 | * Calculates the cross product of two vectors.
77 | * @param {import("./types.js").vec3} a
78 | * @param {import("./types.js").vec3} b
79 | * @returns {import("./types.js").vec3}
80 | */ function cross(a, b) {
81 | const x = a[0];
82 | const y = a[1];
83 | const z = a[2];
84 | const vx = b[0];
85 | const vy = b[1];
86 | const vz = b[2];
87 | a[0] = y * vz - vy * z;
88 | a[1] = z * vx - vz * x;
89 | a[2] = x * vy - vx * y;
90 | return a;
91 | }
92 | /**
93 | * Calculates the length of a vector.
94 | * @param {import("./types.js").vec3} a
95 | * @returns {number}
96 | */ function length(a) {
97 | const x = a[0];
98 | const y = a[1];
99 | const z = a[2];
100 | return Math.sqrt(x * x + y * y + z * z);
101 | }
102 | /**
103 | * Normalises a vector.
104 | * @param {import("./types.js").vec3} a
105 | * @returns {import("./types.js").vec3}
106 | */ function normalize(a) {
107 | const x = a[0];
108 | const y = a[1];
109 | const z = a[2];
110 | let l = Math.sqrt(x * x + y * y + z * z);
111 | l = 1 / (l || 1);
112 | a[0] *= l;
113 | a[1] *= l;
114 | a[2] *= l;
115 | return a;
116 | }
117 | /**
118 | * Prints a vector to a string.
119 | * @param {import("./types.js").vec3} a
120 | * @param {number} [precision=4]
121 | * @returns {string}
122 | */ function toString$4(a, precision) {
123 | if (precision === void 0) precision = 4;
124 | const scale = 10 ** precision;
125 | // prettier-ignore
126 | return `[${Math.floor(a[0] * scale) / scale}, ${Math.floor(a[1] * scale) / scale}, ${Math.floor(a[2] * scale) / scale}]`;
127 | }
128 |
129 | /**
130 | * Sets a vector components.
131 | * @param {import("./types.js").avec3} a
132 | * @param {number} i
133 | * @param {number} x
134 | * @param {number} y
135 | * @param {number} z
136 | */ function set3(a, i, x, y, z) {
137 | a[i * 3] = x;
138 | a[i * 3 + 1] = y;
139 | a[i * 3 + 2] = z;
140 | }
141 |
142 | /**
143 | * Creates a new bounding box.
144 | * @returns {import("./types.js").aabb}
145 | */ function create$3() {
146 | // [min, max]
147 | return [
148 | [
149 | Infinity,
150 | Infinity,
151 | Infinity
152 | ],
153 | [
154 | -Infinity,
155 | -Infinity,
156 | -Infinity
157 | ]
158 | ];
159 | }
160 | /**
161 | * Reset a bounding box.
162 | * @param {import("./types.js").aabb} a
163 | * @returns {import("./types.js").rect}
164 | */ function empty$1(a) {
165 | a[0][0] = Infinity;
166 | a[0][1] = Infinity;
167 | a[0][2] = Infinity;
168 | a[1][0] = -Infinity;
169 | a[1][1] = -Infinity;
170 | a[1][2] = -Infinity;
171 | return a;
172 | }
173 | /**
174 | * Copies a bounding box.
175 | * @param {import("./types.js").aabb} a
176 | * @returns {import("./types.js").aabb}
177 | */ function copy$1(a) {
178 | return [
179 | a[0].slice(),
180 | a[1].slice()
181 | ];
182 | }
183 | /**
184 | * Sets a bounding box to another.
185 | * @param {import("./types.js").aabb} a
186 | * @param {import("./types.js").aabb} b
187 | * @returns {import("./types.js").aabb}
188 | */ function set$1(a, b) {
189 | a[0][0] = b[0][0];
190 | a[0][1] = b[0][1];
191 | a[0][2] = b[0][2];
192 | a[1][0] = b[1][0];
193 | a[1][1] = b[1][1];
194 | a[1][2] = b[1][2];
195 | return a;
196 | }
197 | /**
198 | * Checks if a bounding box is empty.
199 | * @param {import("./types.js").aabb} a
200 | * @returns {boolean}
201 | */ function isEmpty$1(a) {
202 | return a[0][0] > a[1][0] || a[0][1] > a[1][1] || a[0][2] > a[1][2];
203 | }
204 | /**
205 | * Updates a bounding box from a list of points.
206 | * @param {import("./types.js").aabb} a
207 | * @param {import("./types.js").vec3[] | import("./types.js").TypedArray} points
208 | * @returns {import("./types.js").aabb}
209 | */ function fromPoints$1(a, points) {
210 | const isFlatArray = !points[0]?.length;
211 | const l = points.length / (isFlatArray ? 3 : 1);
212 | for(let i = 0; i < l; i++){
213 | if (isFlatArray) {
214 | includePoint$1(a, points, i * 3);
215 | } else {
216 | includePoint$1(a, points[i]);
217 | }
218 | }
219 | return a;
220 | }
221 | /**
222 | * Returns a list of 8 points from a bounding box.
223 | * @param {import("./types.js").aabb} a
224 | * @param {import("./types.js").vec3[]} [points]
225 | * @returns {import("./types.js").vec3[]}
226 | */ function getCorners$1(a, points) {
227 | if (points === void 0) points = Array.from({
228 | length: 8
229 | }, ()=>[]);
230 | set3(points[0], 0, a[0][0], a[0][1], a[0][2]);
231 | set3(points[1], 0, a[1][0], a[0][1], a[0][2]);
232 | set3(points[2], 0, a[1][0], a[0][1], a[1][2]);
233 | set3(points[3], 0, a[0][0], a[0][1], a[1][2]);
234 | set3(points[4], 0, a[0][0], a[1][1], a[0][2]);
235 | set3(points[5], 0, a[1][0], a[1][1], a[0][2]);
236 | set3(points[6], 0, a[1][0], a[1][1], a[1][2]);
237 | set3(points[7], 0, a[0][0], a[1][1], a[1][2]);
238 | return points;
239 | }
240 | /**
241 | * Returns the center of a bounding box.
242 | * @param {import("./types.js").aabb} a
243 | * @param {import("./types.js").vec3} out
244 | * @returns {import("./types.js").vec3}
245 | */ function center$1(a, out) {
246 | if (out === void 0) out = [
247 | 0,
248 | 0,
249 | 0
250 | ];
251 | out[0] = (a[0][0] + a[1][0]) / 2;
252 | out[1] = (a[0][1] + a[1][1]) / 2;
253 | out[2] = (a[0][2] + a[1][2]) / 2;
254 | return out;
255 | }
256 | /**
257 | * Returns the size of a bounding box.
258 | * @param {import("./types.js").aabb} a
259 | * @param {import("./types.js").vec3} out
260 | * @returns {import("./types.js").vec3}
261 | */ function size$1(a, out) {
262 | if (out === void 0) out = [
263 | 0,
264 | 0,
265 | 0
266 | ];
267 | out[0] = Math.abs(a[1][0] - a[0][0]);
268 | out[1] = Math.abs(a[1][1] - a[0][1]);
269 | out[2] = Math.abs(a[1][2] - a[0][2]);
270 | return out;
271 | }
272 | /**
273 | * Checks if a point is inside a bounding box.
274 | * @param {import("./types.js").aabb} a
275 | * @param {import("./types.js").vec3} p
276 | * @returns {boolean}
277 | */ function containsPoint$1(a, param) {
278 | let [x, y, z] = param;
279 | 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];
280 | }
281 | /**
282 | * Includes a bounding box in another.
283 | * @param {import("./types.js").aabb} a
284 | * @param {import("./types.js").aabb} b
285 | * @returns {import("./types.js").aabb}
286 | */ function includeAABB(a, b) {
287 | if (isEmpty$1(a)) {
288 | set$1(a, b);
289 | } else if (isEmpty$1(b)) ; else {
290 | a[0][0] = Math.min(a[0][0], b[0][0]);
291 | a[0][1] = Math.min(a[0][1], b[0][1]);
292 | a[0][2] = Math.min(a[0][2], b[0][2]);
293 | a[1][0] = Math.max(a[1][0], b[1][0]);
294 | a[1][1] = Math.max(a[1][1], b[1][1]);
295 | a[1][2] = Math.max(a[1][2], b[1][2]);
296 | }
297 | return a;
298 | }
299 | /**
300 | * Includes a point in a bounding box.
301 | * @param {import("./types.js").aabb} a
302 | * @param {import("./types.js").vec3} p
303 | * @param {number} [i=0] offset in the point array
304 | * @returns {import("./types.js").vec3}
305 | */ function includePoint$1(a, p, i) {
306 | if (i === void 0) i = 0;
307 | a[0][0] = Math.min(a[0][0], p[i + 0]);
308 | a[0][1] = Math.min(a[0][1], p[i + 1]);
309 | a[0][2] = Math.min(a[0][2], p[i + 2]);
310 | a[1][0] = Math.max(a[1][0], p[i + 0]);
311 | a[1][1] = Math.max(a[1][1], p[i + 1]);
312 | a[1][2] = Math.max(a[1][2], p[i + 2]);
313 | return a;
314 | }
315 | /**
316 | * Prints a bounding box to a string.
317 | * @param {import("./types.js").aabb} a
318 | * @param {number} [precision=4]
319 | * @returns {string}
320 | */ function toString$3(a, precision) {
321 | if (precision === void 0) precision = 4;
322 | // prettier-ignore
323 | return `[${toString$4(a[0], precision)}, ${toString$4(a[1], precision)}]`;
324 | }
325 |
326 | var aabb = /*#__PURE__*/Object.freeze({
327 | __proto__: null,
328 | center: center$1,
329 | containsPoint: containsPoint$1,
330 | copy: copy$1,
331 | create: create$3,
332 | empty: empty$1,
333 | fromPoints: fromPoints$1,
334 | getCorners: getCorners$1,
335 | includeAABB: includeAABB,
336 | includePoint: includePoint$1,
337 | isEmpty: isEmpty$1,
338 | set: set$1,
339 | size: size$1,
340 | toString: toString$3
341 | });
342 |
343 | /**
344 | * Enum for different side values
345 | * @readonly
346 | * @enum {number}
347 | */ const Side = Object.freeze({
348 | OnPlane: 0,
349 | Same: -1,
350 | Opposite: 1
351 | });
352 | const TEMP_0$1 = create$4();
353 | /**
354 | * Creates a new plane
355 | * @returns {import("./types.js").plane}
356 | */ function create$2() {
357 | return [
358 | [
359 | 0,
360 | 0,
361 | 0
362 | ],
363 | [
364 | 0,
365 | 1,
366 | 0
367 | ]
368 | ];
369 | }
370 | /**
371 | * Returns on which side a point is.
372 | * @param {import("./types.js").plane} plane
373 | * @param {import("./types.js").vec3} point
374 | * @returns {number}
375 | */ function side(param, point) {
376 | let [planePoint, planeNormal] = param;
377 | set$2(TEMP_0$1, planePoint);
378 | sub(TEMP_0$1, point);
379 | normalize(TEMP_0$1);
380 | const dot$1 = dot(TEMP_0$1, planeNormal);
381 | if (dot$1 > 0) return Side.Opposite;
382 | if (dot$1 < 0) return Side.Same;
383 | return Side.OnPlane;
384 | }
385 | /**
386 | * Prints a plane to a string.
387 | * @param {import("./types.js").plane} a
388 | * @param {number} [precision=4]
389 | * @returns {string}
390 | */ function toString$2(a, precision) {
391 | if (precision === void 0) precision = 4;
392 | // prettier-ignore
393 | return `[${toString$4(a[0], precision)}, ${toString$4(a[1], precision)}]`;
394 | }
395 |
396 | var plane = /*#__PURE__*/Object.freeze({
397 | __proto__: null,
398 | Side: Side,
399 | create: create$2,
400 | side: side,
401 | toString: toString$2
402 | });
403 |
404 | /**
405 | * Enum for different intersections values
406 | * @readonly
407 | * @enum {number}
408 | */ const Intersections = Object.freeze({
409 | Intersect: 1,
410 | NoIntersect: 0,
411 | SamePlane: -1,
412 | Parallel: -2,
413 | TriangleDegenerate: -2
414 | });
415 | const TEMP_0 = create$4();
416 | const TEMP_1 = create$4();
417 | const TEMP_2 = create$4();
418 | const TEMP_3 = create$4();
419 | const TEMP_4 = create$4();
420 | const TEMP_5 = create$4();
421 | const TEMP_6 = create$4();
422 | const TEMP_7 = create$4();
423 | const EPSILON = 1e-6;
424 | /**
425 | * Creates a new ray
426 | * @returns {import("./types.js").ray}
427 | */ function create$1() {
428 | return [
429 | [
430 | 0,
431 | 0,
432 | 0
433 | ],
434 | [
435 | 0,
436 | 0,
437 | 1
438 | ]
439 | ];
440 | }
441 | /**
442 | * Determines if a ray intersect a plane and set intersection point
443 | * @see {@link https://www.cs.princeton.edu/courses/archive/fall00/cs426/lectures/raycast/sld017.htm}
444 | * @param {import("./types.js").ray} ray
445 | * @param {import("./types.js").plane} plane
446 | * @param {import("./types.js").vec3} out
447 | * @returns {number}
448 | */ function hitTestPlane(param, param1, out) {
449 | let [origin, direction] = param;
450 | let [point, normal] = param1;
451 | if (out === void 0) out = create$4();
452 | set$2(TEMP_0, origin);
453 | set$2(TEMP_1, direction);
454 | const dotDirectionNormal = dot(TEMP_1, normal);
455 | if (dotDirectionNormal === 0) return Intersections.SamePlane;
456 | set$2(TEMP_2, point);
457 | const t = dot(sub(TEMP_2, TEMP_0), normal) / dotDirectionNormal;
458 | if (t < 0) return Intersections.Parallel;
459 | set$2(out, add(TEMP_0, scale$1(TEMP_1, t)));
460 | return Intersections.Intersect;
461 | }
462 | /**
463 | * Determines if a ray intersect a triangle and set intersection point
464 | * @see {@link http://geomalgorithms.com/a06-_intersect-2.html#intersect3D_RayTriangle()}
465 | * @param {import("./types.js").ray} ray
466 | * @param {import("./types.js").triangle} triangle
467 | * @param {import("./types.js").vec3} out
468 | * @returns {number}
469 | */ function hitTestTriangle(param, param1, out) {
470 | let [origin, direction] = param;
471 | let [p0, p1, p2] = param1;
472 | if (out === void 0) out = create$4();
473 | // get triangle edge vectors and plane normal
474 | const u = sub(set$2(TEMP_0, p1), p0);
475 | const v = sub(set$2(TEMP_1, p2), p0);
476 | const n = cross(set$2(TEMP_2, u), v);
477 | if (length(n) < EPSILON) return Intersections.TriangleDegenerate;
478 | // ray vectors
479 | const w0 = sub(set$2(TEMP_3, origin), p0);
480 | // params to calc ray-plane intersect
481 | const a = -dot(n, w0);
482 | const b = dot(n, direction);
483 | if (Math.abs(b) < EPSILON) {
484 | if (a === 0) return Intersections.SamePlane;
485 | return Intersections.NoIntersect;
486 | }
487 | // get intersect point of ray with triangle plane
488 | const r = a / b;
489 | // ray goes away from triangle
490 | if (r < -1e-6) return Intersections.NoIntersect;
491 | // for a segment, also test if (r > 1.0) => no intersect
492 | // intersect point of ray and plane
493 | const I = add(set$2(TEMP_4, origin), scale$1(set$2(TEMP_5, direction), r));
494 | const uu = dot(u, u);
495 | const uv = dot(u, v);
496 | const vv = dot(v, v);
497 | const w = sub(set$2(TEMP_6, I), p0);
498 | const wu = dot(w, u);
499 | const wv = dot(w, v);
500 | const D = uv * uv - uu * vv;
501 | // get and test parametric coords
502 | const s = (uv * wv - vv * wu) / D;
503 | if (s < -1e-6 || s > 1.0 + EPSILON) return Intersections.NoIntersect;
504 | const t = (uv * wu - uu * wv) / D;
505 | if (t < -1e-6 || s + t > 1.0 + EPSILON) return Intersections.NoIntersect;
506 | set$2(out, u);
507 | scale$1(out, s);
508 | add(out, scale$1(set$2(TEMP_7, v), t));
509 | add(out, p0);
510 | return Intersections.Intersect;
511 | }
512 | /**
513 | * Determines if a ray intersect an AABB bounding box
514 | * @see {@link http://gamedev.stackexchange.com/questions/18436/most-efficient-aabb-vs-ray-collision-algorithms}
515 | * @param {import("./types.js").ray} ray
516 | * @param {import("./types.js").aabb} aabb
517 | * @returns {boolean}
518 | */ function hitTestAABB(param, aabb) {
519 | let [origin, direction] = param;
520 | const dirFracx = 1.0 / direction[0];
521 | const dirFracy = 1.0 / direction[1];
522 | const dirFracz = 1.0 / direction[2];
523 | const min = aabb[0];
524 | const max = aabb[1];
525 | const minx = min[0];
526 | const miny = min[1];
527 | const minz = min[2];
528 | const maxx = max[0];
529 | const maxy = max[1];
530 | const maxz = max[2];
531 | const t1 = (minx - origin[0]) * dirFracx;
532 | const t2 = (maxx - origin[0]) * dirFracx;
533 | const t3 = (miny - origin[1]) * dirFracy;
534 | const t4 = (maxy - origin[1]) * dirFracy;
535 | const t5 = (minz - origin[2]) * dirFracz;
536 | const t6 = (maxz - origin[2]) * dirFracz;
537 | const tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)), Math.min(t5, t6));
538 | const tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)), Math.max(t5, t6));
539 | return !(tmax < 0 || tmin > tmax);
540 | }
541 | /**
542 | * Alias for {@link hitTestAABB}
543 | * @function
544 | */ const intersectsAABB = hitTestAABB;
545 | /**
546 | * Prints a plane to a string.
547 | * @param {import("./types.js").ray} a
548 | * @param {number} [precision=4]
549 | * @returns {string}
550 | */ function toString$1(a, precision) {
551 | if (precision === void 0) precision = 4;
552 | // prettier-ignore
553 | return `[${toString$4(a[0], precision)}, ${toString$4(a[1], precision)}]`;
554 | }
555 |
556 | var ray = /*#__PURE__*/Object.freeze({
557 | __proto__: null,
558 | Intersections: Intersections,
559 | create: create$1,
560 | hitTestAABB: hitTestAABB,
561 | hitTestPlane: hitTestPlane,
562 | hitTestTriangle: hitTestTriangle,
563 | intersectsAABB: intersectsAABB,
564 | toString: toString$1
565 | });
566 |
567 | /**
568 | * Creates a new rectangle.
569 | * @returns {import("./types.js").rect}
570 | */ function create() {
571 | return [
572 | [
573 | Infinity,
574 | Infinity
575 | ],
576 | [
577 | -Infinity,
578 | -Infinity
579 | ]
580 | ];
581 | }
582 | /**
583 | * Reset a rectangle.
584 | * @param {import("./types.js").rect} a
585 | * @returns {import("./types.js").rect}
586 | */ function empty(a) {
587 | a[0][0] = a[0][1] = Infinity;
588 | a[1][0] = a[1][1] = -Infinity;
589 | return a;
590 | }
591 | /**
592 | * Copies a rectangle.
593 | * @param {import("./types.js").rect} a
594 | * @returns {import("./types.js").rect}
595 | */ function copy(a) {
596 | return [
597 | a[0].slice(),
598 | a[1].slice()
599 | ];
600 | }
601 | /**
602 | * Sets a rectangle to another.
603 | * @param {import("./types.js").rect} a
604 | * @param {import("./types.js").rect} b
605 | * @returns {import("./types.js").rect}
606 | */ function set(a, b) {
607 | a[0][0] = b[0][0];
608 | a[0][1] = b[0][1];
609 | a[1][0] = b[1][0];
610 | a[1][1] = b[1][1];
611 | return a;
612 | }
613 | /**
614 | * Checks if a rectangle is empty.
615 | * @param {import("./types.js").rect} a
616 | * @returns {boolean}
617 | */ function isEmpty(a) {
618 | return a[0][0] > a[1][0] || a[0][1] > a[1][1];
619 | }
620 | /**
621 | * Updates a rectangle from a list of points.
622 | * @param {import("./types.js").rect} a
623 | * @param {import("./types.js").vec2[] | import("./types.js").TypedArray} points
624 | * @returns {import("./types.js").rect}
625 | */ function fromPoints(a, points) {
626 | const isTypedArray = !Array.isArray(points);
627 | for(let i = 0; i < points.length / (isTypedArray ? 2 : 1); i++){
628 | includePoint(a, isTypedArray ? points.slice(i * 2) : points[i]);
629 | }
630 | return a;
631 | }
632 | /**
633 | * Returns a list of 4 points from a rectangle.
634 | * @param {import("./types.js").rect} a
635 | * @param {import("./types.js").vec2[]} points
636 | * @returns {import("./types.js").vec2[]}
637 | */ function getCorners(a, points) {
638 | if (points === void 0) points = [];
639 | points[0] = a[0].slice();
640 | points[1] = [
641 | a[0][1],
642 | a[1][0]
643 | ];
644 | points[2] = a[1].slice();
645 | points[3] = [
646 | a[1][0],
647 | a[0][1]
648 | ];
649 | return points;
650 | }
651 | /**
652 | * Scales a rectangle.
653 | * @param {import("./types.js").rect} a
654 | * @param {number} n
655 | * @returns {import("./types.js").rect}
656 | */ function scale(a, n) {
657 | a[0][0] *= n;
658 | a[0][1] *= n;
659 | a[1][0] *= n;
660 | a[1][1] *= n;
661 | return a;
662 | }
663 | /**
664 | * Sets the size of a rectangle using width and height.
665 | * @param {import("./types.js").rect} a
666 | * @param {import("./types.js").vec2} size
667 | * @returns {import("./types.js").rect}
668 | */ function setSize(a, size) {
669 | a[1][0] = a[0][0] + size[0];
670 | a[1][1] = a[0][1] + size[1];
671 | return a;
672 | }
673 | /**
674 | * Returns the size of a rectangle.
675 | * @param {import("./types.js").rect} a
676 | * @param {import("./types.js").vec2} out
677 | * @returns {import("./types.js").vec2}
678 | */ function size(a, out) {
679 | if (out === void 0) out = [];
680 | out[0] = width(a);
681 | out[1] = height(a);
682 | return out;
683 | }
684 | /**
685 | * Returns the width of a rectangle.
686 | * @param {import("./types.js").rect} a
687 | * @returns {number}
688 | */ function width(a) {
689 | return a[1][0] - a[0][0];
690 | }
691 | /**
692 | * Returns the height of a rectangle.
693 | * @param {import("./types.js").rect} a
694 | * @returns {number}
695 | */ function height(a) {
696 | return a[1][1] - a[0][1];
697 | }
698 | /**
699 | * Returns the aspect ratio of a rectangle.
700 | * @param {import("./types.js").rect} a
701 | * @returns {number}
702 | */ function aspectRatio(a) {
703 | return width(a) / height(a);
704 | }
705 | /**
706 | * Sets the position of a rectangle.
707 | * @param {import("./types.js").rect} a
708 | * @param {import("./types.js").vec2} p
709 | * @returns {import("./types.js").rect}
710 | */ function setPosition(a, param) {
711 | let [x, y] = param;
712 | const w = width(a);
713 | const h = height(a);
714 | a[0][0] = x;
715 | a[0][1] = y;
716 | a[1][0] = x + w;
717 | a[1][1] = y + h;
718 | return a;
719 | }
720 | /**
721 | * Returns the center of a rectangle.
722 | * @param {import("./types.js").rect} a
723 | * @param {import("./types.js").vec2} out
724 | * @returns {import("./types.js").rect}
725 | */ function center(a, out) {
726 | if (out === void 0) out = [];
727 | out[0] = a[0][0] + width(a) * 0.5;
728 | out[1] = a[0][1] + height(a) * 0.5;
729 | return out;
730 | }
731 | /**
732 | * Checks if a point is inside a rectangle.
733 | * @param {import("./types.js").rect} a
734 | * @param {import("./types.js").vec2} p
735 | * @returns {boolean}
736 | */ function containsPoint(a, param) {
737 | let [x, y] = param;
738 | return x >= a[0][0] && x <= a[1][0] && y >= a[0][1] && y <= a[1][1];
739 | }
740 | /**
741 | * Checks if a rectangle is inside another rectangle.
742 | * @param {import("./types.js").rect} a
743 | * @param {import("./types.js").rect} b
744 | * @returns {boolean}
745 | */ function containsRect(a, b) {
746 | return containsPoint(a, b[0]) && containsPoint(a, b[1]);
747 | }
748 | /**
749 | * Includes a point in a rectangle.
750 | * @param {import("./types.js").rect} a
751 | * @param {import("./types.js").vec2} p
752 | * @returns {import("./types.js").rect}
753 | */ function includePoint(a, param) {
754 | let [x, y] = param;
755 | const minx = a[0][0];
756 | const miny = a[0][1];
757 | const maxx = a[1][0];
758 | const maxy = a[1][1];
759 | a[0][0] = minx > x ? x : minx;
760 | a[0][1] = miny > y ? y : miny;
761 | a[1][0] = maxx < x ? x : maxx;
762 | a[1][1] = maxy < y ? y : maxy;
763 | return a;
764 | }
765 | /**
766 | * Includes a rectangle in another rectangle.
767 | * @param {import("./types.js").rect} a
768 | * @param {import("./types.js").rect} b
769 | * @returns {import("./types.js").rect}
770 | */ function includeRect(a, b) {
771 | includePoint(a, b[0]);
772 | includePoint(a, b[1]);
773 | return a;
774 | }
775 | /**
776 | * Maps a point into the dimensions of a rectangle.
777 | * @param {import("./types.js").rect} a
778 | * @param {import("./types.js").vec2} p
779 | * @returns {import("./types.js").vec2}
780 | */ function mapPoint(a, p) {
781 | const minx = a[0][0];
782 | const miny = a[0][1];
783 | const maxx = a[1][0];
784 | const maxy = a[1][1];
785 | p[0] = Math.max(minx, Math.min(p[0], maxx)) - minx;
786 | p[1] = Math.max(miny, Math.min(p[1], maxy)) - miny;
787 | return p;
788 | }
789 | /**
790 | * Clamps a point into the dimensions of a rectangle.
791 | * @param {import("./types.js").rect} a
792 | * @param {import("./types.js").vec2} p
793 | * @returns {import("./types.js").vec2}
794 | */ function clampPoint(a, p) {
795 | const minx = a[0][0];
796 | const miny = a[0][1];
797 | const maxx = a[1][0];
798 | const maxy = a[1][1];
799 | p[0] = Math.max(minx, Math.min(p[0], maxx));
800 | p[1] = Math.max(miny, Math.min(p[1], maxy));
801 | return p;
802 | }
803 | /**
804 | * Prints a rect to a string.
805 | * @param {import("./types.js").rect} a
806 | * @param {number} [precision=4]
807 | * @returns {string}
808 | */ function toString(a, precision) {
809 | if (precision === void 0) precision = 4;
810 | // prettier-ignore
811 | return `[${toString$5(a[0], precision)}, ${toString$5(a[1], precision)}]`;
812 | }
813 |
814 | var rect = /*#__PURE__*/Object.freeze({
815 | __proto__: null,
816 | aspectRatio: aspectRatio,
817 | center: center,
818 | clampPoint: clampPoint,
819 | containsPoint: containsPoint,
820 | containsRect: containsRect,
821 | copy: copy,
822 | create: create,
823 | empty: empty,
824 | fromPoints: fromPoints,
825 | getCorners: getCorners,
826 | height: height,
827 | includePoint: includePoint,
828 | includeRect: includeRect,
829 | isEmpty: isEmpty,
830 | mapPoint: mapPoint,
831 | scale: scale,
832 | set: set,
833 | setPosition: setPosition,
834 | setSize: setSize,
835 | size: size,
836 | toString: toString,
837 | width: width
838 | });
839 |
840 | export { aabb, plane, ray, rect };
841 |
--------------------------------------------------------------------------------
/web_modules/typed-array-interleave.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module typedArrayInterleave
3 | */ /**
4 | * Interleave n typed arrays
5 | *
6 | * @alias module:typedArrayInterleave
7 | * @param {TypedArray} ResultConstructor Returned typed array constructor
8 | * @param {Array} elements Number of elements to group for each typed array
9 | * @param {...TypedArray} arrays Arrays to interleave
10 | * @returns {TypedArray}
11 | */ function typedArrayInterleave(ResultConstructor, elements) {
12 | for(var _len = arguments.length, arrays = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++){
13 | arrays[_key - 2] = arguments[_key];
14 | }
15 | const totalLength = arrays.reduce((total, arr)=>total + arr.length, 0);
16 | const result = new ResultConstructor(totalLength);
17 | const stride = elements.reduce((a, b)=>a + b);
18 | for(let i = 0; i < totalLength; i++){
19 | let offset = 0;
20 | for(let j = 0; j < elements.length; j++){
21 | for(let k = 0; k < elements[j]; k++){
22 | result[i * stride + offset] = arrays[j][elements[j] * i + k];
23 | offset++;
24 | }
25 | }
26 | }
27 | return result;
28 | }
29 |
30 | export { typedArrayInterleave as default };
31 |
--------------------------------------------------------------------------------