42 Math and Logic Nodes
110 nodes. That’s where Lux is now.
I just added 42 math, logic, and comparison nodes across 5 new plugin crates. These are the building blocks that make everything else work. You can’t do creative coding without math, and now Lux has a proper math library.
Arithmetic: 14 nodes
The basics: Add, Subtract, Multiply, Divide, Min, Max, Clamp, Abs, Negate, Modulo, Power, Sqrt, Sign, Reciprocal.
But here’s what makes them interesting: they’re polymorphic. An Add node doesn’t just add numbers. It adds whatever you give it. Two Numbers? You get a Number. Two Vec3s? Component-wise addition. A Color and a Number? The number broadcasts across all channels.
I built a shared polymorphic.rs helper with binary_op and unary_op functions that handle the dispatch. Every arithmetic node uses the same helper, so they all behave consistently. Feed an Int to an Int and you get an Int back, it doesn’t silently promote to a float. Feed a Number to a Vec4 and the number broadcasts to all four components.
The tricky part was edge cases. Divide by zero? Returns zero, not NaN or infinity. Sqrt of a negative? Returns zero. Power with degenerate inputs? Clamped. I wrote a sanitize() helper that catches NaN and infinity on every output. Creative coding patches should never produce NaN, because NaN propagates through everything downstream and suddenly your whole patch is broken with no obvious cause.
Trigonometry: 8 nodes
Sin, Cos, Tan, Asin, Acos, Atan, Atan2, SinCos.
These work in radians. Sin and Cos are straightforward. Tan has an infinity guard for inputs near pi/2. Asin and Acos clamp their inputs to [-1, 1] because passing 1.0000001 (a float rounding artifact) to acos would return NaN, and that’s not acceptable.
SinCos outputs both sine and cosine at once. It’s a small optimization but it matters when you’re computing circular motion, you almost always need both values, and computing them together is cheaper than computing them separately.
Atan2 takes x and y and returns the angle. Essential for converting cartesian coordinates to polar, which comes up constantly in creative coding. Circular layouts, radial effects, anything involving rotation.
Interpolation: 7 nodes
Lerp blends between two values by a factor. The most-used node in any patch. Factor 0 gives you the first value, factor 1 gives you the second, 0.5 gives you the midpoint. Works on Numbers, Vec2, Vec3, Vec4, and Colors.
SmoothStep is like Lerp but with an S-curve. It eases in and out instead of moving linearly. The classic Hermite interpolation that makes transitions feel natural.
Map remaps a value from one range to another. Input range 0 to 1, output range 100 to 500. This is the “make any signal drive any parameter” node. You’ll use it everywhere.
Wrap wraps a value into a range. Goes past the max? Wraps back to the min. Goes below the min? Wraps to the max. Essential for looping values like angles or spread indices.
Mirror is like Wrap but ping-pongs instead of resetting. Goes past the max, bounces back toward min, hits min, bounces back toward max. Triangle wave on any value.
CubicBezier evaluates a cubic bezier curve at a given t value. Four control points define the curve shape. Feed in a 0 to 1 progress value and get a shaped output.
Spline does Catmull-Rom interpolation through a spread of control points. This one is spread_native because it consumes the spread directly rather than auto-spreading. Feed it a spread of values and a position (0 to 1) and it smoothly interpolates through all of them. Buttery smooth curves through any number of points.
What polymorphic means in practice
Here’s why this matters. In a lot of visual programming tools, you need separate nodes for “Add Numbers” and “Add Vectors” and “Add Colors.” In Lux, there’s one Add node. Connect whatever types you want. It figures it out.
This is especially powerful with spreads. A spread of Vec3 positions plus a single Number offset? The Number broadcasts into each component of each Vec3 in the spread. One node, automatic type handling, automatic spread handling. No configuration needed.
Zero new external dependencies for all 42 nodes. They’re all pure math, built on Rust’s standard library and the types already in lux-core.