├── .changeset └── config.json ├── .codesandbox └── ci.json ├── .github └── workflows │ ├── coverage.yml │ └── release.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .scripts ├── copy.js ├── publish.sh └── version.js ├── README.md ├── babel.config.js ├── demo ├── .gitignore ├── index.html ├── package.json ├── src │ ├── App.tsx │ ├── components │ │ ├── Demo.tsx │ │ ├── Header.tsx │ │ ├── Points.tsx │ │ ├── Sandbox.tsx │ │ └── SandboxOverlay.tsx │ ├── index.css │ ├── logo.svg │ ├── main.tsx │ ├── sandboxes │ │ ├── circumcircle │ │ │ ├── package.json │ │ │ ├── public │ │ │ │ └── index.html │ │ │ ├── src │ │ │ │ ├── App.tsx │ │ │ │ └── index.tsx │ │ │ └── tsconfig.json │ │ ├── convex-hull │ │ │ ├── package.json │ │ │ ├── public │ │ │ │ └── index.html │ │ │ ├── src │ │ │ │ ├── App.tsx │ │ │ │ └── index.tsx │ │ │ └── tsconfig.json │ │ ├── points │ │ │ ├── package.json │ │ │ ├── public │ │ │ │ └── index.html │ │ │ ├── src │ │ │ │ ├── App.tsx │ │ │ │ └── index.tsx │ │ │ └── tsconfig.json │ │ ├── sorting │ │ │ ├── package.json │ │ │ ├── public │ │ │ │ └── index.html │ │ │ ├── src │ │ │ │ ├── App.tsx │ │ │ │ └── index.tsx │ │ │ └── tsconfig.json │ │ └── sutherlandHodgman │ │ │ ├── package.json │ │ │ ├── public │ │ │ └── index.html │ │ │ ├── src │ │ │ ├── App.tsx │ │ │ └── index.tsx │ │ │ └── tsconfig.json │ ├── store.ts │ └── vite-env.d.ts ├── tsconfig.json └── vite.config.ts ├── hero.svg ├── jest.config.ts ├── package.json ├── packages └── maath │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── buffer │ └── package.json │ ├── easing │ └── package.json │ ├── geometry │ └── package.json │ ├── matrix │ └── package.json │ ├── misc │ └── package.json │ ├── package.json │ ├── random │ └── package.json │ ├── src │ ├── __test__ │ │ ├── buffer.test.js │ │ ├── random.test.js │ │ ├── triangle.test.js │ │ └── utils.js │ ├── buffer.ts │ ├── ctypes.ts │ ├── easing.ts │ ├── geometry.ts │ ├── index.ts │ ├── matrix.ts │ ├── misc.ts │ ├── random │ │ ├── index.ts │ │ └── noise.ts │ ├── three.ts │ ├── triangle.ts │ ├── vector2.ts │ └── vector3.ts │ ├── three │ └── package.json │ ├── triangle │ └── package.json │ ├── vector2 │ └── package.json │ └── vector3 │ └── package.json ├── tsconfig.json └── yarn.lock /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.6.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "access": "public", 6 | "baseBranch": "main", 7 | "updateInternalDependencies": "patch", 8 | "ignore": ["demo"] 9 | } 10 | -------------------------------------------------------------------------------- /.codesandbox/ci.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*"], 3 | "sandboxes": [ 4 | "/demo/src/sandboxes/circumcircle", 5 | "/demo/src/sandboxes/convexHull", 6 | "/demo/src/sandboxes/points", 7 | "/demo/src/sandboxes/boxSphere", 8 | "/demo/src/sandboxes/sutherlandHodgman" 9 | ], 10 | "node": "18" 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: "test" 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | - main 7 | jobs: 8 | coverage: 9 | runs-on: ubuntu-latest 10 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Install modules 14 | run: yarn 15 | - name: Preconstruct Build 16 | run: yarn build 17 | - name: test 18 | run: yarn test 19 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "**/package.json" 9 | - ".changeset/**" 10 | 11 | jobs: 12 | release: 13 | name: Release 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout Repo 17 | uses: actions/checkout@v2 18 | with: 19 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 20 | fetch-depth: 0 21 | 22 | - name: Use Node 23 | uses: actions/setup-node@v2 24 | with: 25 | node-version: "14" 26 | 27 | - name: Install dependencies 28 | run: yarn install --silent 29 | 30 | - name: Create Release Pull Request or Publish to npm 31 | id: changesets 32 | uses: changesets/action@master 33 | with: 34 | # This expects you to have a script called release which does a build for your packages and calls changeset publish 35 | publish: yarn release 36 | commit: "chore(release): update monorepo packages versions" 37 | title: "Upcoming Release Changes" 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | .env.production 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | .parcel-cache 80 | 81 | # Next.js build output 82 | .next 83 | out 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # yarn v2 114 | .yarn/cache 115 | .yarn/unplugged 116 | .yarn/build-state.yml 117 | .yarn/install-state.gz 118 | .pnp.* 119 | 120 | .DS_Store -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.scripts/copy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Just copies the utility Points file to sandboxes so that I don't have to deal with that 3 | */ 4 | const sandboxesFolder = "./demo/src/sandboxes/"; 5 | const fs = require("fs"); 6 | 7 | const pointsFile = fs.readFileSync("./demo/src/components/Points.tsx", "utf-8"); 8 | 9 | fs.readdirSync(sandboxesFolder) 10 | .filter((file) => file !== ".DS_Store") 11 | .forEach((file) => { 12 | fs.writeFileSync(`${sandboxesFolder}${file}/src/Points.tsx`, pointsFile); 13 | }); 14 | -------------------------------------------------------------------------------- /.scripts/publish.sh: -------------------------------------------------------------------------------- 1 | node ./.scripts/version.js 2 | NODE_ENV="production" && yarn build 3 | cd packages/maath 4 | npm publish -------------------------------------------------------------------------------- /.scripts/version.js: -------------------------------------------------------------------------------- 1 | const semver = require("semver"); 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | 5 | const sandboxesFolder = ["demo", "src", "sandboxes"]; 6 | 7 | const DREI_V = "^7.22.0"; 8 | const currentV = JSON.parse( 9 | fs.readFileSync(path.join("packages", "maath", "package.json"), "utf-8") 10 | ).version; 11 | const NEW_V = semver.inc(currentV, "prerelease"); 12 | 13 | function mutateJSONAtPath(mutate, ..._path) { 14 | const packagePath = path.join(..._path, "package.json"); 15 | const json = fs.readFileSync(packagePath, "utf-8"); 16 | const parsed = JSON.parse(json); 17 | 18 | mutate(parsed); 19 | fs.writeFileSync(packagePath, JSON.stringify(parsed, null, " ")); 20 | } 21 | 22 | // 1. update all versions in the sandboxes 23 | fs.readdirSync(path.join(...sandboxesFolder)) 24 | .filter((file) => file !== ".DS_Store") 25 | .forEach((file) => { 26 | mutateJSONAtPath( 27 | (json) => { 28 | json.dependencies.maath = NEW_V; 29 | json.dependencies["@react-three/drei"] = DREI_V; 30 | }, 31 | ...sandboxesFolder, 32 | file 33 | ); 34 | }); 35 | 36 | // 2. update version in demo 37 | mutateJSONAtPath((json) => { 38 | json.dependencies.maath = NEW_V; 39 | json.dependencies["@react-three/drei"] = DREI_V; 40 | }, "demo"); 41 | 42 | // 3. update version in package 43 | mutateJSONAtPath((json) => (json.version = NEW_V), "packages", "maath"); 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Discord Shield](https://img.shields.io/discord/740090768164651008?style=flat&colorA=000000&colorB=000000&label=&logo=discord&logoColor=ffffff)](https://discord.gg/poimandres) 2 | 3 | 4 |
5 | 6 | ```bash 7 | yarn add maath 8 | ``` 9 | 10 | This is a collection of useful math helpers, random generators, bits and bobs. 11 | 12 | The library is mostly meant to be used with [three.js](https://github.com/mrdoob/three.js/), so if you are using it outside of a three project, make sure you check the source and - if you don't need the dep - just copy paste! 13 | 14 | ### Check out the demos on Codesandbox: 🪶 15 | 16 | | | | | 17 | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 18 | 19 | > 🟡 The library is still heavily WIP 20 | 21 | ### But why? 22 | 23 | Yes, there are a lot of these libraries. The more the merrier! We are all here to learn, and maintaining a dedicated library for our creative endeavors at [Poimandres](https://github.com/pmndrs) just made sense. 24 | 25 | ## Contributing 26 | 27 | Do you want to add something? No rules, but keep these in mind: 28 | 29 | - try to add explainers to whatever function you add, if you saw it in a tweet link that! 30 | - add a cool example! Make it a sandbox or whatever, just show how the function can be used creatively 31 | - keep copy-paste simple. Try not to add too many inter-dependencies so the functions are copy-paste friendly 32 | - loose typing. Try to add typing, but don't go crazy with generics and complexity 33 | 34 | If you are not sure how to help, check out the [🟡 Roadmap](#-roadmap) below. 35 | 36 | ## 🪶 Reference 37 | 38 | ### Using specific entry points 39 | 40 | ```js 41 | // you can import the namespaces from the main entrypoint 42 | import { buffer, random } from "maath"; 43 | // or import each function or all of them from each namespace entrypoint 44 | import * as buffer from "maath/buffer"; 45 | import { inSphere } from "maath/random"; 46 | ``` 47 | 48 | ### Buffer 49 | 50 | ```js 51 | import * as buffer from "maath/buffer"; 52 | ``` 53 | 54 | #### 🪶 toVectorArray(buffer, stride) 55 | 56 | Converts an `[..., x, y, z, ...]` typed array to a `Vector[]` 57 | 58 | ```js 59 | const myBuffer = new Float32Array(100 * 3); 60 | const myArray = toVectorArray(myBuffer, 3); 61 | ``` 62 | 63 | #### 🪶 swizzleBuffer(buffer, axes) 64 | 65 | [Swizzle]() the individual vectors in a vector buffer 66 | 67 | ```js 68 | const myBuffer = new Float32Array(100 * 3); 69 | myBuffer.push(0, 1, 2); 70 | 71 | swizzleBuffer(myBuffer, "xzy"); // buffer is now [0, 2, 1] 72 | ``` 73 | 74 | This is a way to make simple rotations. 75 | 76 | #### 🪶 addAxis(buffer, size, getZValue) 77 | 78 | Adds a z axis to an `[..., x, y, ...]` typed array: 79 | 80 | ```js 81 | const my2DBuffer = new Float32Array(100 * 2); 82 | const my3DBuffer = addAxis(my2DBuffer, 2, () => Math.random()); // zAxis will now be a random value between 0 and 1 83 | const my4DBuffer = addAxis(my3DBuffer, 3, () => 1); // 4th value (imagine a in rgba) will be 1 84 | ``` 85 | 86 | #### 🪶 lerpBuffers(bufferA, bufferB, destinationBuffer, t) 87 | 88 | Linearly interpolate two buffers, writing on a third one. 89 | 90 | ```js 91 | const mySphere = inSphere(new Float32Array(100 * 3), { radius: 4 }); 92 | const myBox = inBox(new Float32Array(100 * 3), { side: 4 }); 93 | 94 | const interpolationTarget = myBox.slice(0); 95 | 96 | lerpBuffers(mySphere, myBox, interpolationTarget, Math.sin(performance.now())); 97 | ``` 98 | 99 | ### Geometry 100 | 101 | ```js 102 | import * as geometry from "maath/geometry"; 103 | ``` 104 | 105 | #### 🪶 roundedPlaneGeometry(width = 1, height = 1, radius = 0.2, segments = 16) 106 | 107 | ```js 108 | const geo = new RoundedPlaneGeometry(); 109 | const mesh = new THREE.Mesh(geo, material); 110 | ``` 111 | 112 | #### 🪶 applyBoxUV(bufferGeometry) 113 | 114 | Applies box-projected UVs to a buffer geometry. 115 | 116 | #### 🪶 applySphereUV(bufferGeometry) 117 | 118 | Applies spherical UVs to a buffer geometry. 119 | 120 | #### 🪶 applyCylindricalUV(bufferGeometry) 121 | 122 | Applies cylindrical-projected UVs to a buffer geometry. 123 | 124 | ### Easing 125 | 126 | ```js 127 | import * as easing from "maath/easing"; 128 | ``` 129 | 130 | Unity-smooth-damping functions based on Game Programming Gems 4 Chapter 1.10. These are fast, refresh-rate independent, interruptible animation primitives primed to THREE.Vector2D, 3D, 4D, Euler (shortest path), Matrix4, Quaternion and Color. 131 | 132 | ```tsx 133 | export function damp( 134 | /** The object */ 135 | current: { [key: string]: any }, 136 | /** The key to animate */ 137 | prop: string, 138 | /** To target (or goal) value */ 139 | target: number, 140 | /** Approximate time to reach the target. A smaller value will reach the target faster. */ 141 | smoothTime = 0.25, 142 | /** Frame delta, for refreshrate independence */ 143 | delta = 0.01, 144 | /** Optionally allows you to clamp the maximum speed. If smoothTime is 0.25s and looks OK 145 | * going between two close points but not for points far apart as it'll move very rapid, 146 | * then a maxSpeed of e.g. 1 which will clamp the speed to 1 unit per second, it may now 147 | * take much longer than smoothTime to reach the target if it is far away. */ 148 | maxSpeed = Infinity, 149 | /** Easing function */ 150 | easing = (t: number) => 1 / (1 + t + 0.48 * t * t + 0.235 * t * t * t), 151 | /** End of animation precision */ 152 | eps = 0.001 153 | ); 154 | ``` 155 | 156 | ```jsx 157 | import { damp, damp2, damp3, damp4, dampE, dampM, dampQ, dampS, dampC } from 'maath/easing' 158 | 159 | function frameloop() { 160 | const delta = clock.getDelta() 161 | // Animates foo.bar to 10 162 | damp(foo, "bar", 10, 0.25, delta) 163 | 164 | // Animates mesh.position to 0,1,2 165 | damp3(mesh.position, [0, 1, 2], 0.25, delta) 166 | // Also takes vectors, shallow vectors and scalars 167 | // damp3(mesh.position, new THREE.Vector3(0, 1, 2), 0.25, delta) 168 | // damp3(mesh.position, { x: 0, y: 1, z: 2 }, 0.25, delta) 169 | // damp3(mesh.scale, 2, 0.25, delta) 170 | 171 | dampC(mesh.material.color, "green", 0.25, delta) 172 | // Also takes colors, fake colors, numbers and arrays 173 | // dampC(mesh.material.color, new THREE.Color("green"), 0.25, delta) 174 | // dampC(mesh.material.color, 0xdead00, 0.25, delta) 175 | // dampC(mesh.material.color, [1, 0, 0], 0.25, delta) 176 | // dampC(mesh.material.color, { r: 1, g: 0, b: 0 }, 0.25, delta) 177 | 178 | // Animates an euler with a shortest-path algorithm 179 | dampE(mesh.rotation, [Math.PI / 2, 0, 0], 0.25, delta) 180 | // Also takes eulers 181 | // dampE(mesh.rotation, new THREE.Euler(Math.PI / 2, 0, 0), 0.25, delta) 182 | 183 | // damp2 for Vector2 184 | // damp4 for Vector4 185 | // dampM for Matrix4 186 | // dampQ for Quaternion 187 | // dampS for Spherical 188 | ``` 189 | 190 | There are two special damping functions for angles and lookAt: 191 | 192 | ```jsx 193 | import { dampAngle, dampLookAt } from "maath/easing"; 194 | 195 | // Animates angle to targetAngle, with a shortest-path algorithm 196 | dampAngle(angle, targetAngle, 0.25, delta); 197 | // Animates a meshes look-up 198 | dampLookAt(mesh, focus, 0.25, delta); 199 | ``` 200 | 201 | #### 🪶 easing functions 202 | 203 | 6 easing functions are available with in, out and inOut variants: 204 | 205 | ```jsx 206 | import { sine, cubic, quint, circ, quart, expo } from "maath/easing"; 207 | 208 | sine.in(t); 209 | sine.out(t); 210 | sine.inOut(t); 211 | ``` 212 | 213 | 3 specific functions: 214 | 215 | ```jsx 216 | import { rsqw, exp, linear } from "maath/easing"; 217 | 218 | // rounded-square wave 219 | rsqw(t, delta); 220 | // unity smooth damping 221 | exp(t); 222 | // linear 223 | linear(t); // === t 224 | ``` 225 | 226 | ### Matrix 227 | 228 | ```js 229 | import * as matrix from "maath/matrix"; 230 | ``` 231 | 232 | #### 🪶 determinant2(...matrixInRowMajorOrder) 233 | 234 | Returns the determinant of a passed 2x2 matrix: 235 | 236 | ```js 237 | const d = determinant2(1, 1, 2, 2); 238 | ``` 239 | 240 | #### 🪶 determinant3(...matrixInRowMajorOrder) 241 | 242 | Returns the determinant of a passed 3x3 matrix: 243 | 244 | ```js 245 | const d = determinant3(1, 1, 1, 2, 2, 2); 246 | ``` 247 | 248 | #### 🪶 determinant4(...matrixInRowMajorOrder) // TBD 249 | 250 | #### 🪶 getMinor(matrix, column, row) 251 | 252 | Returns the [minor]() of a given matrix. 253 | 254 | ```js 255 | const minor = getMinor([1, 2, 1, 2, 1, 1, 3, 2, 3], 1, 1); 256 | 257 | // minor will be the determinant of the submatrix without row 1 and colum 1 258 | // | 1 1 | 259 | // | 2 3 | 260 | ``` 261 | 262 | ### Misc 263 | 264 | ```tsx 265 | // Clamps a value between a range. 266 | clamp(value: number, min: number, max: number): number 267 | // Loops the value t, so that it is never larger than length and never smaller than 0. 268 | repeat(t: number, length: number): number 269 | // Calculates the shortest difference between two given angles. 270 | deltaAngle(current: number, target: number): number 271 | // Converts degrees to radians. 272 | degToRad(degrees: number): number 273 | // Converts radians to degrees. 274 | radToDeg(radians: number): number 275 | // adapted from https://gist.github.com/stephanbogner/a5f50548a06bec723dcb0991dcbb0856 by https://twitter.com/st_phan 276 | fibonacciOnSphere(buffer: TypedArray, { radius = 1 }): void 277 | // Checks if vector a is equal to vector b, with tolerance 278 | vectorEquals(a, b, eps = Number.EPSILON): boolean 279 | /** 280 | * Sorts vectors in lexicographic order, works with both v2 and v3 281 | * 282 | * Use as: 283 | * const sorted = arrayOfVectors.sort(lexicographicOrder) 284 | */ 285 | // https://en.wikipedia.org/wiki/Lexicographic_order 286 | lexicographic(a: Vector2 | Vector3, b: Vector2 | Vector3): number 287 | /** 288 | * Convex Hull 289 | * 290 | * Returns an array of 2D Vectors representing the convex hull of a set of 2D Vectors 291 | */ 292 | convexHull(_points: Vector2[]): Vector2[] 293 | // ... 294 | remap(x: number, [low1, high1]: number[], [low2, high2]: number[]) 295 | ``` 296 | 297 | ### Random 298 | 299 | ```js 300 | import * as random from "maath/random"; 301 | ``` 302 | 303 | #### 🪶 onTorus(buffer, { innerRadius, outerRadius }) 304 | 305 | [TODO](https://math.stackexchange.com/questions/2017079/uniform-random-points-on-a-torus) 306 | 307 | #### 🪶 inTorus(buffer, { innerRadius, outerRadius }) 308 | 309 | [TODO](https://answers.unity.com/questions/1259394/finding-random-position-in-torus.html) 310 | 311 | ### Triangle 312 | 313 | ```js 314 | import * as triangle from "maath/triangle"; 315 | ``` 316 | 317 | TBD 318 | 319 | ## Inspiration 320 | 321 | The kitchen-sink nature of the library was inspired by other projects that manage to bring together an immense amount of knowledge from different domains that would otherwise be fragmented in many places or even lost: 322 | 323 | - [drei](https://github.com/pmndrs/drei) 🌭 useful helpers for react-three-fiber 324 | - [lygia](https://github.com/patriciogonzalezvivo/lygia) a granular and multi-language shader library designed for performance and flexibility 325 | 326 | ## 🟡 Roadmap 327 | 328 | - Make the random generator seedable for every function 329 | - Figure out a good API for vectors 330 | - Figure out a good API for functions that might work on both buffers and arrays of vectors 331 | - Fix type errors that might come from using different vector libs 332 | - Keep adding tests 333 | - Figure out if we can get rid of the Three.js dependency. While useful, it feels superfluous 334 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@babel/preset-env", "@babel/preset-typescript"], 3 | }; 4 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Maath | Poimandres 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "serve": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@pmndrs/branding": "^0.0.8", 12 | "@react-three/drei": "^7.22.0", 13 | "@react-three/fiber": "^7.0.19", 14 | "@types/three": "^0.134.0", 15 | "colord": "^2.9.1", 16 | "leva": "^0.9.14", 17 | "maath": "*", 18 | "nice-color-palettes": "^3.0.0", 19 | "react": "^17.0.2", 20 | "react-dom": "^17.0.2", 21 | "three": "^0.134.0", 22 | "wouter": "^2.8.0-alpha.2", 23 | "zustand": "^3.6.5" 24 | }, 25 | "devDependencies": { 26 | "@types/react": "^17.0.34", 27 | "@types/react-dom": "^17.0.11", 28 | "@vitejs/plugin-react": "^1.0.9", 29 | "typescript": "^4.4.4", 30 | "vite": "^2.6.14" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /demo/src/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Canvas } from "@react-three/fiber"; 3 | import Demos from "./components/Demo"; 4 | 5 | export default function App() { 6 | return ( 7 | 13 | 14 | 19 | 20 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /demo/src/components/Demo.tsx: -------------------------------------------------------------------------------- 1 | import { lazy, ReactNode, useRef, useState } from "react"; 2 | 3 | import { useFrame } from "@react-three/fiber"; 4 | import { RoundedBox } from "@react-three/drei"; 5 | import { Group, Vector3 } from "three"; 6 | 7 | import { remap, lerp } from "maath/misc"; 8 | 9 | import { useStore } from "../store"; 10 | 11 | // prettier-ignore 12 | const CircumcircleDemo = lazy(() => import("../sandboxes/circumcircle/src/App")); 13 | const ConvexHullDemo = lazy(() => import("../sandboxes/convex-hull/src/App")); 14 | const PointsDemo = lazy(() => import("../sandboxes/points/src/App")); 15 | 16 | function Demo({ 17 | position, 18 | text, 19 | children, 20 | color, 21 | slug, 22 | }: { 23 | position?: Vector3 | [x: number, y: number, z: number]; 24 | text?: string; 25 | color: string; 26 | children: ReactNode; 27 | slug: "circumcircle" | "convex-hull" | "points"; 28 | }) { 29 | const container = useRef(null!); 30 | const [hover, setHover] = useState(false); 31 | 32 | useFrame(({ clock }) => { 33 | if (hover) { 34 | container.current.position.z = lerp( 35 | container.current.position.z, 36 | remap(Math.sin(clock.getElapsedTime() * 3), [-1, 1], [-0.05, 0.05]), 37 | 0.1 38 | ); 39 | } else { 40 | container.current.position.z = lerp(container.current.position.z, 0, 0.1); 41 | } 42 | }); 43 | 44 | const setActive = useStore((state) => state.setActive); 45 | 46 | return ( 47 | 48 | { 50 | setHover(true); 51 | e.stopPropagation(); 52 | }} 53 | onPointerLeave={(e) => { 54 | setHover(false); 55 | e.stopPropagation(); 56 | }} 57 | onPointerDown={() => setActive(slug)} 58 | > 59 | 60 | 66 | 67 | {children} 68 | 69 | 70 | 78 | 79 | 80 | 81 | 82 | ); 83 | } 84 | 85 | export default function Demos() { 86 | return ( 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /demo/src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import logo from "../logo.svg"; 2 | 3 | const Header: React.FC = () => { 4 | return ( 5 | 10 | 11 |
View on Github ⟶
12 |
13 | ); 14 | }; 15 | 16 | export default Header; 17 | -------------------------------------------------------------------------------- /demo/src/components/Points.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, ReactNode, useEffect, useState } from "react"; 2 | 3 | import { 4 | BufferAttribute, 5 | BufferGeometry, 6 | Points, 7 | StreamDrawUsage, 8 | } from "three"; 9 | 10 | const PointsImpl = forwardRef< 11 | Points, 12 | { points: Float32Array; stride: number; children: ReactNode } 13 | >(function Points(props, passedRef) { 14 | const { children, points, stride = 3, ...rest } = props; 15 | 16 | const [geometry] = useState(() => { 17 | const geometry = new BufferGeometry(); 18 | 19 | const attr = new BufferAttribute(points, stride); 20 | attr.usage = StreamDrawUsage; 21 | 22 | geometry.setAttribute("position", attr); 23 | 24 | return geometry; 25 | }); 26 | 27 | useEffect(() => { 28 | const attr = new BufferAttribute(points, stride); 29 | attr.usage = StreamDrawUsage; 30 | 31 | geometry.setAttribute("position", attr); 32 | }, [points]); 33 | 34 | return ( 35 | 36 | 37 | {children} 38 | 39 | ); 40 | }); 41 | 42 | export default PointsImpl; 43 | -------------------------------------------------------------------------------- /demo/src/components/Sandbox.tsx: -------------------------------------------------------------------------------- 1 | import { OrbitControls } from "@react-three/drei"; 2 | import { Canvas } from "@react-three/fiber"; 3 | 4 | function App({ children }: { children: any }) { 5 | return ( 6 | 11 | 12 | 13 | {children} 14 | 15 | 16 | 17 | ); 18 | } 19 | 20 | export default App; 21 | -------------------------------------------------------------------------------- /demo/src/components/SandboxOverlay.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from "../store"; 2 | 3 | function SandboxOverlay() { 4 | const active = useStore((state) => state.active); 5 | const setActive = useStore((state) => state.setActive); 6 | 7 | return ( 8 | <> 9 | {active && ( 10 | 29 | )} 30 |
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 | 2 | 3 | 4 | 5 | 6 | 7 | 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 |