nsi 0.9.0

Nodal Scene Interface for (offline) 3D renderers – ɴsɪ.
Documentation
//#![warn(missing_docs)]
//#![warn(missing_doc_code_examples)]
//! # Nodal Scene Interface -- ɴsɪ
//! A flexible, modern API for offline 3D renderers
//!
//! [Nsɪ](https://nsi.readthedocs.io/) is built around the concept of
//! nodes. Each node has a *unique handle* to identify it. It also has
//! a [type](node) which describes its intended function in the scene.
//!
//! Nodes are abstract containers for data. The interpretation depends
//! on the node type. Nodes can also be [connected to each
//! other](https://nsi.readthedocs.io/en/latest/guidelines.html#basic-scene-anatomy)
//! to express relationships.
//!
//! Data is stored on nodes as *attributes*. Each attribute has a name
//! which is unique on the node and a type which describes the kind of
//! data it holds (strings, integer numbers, floating point numbers,
//! etc.). The standard ɴsɪ attribute names are exposed as typed
//! constants -- see [Typed attribute names](#typed-attribute-names) below.
//!
//! Relationships and data flow between nodes are represented as
//! connections. Connections have a source and a destination. Both can
//! be either a node or a specific attribute of a node. There are no
//! type restrictions for connections in the interface itself. It is
//! acceptable to connect attributes of different types or even
//! attributes to nodes. The validity of such connections depends on
//! the types of the nodes involved.
//!
//! What we refer to as the ɴsɪ has two major components:
//!
//! 1. Methods to create nodes, attributes and their connections. These are
//!    attached to a rendering [`Context`].
//!
//! 2. [Nodes](node) understood by the renderer.
//!
//! Much of the complexity and expressiveness of the interface comes
//! from
//! [the supported nodes](https://nsi.readthedocs.io/en/latest/nodes.html).
//!
//! The first part was kept deliberately simple to make it easy to
//! support multiple ways of creating nodes.
//!
//! ## Examples
//!
//! ```
//! // Create a context to send the scene to.
//! let ctx = nsi::Context::new(None).expect("Could not create NSI context.");
//!
//! // Create a dodecahedron.
//!
//! // 12 regular pentagon faces.
//! let face_index: [i32; 60] = [
//!     0, 16, 2, 10, 8, 0, 8, 4, 14, 12, 16, 17, 1, 12, 0, 1, 9, 11, 3, 17, 1,
//!     12, 14, 5, 9, 2, 13, 15, 6, 10, 13, 3, 17, 16, 2, 3, 11, 7, 15, 13, 4,
//!     8, 10, 6, 18, 14, 5, 19, 18, 4, 5, 19, 7, 11, 9, 15, 7, 19, 18, 6,
//! ];
//!
//! // Golden ratio.
//! let phi: f32 = 0.5 * (1.0 + 5_f32.sqrt());
//!
//! // Golden ratio conjugate.
//! let phi_c: f32 = phi - 1.0;
//!
//! // 20 control points, each a Point3F32 ([f32; 3]).
//! // Length divisibility by 3 is enforced at the type level.
//! let positions: [nsi::Point3F32; 20] = [
//!     [1., 1., 1.],
//!     [1., 1., -1.],
//!     [1., -1., 1.],
//!     [1., -1., -1.],
//!     [-1., 1., 1.],
//!     [-1., 1., -1.],
//!     [-1., -1., 1.],
//!     [-1., -1., -1.],
//!     [0., phi_c, phi],
//!     [0., phi_c, -phi],
//!     [0., -phi_c, phi],
//!     [0., -phi_c, -phi],
//!     [phi_c, phi, 0.],
//!     [phi_c, -phi, 0.],
//!     [-phi_c, phi, 0.],
//!     [-phi_c, -phi, 0.],
//!     [phi, 0., phi_c],
//!     [phi, 0., -phi_c],
//!     [-phi, 0., phi_c],
//!     [-phi, 0., -phi_c],
//! ];
//!
//! // Create a new mesh node and call it 'dodecahedron'.
//! ctx.create("dodecahedron", nsi::MESH, None);
//!
//! // Connect the 'dodecahedron' node to the scene's root.
//! ctx.connect("dodecahedron", None, nsi::ROOT, "objects", None);
//!
//! // Define the geometry of the 'dodecahedron' node.
//! ctx.set_attribute(
//!     "dodecahedron",
//!     &[
//!         // Typed name: `nsi::POSITION` is `Attribute<[nsi::Point3F32]>`.
//!         // Wrong-shape data (e.g. a `&[f32]`) is rejected by rustc at
//!         // this call site.
//!         nsi::point_slice!(nsi::POSITION, &positions),
//!         nsi::i32_slice!("P.indices", &face_index),
//!         // 5 vertices per each face.
//!         nsi::i32_slice!("nvertices", &[5; 12]),
//!         // Render this as a subdivison surface.
//!         nsi::string!("subdivision.scheme", "catmull-clark"),
//!         // Crease each of the dodecahedron's 30 edges. Each edge is
//!         // a pair (start, end) of vertex indices into `positions`,
//!         // so this list is twice as long as `creasesharpness`.
//!         nsi::i32_slice!(
//!             "subdivision.creasevertices",
//!             &[
//!                 0, 8, 0, 12, 0, 16, 1, 9, 1, 12, 1, 17, 2, 10, 2, 13,
//!                 2, 16, 3, 11, 3, 13, 3, 17, 4, 8, 4, 14, 4, 18, 5, 9,
//!                 5, 14, 5, 19, 6, 10, 6, 15, 6, 18, 7, 11, 7, 15, 7, 19,
//!                 8, 10, 9, 11, 12, 14, 13, 15, 16, 17, 18, 19,
//!             ]
//!         ),
//!         nsi::f32_slice!("subdivision.creasesharpness", &[4.2; 30]),
//!     ],
//! );
//! ```
//! ## More Examples
//!
//! These can be found in the [`examples`](https://github.com/virtualritz/nsi/tree/master/examples)
//! folder.
//!
//! *All the examples in this crate require a (free) [3Delight](https://www.3delight.com/)
//! installation to run!*
//!
//! ### Interactive
//!
//! Demonstrates using the [`FnStatus`] callback closure during rendering and a
//! channel for communicating between main- and rendering thread(s).
//!
//! ### Jupyter
//!
//! Render directly into a Jupyter notebook.
//!
//! Follow
//! [these instructions](https://github.com/google/evcxr/blob/master/evcxr_jupyter/README.md)
//! to get a Rust Jupyter kernel up and running first.
//!
//! ### Output
//!
//! This is a full [`output`] example showing color conversion and writing data
//! out to 8bit/channel PNG and 32bit/channel (float) OpenEXR formats.
//!
//! ### Volume
//!
//! Demonstrates rendering an [OpenVDB](https://www.openvdb.org/) asset. Mostly
//! through the [`toolbelt`] helpers.
//!
//! ## Crate Organization
//!
//! The `nsi` crate is a facade. The work is split across smaller crates so
//! consumers can pick the layer they need:
//!
//! - [`nsi-trait`](https://crates.io/crates/nsi-trait) -- pure-Rust trait
//!   crate. Defines [`Nsi`] (`self` _is_ the context), the [`Attribute`]
//!   typed-name machinery, [`NodeType`], [`Action`], and the standard
//!   node-type / attribute-name constants. No FFI deps.
//! - [`nsi-ffi-wrap`](https://crates.io/crates/nsi-ffi-wrap) -- FFI wrapper.
//!   Provides [`Context`], the C-API loader (dynamic via `dlopen2` or static
//!   via `link_lib3delight`), the parameter macros (`f32!`, `point_slice!`,
//!   …), and [`FfiApiAdapter`] which exposes any pure-Rust [`Nsi`] impl
//!   through the C API (handle-mapping is internal, via a factory closure).
//! - [`nsi-3delight`](https://crates.io/crates/nsi-3delight) -- helpers for
//!   the 3Delight renderer (environment lights, shader graphs, etc.).
//! - [`nsi-toolbelt`](https://crates.io/crates/nsi-toolbelt) -- convenience
//!   scene-construction helpers (handle generation, append/prepend,
//!   transform shortcuts).
//! - [`nsi-jupyter`](https://crates.io/crates/nsi-jupyter) -- render into a
//!   Jupyter notebook.
//! - [`nsi-sys`](https://crates.io/crates/nsi-sys) -- auto-generated bindings
//!   for the NSI C header.
//! - [`nsi-procedural`](https://crates.io/crates/nsi-procedural) -- scaffolding
//!   for writing procedural-node plugins.
//!
//! The `nsi` crate re-exports everything from `nsi-ffi-wrap` (which itself
//! pulls types and constants from `nsi-trait`) so a typical user only needs
//! to depend on `nsi`.
//!
//! ## Typed Attribute Names
//!
//! The standard ɴsɪ attribute names are exposed as typed constants of
//! [`Attribute<T>`], where `T` describes the data shape the attribute
//! accepts. Examples:
//!
//! ```text
//! Attribute<f32>           -- e.g. nsi::FIELD_OF_VIEW
//! Attribute<i32>           -- e.g. nsi::U_COUNT, nsi::U_ORDER
//! Attribute<[f32]>         -- e.g. nsi::U_KNOT, nsi::TRIM_CURVES_KNOT
//! Attribute<[Point3F32]>   -- nsi::POSITION (length always divisible by 3)
//! Attribute<[Point4F32]>   -- nsi::WEIGHTED_POSITION (rational xyzw points)
//! Attribute<Matrix4F64>    -- nsi::MATRIX
//! ```
//!
//! The Rust constant identifiers are derived from the new ɴsɪ naming
//! convention (see the `naming-convention.md` chapter in the ɴsɪ spec). The
//! wire-side string literals each constant points to currently still hold
//! the legacy names (`"fov"`, `"nu"`, `"transformationmatrix"`, …) so the
//! constants work against today's renderers; switching to the new wire
//! names is a one-line change per constant when the renderer ships them.
//!
//! Renderer-specific or experimental attributes are added in their own
//! crates without touching this one -- `Attribute::new("custom_name")` is
//! `const`, so consumers declare their own typed constants.
//!
//! Note: the parameter macros (`nsi::f32!`, `nsi::point_slice!`, …)
//! currently accept the wire-side string literal directly; static
//! verification against [`Attribute<T>`] is in progress.
//!
//! ## Getting Pixels
//!
//! The crate has support for streaming pixels from the renderer, via callbacks
//! (i.e. closures) during and/or after rendering via the [`output`] module.
//! This module is enabled through the feature of the same name (see below).
//!
//! It should be straightforward to create an `async` implementation with this
//! or use channels to stream pixels back to a main thread (see the
//! `interactive` example).
//!
//! ## Cargo Features
//!
//! * [`output`] -- Add support for streaming pixels from the renderer to the
//!   calling context via closures.
//!
//! * [`jupyter`] -- Add support for rendering to Jupyter notebooks (when using
//!   a [Rust Jupyter kernel](https://github.com/google/evcxr)).
//!
//! * [`toolbelt`] -- Add convenience methods that work with a [`Context`].
//!
//! * [`delight`] -- Add some nodes & shaders specific to 3Delight.
//!
//! * `nightly` -- Enable some unstable features (suggested if you build with a
//!   `nightly` toolchain)
//!
//! * `ustr_handles` -- use [`ustr`](https://crates.io/crates/ustr) for node
//!   handles. This will give a you a speed boost if your node names aren't
//!   changing while an app using ɴsɪ is running but is not advised otherwise
//!   (`ustr` are never freed).
//!
//! ## Linking Style
//!
//! The 3Delight dynamic library (`lib3delight`) can either be linked to during
//! build or loaded at runtime.
//!
//! By default the lib is loaded at runtime.
//!
//! * Load `lib3deligh` at runtime (default). This has several advantages:
//!
//!   1. If you ship your application or library you can ship it without the
//!      library. It can still run and will print an informative error if the
//!      library cannot be loaded.
//!
//!   2. A user can install an updated version of the renderer and stuff will
//!      ‘just work’.
//!
//! * Dynamically link against `lib3delight`.
//!
//!   * `lib3delight` becomes a dependency. If it cannot be found your lib/app
//!     will not load/start.
//!
//!   * The feature is called `link_lib3delight`.
//!
//!   * You should disable default features (they are not needed/used) in this
//!     case:
//!
//!     ```toml
//!     [dependencies]
//!     nsi = { version = "0.7", default-features = false, features = ["link_lib3delight"] }
//!     ```
//!
//! * Download `lib3delight` during build.
//!
//!   * `lib3delight` is downloaded during build. Note that this may be an
//!     outdated version. This feature mainly exists for CI purposes.
//!
//!   * The feature is called `download_lib3delight`.

// Re-export everything from nsi_ffi_wrap, which includes Action from nsi-trait crate
pub use nsi_ffi_wrap::*;

#[cfg(feature = "delight")]
pub mod delight {
    //! Helpers for using ɴsɪ with 3Delight.
    pub use nsi_3delight::*;
}

#[cfg(feature = "jupyter")]
pub mod jupyter {
    //! Jupyter Notebook support.
    pub use nsi_jupyter::*;
}

#[cfg(feature = "toolbelt")]
pub mod toolbelt {
    //! Convenience methods for an ɴsɪ context.
    pub use nsi_toolbelt::*;
}