diff --git a/experiments/2024-10-30/src/app.rs b/experiments/2024-10-30/src/app.rs index 1e2298651..86ac8ca2f 100644 --- a/experiments/2024-10-30/src/app.rs +++ b/experiments/2024-10-30/src/app.rs @@ -1,3 +1,8 @@ +//! # [winit] event loop +//! +//! Nothing interesting to see here! This is all pretty standard stuff, as far +//! as winit apps are concerned. + use std::{collections::BTreeSet, sync::Arc}; use winit::{ diff --git a/experiments/2024-10-30/src/export.rs b/experiments/2024-10-30/src/export.rs index 84fb25792..edf126e78 100644 --- a/experiments/2024-10-30/src/export.rs +++ b/experiments/2024-10-30/src/export.rs @@ -1,3 +1,8 @@ +//! # Exporting geometry to 3MF +//! +//! Nothing interesting to see here! This is just a thin layer on top of +//! [3mf-rs](threemf). + use std::{collections::BTreeMap, fs::File}; use crate::geometry::{Operation, OpsLog, Vertex}; diff --git a/experiments/2024-10-30/src/geometry/mod.rs b/experiments/2024-10-30/src/geometry/mod.rs index 68f10af27..a00aad739 100644 --- a/experiments/2024-10-30/src/geometry/mod.rs +++ b/experiments/2024-10-30/src/geometry/mod.rs @@ -1,3 +1,22 @@ +//! Core geometry representation +//! +//! This is the core of what this experiment is about! The goal was to build an +//! interactive core around a very simple geometry representation (a triangle +//! mesh). +//! +//! While the geometry representation is very basic, I actually expect that +//! follow-up experiments will add more layers on top to structure it (thereby +//! enriching it with topographical information), rather than replace it with +//! something inherently more advanced (like NURBS or whatever). +//! +//! The idea here is that the triangle mesh works as a uniform intermediate +//! representation for geometry (as I've already been working towards with the +//! mainline Fornjot code), that any necessary algorithms can be built around +//! of. But working that out is not the subject of this experiment. +//! +//! If you're interested in the details, I suggest you start with [`OpsLog`], +//! which is the entry point to this API, and work your way down from there. + mod operation; mod ops_log; mod primitives; diff --git a/experiments/2024-10-30/src/geometry/operation.rs b/experiments/2024-10-30/src/geometry/operation.rs index 27796e2d0..25a98c6c8 100644 --- a/experiments/2024-10-30/src/geometry/operation.rs +++ b/experiments/2024-10-30/src/geometry/operation.rs @@ -1,8 +1,34 @@ +//! # The core trait that ties everything together +//! +//! See [`Operation`]. + use std::fmt; use super::{Triangle, Vertex}; +/// # An operation +/// +/// Provides access to the uniform intermediate representation of operations, +/// which is a triangle mesh. +/// +/// This trait is implemented by all operations. Chiefly by the primitive +/// operations, [`Vertex`] and [`Triangle`], but also by +/// [`OpsLog`](crate::geometry::OpsLog) and various types of its supporting +/// infrastructure. +/// +/// Even though the geometry representation in this experiment is much more +/// basic than what follow-up experiments are expected to explore, this +/// multitude of implementors is a good sign for the flexibility of this +/// concept. pub trait Operation: fmt::Display { + /// # The vertices that are part of the operation's uniform representation + /// + /// Many callers won't have to bother with this method, as the vertices are + /// also available indirectly through [`Operation::triangles`]. But this is + /// used by the viewer, for example, to render the shape as it is + /// constructed, vertex by vertex. fn vertices(&self, vertices: &mut Vec); + + /// # The triangles that are part of the operation's uniform representation fn triangles(&self, triangles: &mut Vec); } diff --git a/experiments/2024-10-30/src/geometry/ops_log.rs b/experiments/2024-10-30/src/geometry/ops_log.rs index 6eb02c3a9..d8f60bc5e 100644 --- a/experiments/2024-10-30/src/geometry/ops_log.rs +++ b/experiments/2024-10-30/src/geometry/ops_log.rs @@ -1,16 +1,81 @@ +//! # The operations log and supporting infrastructure +//! +//! See [`OpsLog`]. + use std::fmt; use tuples::CombinRight; use super::{Operation, Triangle, Vertex}; +/// # The operations that make up geometry, ordered in a specific sequence +/// +/// This is the entry point to the API that is used to create geometry. You +/// create an instance of [`OpsLog`], call its methods to create operations, and +/// can later look at the final result, or any intermediate step. +/// +/// A core idea here is that operations are the primitive that make up any +/// geometry. This is different from mainline Fornjot code, in which geometry +/// (and topology) is made up of *objects* instead. +/// +/// In this new model, objects and operations are the same thing. The most basic +/// operations just create a single object. More complex operations could +/// combine multiple operations (or objects) into higher-level operations (or +/// objects). But they all present [through the same interface](Operation). +/// +/// Right now, the only operations available are [`Vertex`] and [`Triangle`]. +/// [`Vertex`] is the lower-level of the two, while [`Triangle`] builds on top +/// of it to create a higher-level operation/object. +/// +/// It might be worth noting that [`OpsLog`] is itself an operation (i.e. it +/// implements [`Operation`]). It is simply the operation that unifies all the +/// operations that were added to it, and creates the uniform intermediate +/// representation of the complete shape. +/// +/// ## Adding operations +/// +/// Operations are added using the [`OpsLog::vertex`] and [`OpsLog::triangle`] +/// methods. Later prototypes might move these to extension traits or something, +/// to support arbitrary operations, but here those are hardcoded and the only +/// ones supported. +/// +/// Those methods employ some trickery in the form of the [`OperationResult`] +/// return value, which allows the caller both to chain calls to add multiple +/// objects in a row, but then also access every single operation created in a +/// convenient way. +/// +/// Check out the code of [`crate::model::model`] to see how that looks. I'm +/// pretty happy with how it turned out, but we'll have to see how well it +/// scales, once arbitrary (user-defined) operations have to be supported. #[derive(Default)] pub struct OpsLog { + /// # The operations + /// + /// This doesn't store the operations directly. The goal here is to provide + /// access to every intermediate state that the geometry construction went + /// through, and storing operations directly won't achieve that. + /// + /// For example, if you were to look at the second-to-last operation in + /// isolation, you wouldn't see the whole shape before the last operation, + /// but only the single triangle or something that made up this + /// second-to-last operation. + /// + /// So what this does instead, is wrap operations into + /// [`OperationInSequence`], which knows about all previous operations and + /// can provide the intermediate state. pub operations: Vec, + + /// # Which operation is currently selected + /// + /// This is a UI concept and this field probably shouldn't live here. For + /// now, it still does though. pub selected: usize, } impl OpsLog { + /// # Add a vertex + /// + /// See documentation of [`OpsLog`] for context. pub fn vertex( &mut self, vertex: impl Into, @@ -31,6 +96,9 @@ impl OpsLog { } } + /// # Add a triangle + /// + /// See documentation of [`OpsLog`] for context. pub fn triangle( &mut self, triangle: impl Into, @@ -51,20 +119,24 @@ impl OpsLog { } } + /// # Used by the UI; not interesting pub fn select_last(&mut self) { self.selected = self.operations.len().saturating_sub(1); } + /// # Used by the UI; not interesting pub fn select_next(&mut self) { if self.selected < self.operations.len() { self.selected += 1; } } + /// # Used by the UI; not interesting pub fn select_previous(&mut self) { self.selected = self.selected.saturating_sub(1); } + /// # Used by the UI; not interesting pub fn selected(&self) -> Option<&dyn Operation> { self.operations.get(self.selected).map(|op| op as &_) } @@ -94,6 +166,27 @@ impl Operation for OpsLog { } } +/// # Representation of an intermediate state in constructing a shape +/// +/// This is a wrapper around an operation, but it also knows about the previous +/// operation in the sequence (which itself is expected to know about its +/// previous operation). +/// +/// [`OperationInSequence`] is used by [`OpsLog`] to provide the ability to look +/// at every intermediate state. +/// +/// ## Efficiency +/// +/// This is implemented in a rather inefficient way, by cloning the uniform +/// representation of the operations it references, via [`ClonedOperation`]. +/// +/// This is fine within the context of this experiment, and I'm not too worried +/// about it long-term either. Operations can live in centralized stores, which +/// can return handles to refer to them. Similar to what current mainline +/// Fornjot does with its topological objects. +/// +/// I'm not sure if that would be the best way to do it, but at least it +/// wouldn't create multiple clones of every operation. pub struct OperationInSequence { pub operation: ClonedOperation, pub previous: Option, @@ -121,6 +214,10 @@ impl fmt::Display for OperationInSequence { } } +/// # Allow chaining calls to create operations, but also access all results +/// +/// This is an implementation details of [`OpsLog`]. See documentation on adding +/// operations there. pub struct OperationResult<'r, T> { operations: &'r mut OpsLog, results: T, @@ -167,6 +264,7 @@ impl<'r, T> OperationResult<'r, T> { } } +/// # Implementation details of [`OperationInSequence`] pub struct ClonedOperation { pub description: String, pub vertices: Vec, diff --git a/experiments/2024-10-30/src/geometry/primitives.rs b/experiments/2024-10-30/src/geometry/primitives.rs index 71f004d5a..f220176a1 100644 --- a/experiments/2024-10-30/src/geometry/primitives.rs +++ b/experiments/2024-10-30/src/geometry/primitives.rs @@ -1,11 +1,21 @@ +//! # The primitive operations +//! +//! Future experiments may support user-defined operations on top of those, but +//! for now this is all there is. + use std::fmt; use crate::math::Point; use super::Operation; +/// # A vertex +/// +/// This is the most basic operation, which creates a single vertex, represented +/// as a point in 3D space. #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct Vertex { + /// # The point that represents the vertex pub point: Point, } @@ -35,8 +45,27 @@ impl Operation for Vertex { fn triangles(&self, _: &mut Vec) {} } +/// # A triangle +/// +/// Combines three [`Vertex`] instances into a triangle. This is still very +/// simple, but still kind of a prototype for how other non-trivial operations +/// that combine more primitives ones might later look like. #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct Triangle { + /// # The vertices of the triangle + /// + /// This embeds a copy of the vertex, which isn't great for two reasons: + /// + /// - It is rather space-inefficient, as multiple copies of every vertex are + /// bound to exist within the shape. + /// - It loses the identity of the vertex, making it harder to highlight it + /// in the viewer, for example, or to run validation code that could + /// possibly exist later. + /// + /// For now, that's fine. Follow-up experiments will likely use some kind + /// of centralized storage of operations, and handles that refer to the + /// operations in those stores, similar to what mainline Fornjot does with + /// its topographic objects. pub vertices: [Vertex; 3], } diff --git a/experiments/2024-10-30/src/main.rs b/experiments/2024-10-30/src/main.rs index b3c75eaeb..d33b2b4c7 100644 --- a/experiments/2024-10-30/src/main.rs +++ b/experiments/2024-10-30/src/main.rs @@ -1,3 +1,12 @@ +//! # Fornjot - Experiment 2024-10-30 +//! +//! Please check out the accompanying `README.md` file for high-level +//! documentation. +//! +//! As for the details, you are in the right place! I recommend you get started +//! with the [`geometry`] module, as that is the core of what this experiment is +//! about. + #![allow(clippy::module_inception)] mod app; diff --git a/experiments/2024-10-30/src/math.rs b/experiments/2024-10-30/src/math.rs index 0830b7338..427a30e8e 100644 --- a/experiments/2024-10-30/src/math.rs +++ b/experiments/2024-10-30/src/math.rs @@ -1,3 +1,9 @@ +//! # Core math primitives +//! +//! This is just a little math library to support the code in +//! [`geometry`](crate::geometry). Nothing special. + + use std::{cmp::Ordering, ops}; use iter_fixed::IntoIteratorFixed; diff --git a/experiments/2024-10-30/src/model.rs b/experiments/2024-10-30/src/model.rs index d0fd7c560..2a82efcd7 100644 --- a/experiments/2024-10-30/src/model.rs +++ b/experiments/2024-10-30/src/model.rs @@ -1,3 +1,12 @@ +//! # The hardcoded geometry (a cube) +//! +//! This is just the hardcoded geometry that I'm using as a test case for this +//! experiment. I kept it simple, as there wasn't much point in trying to +//! exercise the code in [`geometry`](crate::geometry) with anything complex. +//! It's pretty clear that it's limited. +//! +//! I expect follow-up experiments to have more interesting geometry. + use crate::geometry::OpsLog; pub fn model(ops: &mut OpsLog) { diff --git a/experiments/2024-10-30/src/render/mod.rs b/experiments/2024-10-30/src/render/mod.rs index 74a7cfbcf..101988bf2 100644 --- a/experiments/2024-10-30/src/render/mod.rs +++ b/experiments/2024-10-30/src/render/mod.rs @@ -1,3 +1,25 @@ +//! # Rendering infrastructure +//! +//! Even though most of the work for this prototype went into the renderer, it +//! is not the most interesting aspect, and I'm not going to document it in +//! detail. It's a pretty basic architecture, optimized for the speed of having +//! written it, not speed of rendering. +//! +//! The most interesting aspect in terms of what this experiment could mean for +//! Fornjot, is that this renderer has a direct dependency on +//! [`geometry`](crate::geometry). Versus the current Fornjot renderer, which +//! only communicates with the CAD core through another interop crate. +//! +//! I'm pretty sure that whatever happens with these experiments, I'll go with +//! the simpler approach going forward. I'm not even sure anymore what the +//! thinking behind the original design was (it's been years). +//! +//! I probably overestimated the importance of making things pluggable, and +//! making parts of Fornjot usable in isolation. Going forward, I'm viewing the +//! renderer as something that is very purpose-built for the needs of developing +//! Fornjot. Not something I'd expect anybody building on top of Fornjot would +//! want to use, except maybe to get started. + mod geometry; mod pipelines; mod renderer;