setActive(undefined)}
33 | />
34 | >
35 | );
36 | }
37 |
38 | export default SandboxOverlay;
39 |
--------------------------------------------------------------------------------
/demo/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | cursor: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48Y2lyY2xlIGN4PSIxNiIgY3k9IjE2IiByPSIxMCIgZmlsbD0id2hpdGUiLz48L3N2Zz4="),
9 | auto;
10 | }
11 |
12 | code {
13 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
14 | monospace;
15 | }
16 |
17 | html,
18 | body {
19 | width: 100vw;
20 | height: 100vh;
21 | margin: 0;
22 | overflow: hidden;
23 | }
24 |
25 | canvas {
26 | width: 100vw;
27 | height: 100vh;
28 | }
29 |
30 | .logo {
31 | position: fixed;
32 | top: 6vh;
33 | left: 6vh;
34 | z-index: 2;
35 |
36 | text-decoration: none;
37 | }
38 |
39 | .logo img {
40 | display: block;
41 | }
42 |
43 | .logo div {
44 | font-weight: normal;
45 | color: white;
46 | text-decoration: none;
47 | font-family: monospace;
48 |
49 | margin-left: 0.5rem;
50 | margin-top: 1rem;
51 |
52 | font-size: 12px;
53 | font-weight: bold;
54 |
55 | display: inline-block;
56 |
57 | font-family: "Inter var", Inter, sans-serif;
58 | }
59 |
60 | .logo:hover div {
61 | background-color: #000;
62 | color: white;
63 | }
64 |
65 | .pmndrs-menu {
66 | top: initial !important;
67 | height: auto;
68 | z-index: 2;
69 | bottom: 0;
70 | }
71 |
72 | .pmndrs-menu > div {
73 | padding-left: 0 !important;
74 | padding-right: 50px !important;
75 | }
76 |
77 | .backdrop {
78 | background-color: #000;
79 | position: fixed;
80 | opacity: 0;
81 | z-index: -1;
82 |
83 | inset: 0;
84 |
85 | transition: opacity 0.3s ease;
86 | }
87 |
88 | .backdrop.visible {
89 | opacity: 0.9;
90 | z-index: 3;
91 | }
92 |
--------------------------------------------------------------------------------
/demo/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/demo/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React, { lazy, Suspense } from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | import { Route } from "wouter";
5 |
6 | import "@pmndrs/branding/styles.css";
7 | import { Footer } from "@pmndrs/branding";
8 |
9 | import App from "./App";
10 |
11 | import SandboxOverlay from "./components/SandboxOverlay";
12 | import Header from "./components/Header";
13 |
14 | const Sandbox = lazy(() => import("./components/Sandbox"));
15 | const DevBox = lazy(() => import("./sandboxes/sorting/src/App"));
16 |
17 | import "./index.css";
18 |
19 | ReactDOM.render(
20 |
21 |
22 |
23 |
24 |
25 | {/* @ts-ignore */}
26 |
27 |
28 |
29 | {/* Use this route for local development */}
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | ,
38 | document.getElementById("root")
39 | );
40 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/circumcircle/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "maath-demo-circumcircle",
3 | "version": "1.0.0",
4 | "description": "React and TypeScript example starter project",
5 | "keywords": [
6 | "typescript",
7 | "react",
8 | "starter",
9 | "maath",
10 | "r3f"
11 | ],
12 | "main": "src/index.tsx",
13 | "dependencies": {
14 | "react": "17.0.2",
15 | "react-dom": "17.0.2",
16 | "react-scripts": "4.0.3",
17 | "@react-three/drei": "^7.22.0",
18 | "@react-three/fiber": "7.0.9",
19 | "three": "0.134.0",
20 | "maath": "latest"
21 | },
22 | "devDependencies": {
23 | "@types/react": "17.0.20",
24 | "@types/react-dom": "17.0.9",
25 | "typescript": "4.4.2",
26 | "@types/three": "^0.133.1"
27 | },
28 | "scripts": {
29 | "start": "react-scripts start",
30 | "build": "react-scripts build",
31 | "test": "react-scripts test --env=jsdom",
32 | "eject": "react-scripts eject"
33 | },
34 | "browserslist": [
35 | ">0.2%",
36 | "not dead",
37 | "not ie <= 11",
38 | "not op_mini all"
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/circumcircle/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
14 |
15 |
16 |
24 |
Maath Sandbox
25 |
39 |
40 |
41 |
42 |
43 |
44 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/circumcircle/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from "react";
2 |
3 | import { useFrame } from "@react-three/fiber";
4 | import { EllipseCurve } from "three";
5 |
6 | import * as random from "maath/random";
7 | import * as buffer from "maath/buffer";
8 | import * as triangle from "maath/triangle";
9 | import * as threeUtils from "maath/three";
10 |
11 | import { Points } from "@react-three/drei";
12 |
13 | function TrianglesDemo() {
14 | const pointsRef = useRef
(null!);
15 | const lineRef = useRef(null!);
16 | const circleRef = useRef(null!);
17 | const circle2Ref = useRef(null!);
18 |
19 | const [{ points, pointsB, final }] = useState(() => {
20 | // generate two sets of 4 points in a circle, that we'll use for our circumcircle visualization
21 | const points = random.inCircle(new Float32Array(4 * 2), { radius: 0.4 });
22 | const pointsB = random.inCircle(points.slice(0), { radius: 0.4 });
23 | const final = pointsB.slice(0);
24 |
25 | return { points, pointsB, final };
26 | });
27 |
28 | useFrame(({ clock }, dt) => {
29 | const t = Math.sin(clock.getElapsedTime()) + 1 * 0.5;
30 |
31 | // lerp between the two different sets of point, thus animating the two visualized triangles
32 | buffer.lerp(points, pointsB, final, t);
33 |
34 | // get the points as an array, to use them to create the geometries
35 | const [a, b, c, d] = threeUtils.bufferToVectors(final, 2);
36 |
37 | // prettier-ignore
38 | lineRef.current.geometry.setFromPoints([
39 | a , b , c, a,
40 | b, c, d, b
41 | ], 2);
42 |
43 | {
44 | // get the circumcircle for the first triangle
45 | const circle = triangle.getCircumcircle([
46 | [a.x, a.y],
47 | [b.x, b.y],
48 | [c.x, c.y],
49 | ]);
50 |
51 | const curve = new EllipseCurve(
52 | circle!.x,
53 | circle!.y,
54 | circle!.r,
55 | circle!.r,
56 | 0,
57 | 2 * Math.PI,
58 | false,
59 | 0
60 | );
61 |
62 | circleRef.current.geometry.setFromPoints(curve.getPoints(128));
63 | }
64 |
65 | {
66 | // get the circumcircle for the second triangle
67 | const circle = triangle.getCircumcircle([
68 | [b.x, b.y],
69 | [c.x, c.y],
70 | [d.x, d.y],
71 | ]);
72 |
73 | const curve = new EllipseCurve(
74 | circle!.x,
75 | circle!.y,
76 | circle!.r,
77 | circle!.r,
78 | 0,
79 | 2 * Math.PI,
80 | false,
81 | 0
82 | );
83 |
84 | circle2Ref.current.geometry.setFromPoints(curve.getPoints(128));
85 | }
86 | });
87 |
88 | return (
89 | <>
90 | {/* @ts-ignore */}
91 |
92 |
93 |
94 |
95 | {/* @ts-ignore */}
96 |
97 |
98 |
99 |
100 | {/* @ts-ignore */}
101 |
102 |
103 |
104 |
105 | {/* @ts-ignore */}
106 |
107 |
108 |
109 |
110 | >
111 | );
112 | }
113 |
114 | export default TrianglesDemo;
115 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/circumcircle/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { Canvas } from "@react-three/fiber";
2 | import React from "react";
3 | import ReactDOM from "react-dom";
4 | import App from "./App";
5 |
6 | const rootElement = document.getElementById("root");
7 | ReactDOM.render(
8 |
9 |
13 | ,
14 | rootElement
15 | );
16 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/circumcircle/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src/**/*"],
3 | "compilerOptions": {
4 | "strict": true,
5 | "esModuleInterop": true,
6 | "lib": ["dom", "es2015"],
7 | "jsx": "react-jsx"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/convex-hull/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "maath-demo-convex-hull",
3 | "version": "1.0.0",
4 | "description": "React and TypeScript example starter project",
5 | "keywords": [
6 | "typescript",
7 | "react",
8 | "starter",
9 | "maath",
10 | "r3f"
11 | ],
12 | "main": "src/index.tsx",
13 | "dependencies": {
14 | "react": "17.0.2",
15 | "react-dom": "17.0.2",
16 | "react-scripts": "4.0.3",
17 | "@react-three/drei": "^7.22.0",
18 | "@react-three/fiber": "7.0.9",
19 | "three": "0.134.0",
20 | "maath": "latest"
21 | },
22 | "devDependencies": {
23 | "@types/react": "17.0.20",
24 | "@types/react-dom": "17.0.9",
25 | "typescript": "4.4.2",
26 | "@types/three": "^0.133.1"
27 | },
28 | "scripts": {
29 | "start": "react-scripts start",
30 | "build": "react-scripts build",
31 | "test": "react-scripts test --env=jsdom",
32 | "eject": "react-scripts eject"
33 | },
34 | "browserslist": [
35 | ">0.2%",
36 | "not dead",
37 | "not ie <= 11",
38 | "not op_mini all"
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/convex-hull/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
14 |
15 |
16 |
24 | Maath Sandbox
25 |
39 |
40 |
41 |
42 |
43 |
44 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/convex-hull/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from "react";
2 |
3 | import { useFrame } from "@react-three/fiber";
4 |
5 | import * as random from "maath/random";
6 | import * as buffer from "maath/buffer";
7 | import * as misc from "maath/misc";
8 | import * as v2 from "maath/vector2";
9 | import * as threeUtils from "maath/three";
10 |
11 | import { Points } from "@react-three/drei";
12 | import { Mesh, Vector } from "three";
13 |
14 | export default function ConvexHullDemo() {
15 | const pointsRef = useRef(null!);
16 | const $line = useRef(null!);
17 | const $line2 = useRef(null!);
18 |
19 | const [{ points, randomizedPoints, final }] = useState(() => {
20 | const points = random.inRect(new Float32Array(1_000 * 2)) as Float32Array;
21 | const randomizedPoints = random.inCircle(points.slice(0), { radius: 1 });
22 | const final = points.slice(0);
23 |
24 | return { points, randomizedPoints, final };
25 | });
26 |
27 | useFrame(({ clock }) => {
28 | const t = misc.remap(Math.sin(clock.getElapsedTime()), [-1, 1], [0, 1]);
29 |
30 | buffer.lerp(points, randomizedPoints, final, t);
31 |
32 | const convexHullPoints = misc.convexHull(
33 | threeUtils.bufferToVectors(final, 2) as THREE.Vector2[]
34 | );
35 |
36 | const convexHullBuffer = threeUtils.vectorsToBuffer(convexHullPoints);
37 |
38 | let center = v2.zero();
39 | center = buffer.center(convexHullBuffer, 2);
40 | center = v2.scale(center, 1 / convexHullBuffer.length / 2);
41 |
42 | buffer.expand(convexHullBuffer, 2, { center, distance: 1 * t });
43 |
44 | pointsRef.current.geometry.attributes.position.needsUpdate = true;
45 |
46 | const expandedBufferArr = threeUtils.bufferToVectors(convexHullBuffer, 2);
47 |
48 | $line.current.geometry.setFromPoints(
49 | [...convexHullPoints, convexHullPoints[0]],
50 | 2
51 | );
52 |
53 | $line2.current.geometry.setFromPoints(
54 | [...expandedBufferArr, expandedBufferArr[0]],
55 | 2
56 | );
57 | });
58 |
59 | return (
60 | <>
61 | {/* @ts-ignore */}
62 |
63 |
64 |
65 | {/* @ts-ignore */}
66 |
67 |
68 |
69 |
70 |
71 | {/* @ts-ignore */}
72 |
73 |
74 |
75 |
76 | >
77 | );
78 | }
79 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/convex-hull/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { Canvas } from "@react-three/fiber";
2 | import React from "react";
3 | import ReactDOM from "react-dom";
4 | import App from "./App";
5 |
6 | const rootElement = document.getElementById("root");
7 | ReactDOM.render(
8 |
9 |
13 | ,
14 | rootElement
15 | );
16 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/convex-hull/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src/**/*"],
3 | "compilerOptions": {
4 | "strict": true,
5 | "esModuleInterop": true,
6 | "lib": ["dom", "es2015"],
7 | "jsx": "react-jsx"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/points/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "maath-demo-points",
3 | "version": "1.0.0",
4 | "description": "React and TypeScript example starter project",
5 | "keywords": [
6 | "typescript",
7 | "react",
8 | "starter",
9 | "maath",
10 | "r3f"
11 | ],
12 | "main": "src/index.tsx",
13 | "dependencies": {
14 | "react": "17.0.2",
15 | "react-dom": "17.0.2",
16 | "react-scripts": "4.0.3",
17 | "@react-three/drei": "^7.22.0",
18 | "@react-three/fiber": "7.0.9",
19 | "three": "0.134.0",
20 | "maath": "latest"
21 | },
22 | "devDependencies": {
23 | "@types/react": "17.0.20",
24 | "@types/react-dom": "17.0.9",
25 | "typescript": "4.4.2",
26 | "@types/three": "^0.133.1"
27 | },
28 | "scripts": {
29 | "start": "react-scripts start",
30 | "build": "react-scripts build",
31 | "test": "react-scripts test --env=jsdom",
32 | "eject": "react-scripts eject"
33 | },
34 | "browserslist": [
35 | ">0.2%",
36 | "not dead",
37 | "not ie <= 11",
38 | "not op_mini all"
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/points/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
14 |
15 |
16 |
24 | Maath Sandbox
25 |
39 |
40 |
41 |
42 |
43 |
44 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/points/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from "react";
2 |
3 | import { useFrame } from "@react-three/fiber";
4 | import { Quaternion, Vector3 } from "three";
5 |
6 | import * as random from "maath/random";
7 | import * as buffer from "maath/buffer";
8 | import * as misc from "maath/misc";
9 |
10 | import { Points } from "@react-three/drei";
11 |
12 | const rotationAxis = new Vector3(0, 1, 0).normalize();
13 | const q = new Quaternion();
14 |
15 | export default function PointsDemo(props: any) {
16 | const pointsRef = useRef(null!);
17 | const [{ box, sphere, final }] = useState(() => {
18 | const box = random.inBox(new Float32Array(10_000), { sides: [1, 2, 1] });
19 | const sphere = random.inSphere(box.slice(0), { radius: 0.75 });
20 | const final = box.slice(0); // final buffer that will be used for the points mesh
21 |
22 | return { box, sphere, final };
23 | });
24 |
25 | useFrame(({ clock }) => {
26 | const et = clock.getElapsedTime();
27 | const t = misc.remap(Math.sin(et), [-1, 1], [0, 1]);
28 | const t2 = misc.remap(Math.cos(et * 3), [-1, 1], [0, 1]);
29 |
30 | buffer.rotate(box, {
31 | q: q.setFromAxisAngle(rotationAxis, t2 * 0.1),
32 | });
33 |
34 | buffer.lerp(box, sphere, final, t);
35 | });
36 |
37 | return (
38 |
39 |
40 |
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/points/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { Canvas } from "@react-three/fiber";
2 | import React from "react";
3 | import ReactDOM from "react-dom";
4 | import App from "./App";
5 |
6 | const rootElement = document.getElementById("root");
7 | ReactDOM.render(
8 |
9 |
13 | ,
14 | rootElement
15 | );
16 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/points/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src/**/*"],
3 | "compilerOptions": {
4 | "strict": true,
5 | "esModuleInterop": true,
6 | "lib": ["dom", "es2015"],
7 | "jsx": "react-jsx"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/sorting/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "maath-demo-points",
3 | "version": "1.0.0",
4 | "description": "React and TypeScript example starter project",
5 | "keywords": [
6 | "typescript",
7 | "react",
8 | "starter",
9 | "maath",
10 | "r3f"
11 | ],
12 | "main": "src/index.tsx",
13 | "dependencies": {
14 | "react": "17.0.2",
15 | "react-dom": "17.0.2",
16 | "react-scripts": "4.0.3",
17 | "@react-three/drei": "^7.22.0",
18 | "@react-three/fiber": "7.0.9",
19 | "three": "0.134.0",
20 | "maath": "latest"
21 | },
22 | "devDependencies": {
23 | "@types/react": "17.0.20",
24 | "@types/react-dom": "17.0.9",
25 | "typescript": "4.4.2",
26 | "@types/three": "^0.133.1"
27 | },
28 | "scripts": {
29 | "start": "react-scripts start",
30 | "build": "react-scripts build",
31 | "test": "react-scripts test --env=jsdom",
32 | "eject": "react-scripts eject"
33 | },
34 | "browserslist": [
35 | ">0.2%",
36 | "not dead",
37 | "not ie <= 11",
38 | "not op_mini all"
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/sorting/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
14 |
15 |
16 |
24 | Maath Sandbox
25 |
39 |
40 |
41 |
42 |
43 |
44 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/sorting/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from "react";
2 |
3 | import { useFrame } from "@react-three/fiber";
4 | import { Quaternion, Vector3 } from "three";
5 |
6 | import * as random from "maath/random";
7 | import * as buffer from "maath/buffer";
8 | import * as misc from "maath/misc";
9 | import * as v3 from "maath/vector3";
10 |
11 | import { Points } from "@react-three/drei";
12 |
13 | import { colord } from "colord";
14 |
15 | const rotationAxis = new Vector3(0, 1, 0).normalize();
16 | const q = new Quaternion();
17 |
18 | const x = new Float32Array();
19 |
20 | // function pipe(n: number, s: Q, ...fns: ((b: Float32Array, s: Q) => any)[]) {
21 | // const buffer = new Float32Array(n * s)
22 |
23 | // fns.reduce((buffer, fn) => fn(buffer, s), buffer)
24 |
25 | // return buffer
26 | // }
27 |
28 | // const newB = pipe<3>(
29 | // 1000, 3,
30 | // (b) => random.inBox(b),
31 | // (b, s) => buffer.sort(b, s, (a, b) => v3.length(a) - v3.length(b)),
32 | // )
33 |
34 | // console.log(newB)
35 |
36 | export default function PointsDemo(props: any) {
37 | const pointsRef = useRef(null!);
38 | const [box] = useState(() => {
39 | const box = random.inSphere(new Float32Array(100_000 * 3), { radius: 3 });
40 | // buffer.sort(box, 3, (a,b) => a[0] - b[0]);
41 | buffer.sort(box, 3, (a, b) => a[1] * a[0] * a[2] - b[1] * b[0] * b[2]);
42 |
43 | return box;
44 | });
45 |
46 | const [colors] = useState(() => {
47 | return buffer.map(box.slice(0), 3, (_, i) => {
48 | const h = (i / (box.length / 3)) * 360;
49 | const { r, g, b } = colord({ h, s: 100, v: 100 }).toRgb();
50 | return [r / 255, g / 255, b / 255];
51 | });
52 | });
53 |
54 | useFrame(({ clock }) => {
55 | const et = clock.getElapsedTime();
56 | const t = misc.remap(Math.sin(et), [-1, 1], [0, 1]);
57 | const t2 = misc.remap(Math.cos(et * 3), [-1, 1], [0, 1]);
58 | });
59 |
60 | return (
61 |
68 |
69 |
70 | );
71 | }
72 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/sorting/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { Canvas } from "@react-three/fiber";
2 | import React from "react";
3 | import ReactDOM from "react-dom";
4 | import App from "./App";
5 |
6 | const rootElement = document.getElementById("root");
7 | ReactDOM.render(
8 |
9 |
13 | ,
14 | rootElement
15 | );
16 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/sorting/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src/**/*"],
3 | "compilerOptions": {
4 | "strict": true,
5 | "esModuleInterop": true,
6 | "lib": ["dom", "es2015"],
7 | "jsx": "react-jsx"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/sutherlandHodgman/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "maath-demo-sutherland-hodgman",
3 | "version": "1.0.0",
4 | "description": "React and TypeScript example starter project",
5 | "keywords": [
6 | "typescript",
7 | "react",
8 | "starter",
9 | "maath",
10 | "r3f"
11 | ],
12 | "main": "src/index.tsx",
13 | "dependencies": {
14 | "react": "17.0.2",
15 | "react-dom": "17.0.2",
16 | "react-scripts": "4.0.3",
17 | "@react-three/drei": "^7.22.0",
18 | "@react-three/fiber": "7.0.9",
19 | "three": "0.134.0",
20 | "maath": "latest"
21 | },
22 | "devDependencies": {
23 | "@types/react": "17.0.20",
24 | "@types/react-dom": "17.0.9",
25 | "typescript": "4.4.2",
26 | "@types/three": "^0.133.1"
27 | },
28 | "scripts": {
29 | "start": "react-scripts start",
30 | "build": "react-scripts build",
31 | "test": "react-scripts test --env=jsdom",
32 | "eject": "react-scripts eject"
33 | },
34 | "browserslist": [
35 | ">0.2%",
36 | "not dead",
37 | "not ie <= 11",
38 | "not op_mini all"
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/sutherlandHodgman/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
14 |
15 |
16 |
24 | Maath Sandbox
25 |
39 |
40 |
41 |
42 |
43 |
44 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/sutherlandHodgman/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 |
3 | import {
4 | BufferAttribute,
5 | Group,
6 | Line,
7 | Plane,
8 | Vector3,
9 | Points,
10 | Mesh,
11 | StreamDrawUsage,
12 | } from "three";
13 |
14 | import * as random from "maath/random";
15 | import * as buffer from "maath/buffer";
16 | import * as misc from "maath/misc";
17 | import * as threeUtils from "maath/three";
18 |
19 | function Demo() {
20 | const plane = new Plane(new Vector3().randomDirection().normalize());
21 |
22 | const $group = useRef(null!);
23 | const $line = useRef(null!);
24 | const $points = useRef(null!);
25 | const $planeHelper = useRef(null!);
26 | const $intersection = useRef(null!);
27 |
28 | const points = random.inSphere(new Float32Array(2 * 3));
29 |
30 | useEffect(() => {
31 | const attr = new BufferAttribute(points, 3);
32 | attr.usage = StreamDrawUsage;
33 |
34 | $line.current.geometry.setAttribute("position", attr);
35 | $points.current.geometry.setAttribute("position", attr);
36 |
37 | const [a, b] = threeUtils.bufferToVectors(points, 3);
38 |
39 | $intersection.current.position.copy(
40 | misc.planeSegmentIntersection(plane, [a, b]) as Vector3
41 | );
42 | });
43 |
44 | return (
45 | <>
46 | {/* @ts-ignore */}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | <>
63 | {/* @ts-ignore */}
64 |
65 |
66 | >
67 |
68 |
69 | >
70 | );
71 | }
72 |
73 | export default Demo;
74 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/sutherlandHodgman/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { Canvas } from "@react-three/fiber";
2 | import React from "react";
3 | import ReactDOM from "react-dom";
4 | import App from "./App";
5 |
6 | const rootElement = document.getElementById("root");
7 | ReactDOM.render(
8 |
9 |
13 | ,
14 | rootElement
15 | );
16 |
--------------------------------------------------------------------------------
/demo/src/sandboxes/sutherlandHodgman/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src/**/*"],
3 | "compilerOptions": {
4 | "strict": true,
5 | "esModuleInterop": true,
6 | "lib": ["dom", "es2015"],
7 | "jsx": "react-jsx"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/demo/src/store.ts:
--------------------------------------------------------------------------------
1 | import create from "zustand";
2 |
3 | export const useStore = create<{
4 | active?: "circumcircle" | "convex-hull" | "points";
5 | setActive: (
6 | slug: "circumcircle" | "convex-hull" | "points" | undefined
7 | ) => void;
8 | }>((set) => ({
9 | active: undefined,
10 | setActive: (slug) => {
11 | set({ active: slug });
12 | },
13 | }));
14 |
--------------------------------------------------------------------------------
/demo/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["./src"]
20 | }
21 |
--------------------------------------------------------------------------------
/demo/vite.config.ts:
--------------------------------------------------------------------------------
1 | // vite.config.js
2 | import path from "path";
3 | import { defineConfig } from "vite";
4 | import react from "@vitejs/plugin-react";
5 |
6 | export default defineConfig({
7 | plugins: [react()],
8 | });
9 |
--------------------------------------------------------------------------------
/hero.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * For a detailed explanation regarding each configuration property and type check, visit:
3 | * https://jestjs.io/docs/configuration
4 | */
5 |
6 | export default {
7 | // All imported modules in your tests should be mocked automatically
8 | // automock: false,
9 |
10 | // Stop running tests after `n` failures
11 | // bail: 0,
12 |
13 | // The directory where Jest should store its cached dependency information
14 | // cacheDirectory: "/private/var/folders/6q/_xg3s_hs01gbvv2hpp3fj34w0000gn/T/jest_dx",
15 |
16 | // Automatically clear mock calls and instances between every test
17 | clearMocks: true,
18 |
19 | // Indicates whether the coverage information should be collected while executing the test
20 | collectCoverage: true,
21 |
22 | // An array of glob patterns indicating a set of files for which coverage information should be collected
23 | // collectCoverageFrom: undefined,
24 |
25 | // The directory where Jest should output its coverage files
26 | coverageDirectory: "coverage",
27 |
28 | // An array of regexp pattern strings used to skip coverage collection
29 | // coveragePathIgnorePatterns: [
30 | // "/node_modules/"
31 | // ],
32 |
33 | // Indicates which provider should be used to instrument code for coverage
34 | // coverageProvider: "babel",
35 |
36 | // A list of reporter names that Jest uses when writing coverage reports
37 | // coverageReporters: [
38 | // "json",
39 | // "text",
40 | // "lcov",
41 | // "clover"
42 | // ],
43 |
44 | // An object that configures minimum threshold enforcement for coverage results
45 | // coverageThreshold: undefined,
46 |
47 | // A path to a custom dependency extractor
48 | // dependencyExtractor: undefined,
49 |
50 | // Make calling deprecated APIs throw helpful error messages
51 | // errorOnDeprecated: false,
52 |
53 | // Force coverage collection from ignored files using an array of glob patterns
54 | // forceCoverageMatch: [],
55 |
56 | // A path to a module which exports an async function that is triggered once before all test suites
57 | // globalSetup: undefined,
58 |
59 | // A path to a module which exports an async function that is triggered once after all test suites
60 | // globalTeardown: undefined,
61 |
62 | // A set of global variables that need to be available in all test environments
63 | // globals: {},
64 |
65 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
66 | // maxWorkers: "50%",
67 |
68 | // An array of directory names to be searched recursively up from the requiring module's location
69 | // moduleDirectories: [
70 | // "node_modules"
71 | // ],
72 |
73 | // An array of file extensions your modules use
74 | // moduleFileExtensions: [
75 | // "js",
76 | // "jsx",
77 | // "ts",
78 | // "tsx",
79 | // "json",
80 | // "node"
81 | // ],
82 |
83 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
84 | // moduleNameMapper: {},
85 |
86 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
87 | // modulePathIgnorePatterns: [],
88 |
89 | // Activates notifications for test results
90 | // notify: false,
91 |
92 | // An enum that specifies notification mode. Requires { notify: true }
93 | // notifyMode: "failure-change",
94 |
95 | // A preset that is used as a base for Jest's configuration
96 | // preset: undefined,
97 |
98 | // Run tests from one or more projects
99 | // projects: undefined,
100 |
101 | // Use this configuration option to add custom reporters to Jest
102 | // reporters: undefined,
103 |
104 | // Automatically reset mock state between every test
105 | // resetMocks: false,
106 |
107 | // Reset the module registry before running each individual test
108 | // resetModules: false,
109 |
110 | // A path to a custom resolver
111 | // resolver: undefined,
112 |
113 | // Automatically restore mock state between every test
114 | // restoreMocks: false,
115 |
116 | // The root directory that Jest should scan for tests and modules within
117 | // rootDir: undefined,
118 |
119 | // A list of paths to directories that Jest should use to search for files in
120 | // roots: [
121 | // ""
122 | // ],
123 |
124 | // Allows you to use a custom runner instead of Jest's default test runner
125 | // runner: "jest-runner",
126 |
127 | // The paths to modules that run some code to configure or set up the testing environment before each test
128 | // setupFiles: [],
129 |
130 | // A list of paths to modules that run some code to configure or set up the testing framework before each test
131 | // setupFilesAfterEnv: [],
132 |
133 | // The number of seconds after which a test is considered as slow and reported as such in the results.
134 | // slowTestThreshold: 5,
135 |
136 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing
137 | // snapshotSerializers: [],
138 |
139 | // The test environment that will be used for testing
140 | // testEnvironment: "jest-environment-node",
141 |
142 | // Options that will be passed to the testEnvironment
143 | // testEnvironmentOptions: {},
144 |
145 | // Adds a location field to test results
146 | // testLocationInResults: false,
147 |
148 | // The glob patterns Jest uses to detect test files
149 | // testMatch: [
150 | // "**/__tests__/**/*.[jt]s?(x)",
151 | // "**/?(*.)+(spec|test).[tj]s?(x)"
152 | // ],
153 |
154 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
155 | // testPathIgnorePatterns: [
156 | // "/node_modules/"
157 | // ],
158 |
159 | // The regexp pattern or array of patterns that Jest uses to detect test files
160 | // testRegex: [],
161 |
162 | // This option allows the use of a custom results processor
163 | // testResultsProcessor: undefined,
164 |
165 | // This option allows use of a custom test runner
166 | // testRunner: "jest-circus/runner",
167 |
168 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
169 | // testURL: "http://localhost",
170 |
171 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
172 | // timers: "real",
173 |
174 | // A map from regular expressions to paths to transformers
175 | // transform: undefined,
176 |
177 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
178 | // transformIgnorePatterns: [
179 | // "/node_modules/",
180 | // "\\.pnp\\.[^\\/]+$"
181 | // ],
182 |
183 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
184 | // unmockedModulePathPatterns: undefined,
185 |
186 | // Indicates whether each individual test should be reported during the run
187 | // verbose: undefined,
188 |
189 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
190 | // watchPathIgnorePatterns: [],
191 |
192 | // Whether to use watchman for file crawling
193 | // watchman: true,
194 | };
195 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@maath/root",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "private": true,
7 | "workspaces": [
8 | "packages/*",
9 | "demo"
10 | ],
11 | "preconstruct": {
12 | "packages": [
13 | "packages/*"
14 | ]
15 | },
16 | "scripts": {
17 | "build": "preconstruct build",
18 | "watch": "preconstruct watch",
19 | "validate": "preconstruct validate",
20 | "test": "jest",
21 | "pub": "./.scripts/publish.sh",
22 | "postinstall": "preconstruct dev",
23 | "dev": "yarn workspace demo dev",
24 | "release": "NODE_ENV=production && yarn build && yarn changeset publish",
25 | "prepare": "husky install",
26 | "prettier:fix": "prettier '**/*.ts?(x)' --write"
27 | },
28 | "dependencies": {
29 | "@babel/core": "^7.16.0",
30 | "@babel/preset-env": "^7.16.0",
31 | "@babel/preset-typescript": "^7.16.0",
32 | "@changesets/cli": "^2.18.0",
33 | "@manypkg/cli": "^0.19.1",
34 | "@preconstruct/cli": "^2.1.5",
35 | "@types/jest": "^27.0.2",
36 | "@types/node": "^16.11.7",
37 | "babel-jest": "^27.3.1",
38 | "husky": ">=6",
39 | "jest": "^27.3.1",
40 | "lint-staged": ">=10",
41 | "prettier": "^2.6.2",
42 | "semver": "^7.3.5",
43 | "ts-node": "^10.4.0",
44 | "typescript": "^4.4.4"
45 | },
46 | "packageManager": "yarn@1.22.21",
47 | "lint-staged": {
48 | "*.{ts,tsx,js,css,md}": "prettier --write"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/packages/maath/.npmignore:
--------------------------------------------------------------------------------
1 | src/
--------------------------------------------------------------------------------
/packages/maath/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # maath
2 |
3 | ## 0.10.8
4 |
5 | ### Patch Changes
6 |
7 | - fix: lower three peer dep requirement
8 |
9 | ## 0.10.7
10 |
11 | ### Patch Changes
12 |
13 | - yarn
14 |
15 | ## 0.10.6
16 |
17 | ### Patch Changes
18 |
19 | - ???
20 |
21 | ## 0.10.5
22 |
23 | ### Patch Changes
24 |
25 | - add parameters to roundedplanegeo
26 |
27 | ## 0.10.4
28 |
29 | ### Patch Changes
30 |
31 | - up
32 |
33 | ## 0.10.3
34 |
35 | ### Patch Changes
36 |
37 | - fix publish
38 |
39 | ## 0.10.2
40 |
41 | ### Patch Changes
42 |
43 | - fix: autoburst"
44 |
45 | ## 0.10.1
46 |
47 | ### Patch Changes
48 |
49 | - manual burst flash gen
50 |
51 | ## 0.10.0
52 |
53 | ### Minor Changes
54 |
55 | - feat: flashgen
56 |
57 | ## 0.9.0
58 |
59 | ### Minor Changes
60 |
61 | - add damptLookAt
62 |
63 | ## 0.8.2
64 |
65 | ### Patch Changes
66 |
67 | - expose repeat
68 |
69 | ## 0.8.1
70 |
71 | ### Patch Changes
72 |
73 | - add linear
74 |
75 | ## 0.8.0
76 |
77 | ### Minor Changes
78 |
79 | - add easings functions
80 |
81 | ## 0.7.0
82 |
83 | ### Minor Changes
84 |
85 | - feat: applyCylindricalUV
86 |
87 | ## 0.6.0
88 |
89 | ### Minor Changes
90 |
91 | - uv gen
92 |
93 | ## 0.5.3
94 |
95 | ### Patch Changes
96 |
97 | - fix damp return
98 |
99 | ## 0.5.2
100 |
101 | ### Patch Changes
102 |
103 | - fix types
104 |
105 | ## 0.5.1
106 |
107 | ### Patch Changes
108 |
109 | - fix uppercase for roundedplanegeom
110 |
111 | ## 0.5.0
112 |
113 | ### Minor Changes
114 |
115 | - new category for geometry + roundedplane
116 |
117 | ## 0.4.2
118 |
119 | ### Patch Changes
120 |
121 | - fix: dampc rgb array support
122 |
123 | ## 0.4.1
124 |
125 | ### Patch Changes
126 |
127 | - fix: eps check for dampm"
128 |
129 | ## 0.4.0
130 |
131 | ### Minor Changes
132 |
133 | - feat: dampm, support for matrix4"
134 |
135 | ## 0.3.0
136 |
137 | ### Minor Changes
138 |
139 | - feat: dampq, support for quaternions
140 |
141 | ## 0.2.0
142 |
143 | ### Minor Changes
144 |
145 | - feat: damp2,3,4,E,C
146 |
147 | ## 0.1.1
148 |
149 | ### Patch Changes
150 |
151 | - fix: damp is missing eps
152 |
153 | ## 0.1.0
154 |
155 | ### Minor Changes
156 |
157 | - 1a033d1: adds entry point that exports everything
158 | - feat: exp, deltaAngle, damp, dampAngle
159 |
160 | ## 0.0.2
161 |
162 | ### Patch Changes
163 |
164 | - f9084f6: change(buffer): Makes addAxis more generic, allowing adding axis to 2D and 3D buffers
165 |
166 | ## 0.0.1
167 |
168 | ### Patch Changes
169 |
170 | - ecdd821: new(misc): Adds coords transformation helpers
171 | - 8107891: new(misc): Adds 2D coords transformation helpers
172 |
--------------------------------------------------------------------------------
/packages/maath/README.md:
--------------------------------------------------------------------------------
1 | ../../README.md
--------------------------------------------------------------------------------
/packages/maath/buffer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "dist/maath-buffer.cjs.js",
3 | "module": "dist/maath-buffer.esm.js"
4 | }
5 |
--------------------------------------------------------------------------------
/packages/maath/easing/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "dist/maath-easing.cjs.js",
3 | "module": "dist/maath-easing.esm.js"
4 | }
5 |
--------------------------------------------------------------------------------
/packages/maath/geometry/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "dist/maath-geometry.cjs.js",
3 | "module": "dist/maath-geometry.esm.js"
4 | }
5 |
--------------------------------------------------------------------------------
/packages/maath/matrix/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "dist/maath-matrix.cjs.js",
3 | "module": "dist/maath-matrix.esm.js"
4 | }
5 |
--------------------------------------------------------------------------------
/packages/maath/misc/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "dist/maath-misc.cjs.js",
3 | "module": "dist/maath-misc.esm.js"
4 | }
5 |
--------------------------------------------------------------------------------
/packages/maath/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "maath",
3 | "version": "0.10.8",
4 | "license": "MIT",
5 | "main": "dist/maath.cjs.js",
6 | "module": "dist/maath.esm.js",
7 | "types": "dist/maath.cjs.d.ts",
8 | "preconstruct": {
9 | "entrypoints": [
10 | "index.ts",
11 | "buffer.ts",
12 | "easing.ts",
13 | "matrix.ts",
14 | "misc.ts",
15 | "random/index.ts",
16 | "triangle.ts",
17 | "vector2.ts",
18 | "vector3.ts",
19 | "three.ts",
20 | "geometry.ts"
21 | ]
22 | },
23 | "peerDependencies": {
24 | "@types/three": ">=0.134.0",
25 | "three": ">=0.134.0"
26 | },
27 | "devDependencies": {
28 | "@types/three": "^0.134.0",
29 | "three": "^0.134.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/maath/random/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "dist/maath-random.cjs.js",
3 | "module": "dist/maath-random.esm.js"
4 | }
5 |
--------------------------------------------------------------------------------
/packages/maath/src/__test__/buffer.test.js:
--------------------------------------------------------------------------------
1 | import * as random from "../random";
2 | import * as buffer from "../buffer";
3 |
4 | describe("addAxis", () => {
5 | it("adds a 3rd axis to a 2D buffer", () => {
6 | const my2DBuffer = random.inCircle(new Float32Array(1_000 * 2));
7 | const my3DBuffer = buffer.addAxis(my2DBuffer, 2, () => 0.75);
8 |
9 | for (let i = 0; i < my2DBuffer.length; i += 2) {
10 | let j = (i / 2) * 3;
11 |
12 | expect(my3DBuffer[j]).toEqual(my2DBuffer[i]);
13 | expect(my3DBuffer[j + 1]).toEqual(my2DBuffer[i + 1]);
14 | expect(my3DBuffer[j + 2]).toEqual(0.75);
15 | }
16 |
17 | expect(my3DBuffer.length).toEqual(1_000 * 3);
18 | });
19 |
20 | it("adds a 4th axis to a 3D buffer", () => {
21 | const my3DBuffer = random.inSphere(new Float32Array(1_000 * 3));
22 | const my4DBuffer = buffer.addAxis(my3DBuffer, 3, () => 0.75);
23 |
24 | for (let i = 0; i < my3DBuffer.length; i += 3) {
25 | let j = (i / 3) * 4;
26 |
27 | expect(my4DBuffer[j]).toEqual(my3DBuffer[i]);
28 | expect(my4DBuffer[j + 1]).toEqual(my3DBuffer[i + 1]);
29 | expect(my4DBuffer[j + 2]).toEqual(my3DBuffer[i + 2]);
30 | expect(my4DBuffer[j + 3]).toEqual(0.75);
31 | }
32 |
33 | expect(my4DBuffer.length).toEqual(1_000 * 4);
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/packages/maath/src/__test__/random.test.js:
--------------------------------------------------------------------------------
1 | import { Vector2, Vector3 } from "three";
2 | import {
3 | Generator,
4 | inCircle,
5 | inPoisson,
6 | inSphere,
7 | onCircle,
8 | onSphere,
9 | } from "../random";
10 | import { noise } from "../random";
11 |
12 | describe("random", () => {
13 | describe("inCircle", () => {
14 | const circle = { radius: 3 };
15 |
16 | const buffer = new Float32Array(10_000 * 2);
17 | inCircle(buffer, circle);
18 |
19 | it("should generate n points in a circle", () => {
20 | expect(buffer.length).toBe(20_000);
21 | });
22 |
23 | it("all points in the set belong to the circle", () => {
24 | let check = true;
25 |
26 | const v2 = new Vector2();
27 |
28 | // run through all the points and check that they are at lessEq distance from the center
29 | for (let i = 0; i < buffer.length; i += 2) {
30 | const x = buffer[i];
31 | const y = buffer[i + 1];
32 |
33 | v2.set(x, y);
34 |
35 | check = v2.length() <= circle.radius && check;
36 | }
37 |
38 | expect(check).toBe(true);
39 | });
40 | });
41 |
42 | describe("onCircle", () => {
43 | const circle = { radius: 3 };
44 |
45 | const buffer = new Float32Array(10_000 * 2);
46 | onCircle(buffer, circle);
47 |
48 | it("should generate n points in a circle", () => {
49 | expect(buffer.length).toBe(20_000);
50 | });
51 |
52 | it("all points in the set belong to the circle", () => {
53 | let check = true;
54 |
55 | const v2 = new Vector2();
56 |
57 | // run through all the points and check that they are at lessEq distance from the center
58 | for (let i = 0; i < buffer.length; i += 2) {
59 | const x = buffer[i];
60 | const y = buffer[i + 1];
61 |
62 | v2.set(x, y);
63 |
64 | // give some room for floating precision in the check
65 | check = Math.abs(v2.length() - circle.radius) < 0.000001 && check;
66 | }
67 |
68 | expect(check).toBe(true);
69 | });
70 | });
71 |
72 | describe("inSphere", () => {
73 | const circle = { radius: 3 };
74 |
75 | const buffer = new Float32Array(10_000 * 3);
76 | inSphere(buffer, circle);
77 |
78 | it("should generate n points on a sphere surface", () => {
79 | expect(buffer.length).toBe(30_000);
80 | });
81 |
82 | it("all points in the set belong to the sphere surface", () => {
83 | let check = true;
84 |
85 | const v3 = new Vector3();
86 |
87 | // run through all the points and check that they are at lessEq distance from the center
88 | for (let i = 0; i < buffer.length; i += 3) {
89 | const x = buffer[i];
90 | const y = buffer[i + 1];
91 | const z = buffer[i + 2];
92 |
93 | v3.set(x, y, z);
94 |
95 | // give some room for floating precision in the check
96 | check = v3.length() < circle.radius && check;
97 | }
98 |
99 | expect(check).toBe(true);
100 | });
101 | });
102 |
103 | describe("onSphere", () => {
104 | const circle = { radius: 3 };
105 |
106 | const buffer = new Float32Array(10_000 * 3);
107 | onSphere(buffer, circle);
108 |
109 | it("should generate n points on a sphere surface", () => {
110 | expect(buffer.length).toBe(30_000);
111 | });
112 |
113 | it("all points in the set belong to the sphere surface", () => {
114 | let check = true;
115 |
116 | const v3 = new Vector3();
117 |
118 | // run through all the points and check that they are at lessEq distance from the center
119 | for (let i = 0; i < buffer.length; i += 3) {
120 | const x = buffer[i];
121 | const y = buffer[i + 1];
122 | const z = buffer[i + 2];
123 |
124 | v3.set(x, y, z);
125 |
126 | // give some room for floating precision in the check
127 | check = Math.abs(v3.length() - circle.radius) < 0.000001 && check;
128 | }
129 |
130 | expect(check).toBe(true);
131 | });
132 | });
133 | });
134 |
135 | describe("seeded random", () => {
136 | describe("random value", () => {
137 | it("should always produce the same sequence of numbers given the same seed", () => {
138 | const generator = new Generator("test");
139 | const generator2 = new Generator("test2");
140 |
141 | const numbers = Array.from({ length: 40 }, () => generator.value());
142 | const numbers2 = Array.from({ length: 40 }, () => generator2.value());
143 |
144 | expect(numbers).not.toEqual(numbers2);
145 |
146 | // same seed as the first generator
147 | const generator3 = new Generator("test");
148 | const numbers3 = Array.from({ length: 40 }, () => generator3.value());
149 |
150 | expect(numbers).toEqual(numbers3);
151 | });
152 | });
153 | });
154 |
155 | describe("noise", () => {
156 | it("should produce same values for same coords & seed", () => {
157 | let i = 0;
158 | const values = Array.from({ length: 10 }, () => noise.simplex2(i, i++));
159 |
160 | i = 0;
161 | const values2 = Array.from({ length: 10 }, () => noise.simplex2(i, i++));
162 |
163 | expect(values).toEqual(values2);
164 | });
165 |
166 | it("should produce different values for same coords & different seed", () => {
167 | let i = 0;
168 | const values = Array.from({ length: 10 }, () => noise.simplex2(i, i++));
169 |
170 | noise.seed(1);
171 |
172 | i = 0;
173 | const values2 = Array.from({ length: 10 }, () => noise.simplex2(i, i++));
174 |
175 | expect(values).not.toEqual(values2);
176 | });
177 | });
178 |
--------------------------------------------------------------------------------
/packages/maath/src/__test__/triangle.test.js:
--------------------------------------------------------------------------------
1 | import {
2 | arePointsCollinear,
3 | doThreePointsMakeARight,
4 | getCircumcircle,
5 | isPointInCircumcircle,
6 | isPointInTriangle,
7 | triangleDeterminant,
8 | } from "../triangle";
9 |
10 | const collinearTriangle = [
11 | [0, 0],
12 | [0, 1],
13 | [0, 2],
14 | ];
15 |
16 | const clockwiseTriangle = [
17 | [0, 0],
18 | [-1, 1],
19 | [-2, 0],
20 | ];
21 |
22 | describe("Triangle", () => {
23 | test("triangleDeterminant", () => {
24 | expect(triangleDeterminant(collinearTriangle)).toBe(0);
25 |
26 | expect(triangleDeterminant(clockwiseTriangle)).toBe(2);
27 |
28 | expect(triangleDeterminant(clockwiseTriangle.reverse())).toBe(-2);
29 | });
30 |
31 | describe("isPointInTriangle", () => {
32 | const triangle = [
33 | [-1, 0],
34 | [0, 1],
35 | [1, 0],
36 | ];
37 |
38 | it("returns true if point is on an edge", () => {
39 | expect(isPointInTriangle([0, 0], triangle)).toBe(true);
40 | });
41 |
42 | it("returns false if point is outside of triangle", () => {
43 | expect(isPointInTriangle([10, 10], triangle)).toBe(false);
44 | });
45 | });
46 |
47 | /**
48 | ●──────●──────●
49 | A B C
50 | */
51 | test("arePointsCollinear", () => {
52 | expect(arePointsCollinear(collinearTriangle)).toBe(true);
53 | expect(arePointsCollinear(clockwiseTriangle)).toBe(false);
54 | });
55 |
56 | test("getCircumcircle", () => {
57 | expect(getCircumcircle(collinearTriangle)).toBe(null);
58 |
59 | expect(getCircumcircle(clockwiseTriangle)).toStrictEqual({
60 | x: -1,
61 | y: 0,
62 | r: 1,
63 | });
64 | });
65 |
66 | describe("isPointInCircumcircle", () => {
67 | it("throws if passed collinear points", () => {
68 | expect(() => isPointInCircumcircle([0, 0], collinearTriangle)).toThrow(
69 | Error
70 | );
71 | });
72 |
73 | /**
74 | B
75 | .───●───.
76 | ,─' ╱ ╲ '─.
77 | ,' ╱ ╲ `.
78 | ╱ ╱ ╲ ╲
79 | ; ╱ ╲ :
80 | │ ╱ ╲ │
81 | │ ╱ ╲ │
82 | : ╱ ╲ ;
83 | ╲ ╱ ╲ ╱
84 | ●─────────────────●
85 | A / P `. ,' C
86 | '─. ,─'
87 | `─────'
88 | */
89 | it("returns true if point is one of the vertices", () => {
90 | expect(isPointInCircumcircle([0, 0], clockwiseTriangle)).toBe(true);
91 | });
92 |
93 | /**
94 | B
95 | .───●───.
96 | ,─' ╱ ╲ '─.
97 | ,' ╱ ╲ `.
98 | ╱ ╱ ╲ ╲
99 | ; ╱ ╲ :
100 | │ ╱ ╲ │
101 | │ ╱ ╲ │
102 | : ╱ ╲ ;
103 | ╲ ╱ ┌─┐ ╲ ╱
104 | ●───────┤P├───────●
105 | A `. └─┘ ,' C
106 | '─. ,─'
107 | `─────'
108 | */
109 | it("returns true if point lies on an edge", () => {
110 | expect(isPointInCircumcircle([-1, 0], clockwiseTriangle)).toBe(true);
111 | });
112 |
113 | /**
114 |
115 | B ┌─┐
116 | .───●───. │P│
117 | ,─' ╱ ╲ '─. └─┘
118 | ,' ╱ ╲ `.
119 | ╱ ╱ ╲ ╲
120 | ; ╱ ╲ :
121 | │ ╱ ╲ │
122 | │ ╱ ╲ │
123 | : ╱ ╲ ;
124 | ╲ ╱ ╲ ╱
125 | ●─────────────────●
126 | A `. ,' C
127 | '─. ,─'
128 | `─────'
129 |
130 | */
131 | it("returns false if point is outside of triangle", () => {
132 | expect(isPointInCircumcircle([10, 10], clockwiseTriangle)).toBe(false);
133 | });
134 | });
135 |
136 | describe("doThreePointsMakeARight", () => {
137 | it("returns false if points are collinear, thus not making any turn", () => {
138 | expect(doThreePointsMakeARight(collinearTriangle)).toBe(false);
139 | });
140 |
141 | it("returns true if points are clockwise, thus making a right turn", () => {
142 | expect(doThreePointsMakeARight(clockwiseTriangle)).toBe(true);
143 | });
144 |
145 | it("returns false if points are counter-clockwise, thus making a left turn", () => {
146 | expect(doThreePointsMakeARight(clockwiseTriangle.reverse())).toBe(false);
147 | });
148 | });
149 | });
150 |
--------------------------------------------------------------------------------
/packages/maath/src/__test__/utils.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmndrs/maath/626d198fbae28ba82f2f1b184db7fcafd4d23846/packages/maath/src/__test__/utils.js
--------------------------------------------------------------------------------
/packages/maath/src/buffer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Conventions:
3 | * 1. try to avoid threejs dependencies, TBD how to solve them
4 | * 2. use overload signatures to support stride 2 and 3 with ok typing
5 | */
6 |
7 | import { Quaternion, Vector3 } from "three";
8 | import type { TypedArray, MyVector2, MyVector3 } from "./ctypes";
9 | import { lerp as _lerp } from "./misc";
10 | import * as v2 from "./vector2";
11 | import * as v3 from "./vector3";
12 |
13 | export function swizzle(buffer: TypedArray, stride = 3, swizzle = "xyz") {
14 | const o = { x: 0, y: 0, z: 0 };
15 | for (let i = 0; i < buffer.length; i += stride) {
16 | o.x = buffer[i];
17 | o.y = buffer[i + 1];
18 | o.z = buffer[i + 2];
19 |
20 | const [x, y, z] = swizzle.split("");
21 |
22 | // TODO Fix this ugly type
23 | buffer[i] = o[x as "x" | "y" | "z"];
24 | buffer[i + 1] = o[y as "x" | "y" | "z"];
25 |
26 | if (stride === 3) {
27 | buffer[i + 2] = o[z as "x" | "y" | "z"];
28 | }
29 | }
30 |
31 | return buffer;
32 | }
33 |
34 | /**
35 | * @param buffer A stride 2 points buffer
36 | * @param valueGenerator A function that returns the value of the z axis at index i
37 | * @returns
38 | */
39 | export function addAxis(
40 | buffer: TypedArray,
41 | size: number,
42 | valueGenerator: (j: number) => number = () => Math.random()
43 | ): TypedArray {
44 | const newSize = size + 1;
45 | const newBuffer = new Float32Array(
46 | (buffer.length / size) * newSize
47 | ) as TypedArray;
48 |
49 | for (let i = 0; i < buffer.length; i += size) {
50 | let j = (i / size) * newSize;
51 |
52 | newBuffer[j] = buffer[i];
53 | newBuffer[j + 1] = buffer[i + 1];
54 |
55 | if (size === 2) {
56 | newBuffer[j + 2] = valueGenerator(j);
57 | }
58 |
59 | if (size === 3) {
60 | newBuffer[j + 2] = buffer[i + 2];
61 | newBuffer[j + 3] = valueGenerator(j);
62 | }
63 | }
64 |
65 | return newBuffer;
66 | }
67 |
68 | /**
69 | * Lerps bufferA and bufferB into final
70 | *
71 | * @param bufferA
72 | * @param bufferB
73 | * @param final
74 | * @param t
75 | */
76 | export function lerp(
77 | bufferA: TypedArray,
78 | bufferB: TypedArray,
79 | final: TypedArray,
80 | t: number
81 | ) {
82 | for (let i = 0; i < bufferA.length; i++) {
83 | final[i] = _lerp(bufferA[i], bufferB[i], t);
84 | }
85 | }
86 |
87 | // TODO add stride
88 | // TODO Fix types & vectors
89 | /**
90 | *
91 | * Translate all points in the passed buffer by the passed translactionVector.
92 | *
93 | * @param buffer
94 | * @param translationVector
95 | * @returns
96 | */
97 | export function translate(
98 | buffer: TypedArray,
99 | translationVector: MyVector2 | MyVector3
100 | ) {
101 | const stride = translationVector.length;
102 |
103 | for (let i = 0; i < buffer.length; i += stride) {
104 | buffer[i] += translationVector[0];
105 | buffer[i + 1] += translationVector[1];
106 | buffer[i + 2] += translationVector[2];
107 | }
108 |
109 | return buffer;
110 | }
111 |
112 | // TODO add stride
113 | // TODO remove quaternion & vector3 dependencies
114 | export function rotate(
115 | buffer: TypedArray,
116 | rotation: { q: Quaternion; center?: number[] }
117 | ) {
118 | const defaultRotation = {
119 | center: [0, 0, 0],
120 | q: new Quaternion().identity(),
121 | };
122 |
123 | const v = new Vector3();
124 |
125 | const { q, center } = {
126 | ...defaultRotation,
127 | ...rotation,
128 | };
129 |
130 | for (let i = 0; i < buffer.length; i += 3) {
131 | v.set(
132 | buffer[i] - center[0],
133 | buffer[i + 1] - center[1],
134 | buffer[i + 2] - center[2]
135 | );
136 | v.applyQuaternion(q);
137 |
138 | buffer[i] = v.x + center[0];
139 | buffer[i + 1] = v.y + center[1];
140 | buffer[i + 2] = v.z + center[1];
141 | }
142 |
143 | return buffer;
144 | }
145 |
146 | export function map(
147 | buffer: TypedArray,
148 | stride: 2,
149 | fn: (v: v2.V2, i: number) => number[]
150 | ): TypedArray;
151 |
152 | export function map(
153 | buffer: TypedArray,
154 | stride: 3,
155 | fn: (v: v3.V3, i: number) => number[]
156 | ): TypedArray;
157 |
158 | export function map(buffer: any, stride: any, callback: any) {
159 | for (let i = 0, j = 0; i < buffer.length; i += stride, j++) {
160 | if (stride === 3) {
161 | const res = callback([buffer[i], buffer[i + 1], buffer[i + 2]], j);
162 | buffer.set(res, i);
163 | } else {
164 | buffer.set(callback([buffer[i], buffer[i + 1]], j), i);
165 | }
166 | }
167 |
168 | return buffer;
169 | }
170 |
171 | /**
172 | * Reduces passed buffer
173 | */
174 | type IReduceCallback = (final: T, point: v2.V2, i: number) => T;
175 |
176 | export function reduce(
177 | b: TypedArray,
178 | stride: 2,
179 | callback: IReduceCallback,
180 | acc: T
181 | ): T;
182 |
183 | export function reduce(
184 | b: TypedArray,
185 | stride: 3,
186 | callback: IReduceCallback,
187 | acc: T
188 | ): T;
189 |
190 | export function reduce(b: TypedArray, stride: any, callback: any, acc: T) {
191 | for (let i = 0, j = 0; i < b.length; i += stride, j++) {
192 | if (stride === 2) {
193 | acc = callback(acc, [b[i], b[i + 1]], j);
194 | } else {
195 | acc = callback(acc, [b[i], b[i + 1], b[i + 2]], j);
196 | }
197 | }
198 |
199 | return acc;
200 | }
201 |
202 | type ExpandOptions = {
203 | center?: [number, number];
204 | distance: number;
205 | };
206 |
207 | export function expand(b: TypedArray, stride: 2 | 3, opts: ExpandOptions) {
208 | const defaultExpandOptions = {
209 | center: [0, 0, 0],
210 | };
211 |
212 | const { center, distance } = {
213 | ...defaultExpandOptions,
214 | ...opts,
215 | };
216 |
217 | for (let i = 0; i < b.length; i += stride) {
218 | /**
219 | * 1. translate to origin (subtract the scaling center)
220 | * 2. scale by the correct amount (multiply by a constant)
221 | * 2. translate from origin (add the scaling center)
222 | */
223 | b[i] = (b[i] - center[0]) * (1 + distance) + center[0];
224 | b[i + 1] = (b[i + 1] - center[1]) * (1 + distance) + center[1];
225 |
226 | if (stride === 3) {
227 | b[i + 2] = (b[i + 2] - center[1]) * (1 + distance) + center[2];
228 | }
229 | }
230 |
231 | return b;
232 | }
233 |
234 | export function center(myBuffer: TypedArray, stride: 2): v2.V2;
235 | export function center(myBuffer: TypedArray, stride: 3): v3.V3;
236 | export function center(myBuffer: TypedArray, stride: any) {
237 | return reduce(
238 | myBuffer,
239 | stride,
240 | (acc, point) => {
241 | if (stride === 3) {
242 | // some type hacking is necessary to avoid type errors going from [n, n] => [n, n, n]
243 | // but it's not an actual problem, as this path would always get a v3
244 | acc = v3.add(acc as v3.V3, point as unknown as v3.V3);
245 | } else {
246 | acc = v2.add(acc as v2.V2, point);
247 | }
248 |
249 | return acc;
250 | },
251 | v2.zero()
252 | );
253 | }
254 |
255 | type ISortingCallback = (a: T, b: T) => number;
256 |
257 | export function sort(
258 | myBuffer: TypedArray,
259 | stride: 2,
260 | callback: ISortingCallback
261 | ): TypedArray;
262 | export function sort(
263 | myBuffer: TypedArray,
264 | stride: 3,
265 | callback: ISortingCallback
266 | ): TypedArray;
267 | export function sort(myBuffer: TypedArray, stride: 2 | 3, callback: any) {
268 | // 1. make an array of the correct size
269 | const indices = Int16Array.from(
270 | { length: myBuffer.length / stride },
271 | (_, i) => i
272 | );
273 |
274 | // 2. sort the indices array
275 | indices.sort((a, b) => {
276 | const pa = myBuffer.slice(a * stride, a * stride + stride);
277 | const pb = myBuffer.slice(b * stride, b * stride + stride);
278 |
279 | return callback(pa, pb);
280 | });
281 |
282 | // 3. make a copy of the original array to fetch indices from
283 | const prevBuffer = myBuffer.slice(0);
284 |
285 | // 4. mutate the passed array
286 | for (let i = 0; i < indices.length; i++) {
287 | const j = indices[i];
288 | myBuffer.set(prevBuffer.slice(j * stride, j * stride + stride), i * 3);
289 | }
290 |
291 | return myBuffer;
292 | }
293 |
--------------------------------------------------------------------------------
/packages/maath/src/ctypes.ts:
--------------------------------------------------------------------------------
1 | import { Vector2 } from "three";
2 |
3 | export type TypedArray = Float32Array | Float64Array;
4 |
5 | // fun fact, I think three's types aren't updated, vectors SHOULD be iterable in latest
6 | export type MyVector2 = number[];
7 | export type MyVector3 = number[];
8 |
9 | export type Triangle = [MyVector2, MyVector2, MyVector2] | Vector2[];
10 |
--------------------------------------------------------------------------------
/packages/maath/src/easing.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Vector2,
3 | Vector3,
4 | Vector4,
5 | Euler,
6 | Color,
7 | Matrix4,
8 | Quaternion,
9 | Spherical,
10 | ColorRepresentation,
11 | } from "three";
12 | import { deltaAngle } from "./misc";
13 |
14 | export const rsqw = (t: number, delta = 0.01, a = 1, f = 1 / (2 * Math.PI)) =>
15 | (a / Math.atan(1 / delta)) * Math.atan(Math.sin(2 * Math.PI * t * f) / delta);
16 | export const exp = (t: number) =>
17 | 1 / (1 + t + 0.48 * t * t + 0.235 * t * t * t);
18 | export const linear = (t: number) => t;
19 |
20 | export const sine = {
21 | in: (x: number) => 1 - Math.cos((x * Math.PI) / 2),
22 | out: (x: number) => Math.sin((x * Math.PI) / 2),
23 | inOut: (x: number) => -(Math.cos(Math.PI * x) - 1) / 2,
24 | };
25 |
26 | export const cubic = {
27 | in: (x: number) => x * x * x,
28 | out: (x: number) => 1 - Math.pow(1 - x, 3),
29 | inOut: (x: number) =>
30 | x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2,
31 | };
32 | export const quint = {
33 | in: (x: number) => x * x * x * x * x,
34 | out: (x: number) => 1 - Math.pow(1 - x, 5),
35 | inOut: (x: number) =>
36 | x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2,
37 | };
38 |
39 | export const circ = {
40 | in: (x: number) => 1 - Math.sqrt(1 - Math.pow(x, 2)),
41 | out: (x: number) => Math.sqrt(1 - Math.pow(x - 1, 2)),
42 | inOut: (x: number) =>
43 | x < 0.5
44 | ? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2
45 | : (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2,
46 | };
47 |
48 | export const quart = {
49 | in: (t: number) => t * t * t * t,
50 | out: (t: number) => 1 - --t * t * t * t,
51 | inOut: (t: number) => (t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t),
52 | };
53 |
54 | export const expo = {
55 | in: (x: number) => (x === 0 ? 0 : Math.pow(2, 10 * x - 10)),
56 | out: (x: number) => (x === 1 ? 1 : 1 - Math.pow(2, -10 * x)),
57 | inOut: (x: number) =>
58 | x === 0
59 | ? 0
60 | : x === 1
61 | ? 1
62 | : x < 0.5
63 | ? Math.pow(2, 20 * x - 10) / 2
64 | : (2 - Math.pow(2, -20 * x + 10)) / 2,
65 | };
66 |
67 | /**
68 | * Damp, based on Game Programming Gems 4 Chapter 1.10
69 | * Return value indicates whether the animation is still running.
70 | */
71 | export function damp(
72 | /** The object */
73 | current: { [key: string]: any },
74 | /** The key to animate */
75 | prop: string,
76 | /** To goal value */
77 | target: number,
78 | /** Approximate time to reach the target. A smaller value will reach the target faster. */
79 | smoothTime = 0.25,
80 | /** Frame delta, for refreshrate independence */
81 | delta = 0.01,
82 | /** Optionally allows you to clamp the maximum speed. If smoothTime is 0.25s and looks OK
83 | * going between two close points but not for points far apart as it'll move very rapid,
84 | * then a maxSpeed of e.g. 1 which will clamp the speed to 1 unit per second, it may now
85 | * take much longer than smoothTime to reach the target if it is far away. */
86 | maxSpeed = Infinity,
87 | /** Easing function */
88 | easing = exp,
89 | /** End of animation precision */
90 | eps = 0.001
91 | ) {
92 | const vel = "velocity_" + prop;
93 | if (current.__damp === undefined) current.__damp = {};
94 | if (current.__damp[vel] === undefined) current.__damp[vel] = 0;
95 |
96 | if (Math.abs(current[prop] - target) <= eps) {
97 | current[prop] = target;
98 | return false;
99 | }
100 |
101 | smoothTime = Math.max(0.0001, smoothTime);
102 | const omega = 2 / smoothTime;
103 | const t = easing(omega * delta);
104 | let change = current[prop] - target;
105 | const originalTo = target;
106 | // Clamp maximum maxSpeed
107 | const maxChange = maxSpeed * smoothTime;
108 | change = Math.min(Math.max(change, -maxChange), maxChange);
109 | target = current[prop] - change;
110 | const temp = (current.__damp[vel] + omega * change) * delta;
111 | current.__damp[vel] = (current.__damp[vel] - omega * temp) * t;
112 | let output = target + (change + temp) * t;
113 | // Prevent overshooting
114 | if (originalTo - current[prop] > 0.0 === output > originalTo) {
115 | output = originalTo;
116 | current.__damp[vel] = (output - originalTo) / delta;
117 | }
118 | current[prop] = output;
119 | return true;
120 | }
121 |
122 | /**
123 | * DampLookAt
124 | */
125 | const isCamera = (v: any): v is THREE.Camera => v && v.isCamera;
126 | const isLight = (v: any): v is THREE.Light => v && v.isLight;
127 | const vl3d = /*@__PURE__*/ new Vector3();
128 | const _q1 = /*@__PURE__*/ new Quaternion();
129 | const _q2 = /*@__PURE__*/ new Quaternion();
130 | const _m1 = /*@__PURE__*/ new Matrix4();
131 | const _position = /*@__PURE__*/ new Vector3();
132 | export function dampLookAt(
133 | current: THREE.Object3D,
134 | target: number | [x: number, y: number, z: number] | Vector3,
135 | smoothTime?: number,
136 | delta?: number,
137 | maxSpeed?: number,
138 | easing?: (t: number) => number,
139 | eps?: number
140 | ) {
141 | // This method does not support objects having non-uniformly-scaled parent(s)
142 | if (typeof target === "number") vl3d.setScalar(target);
143 | else if (Array.isArray(target)) vl3d.set(target[0], target[1], target[2]);
144 | else vl3d.copy(target);
145 |
146 | const parent = current.parent;
147 | current.updateWorldMatrix(true, false);
148 | _position.setFromMatrixPosition(current.matrixWorld);
149 | if (isCamera(current) || isLight(current))
150 | _m1.lookAt(_position, vl3d, current.up);
151 | else _m1.lookAt(vl3d, _position, current.up);
152 |
153 | dampQ(
154 | current.quaternion,
155 | _q2.setFromRotationMatrix(_m1),
156 | smoothTime,
157 | delta,
158 | maxSpeed,
159 | easing,
160 | eps
161 | );
162 | if (parent) {
163 | _m1.extractRotation(parent.matrixWorld);
164 | _q1.setFromRotationMatrix(_m1);
165 | dampQ(
166 | current.quaternion,
167 | _q2.copy(current.quaternion).premultiply(_q1.invert()),
168 | smoothTime,
169 | delta,
170 | maxSpeed,
171 | easing,
172 | eps
173 | );
174 | }
175 | }
176 |
177 | /**
178 | * DampAngle, with a shortest-path
179 | */
180 | export function dampAngle(
181 | current: { [key: string]: any },
182 | prop: string,
183 | target: number,
184 | smoothTime?: number,
185 | delta?: number,
186 | maxSpeed?: number,
187 | easing?: (t: number) => number,
188 | eps?: number
189 | ) {
190 | return damp(
191 | current,
192 | prop,
193 | current[prop] + deltaAngle(current[prop], target),
194 | smoothTime,
195 | delta,
196 | maxSpeed,
197 | easing,
198 | eps
199 | );
200 | }
201 |
202 | /**
203 | * Vector2D Damp
204 | */
205 | const v2d = /*@__PURE__*/ new Vector2();
206 | let a2: boolean, b2: boolean;
207 | export function damp2(
208 | current: Vector2,
209 | target: number | [x: number, y: number] | Vector2,
210 | smoothTime?: number,
211 | delta?: number,
212 | maxSpeed?: number,
213 | easing?: (t: number) => number,
214 | eps?: number
215 | ) {
216 | if (typeof target === "number") v2d.setScalar(target);
217 | else if (Array.isArray(target)) v2d.set(target[0], target[1]);
218 | else v2d.copy(target);
219 | a2 = damp(current, "x", v2d.x, smoothTime, delta, maxSpeed, easing, eps);
220 | b2 = damp(current, "y", v2d.y, smoothTime, delta, maxSpeed, easing, eps);
221 | return a2 || b2;
222 | }
223 |
224 | /**
225 | * Vector3D Damp
226 | */
227 | const v3d = /*@__PURE__*/ new Vector3();
228 | let a3: boolean, b3: boolean, c3: boolean;
229 | export function damp3(
230 | current: Vector3,
231 | target: number | [x: number, y: number, z: number] | Vector3,
232 | smoothTime?: number,
233 | delta?: number,
234 | maxSpeed?: number,
235 | easing?: (t: number) => number,
236 | eps?: number
237 | ) {
238 | if (typeof target === "number") v3d.setScalar(target);
239 | else if (Array.isArray(target)) v3d.set(target[0], target[1], target[2]);
240 | else v3d.copy(target);
241 | a3 = damp(current, "x", v3d.x, smoothTime, delta, maxSpeed, easing, eps);
242 | b3 = damp(current, "y", v3d.y, smoothTime, delta, maxSpeed, easing, eps);
243 | c3 = damp(current, "z", v3d.z, smoothTime, delta, maxSpeed, easing, eps);
244 | return a3 || b3 || c3;
245 | }
246 |
247 | /**
248 | * Vector4D Damp
249 | */
250 | const v4d = /*@__PURE__*/ new Vector4();
251 | let a4: boolean, b4: boolean, c4: boolean, d4: boolean;
252 | export function damp4(
253 | current: Vector4,
254 | target: number | [x: number, y: number, z: number, w: number] | Vector4,
255 | smoothTime?: number,
256 | delta?: number,
257 | maxSpeed?: number,
258 | easing?: (t: number) => number,
259 | eps?: number
260 | ) {
261 | if (typeof target === "number") v4d.setScalar(target);
262 | else if (Array.isArray(target))
263 | v4d.set(target[0], target[1], target[2], target[3]);
264 | else v4d.copy(target);
265 | a4 = damp(current, "x", v4d.x, smoothTime, delta, maxSpeed, easing, eps);
266 | b4 = damp(current, "y", v4d.y, smoothTime, delta, maxSpeed, easing, eps);
267 | c4 = damp(current, "z", v4d.z, smoothTime, delta, maxSpeed, easing, eps);
268 | d4 = damp(current, "w", v4d.w, smoothTime, delta, maxSpeed, easing, eps);
269 | return a4 || b4 || c4 || d4;
270 | }
271 |
272 | type EulerOrder = "XYZ" | "YXZ" | "ZXY" | "ZYX" | "YZX" | "XZY";
273 |
274 | /**
275 | * Euler Damp
276 | */
277 | const rot = /*@__PURE__*/ new Euler();
278 | let aE: boolean, bE: boolean, cE: boolean;
279 | export function dampE(
280 | current: Euler,
281 | target: [x: number, y: number, z: number, order?: EulerOrder] | Euler,
282 | smoothTime?: number,
283 | delta?: number,
284 | maxSpeed?: number,
285 | easing?: (t: number) => number,
286 | eps?: number
287 | ) {
288 | if (Array.isArray(target))
289 | rot.set(target[0], target[1], target[2], target[3]);
290 | else rot.copy(target);
291 | aE = dampAngle(current, "x", rot.x, smoothTime, delta, maxSpeed, easing, eps);
292 | bE = dampAngle(current, "y", rot.y, smoothTime, delta, maxSpeed, easing, eps);
293 | cE = dampAngle(current, "z", rot.z, smoothTime, delta, maxSpeed, easing, eps);
294 | return aE || bE || cE;
295 | }
296 |
297 | /**
298 | * Color Damp
299 | */
300 | const col = /*@__PURE__*/ new Color();
301 | let aC: boolean, bC: boolean, cC: boolean;
302 | export function dampC(
303 | current: Color,
304 | target: ColorRepresentation | [r: number, g: number, b: number],
305 | smoothTime?: number,
306 | delta?: number,
307 | maxSpeed?: number,
308 | easing?: (t: number) => number,
309 | eps?: number
310 | ) {
311 | if (target instanceof Color) col.copy(target);
312 | else if (Array.isArray(target)) col.setRGB(target[0], target[1], target[2]);
313 | else col.set(target);
314 | aC = damp(current, "r", col.r, smoothTime, delta, maxSpeed, easing, eps);
315 | bC = damp(current, "g", col.g, smoothTime, delta, maxSpeed, easing, eps);
316 | cC = damp(current, "b", col.b, smoothTime, delta, maxSpeed, easing, eps);
317 | return aC || bC || cC;
318 | }
319 |
320 | /**
321 | * Quaternion Damp
322 | * https://gist.github.com/maxattack/4c7b4de00f5c1b95a33b
323 | * Copyright 2016 Max Kaufmann (max.kaufmann@gmail.com)
324 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
325 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
326 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
327 | */
328 | const qt = /*@__PURE__*/ new Quaternion();
329 | const v4result = /*@__PURE__*/ new Vector4();
330 | const v4velocity = /*@__PURE__*/ new Vector4();
331 | const v4error = /*@__PURE__*/ new Vector4();
332 | let aQ: boolean, bQ: boolean, cQ: boolean, dQ: boolean;
333 | export function dampQ(
334 | current: Quaternion,
335 | target: [x: number, y: number, z: number, w: number] | Quaternion,
336 | smoothTime?: number,
337 | delta?: number,
338 | maxSpeed?: number,
339 | easing?: (t: number) => number,
340 | eps?: number
341 | ) {
342 | const cur = current as Quaternion & { __damp: { [key: string]: number } };
343 | if (Array.isArray(target)) qt.set(target[0], target[1], target[2], target[3]);
344 | else qt.copy(target);
345 |
346 | const multi = current.dot(qt) > 0 ? 1 : -1;
347 | qt.x *= multi;
348 | qt.y *= multi;
349 | qt.z *= multi;
350 | qt.w *= multi;
351 |
352 | aQ = damp(current, "x", qt.x, smoothTime, delta, maxSpeed, easing, eps);
353 | bQ = damp(current, "y", qt.y, smoothTime, delta, maxSpeed, easing, eps);
354 | cQ = damp(current, "z", qt.z, smoothTime, delta, maxSpeed, easing, eps);
355 | dQ = damp(current, "w", qt.w, smoothTime, delta, maxSpeed, easing, eps);
356 |
357 | // smooth damp (nlerp approx)
358 | v4result.set(current.x, current.y, current.z, current.w).normalize();
359 | v4velocity.set(
360 | cur.__damp.velocity_x,
361 | cur.__damp.velocity_y,
362 | cur.__damp.velocity_z,
363 | cur.__damp.velocity_w
364 | );
365 |
366 | // ensure deriv is tangent
367 | v4error
368 | .copy(v4result)
369 | .multiplyScalar(v4velocity.dot(v4result) / v4result.dot(v4result));
370 | cur.__damp.velocity_x -= v4error.x;
371 | cur.__damp.velocity_y -= v4error.y;
372 | cur.__damp.velocity_z -= v4error.z;
373 | cur.__damp.velocity_w -= v4error.w;
374 |
375 | current.set(v4result.x, v4result.y, v4result.z, v4result.w);
376 | return aQ || bQ || cQ || dQ;
377 | }
378 |
379 | /**
380 | * Spherical Damp
381 | */
382 | const spherical = /*@__PURE__*/ new Spherical();
383 | let aS: boolean, bS: boolean, cS: boolean;
384 | export function dampS(
385 | current: Spherical,
386 | target: [radius: number, phi: number, theta: number] | Spherical,
387 | smoothTime?: number,
388 | delta?: number,
389 | maxSpeed?: number,
390 | easing?: (t: number) => number,
391 | eps?: number
392 | ) {
393 | if (Array.isArray(target)) spherical.set(target[0], target[1], target[2]);
394 | else spherical.copy(target);
395 | aS = damp(
396 | current,
397 | "radius",
398 | spherical.radius,
399 | smoothTime,
400 | delta,
401 | maxSpeed,
402 | easing,
403 | eps
404 | );
405 | bS = dampAngle(
406 | current,
407 | "phi",
408 | spherical.phi,
409 | smoothTime,
410 | delta,
411 | maxSpeed,
412 | easing,
413 | eps
414 | );
415 | cS = dampAngle(
416 | current,
417 | "theta",
418 | spherical.theta,
419 | smoothTime,
420 | delta,
421 | maxSpeed,
422 | easing,
423 | eps
424 | );
425 | return aS || bS || cS;
426 | }
427 |
428 | /**
429 | * Matrix4 Damp
430 | */
431 | const mat = /*@__PURE__*/ new Matrix4();
432 | const mPos = /*@__PURE__*/ new Vector3();
433 | const mRot = /*@__PURE__*/ new Quaternion();
434 | const mSca = /*@__PURE__*/ new Vector3();
435 | let aM: boolean, bM: boolean, cM: boolean;
436 | export function dampM(
437 | current: Matrix4,
438 | target:
439 | | [
440 | n11: number,
441 | n12: number,
442 | n13: number,
443 | n14: number,
444 | n21: number,
445 | n22: number,
446 | n23: number,
447 | n24: number,
448 | n31: number,
449 | n32: number,
450 | n33: number,
451 | n34: number,
452 | n41: number,
453 | n42: number,
454 | n43: number,
455 | n44: number
456 | ]
457 | | Matrix4,
458 | smoothTime?: number,
459 | delta?: number,
460 | maxSpeed?: number,
461 | easing?: (t: number) => number,
462 | eps?: number
463 | ) {
464 | const cur = current as Matrix4 & {
465 | __damp: {
466 | position: Vector3;
467 | rotation: Quaternion;
468 | scale: Vector3;
469 | };
470 | };
471 | if (cur.__damp === undefined) {
472 | cur.__damp = {
473 | position: new Vector3(),
474 | rotation: new Quaternion(),
475 | scale: new Vector3(),
476 | };
477 | current.decompose(
478 | cur.__damp.position,
479 | cur.__damp.rotation,
480 | cur.__damp.scale
481 | );
482 | }
483 |
484 | if (Array.isArray(target)) mat.set(...target);
485 | else mat.copy(target);
486 | mat.decompose(mPos, mRot, mSca);
487 |
488 | aM = damp3(
489 | cur.__damp.position,
490 | mPos,
491 | smoothTime,
492 | delta,
493 | maxSpeed,
494 | easing,
495 | eps
496 | );
497 | bM = dampQ(
498 | cur.__damp.rotation,
499 | mRot,
500 | smoothTime,
501 | delta,
502 | maxSpeed,
503 | easing,
504 | eps
505 | );
506 | cM = damp3(cur.__damp.scale, mSca, smoothTime, delta, maxSpeed, easing, eps);
507 | current.compose(cur.__damp.position, cur.__damp.rotation, cur.__damp.scale);
508 | return aM || bM || cM;
509 | }
510 |
--------------------------------------------------------------------------------
/packages/maath/src/geometry.ts:
--------------------------------------------------------------------------------
1 | // From: https://discourse.threejs.org/t/roundedrectangle-squircle/28645/20
2 |
3 | import * as THREE from "three";
4 |
5 | export class RoundedPlaneGeometry extends THREE.BufferGeometry {
6 | parameters: {
7 | width: number;
8 | height: number;
9 | radius: number;
10 | segments: number;
11 | };
12 | constructor(width = 2, height = 1, radius = 0.2, segments = 16) {
13 | super();
14 | this.parameters = {
15 | width,
16 | height,
17 | radius,
18 | segments,
19 | };
20 |
21 | // helper const's
22 | const wi = width / 2 - radius; // inner width
23 | const hi = height / 2 - radius; // inner height
24 | const ul = radius / width; // u left
25 | const ur = (width - radius) / width; // u right
26 | const vl = radius / height; // v low
27 | const vh = (height - radius) / height; // v high
28 |
29 | let positions = [wi, hi, 0, -wi, hi, 0, -wi, -hi, 0, wi, -hi, 0];
30 | let uvs = [ur, vh, ul, vh, ul, vl, ur, vl];
31 | let n = [
32 | 3 * (segments + 1) + 3,
33 | 3 * (segments + 1) + 4,
34 | segments + 4,
35 | segments + 5,
36 | 2 * (segments + 1) + 4,
37 | 2,
38 | 1,
39 | 2 * (segments + 1) + 3,
40 | 3,
41 | 4 * (segments + 1) + 3,
42 | 4,
43 | 0,
44 | ];
45 | let indices = [
46 | n[0],
47 | n[1],
48 | n[2],
49 | n[0],
50 | n[2],
51 | n[3],
52 | n[4],
53 | n[5],
54 | n[6],
55 | n[4],
56 | n[6],
57 | n[7],
58 | n[8],
59 | n[9],
60 | n[10],
61 | n[8],
62 | n[10],
63 | n[11],
64 | ];
65 | let phi, cos, sin, xc, yc, uc, vc, idx;
66 |
67 | for (let i = 0; i < 4; i++) {
68 | xc = i < 1 || i > 2 ? wi : -wi;
69 | yc = i < 2 ? hi : -hi;
70 | uc = i < 1 || i > 2 ? ur : ul;
71 | vc = i < 2 ? vh : vl;
72 | for (let j = 0; j <= segments; j++) {
73 | phi = (Math.PI / 2) * (i + j / segments);
74 | cos = Math.cos(phi);
75 | sin = Math.sin(phi);
76 | positions.push(xc + radius * cos, yc + radius * sin, 0);
77 | uvs.push(uc + ul * cos, vc + vl * sin);
78 | if (j < segments) {
79 | idx = (segments + 1) * i + j + 4;
80 | indices.push(i, idx, idx + 1);
81 | }
82 | }
83 | }
84 |
85 | this.setIndex(new THREE.BufferAttribute(new Uint32Array(indices), 1));
86 | this.setAttribute(
87 | "position",
88 | new THREE.BufferAttribute(new Float32Array(positions), 3)
89 | );
90 | this.setAttribute(
91 | "uv",
92 | new THREE.BufferAttribute(new Float32Array(uvs), 2)
93 | );
94 | }
95 | }
96 |
97 | // Author: https://stackoverflow.com/users/128511/gman
98 | // https://stackoverflow.com/questions/34958072/programmatically-generate-simple-uv-mapping-for-models
99 |
100 | export function applyCylindricalUV(bufferGeometry: THREE.BufferGeometry) {
101 | const uvs = [];
102 | for (
103 | let i = 0;
104 | i < bufferGeometry.attributes.position.array.length / 3;
105 | i++
106 | ) {
107 | const x = bufferGeometry.attributes.position.array[i * 3 + 0];
108 | const y = bufferGeometry.attributes.position.array[i * 3 + 1];
109 | const z = bufferGeometry.attributes.position.array[i * 3 + 2];
110 | uvs.push(
111 | (Math.atan2(x, z) / Math.PI) * 0.5 + 0.5,
112 | (y / Math.PI) * 0.5 + 0.5
113 | );
114 | }
115 | if (bufferGeometry.attributes.uv) delete bufferGeometry.attributes.uv;
116 | bufferGeometry.setAttribute("uv", new THREE.Float32BufferAttribute(uvs, 2));
117 | bufferGeometry.attributes.uv.needsUpdate = true;
118 | return bufferGeometry;
119 | }
120 |
121 | // Author: https://stackoverflow.com/users/268905/knee-cola
122 | // https://stackoverflow.com/questions/20774648/three-js-generate-uv-coordinate
123 |
124 | export function applySphereUV(bufferGeometry: THREE.BufferGeometry) {
125 | const uvs = [];
126 | const vertices = [];
127 |
128 | for (
129 | let i = 0;
130 | i < bufferGeometry.attributes.position.array.length / 3;
131 | i++
132 | ) {
133 | const x = bufferGeometry.attributes.position.array[i * 3 + 0];
134 | const y = bufferGeometry.attributes.position.array[i * 3 + 1];
135 | const z = bufferGeometry.attributes.position.array[i * 3 + 2];
136 | vertices.push(new THREE.Vector3(x, y, z));
137 | }
138 |
139 | const polarVertices = vertices.map(cartesian2polar);
140 |
141 | for (let i = 0; i < polarVertices.length / 3; i++) {
142 | const tri = new THREE.Triangle(
143 | vertices[i * 3 + 0],
144 | vertices[i * 3 + 1],
145 | vertices[i * 3 + 2]
146 | );
147 | const normal = tri.getNormal(new THREE.Vector3());
148 |
149 | for (let f = 0; f < 3; f++) {
150 | let vertex = polarVertices[i * 3 + f];
151 | if (vertex.theta === 0 && (vertex.phi === 0 || vertex.phi === Math.PI)) {
152 | const alignedVertice = vertex.phi === 0 ? i * 3 + 1 : i * 3 + 0;
153 | vertex = {
154 | r: vertex.r,
155 | phi: vertex.phi,
156 | theta: polarVertices[alignedVertice].theta,
157 | };
158 | }
159 | if (
160 | vertex.theta === Math.PI &&
161 | cartesian2polar(normal).theta < Math.PI / 2
162 | ) {
163 | vertex.theta = -Math.PI;
164 | }
165 | const canvasPoint = polar2canvas(vertex);
166 | uvs.push(1 - canvasPoint.x, 1 - canvasPoint.y);
167 | }
168 | }
169 |
170 | if (bufferGeometry.attributes.uv) delete bufferGeometry.attributes.uv;
171 | bufferGeometry.setAttribute("uv", new THREE.Float32BufferAttribute(uvs, 2));
172 | bufferGeometry.attributes.uv.needsUpdate = true;
173 | return bufferGeometry;
174 | }
175 |
176 | type PolarPoint = { r: number; phi: number; theta: number };
177 |
178 | function cartesian2polar(position: THREE.Vector3): PolarPoint {
179 | var r = Math.sqrt(
180 | position.x * position.x + position.z * position.z + position.y * position.y
181 | );
182 | return {
183 | r,
184 | phi: Math.acos(position.y / r),
185 | theta: Math.atan2(position.z, position.x),
186 | };
187 | }
188 |
189 | function polar2canvas(polarPoint: PolarPoint) {
190 | return {
191 | y: polarPoint.phi / Math.PI,
192 | x: (polarPoint.theta + Math.PI) / (2 * Math.PI),
193 | };
194 | }
195 |
196 | // Author: Alex Khoroshylov (https://stackoverflow.com/users/8742287/alex-khoroshylov)
197 | // https://stackoverflow.com/questions/20774648/three-js-generate-uv-coordinate
198 |
199 | export function applyBoxUV(bufferGeometry: THREE.BufferGeometry) {
200 | bufferGeometry.computeBoundingBox();
201 | let bboxSize = bufferGeometry.boundingBox!.getSize(new THREE.Vector3());
202 | let boxSize = Math.min(bboxSize.x, bboxSize.y, bboxSize.z);
203 | let boxGeometry = new THREE.BoxGeometry(boxSize, boxSize, boxSize);
204 |
205 | let cube = new THREE.Mesh(boxGeometry);
206 | cube.rotation.set(0, 0, 0);
207 | cube.updateWorldMatrix(true, false);
208 |
209 | const transformMatrix = cube.matrix.clone().invert();
210 |
211 | let uvBbox = new THREE.Box3(
212 | new THREE.Vector3(-boxSize / 2, -boxSize / 2, -boxSize / 2),
213 | new THREE.Vector3(boxSize / 2, boxSize / 2, boxSize / 2)
214 | );
215 | _applyBoxUV(bufferGeometry, transformMatrix, uvBbox, boxSize);
216 | bufferGeometry.attributes.uv.needsUpdate = true;
217 | return bufferGeometry;
218 | }
219 |
220 | function _applyBoxUV(
221 | geom: THREE.BufferGeometry,
222 | transformMatrix: THREE.Matrix4,
223 | bbox: THREE.Box3,
224 | bbox_max_size: number
225 | ) {
226 | let coords: number[] = [];
227 | coords.length = (2 * geom.attributes.position.array.length) / 3;
228 |
229 | //maps 3 verts of 1 face on the better side of the cube
230 | //side of the cube can be XY, XZ or YZ
231 | let makeUVs = function (
232 | v0: THREE.Vector3,
233 | v1: THREE.Vector3,
234 | v2: THREE.Vector3
235 | ) {
236 | //pre-rotate the model so that cube sides match world axis
237 | v0.applyMatrix4(transformMatrix);
238 | v1.applyMatrix4(transformMatrix);
239 | v2.applyMatrix4(transformMatrix);
240 |
241 | //get normal of the face, to know into which cube side it maps better
242 | let n = new THREE.Vector3();
243 | n.crossVectors(v1.clone().sub(v0), v1.clone().sub(v2)).normalize();
244 | n.x = Math.abs(n.x);
245 | n.y = Math.abs(n.y);
246 | n.z = Math.abs(n.z);
247 |
248 | let uv0 = new THREE.Vector2();
249 | let uv1 = new THREE.Vector2();
250 | let uv2 = new THREE.Vector2();
251 | // xz mapping
252 | if (n.y > n.x && n.y > n.z) {
253 | uv0.x = (v0.x - bbox.min.x) / bbox_max_size;
254 | uv0.y = (bbox.max.z - v0.z) / bbox_max_size;
255 | uv1.x = (v1.x - bbox.min.x) / bbox_max_size;
256 | uv1.y = (bbox.max.z - v1.z) / bbox_max_size;
257 | uv2.x = (v2.x - bbox.min.x) / bbox_max_size;
258 | uv2.y = (bbox.max.z - v2.z) / bbox_max_size;
259 | } else if (n.x > n.y && n.x > n.z) {
260 | uv0.x = (v0.z - bbox.min.z) / bbox_max_size;
261 | uv0.y = (v0.y - bbox.min.y) / bbox_max_size;
262 | uv1.x = (v1.z - bbox.min.z) / bbox_max_size;
263 | uv1.y = (v1.y - bbox.min.y) / bbox_max_size;
264 | uv2.x = (v2.z - bbox.min.z) / bbox_max_size;
265 | uv2.y = (v2.y - bbox.min.y) / bbox_max_size;
266 | } else if (n.z > n.y && n.z > n.x) {
267 | uv0.x = (v0.x - bbox.min.x) / bbox_max_size;
268 | uv0.y = (v0.y - bbox.min.y) / bbox_max_size;
269 | uv1.x = (v1.x - bbox.min.x) / bbox_max_size;
270 | uv1.y = (v1.y - bbox.min.y) / bbox_max_size;
271 | uv2.x = (v2.x - bbox.min.x) / bbox_max_size;
272 | uv2.y = (v2.y - bbox.min.y) / bbox_max_size;
273 | }
274 | return {
275 | uv0: uv0,
276 | uv1: uv1,
277 | uv2: uv2,
278 | };
279 | };
280 |
281 | if (geom.index) {
282 | // is it indexed buffer geometry?
283 | for (let vi = 0; vi < geom.index.array.length; vi += 3) {
284 | let idx0 = geom.index.array[vi];
285 | let idx1 = geom.index.array[vi + 1];
286 | let idx2 = geom.index.array[vi + 2];
287 | let vx0 = geom.attributes.position.array[3 * idx0];
288 | let vy0 = geom.attributes.position.array[3 * idx0 + 1];
289 | let vz0 = geom.attributes.position.array[3 * idx0 + 2];
290 | let vx1 = geom.attributes.position.array[3 * idx1];
291 | let vy1 = geom.attributes.position.array[3 * idx1 + 1];
292 | let vz1 = geom.attributes.position.array[3 * idx1 + 2];
293 | let vx2 = geom.attributes.position.array[3 * idx2];
294 | let vy2 = geom.attributes.position.array[3 * idx2 + 1];
295 | let vz2 = geom.attributes.position.array[3 * idx2 + 2];
296 | let v0 = new THREE.Vector3(vx0, vy0, vz0);
297 | let v1 = new THREE.Vector3(vx1, vy1, vz1);
298 | let v2 = new THREE.Vector3(vx2, vy2, vz2);
299 | let uvs = makeUVs(v0, v1, v2);
300 | coords[2 * idx0] = uvs.uv0.x;
301 | coords[2 * idx0 + 1] = uvs.uv0.y;
302 | coords[2 * idx1] = uvs.uv1.x;
303 | coords[2 * idx1 + 1] = uvs.uv1.y;
304 | coords[2 * idx2] = uvs.uv2.x;
305 | coords[2 * idx2 + 1] = uvs.uv2.y;
306 | }
307 | } else {
308 | for (let vi = 0; vi < geom.attributes.position.array.length; vi += 9) {
309 | let vx0 = geom.attributes.position.array[vi];
310 | let vy0 = geom.attributes.position.array[vi + 1];
311 | let vz0 = geom.attributes.position.array[vi + 2];
312 | let vx1 = geom.attributes.position.array[vi + 3];
313 | let vy1 = geom.attributes.position.array[vi + 4];
314 | let vz1 = geom.attributes.position.array[vi + 5];
315 | let vx2 = geom.attributes.position.array[vi + 6];
316 | let vy2 = geom.attributes.position.array[vi + 7];
317 | let vz2 = geom.attributes.position.array[vi + 8];
318 | let v0 = new THREE.Vector3(vx0, vy0, vz0);
319 | let v1 = new THREE.Vector3(vx1, vy1, vz1);
320 | let v2 = new THREE.Vector3(vx2, vy2, vz2);
321 | let uvs = makeUVs(v0, v1, v2);
322 | let idx0 = vi / 3;
323 | let idx1 = idx0 + 1;
324 | let idx2 = idx0 + 2;
325 | coords[2 * idx0] = uvs.uv0.x;
326 | coords[2 * idx0 + 1] = uvs.uv0.y;
327 | coords[2 * idx1] = uvs.uv1.x;
328 | coords[2 * idx1 + 1] = uvs.uv1.y;
329 | coords[2 * idx2] = uvs.uv2.x;
330 | coords[2 * idx2 + 1] = uvs.uv2.y;
331 | }
332 | }
333 | if (geom.attributes.uv) delete geom.attributes.uv;
334 | geom.setAttribute("uv", new THREE.Float32BufferAttribute(coords, 2));
335 | }
336 |
--------------------------------------------------------------------------------
/packages/maath/src/index.ts:
--------------------------------------------------------------------------------
1 | export * as buffer from "./buffer";
2 | export * as random from "./random";
3 | export * as easing from "./easing";
4 | export * as geometry from "./geometry";
5 | export * as matrix from "./matrix";
6 | export * as misc from "./misc";
7 | export * as three from "./three";
8 | export * as triangle from "./triangle";
9 | export * as vector2 from "./vector2";
10 | export * as vector3 from "./vector3";
11 |
--------------------------------------------------------------------------------
/packages/maath/src/matrix.ts:
--------------------------------------------------------------------------------
1 | import { Matrix3, Matrix4 } from "three";
2 |
3 | /**
4 | *
5 | * @param terms
6 | *
7 | * | a b |
8 | * | c d |
9 | *
10 | * @returns {number} determinant
11 | */
12 | export function determinant2(...terms: number[]) {
13 | const [a, b, c, d] = terms;
14 |
15 | return a * d - b * c;
16 | }
17 |
18 | /**
19 | *
20 | * @param terms
21 | *
22 | * | a b c |
23 | * | d e f |
24 | * | g h i |
25 | *
26 | * @returns {number} determinant
27 | */
28 | export function determinant3(...terms: number[]) {
29 | const [a, b, c, d, e, f, g, h, i] = terms;
30 |
31 | return a * e * i + b * f * g + c * d * h - c * e * g - b * d * i - a * f * h;
32 | }
33 |
34 | /**
35 | *
36 | * @param terms
37 | *
38 | * | a b c g |
39 | * | h i j k |
40 | * | l m n o |
41 | *
42 | * @returns {number} determinant
43 | */
44 | export function determinant4(...terms: number[]) {
45 | const [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o] = terms;
46 |
47 | // TODO
48 | }
49 |
50 | /**
51 | *
52 | * Get the determinant of matrix m without row r and col c
53 | *
54 | * @param {matrix} m Starter matrix
55 | * @param r row to remove
56 | * @param c col to remove
57 | *
58 | * | a b c |
59 | * m = | d e f |
60 | * | g h i |
61 | *
62 | * getMinor(m, 1, 1) would result in this determinant
63 | *
64 | * | a c |
65 | * | g i |
66 | *
67 | * @returns {number} determinant
68 | */
69 | export function getMinor(matrix: Matrix4, r: number, c: number) {
70 | const _matrixTranspose = matrix.clone().transpose();
71 |
72 | const x = [];
73 |
74 | const l = _matrixTranspose.elements.length;
75 | const n = Math.sqrt(l);
76 |
77 | for (let i = 0; i < l; i++) {
78 | const element = _matrixTranspose.elements[i];
79 |
80 | const row = Math.floor(i / n);
81 | const col = i % n;
82 |
83 | if (row !== r - 1 && col !== c - 1) {
84 | x.push(element);
85 | }
86 | }
87 |
88 | return determinant3(...x);
89 | }
90 |
91 | /**
92 | *
93 | */
94 | export function matrixSum3(m1: Matrix3, m2: Matrix3) {
95 | const sum = [];
96 | const m1Array = m1.toArray();
97 | const m2Array = m2.toArray();
98 |
99 | for (let i = 0; i < m1Array.length; i++) {
100 | sum[i] = m1Array[i] + m2Array[i];
101 | }
102 |
103 | return new Matrix3().fromArray(sum);
104 | }
105 |
--------------------------------------------------------------------------------
/packages/maath/src/misc.ts:
--------------------------------------------------------------------------------
1 | import { Matrix3, Plane, Vector2, Vector3 } from "three";
2 | import { doThreePointsMakeARight } from "./triangle";
3 | import type { TypedArray } from "./ctypes";
4 | import { matrixSum3 } from "./matrix";
5 | import { V3 } from "./vector3";
6 | import { V2 } from "./vector2";
7 |
8 | /**
9 | * Clamps a value between a range.
10 | */
11 | export function clamp(value: number, min: number, max: number) {
12 | return Math.max(min, Math.min(max, value));
13 | }
14 |
15 | // Loops the value t, so that it is never larger than length and never smaller than 0.
16 | export function repeat(t: number, length: number) {
17 | return clamp(t - Math.floor(t / length) * length, 0, length);
18 | }
19 |
20 | // Calculates the shortest difference between two given angles.
21 | export function deltaAngle(current: number, target: number) {
22 | let delta = repeat(target - current, Math.PI * 2);
23 | if (delta > Math.PI) delta -= Math.PI * 2;
24 | return delta;
25 | }
26 |
27 | /**
28 | * Converts degrees to radians.
29 | */
30 | export function degToRad(degrees: number) {
31 | return (degrees / 180) * Math.PI;
32 | }
33 |
34 | /**
35 | * Converts radians to degrees.
36 | */
37 | export function radToDeg(radians: number) {
38 | return (radians * 180) / Math.PI;
39 | }
40 |
41 | // adapted from https://gist.github.com/stephanbogner/a5f50548a06bec723dcb0991dcbb0856 by https://twitter.com/st_phan
42 | export function fibonacciOnSphere(buffer: TypedArray, { radius = 1 }) {
43 | const samples = buffer.length / 3;
44 |
45 | const offset = 2 / samples;
46 | const increment = Math.PI * (3 - 2.2360679775);
47 |
48 | for (let i = 0; i < buffer.length; i += 3) {
49 | let y = i * offset - 1 + offset / 2;
50 | const distance = Math.sqrt(1 - Math.pow(y, 2));
51 |
52 | const phi = (i % samples) * increment;
53 |
54 | let x = Math.cos(phi) * distance;
55 | let z = Math.sin(phi) * distance;
56 |
57 | buffer[i] = x * radius;
58 | buffer[i + 1] = y * radius;
59 | buffer[i + 2] = z * radius;
60 | }
61 | }
62 |
63 | // @ts-ignore
64 | export function vectorEquals(a, b, eps = Number.EPSILON) {
65 | return (
66 | Math.abs(a.x - b.x) < eps &&
67 | Math.abs(a.y - b.y) < eps &&
68 | Math.abs(a.z - b.z) < eps
69 | );
70 | }
71 |
72 | /**
73 | * Sorts vectors in lexicographic order, works with both v2 and v3
74 | *
75 | * Use as:
76 | * const sorted = arrayOfVectors.sort(lexicographicOrder)
77 | */
78 | // https://en.wikipedia.org/wiki/Lexicographic_order
79 | export function lexicographic(a: Vector2 | Vector3, b: Vector2 | Vector3) {
80 | if (a.x === b.x) {
81 | // do a check to see if points is 3D,
82 | // in which case add y eq check and sort by z
83 | if (typeof (a as Vector3).z !== "undefined") {
84 | if (a.y === b.y) {
85 | return (a as Vector3).z - (b as Vector3).z;
86 | }
87 | }
88 |
89 | return a.y - b.y;
90 | }
91 |
92 | return a.x - b.x;
93 | }
94 |
95 | /**
96 | * Convex Hull
97 | *
98 | * Returns an array of 2D Vectors representing the convex hull of a set of 2D Vectors
99 | */
100 |
101 | /**
102 | * Calculate the convex hull of a set of points
103 | */
104 | export function convexHull(_points: Vector2[]) {
105 | let points = _points.sort(lexicographic);
106 |
107 | // put p1 and p2 in a list lUpper with p1 as the first point
108 | const lUpper = [points[0], points[1]];
109 |
110 | // for i <- 3 to n
111 | for (let i = 2; i < points.length; i++) {
112 | lUpper.push(points[i]);
113 |
114 | // while lUpper contains more than 2 points and the last three points in lUpper do not make a right turn
115 | while (
116 | lUpper.length > 2 &&
117 | doThreePointsMakeARight([...lUpper.slice(-3)])
118 | ) {
119 | // delete the middle of the last three points from lUpper
120 | lUpper.splice(lUpper.length - 2, 1);
121 | }
122 | 4;
123 | }
124 |
125 | // put pn and pn-1 in a list lLower with pn as the first point
126 | const lLower = [points[points.length - 1], points[points.length - 2]];
127 |
128 | // for (i <- n - 2 downto 1)
129 | for (let i = points.length - 3; i >= 0; i--) {
130 | // append pi to lLower
131 | lLower.push(points[i]);
132 |
133 | // while lLower contains more than 2 points and the last three points in lLower do not make a right turn
134 | while (
135 | lLower.length > 2 &&
136 | doThreePointsMakeARight([...lLower.slice(-3)])
137 | ) {
138 | // delete the middle of the last three points from lLower
139 | lLower.splice(lLower.length - 2, 1);
140 | }
141 | }
142 |
143 | // remove the first and last point from lLower to avoid duplication of the points where the upper and lower hull meet
144 | lLower.splice(0, 1);
145 | lLower.splice(lLower.length - 1, 1);
146 |
147 | // prettier-ignore
148 | const c = [
149 | ...lUpper,
150 | ...lLower,
151 | ]
152 |
153 | return c;
154 | }
155 |
156 | export function remap(
157 | x: number,
158 | [low1, high1]: number[],
159 | [low2, high2]: number[]
160 | ) {
161 | return low2 + ((x - low1) * (high2 - low2)) / (high1 - low1);
162 | }
163 |
164 | /**
165 | *
166 | * https://www.desmos.com/calculator/vsnmlaljdu
167 | *
168 | * Ease-in-out, goes to -Infinite before 0 and Infinite after 1
169 | *
170 | * @param t
171 | * @returns
172 | */
173 | export function fade(t: number) {
174 | return t * t * t * (t * (t * 6 - 15) + 10);
175 | }
176 |
177 | /**
178 | *
179 | * Returns the result of linearly interpolating between input A and input B by input T.
180 | *
181 | * @param v0
182 | * @param v1
183 | * @param t
184 | * @returns
185 | */
186 | export function lerp(v0: number, v1: number, t: number) {
187 | return v0 * (1 - t) + v1 * t;
188 | }
189 |
190 | /**
191 | *
192 | * Returns the linear parameter that produces the interpolant specified by input T within the range of input A to input B.
193 | *
194 | * @param v0
195 | * @param v1
196 | * @param t
197 | * @returns
198 | */
199 | export function inverseLerp(v0: number, v1: number, t: number) {
200 | return (t - v0) / (v1 - v0);
201 | }
202 |
203 | /**
204 | *
205 | */
206 |
207 | export function normalize(x: number, y: number, z: number) {
208 | const m = Math.sqrt(x * x + y * y + z * z);
209 |
210 | return [x / m, y / m, z / m];
211 | }
212 |
213 | /**
214 | *
215 | */
216 | export function pointOnCubeToPointOnSphere(x: number, y: number, z: number) {
217 | const x2 = x * x;
218 | const y2 = y * y;
219 | const z2 = z * z;
220 |
221 | const nx = x * Math.sqrt(1 - (y2 + z2) / 2 + (y2 * z2) / 3);
222 | const ny = y * Math.sqrt(1 - (z2 + x2) / 2 + (z2 * x2) / 3);
223 | const nz = z * Math.sqrt(1 - (x2 + y2) / 2 + (x2 * y2) / 3);
224 |
225 | return [nx, ny, nz];
226 | }
227 |
228 | // https://math.stackexchange.com/questions/180418/calculate-rotation-matrix-to-align-vector-a-to-vector-b-in-3d
229 | /**
230 | * Give two unit vectors a and b, returns the transformation matrix that rotates a onto b.
231 | *
232 | * */
233 | export function rotateVectorOnVector(a: Vector3, b: Vector3): Matrix3 {
234 | const v = new Vector3().crossVectors(a, b);
235 | const c = a.dot(b);
236 |
237 | const i = new Matrix3().identity();
238 | // skew-symmetric cross-product matrix of 𝑣 https://en.wikipedia.org/wiki/Skew-symmetric_matrix
239 | // prettier-ignore
240 | const vx = new Matrix3().set(
241 | 0, -v.z, v.y,
242 | v.z, 0, -v.x,
243 | -v.y, v.x, 0
244 | );
245 |
246 | const vxsquared = new Matrix3()
247 | .multiplyMatrices(vx, vx)
248 | .multiplyScalar(1 / (1 + c));
249 |
250 | const final = matrixSum3(matrixSum3(i, vx), vxsquared);
251 |
252 | return final;
253 | }
254 |
255 | // calculate latitude and longitude (in radians) from point on unit sphere
256 | export function pointToCoordinate(x: number, y: number, z: number) {
257 | const lat = Math.asin(y);
258 | const lon = Math.atan2(x, -z);
259 |
260 | return [lat, lon];
261 | }
262 |
263 | // calculate point on unit sphere given latitude and logitude in radians
264 | export function coordinateToPoint(lat: number, lon: number) {
265 | const y = Math.sin(lat);
266 | const r = Math.cos(lat);
267 | const x = Math.sin(lon) * r;
268 | const z = -Math.cos(lon) * r;
269 |
270 | return [x, y, z];
271 | }
272 |
273 | /**
274 | * Given a plane and a segment, return the intersection point if it exists or null it doesn't.
275 | */
276 | export function planeSegmentIntersection(
277 | plane: Plane,
278 | segment: Vector3[]
279 | ): null | Vector3 {
280 | const [a, b] = segment;
281 | const matrix = rotateVectorOnVector(plane.normal, new Vector3(0, 1, 0));
282 |
283 | const t = inverseLerp(
284 | a.clone().applyMatrix3(matrix).y,
285 | b.clone().applyMatrix3(matrix).y,
286 | 0
287 | );
288 |
289 | return new Vector3().lerpVectors(a, b, t);
290 | }
291 |
292 | /**
293 | * Given a plane and a point, return the distance.
294 | */
295 | export function pointToPlaneDistance(p: Vector3, plane: Plane): number {
296 | const d = plane.normal.dot(p);
297 |
298 | // TODO
299 |
300 | return d;
301 | }
302 |
303 | export function getIndexFrom3D(coords: V3, sides: V3) {
304 | const [ix, iy, iz] = coords;
305 | const [rx, ry] = sides;
306 |
307 | return iz * rx * ry + iy * rx + ix;
308 | }
309 |
310 | export function get3DFromIndex(index: number, size: V3): V3 {
311 | const [rx, ry] = size;
312 |
313 | let a = rx * ry;
314 | const z = index / a;
315 | let b = index - a * z;
316 | const y = b / rx;
317 | const x = b % rx;
318 |
319 | return [x, y, z];
320 | }
321 |
322 | export function getIndexFrom2D(coords: V2, size: V2): number {
323 | return coords[0] + size[0] * coords[1];
324 | }
325 |
326 | export function get2DFromIndex(index: number, columns: number): V2 {
327 | const x = index % columns;
328 | const y = Math.floor(index / columns);
329 |
330 | return [x, y];
331 | }
332 |
--------------------------------------------------------------------------------
/packages/maath/src/random/index.ts:
--------------------------------------------------------------------------------
1 | import type { TypedArray } from "../ctypes";
2 |
3 | const TAU = Math.PI * 2;
4 |
5 | export class FlashGen {
6 | nextBurstTime = 0;
7 | nextFlashEndTime = 0;
8 | flashesDone = 0;
9 | isFlashing = false;
10 | currentCount = 0;
11 | flashIntensity = 0;
12 | isDecaying = false;
13 | autoBurst = true;
14 | decaySpeed = 40;
15 |
16 | minInterval = 5000;
17 | maxInterval = 10000;
18 | minDuration = 50;
19 | maxDuration = 300;
20 | count = 5;
21 |
22 | constructor(props: {
23 | decaySpeed?: number;
24 | minInterval?: number;
25 | maxInterval?: number;
26 | minDuration?: number;
27 | maxDuration?: number;
28 | count?: number;
29 | }) {
30 | Object.assign(this, props);
31 | }
32 |
33 | scheduleNextBurst(currentTime: number) {
34 | const burstInterval =
35 | Math.random() * (this.maxInterval - this.minInterval) + this.minInterval;
36 | this.nextBurstTime = currentTime + burstInterval / 1000;
37 | this.flashesDone = 0;
38 | this.isFlashing = false;
39 | }
40 |
41 | burst() {
42 | this.nextBurstTime = 0;
43 | this.flashesDone = 0;
44 | this.isFlashing = false;
45 | }
46 |
47 | update(currentTime: number, delta: number) {
48 | if (currentTime > this.nextBurstTime && this.currentCount === 0) {
49 | this.currentCount = Math.floor(Math.random() * this.count) + 1;
50 | }
51 |
52 | if (
53 | this.flashesDone < this.currentCount &&
54 | currentTime > this.nextBurstTime
55 | ) {
56 | if (!this.isFlashing) {
57 | this.isFlashing = true;
58 | this.flashIntensity = 1;
59 | const flashDuration =
60 | Math.random() * (this.maxDuration - this.minDuration) +
61 | this.minDuration;
62 | this.nextFlashEndTime = currentTime + flashDuration / 1000;
63 | } else if (this.isFlashing && currentTime > this.nextFlashEndTime) {
64 | this.isFlashing = false;
65 | this.isDecaying = true;
66 | this.flashesDone++;
67 | if (this.flashesDone >= this.currentCount) {
68 | this.currentCount = 0;
69 | if (this.autoBurst) this.scheduleNextBurst(currentTime);
70 | }
71 | }
72 | }
73 |
74 | if (this.isDecaying) {
75 | this.flashIntensity -= delta * this.decaySpeed;
76 | this.flashIntensity = Math.max(0, Math.min(1, this.flashIntensity));
77 | if (this.flashIntensity <= 0) {
78 | this.isDecaying = false;
79 | this.flashIntensity = 0;
80 | }
81 | }
82 |
83 | return this.flashIntensity;
84 | }
85 | }
86 |
87 | // Credits @kchapelier https://github.com/kchapelier/wavefunctioncollapse/blob/master/example/lcg.js#L22-L30
88 | function normalizeSeed(seed: number | string) {
89 | if (typeof seed === "number") {
90 | seed = Math.abs(seed);
91 | } else if (typeof seed === "string") {
92 | const string = seed;
93 | seed = 0;
94 | for (let i = 0; i < string.length; i++) {
95 | seed = (seed + (i + 1) * (string.charCodeAt(i) % 96)) % 2147483647;
96 | }
97 | }
98 | if (seed === 0) {
99 | seed = 311;
100 | }
101 | return seed;
102 | }
103 |
104 | function lcgRandom(seed: number | string) {
105 | let state = normalizeSeed(seed);
106 | return function () {
107 | const result = (state * 48271) % 2147483647;
108 | state = result;
109 | return result / 2147483647;
110 | };
111 | }
112 |
113 | export class Generator {
114 | seed: string | number = 0;
115 |
116 | constructor(seed: string | number) {
117 | this.init(seed);
118 | }
119 |
120 | init = (seed: number | string) => {
121 | this.seed = seed;
122 | this.value = lcgRandom(seed);
123 | };
124 |
125 | value = lcgRandom(this.seed);
126 | }
127 |
128 | const defaultGen = new Generator(Math.random());
129 |
130 | /***
131 | * [3D] Sphere
132 | */
133 | type Sphere = {
134 | radius?: number;
135 | center?: number[];
136 | };
137 |
138 | const defaultSphere = {
139 | radius: 1,
140 | center: [0, 0, 0],
141 | };
142 |
143 | // random on surface of sphere
144 | // - https://twitter.com/fermatslibrary/status/1430932503578226688
145 | // - https://mathworld.wolfram.com/SpherePointPicking.html
146 | export function onSphere(
147 | buffer: TypedArray,
148 | sphere?: Sphere,
149 | rng: Generator = defaultGen
150 | ) {
151 | const { radius, center } = {
152 | ...defaultSphere,
153 | ...sphere,
154 | };
155 |
156 | for (let i = 0; i < buffer.length; i += 3) {
157 | const u = rng.value();
158 | const v = rng.value();
159 |
160 | const theta = Math.acos(2 * v - 1);
161 | const phi = TAU * u;
162 |
163 | buffer[i] = Math.sin(theta) * Math.cos(phi) * radius + center[0];
164 | buffer[i + 1] = Math.sin(theta) * Math.sin(phi) * radius + center[1];
165 | buffer[i + 2] = Math.cos(theta) * radius + center[2];
166 | }
167 |
168 | return buffer;
169 | }
170 |
171 | // from "Another Method" https://datagenetics.com/blog/january32020/index.html
172 | export function inSphere(
173 | buffer: TypedArray,
174 | sphere?: Sphere,
175 | rng: Generator = defaultGen
176 | ) {
177 | const { radius, center } = {
178 | ...defaultSphere,
179 | ...sphere,
180 | };
181 | for (let i = 0; i < buffer.length; i += 3) {
182 | const u = Math.pow(rng.value(), 1 / 3);
183 |
184 | let x = rng.value() * 2 - 1;
185 | let y = rng.value() * 2 - 1;
186 | let z = rng.value() * 2 - 1;
187 |
188 | const mag = Math.sqrt(x * x + y * y + z * z);
189 |
190 | x = (u * x) / mag;
191 | y = (u * y) / mag;
192 | z = (u * z) / mag;
193 |
194 | buffer[i] = x * radius + center[0];
195 | buffer[i + 1] = y * radius + center[1];
196 | buffer[i + 2] = z * radius + center[2];
197 | }
198 |
199 | return buffer;
200 | }
201 |
202 | /***
203 | * [2D] Circle
204 | */
205 | type Circle = {
206 | radius?: number;
207 | center?: number[];
208 | };
209 |
210 | const defaultCircle = {
211 | radius: 1,
212 | center: [0, 0],
213 | };
214 |
215 | // random circle https://stackoverflow.com/a/50746409
216 | export function inCircle(
217 | buffer: TypedArray,
218 | circle?: Circle,
219 | rng: Generator = defaultGen
220 | ): TypedArray {
221 | const { radius, center } = {
222 | ...defaultCircle,
223 | ...circle,
224 | };
225 |
226 | for (let i = 0; i < buffer.length; i += 2) {
227 | const r = radius * Math.sqrt(rng.value());
228 | const theta = rng.value() * TAU;
229 |
230 | buffer[i] = Math.sin(theta) * r + center[0];
231 | buffer[i + 1] = Math.cos(theta) * r + center[1];
232 | }
233 |
234 | return buffer;
235 | }
236 |
237 | export function onCircle(
238 | buffer: TypedArray,
239 | circle?: Circle,
240 | rng: Generator = defaultGen
241 | ) {
242 | const { radius, center } = {
243 | ...defaultCircle,
244 | ...circle,
245 | };
246 |
247 | for (let i = 0; i < buffer.length; i += 2) {
248 | const theta = rng.value() * TAU;
249 |
250 | buffer[i] = Math.sin(theta) * radius + center[0];
251 | buffer[i + 1] = Math.cos(theta) * radius + center[1];
252 | }
253 |
254 | return buffer;
255 | }
256 |
257 | /**
258 | * [2D] Plane
259 | */
260 | type Rect = {
261 | sides: number | number[];
262 | };
263 |
264 | const defaultRect = {
265 | sides: 1,
266 | center: [0, 0],
267 | };
268 |
269 | export function inRect(
270 | buffer: T,
271 | rect?: Rect,
272 | rng: Generator = defaultGen
273 | ): T {
274 | const { sides, center } = {
275 | ...defaultRect,
276 | ...rect,
277 | };
278 |
279 | const sideX = typeof sides === "number" ? sides : sides[0];
280 | const sideY = typeof sides === "number" ? sides : sides[1];
281 |
282 | for (let i = 0; i < buffer.length; i += 2) {
283 | buffer[i] = (rng.value() - 0.5) * sideX + center[0];
284 | buffer[i + 1] = (rng.value() - 0.5) * sideY + center[1];
285 | }
286 |
287 | return buffer;
288 | }
289 |
290 | export function onRect(
291 | buffer: TypedArray,
292 | rect?: Rect,
293 | rng: Generator = defaultGen
294 | ) {
295 | return buffer;
296 | }
297 |
298 | /***
299 | * [3D] Box
300 | */
301 | export function inBox(
302 | buffer: TypedArray,
303 | box?: Box,
304 | rng: Generator = defaultGen
305 | ) {
306 | const { sides, center } = {
307 | ...defaultBox,
308 | ...box,
309 | };
310 |
311 | const sideX = typeof sides === "number" ? sides : sides[0];
312 | const sideY = typeof sides === "number" ? sides : sides[1];
313 | const sideZ = typeof sides === "number" ? sides : sides[2];
314 |
315 | for (let i = 0; i < buffer.length; i += 3) {
316 | buffer[i] = (rng.value() - 0.5) * sideX + center[0];
317 | buffer[i + 1] = (rng.value() - 0.5) * sideY + center[1];
318 | buffer[i + 2] = (rng.value() - 0.5) * sideZ + center[2];
319 | }
320 |
321 | return buffer;
322 | }
323 |
324 | type Box = {
325 | sides?: number[] | number;
326 | center?: number[];
327 | };
328 |
329 | const defaultBox = {
330 | sides: 1,
331 | center: [0, 0, 0],
332 | };
333 |
334 | export function onBox(
335 | buffer: TypedArray,
336 | box?: Box,
337 | rng: Generator = defaultGen
338 | ) {
339 | const { sides, center } = {
340 | ...defaultBox,
341 | ...box,
342 | };
343 |
344 | const sideX = typeof sides === "number" ? sides : sides[0];
345 | const sideY = typeof sides === "number" ? sides : sides[1];
346 | const sideZ = typeof sides === "number" ? sides : sides[2];
347 |
348 | for (let i = 0; i < buffer.length; i += 3) {
349 | buffer[i] = (rng.value() - 0.5) * sideX + center[0];
350 | buffer[i + 1] = (rng.value() - 0.5) * sideY + center[1];
351 | buffer[i + 2] = (rng.value() - 0.5) * sideZ + center[2];
352 | }
353 |
354 | return buffer;
355 | }
356 |
357 | export * as noise from "./noise";
358 |
--------------------------------------------------------------------------------
/packages/maath/src/random/noise.ts:
--------------------------------------------------------------------------------
1 | // Adapated from https://github.com/josephg/noisejs
2 |
3 | import { fade, lerp } from "../misc";
4 |
5 | /*
6 | * A speed-improved perlin and simplex noise algorithms for 2D.
7 | *
8 | * Based on example code by Stefan Gustavson (stegu@itn.liu.se).
9 | * Optimisations by Peter Eastman (peastman@drizzle.stanford.edu).
10 | * Better rank ordering method by Stefan Gustavson in 2012.
11 | * Converted to Javascript by Joseph Gentle.
12 | *
13 | * Version 2012-03-09
14 | *
15 | * This code was placed in the public domain by its original author,
16 | * Stefan Gustavson. You may use it as you see fit, but
17 | * attribution is appreciated.
18 | *
19 | */
20 |
21 | class Grad {
22 | constructor(public x: number, public y: number, public z: number) {}
23 |
24 | dot2 = (x: number, y: number) => {
25 | return this.x * x + this.y * y;
26 | };
27 |
28 | dot3 = (x: number, y: number, z: number) => {
29 | return this.x * x + this.y * y + this.z * z;
30 | };
31 | }
32 |
33 | var grad3 = [
34 | new Grad(1, 1, 0),
35 | new Grad(-1, 1, 0),
36 | new Grad(1, -1, 0),
37 | new Grad(-1, -1, 0),
38 | new Grad(1, 0, 1),
39 | new Grad(-1, 0, 1),
40 | new Grad(1, 0, -1),
41 | new Grad(-1, 0, -1),
42 | new Grad(0, 1, 1),
43 | new Grad(0, -1, 1),
44 | new Grad(0, 1, -1),
45 | new Grad(0, -1, -1),
46 | ];
47 |
48 | var p = [
49 | 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140,
50 | 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234,
51 | 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237,
52 | 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48,
53 | 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105,
54 | 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73,
55 | 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86,
56 | 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38,
57 | 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189,
58 | 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101,
59 | 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232,
60 | 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12,
61 | 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31,
62 | 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254,
63 | 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215,
64 | 61, 156, 180,
65 | ];
66 |
67 | // To remove the need for index wrapping, double the permutation table length
68 | var perm = new Array(512);
69 | var gradP = new Array(512);
70 |
71 | // This isn't a very good seeding function, but it works ok. It supports 2^16
72 | // different seed values. Write something better if you need more seeds.
73 | export const seed = function (seed: number) {
74 | if (seed > 0 && seed < 1) {
75 | // Scale the seed out
76 | seed *= 65536;
77 | }
78 |
79 | seed = Math.floor(seed);
80 | if (seed < 256) {
81 | seed |= seed << 8;
82 | }
83 |
84 | for (var i = 0; i < 256; i++) {
85 | var v;
86 | if (i & 1) {
87 | v = p[i] ^ (seed & 255);
88 | } else {
89 | v = p[i] ^ ((seed >> 8) & 255);
90 | }
91 |
92 | perm[i] = perm[i + 256] = v;
93 | gradP[i] = gradP[i + 256] = grad3[v % 12];
94 | }
95 | };
96 |
97 | seed(0);
98 |
99 | /*
100 | for(var i=0; i<256; i++) {
101 | perm[i] = perm[i + 256] = p[i];
102 | gradP[i] = gradP[i + 256] = grad3[perm[i] % 12];
103 | }*/
104 |
105 | // Skewing and unskewing factors for 2, 3, and 4 dimensions
106 | var F2 = 0.5 * (Math.sqrt(3) - 1);
107 | var G2 = (3 - Math.sqrt(3)) / 6;
108 |
109 | var F3 = 1 / 3;
110 | var G3 = 1 / 6;
111 |
112 | // 2D simplex noise
113 | export const simplex2 = function (xin: number, yin: number) {
114 | var n0, n1, n2; // Noise contributions from the three corners
115 | // Skew the input space to determine which simplex cell we're in
116 | var s = (xin + yin) * F2; // Hairy factor for 2D
117 | var i = Math.floor(xin + s);
118 | var j = Math.floor(yin + s);
119 | var t = (i + j) * G2;
120 | var x0 = xin - i + t; // The x,y distances from the cell origin, unskewed.
121 | var y0 = yin - j + t;
122 | // For the 2D case, the simplex shape is an equilateral triangle.
123 | // Determine which simplex we are in.
124 | var i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords
125 | if (x0 > y0) {
126 | // lower triangle, XY order: (0,0)->(1,0)->(1,1)
127 | i1 = 1;
128 | j1 = 0;
129 | } else {
130 | // upper triangle, YX order: (0,0)->(0,1)->(1,1)
131 | i1 = 0;
132 | j1 = 1;
133 | }
134 | // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
135 | // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
136 | // c = (3-sqrt(3))/6
137 | var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords
138 | var y1 = y0 - j1 + G2;
139 | var x2 = x0 - 1 + 2 * G2; // Offsets for last corner in (x,y) unskewed coords
140 | var y2 = y0 - 1 + 2 * G2;
141 | // Work out the hashed gradient indices of the three simplex corners
142 | i &= 255;
143 | j &= 255;
144 | var gi0 = gradP[i + perm[j]];
145 | var gi1 = gradP[i + i1 + perm[j + j1]];
146 | var gi2 = gradP[i + 1 + perm[j + 1]];
147 | // Calculate the contribution from the three corners
148 | var t0 = 0.5 - x0 * x0 - y0 * y0;
149 | if (t0 < 0) {
150 | n0 = 0;
151 | } else {
152 | t0 *= t0;
153 | n0 = t0 * t0 * gi0.dot2(x0, y0); // (x,y) of grad3 used for 2D gradient
154 | }
155 | var t1 = 0.5 - x1 * x1 - y1 * y1;
156 | if (t1 < 0) {
157 | n1 = 0;
158 | } else {
159 | t1 *= t1;
160 | n1 = t1 * t1 * gi1.dot2(x1, y1);
161 | }
162 | var t2 = 0.5 - x2 * x2 - y2 * y2;
163 | if (t2 < 0) {
164 | n2 = 0;
165 | } else {
166 | t2 *= t2;
167 | n2 = t2 * t2 * gi2.dot2(x2, y2);
168 | }
169 | // Add contributions from each corner to get the final noise value.
170 | // The result is scaled to return values in the interval [-1,1].
171 | return 70 * (n0 + n1 + n2);
172 | };
173 |
174 | // 3D simplex noise
175 | export const simplex3 = function (xin: number, yin: number, zin: number) {
176 | var n0, n1, n2, n3; // Noise contributions from the four corners
177 |
178 | // Skew the input space to determine which simplex cell we're in
179 | var s = (xin + yin + zin) * F3; // Hairy factor for 2D
180 | var i = Math.floor(xin + s);
181 | var j = Math.floor(yin + s);
182 | var k = Math.floor(zin + s);
183 |
184 | var t = (i + j + k) * G3;
185 | var x0 = xin - i + t; // The x,y distances from the cell origin, unskewed.
186 | var y0 = yin - j + t;
187 | var z0 = zin - k + t;
188 |
189 | // For the 3D case, the simplex shape is a slightly irregular tetrahedron.
190 | // Determine which simplex we are in.
191 | var i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords
192 | var i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords
193 | if (x0 >= y0) {
194 | if (y0 >= z0) {
195 | i1 = 1;
196 | j1 = 0;
197 | k1 = 0;
198 | i2 = 1;
199 | j2 = 1;
200 | k2 = 0;
201 | } else if (x0 >= z0) {
202 | i1 = 1;
203 | j1 = 0;
204 | k1 = 0;
205 | i2 = 1;
206 | j2 = 0;
207 | k2 = 1;
208 | } else {
209 | i1 = 0;
210 | j1 = 0;
211 | k1 = 1;
212 | i2 = 1;
213 | j2 = 0;
214 | k2 = 1;
215 | }
216 | } else {
217 | if (y0 < z0) {
218 | i1 = 0;
219 | j1 = 0;
220 | k1 = 1;
221 | i2 = 0;
222 | j2 = 1;
223 | k2 = 1;
224 | } else if (x0 < z0) {
225 | i1 = 0;
226 | j1 = 1;
227 | k1 = 0;
228 | i2 = 0;
229 | j2 = 1;
230 | k2 = 1;
231 | } else {
232 | i1 = 0;
233 | j1 = 1;
234 | k1 = 0;
235 | i2 = 1;
236 | j2 = 1;
237 | k2 = 0;
238 | }
239 | }
240 | // A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
241 | // a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
242 | // a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
243 | // c = 1/6.
244 | var x1 = x0 - i1 + G3; // Offsets for second corner
245 | var y1 = y0 - j1 + G3;
246 | var z1 = z0 - k1 + G3;
247 |
248 | var x2 = x0 - i2 + 2 * G3; // Offsets for third corner
249 | var y2 = y0 - j2 + 2 * G3;
250 | var z2 = z0 - k2 + 2 * G3;
251 |
252 | var x3 = x0 - 1 + 3 * G3; // Offsets for fourth corner
253 | var y3 = y0 - 1 + 3 * G3;
254 | var z3 = z0 - 1 + 3 * G3;
255 |
256 | // Work out the hashed gradient indices of the four simplex corners
257 | i &= 255;
258 | j &= 255;
259 | k &= 255;
260 | var gi0 = gradP[i + perm[j + perm[k]]];
261 | var gi1 = gradP[i + i1 + perm[j + j1 + perm[k + k1]]];
262 | var gi2 = gradP[i + i2 + perm[j + j2 + perm[k + k2]]];
263 | var gi3 = gradP[i + 1 + perm[j + 1 + perm[k + 1]]];
264 |
265 | // Calculate the contribution from the four corners
266 | var t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
267 | if (t0 < 0) {
268 | n0 = 0;
269 | } else {
270 | t0 *= t0;
271 | n0 = t0 * t0 * gi0.dot3(x0, y0, z0); // (x,y) of grad3 used for 2D gradient
272 | }
273 | var t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
274 | if (t1 < 0) {
275 | n1 = 0;
276 | } else {
277 | t1 *= t1;
278 | n1 = t1 * t1 * gi1.dot3(x1, y1, z1);
279 | }
280 | var t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
281 | if (t2 < 0) {
282 | n2 = 0;
283 | } else {
284 | t2 *= t2;
285 | n2 = t2 * t2 * gi2.dot3(x2, y2, z2);
286 | }
287 | var t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
288 | if (t3 < 0) {
289 | n3 = 0;
290 | } else {
291 | t3 *= t3;
292 | n3 = t3 * t3 * gi3.dot3(x3, y3, z3);
293 | }
294 | // Add contributions from each corner to get the final noise value.
295 | // The result is scaled to return values in the interval [-1,1].
296 | return 32 * (n0 + n1 + n2 + n3);
297 | };
298 |
299 | // ##### Perlin noise stuff
300 |
301 | // 2D Perlin Noise
302 | export const perlin2 = function (x: number, y: number) {
303 | // Find unit grid cell containing point
304 | var X = Math.floor(x),
305 | Y = Math.floor(y);
306 | // Get relative xy coordinates of point within that cell
307 | x = x - X;
308 | y = y - Y;
309 | // Wrap the integer cells at 255 (smaller integer period can be introduced here)
310 | X = X & 255;
311 | Y = Y & 255;
312 |
313 | // Calculate noise contributions from each of the four corners
314 | var n00 = gradP[X + perm[Y]].dot2(x, y);
315 | var n01 = gradP[X + perm[Y + 1]].dot2(x, y - 1);
316 | var n10 = gradP[X + 1 + perm[Y]].dot2(x - 1, y);
317 | var n11 = gradP[X + 1 + perm[Y + 1]].dot2(x - 1, y - 1);
318 |
319 | // Compute the fade curve value for x
320 | var u = fade(x);
321 |
322 | // Interpolate the four results
323 | return lerp(lerp(n00, n10, u), lerp(n01, n11, u), fade(y));
324 | };
325 |
326 | // 3D Perlin Noise
327 | export const perlin3 = function (x: number, y: number, z: number): number {
328 | // Find unit grid cell containing point
329 | var X = Math.floor(x),
330 | Y = Math.floor(y),
331 | Z = Math.floor(z);
332 | // Get relative xyz coordinates of point within that cell
333 | x = x - X;
334 | y = y - Y;
335 | z = z - Z;
336 | // Wrap the integer cells at 255 (smaller integer period can be introduced here)
337 | X = X & 255;
338 | Y = Y & 255;
339 | Z = Z & 255;
340 |
341 | // Calculate noise contributions from each of the eight corners
342 | var n000 = gradP[X + perm[Y + perm[Z]]].dot3(x, y, z);
343 | var n001 = gradP[X + perm[Y + perm[Z + 1]]].dot3(x, y, z - 1);
344 | var n010 = gradP[X + perm[Y + 1 + perm[Z]]].dot3(x, y - 1, z);
345 | var n011 = gradP[X + perm[Y + 1 + perm[Z + 1]]].dot3(x, y - 1, z - 1);
346 | var n100 = gradP[X + 1 + perm[Y + perm[Z]]].dot3(x - 1, y, z);
347 | var n101 = gradP[X + 1 + perm[Y + perm[Z + 1]]].dot3(x - 1, y, z - 1);
348 | var n110 = gradP[X + 1 + perm[Y + 1 + perm[Z]]].dot3(x - 1, y - 1, z);
349 | var n111 = gradP[X + 1 + perm[Y + 1 + perm[Z + 1]]].dot3(x - 1, y - 1, z - 1);
350 |
351 | // Compute the fade curve value for x, y, z
352 | var u = fade(x);
353 | var v = fade(y);
354 | var w = fade(z);
355 |
356 | // Interpolate
357 | return lerp(
358 | lerp(lerp(n000, n100, u), lerp(n001, n101, u), w),
359 | lerp(lerp(n010, n110, u), lerp(n011, n111, u), w),
360 | v
361 | );
362 | };
363 |
--------------------------------------------------------------------------------
/packages/maath/src/three.ts:
--------------------------------------------------------------------------------
1 | import type { TypedArray } from "./ctypes";
2 | import { Vector2, Vector3 } from "three";
3 |
4 | /**
5 | * Helpers for converting buffers to and from Three.js objects
6 | */
7 |
8 | export function bufferToVectors(buffer: TypedArray, stride: 3): Vector3[];
9 | export function bufferToVectors(buffer: TypedArray, stride: 2): Vector2[];
10 | /**
11 | * Convents passed buffer of passed stride to an array of vectors with the correct length.
12 | *
13 | * @param buffer
14 | * @param stride
15 | * @returns
16 | */
17 | export function bufferToVectors(buffer: TypedArray, stride = 3) {
18 | const p = [];
19 |
20 | for (let i = 0, j = 0; i < buffer.length; i += stride, j++) {
21 | if (stride === 3) {
22 | p[j] = new Vector3(buffer[i], buffer[i + 1], buffer[i + 2]);
23 | } else {
24 | p[j] = new Vector2(buffer[i], buffer[i + 1]);
25 | }
26 | }
27 |
28 | return p;
29 | }
30 |
31 | /**
32 | * Transforms a passed Vector2 or Vector3 array to a points buffer
33 | *
34 | * @param vectorArray
35 | * @returns
36 | */
37 | export function vectorsToBuffer(vectorArray: Vector2[] | Vector3[]) {
38 | const l = vectorArray.length;
39 | const stride = vectorArray[0].hasOwnProperty("z") ? 3 : 2;
40 | const buffer = new Float32Array(l * stride);
41 |
42 | for (let i = 0; i < l; i++) {
43 | let j = i * stride;
44 |
45 | buffer[j] = vectorArray[i].x;
46 | buffer[j + 1] = vectorArray[i].y;
47 |
48 | if (stride === 3) {
49 | buffer[j + 2] = (vectorArray[i] as Vector3).z;
50 | }
51 | }
52 |
53 | return buffer;
54 | }
55 |
--------------------------------------------------------------------------------
/packages/maath/src/triangle.ts:
--------------------------------------------------------------------------------
1 | import { Matrix4, Vector2 } from "three";
2 | import { determinant3, getMinor } from "./matrix";
3 | import type { Triangle } from "./ctypes";
4 |
5 | /**
6 | *
7 | * @param point
8 | *
9 | * @param triangle
10 | *
11 | * @returns {boolean} true if the point is in the triangle
12 | *
13 | * TODO: Find explainer
14 | */
15 | export function isPointInTriangle(point: number[], triangle: Triangle) {
16 | const [ax, ay] = triangle[0];
17 | const [bx, by] = triangle[1];
18 | const [cx, cy] = triangle[2];
19 |
20 | const [px, py] = point;
21 |
22 | // TODO Sub with static calc
23 | const matrix = new Matrix4();
24 |
25 | // prettier-ignore
26 | matrix.set(
27 | ax, ay, ax * ax + ay * ay, 1,
28 | bx, by, bx * bx + by * by, 1,
29 | cx, cy, cx * cx + cy * cy, 1,
30 | px, py, px * px + py * py, 1
31 | )
32 |
33 | return matrix.determinant() <= 0;
34 | }
35 |
36 | export function triangleDeterminant(triangle: Triangle) {
37 | const [x1, y1] = triangle[0];
38 | const [x2, y2] = triangle[1];
39 | const [x3, y3] = triangle[2];
40 |
41 | // prettier-ignore
42 | return determinant3(
43 | x1, y1, 1,
44 | x2, y2, 1,
45 | x3, y3, 1
46 | )
47 | }
48 |
49 | /**
50 | * Uses triangle area determinant to check if 3 points are collinear.
51 | * If they are, they can't make a triangle, so the determinant will be 0!
52 | *
53 | * 0 1 2
54 | * ─────■─────■─────■
55 | *
56 | *
57 | * Fun fact, you can use this same determinant to check the order of the points in the triangle
58 | *
59 | * NOTE: Should this use a buffer instead? NOTE: Should this use a buffer instead? [x0, y0, x1, y1, x2, y2]?
60 | *
61 | */
62 | export function arePointsCollinear(points: Triangle) {
63 | return triangleDeterminant(points) === 0;
64 | }
65 |
66 | // TODO This is the same principle as the prev function, find a way to make it have sense
67 | export function isTriangleClockwise(triangle: Triangle) {
68 | return triangleDeterminant(triangle) < 0;
69 | }
70 |
71 | /**
72 |
73 | The circumcircle is a circle touching all the vertices of a triangle or polygon.
74 |
75 | ┌───┐
76 | │ B │
77 | └───┘
78 | .───●───.
79 | ,─' ╱ ╲ '─.
80 | ,' ╱ ╲ `.
81 | ╱ ╱ ╲ ╲
82 | ; ╱ ╲ :
83 | │ ╱ ╲ │
84 | │ ╱ ╲ │
85 | : ╱ ╲ ;
86 | ╲ ╱ ╲ ╱
87 | ┌───┐ ●─────────────────● ┌───┐
88 | │ A │ `. ,' │ C │
89 | └───┘ '─. ,─' └───┘
90 | `─────'
91 | */
92 |
93 | /**
94 | *
95 | * @param triangle
96 | *
97 | * @returns {number} circumcircle
98 | */
99 |
100 | // https://math.stackexchange.com/a/1460096
101 | export function getCircumcircle(triangle: Triangle) {
102 | // TS-TODO the next few lines are ignored because the types aren't current to the change in vectors (that can now be iterated)
103 |
104 | // @ts-ignore
105 | const [ax, ay] = triangle[0];
106 | // @ts-ignore
107 | const [bx, by] = triangle[1];
108 | // @ts-ignore
109 | const [cx, cy] = triangle[2];
110 |
111 | if (arePointsCollinear(triangle)) return null; // points are collinear
112 |
113 | const m = new Matrix4();
114 | // prettier-ignore
115 | m.set(
116 | 1, 1, 1, 1,
117 | ax * ax + ay * ay, ax, ay, 1,
118 | bx * bx + by * by, bx, by, 1,
119 | cx * cx + cy * cy, cx, cy, 1
120 | )
121 |
122 | const m11 = getMinor(m, 1, 1);
123 | const m13 = getMinor(m, 1, 3);
124 | const m12 = getMinor(m, 1, 2);
125 | const m14 = getMinor(m, 1, 4);
126 |
127 | const x0 = 0.5 * (m12 / m11);
128 | const y0 = 0.5 * (m13 / m11);
129 |
130 | const r2 = x0 * x0 + y0 * y0 + m14 / m11;
131 |
132 | return {
133 | x: Math.abs(x0) === 0 ? 0 : x0,
134 | y: Math.abs(y0) === 0 ? 0 : -y0,
135 | r: Math.sqrt(r2),
136 | };
137 | }
138 |
139 | // https://stackoverflow.com/questions/39984709/how-can-i-check-wether-a-point-is-inside-the-circumcircle-of-3-points
140 | export function isPointInCircumcircle(point: number[], triangle: Triangle) {
141 | const [ax, ay] = Array.isArray(triangle[0])
142 | ? triangle[0]
143 | : triangle[0].toArray();
144 | const [bx, by] = Array.isArray(triangle[1])
145 | ? triangle[1]
146 | : triangle[1].toArray();
147 | const [cx, cy] = Array.isArray(triangle[2])
148 | ? triangle[2]
149 | : triangle[2].toArray();
150 |
151 | const [px, py] = point;
152 |
153 | if (arePointsCollinear(triangle))
154 | throw new Error("Collinear points don't form a triangle");
155 |
156 | /**
157 | | ax-px, ay-py, (ax-px)² + (ay-py)² |
158 | det = | bx-px, by-py, (bx-px)² + (by-py)² |
159 | | cx-px, cy-py, (cx-px)² + (cy-py)² |
160 | */
161 | const x1mpx = ax - px;
162 | const aympy = ay - py;
163 |
164 | const bxmpx = bx - px;
165 | const bympy = by - py;
166 |
167 | const cxmpx = cx - px;
168 | const cympy = cy - py;
169 |
170 | // prettier-ignore
171 | const d = determinant3(
172 | x1mpx, aympy, x1mpx * x1mpx + aympy * aympy,
173 | bxmpx, bympy, bxmpx * bxmpx + bympy * bympy,
174 | cxmpx, cympy, cxmpx * cxmpx + cympy * cympy,
175 | )
176 |
177 | // if d is 0, the point is on C
178 | if (d === 0) {
179 | return true;
180 | }
181 |
182 | return !isTriangleClockwise(triangle) ? d > 0 : d < 0;
183 | }
184 |
185 | // From https://algorithmtutor.com/Computational-Geometry/Determining-if-two-consecutive-segments-turn-left-or-right/
186 | const mv1 = new Vector2();
187 | const mv2 = new Vector2();
188 |
189 | /**
190 |
191 | ╱ ╲
192 | ╱ ╲
193 | ▕ ▏
194 |
195 | right left
196 |
197 | * NOTE: Should this use a buffer instead? [x0, y0, x1, y1]?
198 | */
199 | export function doThreePointsMakeARight(points: Triangle | Vector2[]) {
200 | const [p1, p2, p3] = points.map((p) => {
201 | if (Array.isArray(p)) {
202 | return new Vector2(...p);
203 | }
204 |
205 | return p;
206 | });
207 |
208 | if (arePointsCollinear(points)) return false;
209 |
210 | // @ts-ignore
211 | const p2p1 = mv1.subVectors(p2, p1);
212 | // @ts-ignore
213 | const p3p1 = mv2.subVectors(p3, p1);
214 |
215 | const cross = p3p1.cross(p2p1);
216 |
217 | return cross > 0;
218 | }
219 |
--------------------------------------------------------------------------------
/packages/maath/src/vector2.ts:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | export type V2 = [x: number, y: number];
5 |
6 | export function zero(): V2 {
7 | return [0, 0];
8 | }
9 |
10 | export function one(): V2 {
11 | return [1, 1];
12 | }
13 |
14 | export function add(a: V2, b: V2): V2 {
15 | return [a[0] + b[0], a[1] + b[1]];
16 | }
17 |
18 | export function addValue(a: V2, n: number): V2 {
19 | return [a[0] + n, a[1] + n];
20 | }
21 |
22 | export function sub(a: V2, b: V2): V2 {
23 | return [a[0] - b[0], a[1] - b[1]];
24 | }
25 |
26 | export function subValue(a: V2, n: number): V2 {
27 | return [a[0] - n, a[1] - n];
28 | }
29 |
30 | export function scale(a: V2, n: number): V2 {
31 | return [a[0] * n, a[1] * n];
32 | }
33 |
34 | export function dot(a: V2, b: V2): number {
35 | return a[0] * b[0] + a[1] * b[1];
36 | }
37 |
38 | /**
39 | * Calculate the squared length of a vector.
40 | * Use this when comparing two vectors instead of length, as it's more efficient (no sqrt)
41 | */
42 | export function lengthSqr(a: V2): number {
43 | return a[0] * a[0] + a[1] * a[1];
44 | }
45 |
46 | /**
47 | * Calculate the length of a vector.
48 | * If you only need to compare lenghts, consider using the more efficient lengthSqr
49 | */
50 | export function length(a: V2): number {
51 | return Math.sqrt(a[0] * a[0] + a[1] * a[1]);
52 | }
53 |
54 | export function distance(a: V2, b: V2): number {
55 | return Math.sqrt(
56 | (a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1])
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/packages/maath/src/vector3.ts:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | export type V3 = [x: number, y: number, z: number];
5 |
6 | export function zero(): V3 {
7 | return [0, 0, 0];
8 | }
9 |
10 | export function one(): V3 {
11 | return [1, 1, 1];
12 | }
13 |
14 | export function add(a: V3, b: V3): V3 {
15 | return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
16 | }
17 |
18 | export function addValue(a: V3, n: number): V3 {
19 | return [a[0] + n, a[1] + n, a[2] + n];
20 | }
21 |
22 | export function sub(a: V3, b: V3): V3 {
23 | return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
24 | }
25 |
26 | export function subValue(a: V3, n: number): V3 {
27 | return [a[0] - n, a[1] - n, a[2] - n];
28 | }
29 |
30 | export function scale(a: V3, n: number): V3 {
31 | return [a[0] * n, a[1] * n, a[2] * n];
32 | }
33 |
34 | export function dot(a: V3, b: V3): number {
35 | return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
36 | }
37 |
38 | export function cross(a: V3, b: V3): V3 {
39 | const x = a[1] * b[2] - a[2] * b[1];
40 | const y = a[2] * b[0] - a[0] * b[2];
41 | const z = a[0] * b[1] - a[1] * b[0];
42 |
43 | return [x, y, z];
44 | }
45 |
46 | /**
47 | * Calculate the squared length of a vector.
48 | * Use this when comparing two vectors instead of length, as it's more efficient (no sqrt)
49 | */
50 | export function lengthSqr(a: V3): number {
51 | return a[0] * a[0] + a[1] * a[1] + a[2] * a[2];
52 | }
53 |
54 | /**
55 | * Calculate the length of a vector.
56 | * If you only need to compare lenghts, consider using the more efficient lengthSqr
57 | */
58 | export function length(a: V3): number {
59 | return Math.sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]);
60 | }
61 |
62 | export function distance(a: V3, b: V3): number {
63 | return Math.sqrt(
64 | (a[0] - b[0]) * (a[0] - b[0]) +
65 | (a[1] - b[1]) * (a[1] - b[1]) +
66 | (a[2] - b[2]) * (a[2] - b[2])
67 | );
68 | }
69 |
--------------------------------------------------------------------------------
/packages/maath/three/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "dist/maath-three.cjs.js",
3 | "module": "dist/maath-three.esm.js",
4 | "peerDependencies": {
5 | "three": "^0.134.0"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/maath/triangle/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "dist/maath-triangle.cjs.js",
3 | "module": "dist/maath-triangle.esm.js"
4 | }
5 |
--------------------------------------------------------------------------------
/packages/maath/vector2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "dist/maath-vector2.cjs.js",
3 | "module": "dist/maath-vector2.esm.js"
4 | }
5 |
--------------------------------------------------------------------------------
/packages/maath/vector3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "dist/maath-vector3.cjs.js",
3 | "module": "dist/maath-vector3.esm.js"
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | // prettier-ignore
2 | {
3 | "include": ["packages/*/src/**/*"],
4 | "compilerOptions": {
5 | "target": "ES5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
6 | "module": "ESNext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
7 | "lib": ["DOM", "ESNext"] /* Specify library files to be included in the compilation. */,
8 | "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
9 | "noEmit": true, /* Do not emit outputs. */
10 | "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. We use it for the Bezier returned value. */
11 | "strict": true /* Enable all strict type-checking options. */,
12 | "noUnusedLocals": true, /* Report errors on unused locals. */
13 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
14 | "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
15 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
16 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
17 | "skipLibCheck": true,
18 | "forceConsistentCasingInFileNames": true,
19 | }
20 | }
21 |
--------------------------------------------------------------------------------