Skip to main content

fidget/
lib.rs

1//! Fidget is a library of infrastructure and algorithms for function
2//! evaluation, with an emphasis on complex closed-form implicit surfaces.
3//!
4//! An **implicit surface** is a function `f(x, y, z)`, where `x`, `y`, and `z`
5//! represent a position in 3D space.  By convention, if `f(x, y, z) < 0`, then
6//! that position is _inside_ the shape; if it's `> 0`, then that position is
7//! _outside_ the shape; otherwise, it's on the boundary of the shape.
8//!
9//! A **closed-form** implicit surface means that the function is given as a
10//! fixed expression built from closed-form operations (addition, subtraction,
11//! etc), with no mutable state.  This is in contrast to
12//! [ShaderToy](https://www.shadertoy.com/)-style implicit surface functions,
13//! which often include mutable state and make control-flow decisions at
14//! runtime.
15//!
16//! Finally, **complex** means that that the library scales to expressions with
17//! thousands of clauses.
18//!
19//! Details on overall project status are in the
20//! [project's README](https://github.com/mkeeter/fidget);
21//! the rest of this page is a quick tour through the library APIs.
22//!
23//! # Shape construction
24//! A "shape" is a closed-form function of `(x, y, z)` with a single output.
25//! For example, a circle of radius `1` could be expressed as
26//! `sqrt(x*x + y*y) - 1`.
27//!
28//! Shapes are constructed within a
29//! [`fidget::context::Context`](crate::context::Context).  A context serves as
30//! an arena-style allocator, doing local deduplication and other simple
31//! optimizations (e.g. constant folding).
32//!
33//! Shapes can be constructed manually, using functions on a context:
34//! ```
35//! use fidget::context::Context;
36//!
37//! let mut ctx = Context::new();
38//! let x = ctx.x();
39//! let y = ctx.y();
40//! let sum = ctx.add(x, y)?;
41//! # Ok::<(), fidget::Error>(())
42//! ```
43//!
44//! This is efficient, but is awkward to write.  It's also possible to construct
45//! shapes without a [`Context`] using the [`Tree`](crate::context::Tree) type,
46//! then import the tree into a context:
47//! ```
48//! use fidget::context::{Context, Tree};
49//!
50//! let t = Tree::x() + Tree::y();
51//! let mut ctx = Context::new();
52//! let sum = ctx.import(&t);
53//! ```
54//!
55//! As a third alternative, Fidget includes bindings to [Rhai](https://rhai.rs),
56//! a simple Rust-native scripting language, in the [`fidget::rhai`
57//! namespace](crate::rhai).  These bindings allow shapes to be constructed from
58//! a script, adding flexibility:
59//!
60//! ```
61//! # use fidget::context::Context;
62//! let t = fidget::rhai::engine().eval("x + y").unwrap();
63//! let mut ctx = Context::new();
64//! let sum = ctx.import(&t);
65//! ```
66//!
67//! # Evaluation
68//! The main operation performed on an implicit surface is **evaluation**, i.e.
69//! passing it some position `(x, y, z)` and getting back a result.  This will
70//! be done _a lot_, so it has to be fast.
71//!
72//! Evaluation is deliberately agnostic to the specific details of how we go
73//! from position to results.  This abstraction is represented by the
74//! [`Function` trait](crate::eval::Function), which defines how to make both
75//! **evaluators** and **tapes**.
76//!
77//! An **evaluator** is an object which performs evaluation of some kind (point,
78//! array, gradient, interval).  It carries no persistent data, and would
79//! typically be constructed on a per-thread basis.
80//!
81//! A **tape** contains instructions for an evaluator.
82//!
83//! At the moment, Fidget implements two kinds of functions:
84//!
85//! - [`fidget::vm::VmFunction`](crate::vm::VmFunction) evaluates a list of
86//!   opcodes using an interpreter.  This is slower, but can run in more
87//!   situations (e.g. in WebAssembly).
88//! - [`fidget::jit::JitFunction`](crate::jit::JitFunction) performs fast
89//!   evaluation by compiling expressions down to native code.
90//!
91//! The [`Function`](crate::eval::Function) trait requires four different kinds
92//! of evaluation:
93//!
94//! - Single-point evaluation
95//! - Interval evaluation
96//! - Evaluation on an array of points, returning `f32` values
97//! - Evaluation on an array of points, returning partial derivatives with
98//!   respect to input variables
99//!
100//! These evaluation flavors are used in rendering:
101//! - Interval evaluation can conservatively prove large regions of space to be
102//!   empty or full, at which point they don't need to be considered further.
103//! - Array-of-points evaluation speeds up calculating occupancy (inside /
104//!   outside) when given a set of voxels, because dispatch overhead is
105//!   amortized over many points.
106//! - At the surface of the model, partial derivatives represent normals and
107//!   can be used for shading.
108//!
109//! # Functions and shapes
110//! The [`Function`](crate::eval::Function) trait supports arbitrary numbers of
111//! variables; when using it for implicit surfaces, it's common to wrap it in a
112//! [`Shape`](crate::shape::Shape), which binds `(x, y, z)` axes to specific
113//! variables.
114//!
115//! Here's a simple example of multi-point evaluation, using a `VmShape` to
116//! evaluate the function `X + Y` at four sample locations:
117//!
118//! ```
119//! use fidget::{
120//!     context::Tree,
121//!     shape::{Shape, EzShape},
122//!     vm::VmShape
123//! };
124//!
125//! let tree = Tree::x() + Tree::y();
126//! let shape = VmShape::from(tree);
127//! let mut eval = VmShape::new_float_slice_eval();
128//! let tape = shape.ez_float_slice_tape();
129//! let out = eval.eval(
130//!     &tape,
131//!     &[0.0, 1.0, 2.0, 3.0], // X
132//!     &[2.0, 3.0, 4.0, 5.0], // Y
133//!     &[0.0, 0.0, 0.0, 0.0], // Z
134//! )?;
135//! assert_eq!(out, &[2.0, 4.0, 6.0, 8.0]);
136//! # Ok::<(), fidget::Error>(())
137//! ```
138//!
139//! # Shape simplification
140//! Interval evaluation serves two purposes.  As we already mentioned, it can be
141//! used to prove large regions empty or filled, which lets us do less work when
142//! rendering.  In addition, it can discover **sections of the tape** that are
143//! always inactive in a particular spatial region.
144//!
145//! Consider evaluating `f(x, y, z) = max(x, y)` with `x = [0, 1]` and
146//! `y = [2, 3]`:
147//! ```
148//! use fidget::{
149//!     context::Tree,
150//!     shape::EzShape,
151//!     vm::VmShape
152//! };
153//!
154//! let tree = Tree::x().min(Tree::y());
155//! let shape = VmShape::from(tree);
156//! let mut interval_eval = VmShape::new_interval_eval();
157//! let tape = shape.ez_interval_tape();
158//! let (out, trace) = interval_eval.eval(
159//!     &tape,
160//!     [0.0, 1.0], // X
161//!     [2.0, 3.0], // Y
162//!     [0.0, 0.0], // Z
163//! )?;
164//! assert_eq!(out, [0.0, 1.0].into());
165//! # Ok::<(), fidget::Error>(())
166//! ```
167//!
168//! In the evaluation region `x = [0, 1]; y = [2, 3]`, `x` is **strictly less
169//! than** `y` in the `min(x, y)` clause.  This means that we can simplify the
170//! tape from `min(x, y) →  x`.
171//!
172//! Interval evaluation is a kind of
173//! [tracing evaluation](crate::eval::TracingEvaluator), which returns a tuple
174//! of `(value, trace)`.  The trace can be used to simplify the original shape:
175//!
176//! ```
177//! # use fidget::{
178//! #     context::Tree,
179//! #     shape::EzShape,
180//! #     vm::VmShape
181//! # };
182//! # let tree = Tree::x().min(Tree::y());
183//! # let shape = VmShape::from(tree);
184//! assert_eq!(shape.size(), 4); // min, X, Y, output
185//! # let mut interval_eval = VmShape::new_interval_eval();
186//! # let tape = shape.ez_interval_tape();
187//! # let (out, trace) = interval_eval.eval(
188//! #         &tape,
189//! #         [0.0, 1.0], // X
190//! #         [2.0, 3.0], // Y
191//! #         [0.0, 0.0], // Z
192//! #     )?;
193//! // (same code as above)
194//! let new_shape = shape.ez_simplify(trace.unwrap())?;
195//! assert_eq!(new_shape.size(), 2); // just the X term, then the output
196//! # Ok::<(), fidget::Error>(())
197//! ```
198//!
199//! Remember that this simplified tape is only valid for points (or intervals)
200//! within the interval region `x = [0, 1]; y = [2, 3]`.  It's up to you to make
201//! sure this is upheld!
202//!
203//! # Rasterization
204//! Fidget implements both 2D and 3D rasterization of implicit surfaces,
205//! implemented in the [`fidget::raster` module](raster).
206//!
207//! Here's a quick example:
208//! ```
209//! use fidget::{
210//!     context::{Tree, Context},
211//!     render::ImageSize,
212//!     raster::ImageRenderConfig,
213//!     vm::VmShape,
214//! };
215//!
216//! let x = Tree::x();
217//! let y = Tree::y();
218//! let tree = (x.square() + y.square()).sqrt() - 1.0;
219//! let cfg = ImageRenderConfig {
220//!     image_size: ImageSize::from(32),
221//!     ..Default::default()
222//! };
223//! let shape = VmShape::from(tree);
224//! let out = cfg.run(shape).unwrap();
225//! let mut iter = out.iter();
226//! for y in 0..cfg.image_size.height() {
227//!     for x in 0..cfg.image_size.width() {
228//!         if iter.next().unwrap().inside() {
229//!             print!("##");
230//!         } else {
231//!             print!("  ");
232//!         }
233//!     }
234//!     println!();
235//! }
236//!
237//! // This will print
238//! //           ##########
239//! //       ##################
240//! //     ######################
241//! //   ##########################
242//! //   ##########################
243//! // ##############################
244//! // ##############################
245//! // ##############################
246//! // ##############################
247//! // ##############################
248//! //   ##########################
249//! //   ##########################
250//! //     ######################
251//! //       ##################
252//! //           ##########
253//! # Ok::<(), fidget::Error>(())
254//! ```
255//!
256//! # Meshing
257//! Fidget implements
258//! [Manifold Dual Contouring](https://people.engr.tamu.edu/schaefer/research/dualsimp_tvcg.pdf),
259//! which converts from implicit surfaces to triangle meshes.
260//!
261//! This is documented in the [`fidget::mesh`](mesh) module.
262//!
263//! # WebAssembly notes
264//! The `getrandom` backend must be selected with `RUSTFLAGS`, e.g.
265//! `RUSTFLAGS='--cfg getrandom_backend="wasm_js"'`.  This can be specified on
266//! the command line or in a `.cargo/config.toml` configuration file (e.g.
267//! [this file](https://github.com/mkeeter/fidget/tree/main/.cargo/config.toml)
268//! in Fidget itself).
269//!
270//! See
271//! [the `getrandom` docs](https://docs.rs/getrandom/latest/getrandom/#webassembly-support)
272//! for more details on why this is necessary.
273//!
274//! # Crate organization
275//! The Fidget crate is a thin wrapper about multiple smaller crates, for
276//! improved compilation speed and modulatiry.
277//!
278//! With no features enabled, the `fidget` crate simply re-exports everything
279//! from [`fidget_core`].  Fine-grained features add other modules (listed
280//! below); each top-level module in `fidget` is implemented in a standalone
281//! crate.
282//!
283//! This organization is an implementation detail; users should just depend on
284//! `fidget` and not worry too much about it.
285//!
286//! # Feature flags
287#![doc = document_features::document_features!()]
288#![cfg_attr(docsrs, feature(doc_cfg))]
289#![warn(missing_docs)]
290
291pub use fidget_core::*;
292
293#[cfg(feature = "rhai")]
294pub use fidget_rhai as rhai;
295
296#[cfg(feature = "bytecode")]
297pub use fidget_bytecode as bytecode;
298
299#[cfg(feature = "mesh")]
300pub use fidget_mesh as mesh;
301
302#[cfg(feature = "shapes")]
303pub use fidget_shapes as shapes;
304
305#[cfg(feature = "solver")]
306pub use fidget_solver as solver;
307
308#[cfg(feature = "raster")]
309pub use fidget_raster as raster;
310
311#[cfg(feature = "gui")]
312pub use fidget_gui as gui;
313
314#[cfg(all(feature = "jit", not(target_arch = "wasm32")))]
315pub use fidget_jit as jit;