From fa3067a16f8ba06ee756eb2b2293be4cb728681d Mon Sep 17 00:00:00 2001 From: brian cully Date: Fri, 26 Dec 2025 12:04:14 -0500 Subject: add bouncing particles --- .dir-locals.el | 10 ++++ .envrc | 1 + .gitignore | 2 + Cargo.lock | 166 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 18 ++++++ LICENSE | 5 ++ Makefile | 13 +++++ shell.nix | 31 +++++++++++ site/favicon.ico | Bin 0 -> 1258 bytes site/index.html | 25 +++++++++ site/main.css | 11 ++++ site/main.mjs | 140 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 11 ++++ 13 files changed, 433 insertions(+) create mode 100644 .dir-locals.el create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 shell.nix create mode 100644 site/favicon.ico create mode 100644 site/index.html create mode 100644 site/main.css create mode 100644 site/main.mjs create mode 100644 src/lib.rs diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..058923b --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,10 @@ +;;; Directory Local Variables -*- no-byte-compile: t -*- +;;; For more information see (info "(emacs) Directory Variables") + +((nil . ((compile-command . "make serve") + ;; https://rust-analyzer.github.io/book/configuration.html + (eglot-workspace-configuration + . (:rust-analyzer ( :typing (:triggerChars ".=()<>")))))) + (js-base-mode . ((js-indent-level . 4) + (js-chain-indent . t) + (js-indent-first-init . dynamic)))) diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..1d953f4 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce39741 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/site/wasm diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1b38286 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,166 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "console_log" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f" +dependencies = [ + "log", + "web-sys", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "polyring" +version = "0.1.0" +dependencies = [ + "console_log", + "log", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c17179b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "polyring" +version = "0.1.0" +edition = "2024" +license-file = "LICENSE" + +[lib] +# cdylib for wasm, rlib for integration tests +crate-type = ["cdylib", "rlib"] + +[dependencies] +console_log = "1.0" +log = "0.4" +wasm-bindgen = "0.2" + +[dependencies.web-sys] +features = ["Document", "Element", "HtmlElement", "Node", "Window"] +version = "0.3" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..944f969 --- /dev/null +++ b/LICENSE @@ -0,0 +1,5 @@ +this work is granted to the public domain wherever possible. where it +is not possible, this work is licensed under the terms of the GNU +Affero General Public License version 3¹ + +¹ https://www.gnu.org/licenses/agpl-3.0.html diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5d17ff0 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +BUILDDIR=site +HOST=coleridge:public_html/automathon + +# the --target is currently necessary or firefox will return +# "disallowed mime type" -bjc 7-aug-2025 +build: + wasm-pack build --target web --out-dir $(BUILDDIR)/wasm + +serve: build + (cd $(BUILDDIR) && python -m http.server 8118) + +deploy: build + rsync -avz --delete --exclude='*~' "$(BUILDDIR)"/ "$(HOST)" diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..3d960fa --- /dev/null +++ b/shell.nix @@ -0,0 +1,31 @@ +{ pkgs ? import {} }: + +pkgs.mkShellNoCC { + packages = with pkgs; [ + rustc + clang # yes, it's necessary or ‘cc’ can't be found. -bjc 2025-aug-7 + lld # ibid. + cargo + wasm-pack + rust-analyzer + clippy + rustfmt + # the only thing better than needing cargo's infinite dependencies + # is needing npm's as well, just so we can use a bundler built for + # another, wildly different, registry. + nodePackages.npm + + typescript-language-server + vscode-langservers-extracted + + # for http.server + python3 + + # testing scheme wasm stuff + guile + guile-hoot + ]; + + TMPDIR = "/tmp"; # javascript lsp needs it + CARGO_HOME = "/data/bjc/cargo"; +} diff --git a/site/favicon.ico b/site/favicon.ico new file mode 100644 index 0000000..73de524 Binary files /dev/null and b/site/favicon.ico differ diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..012e301 --- /dev/null +++ b/site/index.html @@ -0,0 +1,25 @@ + + + + + polyring + + + + + +

polyring

+

benchmarking maximal convex polygon finding

+ + + fps: n/a +
+ + + + + + + diff --git a/site/main.css b/site/main.css new file mode 100644 index 0000000..f875232 --- /dev/null +++ b/site/main.css @@ -0,0 +1,11 @@ +html { + box-sizing: border-box; +} +*, *::before, *::after { + box-sizing: inherit; +} + +canvas { + border: 1px solid orangered; + background-color: rgb(200 200 200); +} diff --git a/site/main.mjs b/site/main.mjs new file mode 100644 index 0000000..0c81b41 --- /dev/null +++ b/site/main.mjs @@ -0,0 +1,140 @@ +const NUM_POINTS = 5; + +// thanks vihart +const TAU = 2 * Math.PI; + +// no more than 1% of world size per tick. +const MAX_SPEED = 0.01; + +// size of rendered points in proportion to canvas. +const POINT_RADIUS = 0.01; + +class Point { + x = Math.random(); + y = Math.random(); + #heading = Math.random() * TAU; + speed = Math.random() * MAX_SPEED; + color = randColor(); + + #speedX; + get speedX() { + if (this.#speedX === undefined) { + this.#speedX = this.speed * Math.cos(this.heading); + } + return this.#speedX; + } + + #speedY; + get speedY() { + if (this.#speedY === undefined) { + this.#speedY = this.speed * Math.sin(this.heading); + } + return this.#speedY; + } + + get heading() { + return this.#heading; + } + + set heading(val) { + this.#heading = val; + this.#speedX = this.#speedY = undefined; + } +} + +const points = [...Array(NUM_POINTS)].map(_ => new Point()); + +function randColor() { + const [r, g, b] = [...Array(3)].map(_ => Math.random() * 255); + return `rgb(${r} ${g} ${b})` +} + +function renderPoints(ctx, points) { + points.forEach(p => { + ctx.fillStyle = p.color; + ctx.beginPath(); + ctx.arc(p.x, p.y, POINT_RADIUS, 0, TAU); + ctx.fill(); + + ctx.lineWidth = 0.005; + ctx.beginPath(); + ctx.moveTo(p.x, p.y); + ctx.lineTo(p.x + p.speedX * 3, p.y + p.speedY * 3); + ctx.stroke(); + }); +} + +function movePoints(points) { + points.forEach(p => { + p.x += p.speedX; + p.y += p.speedY; + }); +} + +function bouncePoints(points) { + let didBounce = false; + points.forEach(p => { + const x = p.x + p.speedX; + const y = p.y + p.speedY; + + if (x < 0) { + p.heading = Math.PI - p.heading; + didBounce = true; + } else if (x >= 1) { + p.heading = Math.PI - p.heading; + didBounce = true; + } else if (y < 0) { + p.heading = -1 * p.heading; + didBounce = true; + } else if (y >= 1) { + p.heading = -1 * p.heading; + didBounce = true; + } + }); + return didBounce; +} + +async function loaded() { + const canvas = document.querySelector('canvas'); + const ctx = canvas.getContext('2d'); + console.debug('canvas:', canvas, 'ctx', ctx, 'points:', points); + ctx.scale(canvas.width, canvas.height); + + const fps = document.querySelector('#fps'); + + const goButton = document.querySelector('button'); + let paused = true; + goButton.onclick = e => { + paused = !paused; + if (paused) { + e.target.textContent = 'go'; + } else { + e.target.textContent = 'pause'; + self.requestAnimationFrame(render); + } + }; + + let lastTime = document.timeline.currentTime; + function render(t) { + if (t > lastTime) { + fps.textContent = Math.floor(1_000 / (t - lastTime)); + lastTime = t; + } + + ctx.clearRect(0, 0, canvas.width, canvas.height); + renderPoints(ctx, points); + if (bouncePoints(points)) { + //goButton.onclick({ target: goButton }); + } + movePoints(points); + + if (!paused) { + self.requestAnimationFrame(render); + } + } + + //goButton.onclick({ target: goButton }); + render(document.timeline.currentTime); +} + +document.addEventListener('DOMContentLoaded', loaded); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..66bafe7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ +use log::{Level, error, info}; +use wasm_bindgen::prelude::*; +use web_sys::js_sys; + +#[wasm_bindgen(start)] +pub fn init() -> Result<(), JsValue> { + console_log::init_with_level(Level::Debug).expect("couldn't init console log"); + info!("wasm init"); + + Ok(()) +} -- cgit v1.3