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>
|
||||
<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 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 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)
|
||||
|
|
|
@ -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();
|
||||
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", () => {
|
||||
source_text.value = source_text.value.replace(
|
||||
specialChars,
|
||||
(_m, p) => charMap[p]
|
||||
)
|
||||
});
|
||||
e.checked = false;
|
||||
}
|
||||
|
||||
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;
|
||||
//
|
||||
// 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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,28 +295,46 @@ 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 {
|
||||
if z.x != z.x || z.y != z.y {
|
||||
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);
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in a new issue