changed things

This commit is contained in:
TriMill 2023-06-14 00:03:41 -04:00
parent 866f979c0c
commit 36e2f03894
9 changed files with 727 additions and 222 deletions

View file

@ -7,16 +7,40 @@
<body>
<div class="canvas-container">
<canvas id="canvas"></canvas>
<canvas id="grid_canvas"></canvas>
<svg id="overlay">
<line id="overlay_axis_x" x1="0" y1="0" x2="0" y2="0" stroke="#0006" stroke-width="1.5" visibility="hidden" />
<line id="overlay_axis_y" x1="0" y1="0" x2="0" y2="0" stroke="#0006" 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" />
</g>
<g id="overlay_points">
</g>
</svg>
</div>
<div class="menu"><details open>
<summary>Menu</summary>
<div><input type="button" id="button_graph" value="Graph"></div>
<div class="menus">
<details class="menu" open>
<summary>Source</summary>
<details>
<div>
<input type="button" id="button_graph" value="Graph">
<input type="button" id="button_redraw" value="Redraw">
<input type="checkbox" id="checkbox_autoredraw" checked>
<label for="checkbox_autoredraw">Auto redraw</label>
</div>
<div id="div_error_msg" hidden></div>
<div><textarea id="source_text">f(z) = z^2 + 3i&#10;plot(z) = 5z^2 + f(1/z) - 1</textarea></div>
</details>
<div>
<details class="menu" open>
<summary>Options</summary>
<div>
<input type="button" id="button_reset_view" value="Reset view">
<input type="button" id="button_help" value="Help" onclick="window.open('docs.html')">
</div>
<div>
<div><label for="range_resolution">Resolution</label></div>
<input type="range" id="range_resolution" name="resolution" min="-2" max="2" step="1" value="0">
@ -63,11 +87,52 @@
<input type="radio" name="color_mode" id="radio_color_2" data-value="2">
<label for="radio_color_2">None</label>
</div>
<div>
Overlay
<div>
<input type="checkbox" id="overlay_axes" checked>
<label for="overlay_axes">Draw axes</label>
</div>
</div>
</details>
<div id="div_error_msg" hidden></div>
<div><textarea id="source_text">f(z) = z^2 + 3i&#10;plot(z) = 5z^2 + f(1/z) - 1</textarea></div>
</div></div>
<details class="menu" open>
<summary>Variables</summary>
<div id="slider_template" hidden>
<div>
<input type="text" class="var-name" style="width: 5ch;"">
=
<input type="number" class="var-value" style="width: 10ch;" value="0" required>
<input type="button" class="var-delete" value="X">
</div>
<div>
<input type="range" class="var-slider" min="-1" max="1" step="0.01" value="0">
</div>
<div style="display: flex; flex-direction: row; justify-content: space-between;">
<input type="number" class="var-min" style="width: 6ch;" required value="-1">
<input type="number" class="var-step" style="width: 6ch;" required value="0.01">
<input type="number" class="var-max" style="width: 6ch;" required value="1">
</div>
<hr>
</div>
<div id="point_template" hidden>
<input type="text" class="var-name" style="width: 5ch;">
=
<input type="number" class="var-value-re" style="width: 8ch;" required value="0">
+
<input type="number" class="var-value-im" style="width: 8ch;" required value="0">i
<input type="button" class="var-delete" value="X">
<hr>
</div>
<div id="div_variables"></div>
<div id="buttons_var_new">
<input type="button" id="button_slider_new" value="+slider">
<input type="button" id="button_point_new" value="+point">
</div>
</details>
</div>
</div>
<script>
import('./index.js').then(m => window.module = m)

View file

@ -1,3 +1,5 @@
"use strict"
import init, * as cxgraph from "./pkg/cxgraph_web.js";
await init();
@ -6,40 +8,94 @@ let graphView = {
yoff: 0,
scale: 3,
res_mult: 1,
varNames: [],
};
let graphPoints = [];
let graphSliders = [];
let mouseX = 0.0;
let mouseY = 0.0;
let mousePressed = false;
function remap(x, lo1, hi1, lo2, hi2) {
return lo2 + (hi2 - lo2) * (x - lo1) / (hi1 - lo1);
}
function cxToScreen(cx) {
let [sc, sca] = [graphView.scale, graphView.scale * (window.innerWidth / window.innerHeight)];
return {
x: remap(cx.re - graphView.xoff, -sca, sca, 0, window.innerWidth),
y: remap(cx.im + graphView.yoff, -sc, sc, 0, window.innerHeight),
}
}
function screenToCx(screen) {
let [sc, sca] = [graphView.scale, graphView.scale * (window.innerWidth / window.innerHeight)];
return {
re: graphView.xoff + remap(screen.x, 0, window.innerWidth, -sca, sca),
im: -graphView.yoff + remap(screen.y, 0, window.innerHeight, -sc, sc),
}
}
//
// Canvas
//
function redraw() {
cxgraph.redraw();
}
function onViewChange() {
function tryRedraw() {
if(checkbox_autoredraw.checked) {
redraw();
}
}
function calcBounds() {
let width = window.innerWidth;
let height = window.innerHeight;
let aspect = width / height;
cxgraph.set_bounds(
graphView.xoff - graphView.scale * aspect,
graphView.yoff - graphView.scale,
graphView.xoff + graphView.scale * aspect,
graphView.yoff + graphView.scale
);
redraw();
return {
x_min: graphView.xoff - graphView.scale * aspect,
y_min: graphView.yoff - graphView.scale,
x_max: graphView.xoff + graphView.scale * aspect,
y_max: graphView.yoff + graphView.scale,
};
}
function onViewChange() {
let dim = { w: window.innerWidth, h: innerHeight };
let bounds = calcBounds();
cxgraph.set_bounds(bounds.x_min, bounds.y_min, bounds.x_max, bounds.y_max);
let origin = cxToScreen({ re: 0, im: 0 });
overlay_axis_x.setAttribute("x1", 0)
overlay_axis_x.setAttribute("x2", dim.w);
overlay_axis_x.setAttribute("y1", origin.y);
overlay_axis_x.setAttribute("y2", origin.y);
overlay_axis_y.setAttribute("x1", origin.x);
overlay_axis_y.setAttribute("x2", origin.x);
overlay_axis_y.setAttribute("y1", 0);
overlay_axis_y.setAttribute("y2", dim.h);
for(let point of graphPoints) {
point.onViewChange();
}
tryRedraw();
}
function onResize() {
let width = window.innerWidth;
let height = window.innerHeight;
cxgraph.resize(width*graphView.res_mult, height*graphView.res_mult);
grid_canvas.width = width;
grid_canvas.height = height;
canvas.style.width = "100vw";
canvas.style.height = "100vh";
onViewChange();
}
let mouseX = 0.0;
let mouseY = 0.0;
let mousePressed = false;
function onWheel(e) {
graphView.scale *= Math.exp(e.deltaY * 0.0007);
onViewChange();
@ -53,6 +109,9 @@ function onMouseDown(e) {
function onMouseUp(e) {
mousePressed = false;
for(let point of graphPoints) {
point.mousePressed = false;
}
}
function onMouseMove(e) {
@ -64,13 +123,27 @@ function onMouseMove(e) {
graphView.xoff -= 2.0 * graphView.scale * dX / window.innerHeight;
graphView.yoff += 2.0 * graphView.scale * dY / window.innerHeight;
onViewChange();
} else {
for(let point of graphPoints) {
point.onMouseMove(e);
}
}
}
window.addEventListener("resize", onResize);
canvas.addEventListener("wheel", onWheel);
canvas.addEventListener("mousedown", onMouseDown);
canvas.addEventListener("mouseup", onMouseUp);
canvas.addEventListener("mousemove", onMouseMove);
//
// Graph/redraw
//
function onGraph() {
let src = document.getElementById("source_text").value;
let src = source_text.value;
try {
cxgraph.load_shader(src);
cxgraph.load_shader(src, graphView.varNames);
div_error_msg.hidden = true;
redraw();
} catch(e) {
@ -80,50 +153,94 @@ function onGraph() {
}
}
window.addEventListener("resize", onResize);
canvas.addEventListener("wheel", onWheel);
canvas.addEventListener("mousedown", onMouseDown);
canvas.addEventListener("mouseup", onMouseUp);
canvas.addEventListener("mousemove", onMouseMove);
button_graph.addEventListener("click", onGraph);
button_redraw.addEventListener("click", redraw);
let class_decor = document.getElementsByClassName("decor")
for(let e of class_decor) {
e.addEventListener("change", () => {
let decor = 0;
for(let elem of class_decor) {
if(elem.checked) {
decor += parseInt(elem.getAttribute("data-value"));
}
}
cxgraph.set_decorations(decor);
redraw();
});
e.checked = false;
}
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);
let name_color_mode = document.getElementsByName("color_mode");
for(let e of name_color_mode) {
e.addEventListener("change", () => {
let selected = document.querySelector("input[name=color_mode]:checked");
cxgraph.set_coloring(parseInt(selected.getAttribute("data-value")));
redraw();
});
e.checked = false;
}
name_color_mode[0].checked = true;
source_text.addEventListener("input", () => {
source_text.value = source_text.value.replace(
specialChars,
(_m, p) => charMap[p]
)
});
//
// Options
//
button_reset_view.addEventListener("click", () => {
graphView.xoff = 0;
graphView.yoff = 0;
graphView.scale = 3;
onViewChange();
})
range_shading.addEventListener("change", () => {
let value = parseFloat(range_shading.value);
cxgraph.set_shading_intensity(value);
redraw();
tryRedraw();
});
range_contour.addEventListener("change", () => {
let value = parseFloat(range_contour.value);
cxgraph.set_contour_intensity(value);
redraw();
tryRedraw();
});
range_resolution.addEventListener("change", () => {
@ -131,7 +248,220 @@ range_resolution.addEventListener("change", () => {
onResize();
});
let classDecor = document.getElementsByClassName("decor")
for(let e of classDecor) {
e.addEventListener("change", () => {
let decor = 0;
for(let elem of classDecor) {
if(elem.checked) {
decor += parseInt(elem.getAttribute("data-value"));
}
}
cxgraph.set_decorations(decor);
tryRedraw();
});
e.checked = false;
}
let nameColorMode = document.getElementsByName("color_mode");
for(let e of nameColorMode) {
e.addEventListener("change", () => {
let selected = document.querySelector("input[name=color_mode]:checked");
cxgraph.set_coloring(parseInt(selected.getAttribute("data-value")));
tryRedraw();
});
e.checked = false;
}
nameColorMode[0].checked = true;
overlay_axes.addEventListener("change", () => {
let vis = overlay_axes.checked ? "visible" : "hidden";
overlay_axis_x.setAttribute("visibility", vis);
overlay_axis_y.setAttribute("visibility", vis);
});
//
// Variables
//
let nextVarId = 0;
let varCount = 0;
function genVarData() {
varCount = 0;
for(let child of div_variables.children) {
if(child.id.startsWith("slider")) {
let value = parseFloat(child.querySelector(".var-value").value) || 0;
cxgraph.set_variable(varCount, value, 0);
varCount++;
} else if(child.id.startsWith("point")) {
let re = parseFloat(child.querySelector(".var-value-re").value) || 0;
let im = parseFloat(child.querySelector(".var-value-im").value) || 0;
cxgraph.set_variable(varCount, re, im);
varCount++;
}
}
}
function genVarNames() {
graphView.varNames = [];
varCount = 0;
for(let child of div_variables.children) {
if(child.id.startsWith("slider")) {
let name = child.querySelector(".var-name").value || "";
graphView.varNames.push(name);
varCount++;
} else if(child.id.startsWith("point")) {
let name = child.querySelector(".var-name").value || "";
graphView.varNames.push(name);
varCount++;
}
}
genVarData();
onGraph();
}
function addSlider() {
if(varCount >= 8) {
return;
}
let newSlider = slider_template.cloneNode(true);
let id = nextVarId++;
newSlider.id = "slider_" + id;
newSlider.hidden = false;
div_variables.appendChild(newSlider);
newSlider.querySelector(".var-name").addEventListener("change", genVarNames);
newSlider.querySelector(".var-delete").addEventListener("click", () => {
document.getElementById("slider_" + id).remove()
genVarNames();
});
newSlider.querySelector(".var-min").addEventListener("input", (e) => {
newSlider.querySelector(".var-slider").min = e.target.value;
genVarData();
tryRedraw();
});
newSlider.querySelector(".var-max").addEventListener("input", (e) => {
newSlider.querySelector(".var-slider").max = e.target.value
genVarData();
tryRedraw();
});
newSlider.querySelector(".var-step").addEventListener("input", (e) => {
newSlider.querySelector(".var-slider").step = e.target.value;
genVarData();
tryRedraw();
});
newSlider.querySelector(".var-slider").addEventListener("input", (e) => {
newSlider.querySelector(".var-value").value = e.target.value
genVarData();
tryRedraw();
});
newSlider.querySelector(".var-value").addEventListener("input", (e) => {
newSlider.querySelector(".var-slider").value = e.target.value
genVarData();
tryRedraw();
});
genVarNames();
}
class Point {
constructor(id) {
this.id = id;
let menuPoint = point_template.cloneNode(true);
this.menuPoint = menuPoint;
menuPoint.id = "point_" + id;
menuPoint.hidden = false;
div_variables.appendChild(menuPoint);
let svgPoint = svg_point_template.cloneNode(true);
this.svgPoint = svgPoint;
svgPoint.id = "svg_point_" + id;
svgPoint.setAttribute("visibility", "");
svgPoint.setAttribute("data-id", menuPoint.id);
overlay_points.appendChild(svgPoint);
this.mousePressed = false;
menuPoint.querySelector(".var-name").addEventListener("change", genVarNames);
menuPoint.querySelector(".var-delete").addEventListener("click", () => this.destroy());
menuPoint.querySelector(".var-value-re").addEventListener("input", () => {
this.onViewChange();
genVarData();
tryRedraw();
});
menuPoint.querySelector(".var-value-im").addEventListener("input", () => {
this.onViewChange();
genVarData();
tryRedraw();
});
svgPoint.addEventListener("mousedown", (e) => {
this.mousePressed = true;
mouseX = e.offsetX;
mouseY = e.offsetY;
});
svgPoint.addEventListener("mouseup", () => {
this.mousePressed = false;
mousePressed = false;
});
svgPoint.addEventListener("mousemove", (e) => this.onMouseMove(e));
this.onViewChange();
genVarNames();
}
onMouseMove(e) {
if(this.mousePressed) {
mouseX = e.offsetX;
mouseY = e.offsetY;
let cx = screenToCx({ x: mouseX, y: mouseY });
this.menuPoint.querySelector(".var-value-re").value = cx.re;
this.menuPoint.querySelector(".var-value-im").value = -cx.im;
this.onViewChange();
genVarData();
redraw();
}
}
onViewChange() {
let re = parseFloat(this.menuPoint.querySelector(".var-value-re").value) || 0;
let im = parseFloat(this.menuPoint.querySelector(".var-value-im").value) || 0;
let screen = cxToScreen({ re: re, im: -im });
this.svgPoint.setAttribute("transform", `translate(${screen.x} ${screen.y})`);
}
destroy() {
this.menuPoint.remove();
this.svgPoint.remove();
graphPoints = graphPoints.filter(p => p != this);
genVarNames();
}
}
function addPoint() {
if(varCount >= 8) {
return;
}
graphPoints.push(new Point(nextVarId++));
}
button_slider_new.addEventListener("click", addSlider);
button_point_new.addEventListener("click", addPoint);
//
// Init
//
onResize();
onGraph();
// menu
// Debug
export function show_ast() {
console.info(cxgraph.show_shader_ast(source_text.value));
}

View file

@ -1,6 +1,7 @@
use std::collections::HashMap;
use libcxgraph::{renderer::WgpuState, language::compile};
use libcxgraph::{renderer::WgpuState, language::{compile, show_ast}};
use log::info;
use winit::{window::WindowBuilder, event_loop::EventLoop, platform::web::WindowBuilderExtWebSys};
use wasm_bindgen::{prelude::*, JsValue};
use web_sys::HtmlCanvasElement;
@ -48,15 +49,26 @@ pub async fn start() {
state.uniforms.shading_intensity = 0.3;
state.uniforms.contour_intensity = 0.0;
unsafe { WGPU_STATE = Some(state) };
info!("Initialized");
}
#[wasm_bindgen]
pub fn load_shader(src: &str) -> Result<(), JsValue> {
let wgsl = compile(src, &HashMap::new()).map_err(|e| e.to_string())?;
pub fn load_shader(src: &str, var_names: Box<[JsValue]>) -> Result<(), JsValue> {
let names: HashMap<String, usize> = var_names.iter()
.enumerate()
.map(|(i, e)| (e.as_string().unwrap(), i))
.collect();
let wgsl = compile(src, &names).map_err(|e| e.to_string())?;
info!("Generated WGSL:\n{}", wgsl);
with_state(|state| state.load_shaders(&wgsl));
Ok(())
}
#[wasm_bindgen]
pub fn show_shader_ast(src: &str) -> Result<String, JsValue> {
show_ast(src).map_err(|e| e.to_string().into())
}
#[wasm_bindgen]
pub fn redraw() {
with_state(|state| state.redraw());
@ -95,3 +107,11 @@ pub fn set_coloring(value: u32) {
pub fn set_decorations(value: u32) {
with_state(|state| state.uniforms.decorations = value);
}
#[wasm_bindgen]
pub fn set_variable(idx: usize, re: f32, im: f32) {
with_state(|state| {
state.uniforms.variables[idx*2 + 0] = re;
state.uniforms.variables[idx*2 + 1] = im;
});
}

View file

@ -1,5 +1,6 @@
* {
font-family: monospace;
font-size: 16px;
}
body, html {
@ -15,7 +16,7 @@ body, html {
top: 0px;
}
canvas {
canvas, #overlay {
position: absolute;
left: 0px;
top: 0px;
@ -27,34 +28,35 @@ canvas {
z-index: 0;
}
#grid_canvas {
#overlay {
z-index: 1;
pointer-events: none;
}
.menu {
#overlay_points {
pointer-events: all;
}
.menus {
position: absolute;
left: 0px;
top: 0px;
right: 0px;
pointer-events: none;
display: flex;
flex-direction: row;
justify-content: space-between;
z-index: 10;
}
.menu {
pointer-events: all;
margin: 10px;
padding: 10px;
background: #334;
color: #fff;
z-index: 10;
}
#source_text {
min-width: max-content;
width: 300px;
height: 500px;
}
#div_error_msg {
color: #f9a;
}
div.menu > details {
max-width: min-content;
height: fit-content;
box-shadow: 0 0 5px 1px #00000088;
}
details > *:not(summary) {
@ -63,3 +65,44 @@ details > *:not(summary) {
padding-left: 8px;
border-left: 1px solid #fff;
}
summary {
padding-bottom: 5px;
}
#source_text {
width: 300px;
height: 500px;
font-size: 15px;
}
#div_error_msg {
color: #f9a;
}
input {
color: #fff;
background: #667;
border: none;
border-radius: 2px;
margin-top: 2px;
margin-bottom: 2px;
padding-left: 3px;
padding-right: 3px;
}
input, textarea{
box-shadow: 0 0 5px 1px #00000044;
}
input[type=number], input[type=text] {
border-bottom: 2px solid #def;
}
input:hover {
background: #889;
}
input[type=button]:active {
background: #667;
}

View file

@ -1,105 +1,69 @@
use std::{collections::HashMap, ops, f64::consts::{TAU, E}};
use std::collections::HashMap;
use num_complex::Complex64 as Complex;
#[derive(Clone, Copy)]
pub enum Func {
One(fn(Complex) -> Complex),
Two(fn(Complex, Complex) -> Complex),
}
impl Func {
pub fn argc(&self) -> usize {
match self {
Func::One(_) => 1,
Func::Two(_) => 2,
}
}
}
fn neg(z: Complex) -> Complex { -z }
fn re(z: Complex) -> Complex { Complex::from(z.re) }
fn im(z: Complex) -> Complex { Complex::from(z.im) }
fn abs_sq(z: Complex) -> Complex { Complex::from(z.norm_sqr()) }
fn abs(z: Complex) -> Complex { Complex::from(z.norm()) }
fn arg(z: Complex) -> Complex { Complex::from(z.arg()) }
fn recip(z: Complex) -> Complex { Complex::new(1.0, 0.0) / z }
fn conj(z: Complex) -> Complex { z.conj() }
fn gamma(z: Complex) -> Complex {
let reflect = z.re < 0.5;
let zp = if reflect { 1.0 - z } else { z };
let mut w = gamma_inner(zp + 3.0) / (zp*(zp+1.0)*(zp+2.0)*(zp+3.0));
if reflect {
w = TAU * 0.5 / ((TAU * 0.5 * z).sin() * w);
}
return w;
}
// Yang, ZH., Tian, JF. An accurate approximation formula for gamma function. J Inequal Appl 2018, 56 (2018).
// https://doi.org/10.1186/s13660-018-1646-6
fn gamma_inner(z: Complex) -> Complex {
let z2 = z * z;
let z3 = z2 * z;
let a = (TAU * z).sqrt();
let b = (1.0 / (E * E) * z3 * (1.0/z).sinh()).powc(0.5 * z);
let c = ((7.0/324.0) / (z3 * (35.0 * z2 + 33.0))).exp();
return a * b * c;
}
thread_local! {
pub static BUILTIN_FUNCS: HashMap<&'static str, (&'static str, Func)> = {
pub static BUILTIN_FUNCS: HashMap<&'static str, (&'static str, usize)> = {
let mut m = HashMap::new();
m.insert("pos", ("c_pos", Func::One(std::convert::identity)));
m.insert("neg", ("c_neg", Func::One(neg)));
m.insert("recip", ("c_recip", Func::One(recip)));
m.insert("conj", ("c_conj", Func::One(conj)));
m.insert("pos", ("c_pos", 1));
m.insert("neg", ("c_neg", 1));
m.insert("recip", ("c_recip", 1));
m.insert("conj", ("c_conj", 1));
m.insert("re", ("c_re", Func::One(re)));
m.insert("im", ("c_im", Func::One(im)));
m.insert("abs_sq", ("c_abs_sq", Func::One(abs_sq)));
m.insert("abs", ("c_abs", Func::One(abs)));
m.insert("arg", ("c_arg", Func::One(arg)));
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("abs_sq", ("c_abs_sq", 1));
m.insert("abs", ("c_abs", 1));
m.insert("arg", ("c_arg", 1));
m.insert("argbr", ("c_argbr", 2));
m.insert("add", ("c_add", Func::Two(<Complex as ops::Add>::add)));
m.insert("sub", ("c_sub", Func::Two(<Complex as ops::Sub>::sub)));
m.insert("mul", ("c_mul", Func::Two(<Complex as ops::Mul>::mul)));
m.insert("div", ("c_div", Func::Two(<Complex as ops::Div>::div)));
m.insert("pow", ("c_pow", Func::Two(Complex::powc)));
m.insert("add", ("c_add", 2));
m.insert("sub", ("c_sub", 2));
m.insert("mul", ("c_mul", 2));
m.insert("div", ("c_div", 2));
m.insert("pow", ("c_pow", 2));
m.insert("exp", ("c_exp", Func::One(Complex::exp)));
m.insert("log", ("c_log", Func::One(Complex::ln)));
m.insert("sqrt", ("c_sqrt", Func::One(Complex::sqrt)));
m.insert("exp", ("c_exp", 1));
m.insert("log", ("c_log", 1));
m.insert("logbr", ("c_logbr", 2));
m.insert("sqrt", ("c_sqrt", 1));
m.insert("sin", ("c_sin", Func::One(Complex::sin)));
m.insert("cos", ("c_cos", Func::One(Complex::cos)));
m.insert("tan", ("c_tan", Func::One(Complex::tan)));
m.insert("sinh", ("c_sinh", Func::One(Complex::sinh)));
m.insert("cosh", ("c_cosh", Func::One(Complex::cosh)));
m.insert("tanh", ("c_tanh", Func::One(Complex::tanh)));
m.insert("sin", ("c_sin", 1));
m.insert("cos", ("c_cos", 1));
m.insert("tan", ("c_tan", 1));
m.insert("sinh", ("c_sinh", 1));
m.insert("cosh", ("c_cosh", 1));
m.insert("tanh", ("c_tanh", 1));
m.insert("asin", ("c_asin", Func::One(Complex::asin)));
m.insert("acos", ("c_acos", Func::One(Complex::acos)));
m.insert("atan", ("c_atan", Func::One(Complex::atan)));
m.insert("asinh", ("c_asinh", Func::One(Complex::asinh)));
m.insert("acosh", ("c_acosh", Func::One(Complex::acosh)));
m.insert("atanh", ("c_atanh", Func::One(Complex::atanh)));
m.insert("asin", ("c_asin", 1));
m.insert("acos", ("c_acos", 1));
m.insert("atan", ("c_atan", 1));
m.insert("asinh", ("c_asinh", 1));
m.insert("acosh", ("c_acosh", 1));
m.insert("atanh", ("c_atanh", 1));
m.insert("gamma", ("c_gamma", Func::One(gamma)));
m.insert("\u{0393}", ("c_gamma", Func::One(gamma)));
m.insert("gamma", ("c_gamma", 1));
m.insert("\u{0393}", ("c_gamma", 1));
m.insert("loggamma", ("c_loggamma", 1));
m.insert("log\u{0393}", ("c_loggamma", 1));
m.insert("digamma", ("c_digamma", 1));
m.insert("\u{03C8}", ("c_digamma", 1));
m
};
pub static BUILTIN_CONSTS: HashMap<&'static str, (&'static str, Complex)> = {
let mut m = HashMap::new();
m.insert("i", ("C_I", Complex::new(0.0, 1.0)));
m.insert("e", ("C_E", Complex::new(std::f64::consts::E, 0.0)));
m.insert("tau", ("C_TAU", Complex::new(std::f64::consts::TAU, 0.0)));
m.insert("\u{03C4}", ("C_TAU", Complex::new(std::f64::consts::TAU, 0.0)));
m.insert("e", ("C_E", Complex::new(std::f64::consts::E, 0.0)));
m.insert("i", ("C_I", Complex::new(0.0, 1.0)));
m.insert("emgamma", ("C_EMGAMMA", Complex::new(0.5772156649015329, 0.0)));
m.insert("\u{03B3}", ("C_EMGAMMA", Complex::new(0.5772156649015329, 0.0)));
m.insert("phi", ("C_PHI", Complex::new(1.618033988749895, 0.0)));
m.insert("\u{03C6}", ("C_PHI", Complex::new(1.618033988749895, 0.0)));
m
}
}

View file

@ -1,6 +1,6 @@
use std::{collections::{HashSet, HashMap}, fmt};
use super::{ast::{Definition, Expression, ExpressionType, BinaryOp, UnaryOp}, builtins::{BUILTIN_CONSTS, BUILTIN_FUNCS}, Variable};
use super::{ast::{Definition, Expression, ExpressionType, BinaryOp, UnaryOp}, builtins::{BUILTIN_CONSTS, BUILTIN_FUNCS}};
#[derive(Clone, Debug)]
pub struct CompileError(String);
@ -25,44 +25,11 @@ impl From<fmt::Error> for CompileError {
}
}
const GREEK_LOWER: [&str; 25] = [
"Al", "Be", "Ga", "De", "Ep",
"Ze", "Et", "Th", "Io", "Ka",
"La", "Mu", "Nu", "Xi", "Om",
"Pi", "Rh", "Sj", "Si", "Ta",
"Yp", "Ph", "Ch", "Ps", "Oa",
];
const GREEK_UPPER: [&str; 25] = [
"AL", "BE", "GA", "DE", "EP",
"ZE", "ET", "TH", "IO", "KA",
"LA", "MU", "NU", "XI", "OM",
"PI", "RH", "SJ", "SI", "TA",
"YP", "PH", "CH", "PS", "OA",
];
fn format_char(buf: &mut String, c: char) {
match c {
'a'..='z' | 'A'..='Z' | '0'..='9' => buf.push(c),
'_' => buf.push_str("__"),
'\'' => buf.push_str("_p"),
'\u{0391}'..='\u{03A9}' => {
buf.push('_');
buf.push_str(GREEK_UPPER[c as usize - 0x0391])
},
'\u{03B1}'..='\u{03C9}' => {
buf.push('_');
buf.push_str(GREEK_LOWER[c as usize - 0x03B1])
},
c => {
buf.push('_');
let mut b = [0u8; 8];
let s = c.encode_utf8(&mut b);
for b in s.bytes() {
buf.push(char::from_digit(b as u32 >> 4, 16).unwrap());
buf.push(char::from_digit(b as u32 & 0x0f, 16).unwrap());
}
}
'_' => buf.push_str("u_"),
'\'' => buf.push_str("p_"),
c => buf.push(c),
}
}
@ -83,7 +50,7 @@ fn format_tmp(idx: usize) -> String { format!("tmp_{}", idx) }
pub struct Compiler<'w, 'i, W: fmt::Write> {
buf: &'w mut W,
vars: &'w HashMap<String, Variable>,
vars: &'w HashMap<String, usize>,
global_funcs: HashMap<&'i str, usize>,
global_consts: HashSet<&'i str>,
}
@ -110,7 +77,7 @@ impl<'i> LocalState<'i> {
}
impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
pub fn new(buf: &'w mut W, vars: &'w HashMap<String, Variable>) -> Self {
pub fn new(buf: &'w mut W, vars: &'w HashMap<String, usize>) -> Self {
Self {
buf,
vars,
@ -167,9 +134,13 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
}
pub fn ensure_plot_defined(&self) -> Result<(), CompileError> {
if self.global_funcs.contains_key("plot") {
if let Some(n) = self.global_funcs.get("plot") {
if *n == 1 {
Ok(())
} else {
Err("Plot function has wrong number of arguments".to_owned().into())
}
} else {
Err("No plot function defined".to_owned().into())
}
}
@ -272,7 +243,7 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
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} < {count}; {ivar}++) {{")?;
let mut loop_local = local.clone();
loop_local.local_vars.insert(itervar);
let mut last = String::new();
@ -288,8 +259,8 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
fn resolve_func(&self, name: &str) -> Result<(String, usize), CompileError> {
if let Some(argc) = self.global_funcs.get(name) {
Ok((format_func(name), *argc))
} else if let Some((var, f)) = BUILTIN_FUNCS.with(|c| c.get(name).copied()) {
Ok(((*var).to_owned(), f.argc()))
} else if let Some((var, argc)) = BUILTIN_FUNCS.with(|c| c.get(name).copied()) {
Ok(((*var).to_owned(), argc))
} else {
Err(format!("use of undeclared function {name}").into())
}
@ -300,6 +271,12 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
Ok(format_local(name))
} else if self.global_consts.contains(name) {
Ok(format_const(name) + "()")
} else if let Some(var) = self.vars.get(name) {
if var % 2 == 0 {
Ok(format!("uniforms.variables[{}].xy", var/2))
} else {
Ok(format!("uniforms.variables[{}].zw", var/2))
}
} else if let Some(var) = BUILTIN_CONSTS.with(|c| Some(c.get(name)?.0)) {
Ok(var.to_owned())
} else {

View file

@ -4,7 +4,7 @@ use lalrpop_util::lalrpop_mod;
use crate::language::token::Lexer;
use self::compiler::Compiler;
use self::{compiler::Compiler, ast::display_def};
mod token;
mod ast;
@ -13,12 +13,7 @@ mod builtins;
lalrpop_mod!(pub syntax, "/language/syntax.rs");
pub enum Variable {
Slider(usize),
Point(usize, usize),
}
pub fn compile(src: &str, vars: &HashMap<String, Variable>) -> Result<String, Box<dyn std::error::Error>> {
pub fn compile(src: &str, vars: &HashMap<String, usize>) -> Result<String, Box<dyn std::error::Error>> {
let lexer = Lexer::new(src);
let result = syntax::ProgramParser::new()
.parse(src, lexer)
@ -31,3 +26,15 @@ pub fn compile(src: &str, vars: &HashMap<String, Variable>) -> Result<String, Bo
cmp.ensure_plot_defined()?;
Ok(wgsl)
}
pub fn show_ast(src: &str) -> Result<String, Box<dyn std::error::Error>> {
let lexer = Lexer::new(src);
let result = syntax::ProgramParser::new()
.parse(src, lexer)
.map_err(|e| e.to_string())?;
let mut buf = String::new();
for defn in result {
display_def(&mut buf, &defn)?;
}
Ok(buf)
}

View file

@ -38,10 +38,14 @@ fn correct_mod2(x: vec2f, y: vec2f) -> vec2f {
const TAU = 6.283185307179586;
const E = 2.718281828459045;
const RECIP_SQRT2 = 0.7071067811865475;
const LOG_TAU = 1.8378770664093453;
const LOG_2 = 0.6931471805599453;
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 //
@ -55,6 +59,14 @@ fn c_im(z: vec2f) -> vec2f {
return vec2(z.y, 0.0);
}
fn c_signre(z: vec2f) -> vec2f {
return vec2(sign(z.x), 0.0);
}
fn c_signim(z: vec2f) -> vec2f {
return vec2(sign(z.y), 0.0);
}
fn c_conj(z: vec2f) -> vec2f {
return z * vec2(1.0, -1.0);
}
@ -71,6 +83,12 @@ fn c_arg(z: vec2f) -> vec2f {
return vec2(atan2(z.y, z.x), 0.0);
}
fn c_argbr(z: vec2f, br: vec2f) -> vec2f {
let r = vec2(cos(-br.x), sin(-br.x));
let zr = c_mul(z, r);
return vec2(atan2(zr.y, zr.x) + br.x, 0.0);
}
fn c_add(u: vec2f, v: vec2f) -> vec2f {
return u + v;
}
@ -107,6 +125,10 @@ fn c_log(z: vec2f) -> vec2f {
return vec2(0.5 * log(dot(z, z)), atan2(z.y, z.x));
}
fn c_logbr(z: vec2f, br: vec2f) -> vec2f {
return vec2(0.5 * log(dot(z, z)), c_argbr(z, br).x);
}
fn c_pow(u: vec2f, v: vec2f) -> vec2f {
return c_exp(c_mul(c_log(u), v));
}
@ -174,6 +196,8 @@ fn c_atanh(z: vec2f) -> vec2f {
return 0.5 * c_log(c_div(u, v));
}
// gamma //
fn c_gamma(z: vec2f) -> vec2f {
let reflect = z.x < 0.5;
var zp = z;
@ -205,6 +229,62 @@ fn c_gamma_inner2(z: vec2f) -> vec2f {
return c_div(w, c_mul(c_mul(z, z + vec2(1.0, 0.0)), c_mul(z + vec2(2.0, 0.0), z + vec2(3.0, 0.0))));
}
// log gamma //
fn c_loggamma(z: vec2f) -> vec2f {
let reflect = z.x < 0.5 && abs(z.y) < 10.0;
var zp = z;
if reflect {
zp = vec2(1.0, 0.0) - z;
}
var w = c_loggamma_inner2(zp);
if reflect {
//let offset = select(0.0, TAU / 2.0, z % 2.0 < 1.0);
let br = 0.5 * TAU * (0.5 - z.x) * sign(z.y);
w = vec2(LOG_TAU - LOG_2, 0.0) - c_logbr(c_sin(TAU/2.0 * z), vec2(br, 0.0)) - w;
}
return w;
}
fn c_loggamma_inner(z: vec2f) -> vec2f {
return c_mul(z - vec2(0.5, 0.0), c_log(z)) - z + vec2(0.5*LOG_TAU, 0.0) + c_recip(12.0 * z);
}
fn c_loggamma_inner2(z: vec2f) -> vec2f {
let w = c_loggamma_inner(z + vec2(3.0, 0.0));
let l = c_log(z) + c_log(z + vec2(1.0, 0.0)) + c_log(z + vec2(2.0, 0.0));
return w - l;
}
// digamma //
fn c_digamma(z: vec2f) -> vec2f {
let reflect = z.x < 0.5;
var zp = z;
if reflect {
zp = vec2(1.0, 0.0) - z;
}
var w = c_digamma_inner2(zp);
if reflect {
w -= TAU / 2.0 * c_recip(c_tan(TAU / 2.0 * z));
}
return w;
}
fn c_digamma_inner(z: vec2f) -> vec2f {
let zr = c_recip(z);
let zr2 = c_mul(zr, zr);
let zr4 = c_mul(zr2, zr2);
let zr6 = c_mul(zr2, zr4);
return c_log(z) - 0.5*zr - (1.0/12.0)*zr2 + (1.0/120.0)*zr4 - (1.0/252.0)*zr6;
}
fn c_digamma_inner2(z: vec2f) -> vec2f {
let w = c_digamma_inner(z + vec2(3.0, 0.0));
let l = c_recip(z + vec2(2.0, 0.0)) + c_recip(z + vec2(1.0, 0.0)) + c_recip(z);
return w - l;
}
/////////////////
// rendering //
/////////////////
@ -215,8 +295,12 @@ fn hsv2rgb(c: vec3f) -> vec3f {
}
fn shademap(r: f32) -> f32 {
if uniforms.shading_intensity == 0.0 {
return 1.0;
} else {
let i = uniforms.shading_intensity * uniforms.shading_intensity * uniforms.shading_intensity;
return r*inverseSqrt(r * r + 0.0625 * i);
}
}
fn coloring_standard(z: vec2f) -> vec3f {
@ -224,19 +308,33 @@ fn coloring_standard(z: vec2f) -> vec3f {
return vec3f(0.5, 0.5, 0.5);
}
let r = length(z);
let mag_sq = dot(z, z);
if mag_sq > 3.40282347E+38 {
return vec3f(1.0, 1.0, 1.0);
}
let mag = sqrt(mag_sq);
let arg = atan2(z.y, z.x);
let hsv = vec3f(arg / TAU + 1.0, shademap(1.0/r), shademap(r));
let hsv = vec3f(arg / TAU + 1.0, shademap(1.0/mag), shademap(mag));
return hsv2rgb(hsv);
}
fn coloring_uniform(z: vec2f) -> vec3f {
if z.x == 0.0 && z.y == 0.0 {
return vec3f(0.0, 0.0, 0.0);
}
if z.x != z.x || z.y != z.y {
return vec3f(0.5, 0.5, 0.5);
}
let mag_sq = dot(z, z);
if mag_sq > 3.40282347E+38 {
return vec3f(1.0, 1.0, 1.0);
}
let mag = sqrt(mag_sq);
let arg = atan2(z.y, z.x);
let mag = length(z);
let r = cos(arg - 0.0*TAU/3.0)*0.5 + 0.5;
let g = cos(arg - 1.0*TAU/3.0)*0.5 + 0.5;
@ -307,6 +405,10 @@ fn main(@builtin(position) in: vec4f) -> @location(0) vec4f {
contours *= decoration_contour_mag(z);
}
if(contours != contours) {
contours = 0.0;
}
let final_col = mix(col, vec3f(contours * 0.5 + 0.5), uniforms.contour_intensity);
return vec4f(pow(final_col, vec3(1.68)), 1.0);

View file

@ -1,6 +1,5 @@
use std::{num::NonZeroU64, io::Cursor};
use log::info;
use wgpu::util::DeviceExt;
#[derive(Debug)]
@ -233,8 +232,6 @@ impl WgpuState {
rpass.draw(1..4, 0..1);
}
}
info!("Redrawing");
info!("Uniforms: {:?}", self.uniforms);
let mut cursor = Cursor::new([0; UNIFORM_SIZE]);
self.uniforms.encode(&mut cursor).unwrap();
self.queue.write_buffer(&self.uniform_buffer, 0, &cursor.into_inner());