made changes

This commit is contained in:
TriMill 2023-06-15 00:27:09 -04:00
parent 36e2f03894
commit 4fcd317390
18 changed files with 528 additions and 230 deletions

8
README.md Normal file
View file

@ -0,0 +1,8 @@
# cxgraph
cxgraph is a complex function graphing tool built around WebGPU available
[on the web](https://cx.trimill.xyz/) or (slightly) for desktop.
## documentation
- [language](docs/language.md)
- [web interface](docs/web.md)

View file

@ -8,8 +8,9 @@
<div class="canvas-container"> <div class="canvas-container">
<canvas id="canvas"></canvas> <canvas id="canvas"></canvas>
<svg id="overlay"> <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="svg_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" /> <line id="svg_axis_y" x1="0" y1="0" x2="0" y2="0" stroke="#0006" stroke-width="1.5" visibility="hidden" />
<circle id="svg_unitcircle" cx="0" cy="0" r="0" stroke="#0006" fill="none" stroke-width="1.5" visibility="hidden" />
<g id="svg_point_template" visibility="hidden"> <g id="svg_point_template" visibility="hidden">
<circle cx="0" cy="0" r="15" stroke="none" fill="#6664" /> <circle cx="0" cy="0" r="15" stroke="none" fill="#6664" />
<circle cx="0" cy="0" r="5" stroke="none" fill="#6666" /> <circle cx="0" cy="0" r="5" stroke="none" fill="#6666" />
@ -39,7 +40,7 @@
<summary>Options</summary> <summary>Options</summary>
<div> <div>
<input type="button" id="button_reset_view" value="Reset view"> <input type="button" id="button_reset_view" value="Reset view">
<input type="button" id="button_help" value="Help" onclick="window.open('docs.html')"> <input type="button" id="button_help" value="Help" onclick="window.open('https://g.trimill.xyz/trimill/cxgraph')">
</div> </div>
<div> <div>
<div><label for="range_resolution">Resolution</label></div> <div><label for="range_resolution">Resolution</label></div>
@ -91,9 +92,13 @@
<div> <div>
Overlay Overlay
<div> <div>
<input type="checkbox" id="overlay_axes" checked> <input type="checkbox" id="overlay_axes">
<label for="overlay_axes">Draw axes</label> <label for="overlay_axes">Draw axes</label>
</div> </div>
<div>
<input type="checkbox" id="overlay_unitcircle">
<label for="overlay_unitcircle">Draw unit circle</label>
</div>
</div> </div>
</details> </details>

View file

@ -70,15 +70,25 @@ function onViewChange() {
let bounds = calcBounds(); let bounds = calcBounds();
cxgraph.set_bounds(bounds.x_min, bounds.y_min, bounds.x_max, bounds.y_max); cxgraph.set_bounds(bounds.x_min, bounds.y_min, bounds.x_max, bounds.y_max);
let origin = cxToScreen({ re: 0, im: 0 }); let origin = cxToScreen({ re: 0, im: 0 });
let one = cxToScreen({ re: 1, im: 0 });
overlay_axis_x.setAttribute("x1", 0) if(svg_axis_x.visibility != "hidden") {
overlay_axis_x.setAttribute("x2", dim.w); svg_axis_x.setAttribute("x1", 0)
overlay_axis_x.setAttribute("y1", origin.y); svg_axis_x.setAttribute("x2", dim.w);
overlay_axis_x.setAttribute("y2", origin.y); svg_axis_x.setAttribute("y1", origin.y);
overlay_axis_y.setAttribute("x1", origin.x); svg_axis_x.setAttribute("y2", origin.y);
overlay_axis_y.setAttribute("x2", origin.x); }
overlay_axis_y.setAttribute("y1", 0); if(svg_axis_y.visibility != "hidden") {
overlay_axis_y.setAttribute("y2", dim.h); svg_axis_y.setAttribute("x1", origin.x);
svg_axis_y.setAttribute("x2", origin.x);
svg_axis_y.setAttribute("y1", 0);
svg_axis_y.setAttribute("y2", dim.h);
}
if(svg_unitcircle.visibility != "hidden") {
svg_unitcircle.setAttribute("cx", origin.x);
svg_unitcircle.setAttribute("cy", origin.y);
svg_unitcircle.setAttribute("r", one.x - origin.x);
}
for(let point of graphPoints) { for(let point of graphPoints) {
point.onViewChange(); point.onViewChange();
@ -148,7 +158,7 @@ function onGraph() {
redraw(); redraw();
} catch(e) { } catch(e) {
console.log(e); console.log(e);
div_error_msg.textContent = e.toString(); div_error_msg.textContent = e.toString().replace("\n", "\r\n");
div_error_msg.hidden = false; div_error_msg.hidden = false;
} }
} }
@ -214,10 +224,16 @@ let specialChars = new RegExp(
console.log(specialChars); console.log(specialChars);
source_text.addEventListener("input", () => { source_text.addEventListener("input", () => {
let e = source_text.selectionEnd;
let amnt = 0;
source_text.value = source_text.value.replace( source_text.value = source_text.value.replace(
specialChars, specialChars,
(_m, p) => charMap[p] (m, p) => {
) amnt += m.length - charMap[p].length;
return charMap[p];
}
);
source_text.selectionEnd = e - amnt;
}); });
// //
@ -276,8 +292,13 @@ nameColorMode[0].checked = true;
overlay_axes.addEventListener("change", () => { overlay_axes.addEventListener("change", () => {
let vis = overlay_axes.checked ? "visible" : "hidden"; let vis = overlay_axes.checked ? "visible" : "hidden";
overlay_axis_x.setAttribute("visibility", vis); svg_axis_x.setAttribute("visibility", vis);
overlay_axis_y.setAttribute("visibility", vis); svg_axis_y.setAttribute("visibility", vis);
});
overlay_unitcircle.addEventListener("change", () => {
let vis = overlay_unitcircle.checked ? "visible" : "hidden";
svg_unitcircle.setAttribute("visibility", vis);
}); });
// //

View file

@ -71,13 +71,14 @@ summary {
} }
#source_text { #source_text {
width: 300px; width: 400px;
height: 500px; height: 150px;
font-size: 15px; font-size: 15px;
} }
#div_error_msg { #div_error_msg {
color: #f9a; color: #f9a;
white-space: pre-line;
} }
input { input {

165
docs/language.md Normal file
View file

@ -0,0 +1,165 @@
# cxgraph language
cxgraph uses a custom expression language that is compiled to WGSL.
## names
names must begin with any alphabetic character (lowercase or capital letters, Greek letters,
etc.) and may contain alphanumeric chararcters as well as underscores (`_`) and apostrophes (`'`).
the words `sum`, `prod`, and `iter` may not be used for names. names may refer to either
functions or variables.
examples of names include:
```
a A aaa ω z_3 __5__ f' Кαl'けx焼__검
```
names may either be **built-in**, **global**, or **local**. global or local names may shadow
built-in names, and local names may shadow global ones.
## declarations
a **function declaration** declares a new function. functions may have zero or more arguments.
```
f(x) = 3
```
a **constant declaration** declares a new constant.
```
n = 5
```
declarations are separated by newlines. declarations may only reference functions and constants in
declarations that precede them. the name used in a declaration (`f` and `n` in the above examples)
is in the global scope
## built-ins
arithmetic functions:
| name | description |
|---------------|-------------------------------------------------------|
| `pos(z)` | equivalent to unary `+` |
| `neg(z)` | equivalent to unary `-` |
| `conj(z)` | complex conjugate, equivalent to unary `*` |
| `re(z)` | real part |
| `im(z)` | imaginary part |
| `abs(z)` | absolute value (distance from `0`) |
| `abs_sq(z)` | square of absolute value |
| `arg(z)` | argument (angle about `0`) in the range `(-τ/2, τ/2]` |
| `argbr(z,br)` | argument in the range `(-τ/2, τ/2] + br` |
| `add(z,w)` | equivalent to `z + w` |
| `sub(z,w)` | equivalent to `z - w` |
| `mul(z,w)` | equivalent to `z * w` |
| `div(z,w)` | equivalent to `z / w` |
| `recip(z)` | reciprocal, equivalent to `1/z` |
power/exponential functions:
| name | description |
|---------------|-------------------------------------------|
| `exp(z)` | exponential function, equivalent to `e^z` |
| `log(z)` | logarithm base `e` |
| `logbr(z,br)` | logarithm base `e` with specified branch |
| `pow(z)` | power, equivalent to `^` |
| `powbr(z,br)` | `pow` with specified branch |
| `sqrt(z)` | square root, equivalent to `z^0.5` |
| `sqrtbr(z,br)`| square root with specified branch |
| `cbrt(z)` | cube root, equivalent to `z^0.5` |
| `cbrtbr(z,br)`| cube root with specified branch |
trigonometric functions:
| name | description |
|------------|-------------------------------------|
| `sin(z)` | sine function |
| `cos(z)` | cosine function |
| `tan(z)` | tangent function |
| `sinh(z)` | hyperbolic sine function |
| `cosh(z)` | hyperbolic cosine function |
| `tanh(z)` | hyperbolic tangent function |
| `asin(z)` | inverse sine function |
| `acos(z)` | inverse cosine function |
| `atan(z)` | inverse tangent function |
| `asinh(z)` | inverse hyperbolic sine function |
| `acosh(z)` | inverse hyperbolic cosine function |
| `atanh(z)` | inverse hyperbolic tangent function |
special functions:
| function | description |
|--------------------------|--------------------------------------------------------------------|
| `gamma(z)`, `Γ(z)` | [gamma function](https://en.wikipedia.org/wiki/Gamma_function) |
| `invgamma(z)`, `invΓ(z)` | reciprocal of the gamma function |
| `loggamma(z)`, `logΓ(z)` | logarithm of the gamma function |
| `digamma(z)`, `ψ(z)` | [digamma function](https://en.wikipedia.org/wiki/Digamma_function) |
logic functions:
| function | description |
|-----------------|----------------------------------------------------------------------------|
| `signre(z)` | sign of real part (1 if `re(z) > 0`, -1 if `re(z) < 0`, 0 if `re(z) == 0`) |
| `signim(z)` | sign of imaginary part |
| `ifgt(p,q,z,w)` | evaluates to `z` if `re(p) > re(q)`, otherwise `w` |
| `iflt(p,q,z,w)` | evaluates to `z` if `re(p) < re(q)`, otherwise `w` |
| `ifge(p,q,z,w)` | evaluates to `z` if `re(p) ≥ re(q)`, otherwise `w` |
| `ifle(p,q,z,w)` | evaluates to `z` if `re(p) ≤ re(q)`, otherwise `w` |
| `ifeq(p,q,z,w)` | evaluates to `z` if `re(p) = re(q)`, otherwise `w` |
| `ifne(p,q,z,w)` | evaluates to `z` if `re(p) ≠ re(q)`, otherwise `w` |
| `ifnan(p,z,w)` | evaluates to `z` if `p` is `NaN`, otherwise `w` |
constants:
| name | description |
|----------------|--------------------------------------------------------------------------------------------------------|
| `i` | the imaginary constant, equal to `sqrt(-1)` |
| `e` | the [exponential constant](https://en.wikipedia.org/wiki/E_(mathematical_constant)), equal to `exp(1)` |
| `tau`, `τ` | the [circle constant](https://tauday.com/tau-manifesto) |
| `emgamma`, `γ` | the [Euler-Mascheroni](https://en.wikipedia.org/wiki/Euler%27s_constant) constant, equal to `-ψ(1)` |
| `phi`, `φ` | the [golden ratio](https://en.wikipedia.org/wiki/Golden_ratio), equal to `1/2 + sqrt(5)/2` |
## ebnf grammar
```
Program := Definitions
Definitions := NEWLINE* (Definition NEWLINE+)* Definition?
Definition := NAME "(" (NAME ",") NAME? ")" "=" Exprs
| NAME "=" Exprs
Exprs := (Expr ",")* Expr ","?
Expr := Store
Store := Store "->" NAME | Sum
Sum := Sum "+" Product
| Sum "-" Product
| Product
Product := Product "*" Unary
| Product "/" Unary
| Unary
Unary := "+" Unary
| "-" Unary
| "*" Unary
| Juxtapose Power
| Power
Juxtapose := Juxtapose PreJuxtapose | PreJuxtapose
Power := FnCall "^" Unary | FnCall
FnCall := NAME "(" Exprs ")" | Item
PreJuxtapose := Number | "(" <Expr> ")"
Item := Number
| NAME
| "(" Expr ")"
| "{" Exprs "}"
| "sum" "(" NAME ":" INT "," INT ")" "{" Exprs "}"
| "prod" "(" NAME ":" INT "," INT ")" "{" Exprs "}"
| "iter" "(" INT "," NAME ":" Expr ")" "{" Exprs "}"
Number = FLOAT | INT
```

0
docs/web.md Normal file
View file

View file

@ -14,6 +14,7 @@ pub enum UnaryOp {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum ExpressionType<'a> { pub enum ExpressionType<'a> {
Block,
Number(Complex), Number(Complex),
Name(&'a str), Name(&'a str),
Binary(BinaryOp), Binary(BinaryOp),
@ -32,6 +33,10 @@ pub struct Expression<'a> {
} }
impl<'a> Expression<'a> { impl<'a> Expression<'a> {
pub fn new_block(exs: Vec<Expression<'a>>) -> Self {
Self { ty: ExpressionType::Block, children: exs }
}
pub fn new_number(x: f64) -> Self { pub fn new_number(x: f64) -> Self {
Self { ty: ExpressionType::Number(Complex::new(x, 0.0)), children: Vec::with_capacity(0) } Self { ty: ExpressionType::Number(Complex::new(x, 0.0)), children: Vec::with_capacity(0) }
} }
@ -87,6 +92,7 @@ pub enum Definition<'a> {
fn display_expr(w: &mut impl fmt::Write, expr: &Expression, depth: usize) -> fmt::Result { fn display_expr(w: &mut impl fmt::Write, expr: &Expression, depth: usize) -> fmt::Result {
let indent = depth*2; let indent = depth*2;
match expr.ty { match expr.ty {
ExpressionType::Block => write!(w, "{:indent$}BLOCK", "", indent=indent)?,
ExpressionType::Number(n) => write!(w, "{:indent$}NUMBER {n:?}", "", indent=indent)?, ExpressionType::Number(n) => write!(w, "{:indent$}NUMBER {n:?}", "", indent=indent)?,
ExpressionType::Name(n) => write!(w, "{:indent$}NAME {n}", "", indent=indent)?, ExpressionType::Name(n) => write!(w, "{:indent$}NAME {n}", "", indent=indent)?,
ExpressionType::Binary(op) => write!(w, "{:indent$}OP {op:?}", "", indent=indent)?, ExpressionType::Binary(op) => write!(w, "{:indent$}OP {op:?}", "", indent=indent)?,

View file

@ -10,6 +10,14 @@ thread_local! {
m.insert("recip", ("c_recip", 1)); m.insert("recip", ("c_recip", 1));
m.insert("conj", ("c_conj", 1)); m.insert("conj", ("c_conj", 1));
m.insert("ifgt", ("c_ifgt", 4));
m.insert("iflt", ("c_iflt", 4));
m.insert("ifge", ("c_ifge", 4));
m.insert("ifle", ("c_ifle", 4));
m.insert("ifeq", ("c_ifeq", 4));
m.insert("ifne", ("c_ifne", 4));
m.insert("ifnan", ("c_ifnan", 3));
m.insert("re", ("c_re", 1)); m.insert("re", ("c_re", 1));
m.insert("im", ("c_im", 1)); m.insert("im", ("c_im", 1));
m.insert("signre", ("c_signre", 1)); m.insert("signre", ("c_signre", 1));
@ -24,11 +32,15 @@ thread_local! {
m.insert("mul", ("c_mul", 2)); m.insert("mul", ("c_mul", 2));
m.insert("div", ("c_div", 2)); m.insert("div", ("c_div", 2));
m.insert("pow", ("c_pow", 2)); m.insert("pow", ("c_pow", 2));
m.insert("powbr", ("c_powbr", 3));
m.insert("exp", ("c_exp", 1)); m.insert("exp", ("c_exp", 1));
m.insert("log", ("c_log", 1)); m.insert("log", ("c_log", 1));
m.insert("logbr", ("c_logbr", 2)); m.insert("logbr", ("c_logbr", 2));
m.insert("sqrt", ("c_sqrt", 1)); m.insert("sqrt", ("c_sqrt", 1));
m.insert("sqrtbr", ("c_sqrtbr", 2));
m.insert("cbrt", ("c_cbrt", 1));
m.insert("cbrtbr", ("c_cbrtbr", 2));
m.insert("sin", ("c_sin", 1)); m.insert("sin", ("c_sin", 1));
m.insert("cos", ("c_cos", 1)); m.insert("cos", ("c_cos", 1));
@ -46,6 +58,8 @@ thread_local! {
m.insert("gamma", ("c_gamma", 1)); m.insert("gamma", ("c_gamma", 1));
m.insert("\u{0393}", ("c_gamma", 1)); m.insert("\u{0393}", ("c_gamma", 1));
m.insert("invgamma", ("c_invgamma", 1));
m.insert("inv\u{0393}", ("c_invgamma", 1));
m.insert("loggamma", ("c_loggamma", 1)); m.insert("loggamma", ("c_loggamma", 1));
m.insert("log\u{0393}", ("c_loggamma", 1)); m.insert("log\u{0393}", ("c_loggamma", 1));
m.insert("digamma", ("c_digamma", 1)); m.insert("digamma", ("c_digamma", 1));

View file

@ -148,6 +148,19 @@ impl<'w, 'i, W: fmt::Write> Compiler<'w, 'i, W> {
fn compile_expr(&mut self, local: &mut LocalState<'i>, expr: &Expression<'i>) fn compile_expr(&mut self, local: &mut LocalState<'i>, expr: &Expression<'i>)
-> Result<String, CompileError> { -> Result<String, CompileError> {
match expr.ty { match expr.ty {
ExpressionType::Block => {
let tmp = local.next_tmp();
writeln!(self.buf, "var {tmp}: vec2f;")?;
writeln!(self.buf, "{{")?;
let mut block_local = local.clone();
let mut last = String::new();
for child in &expr.children {
last = self.compile_expr(&mut block_local, child)?;
}
writeln!(self.buf, "{tmp} = {last};")?;
writeln!(self.buf, "}}")?;
Ok(tmp)
}
ExpressionType::Name(v) => self.resolve_var(local, v), ExpressionType::Name(v) => self.resolve_var(local, v),
ExpressionType::Store(var) => { ExpressionType::Store(var) => {
let a = self.compile_expr(local, &expr.children[0])?; let a = self.compile_expr(local, &expr.children[0])?;

View file

@ -109,6 +109,7 @@ Item: Expression<'input> = {
Number, Number,
<n:Name> => Expression::new_name(n), <n:Name> => Expression::new_name(n),
"(" <Expr> ")", "(" <Expr> ")",
"{" <exs:Exprs> "}" => Expression::new_block(exs),
"sum" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" "{" <exs:Exprs> "}" "sum" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" "{" <exs:Exprs> "}"
=> Expression::new_sum(name, min, max, exs), => Expression::new_sum(name, min, max, exs),
"prod" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" "{" <exs:Exprs> "}" "prod" "(" <name:Name> ":" <min:Int> "," <max:Int> ")" "{" <exs:Exprs> "}"

View file

@ -25,8 +25,8 @@ impl<'i> fmt::Display for Token<'i> {
Token::Iter => f.write_str("iter"), Token::Iter => f.write_str("iter"),
Token::LParen => f.write_str("("), Token::LParen => f.write_str("("),
Token::RParen => f.write_str(")"), Token::RParen => f.write_str(")"),
Token::LBrace => f.write_str("{{"), Token::LBrace => f.write_str("{"),
Token::RBrace => f.write_str("}}"), Token::RBrace => f.write_str("}"),
Token::Plus => f.write_str("+"), Token::Plus => f.write_str("+"),
Token::Minus => f.write_str("-"), Token::Minus => f.write_str("-"),
Token::Star => f.write_str("*"), Token::Star => f.write_str("*"),
@ -61,6 +61,7 @@ pub type Spanned<T, L, E> = Result<(L, T, L), E>;
pub struct Lexer<'i> { pub struct Lexer<'i> {
src: &'i str, src: &'i str,
chars: Peekable<CharIndices<'i>>, chars: Peekable<CharIndices<'i>>,
bracket_depth: usize,
} }
fn is_ident_begin(c: char) -> bool { fn is_ident_begin(c: char) -> bool {
@ -73,7 +74,11 @@ fn is_ident_middle(c: char) -> bool {
impl<'i> Lexer<'i> { impl<'i> Lexer<'i> {
pub fn new(src: &'i str) -> Self { pub fn new(src: &'i str) -> Self {
Self { src, chars: src.char_indices().peekable() } Self {
src,
chars: src.char_indices().peekable(),
bracket_depth: 0,
}
} }
fn next_number(&mut self, i: usize, mut has_dot: bool) -> Spanned<Token<'i>, usize, LexerError> { fn next_number(&mut self, i: usize, mut has_dot: bool) -> Spanned<Token<'i>, usize, LexerError> {
@ -117,16 +122,29 @@ impl<'i> Lexer<'i> {
} }
} }
fn next_token(&mut self) -> Option<Spanned<Token<'i>, usize, LexerError>> { fn skip_whitespace(&mut self) {
while matches!(self.chars.peek(), Some((_, ' ' | '\t' | '\r'))) { while matches!(self.chars.peek(), Some((_, ' ' | '\t' | '\n' | '\r'))) {
if self.bracket_depth == 0 && matches!(self.chars.peek(), Some((_, '\n'))) {
break
}
self.chars.next(); self.chars.next();
} }
}
fn next_token(&mut self) -> Option<Spanned<Token<'i>, usize, LexerError>> {
self.skip_whitespace();
Some(match self.chars.next()? { Some(match self.chars.next()? {
(i, '(') => Ok((i, Token::LParen, i + 1)), (_, '#') => {
(i, ')') => Ok((i, Token::RParen, i + 1)), while !matches!(self.chars.peek(), Some((_, '\n')) | None) {
(i, '{') => Ok((i, Token::LBrace, i + 1)), self.chars.next();
(i, '}') => Ok((i, Token::RBrace, i + 1)), }
self.next_token()?
}
(i, '(') => { self.bracket_depth += 1; Ok((i, Token::LParen, i + 1)) },
(i, ')') => { self.bracket_depth -= 1; Ok((i, Token::RParen, i + 1)) },
(i, '{') => { self.bracket_depth += 1; Ok((i, Token::LBrace, i + 1)) },
(i, '}') => { self.bracket_depth -= 1; Ok((i, Token::RBrace, i + 1)) },
(i, '+') => Ok((i, Token::Plus, i + 1)), (i, '+') => Ok((i, Token::Plus, i + 1)),
(i, '-') => match self.chars.peek() { (i, '-') => match self.chars.peek() {
Some((_, '>')) => { Some((_, '>')) => {

View file

@ -31,6 +31,18 @@ fn correct_mod2(x: vec2f, y: vec2f) -> vec2f {
return ((x % y) + y) % y; return ((x % y) + y) % y;
} }
fn vlength(v: vec2f) -> f32 {
let l = length(v);
if l != l || l <= 3.4e+38 {
return l;
}
let a = max(abs(v.x), abs(v.y));
let b = RECIP_SQRT2 * abs(v.x) + RECIP_SQRT2 * abs(v.y);
let c = 2.0 * RECIP_SQRT29 * abs(v.x) + 5.0 * RECIP_SQRT29 * abs(v.y);
let d = 5.0 * RECIP_SQRT29 * abs(v.x) + 2.0 * RECIP_SQRT29 * abs(v.y);
return max(max(a, b), max(c, d));
}
///////////////// /////////////////
// constants // // constants //
///////////////// /////////////////
@ -40,6 +52,7 @@ const E = 2.718281828459045;
const RECIP_SQRT2 = 0.7071067811865475; const RECIP_SQRT2 = 0.7071067811865475;
const LOG_TAU = 1.8378770664093453; const LOG_TAU = 1.8378770664093453;
const LOG_2 = 0.6931471805599453; const LOG_2 = 0.6931471805599453;
const RECIP_SQRT29 = 0.18569533817705186;
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);
@ -67,6 +80,34 @@ fn c_signim(z: vec2f) -> vec2f {
return vec2(sign(z.y), 0.0); return vec2(sign(z.y), 0.0);
} }
fn c_ifgt(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f {
return select(w, z, p.x > q.x);
}
fn c_iflt(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f {
return select(w, z, p.x < q.x);
}
fn c_ifge(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f {
return select(w, z, p.x >= q.x);
}
fn c_ifle(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f {
return select(w, z, p.x <= q.x);
}
fn c_ifeq(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f {
return select(w, z, p.x == q.x);
}
fn c_ifne(p: vec2f, q: vec2f, z: vec2f, w: vec2f) -> vec2f {
return select(w, z, p.x != q.x);
}
fn c_ifnan(p: vec2f, z: vec2f, w: vec2f) -> vec2f {
return select(w, z, p.x != p.x && p.y != p.y);
}
fn c_conj(z: vec2f) -> vec2f { fn c_conj(z: vec2f) -> vec2f {
return z * vec2(1.0, -1.0); return z * vec2(1.0, -1.0);
} }
@ -76,17 +117,20 @@ fn c_abs_sq(z: vec2f) -> vec2f {
} }
fn c_abs(z: vec2f) -> vec2f { fn c_abs(z: vec2f) -> vec2f {
return vec2(length(z), 0.0); return vec2(vlength(z), 0.0);
} }
fn c_arg(z: vec2f) -> vec2f { fn c_arg(z: vec2f) -> vec2f {
if z.x < 0.0 && z.y == 0.0 {
return vec2(TAU/2.0, 0.0);
}
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 { fn c_argbr(z: vec2f, br: vec2f) -> vec2f {
let r = vec2(cos(-br.x), sin(-br.x)); let r = vec2(cos(-br.x), sin(-br.x));
let zr = c_mul(z, r); let zr = c_mul(z, r);
return vec2(atan2(zr.y, zr.x) + br.x, 0.0); return c_arg(zr) + vec2(br.x, 0.0);
} }
fn c_add(u: vec2f, v: vec2f) -> vec2f { fn c_add(u: vec2f, v: vec2f) -> vec2f {
@ -122,7 +166,7 @@ fn c_exp(z: vec2f) -> vec2f {
} }
fn c_log(z: vec2f) -> vec2f { 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)), c_arg(z).x);
} }
fn c_logbr(z: vec2f, br: vec2f) -> vec2f { fn c_logbr(z: vec2f, br: vec2f) -> vec2f {
@ -133,10 +177,26 @@ 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));
} }
fn c_powbr(u: vec2f, v: vec2f, br: vec2f) -> vec2f {
return c_exp(c_mul(c_logbr(u, br), v));
}
fn c_sqrt(z: vec2f) -> vec2f { fn c_sqrt(z: vec2f) -> vec2f {
return c_pow(z, vec2(0.5, 0.0)); return c_pow(z, vec2(0.5, 0.0));
} }
fn c_sqrtbr(z: vec2f, br: vec2f) -> vec2f {
return c_powbr(z, vec2(0.5, 0.0), br);
}
fn c_cbrt(z: vec2f, br: vec2f) -> vec2f {
return c_pow(z, vec2(1.0/3.0, 0.0));
}
fn c_cbrtbr(z: vec2f, br: vec2f) -> vec2f {
return c_powbr(z, vec2(1.0/3.0, 0.0), br);
}
fn c_sin(z: vec2f) -> vec2f { fn c_sin(z: vec2f) -> vec2f {
return vec2(sin(z.x)*cosh(z.y), cos(z.x)*sinh(z.y)); return vec2(sin(z.x)*cosh(z.y), cos(z.x)*sinh(z.y));
} }
@ -162,84 +222,50 @@ fn c_tanh(z: vec2f) -> vec2f {
} }
fn c_asin(z: vec2f) -> vec2f { fn c_asin(z: vec2f) -> vec2f {
let u = c_sqrt(vec2f(1.0, 0.0) - c_mul(z, z)); let u = c_sqrt(vec2(1.0, 0.0) - c_mul(z, z));
let v = c_log(u + vec2(-z.y, z.x)); let v = c_log(u + vec2(-z.y, z.x));
return vec2(v.y, -v.x); return vec2(v.y, -v.x);
} }
fn c_acos(z: vec2f) -> vec2f { fn c_acos(z: vec2f) -> vec2f {
let u = c_sqrt(vec2f(1.0, 0.0) - c_mul(z, z)); let u = c_sqrt(vec2(1.0, 0.0) - c_mul(z, z));
let v = c_log(u + vec2f(-z.y, z.x)); let v = c_log(u + vec2(-z.y, z.x));
return vec2f(TAU*0.25 - v.y, v.x); return vec2(TAU*0.25 - v.y, v.x);
} }
fn c_atan(z: vec2f) -> vec2f { fn c_atan(z: vec2f) -> vec2f {
let u = vec2f(1.0, 0.0) - vec2f(-z.y, z.x); let u = vec2(1.0, 0.0) - vec2(-z.y, z.x);
let v = vec2f(1.0, 0.0) + vec2f(-z.y, z.x); let v = vec2(1.0, 0.0) + vec2(-z.y, z.x);
let w = c_log(c_div(u, v)); let w = c_log(c_div(u, v));
return 0.5 * vec2f(-w.y, w.x); return 0.5 * vec2(-w.y, w.x);
} }
fn c_asinh(z: vec2f) -> vec2f { fn c_asinh(z: vec2f) -> vec2f {
let u = c_sqrt(vec2f(1.0, 0.0) + c_mul(z, z)); let u = c_sqrt(vec2(1.0, 0.0) + c_mul(z, z));
return c_log(u + z); return c_log(u + z);
} }
fn c_acosh(z: vec2f) -> vec2f { fn c_acosh(z: vec2f) -> vec2f {
let u = c_sqrt(vec2f(-1.0, 0.0) + c_mul(z, z)); let u = c_sqrt(vec2(-1.0, 0.0) + c_mul(z, z));
return c_log(u + z); return c_log(u + z);
} }
fn c_atanh(z: vec2f) -> vec2f { fn c_atanh(z: vec2f) -> vec2f {
let u = vec2f(1.0, 0.0) + z; let u = vec2(1.0, 0.0) + z;
let v = vec2f(1.0, 0.0) - z; let v = vec2(1.0, 0.0) - z;
return 0.5 * c_log(c_div(u, v)); return 0.5 * c_log(c_div(u, v));
} }
// gamma //
fn c_gamma(z: vec2f) -> vec2f {
let reflect = z.x < 0.5;
var zp = z;
if reflect {
zp = vec2(1.0, 0.0) - z;
}
var w = c_gamma_inner2(zp);
if reflect {
w = TAU * 0.5 * c_recip(c_mul(c_sin(TAU * 0.5 * z), 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 c_gamma_inner(z: vec2f) -> vec2f {
let z2 = c_mul(z, z);
let z3 = c_mul(z2, z);
let a = c_sqrt(TAU * z);
let b = c_pow(1.0 / (E * E) * c_mul(z3, c_sinh(c_recip(z))), 0.5 * z);
let c = c_exp(7.0/324.0 * c_recip(c_mul(z3, 35.0 * z2 + 33.0)));
return c_mul(c_mul(a, b), c);
}
fn c_gamma_inner2(z: vec2f) -> vec2f {
let w = c_gamma_inner(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 // // log gamma //
fn c_loggamma(z: vec2f) -> vec2f { fn c_loggamma(z: vec2f) -> vec2f {
let reflect = z.x < 0.5 && abs(z.y) < 10.0; let reflect = z.x < 0.5 && abs(z.y) < 13.0;
var zp = z; var zp = z;
if reflect { if reflect {
zp = vec2(1.0, 0.0) - z; zp = vec2(1.0, 0.0) - z;
} }
var w = c_loggamma_inner2(zp); var w = c_loggamma_inner2(zp);
if reflect { 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); 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; w = vec2(LOG_TAU - LOG_2, 0.0) - c_logbr(c_sin(TAU/2.0 * z), vec2(br, 0.0)) - w;
} }
@ -256,10 +282,20 @@ fn c_loggamma_inner2(z: vec2f) -> vec2f {
return w - l; return w - l;
} }
// gamma //
fn c_gamma(z: vec2f) -> vec2f {
return c_exp(c_loggamma(z));
}
fn c_invgamma(z: vec2f) -> vec2f {
return c_exp(-c_loggamma(z));
}
// digamma // // digamma //
fn c_digamma(z: vec2f) -> vec2f { fn c_digamma(z: vec2f) -> vec2f {
let reflect = z.x < 0.5; let reflect = z.x < 0.5 && abs(z.y) < 13.0;
var zp = z; var zp = z;
if reflect { if reflect {
zp = vec2(1.0, 0.0) - z; zp = vec2(1.0, 0.0) - z;
@ -308,13 +344,18 @@ fn coloring_standard(z: vec2f) -> vec3f {
return vec3f(0.5, 0.5, 0.5); return vec3f(0.5, 0.5, 0.5);
} }
let mag_sq = dot(z, z); let mag = vlength(z);
if mag_sq > 3.40282347E+38 { if mag > 3.40282347E+38 {
return vec3f(1.0, 1.0, 1.0); return vec3f(1.0, 1.0, 1.0);
} }
let mag = sqrt(mag_sq); if uniforms.shading_intensity > 0.0 && mag > 1.8446E+19 {
return vec3f(1.0, 1.0, 1.0);
}
if uniforms.shading_intensity == 0.0 && mag < 1.0E-38 {
return vec3f(0.0, 0.0, 0.0);
}
let arg = atan2(z.y, z.x); let arg = c_arg(z).x;
let hsv = vec3f(arg / TAU + 1.0, shademap(1.0/mag), shademap(mag)); let hsv = vec3f(arg / TAU + 1.0, shademap(1.0/mag), shademap(mag));
return hsv2rgb(hsv); return hsv2rgb(hsv);
@ -328,13 +369,18 @@ fn coloring_uniform(z: vec2f) -> vec3f {
return vec3f(0.5, 0.5, 0.5); return vec3f(0.5, 0.5, 0.5);
} }
let mag_sq = dot(z, z); let mag = vlength(z);
if mag_sq > 3.40282347E+38 { if mag > 3.40282347E+38 {
return vec3f(1.0, 1.0, 1.0); return vec3f(1.0, 1.0, 1.0);
} }
let mag = sqrt(mag_sq); if uniforms.shading_intensity > 0.0 && mag > 1.8446E+19 {
return vec3f(1.0, 1.0, 1.0);
}
if uniforms.shading_intensity == 0.0 && mag < 1.0E-38 {
return vec3f(0.0, 0.0, 0.0);
}
let arg = atan2(z.y, z.x); let arg = c_arg(z).x;
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;
@ -358,7 +404,7 @@ fn decoration_contour_im(z: vec2f) -> f32 {
} }
fn decoration_contour_arg(z: vec2f) -> f32 { fn decoration_contour_arg(z: vec2f) -> f32 {
let arg = atan2(z.y, z.x); let arg = c_arg(z).x;
return round(correct_mod(arg + TAU, TAU/8.0) * 8.0/TAU) * 2.0 - 1.0; return round(correct_mod(arg + TAU, TAU/8.0) * 8.0/TAU) * 2.0 - 1.0;
} }
@ -370,7 +416,7 @@ fn decoration_contour_mag(z: vec2f) -> f32 {
@fragment @fragment
fn main(@builtin(position) in: vec4f) -> @location(0) vec4f { fn main(@builtin(position) in: vec4f) -> @location(0) vec4f {
let pos = vec2(in.x, f32(uniforms.resolution.y) - in.y); let pos = vec2(in.x, f32(uniforms.resolution.y) - in.y);
let w = remap(pos, vec2(0.0), vec2f(uniforms.resolution), uniforms.bounds_min, uniforms.bounds_max); let w = remap(pos, vec2(0.0, 0.0), vec2f(uniforms.resolution), uniforms.bounds_min, uniforms.bounds_max);
let z = func_plot(w); let z = func_plot(w);