Skip to main content

fidget_core/context/
op.rs

1use crate::{
2    context::{Node, indexed::Index},
3    types::FloatExt,
4    var::Var,
5};
6use ordered_float::OrderedFloat;
7
8/// A one-argument math operation
9#[allow(missing_docs)]
10#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
11pub enum UnaryOpcode {
12    Neg,
13    Abs,
14    Recip,
15    Sqrt,
16    Square,
17    Floor,
18    Ceil,
19    Round,
20    Sin,
21    Cos,
22    Tan,
23    Asin,
24    Acos,
25    Atan,
26    Exp,
27    Ln,
28    Not,
29    Rand,
30}
31
32/// A two-argument math operation
33#[allow(missing_docs)]
34#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
35pub enum BinaryOpcode {
36    Add,
37    Sub,
38    Mul,
39    Div,
40    Atan,
41    Min,
42    Max,
43    Compare,
44    Mod,
45    And,
46    Or,
47    Mix,
48}
49
50impl BinaryOpcode {
51    /// Evaluates the opcode
52    pub fn eval(&self, a: f32, b: f32) -> f32 {
53        match self {
54            BinaryOpcode::Add => a + b,
55            BinaryOpcode::Sub => a - b,
56            BinaryOpcode::Mul => a * b,
57            BinaryOpcode::Div => a / b,
58            BinaryOpcode::Atan => a.atan2(b),
59            BinaryOpcode::Min => a.min_choice(b).0,
60            BinaryOpcode::Max => a.max_choice(b).0,
61            BinaryOpcode::Compare => a.compare(b),
62            BinaryOpcode::Mod => a.rem_euclid(b),
63            BinaryOpcode::And => a.and_choice(b).0,
64            BinaryOpcode::Or => a.or_choice(b).0,
65            BinaryOpcode::Mix => a.mix(b),
66        }
67    }
68}
69
70impl UnaryOpcode {
71    /// Evaluates the opcode
72    pub fn eval(&self, a: f32) -> f32 {
73        match self {
74            UnaryOpcode::Neg => -a,
75            UnaryOpcode::Abs => a.abs(),
76            UnaryOpcode::Recip => 1.0 / a,
77            UnaryOpcode::Sqrt => a.sqrt(),
78            UnaryOpcode::Square => a * a,
79            UnaryOpcode::Floor => a.floor(),
80            UnaryOpcode::Ceil => a.ceil(),
81            UnaryOpcode::Round => a.round(),
82            UnaryOpcode::Sin => a.sin(),
83            UnaryOpcode::Cos => a.cos(),
84            UnaryOpcode::Tan => a.tan(),
85            UnaryOpcode::Asin => a.asin(),
86            UnaryOpcode::Acos => a.acos(),
87            UnaryOpcode::Atan => a.atan(),
88            UnaryOpcode::Exp => a.exp(),
89            UnaryOpcode::Ln => a.ln(),
90            UnaryOpcode::Not => a.not(),
91            UnaryOpcode::Rand => a.rand(),
92        }
93    }
94}
95
96/// An operation in a math expression
97///
98/// `Op`s should be constructed by calling functions on
99/// [`Context`](crate::context::Context), e.g.
100/// [`Context::add`](crate::context::Context::add) will generate an
101/// `Op::Binary(BinaryOpcode::Add, .., ..)` node and return an opaque handle.
102///
103/// Each `Op` is tightly coupled to the [`Context`](crate::context::Context)
104/// which generated it, and will not be valid for a different `Context`.
105#[allow(missing_docs)]
106#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
107pub enum Op {
108    Input(Var),
109    Const(OrderedFloat<f32>),
110    Binary(BinaryOpcode, Node, Node),
111    Unary(UnaryOpcode, Node),
112}
113
114fn dot_color_to_rgb(s: &str) -> &'static str {
115    match s {
116        "red" => "#FF0000",
117        "green" => "#00FF00",
118        "goldenrod" => "#DAA520",
119        "dodgerblue" => "#1E90FF",
120        s => panic!("Unknown X11 color '{s}'"),
121    }
122}
123
124impl Op {
125    /// Returns the color to be used in a GraphViz drawing for this node
126    pub fn dot_node_color(&self) -> &str {
127        match self {
128            Op::Const(..) => "green",
129            Op::Input(..) => "red",
130            Op::Binary(BinaryOpcode::Min | BinaryOpcode::Max, ..) => {
131                "dodgerblue"
132            }
133            Op::Binary(..) | Op::Unary(..) => "goldenrod",
134        }
135    }
136
137    /// Returns the shape to be used in a GraphViz drawing for this node
138    pub fn dot_node_shape(&self) -> &str {
139        match self {
140            Op::Const(..) => "oval",
141            Op::Input(..) => "circle",
142            Op::Binary(..) | Op::Unary(..) => "box",
143        }
144    }
145
146    /// Iterates over children, producing 0, 1, or 2 values
147    pub fn iter_children(&self) -> impl Iterator<Item = Node> {
148        let out = match self {
149            Op::Binary(_, a, b) => [Some(*a), Some(*b)],
150            Op::Unary(_, a) => [Some(*a), None],
151            Op::Input(..) | Op::Const(..) => [None, None],
152        };
153        out.into_iter().flatten()
154    }
155
156    /// Returns a GraphViz string of edges from this node to its children
157    pub fn dot_edges(&self, i: Node) -> String {
158        let mut out = String::new();
159        for c in self.iter_children() {
160            out += &self.dot_edge(i, c, "FF");
161        }
162        out
163    }
164
165    /// Returns a single edge with user-specified transparency
166    pub fn dot_edge(&self, a: Node, b: Node, alpha: &str) -> String {
167        let color = dot_color_to_rgb(self.dot_node_color()).to_owned() + alpha;
168        format!("n{} -> n{} [color = \"{color}\"]\n", a.get(), b.get(),)
169    }
170}