changed things
This commit is contained in:
parent
866f979c0c
commit
36e2f03894
9 changed files with 727 additions and 222 deletions
|
@ -7,16 +7,40 @@
|
||||||
<body>
|
<body>
|
||||||
<div class="canvas-container">
|
<div class="canvas-container">
|
||||||
<canvas id="canvas"></canvas>
|
<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>
|
||||||
|
|
||||||
<div class="menu"><details open>
|
<div class="menus">
|
||||||
<summary>Menu</summary>
|
<details class="menu" open>
|
||||||
<div><input type="button" id="button_graph" value="Graph"></div>
|
<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 plot(z) = 5z^2 + f(1/z) - 1</textarea></div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<details class="menu" open>
|
||||||
<summary>Options</summary>
|
<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>
|
||||||
<div><label for="range_resolution">Resolution</label></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">
|
<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">
|
<input type="radio" name="color_mode" id="radio_color_2" data-value="2">
|
||||||
<label for="radio_color_2">None</label>
|
<label for="radio_color_2">None</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
Overlay
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="overlay_axes" checked>
|
||||||
|
<label for="overlay_axes">Draw axes</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<div id="div_error_msg" hidden></div>
|
<details class="menu" open>
|
||||||
<div><textarea id="source_text">f(z) = z^2 + 3i plot(z) = 5z^2 + f(1/z) - 1</textarea></div>
|
<summary>Variables</summary>
|
||||||
</div></div>
|
<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>
|
<script>
|
||||||
import('./index.js').then(m => window.module = m)
|
import('./index.js').then(m => window.module = m)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
"use strict"
|
||||||
|
|
||||||
import init, * as cxgraph from "./pkg/cxgraph_web.js";
|
import init, * as cxgraph from "./pkg/cxgraph_web.js";
|
||||||
await init();
|
await init();
|
||||||
|
|
||||||
|
@ -6,40 +8,94 @@ let graphView = {
|
||||||
yoff: 0,
|
yoff: 0,
|
||||||
scale: 3,
|
scale: 3,
|
||||||
res_mult: 1,
|
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() {
|
function redraw() {
|
||||||
cxgraph.redraw();
|
cxgraph.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onViewChange() {
|
function tryRedraw() {
|
||||||
|
if(checkbox_autoredraw.checked) {
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcBounds() {
|
||||||
let width = window.innerWidth;
|
let width = window.innerWidth;
|
||||||
let height = window.innerHeight;
|
let height = window.innerHeight;
|
||||||
let aspect = width / height;
|
let aspect = width / height;
|
||||||
cxgraph.set_bounds(
|
return {
|
||||||
graphView.xoff - graphView.scale * aspect,
|
x_min: graphView.xoff - graphView.scale * aspect,
|
||||||
graphView.yoff - graphView.scale,
|
y_min: graphView.yoff - graphView.scale,
|
||||||
graphView.xoff + graphView.scale * aspect,
|
x_max: graphView.xoff + graphView.scale * aspect,
|
||||||
graphView.yoff + graphView.scale
|
y_max: graphView.yoff + graphView.scale,
|
||||||
);
|
};
|
||||||
redraw();
|
}
|
||||||
|
|
||||||
|
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() {
|
function onResize() {
|
||||||
let width = window.innerWidth;
|
let width = window.innerWidth;
|
||||||
let height = window.innerHeight;
|
let height = window.innerHeight;
|
||||||
cxgraph.resize(width*graphView.res_mult, height*graphView.res_mult);
|
cxgraph.resize(width*graphView.res_mult, height*graphView.res_mult);
|
||||||
grid_canvas.width = width;
|
|
||||||
grid_canvas.height = height;
|
|
||||||
canvas.style.width = "100vw";
|
canvas.style.width = "100vw";
|
||||||
canvas.style.height = "100vh";
|
canvas.style.height = "100vh";
|
||||||
onViewChange();
|
onViewChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mouseX = 0.0;
|
|
||||||
let mouseY = 0.0;
|
|
||||||
let mousePressed = false;
|
|
||||||
|
|
||||||
function onWheel(e) {
|
function onWheel(e) {
|
||||||
graphView.scale *= Math.exp(e.deltaY * 0.0007);
|
graphView.scale *= Math.exp(e.deltaY * 0.0007);
|
||||||
onViewChange();
|
onViewChange();
|
||||||
|
@ -53,6 +109,9 @@ function onMouseDown(e) {
|
||||||
|
|
||||||
function onMouseUp(e) {
|
function onMouseUp(e) {
|
||||||
mousePressed = false;
|
mousePressed = false;
|
||||||
|
for(let point of graphPoints) {
|
||||||
|
point.mousePressed = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseMove(e) {
|
function onMouseMove(e) {
|
||||||
|
@ -64,13 +123,27 @@ function onMouseMove(e) {
|
||||||
graphView.xoff -= 2.0 * graphView.scale * dX / window.innerHeight;
|
graphView.xoff -= 2.0 * graphView.scale * dX / window.innerHeight;
|
||||||
graphView.yoff += 2.0 * graphView.scale * dY / window.innerHeight;
|
graphView.yoff += 2.0 * graphView.scale * dY / window.innerHeight;
|
||||||
onViewChange();
|
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() {
|
function onGraph() {
|
||||||
let src = document.getElementById("source_text").value;
|
let src = source_text.value;
|
||||||
try {
|
try {
|
||||||
cxgraph.load_shader(src);
|
cxgraph.load_shader(src, graphView.varNames);
|
||||||
div_error_msg.hidden = true;
|
div_error_msg.hidden = true;
|
||||||
redraw();
|
redraw();
|
||||||
} catch(e) {
|
} 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_graph.addEventListener("click", onGraph);
|
||||||
|
button_redraw.addEventListener("click", redraw);
|
||||||
|
|
||||||
let class_decor = document.getElementsByClassName("decor")
|
let charMap = {
|
||||||
for(let e of class_decor) {
|
"alpha": "\u03b1",
|
||||||
e.addEventListener("change", () => {
|
"beta": "\u03b2",
|
||||||
let decor = 0;
|
"gamma": "\u03b3",
|
||||||
for(let elem of class_decor) {
|
"delta": "\u03b4",
|
||||||
if(elem.checked) {
|
"epsilon": "\u03b5",
|
||||||
decor += parseInt(elem.getAttribute("data-value"));
|
"zeta": "\u03b6",
|
||||||
}
|
"eta": "\u03b7",
|
||||||
}
|
"theta": "\u03b8",
|
||||||
cxgraph.set_decorations(decor);
|
"iota": "\u03b9",
|
||||||
redraw();
|
"kappa": "\u03ba",
|
||||||
});
|
"lambda": "\u03bb",
|
||||||
e.checked = false;
|
"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");
|
source_text.addEventListener("input", () => {
|
||||||
for(let e of name_color_mode) {
|
source_text.value = source_text.value.replace(
|
||||||
e.addEventListener("change", () => {
|
specialChars,
|
||||||
let selected = document.querySelector("input[name=color_mode]:checked");
|
(_m, p) => charMap[p]
|
||||||
cxgraph.set_coloring(parseInt(selected.getAttribute("data-value")));
|
)
|
||||||
redraw();
|
});
|
||||||
});
|
|
||||||
e.checked = false;
|
|
||||||
}
|
|
||||||
name_color_mode[0].checked = true;
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Options
|
||||||
|
//
|
||||||
|
|
||||||
|
button_reset_view.addEventListener("click", () => {
|
||||||
|
graphView.xoff = 0;
|
||||||
|
graphView.yoff = 0;
|
||||||
|
graphView.scale = 3;
|
||||||
|
onViewChange();
|
||||||
|
})
|
||||||
|
|
||||||
range_shading.addEventListener("change", () => {
|
range_shading.addEventListener("change", () => {
|
||||||
let value = parseFloat(range_shading.value);
|
let value = parseFloat(range_shading.value);
|
||||||
cxgraph.set_shading_intensity(value);
|
cxgraph.set_shading_intensity(value);
|
||||||
redraw();
|
tryRedraw();
|
||||||
});
|
});
|
||||||
|
|
||||||
range_contour.addEventListener("change", () => {
|
range_contour.addEventListener("change", () => {
|
||||||
let value = parseFloat(range_contour.value);
|
let value = parseFloat(range_contour.value);
|
||||||
cxgraph.set_contour_intensity(value);
|
cxgraph.set_contour_intensity(value);
|
||||||
redraw();
|
tryRedraw();
|
||||||
});
|
});
|
||||||
|
|
||||||
range_resolution.addEventListener("change", () => {
|
range_resolution.addEventListener("change", () => {
|
||||||
|
@ -131,7 +248,220 @@ range_resolution.addEventListener("change", () => {
|
||||||
onResize();
|
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();
|
onResize();
|
||||||
onGraph();
|
onGraph();
|
||||||
|
|
||||||
// menu
|
// Debug
|
||||||
|
|
||||||
|
export function show_ast() {
|
||||||
|
console.info(cxgraph.show_shader_ast(source_text.value));
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::collections::HashMap;
|
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 winit::{window::WindowBuilder, event_loop::EventLoop, platform::web::WindowBuilderExtWebSys};
|
||||||
use wasm_bindgen::{prelude::*, JsValue};
|
use wasm_bindgen::{prelude::*, JsValue};
|
||||||
use web_sys::HtmlCanvasElement;
|
use web_sys::HtmlCanvasElement;
|
||||||
|
@ -48,15 +49,26 @@ pub async fn start() {
|
||||||
state.uniforms.shading_intensity = 0.3;
|
state.uniforms.shading_intensity = 0.3;
|
||||||
state.uniforms.contour_intensity = 0.0;
|
state.uniforms.contour_intensity = 0.0;
|
||||||
unsafe { WGPU_STATE = Some(state) };
|
unsafe { WGPU_STATE = Some(state) };
|
||||||
|
info!("Initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn load_shader(src: &str) -> Result<(), JsValue> {
|
pub fn load_shader(src: &str, var_names: Box<[JsValue]>) -> Result<(), JsValue> {
|
||||||
let wgsl = compile(src, &HashMap::new()).map_err(|e| e.to_string())?;
|
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));
|
with_state(|state| state.load_shaders(&wgsl));
|
||||||
Ok(())
|
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]
|
#[wasm_bindgen]
|
||||||
pub fn redraw() {
|
pub fn redraw() {
|
||||||
with_state(|state| state.redraw());
|
with_state(|state| state.redraw());
|
||||||
|
@ -95,3 +107,11 @@ pub fn set_coloring(value: u32) {
|
||||||
pub fn set_decorations(value: u32) {
|
pub fn set_decorations(value: u32) {
|
||||||
with_state(|state| state.uniforms.decorations = value);
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
* {
|
* {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body, html {
|
body, html {
|
||||||
|
@ -15,7 +16,7 @@ body, html {
|
||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas {
|
canvas, #overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
|
@ -27,34 +28,35 @@ canvas {
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#grid_canvas {
|
#overlay {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
#overlay_points {
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menus {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
top: 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;
|
margin: 10px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: #334;
|
background: #334;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
z-index: 10;
|
height: fit-content;
|
||||||
}
|
box-shadow: 0 0 5px 1px #00000088;
|
||||||
|
|
||||||
#source_text {
|
|
||||||
min-width: max-content;
|
|
||||||
width: 300px;
|
|
||||||
height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#div_error_msg {
|
|
||||||
color: #f9a;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.menu > details {
|
|
||||||
max-width: min-content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
details > *:not(summary) {
|
details > *:not(summary) {
|
||||||
|
@ -63,3 +65,44 @@ details > *:not(summary) {
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
border-left: 1px solid #fff;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -1,105 +1,69 @@
|
||||||
use std::{collections::HashMap, ops, f64::consts::{TAU, E}};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use num_complex::Complex64 as Complex;
|
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! {
|
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();
|
let mut m = HashMap::new();
|
||||||
m.insert("pos", ("c_pos", Func::One(std::convert::identity)));
|
m.insert("pos", ("c_pos", 1));
|
||||||
m.insert("neg", ("c_neg", Func::One(neg)));
|
m.insert("neg", ("c_neg", 1));
|
||||||
m.insert("recip", ("c_recip", Func::One(recip)));
|
m.insert("recip", ("c_recip", 1));
|
||||||
m.insert("conj", ("c_conj", Func::One(conj)));
|
m.insert("conj", ("c_conj", 1));
|
||||||
|
|
||||||
m.insert("re", ("c_re", Func::One(re)));
|
m.insert("re", ("c_re", 1));
|
||||||
m.insert("im", ("c_im", Func::One(im)));
|
m.insert("im", ("c_im", 1));
|
||||||
m.insert("abs_sq", ("c_abs_sq", Func::One(abs_sq)));
|
m.insert("signre", ("c_signre", 1));
|
||||||
m.insert("abs", ("c_abs", Func::One(abs)));
|
m.insert("signim", ("c_signim", 1));
|
||||||
m.insert("arg", ("c_arg", Func::One(arg)));
|
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("add", ("c_add", 2));
|
||||||
m.insert("sub", ("c_sub", Func::Two(<Complex as ops::Sub>::sub)));
|
m.insert("sub", ("c_sub", 2));
|
||||||
m.insert("mul", ("c_mul", Func::Two(<Complex as ops::Mul>::mul)));
|
m.insert("mul", ("c_mul", 2));
|
||||||
m.insert("div", ("c_div", Func::Two(<Complex as ops::Div>::div)));
|
m.insert("div", ("c_div", 2));
|
||||||
m.insert("pow", ("c_pow", Func::Two(Complex::powc)));
|
m.insert("pow", ("c_pow", 2));
|
||||||
|
|
||||||
m.insert("exp", ("c_exp", Func::One(Complex::exp)));
|
m.insert("exp", ("c_exp", 1));
|
||||||
m.insert("log", ("c_log", Func::One(Complex::ln)));
|
m.insert("log", ("c_log", 1));
|
||||||
m.insert("sqrt", ("c_sqrt", Func::One(Complex::sqrt)));
|
m.insert("logbr", ("c_logbr", 2));
|
||||||
|
m.insert("sqrt", ("c_sqrt", 1));
|
||||||
|
|
||||||
m.insert("sin", ("c_sin", Func::One(Complex::sin)));
|
m.insert("sin", ("c_sin", 1));
|
||||||
m.insert("cos", ("c_cos", Func::One(Complex::cos)));
|
m.insert("cos", ("c_cos", 1));
|
||||||
m.insert("tan", ("c_tan", Func::One(Complex::tan)));
|
m.insert("tan", ("c_tan", 1));
|
||||||
m.insert("sinh", ("c_sinh", Func::One(Complex::sinh)));
|
m.insert("sinh", ("c_sinh", 1));
|
||||||
m.insert("cosh", ("c_cosh", Func::One(Complex::cosh)));
|
m.insert("cosh", ("c_cosh", 1));
|
||||||
m.insert("tanh", ("c_tanh", Func::One(Complex::tanh)));
|
m.insert("tanh", ("c_tanh", 1));
|
||||||
|
|
||||||
m.insert("asin", ("c_asin", Func::One(Complex::asin)));
|
m.insert("asin", ("c_asin", 1));
|
||||||
m.insert("acos", ("c_acos", Func::One(Complex::acos)));
|
m.insert("acos", ("c_acos", 1));
|
||||||
m.insert("atan", ("c_atan", Func::One(Complex::atan)));
|
m.insert("atan", ("c_atan", 1));
|
||||||
m.insert("asinh", ("c_asinh", Func::One(Complex::asinh)));
|
m.insert("asinh", ("c_asinh", 1));
|
||||||
m.insert("acosh", ("c_acosh", Func::One(Complex::acosh)));
|
m.insert("acosh", ("c_acosh", 1));
|
||||||
m.insert("atanh", ("c_atanh", Func::One(Complex::atanh)));
|
m.insert("atanh", ("c_atanh", 1));
|
||||||
|
|
||||||
m.insert("gamma", ("c_gamma", Func::One(gamma)));
|
m.insert("gamma", ("c_gamma", 1));
|
||||||
m.insert("\u{0393}", ("c_gamma", Func::One(gamma)));
|
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
|
m
|
||||||
};
|
};
|
||||||
|
|
||||||
pub static BUILTIN_CONSTS: HashMap<&'static str, (&'static str, Complex)> = {
|
pub static BUILTIN_CONSTS: HashMap<&'static str, (&'static str, Complex)> = {
|
||||||
let mut m = HashMap::new();
|
let mut m = HashMap::new();
|
||||||
m.insert("tau", ("C_TAU", Complex::new(std::f64::consts::TAU, 0.0)));
|
m.insert("i", ("C_I", Complex::new(0.0, 1.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("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("i", ("C_I", Complex::new(0.0, 1.0)));
|
m.insert("\u{03C4}", ("C_TAU", Complex::new(std::f64::consts::TAU, 0.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
|
m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{collections::{HashSet, HashMap}, fmt};
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CompileError(String);
|
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) {
|
fn format_char(buf: &mut String, c: char) {
|
||||||
match c {
|
match c {
|
||||||
'a'..='z' | 'A'..='Z' | '0'..='9' => buf.push(c),
|
'_' => buf.push_str("u_"),
|
||||||
'_' => buf.push_str("__"),
|
'\'' => buf.push_str("p_"),
|
||||||
'\'' => buf.push_str("_p"),
|
c => buf.push(c),
|
||||||
'\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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +50,7 @@ fn format_tmp(idx: usize) -> String { format!("tmp_{}", idx) }
|
||||||
|
|
||||||
pub struct Compiler<'w, 'i, W: fmt::Write> {
|
pub struct Compiler<'w, 'i, W: fmt::Write> {
|
||||||
buf: &'w mut W,
|
buf: &'w mut W,
|
||||||
vars: &'w HashMap<String, Variable>,
|
vars: &'w HashMap<String, usize>,
|
||||||
global_funcs: HashMap<&'i str, usize>,
|
global_funcs: HashMap<&'i str, usize>,
|
||||||
global_consts: HashSet<&'i str>,
|
global_consts: HashSet<&'i str>,
|
||||||
}
|
}
|
||||||
|
@ -110,7 +77,7 @@ impl<'i> LocalState<'i> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
|
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 {
|
Self {
|
||||||
buf,
|
buf,
|
||||||
vars,
|
vars,
|
||||||
|
@ -167,8 +134,12 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_plot_defined(&self) -> Result<(), CompileError> {
|
pub fn ensure_plot_defined(&self) -> Result<(), CompileError> {
|
||||||
if self.global_funcs.contains_key("plot") {
|
if let Some(n) = self.global_funcs.get("plot") {
|
||||||
Ok(())
|
if *n == 1 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Plot function has wrong number of arguments".to_owned().into())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err("No plot function defined".to_owned().into())
|
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)?;
|
let v = self.compile_expr(local, init)?;
|
||||||
writeln!(self.buf, "var {itervar_fmt} = {v};")?;
|
writeln!(self.buf, "var {itervar_fmt} = {v};")?;
|
||||||
let ivar = local.next_tmp();
|
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();
|
let mut loop_local = local.clone();
|
||||||
loop_local.local_vars.insert(itervar);
|
loop_local.local_vars.insert(itervar);
|
||||||
let mut last = String::new();
|
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> {
|
fn resolve_func(&self, name: &str) -> Result<(String, usize), CompileError> {
|
||||||
if let Some(argc) = self.global_funcs.get(name) {
|
if let Some(argc) = self.global_funcs.get(name) {
|
||||||
Ok((format_func(name), *argc))
|
Ok((format_func(name), *argc))
|
||||||
} else if let Some((var, f)) = BUILTIN_FUNCS.with(|c| c.get(name).copied()) {
|
} else if let Some((var, argc)) = BUILTIN_FUNCS.with(|c| c.get(name).copied()) {
|
||||||
Ok(((*var).to_owned(), f.argc()))
|
Ok(((*var).to_owned(), argc))
|
||||||
} else {
|
} else {
|
||||||
Err(format!("use of undeclared function {name}").into())
|
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))
|
Ok(format_local(name))
|
||||||
} else if self.global_consts.contains(name) {
|
} else if self.global_consts.contains(name) {
|
||||||
Ok(format_const(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)) {
|
} else if let Some(var) = BUILTIN_CONSTS.with(|c| Some(c.get(name)?.0)) {
|
||||||
Ok(var.to_owned())
|
Ok(var.to_owned())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lalrpop_util::lalrpop_mod;
|
||||||
|
|
||||||
use crate::language::token::Lexer;
|
use crate::language::token::Lexer;
|
||||||
|
|
||||||
use self::compiler::Compiler;
|
use self::{compiler::Compiler, ast::display_def};
|
||||||
|
|
||||||
mod token;
|
mod token;
|
||||||
mod ast;
|
mod ast;
|
||||||
|
@ -13,12 +13,7 @@ mod builtins;
|
||||||
|
|
||||||
lalrpop_mod!(pub syntax, "/language/syntax.rs");
|
lalrpop_mod!(pub syntax, "/language/syntax.rs");
|
||||||
|
|
||||||
pub enum Variable {
|
pub fn compile(src: &str, vars: &HashMap<String, usize>) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
Slider(usize),
|
|
||||||
Point(usize, usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compile(src: &str, vars: &HashMap<String, Variable>) -> Result<String, Box<dyn std::error::Error>> {
|
|
||||||
let lexer = Lexer::new(src);
|
let lexer = Lexer::new(src);
|
||||||
let result = syntax::ProgramParser::new()
|
let result = syntax::ProgramParser::new()
|
||||||
.parse(src, lexer)
|
.parse(src, lexer)
|
||||||
|
@ -31,3 +26,15 @@ pub fn compile(src: &str, vars: &HashMap<String, Variable>) -> Result<String, Bo
|
||||||
cmp.ensure_plot_defined()?;
|
cmp.ensure_plot_defined()?;
|
||||||
Ok(wgsl)
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -38,10 +38,14 @@ fn correct_mod2(x: vec2f, y: vec2f) -> vec2f {
|
||||||
const TAU = 6.283185307179586;
|
const TAU = 6.283185307179586;
|
||||||
const E = 2.718281828459045;
|
const E = 2.718281828459045;
|
||||||
const RECIP_SQRT2 = 0.7071067811865475;
|
const RECIP_SQRT2 = 0.7071067811865475;
|
||||||
|
const LOG_TAU = 1.8378770664093453;
|
||||||
|
const LOG_2 = 0.6931471805599453;
|
||||||
|
|
||||||
const C_TAU = vec2f(TAU, 0.0);
|
const C_TAU = vec2f(TAU, 0.0);
|
||||||
const C_E = vec2f(E, 0.0);
|
const C_E = vec2f(E, 0.0);
|
||||||
const C_I = vec2f(0.0, 1.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 //
|
// complex functions //
|
||||||
|
@ -55,6 +59,14 @@ fn c_im(z: vec2f) -> vec2f {
|
||||||
return vec2(z.y, 0.0);
|
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 {
|
fn c_conj(z: vec2f) -> vec2f {
|
||||||
return z * vec2(1.0, -1.0);
|
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);
|
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 {
|
fn c_add(u: vec2f, v: vec2f) -> vec2f {
|
||||||
return u + v;
|
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));
|
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 {
|
fn c_pow(u: vec2f, v: vec2f) -> vec2f {
|
||||||
return c_exp(c_mul(c_log(u), v));
|
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));
|
return 0.5 * c_log(c_div(u, v));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gamma //
|
||||||
|
|
||||||
fn c_gamma(z: vec2f) -> vec2f {
|
fn c_gamma(z: vec2f) -> vec2f {
|
||||||
let reflect = z.x < 0.5;
|
let reflect = z.x < 0.5;
|
||||||
var zp = z;
|
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))));
|
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 //
|
// rendering //
|
||||||
/////////////////
|
/////////////////
|
||||||
|
@ -215,8 +295,12 @@ fn hsv2rgb(c: vec3f) -> vec3f {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shademap(r: f32) -> f32 {
|
fn shademap(r: f32) -> f32 {
|
||||||
let i = uniforms.shading_intensity * uniforms.shading_intensity * uniforms.shading_intensity;
|
if uniforms.shading_intensity == 0.0 {
|
||||||
return r*inverseSqrt(r * r + 0.0625 * i);
|
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 {
|
fn coloring_standard(z: vec2f) -> vec3f {
|
||||||
|
@ -224,19 +308,33 @@ fn coloring_standard(z: vec2f) -> vec3f {
|
||||||
return vec3f(0.5, 0.5, 0.5);
|
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 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);
|
return hsv2rgb(hsv);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn coloring_uniform(z: vec2f) -> vec3f {
|
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 {
|
if z.x != z.x || z.y != z.y {
|
||||||
return vec3f(0.5, 0.5, 0.5);
|
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 arg = atan2(z.y, z.x);
|
||||||
let mag = length(z);
|
|
||||||
|
|
||||||
let r = cos(arg - 0.0*TAU/3.0)*0.5 + 0.5;
|
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;
|
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);
|
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);
|
let final_col = mix(col, vec3f(contours * 0.5 + 0.5), uniforms.contour_intensity);
|
||||||
|
|
||||||
return vec4f(pow(final_col, vec3(1.68)), 1.0);
|
return vec4f(pow(final_col, vec3(1.68)), 1.0);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::{num::NonZeroU64, io::Cursor};
|
use std::{num::NonZeroU64, io::Cursor};
|
||||||
|
|
||||||
use log::info;
|
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -233,8 +232,6 @@ impl WgpuState {
|
||||||
rpass.draw(1..4, 0..1);
|
rpass.draw(1..4, 0..1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!("Redrawing");
|
|
||||||
info!("Uniforms: {:?}", self.uniforms);
|
|
||||||
let mut cursor = Cursor::new([0; UNIFORM_SIZE]);
|
let mut cursor = Cursor::new([0; UNIFORM_SIZE]);
|
||||||
self.uniforms.encode(&mut cursor).unwrap();
|
self.uniforms.encode(&mut cursor).unwrap();
|
||||||
self.queue.write_buffer(&self.uniform_buffer, 0, &cursor.into_inner());
|
self.queue.write_buffer(&self.uniform_buffer, 0, &cursor.into_inner());
|
||||||
|
|
Loading…
Reference in a new issue