Compare commits
2 commits
4fcd317390
...
e254edabf7
Author | SHA1 | Date | |
---|---|---|---|
e254edabf7 | |||
8702cddf44 |
21 changed files with 1804 additions and 1116 deletions
1685
Cargo.lock
generated
1685
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -2,7 +2,6 @@
|
|||
|
||||
members = [
|
||||
"libcxgraph",
|
||||
"cxgraph-desktop",
|
||||
"cxgraph-web",
|
||||
]
|
||||
|
||||
resolver = "2"
|
||||
|
|
|
@ -3,6 +3,15 @@
|
|||
cxgraph is a complex function graphing tool built around WebGPU available
|
||||
[on the web](https://cx.trimill.xyz/) or (slightly) for desktop.
|
||||
|
||||
## building (web)
|
||||
|
||||
install `wasm-pack` through your package manager or with `cargo install wasm-pack`.
|
||||
|
||||
```sh
|
||||
cd cxgraph-web
|
||||
wasm-pack build --no-typescript --no-pack --target web
|
||||
```
|
||||
|
||||
## documentation
|
||||
- [language](docs/language.md)
|
||||
- [web interface](docs/web.md)
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
[package]
|
||||
name = "cxgraph-desktop"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
libcxgraph = { path = "../libcxgraph" }
|
||||
log = "0.4"
|
||||
env_logger = "0.10"
|
||||
winit = "0.28"
|
||||
pollster = "0.3"
|
|
@ -1,47 +0,0 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use libcxgraph::{renderer::WgpuState, language::compile};
|
||||
use winit::{event_loop::EventLoop, window::Window, event::{Event, WindowEvent}};
|
||||
|
||||
fn main() {
|
||||
env_logger::builder()
|
||||
.filter_level(log::LevelFilter::Warn)
|
||||
.init();
|
||||
|
||||
let src = "plot(z) = 27^z - 9^z - 3^z";
|
||||
let wgsl = compile(src, &HashMap::new()).unwrap();
|
||||
println!("{wgsl}");
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let window = Window::new(&event_loop).unwrap();
|
||||
window.set_title("window");
|
||||
|
||||
pollster::block_on(run(event_loop, window, &wgsl));
|
||||
}
|
||||
|
||||
async fn run(event_loop: EventLoop<()>, window: Window, code: &str) {
|
||||
let size = window.inner_size();
|
||||
let mut state = WgpuState::new(&window, size.into()).await;
|
||||
|
||||
state.load_shaders(code);
|
||||
|
||||
state.uniforms.bounds_min = (-5.0, -5.0).into();
|
||||
state.uniforms.bounds_max = ( 5.0, 5.0).into();
|
||||
state.uniforms.shading_intensity = 0.3;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
match event {
|
||||
Event::WindowEvent { event: WindowEvent::CloseRequested, .. }
|
||||
=> control_flow.set_exit(),
|
||||
Event::RedrawRequested(_)
|
||||
=> state.redraw(),
|
||||
Event::WindowEvent { event: WindowEvent::Resized(size), .. } => {
|
||||
state.resize(size.into());
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
|
@ -9,7 +9,7 @@ crate-type = ["cdylib", "rlib"]
|
|||
[dependencies]
|
||||
libcxgraph = { path = "../libcxgraph", features = ["webgl"] }
|
||||
log = "0.4"
|
||||
winit = "0.28"
|
||||
winit = "0.29"
|
||||
console_error_panic_hook = "0.1"
|
||||
console_log = "1.0"
|
||||
wasm-bindgen = "0.2"
|
||||
|
|
161
cxgraph-web/editor.js
Normal file
161
cxgraph-web/editor.js
Normal file
|
@ -0,0 +1,161 @@
|
|||
//
|
||||
// Keyboard handling
|
||||
//
|
||||
|
||||
const TAB_WIDTH = 4;
|
||||
|
||||
let sourceFocused = false;
|
||||
|
||||
source_text.addEventListener("mousedown", () => { sourceFocused = true; });
|
||||
source_text.addEventListener("focusout", () => { sourceFocused = false; });
|
||||
|
||||
source_text.addEventListener("keydown", (event) => {
|
||||
console.log(event);
|
||||
if (event.key != "Tab") {
|
||||
sourceFocused = true;
|
||||
}
|
||||
|
||||
if (event.key == "Enter" && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
button_graph.click();
|
||||
} else if (event.key == "Escape") {
|
||||
sourceFocused = false;
|
||||
} else if (event.key == "Backspace" && !event.ctrlKey) {
|
||||
let selStart = source_text.selectionStart;
|
||||
let selEnd = source_text.selectionEnd;
|
||||
|
||||
if (selStart == selEnd) {
|
||||
let pre = source_text.value.slice(0, selStart);
|
||||
let lineStart = pre.lastIndexOf("\n") + 1;
|
||||
let preLine = pre.slice(lineStart);
|
||||
|
||||
if (preLine.length > 2 && preLine.trim() == "") {
|
||||
let count = (selStart - lineStart - 1)%TAB_WIDTH + 1;
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (pre[pre.length - i - 1] != " ") {
|
||||
count = i;
|
||||
}
|
||||
}
|
||||
|
||||
let post = source_text.value.slice(selStart);
|
||||
source_text.value = pre.slice(0, selStart - count) + post;
|
||||
source_text.selectionStart = selStart - count;
|
||||
source_text.selectionEnd = selEnd - count;
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
} else if (event.key == "Tab" && sourceFocused) {
|
||||
event.preventDefault();
|
||||
|
||||
let selStart = source_text.selectionStart;
|
||||
let selEnd = source_text.selectionEnd;
|
||||
let pre = source_text.value.slice(0, selStart);
|
||||
let post = source_text.value.slice(selStart);
|
||||
let lineStart = pre.lastIndexOf("\n") + 1;
|
||||
|
||||
if (event.shiftKey) {
|
||||
let count = (selStart - lineStart - 1)%TAB_WIDTH + 1;
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (pre[pre.length - i - 1] != " ") {
|
||||
count = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
source_text.value = pre.slice(0, selStart - count) + post;
|
||||
source_text.selectionStart = selStart - count;
|
||||
source_text.selectionEnd = selEnd - count;
|
||||
}
|
||||
} else {
|
||||
let count = TAB_WIDTH - (selStart - lineStart)%TAB_WIDTH;
|
||||
source_text.value = pre + " ".repeat(count) + post;
|
||||
source_text.selectionStart = selStart + count;
|
||||
source_text.selectionEnd = selEnd + count;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Special characters
|
||||
//
|
||||
|
||||
export let charMap = {
|
||||
"alpha": "\u03b1",
|
||||
"beta": "\u03b2",
|
||||
"gamma": "\u03b3",
|
||||
"delta": "\u03b4",
|
||||
"epsilon": "\u03b5",
|
||||
"zeta": "\u03b6",
|
||||
"eta": "\u03b7",
|
||||
"theta": "\u03b8",
|
||||
"iota": "\u03b9",
|
||||
"kappa": "\u03ba",
|
||||
"lambda": "\u03bb",
|
||||
"mu": "\u03bc",
|
||||
"nu": "\u03bd",
|
||||
"xi": "\u03be",
|
||||
"omicron": "\u03bf",
|
||||
"pi": "\u03c0",
|
||||
"rho": "\u03c1",
|
||||
"fsigma": "\u03c2",
|
||||
"sigma": "\u03c3",
|
||||
"tau": "\u03c4",
|
||||
"upsilon": "\u03c5",
|
||||
"phi": "\u03c6",
|
||||
"chi": "\u03c7",
|
||||
"psi": "\u03c8",
|
||||
"omega": "\u03c9",
|
||||
"Alpha": "\u0391",
|
||||
"Beta": "\u0392",
|
||||
"Gamma": "\u0393",
|
||||
"Delta": "\u0394",
|
||||
"Epsilon": "\u0395",
|
||||
"Zeta": "\u0396",
|
||||
"Eta": "\u0397",
|
||||
"Theta": "\u0398",
|
||||
"Iota": "\u0399",
|
||||
"Kappa": "\u039a",
|
||||
"Lambda": "\u039b",
|
||||
"Mu": "\u039c",
|
||||
"Nu": "\u039d",
|
||||
"Xi": "\u039e",
|
||||
"Omicron": "\u039f",
|
||||
"Pi": "\u03a0",
|
||||
"Rho": "\u03a1",
|
||||
"Sigma": "\u03a3",
|
||||
"Tau": "\u03a4",
|
||||
"Upsilon": "\u03a5",
|
||||
"Phi": "\u03a6",
|
||||
"Chi": "\u03a7",
|
||||
"Psi": "\u03a8",
|
||||
"Omega": "\u03a9",
|
||||
"vartheta": "\u03d1",
|
||||
"0": "\u2080",
|
||||
"1": "\u2081",
|
||||
"2": "\u2082",
|
||||
"3": "\u2083",
|
||||
"4": "\u2084",
|
||||
"5": "\u2085",
|
||||
"6": "\u2086",
|
||||
"7": "\u2087",
|
||||
"8": "\u2088",
|
||||
"9": "\u2089",
|
||||
};
|
||||
let specialChars = new RegExp(
|
||||
`\\\\(${Object.keys(charMap).join("|")})`
|
||||
);
|
||||
|
||||
source_text.addEventListener("input", (event) => {
|
||||
if(event.isComposing) return;
|
||||
let e = source_text.selectionEnd;
|
||||
let amnt = 0;
|
||||
source_text.value = source_text.value.replace(
|
||||
specialChars,
|
||||
(m, p) => {
|
||||
amnt += m.length - charMap[p].length;
|
||||
return charMap[p];
|
||||
}
|
||||
);
|
||||
source_text.selectionEnd = e - amnt;
|
||||
});
|
||||
|
|
@ -1,7 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>cxgraph</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="author" content="trimill">
|
||||
<meta name="description" content="Plot complex functions">
|
||||
<meta property="og:description" content="Plot complex functions">
|
||||
<meta property="og:title" content="CXGraph">
|
||||
<meta property="og:site_name" content="cx.trimill.xyz">
|
||||
|
||||
<title>CXGraph</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
@ -12,8 +21,8 @@
|
|||
<line id="svg_axis_y" x1="0" y1="0" x2="0" y2="0" stroke="#0006" stroke-width="1.5" visibility="hidden" />
|
||||
<circle id="svg_unitcircle" cx="0" cy="0" r="0" stroke="#0006" fill="none" stroke-width="1.5" visibility="hidden" />
|
||||
<g id="svg_point_template" visibility="hidden">
|
||||
<circle cx="0" cy="0" r="15" stroke="none" fill="#6664" />
|
||||
<circle cx="0" cy="0" r="5" stroke="none" fill="#6666" />
|
||||
<circle cx="0" cy="0" r="15" stroke="none" fill="#3337" />
|
||||
<circle cx="0" cy="0" r="5" stroke="none" fill="#dddc" />
|
||||
</g>
|
||||
<g id="overlay_points">
|
||||
</g>
|
||||
|
@ -32,7 +41,7 @@
|
|||
</div>
|
||||
|
||||
<div id="div_error_msg" hidden></div>
|
||||
<div><textarea id="source_text">f(z) = z^2 + 3i plot(z) = 5z^2 + f(1/z) - 1</textarea></div>
|
||||
<div><textarea id="source_text">f(z) = 6z^2 - 2i - 1 plot(z) = f(1 + sin(z)) / 8</textarea></div>
|
||||
</details>
|
||||
|
||||
<div>
|
||||
|
@ -52,11 +61,6 @@
|
|||
<input type="range" id="range_shading" min="0" max="1" step="0.01" value="0.3">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div><label for="range_contour">Contour intensity</label></div>
|
||||
<input type="range" id="range_contour" min="0" max="1" step="0.01" value="0.0">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<input type="checkbox" class="decor" id="checkbox_decor_1" data-value="1">
|
||||
|
@ -77,11 +81,16 @@
|
|||
<input type="checkbox" class="decor" id="checkbox_decor_8" data-value="8">
|
||||
<label for="checkbox_decor_8">Magnitude contours</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div><label for="range_contour">Contour intensity</label></div>
|
||||
<input type="range" id="range_contour" min="0" max="1" step="0.01" value="0.0">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>Coloring</div>
|
||||
<input type="radio" name="color_mode" id="radio_color_0" data-value="0" checked>
|
||||
<input type="radio" name="color_mode" id="radio_color_0" data-value="0">
|
||||
<label for="radio_color_0">Standard</label><br>
|
||||
<input type="radio" name="color_mode" id="radio_color_1" data-value="1">
|
||||
<label for="radio_color_1">Uniform</label><br>
|
||||
|
@ -106,7 +115,7 @@
|
|||
<summary>Variables</summary>
|
||||
<div id="slider_template" hidden>
|
||||
<div>
|
||||
<input type="text" class="var-name" style="width: 5ch;"">
|
||||
<input type="text" class="var-name" style="width: 5ch;" placeholder="a">
|
||||
=
|
||||
<input type="number" class="var-value" style="width: 10ch;" value="0" required>
|
||||
<input type="button" class="var-delete" value="X">
|
||||
|
@ -122,7 +131,7 @@
|
|||
<hr>
|
||||
</div>
|
||||
<div id="point_template" hidden>
|
||||
<input type="text" class="var-name" style="width: 5ch;">
|
||||
<input type="text" class="var-name" style="width: 5ch;" placeholder="b">
|
||||
=
|
||||
<input type="number" class="var-value-re" style="width: 8ch;" required value="0">
|
||||
+
|
||||
|
@ -140,7 +149,9 @@
|
|||
</div>
|
||||
|
||||
<script>
|
||||
import('./index.js').then(m => window.module = m)
|
||||
window.modules = {};
|
||||
import('./index.js').then(m => window.modules.index = m);
|
||||
import('./editor.js').then(m => window.modules.editor = m);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -4,15 +4,14 @@ import init, * as cxgraph from "./pkg/cxgraph_web.js";
|
|||
await init();
|
||||
|
||||
let graphView = {
|
||||
xoff: 0,
|
||||
yoff: 0,
|
||||
scale: 3,
|
||||
xoff: 0.00001,
|
||||
yoff: 0.00001,
|
||||
scale: 2.99932736,
|
||||
res_mult: 1,
|
||||
varNames: [],
|
||||
};
|
||||
|
||||
let graphPoints = [];
|
||||
let graphSliders = [];
|
||||
|
||||
let mouseX = 0.0;
|
||||
let mouseY = 0.0;
|
||||
|
@ -111,20 +110,20 @@ function onWheel(e) {
|
|||
onViewChange();
|
||||
}
|
||||
|
||||
function onMouseDown(e) {
|
||||
function onPointerDown(e) {
|
||||
mousePressed = true;
|
||||
mouseX = e.offsetX;
|
||||
mouseY = e.offsetY;
|
||||
}
|
||||
|
||||
function onMouseUp(e) {
|
||||
function onPointerUp() {
|
||||
mousePressed = false;
|
||||
for(let point of graphPoints) {
|
||||
point.mousePressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseMove(e) {
|
||||
function onPointerMove(e) {
|
||||
if(mousePressed) {
|
||||
let dX = e.offsetX - mouseX;
|
||||
let dY = e.offsetY - mouseY;
|
||||
|
@ -135,16 +134,16 @@ function onMouseMove(e) {
|
|||
onViewChange();
|
||||
} else {
|
||||
for(let point of graphPoints) {
|
||||
point.onMouseMove(e);
|
||||
point.onPointerMove(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("resize", onResize);
|
||||
canvas.addEventListener("wheel", onWheel);
|
||||
canvas.addEventListener("mousedown", onMouseDown);
|
||||
canvas.addEventListener("mouseup", onMouseUp);
|
||||
canvas.addEventListener("mousemove", onMouseMove);
|
||||
canvas.addEventListener("pointerdown", onPointerDown);
|
||||
canvas.addEventListener("pointerup", onPointerUp);
|
||||
canvas.addEventListener("pointermove", onPointerMove);
|
||||
|
||||
//
|
||||
// Graph/redraw
|
||||
|
@ -166,76 +165,6 @@ function onGraph() {
|
|||
button_graph.addEventListener("click", onGraph);
|
||||
button_redraw.addEventListener("click", redraw);
|
||||
|
||||
let charMap = {
|
||||
"alpha": "\u03b1",
|
||||
"beta": "\u03b2",
|
||||
"gamma": "\u03b3",
|
||||
"delta": "\u03b4",
|
||||
"epsilon": "\u03b5",
|
||||
"zeta": "\u03b6",
|
||||
"eta": "\u03b7",
|
||||
"theta": "\u03b8",
|
||||
"iota": "\u03b9",
|
||||
"kappa": "\u03ba",
|
||||
"lambda": "\u03bb",
|
||||
"mu": "\u03bc",
|
||||
"nu": "\u03bd",
|
||||
"xi": "\u03be",
|
||||
"omicron": "\u03bf",
|
||||
"pi": "\u03c0",
|
||||
"rho": "\u03c1",
|
||||
"fsigma": "\u03c2",
|
||||
"sigma": "\u03c3",
|
||||
"tau": "\u03c4",
|
||||
"upsilon": "\u03c5",
|
||||
"phi": "\u03c6",
|
||||
"chi": "\u03c7",
|
||||
"psi": "\u03c8",
|
||||
"omega": "\u03c9",
|
||||
"Alpha": "\u0391",
|
||||
"Beta": "\u0392",
|
||||
"Gamma": "\u0393",
|
||||
"Delta": "\u0394",
|
||||
"Epsilon": "\u0395",
|
||||
"Zeta": "\u0396",
|
||||
"Eta": "\u0397",
|
||||
"Theta": "\u0398",
|
||||
"Iota": "\u0399",
|
||||
"Kappa": "\u039a",
|
||||
"Lambda": "\u039b",
|
||||
"Mu": "\u039c",
|
||||
"Nu": "\u039d",
|
||||
"Xi": "\u039e",
|
||||
"Omicron": "\u039f",
|
||||
"Pi": "\u03a0",
|
||||
"Rho": "\u03a1",
|
||||
"Sigma": "\u03a3",
|
||||
"Tau": "\u03a4",
|
||||
"Upsilon": "\u03a5",
|
||||
"Phi": "\u03a6",
|
||||
"Chi": "\u03a7",
|
||||
"Psi": "\u03a8",
|
||||
"Omega": "\u03a9",
|
||||
"vartheta": "\u03d1",
|
||||
};
|
||||
let specialChars = new RegExp(
|
||||
`\\\\(${Object.keys(charMap).join("|")})`
|
||||
);
|
||||
console.log(specialChars);
|
||||
|
||||
source_text.addEventListener("input", () => {
|
||||
let e = source_text.selectionEnd;
|
||||
let amnt = 0;
|
||||
source_text.value = source_text.value.replace(
|
||||
specialChars,
|
||||
(m, p) => {
|
||||
amnt += m.length - charMap[p].length;
|
||||
return charMap[p];
|
||||
}
|
||||
);
|
||||
source_text.selectionEnd = e - amnt;
|
||||
});
|
||||
|
||||
//
|
||||
// Options
|
||||
//
|
||||
|
@ -288,7 +217,8 @@ for(let e of nameColorMode) {
|
|||
});
|
||||
e.checked = false;
|
||||
}
|
||||
nameColorMode[0].checked = true;
|
||||
nameColorMode[1].checked = true;
|
||||
cxgraph.set_coloring(1);
|
||||
|
||||
overlay_axes.addEventListener("change", () => {
|
||||
let vis = overlay_axes.checked ? "visible" : "hidden";
|
||||
|
@ -419,24 +349,24 @@ class Point {
|
|||
tryRedraw();
|
||||
});
|
||||
|
||||
svgPoint.addEventListener("mousedown", (e) => {
|
||||
svgPoint.addEventListener("pointerdown", (e) => {
|
||||
this.mousePressed = true;
|
||||
mouseX = e.offsetX;
|
||||
mouseY = e.offsetY;
|
||||
});
|
||||
|
||||
svgPoint.addEventListener("mouseup", () => {
|
||||
svgPoint.addEventListener("pointerup", () => {
|
||||
this.mousePressed = false;
|
||||
mousePressed = false;
|
||||
});
|
||||
|
||||
svgPoint.addEventListener("mousemove", (e) => this.onMouseMove(e));
|
||||
svgPoint.addEventListener("pointermove", (e) => this.onPointerMove(e));
|
||||
|
||||
this.onViewChange();
|
||||
genVarNames();
|
||||
}
|
||||
|
||||
onMouseMove(e) {
|
||||
onPointerMove(e) {
|
||||
if(this.mousePressed) {
|
||||
mouseX = e.offsetX;
|
||||
mouseY = e.offsetY;
|
||||
|
@ -486,3 +416,5 @@ onGraph();
|
|||
export function show_ast() {
|
||||
console.info(cxgraph.show_shader_ast(source_text.value));
|
||||
}
|
||||
|
||||
export function get_cxgraph() { return cxgraph; }
|
||||
|
|
|
@ -20,7 +20,6 @@ where F: Fn(&mut WgpuState) {
|
|||
#[wasm_bindgen(start)]
|
||||
pub async fn start() {
|
||||
use winit::dpi::PhysicalSize;
|
||||
use winit::platform::web::WindowExtWebSys;
|
||||
|
||||
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
console_log::init_with_level(log::Level::Info).expect("Couldn't initialize logger");
|
||||
|
@ -34,16 +33,18 @@ pub async fn start() {
|
|||
.dyn_into()
|
||||
.expect("Canvas was not a canvas");
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let window = WindowBuilder::new()
|
||||
.with_canvas(Some(canvas))
|
||||
.with_prevent_default(false)
|
||||
.with_inner_size(PhysicalSize::new(100, 100))
|
||||
.with_title("window")
|
||||
.build(&event_loop)
|
||||
.expect("Failed to build window");
|
||||
|
||||
let size = window.inner_size();
|
||||
let mut state = WgpuState::new(&window, size.into()).await;
|
||||
let window_ref = Box::leak(Box::new(window));
|
||||
|
||||
let mut state = WgpuState::new(window_ref, (100, 100)).await;
|
||||
state.uniforms.bounds_min = (-5.0, -5.0).into();
|
||||
state.uniforms.bounds_max = ( 5.0, 5.0).into();
|
||||
state.uniforms.shading_intensity = 0.3;
|
||||
|
|
|
@ -26,6 +26,7 @@ canvas, #overlay {
|
|||
|
||||
#canvas {
|
||||
z-index: 0;
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
#overlay {
|
||||
|
|
206
docs/language.md
206
docs/language.md
|
@ -1,47 +1,123 @@
|
|||
# cxgraph language
|
||||
# CXGraph language
|
||||
|
||||
cxgraph uses a custom expression language that is compiled to WGSL.
|
||||
CXGraph uses a custom expression language that is compiled to WGSL.
|
||||
|
||||
## names
|
||||
## Names
|
||||
|
||||
names must begin with any alphabetic character (lowercase or capital letters, Greek letters,
|
||||
etc.) and may contain alphanumeric chararcters as well as underscores (`_`) and apostrophes (`'`).
|
||||
the words `sum`, `prod`, and `iter` may not be used for names. names may refer to either
|
||||
functions or variables.
|
||||
Names must begin with any alphabetic character (lowercase or capital letters,
|
||||
Greek letters, etc.) and may contain alphanumeric chararcters as well as
|
||||
underscores (`_`) and apostrophes (`'`). The words `sum`, `prod`, `iter`,
|
||||
and `if` may not be used for names. Names may refer to either functions
|
||||
or variables.
|
||||
|
||||
examples of names include:
|
||||
Examples of names include:
|
||||
```
|
||||
a A aaa ω z_3 __5__ f' К'םαl'けx焼__검
|
||||
```
|
||||
|
||||
names may either be **built-in**, **global**, or **local**. global or local names may shadow
|
||||
built-in names, and local names may shadow global ones.
|
||||
Names may either be **built-in**, **global**, or **local**. global or local names
|
||||
may shadow built-in names, and local names may shadow global ones.
|
||||
|
||||
## declarations
|
||||
## Declarations
|
||||
|
||||
a **function declaration** declares a new function. functions may have zero or more arguments.
|
||||
A **function declaration** declares a new function. Functions may have zero
|
||||
or more arguments.
|
||||
|
||||
```
|
||||
f(x) = 3
|
||||
```
|
||||
|
||||
a **constant declaration** declares a new constant.
|
||||
A **constant declaration** declares a new constant.
|
||||
|
||||
```
|
||||
n = 5
|
||||
```
|
||||
|
||||
declarations are separated by newlines. declarations may only reference functions and constants in
|
||||
declarations that precede them. the name used in a declaration (`f` and `n` in the above examples)
|
||||
is in the global scope
|
||||
Declarations are separated by newlines. Declarations may only reference functions
|
||||
and constants in declarations that precede them. The name used in a declaration
|
||||
(`f` and `n` in the above examples) is in the global scope.
|
||||
|
||||
## built-ins
|
||||
The `plot` function is special and serves as the entry point. It must exist and have exactly one argument.
|
||||
|
||||
## Operators
|
||||
|
||||
Below is a reference to all operators in the CXGraph language.
|
||||
|
||||
| operator | description | precedence |
|
||||
|----------|----------------------------|------------|
|
||||
| `,` | separate expressions | 0 |
|
||||
| `->` | assign to | 1 |
|
||||
| `==` | equal | 2 |
|
||||
| `!=` | not equal | 2 |
|
||||
| `>` | real part greater | 3 |
|
||||
| `<` | real part less | 3 |
|
||||
| `>=` | real part greater or equal | 3 |
|
||||
| `<=` | real part less or equal | 3 |
|
||||
| `+` | addition | 4 |
|
||||
| `-` | subtraction | 4 |
|
||||
| `*` | multiplication | 5 |
|
||||
| `/` | division | 5 |
|
||||
| `^` | power | 6 |
|
||||
|
||||
The comma `,` separates expressions in locations where multiple are allowed (ie.
|
||||
in a definition or block).
|
||||
|
||||
The arrow `->` stores the value of the expression to its left to the local
|
||||
variable on the right.
|
||||
|
||||
The equality operators `==` and `!=` compare two values, considering both their
|
||||
real and imaginary components. The comparison operators `>`, `<`, `>=`, and `<=`
|
||||
only consider the real component.
|
||||
|
||||
`+`, `-`, and `*` also function as the unary plus, minus, and conugation operators.
|
||||
|
||||
Multiplication can also be done via juxtaposition - `2x(x+1)` is equivalent to `2*x*(x+1)`.
|
||||
Juxtaposition takes precedence over other operations, so `1/2x` is `1/(2*x)`, not `1/2*x`,
|
||||
and `z^5(x+1)` is `z^(5*(x+1))`, not `z^5*(x+1)`.
|
||||
|
||||
## Grouping
|
||||
|
||||
Parentheses `( )` can be used to group within an expression. Braces `{ }` can be used to group multiple expressions, separated by commas. Newlines, which ordinarily separate declarations, are treated as whitespace between grouping symbols, allowing for multiline definitions.
|
||||
|
||||
## Repetition and conditionals
|
||||
|
||||
To allow for finite sums and products, the `sum` and `prod` expressions can be used.
|
||||
|
||||
```
|
||||
plot(z) = sum(n: 0, 20) { z^n / n }
|
||||
```
|
||||
|
||||
The first parameter to `sum` or `prod` is the name of the summation index, the next two
|
||||
values are the lower and upper bounds (inclusive). These may be arbitrary expressions,
|
||||
they are converted to integers by rounding down the real component. The value of the body
|
||||
for each index value is summed or multiplied and the result is returned.
|
||||
|
||||
`iter` works similarly:
|
||||
|
||||
```
|
||||
plot(c) = iter(20, 0 -> z) { z^2 + c }
|
||||
```
|
||||
|
||||
The first parameter is the number of iterations, the second is an assignment to initialize
|
||||
the iteration variable. For each iteration, the iteration variable will be updated to the
|
||||
new value of the body.
|
||||
|
||||
`if` can be used to choose between two expressions based on a condition:
|
||||
|
||||
```
|
||||
if(z > 0) { z^2 } { 2z }
|
||||
```
|
||||
|
||||
If the argument's real part is positive, the first body will be evaluated, and otherwise the
|
||||
second will be.
|
||||
|
||||
|
||||
## Built-in functions and constants
|
||||
|
||||
arithmetic functions:
|
||||
| name | description |
|
||||
|---------------|-------------------------------------------------------|
|
||||
| `pos(z)` | equivalent to unary `+` |
|
||||
| `pos(z)` | identity, equivalent to unary `+` |
|
||||
| `neg(z)` | equivalent to unary `-` |
|
||||
| `conj(z)` | complex conjugate, equivalent to unary `*` |
|
||||
| `re(z)` | real part |
|
||||
|
@ -58,11 +134,11 @@ arithmetic functions:
|
|||
|
||||
power/exponential functions:
|
||||
| name | description |
|
||||
|---------------|-------------------------------------------|
|
||||
| `exp(z)` | exponential function, equivalent to `e^z` |
|
||||
| `log(z)` | logarithm base `e` |
|
||||
| `logbr(z,br)` | logarithm base `e` with specified branch |
|
||||
| `pow(z)` | power, equivalent to `^` |
|
||||
|----------------|-------------------------------------------|
|
||||
| `exp(z)` | Exponential function, equivalent to `e^z` |
|
||||
| `log(z)` | Natural logarithm |
|
||||
| `logbr(z,br)` | Natural logarithm with specified branch |
|
||||
| `pow(z)` | Power, equivalent to `^` |
|
||||
| `powbr(z,br)` | `pow` with specified branch |
|
||||
| `sqrt(z)` | square root, equivalent to `z^0.5` |
|
||||
| `sqrtbr(z,br)` | square root with specified branch |
|
||||
|
@ -72,48 +148,47 @@ power/exponential functions:
|
|||
trigonometric functions:
|
||||
| name | description |
|
||||
|------------|-------------------------------------|
|
||||
| `sin(z)` | sine function |
|
||||
| `cos(z)` | cosine function |
|
||||
| `tan(z)` | tangent function |
|
||||
| `sinh(z)` | hyperbolic sine function |
|
||||
| `cosh(z)` | hyperbolic cosine function |
|
||||
| `tanh(z)` | hyperbolic tangent function |
|
||||
| `asin(z)` | inverse sine function |
|
||||
| `acos(z)` | inverse cosine function |
|
||||
| `atan(z)` | inverse tangent function |
|
||||
| `asinh(z)` | inverse hyperbolic sine function |
|
||||
| `acosh(z)` | inverse hyperbolic cosine function |
|
||||
| `atanh(z)` | inverse hyperbolic tangent function |
|
||||
| `sin(z)` | Sine function |
|
||||
| `cos(z)` | Cosine function |
|
||||
| `tan(z)` | Tangent function |
|
||||
| `sinh(z)` | Hyperbolic sine function |
|
||||
| `cosh(z)` | Hyperbolic cosine function |
|
||||
| `tanh(z)` | Hyperbolic tangent function |
|
||||
| `asin(z)` | Inverse sine function |
|
||||
| `acos(z)` | Inverse cosine function |
|
||||
| `atan(z)` | Inverse tangent function |
|
||||
| `asinh(z)` | Inverse hyperbolic sine function |
|
||||
| `acosh(z)` | Inverse hyperbolic cosine function |
|
||||
| `atanh(z)` | Inverse hyperbolic tangent function |
|
||||
|
||||
special functions:
|
||||
| function | description |
|
||||
|--------------------------|--------------------------------------------------------------------|
|
||||
|--------------------------|------------------------------------------------------------------------|
|
||||
| `gamma(z)`, `Γ(z)` | [gamma function](https://en.wikipedia.org/wiki/Gamma_function) |
|
||||
| `invgamma(z)`, `invΓ(z)` | reciprocal of the gamma function |
|
||||
| `loggamma(z)`, `logΓ(z)` | logarithm of the gamma function |
|
||||
| `digamma(z)`, `ψ(z)` | [digamma function](https://en.wikipedia.org/wiki/Digamma_function) |
|
||||
| `lambertw(z)` | [Lambert W function](https://en.wikipedia.org/wiki/Lambert_W_function) |
|
||||
| `lambertwbr(z,br)` | Lambert W function on specfied branch |
|
||||
| `erf(z)` | The [Error function](https://en.wikipedia.org/wiki/Error_function) |
|
||||
|
||||
logic functions:
|
||||
| function | description |
|
||||
|-----------------|----------------------------------------------------------------------------|
|
||||
| `signre(z)` | sign of real part (1 if `re(z) > 0`, -1 if `re(z) < 0`, 0 if `re(z) == 0`) |
|
||||
| `signim(z)` | sign of imaginary part |
|
||||
| `ifgt(p,q,z,w)` | evaluates to `z` if `re(p) > re(q)`, otherwise `w` |
|
||||
| `iflt(p,q,z,w)` | evaluates to `z` if `re(p) < re(q)`, otherwise `w` |
|
||||
| `ifge(p,q,z,w)` | evaluates to `z` if `re(p) ≥ re(q)`, otherwise `w` |
|
||||
| `ifle(p,q,z,w)` | evaluates to `z` if `re(p) ≤ re(q)`, otherwise `w` |
|
||||
| `ifeq(p,q,z,w)` | evaluates to `z` if `re(p) = re(q)`, otherwise `w` |
|
||||
| `ifne(p,q,z,w)` | evaluates to `z` if `re(p) ≠ re(q)`, otherwise `w` |
|
||||
| `ifnan(p,z,w)` | evaluates to `z` if `p` is `NaN`, otherwise `w` |
|
||||
|-------------|----------------------------------------------------------------------------|
|
||||
| `signre(z)` | Sign of real part (1 if `re(z) > 0`, -1 if `re(z) < 0`, 0 if `re(z) == 0`) |
|
||||
| `signim(z)` | Sign of imaginary part |
|
||||
| `absre(z)` | Absolute value of real part |
|
||||
| `absim(z)` | Absolute value of imaginary part |
|
||||
| `isnan(z)` | 1 if `z` is NaN, 0 otherwise |
|
||||
|
||||
constants:
|
||||
| name | description |
|
||||
|----------------|--------------------------------------------------------------------------------------------------------|
|
||||
| `i` | the imaginary constant, equal to `sqrt(-1)` |
|
||||
| `e` | the [exponential constant](https://en.wikipedia.org/wiki/E_(mathematical_constant)), equal to `exp(1)` |
|
||||
| `tau`, `τ` | the [circle constant](https://tauday.com/tau-manifesto) |
|
||||
| `emgamma`, `γ` | the [Euler-Mascheroni](https://en.wikipedia.org/wiki/Euler%27s_constant) constant, equal to `-ψ(1)` |
|
||||
| `phi`, `φ` | the [golden ratio](https://en.wikipedia.org/wiki/Golden_ratio), equal to `1/2 + sqrt(5)/2` |
|
||||
| `i` | The imaginary constant, equal to `sqrt(-1)` |
|
||||
| `e` | The [exponential constant](https://en.wikipedia.org/wiki/E_(mathematical_constant)), equal to `exp(1)` |
|
||||
| `tau`, `τ` | The [circle constant](https://tauday.com/tau-manifesto) |
|
||||
| `emgamma`, `γ` | The [Euler-Mascheroni](https://en.wikipedia.org/wiki/Euler%27s_constant) constant, equal to `-ψ(1)` |
|
||||
| `phi`, `φ` | The [golden ratio](https://en.wikipedia.org/wiki/Golden_ratio), equal to `1/2 + sqrt(5)/2` |
|
||||
|
||||
## ebnf grammar
|
||||
|
||||
|
@ -129,7 +204,17 @@ Exprs := (Expr ",")* Expr ","?
|
|||
|
||||
Expr := Store
|
||||
|
||||
Store := Store "->" NAME | Sum
|
||||
Store := Equality "->" NAME | Equality
|
||||
|
||||
Equality := Compare "==" Compare
|
||||
| Compare "!=" Compare
|
||||
| Compare
|
||||
|
||||
Compare := Sum ">" Sum
|
||||
| Sum "<" Sum
|
||||
| Sum ">=" Sum
|
||||
| Sum "<=" Sum
|
||||
| Sum
|
||||
|
||||
Sum := Sum "+" Product
|
||||
| Sum "-" Product
|
||||
|
@ -151,15 +236,16 @@ Power := FnCall "^" Unary | FnCall
|
|||
|
||||
FnCall := NAME "(" Exprs ")" | Item
|
||||
|
||||
PreJuxtapose := Number | "(" <Expr> ")"
|
||||
PreJuxtapose := NUMBER | "(" <Expr> ")"
|
||||
|
||||
Item := Number
|
||||
Block := "{" Exprs "}"
|
||||
|
||||
Item := NUMBER
|
||||
| NAME
|
||||
| "(" Expr ")"
|
||||
| "{" Exprs "}"
|
||||
| "sum" "(" NAME ":" INT "," INT ")" "{" Exprs "}"
|
||||
| "prod" "(" NAME ":" INT "," INT ")" "{" Exprs "}"
|
||||
| "iter" "(" INT "," NAME ":" Expr ")" "{" Exprs "}"
|
||||
|
||||
Number = FLOAT | INT
|
||||
| Block
|
||||
| "sum" "(" NAME ":" Expr "," Expr ")" Block
|
||||
| "prod" "(" NAME ":" Expr "," Expr ")" block
|
||||
| "iter" "(" Expr "," Expr "->" NAME ")" Block
|
||||
| "if" "(" Expr ")" Block Block
|
||||
```
|
||||
|
|
33
docs/web.md
33
docs/web.md
|
@ -0,0 +1,33 @@
|
|||
# CXGraph Web UI
|
||||
|
||||
## Source
|
||||
|
||||
Enter the program to plot into the text area. For more information, see [the language docs](language.md).
|
||||
|
||||
The Graph button compiles the program and redraws the screen. This must be pressed after changes are made to the program to see them. This can also be accomplished by pressing `Shift`+`Enter` in the text area.
|
||||
|
||||
The Redraw button redraws the screen. If Auto Redraw is enabled, the screen will be redrawn automatically after every change (eg. dragging, zooming, changing options or variables).
|
||||
|
||||
## The plot
|
||||
|
||||
Most of the screen is occupied by the plot. Click and drag with the mouse to move around, and use the scroll wheel to zoom in and out.
|
||||
|
||||
## Options
|
||||
|
||||
Reset View resets the plot's position and scale. Help opens the documentation.
|
||||
|
||||
The Resolution slider controls the canvas's resolution scale on a range from x0.25 to x4. This is set to x1 by default. Higher values provide better visuals at the expense of performance.
|
||||
|
||||
Shading Intensity controls how intense the black and white shading near zero/infinity is. Setting this to zero disables shading.
|
||||
|
||||
Contours can be toggled with the contour checkboxes. Real and imaginary contours show the integer grid, argument contours show angles around the origin divided into 16 segments, and magnitude contours show magnitudes delineated by powers of two.
|
||||
|
||||
Standard coloring directly maps argument to hue in HSV while keeping saturation and value constant. Uniform coloring uses a modified mapping that tries to avoid variation in perceptual brightness. None disables coloring entirely.
|
||||
|
||||
Draw Axes and Unit Circle enable or disable the axes and circle overlays.
|
||||
|
||||
## Variables
|
||||
|
||||
Sliders can be added with the "+slider" button. Once the slider has been named, the variable name can be used in the program and the plot will redraw automatically when the slider's value is changed. The slider's start, step, and end default to 1, 0.01, and -1, respectively. The slider's value can also be edited directly.
|
||||
|
||||
Points can be added with the "+point" button. They behave similarly to sliders, but add a draggable point to the plot. The position of the point can also be edited directly.
|
|
@ -9,10 +9,11 @@ webgl = ["wgpu/webgl"]
|
|||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
lalrpop-util = { version = "0.20.0", features = ["lexer", "unicode"] }
|
||||
lalrpop-util = { version = "0.21.0", features = ["lexer", "unicode"] }
|
||||
num-complex = "0.4"
|
||||
wgpu = "0.16"
|
||||
raw-window-handle = "0.5"
|
||||
wgpu = "22.1"
|
||||
raw-window-handle = "0.6"
|
||||
unicode-xid = "0.2"
|
||||
|
||||
[build-dependencies]
|
||||
lalrpop = "0.20.0"
|
||||
lalrpop = "0.21.0"
|
||||
|
|
|
@ -5,6 +5,7 @@ use num_complex::Complex64 as Complex;
|
|||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum BinaryOp {
|
||||
Add, Sub, Mul, Div, Pow,
|
||||
Gt, Lt, Ge, Le, Eq, Ne,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
@ -21,9 +22,10 @@ pub enum ExpressionType<'a> {
|
|||
Unary(UnaryOp),
|
||||
FnCall(&'a str),
|
||||
Store(&'a str),
|
||||
Sum { countvar: &'a str, min: i32, max: i32 },
|
||||
Prod { countvar: &'a str, min: i32, max: i32 },
|
||||
Iter { itervar: &'a str, count: i32 },
|
||||
If,
|
||||
Sum { countvar: &'a str },
|
||||
Prod { countvar: &'a str },
|
||||
Iter { itervar: &'a str },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -61,25 +63,31 @@ impl<'a> Expression<'a> {
|
|||
Self { ty: ExpressionType::Store(name), children: vec![expr] }
|
||||
}
|
||||
|
||||
pub fn new_sum(countvar: &'a str, min: i32, max: i32, body: Vec<Self>) -> Self {
|
||||
pub fn new_if(cond: Self, t: Self, f: Self) -> Self {
|
||||
Self {
|
||||
ty: ExpressionType::Sum { countvar, min, max },
|
||||
children: body,
|
||||
ty: ExpressionType::If,
|
||||
children: vec![cond, t, f],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_prod(accvar: &'a str, min: i32, max: i32, body: Vec<Self>) -> Self {
|
||||
pub fn new_sum(countvar: &'a str, min: Self, max: Self, body: Self) -> Self {
|
||||
Self {
|
||||
ty: ExpressionType::Prod { countvar: accvar, min, max },
|
||||
children: body,
|
||||
ty: ExpressionType::Sum { countvar },
|
||||
children: vec![min, max, body],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_iter(itervar: &'a str, count: i32, init: Self, mut body: Vec<Self>) -> Self {
|
||||
body.push(init);
|
||||
pub fn new_prod(countvar: &'a str, min: Self, max: Self, body: Self) -> Self {
|
||||
Self {
|
||||
ty: ExpressionType::Iter { itervar, count },
|
||||
children: body,
|
||||
ty: ExpressionType::Prod { countvar },
|
||||
children: vec![min, max, body],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_iter(itervar: &'a str, count: Self, init: Self, body: Self) -> Self {
|
||||
Self {
|
||||
ty: ExpressionType::Iter { itervar },
|
||||
children: vec![count, init, body],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,9 +107,10 @@ fn display_expr(w: &mut impl fmt::Write, expr: &Expression, depth: usize) -> fmt
|
|||
ExpressionType::Unary(op) => write!(w, "{:indent$}OP {op:?}", "", indent=indent)?,
|
||||
ExpressionType::FnCall(f) => write!(w, "{:indent$}CALL {f}", "", indent=indent)?,
|
||||
ExpressionType::Store(n) => write!(w, "{:indent$}STORE {n}", "", indent=indent)?,
|
||||
ExpressionType::Sum { countvar, min, max } => write!(w, "{:indent$}SUM {countvar} {min} {max}", "", indent=indent)?,
|
||||
ExpressionType::Prod { countvar, min, max } => write!(w, "{:indent$}PROD {countvar} {min} {max}", "", indent=indent)?,
|
||||
ExpressionType::Iter { itervar, count } => write!(w, "{:indent$}ITER {itervar} {count}", "", indent=indent)?,
|
||||
ExpressionType::If => write!(w, "{:indent$}IF", "", indent=indent)?,
|
||||
ExpressionType::Sum { countvar } => write!(w, "{:indent$}SUM {countvar}", "", indent=indent)?,
|
||||
ExpressionType::Prod { countvar } => write!(w, "{:indent$}PROD {countvar}", "", indent=indent)?,
|
||||
ExpressionType::Iter { itervar } => write!(w, "{:indent$}ITER {itervar}", "", indent=indent)?,
|
||||
}
|
||||
writeln!(w)?;
|
||||
for child in &expr.children {
|
||||
|
|
|
@ -10,18 +10,13 @@ thread_local! {
|
|||
m.insert("recip", ("c_recip", 1));
|
||||
m.insert("conj", ("c_conj", 1));
|
||||
|
||||
m.insert("ifgt", ("c_ifgt", 4));
|
||||
m.insert("iflt", ("c_iflt", 4));
|
||||
m.insert("ifge", ("c_ifge", 4));
|
||||
m.insert("ifle", ("c_ifle", 4));
|
||||
m.insert("ifeq", ("c_ifeq", 4));
|
||||
m.insert("ifne", ("c_ifne", 4));
|
||||
m.insert("ifnan", ("c_ifnan", 3));
|
||||
|
||||
m.insert("re", ("c_re", 1));
|
||||
m.insert("im", ("c_im", 1));
|
||||
m.insert("signre", ("c_signre", 1));
|
||||
m.insert("signim", ("c_signim", 1));
|
||||
m.insert("absre", ("c_absre", 1));
|
||||
m.insert("absim", ("c_absim", 1));
|
||||
m.insert("isnan", ("c_isnan", 1));
|
||||
m.insert("abs_sq", ("c_abs_sq", 1));
|
||||
m.insert("abs", ("c_abs", 1));
|
||||
m.insert("arg", ("c_arg", 1));
|
||||
|
@ -48,7 +43,6 @@ thread_local! {
|
|||
m.insert("sinh", ("c_sinh", 1));
|
||||
m.insert("cosh", ("c_cosh", 1));
|
||||
m.insert("tanh", ("c_tanh", 1));
|
||||
|
||||
m.insert("asin", ("c_asin", 1));
|
||||
m.insert("acos", ("c_acos", 1));
|
||||
m.insert("atan", ("c_atan", 1));
|
||||
|
@ -64,6 +58,9 @@ thread_local! {
|
|||
m.insert("log\u{0393}", ("c_loggamma", 1));
|
||||
m.insert("digamma", ("c_digamma", 1));
|
||||
m.insert("\u{03C8}", ("c_digamma", 1));
|
||||
m.insert("lambertw", ("c_lambertw", 1));
|
||||
m.insert("lambertwbr", ("c_lambertwbr", 2));
|
||||
m.insert("erf", ("c_erf", 1));
|
||||
|
||||
m
|
||||
};
|
||||
|
|
|
@ -29,6 +29,11 @@ fn format_char(buf: &mut String, c: char) {
|
|||
match c {
|
||||
'_' => buf.push_str("u_"),
|
||||
'\'' => buf.push_str("p_"),
|
||||
'\u{2080}'..='\u{2089}' => {
|
||||
buf.push('s');
|
||||
buf.push((c as u32 - 0x2080 + '0' as u32).try_into().expect("invalid codepoint"));
|
||||
buf.push('_');
|
||||
}
|
||||
c => buf.push(c),
|
||||
}
|
||||
}
|
||||
|
@ -190,6 +195,12 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
|
|||
BinaryOp::Mul => writeln!(self.buf, "var {name} = c_mul({a}, {b});")?,
|
||||
BinaryOp::Div => writeln!(self.buf, "var {name} = c_div({a}, {b});")?,
|
||||
BinaryOp::Pow => writeln!(self.buf, "var {name} = c_pow({a}, {b});")?,
|
||||
BinaryOp::Gt => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, {a}.x > {b}.x);")?,
|
||||
BinaryOp::Lt => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, {a}.x < {b}.x);")?,
|
||||
BinaryOp::Ge => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, {a}.x >= {b}.x);")?,
|
||||
BinaryOp::Le => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, {a}.x <= {b}.x);")?,
|
||||
BinaryOp::Eq => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, ({a}.x == {b}.x) && ({a}.y == {b}.y));")?,
|
||||
BinaryOp::Ne => writeln!(self.buf, "var {name} = select(C_ZERO, C_ONE, ({a}.x != {b}.x) || ({a}.y != {b}.y));")?,
|
||||
}
|
||||
|
||||
Ok(name)
|
||||
|
@ -226,8 +237,28 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
|
|||
|
||||
Ok(name)
|
||||
},
|
||||
ExpressionType::Sum { countvar, min, max }
|
||||
| ExpressionType::Prod { countvar, min, max } => {
|
||||
ExpressionType::If => {
|
||||
let cond = self.compile_expr(local, &expr.children[0])?;
|
||||
let result = local.next_tmp();
|
||||
writeln!(self.buf, "var {result}: vec2f;")?;
|
||||
writeln!(self.buf, "if {cond}.x > 0.0 {{")?;
|
||||
let t = self.compile_expr(local, &expr.children[1])?;
|
||||
writeln!(self.buf, "{result} = {t};")?;
|
||||
writeln!(self.buf, "}} else {{")?;
|
||||
let f = self.compile_expr(local, &expr.children[2])?;
|
||||
writeln!(self.buf, "{result} = {f};")?;
|
||||
writeln!(self.buf, "}}")?;
|
||||
Ok(result)
|
||||
},
|
||||
ExpressionType::Sum { countvar }
|
||||
| ExpressionType::Prod { countvar } => {
|
||||
let min = local.next_tmp();
|
||||
let max = local.next_tmp();
|
||||
let v = self.compile_expr(local, &expr.children[0])?;
|
||||
writeln!(self.buf, "var {min} = i32(floor({v}.x));")?;
|
||||
let v = self.compile_expr(local, &expr.children[1])?;
|
||||
writeln!(self.buf, "var {max} = i32(floor({v}.x));")?;
|
||||
|
||||
let acc = local.next_tmp();
|
||||
let ivar = local.next_tmp();
|
||||
if matches!(expr.ty, ExpressionType::Sum { .. }) {
|
||||
|
@ -235,35 +266,36 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
|
|||
} else {
|
||||
writeln!(self.buf, "var {acc} = vec2f(1.0, 0.0);")?;
|
||||
}
|
||||
writeln!(self.buf, "for(var {ivar}: i32 = {min}; {ivar} <= {max}; {ivar}++) {{")?;
|
||||
writeln!(self.buf, "for(var {ivar} = {min}; {ivar} <= {max}; {ivar}++) {{")?;
|
||||
writeln!(self.buf, "var {} = vec2f(f32({ivar}), 0.0);", format_local(countvar))?;
|
||||
let mut loop_local = local.clone();
|
||||
loop_local.local_vars.insert(countvar);
|
||||
let mut last = String::new();
|
||||
for child in &expr.children {
|
||||
last = self.compile_expr(&mut loop_local, child)?;
|
||||
}
|
||||
let body = self.compile_expr(&mut loop_local, &expr.children[2])?;
|
||||
if matches!(expr.ty, ExpressionType::Sum { .. }) {
|
||||
writeln!(self.buf, "{acc} = {acc} + {last};\n}}")?;
|
||||
writeln!(self.buf, "{acc} = {acc} + {body};")?;
|
||||
} else {
|
||||
writeln!(self.buf, "{acc} = c_mul({acc}, {last});\n}}")?;
|
||||
writeln!(self.buf, "{acc} = c_mul({acc}, {body});")?;
|
||||
}
|
||||
writeln!(self.buf, "}}")?;
|
||||
Ok(acc)
|
||||
},
|
||||
ExpressionType::Iter { itervar, count } => {
|
||||
let init = expr.children.last().unwrap();
|
||||
ExpressionType::Iter { itervar } => {
|
||||
let countvar = local.next_tmp();
|
||||
let v = self.compile_expr(local, &expr.children[0])?;
|
||||
writeln!(self.buf, "var {countvar} = i32(floor({v}.x));")?;
|
||||
|
||||
let init = &expr.children[1];
|
||||
let itervar_fmt = format_local(itervar);
|
||||
let v = self.compile_expr(local, init)?;
|
||||
writeln!(self.buf, "var {itervar_fmt} = {v};")?;
|
||||
|
||||
let ivar = local.next_tmp();
|
||||
writeln!(self.buf, "for(var {ivar}: i32 = 0; {ivar} < {count}; {ivar}++) {{")?;
|
||||
writeln!(self.buf, "for(var {ivar}: i32 = 0; {ivar} < {countvar}; {ivar}++) {{")?;
|
||||
let mut loop_local = local.clone();
|
||||
loop_local.local_vars.insert(itervar);
|
||||
let mut last = String::new();
|
||||
for child in &expr.children[..expr.children.len() - 1] {
|
||||
last = self.compile_expr(&mut loop_local, child)?;
|
||||
}
|
||||
writeln!(self.buf, "{itervar_fmt} = {last};\n}}")?;
|
||||
let body = self.compile_expr(&mut loop_local, &expr.children[2])?;
|
||||
writeln!(self.buf, "{itervar_fmt} = {body};")?;
|
||||
writeln!(self.buf, "}}")?;
|
||||
Ok(itervar_fmt)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,12 +21,18 @@ extern {
|
|||
"->" => Token::Arrow,
|
||||
"=" => Token::Equal,
|
||||
":" => Token::Colon,
|
||||
">" => Token::Greater,
|
||||
"<" => Token::Less,
|
||||
">=" => Token::GreaterEqual,
|
||||
"<=" => Token::LessEqual,
|
||||
"==" => Token::EqualEqual,
|
||||
"!=" => Token::BangEqual,
|
||||
"\n" => Token::Newline,
|
||||
"sum" => Token::Sum,
|
||||
"prod" => Token::Prod,
|
||||
"iter" => Token::Iter,
|
||||
Float => Token::Float(<f64>),
|
||||
Int => Token::Int(<i32>),
|
||||
"if" => Token::If,
|
||||
Number => Token::Number(<f64>),
|
||||
Name => Token::Name(<&'input str>),
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +66,21 @@ Exprs: Vec<Expression<'input>> = {
|
|||
Expr: Expression<'input> = Store;
|
||||
|
||||
Store: Expression<'input> = {
|
||||
<a:Store> "->" <n:Name> => Expression::new_store(a, n),
|
||||
<a:Equality> "->" <n:Name> => Expression::new_store(a, n),
|
||||
Equality,
|
||||
}
|
||||
|
||||
Equality: Expression<'input> = {
|
||||
<a:Compare> "==" <b:Compare> => Expression::new_binary(BinaryOp::Eq, a, b),
|
||||
<a:Compare> "!=" <b:Compare> => Expression::new_binary(BinaryOp::Ne, a, b),
|
||||
Compare,
|
||||
}
|
||||
|
||||
Compare: Expression<'input> = {
|
||||
<a:Sum> ">" <b:Sum> => Expression::new_binary(BinaryOp::Gt, a, b),
|
||||
<a:Sum> "<" <b:Sum> => Expression::new_binary(BinaryOp::Lt, a, b),
|
||||
<a:Sum> ">=" <b:Sum> => Expression::new_binary(BinaryOp::Ge, a, b),
|
||||
<a:Sum> "<=" <b:Sum> => Expression::new_binary(BinaryOp::Le, a, b),
|
||||
Sum,
|
||||
}
|
||||
|
||||
|
@ -101,24 +121,25 @@ FnCall: Expression<'input> = {
|
|||
}
|
||||
|
||||
PreJuxtapose: Expression<'input> = {
|
||||
Number,
|
||||
<n:Number> => Expression::new_number(n),
|
||||
"(" <Expr> ")",
|
||||
}
|
||||
|
||||
Block: Expression<'input> = {
|
||||
"{" <exs:Exprs> "}" => Expression::new_block(exs),
|
||||
}
|
||||
|
||||
Item: Expression<'input> = {
|
||||
Number,
|
||||
<n:Number> => Expression::new_number(n),
|
||||
<n:Name> => Expression::new_name(n),
|
||||
"(" <Expr> ")",
|
||||
"{" <exs:Exprs> "}" => Expression::new_block(exs),
|
||||
"sum" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" "{" <exs:Exprs> "}"
|
||||
=> Expression::new_sum(name, min, max, exs),
|
||||
"prod" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" "{" <exs:Exprs> "}"
|
||||
=> Expression::new_prod(name, min, max, exs),
|
||||
"iter" "(" <count:Int> "," <name:Name> ":" <init:Expr> ")" "{" <exs:Exprs> "}"
|
||||
=> Expression::new_iter(name, count, init, exs),
|
||||
}
|
||||
|
||||
Number: Expression<'input> = {
|
||||
<n:Float> => Expression::new_number(n),
|
||||
<n:Int> => Expression::new_number(n as f64),
|
||||
Block,
|
||||
"sum" "(" <name:Name> ":" <min:Expr> "," <max:Expr> ")" <body:Block>
|
||||
=> Expression::new_sum(name, min, max, body),
|
||||
"prod" "(" <name:Name> ":" <min:Expr> "," <max:Expr> ")" <body:Block>
|
||||
=> Expression::new_prod(name, min, max, body),
|
||||
"iter" "(" <count:Expr> "," <init:Equality> "->" <name:Name> ")" <body:Block>
|
||||
=> Expression::new_iter(name, count, init, body),
|
||||
"if" "(" <cond:Expr> ")" <t:Block> <f:Block>
|
||||
=> Expression::new_if(cond, t, f),
|
||||
}
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
use std::{str::CharIndices, iter::Peekable, fmt};
|
||||
|
||||
use unicode_xid::UnicodeXID;
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Token<'i> {
|
||||
Float(f64),
|
||||
Int(i32),
|
||||
Number(f64),
|
||||
Name(&'i str),
|
||||
Sum, Prod, Iter,
|
||||
Sum, Prod, Iter, If,
|
||||
LParen, RParen,
|
||||
LBrace, RBrace,
|
||||
Plus, Minus, Star, Slash, Caret,
|
||||
Greater, Less, GreaterEqual, LessEqual,
|
||||
EqualEqual, BangEqual,
|
||||
Comma, Arrow, Equal, Colon,
|
||||
Newline,
|
||||
}
|
||||
|
@ -17,12 +20,12 @@ pub enum Token<'i> {
|
|||
impl<'i> fmt::Display for Token<'i> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Token::Float(n) => write!(f, "{n}"),
|
||||
Token::Int(n) => write!(f, "{n}"),
|
||||
Token::Number(n) => write!(f, "{n}"),
|
||||
Token::Name(n) => write!(f, "{n}"),
|
||||
Token::Sum => f.write_str("sum"),
|
||||
Token::Prod => f.write_str("prod"),
|
||||
Token::Iter => f.write_str("iter"),
|
||||
Token::If => f.write_str("if"),
|
||||
Token::LParen => f.write_str("("),
|
||||
Token::RParen => f.write_str(")"),
|
||||
Token::LBrace => f.write_str("{"),
|
||||
|
@ -36,6 +39,12 @@ impl<'i> fmt::Display for Token<'i> {
|
|||
Token::Arrow => f.write_str("->"),
|
||||
Token::Equal => f.write_str("="),
|
||||
Token::Colon => f.write_str(":"),
|
||||
Token::Greater => f.write_str(">"),
|
||||
Token::Less => f.write_str("<"),
|
||||
Token::GreaterEqual => f.write_str(">="),
|
||||
Token::LessEqual => f.write_str("<="),
|
||||
Token::EqualEqual => f.write_str("=="),
|
||||
Token::BangEqual => f.write_str("!="),
|
||||
Token::Newline => f.write_str("newline")
|
||||
}
|
||||
}
|
||||
|
@ -44,12 +53,14 @@ impl<'i> fmt::Display for Token<'i> {
|
|||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum LexerError {
|
||||
Unexpected(usize, char),
|
||||
UnexpectedEof,
|
||||
InvalidNumber(usize, usize),
|
||||
}
|
||||
|
||||
impl fmt::Display for LexerError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
LexerError::UnexpectedEof => write!(f, "Unexpected EOF during lexing"),
|
||||
LexerError::Unexpected(i, c) => write!(f, "Unexpected character {c:?} at {i}"),
|
||||
LexerError::InvalidNumber(i, j) => write!(f, "Invalid number at {i}:{j}"),
|
||||
}
|
||||
|
@ -61,15 +72,15 @@ pub type Spanned<T, L, E> = Result<(L, T, L), E>;
|
|||
pub struct Lexer<'i> {
|
||||
src: &'i str,
|
||||
chars: Peekable<CharIndices<'i>>,
|
||||
bracket_depth: usize,
|
||||
bracket_depth: isize,
|
||||
}
|
||||
|
||||
fn is_ident_begin(c: char) -> bool {
|
||||
c.is_alphabetic()
|
||||
c.is_xid_start()
|
||||
}
|
||||
|
||||
fn is_ident_middle(c: char) -> bool {
|
||||
c.is_alphanumeric() || c == '_' || c == '\''
|
||||
c.is_xid_continue() || matches!(c, '\'' | '\u{2080}'..='\u{2089}')
|
||||
}
|
||||
|
||||
impl<'i> Lexer<'i> {
|
||||
|
@ -81,7 +92,7 @@ impl<'i> Lexer<'i> {
|
|||
}
|
||||
}
|
||||
|
||||
fn next_number(&mut self, i: usize, mut has_dot: bool) -> Spanned<Token<'i>, usize, LexerError> {
|
||||
fn next_number(&mut self, i: usize, has_dot: bool) -> Spanned<Token<'i>, usize, LexerError> {
|
||||
let mut j = i;
|
||||
|
||||
while self.chars.peek().is_some_and(|(_, c)| c.is_ascii_digit()) {
|
||||
|
@ -90,20 +101,14 @@ impl<'i> Lexer<'i> {
|
|||
|
||||
if !has_dot && matches!(self.chars.peek(), Some((_, '.'))) {
|
||||
j = self.chars.next().unwrap().0;
|
||||
has_dot = true;
|
||||
while self.chars.peek().is_some_and(|(_, c)| c.is_ascii_digit()) {
|
||||
j = self.chars.next().unwrap().0;
|
||||
}
|
||||
}
|
||||
|
||||
let s = &self.src[i..j+1];
|
||||
if !has_dot {
|
||||
if let Ok(n) = s.parse::<i32>() {
|
||||
return Ok((i, Token::Int(n), j+1))
|
||||
}
|
||||
}
|
||||
match s.parse::<f64>() {
|
||||
Ok(n) => Ok((i, Token::Float(n), j+1)),
|
||||
Ok(n) => Ok((i, Token::Number(n), j+1)),
|
||||
Err(_) => Err(LexerError::InvalidNumber(i, j+1)),
|
||||
}
|
||||
}
|
||||
|
@ -118,6 +123,7 @@ impl<'i> Lexer<'i> {
|
|||
"sum" => Ok((i, Token::Sum, j)),
|
||||
"prod" => Ok((i, Token::Prod, j)),
|
||||
"iter" => Ok((i, Token::Iter, j)),
|
||||
"if" => Ok((i, Token::If, j)),
|
||||
_ => Ok((i, Token::Name(s), j)),
|
||||
}
|
||||
}
|
||||
|
@ -141,25 +147,48 @@ impl<'i> Lexer<'i> {
|
|||
}
|
||||
self.next_token()?
|
||||
}
|
||||
|
||||
(i, '\n') => Ok((i, Token::Newline, i + 1)),
|
||||
|
||||
(i, '(') => { self.bracket_depth += 1; Ok((i, Token::LParen, i + 1)) },
|
||||
(i, ')') => { self.bracket_depth -= 1; Ok((i, Token::RParen, i + 1)) },
|
||||
(i, '{') => { self.bracket_depth += 1; Ok((i, Token::LBrace, i + 1)) },
|
||||
(i, '}') => { self.bracket_depth -= 1; Ok((i, Token::RBrace, i + 1)) },
|
||||
|
||||
(i, '+') => Ok((i, Token::Plus, i + 1)),
|
||||
(i, '-') => match self.chars.peek() {
|
||||
Some((_, '>')) => {
|
||||
self.chars.next();
|
||||
Ok((i, Token::Arrow, i + 2))
|
||||
},
|
||||
(i, '-') => match self.chars.next_if(|(_, c)| *c == '>') {
|
||||
Some(_) => Ok((i, Token::Arrow, i + 2)),
|
||||
_ => Ok((i, Token::Minus, i + 1)),
|
||||
}
|
||||
},
|
||||
(i, '*') => Ok((i, Token::Star, i + 1)),
|
||||
(i, '\u{22C5}') => Ok((i, Token::Star, i + '\u{22C5}'.len_utf8())),
|
||||
(i, '/') => Ok((i, Token::Slash, i + 1)),
|
||||
(i, '^') => Ok((i, Token::Caret, i + 1)),
|
||||
|
||||
(i, '<') => match self.chars.next_if(|(_, c)| *c == '=') {
|
||||
Some(_) => Ok((i, Token::LessEqual, i + 2)),
|
||||
_ => Ok((i, Token::Less, i + 1)),
|
||||
},
|
||||
(i, '\u{2264}') => Ok((i, Token::LessEqual, i + '\u{2264}'.len_utf8())),
|
||||
(i, '>') => match self.chars.next_if(|(_, c)| *c == '=') {
|
||||
Some(_) => Ok((i, Token::GreaterEqual, i + 2)),
|
||||
_ => Ok((i, Token::Greater, i + 1)),
|
||||
},
|
||||
(i, '\u{2265}') => Ok((i, Token::GreaterEqual, i + '\u{2265}'.len_utf8())),
|
||||
(i, '=') => match self.chars.next_if(|(_, c)| *c == '=') {
|
||||
Some(_) => Ok((i, Token::EqualEqual, i + 2)),
|
||||
_ => Ok((i, Token::Equal, i + 1)),
|
||||
}
|
||||
(i, '!') => match self.chars.next() {
|
||||
Some((_, '=')) => Ok((i, Token::BangEqual, i + 2)),
|
||||
Some((_, c)) => Err(LexerError::Unexpected(i+1, c)),
|
||||
None => Err(LexerError::UnexpectedEof),
|
||||
}
|
||||
(i, '\u{2260}') => Ok((i, Token::BangEqual, i + '\u{2260}'.len_utf8())),
|
||||
|
||||
(i, ',') => Ok((i, Token::Comma, i + 1)),
|
||||
(i, '=') => Ok((i, Token::Equal, i + 1)),
|
||||
(i, ':') => Ok((i, Token::Colon, i + 1)),
|
||||
(i, '\n') => Ok((i, Token::Newline, i + 1)),
|
||||
|
||||
(i, '0'..='9') => self.next_number(i, false),
|
||||
(i, '.') => self.next_number(i, true),
|
||||
(i, c) if is_ident_begin(c) => self.next_word(i, i + c.len_utf8()),
|
||||
|
|
|
@ -15,6 +15,26 @@ struct Uniforms {
|
|||
|
||||
@group(0) @binding(1) var<uniform> uniforms: Uniforms;
|
||||
|
||||
/////////////////
|
||||
// constants //
|
||||
/////////////////
|
||||
|
||||
const TAU = 6.283185307179586;
|
||||
const E = 2.718281828459045;
|
||||
const RECIP_SQRT2 = 0.7071067811865475;
|
||||
const LOG_TAU = 1.8378770664093453;
|
||||
const LOG_2 = 0.6931471805599453;
|
||||
const RECIP_SQRT29 = 0.18569533817705186;
|
||||
|
||||
const C_TAU = vec2f(TAU, 0.0);
|
||||
const C_E = vec2f(E, 0.0);
|
||||
const C_I = vec2f(0.0, 1.0);
|
||||
const C_EMGAMMA = vec2f(0.5772156649015329, 0.0);
|
||||
const C_PHI = vec2f(1.618033988749895, 0.0);
|
||||
const C_ZERO = vec2f(0.0, 0.0);
|
||||
const C_ONE = vec2f(1.0, 0.0);
|
||||
|
||||
|
||||
///////////////
|
||||
// utility //
|
||||
///////////////
|
||||
|
@ -43,23 +63,6 @@ fn vlength(v: vec2f) -> f32 {
|
|||
return max(max(a, b), max(c, d));
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// constants //
|
||||
/////////////////
|
||||
|
||||
const TAU = 6.283185307179586;
|
||||
const E = 2.718281828459045;
|
||||
const RECIP_SQRT2 = 0.7071067811865475;
|
||||
const LOG_TAU = 1.8378770664093453;
|
||||
const LOG_2 = 0.6931471805599453;
|
||||
const RECIP_SQRT29 = 0.18569533817705186;
|
||||
|
||||
const C_TAU = vec2f(TAU, 0.0);
|
||||
const C_E = vec2f(E, 0.0);
|
||||
const C_I = vec2f(0.0, 1.0);
|
||||
const C_EMGAMMA = vec2f(0.5772156649015329, 0.0);
|
||||
const C_PHI = vec2f(1.618033988749895, 0.0);
|
||||
|
||||
/////////////////////////
|
||||
// complex functions //
|
||||
/////////////////////////
|
||||
|
@ -80,32 +83,16 @@ fn c_signim(z: vec2f) -> vec2f {
|
|||
return vec2(sign(z.y), 0.0);
|
||||
}
|
||||
|
||||
fn c_ifgt(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f {
|
||||
return select(w, z, p.x > q.x);
|
||||
fn c_absre(z: vec2f) -> vec2f {
|
||||
return vec2(abs(z.x), 0.0);
|
||||
}
|
||||
|
||||
fn c_iflt(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f {
|
||||
return select(w, z, p.x < q.x);
|
||||
fn c_absim(z: vec2f) -> vec2f {
|
||||
return vec2(abs(z.y), 0.0);
|
||||
}
|
||||
|
||||
fn c_ifge(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f {
|
||||
return select(w, z, p.x >= q.x);
|
||||
}
|
||||
|
||||
fn c_ifle(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f {
|
||||
return select(w, z, p.x <= q.x);
|
||||
}
|
||||
|
||||
fn c_ifeq(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f {
|
||||
return select(w, z, p.x == q.x);
|
||||
}
|
||||
|
||||
fn c_ifne(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f {
|
||||
return select(w, z, p.x != q.x);
|
||||
}
|
||||
|
||||
fn c_ifnan(p: vec2f, z: vec2f, w: vec2f) -> vec2f {
|
||||
return select(w, z, p.x != p.x && p.y != p.y);
|
||||
fn c_isnan(z: vec2f) -> vec2f {
|
||||
return select(C_ZERO, C_ONE, z.x != z.x || z.y != z.y);
|
||||
}
|
||||
|
||||
fn c_conj(z: vec2f) -> vec2f {
|
||||
|
@ -128,9 +115,12 @@ fn c_arg(z: vec2f) -> vec2f {
|
|||
}
|
||||
|
||||
fn c_argbr(z: vec2f, br: vec2f) -> vec2f {
|
||||
if z.x < 0.0 && z.y == 0.0 {
|
||||
return vec2(TAU/2.0 + floor(br.x/TAU) * TAU, 0.0);
|
||||
}
|
||||
let r = vec2(cos(-br.x), sin(-br.x));
|
||||
let zr = c_mul(z, r);
|
||||
return c_arg(zr) + vec2(br.x, 0.0);
|
||||
return vec2(br.x + atan2(zr.y, zr.x), 0.0);
|
||||
}
|
||||
|
||||
fn c_add(u: vec2f, v: vec2f) -> vec2f {
|
||||
|
@ -222,15 +212,18 @@ fn c_tanh(z: vec2f) -> vec2f {
|
|||
}
|
||||
|
||||
fn c_asin(z: vec2f) -> vec2f {
|
||||
let m = select(-1.0, 1.0, z.y < 0.0 || (z.y == 0.0 && z.x > 0.0));
|
||||
let u = c_sqrt(vec2(1.0, 0.0) - c_mul(z, z));
|
||||
let v = c_log(u + vec2(-z.y, z.x));
|
||||
return vec2(v.y, -v.x);
|
||||
let v = c_log(u + m*vec2(-z.y, z.x));
|
||||
return m*vec2(v.y, -v.x);
|
||||
}
|
||||
|
||||
// TODO fix
|
||||
fn c_acos(z: vec2f) -> vec2f {
|
||||
let m = select(-1.0, 1.0, z.y < 0.0 || (z.y == 0.0 && z.x > 0.0));
|
||||
let u = c_sqrt(vec2(1.0, 0.0) - c_mul(z, z));
|
||||
let v = c_log(u + vec2(-z.y, z.x));
|
||||
return vec2(TAU*0.25 - v.y, v.x);
|
||||
let v = c_log(u + m*vec2(-z.y, z.x));
|
||||
return C_TAU/4.0 + m*vec2(-v.y, v.x);
|
||||
}
|
||||
|
||||
fn c_atan(z: vec2f) -> vec2f {
|
||||
|
@ -241,19 +234,19 @@ fn c_atan(z: vec2f) -> vec2f {
|
|||
}
|
||||
|
||||
fn c_asinh(z: vec2f) -> vec2f {
|
||||
let m = select(-1.0, 1.0, z.x > 0.0 || (z.x == 0.0 && z.y > 0.0));
|
||||
let u = c_sqrt(vec2(1.0, 0.0) + c_mul(z, z));
|
||||
return c_log(u + z);
|
||||
return c_log(u + z*m) * m;
|
||||
}
|
||||
|
||||
fn c_acosh(z: vec2f) -> vec2f {
|
||||
let u = c_sqrt(vec2(-1.0, 0.0) + c_mul(z, z));
|
||||
let b = select(0.0, TAU, z.x < 0.0 || (z.x == 0.0 && z.y < 0.0));
|
||||
let u = c_sqrtbr(vec2(-1.0, 0.0) + c_mul(z, z), vec2(b, 0.0));
|
||||
return c_log(u + z);
|
||||
}
|
||||
|
||||
fn c_atanh(z: vec2f) -> vec2f {
|
||||
let u = vec2(1.0, 0.0) + z;
|
||||
let v = vec2(1.0, 0.0) - z;
|
||||
return 0.5 * c_log(c_div(u, v));
|
||||
return 0.5 * (c_log(C_ONE + z) - c_log(C_ONE - z));
|
||||
}
|
||||
|
||||
// log gamma //
|
||||
|
@ -321,6 +314,80 @@ fn c_digamma_inner2(z: vec2f) -> vec2f {
|
|||
return w - l;
|
||||
}
|
||||
|
||||
// lambert w //
|
||||
|
||||
fn c_lambertw(z: vec2f) -> vec2f {
|
||||
var w = c_lambertw_init(z, 0.0);
|
||||
return c_lambertw_iter(z, w);
|
||||
}
|
||||
|
||||
fn c_lambertwbr(z: vec2f, br: vec2f) -> vec2f {
|
||||
// branch number
|
||||
let br_n = br.x / TAU;
|
||||
|
||||
// if -TAU/2 < br < TAU/2 then use -1/e as the branch point,
|
||||
// otherwise use 0
|
||||
let branch_point = select(C_ZERO, vec2(-1.0/E, 0.0), abs(br.x) < TAU / 2.0);
|
||||
let arg = c_arg(z - branch_point).x;
|
||||
|
||||
// if we're past the branch cut then take branch ceil(br_n),
|
||||
// otherwise take branch floor(br_n)
|
||||
let take_ceil = (br_n - floor(br_n) >= arg / TAU + 0.5);
|
||||
var init_br = select(floor(br_n), ceil(br_n), take_ceil);
|
||||
|
||||
var w = c_lambertw_init(z, init_br);
|
||||
// newton's method
|
||||
return c_lambertw_iter(z, w);
|
||||
}
|
||||
|
||||
fn c_lambertw_iter(z: vec2f, init: vec2f) -> vec2f {
|
||||
var w = init;
|
||||
for(var i = 0; i < 5; i++) {
|
||||
w = c_div(c_mul(w, w) + c_mul(z, c_exp(-w)), w + C_ONE);
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
fn c_lambertw_init(z: vec2f, br: f32) -> vec2f {
|
||||
let b = vec2(TAU * br, 0.0);
|
||||
let oz = z + vec2(1.25, 0.0);
|
||||
if br == 0.0 && dot(z, z) <= 50.0
|
||||
|| br == 1.0 && z.y < 0.0 && dot(oz, oz) < 1.0
|
||||
|| br == -1.0 && z.y > 0.0 && dot(oz, oz) < 1.0 {
|
||||
// accurate near 0, near principle branch
|
||||
let w = C_ONE + c_sqrtbr(C_ONE + E*z, b);
|
||||
return c_div(c_mul(E*z, c_log(w)), w + E*z);
|
||||
} else {
|
||||
// accurate asymptotically
|
||||
let logz = c_logbr(z, b);
|
||||
return logz - c_log(logz);
|
||||
}
|
||||
}
|
||||
|
||||
fn c_erf(z: vec2f) -> vec2f {
|
||||
if z.x >= 0 {
|
||||
return c_erf_plus(z);
|
||||
} else {
|
||||
return -c_erf_plus(-z);
|
||||
}
|
||||
}
|
||||
|
||||
const ERF_P = 0.3275911;
|
||||
const ERF_A1 = 0.2548295922;
|
||||
const ERF_A2 = -0.2844967358;
|
||||
const ERF_A3 = 1.4214137412;
|
||||
const ERF_A4 = -1.4531520268;
|
||||
const ERF_A5 = 1.0614054292;
|
||||
fn c_erf_plus(z: vec2f) -> vec2f {
|
||||
let t = c_recip(vec2(1.0, 0.0) + ERF_P * z);
|
||||
let m = c_exp(-c_mul(z, z));
|
||||
let r = c_mul(t, vec2f(ERF_A1, 0.0)
|
||||
+ c_mul(t, vec2f(ERF_A2, 0.0)
|
||||
+ c_mul(t, vec2f(ERF_A3, 0.0)
|
||||
+ c_mul(t, vec2f(ERF_A4, 0.0) + t * ERF_A5))));
|
||||
return vec2f(1.0, 0.0) - c_mul(m, r);
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// rendering //
|
||||
/////////////////
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{num::NonZeroU64, io::Cursor};
|
||||
|
||||
use wgpu::util::DeviceExt;
|
||||
use wgpu::{util::DeviceExt, MemoryHints};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
|
@ -37,9 +37,9 @@ impl Uniforms {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct WgpuState {
|
||||
pub struct WgpuState<'a> {
|
||||
pub uniforms: Uniforms,
|
||||
surface: wgpu::Surface,
|
||||
surface: wgpu::Surface<'a>,
|
||||
device: wgpu::Device,
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
render_pipeline: Option<wgpu::RenderPipeline>,
|
||||
|
@ -49,12 +49,12 @@ pub struct WgpuState {
|
|||
queue: wgpu::Queue
|
||||
}
|
||||
|
||||
impl WgpuState {
|
||||
pub async fn new<W>(window: &W, size: (u32, u32)) -> Self
|
||||
where W: raw_window_handle::HasRawWindowHandle + raw_window_handle::HasRawDisplayHandle {
|
||||
impl<'a> WgpuState<'a> {
|
||||
pub async fn new<W>(window: &'a W, size: (u32, u32)) -> Self
|
||||
where W: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle + Sync {
|
||||
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default());
|
||||
let surface = unsafe { instance.create_surface(&window) }.unwrap();
|
||||
let surface = instance.create_surface(window).unwrap();
|
||||
|
||||
let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::default(),
|
||||
|
@ -65,8 +65,12 @@ impl WgpuState {
|
|||
let (device, queue) = adapter.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
features: wgpu::Features::empty(),
|
||||
limits: wgpu::Limits::downlevel_webgl2_defaults(),
|
||||
required_features: wgpu::Features::empty(),
|
||||
required_limits: wgpu::Limits {
|
||||
max_texture_dimension_2d: 8192,
|
||||
..wgpu::Limits::downlevel_webgl2_defaults()
|
||||
},
|
||||
memory_hints: MemoryHints::Performance,
|
||||
},
|
||||
None
|
||||
).await.map_err(|e| e.to_string()).unwrap();
|
||||
|
@ -81,6 +85,7 @@ impl WgpuState {
|
|||
present_mode: wgpu::PresentMode::Fifo,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||
view_formats: vec![format],
|
||||
desired_maximum_frame_latency: 2,
|
||||
};
|
||||
surface.configure(&device, &config);
|
||||
|
||||
|
@ -168,12 +173,14 @@ impl WgpuState {
|
|||
let vertex = wgpu::VertexState {
|
||||
module: &vertex_module,
|
||||
entry_point: "main",
|
||||
compilation_options: Default::default(),
|
||||
buffers: &[]
|
||||
};
|
||||
|
||||
let fragment = wgpu::FragmentState {
|
||||
module: &fragment_module,
|
||||
entry_point: "main",
|
||||
compilation_options: Default::default(),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: self.config.format,
|
||||
blend: Some(wgpu::BlendState {
|
||||
|
@ -201,6 +208,7 @@ impl WgpuState {
|
|||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
self.render_pipeline = Some(render_pipeline);
|
||||
|
@ -216,7 +224,7 @@ impl WgpuState {
|
|||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||
store: true,
|
||||
store: wgpu::StoreOp::Store,
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -224,6 +232,8 @@ impl WgpuState {
|
|||
label: None,
|
||||
color_attachments: &[Some(color_attachment)],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
if let Some(pipeline) = &self.render_pipeline {
|
||||
rpass.set_pipeline(pipeline);
|
||||
|
|
Loading…
Reference in a new issue