mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 13:30:48 +00:00
New node: Math (#2121)
* 2115 IP * Initial implementation of Expression node * Register Expression Node * Add Expression DocumentNode Definition * DocumentNodeImplementation::Expresssion in guess_type_from_node * Move expression.rs to graphene-core * WIP: Investigating 'exposed' & 'value_source' params for Expression property * Node graph render debug IP * Single input can change node properties; complex debug IP * Fix epsilon in test * Handle invalid expressions in expression_node by returning 0.0 * Run cargo fmt * Set the default expression to "1 + 1" * Hardcode the A and B inputs at Keavon's request * Rename and clean up UX * Move into ops.rs --------- Co-authored-by: hypercube <0hypercube@gmail.com> Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
79b4f4df7b
commit
3423c8ec13
6 changed files with 152 additions and 8 deletions
|
@ -78,6 +78,7 @@ web-sys = { workspace = true, optional = true, features = [
|
|||
image = { workspace = true, optional = true, default-features = false, features = [
|
||||
"png",
|
||||
] }
|
||||
math-parser = { path = "../../libraries/math-parser" }
|
||||
|
||||
[dev-dependencies]
|
||||
# Workspace dependencies
|
||||
|
|
|
@ -4,6 +4,10 @@ use crate::registry::types::Percentage;
|
|||
use crate::vector::style::GradientStops;
|
||||
use crate::{Color, Node};
|
||||
|
||||
use math_parser::ast;
|
||||
use math_parser::context::{EvalContext, NothingMap, ValueProvider};
|
||||
use math_parser::value::{Number, Value};
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::{Add, Div, Mul, Rem, Sub};
|
||||
use glam::DVec2;
|
||||
|
@ -13,6 +17,70 @@ use rand::{Rng, SeedableRng};
|
|||
#[cfg(target_arch = "spirv")]
|
||||
use spirv_std::num_traits::float::Float;
|
||||
|
||||
/// The struct that stores the context for the maths parser.
|
||||
/// This is currently just limited to supplying `a` and `b` until we add better node graph support and UI for variadic inputs.
|
||||
struct MathNodeContext {
|
||||
a: f64,
|
||||
b: f64,
|
||||
}
|
||||
|
||||
impl ValueProvider for MathNodeContext {
|
||||
fn get_value(&self, name: &str) -> Option<Value> {
|
||||
if name.eq_ignore_ascii_case("a") {
|
||||
Some(Value::from_f64(self.a))
|
||||
} else if name.eq_ignore_ascii_case("b") {
|
||||
Some(Value::from_f64(self.b))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates a mathematical expression with input values "A" and "B"
|
||||
#[node_macro::node(category("Math"))]
|
||||
fn math<U: num_traits::float::Float>(
|
||||
_: (),
|
||||
/// The value of "A" when calculating the expression
|
||||
#[implementations(f64, f32)]
|
||||
operand_a: U,
|
||||
/// A math expression that may incorporate "A" and/or "B", such as "sqrt(A + B) - B^2"
|
||||
#[default(A + B)]
|
||||
expression: String,
|
||||
/// The value of "B" when calculating the expression
|
||||
#[implementations(f64, f32)]
|
||||
#[default(1.)]
|
||||
operand_b: U,
|
||||
) -> U {
|
||||
let (node, _unit) = match ast::Node::try_parse_from_str(&expression) {
|
||||
Ok(expr) => expr,
|
||||
Err(e) => {
|
||||
warn!("Invalid expression: `{expression}`\n{e:?}");
|
||||
return U::from(0.).unwrap();
|
||||
}
|
||||
};
|
||||
let context = EvalContext::new(
|
||||
MathNodeContext {
|
||||
a: operand_a.to_f64().unwrap(),
|
||||
b: operand_b.to_f64().unwrap(),
|
||||
},
|
||||
NothingMap,
|
||||
);
|
||||
|
||||
let value = match node.eval(&context) {
|
||||
Ok(value) => value,
|
||||
Err(e) => {
|
||||
warn!("Expression evaluation error: {e:?}");
|
||||
return U::from(0.).unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
let Value::Number(num) = value;
|
||||
match num {
|
||||
Number::Real(val) => U::from(val).unwrap(),
|
||||
Number::Complex(c) => U::from(c.re).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The addition operation (+) calculates the sum of two numbers.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn add<U: Add<T>, T>(
|
||||
|
@ -471,6 +539,37 @@ mod test {
|
|||
use super::*;
|
||||
use crate::{generic::*, structural::*, value::*};
|
||||
|
||||
#[test]
|
||||
pub fn dot_product_function() {
|
||||
let vector_a = glam::DVec2::new(1., 2.);
|
||||
let vector_b = glam::DVec2::new(3., 4.);
|
||||
assert_eq!(dot_product(vector_a, vector_b), 11.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_expression() {
|
||||
let result = math((), 0., "2 + 2".to_string(), 0.);
|
||||
assert_eq!(result, 4.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_expression() {
|
||||
let result = math((), 0., "(5 * 3) + (10 / 2)".to_string(), 0.);
|
||||
assert_eq!(result, 20.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_expression() {
|
||||
let result = math((), 0., "0".to_string(), 0.);
|
||||
assert_eq!(result, 0.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_expression() {
|
||||
let result = math((), 0., "invalid".to_string(), 0.);
|
||||
assert_eq!(result, 0.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn identity_node() {
|
||||
let value = ValueNode(4u32).then(IdentityNode::new());
|
||||
|
@ -482,11 +581,4 @@ mod test {
|
|||
let fnn = FnNode::new(|(a, b)| (b, a));
|
||||
assert_eq!(fnn.eval((1u32, 2u32)), (2, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn dot_product_function() {
|
||||
let vector_a = glam::DVec2::new(1., 2.);
|
||||
let vector_b = glam::DVec2::new(3., 4.);
|
||||
assert_eq!(dot_product(vector_a, vector_b), 11.);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue