mirror of
https://github.com/hannobraun/Fornjot
synced 2025-01-09 17:56:56 +00:00
Document the code of the experiment
This commit is contained in:
parent
b9a8dabe29
commit
059d5e616f
@ -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 std::{collections::BTreeSet, sync::Arc};
|
||||||
|
|
||||||
use winit::{
|
use winit::{
|
||||||
|
@ -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 std::{collections::BTreeMap, fs::File};
|
||||||
|
|
||||||
use crate::geometry::{Operation, OpsLog, Vertex};
|
use crate::geometry::{Operation, OpsLog, Vertex};
|
||||||
|
@ -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 operation;
|
||||||
mod ops_log;
|
mod ops_log;
|
||||||
mod primitives;
|
mod primitives;
|
||||||
|
@ -1,8 +1,34 @@
|
|||||||
|
//! # The core trait that ties everything together
|
||||||
|
//!
|
||||||
|
//! See [`Operation`].
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use super::{Triangle, Vertex};
|
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 {
|
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<Vertex>);
|
fn vertices(&self, vertices: &mut Vec<Vertex>);
|
||||||
|
|
||||||
|
/// # The triangles that are part of the operation's uniform representation
|
||||||
fn triangles(&self, triangles: &mut Vec<Triangle>);
|
fn triangles(&self, triangles: &mut Vec<Triangle>);
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,81 @@
|
|||||||
|
//! # The operations log and supporting infrastructure
|
||||||
|
//!
|
||||||
|
//! See [`OpsLog`].
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use tuples::CombinRight;
|
use tuples::CombinRight;
|
||||||
|
|
||||||
use super::{Operation, Triangle, Vertex};
|
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)]
|
#[derive(Default)]
|
||||||
pub struct OpsLog {
|
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<OperationInSequence>,
|
pub operations: Vec<OperationInSequence>,
|
||||||
|
|
||||||
|
/// # 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,
|
pub selected: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpsLog {
|
impl OpsLog {
|
||||||
|
/// # Add a vertex
|
||||||
|
///
|
||||||
|
/// See documentation of [`OpsLog`] for context.
|
||||||
pub fn vertex(
|
pub fn vertex(
|
||||||
&mut self,
|
&mut self,
|
||||||
vertex: impl Into<Vertex>,
|
vertex: impl Into<Vertex>,
|
||||||
@ -31,6 +96,9 @@ impl OpsLog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Add a triangle
|
||||||
|
///
|
||||||
|
/// See documentation of [`OpsLog`] for context.
|
||||||
pub fn triangle(
|
pub fn triangle(
|
||||||
&mut self,
|
&mut self,
|
||||||
triangle: impl Into<Triangle>,
|
triangle: impl Into<Triangle>,
|
||||||
@ -51,20 +119,24 @@ impl OpsLog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Used by the UI; not interesting
|
||||||
pub fn select_last(&mut self) {
|
pub fn select_last(&mut self) {
|
||||||
self.selected = self.operations.len().saturating_sub(1);
|
self.selected = self.operations.len().saturating_sub(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Used by the UI; not interesting
|
||||||
pub fn select_next(&mut self) {
|
pub fn select_next(&mut self) {
|
||||||
if self.selected < self.operations.len() {
|
if self.selected < self.operations.len() {
|
||||||
self.selected += 1;
|
self.selected += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Used by the UI; not interesting
|
||||||
pub fn select_previous(&mut self) {
|
pub fn select_previous(&mut self) {
|
||||||
self.selected = self.selected.saturating_sub(1);
|
self.selected = self.selected.saturating_sub(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Used by the UI; not interesting
|
||||||
pub fn selected(&self) -> Option<&dyn Operation> {
|
pub fn selected(&self) -> Option<&dyn Operation> {
|
||||||
self.operations.get(self.selected).map(|op| op as &_)
|
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 struct OperationInSequence {
|
||||||
pub operation: ClonedOperation,
|
pub operation: ClonedOperation,
|
||||||
pub previous: Option<ClonedOperation>,
|
pub previous: Option<ClonedOperation>,
|
||||||
@ -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> {
|
pub struct OperationResult<'r, T> {
|
||||||
operations: &'r mut OpsLog,
|
operations: &'r mut OpsLog,
|
||||||
results: T,
|
results: T,
|
||||||
@ -167,6 +264,7 @@ impl<'r, T> OperationResult<'r, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Implementation details of [`OperationInSequence`]
|
||||||
pub struct ClonedOperation {
|
pub struct ClonedOperation {
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub vertices: Vec<Vertex>,
|
pub vertices: Vec<Vertex>,
|
||||||
|
@ -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 std::fmt;
|
||||||
|
|
||||||
use crate::math::Point;
|
use crate::math::Point;
|
||||||
|
|
||||||
use super::Operation;
|
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)]
|
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
pub struct Vertex {
|
pub struct Vertex {
|
||||||
|
/// # The point that represents the vertex
|
||||||
pub point: Point,
|
pub point: Point,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,8 +45,27 @@ impl Operation for Vertex {
|
|||||||
fn triangles(&self, _: &mut Vec<Triangle>) {}
|
fn triangles(&self, _: &mut Vec<Triangle>) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # 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)]
|
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
pub struct Triangle {
|
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],
|
pub vertices: [Vertex; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)]
|
#![allow(clippy::module_inception)]
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
|
@ -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 std::{cmp::Ordering, ops};
|
||||||
|
|
||||||
use iter_fixed::IntoIteratorFixed;
|
use iter_fixed::IntoIteratorFixed;
|
||||||
|
@ -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;
|
use crate::geometry::OpsLog;
|
||||||
|
|
||||||
pub fn model(ops: &mut OpsLog) {
|
pub fn model(ops: &mut OpsLog) {
|
||||||
|
@ -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 geometry;
|
||||||
mod pipelines;
|
mod pipelines;
|
||||||
mod renderer;
|
mod renderer;
|
||||||
|
Loading…
Reference in New Issue
Block a user