Document code of current experiment

This commit is contained in:
Hanno Braun 2025-03-17 19:30:37 +01:00
parent cf7c1f6d52
commit 53b3a55250
28 changed files with 270 additions and 0 deletions

View File

@ -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::{

View File

@ -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::object::Object;

View File

@ -1 +1,10 @@
//! # Extra stuff that didn't seem to fit anywhere else
//!
//! I'm not sure why this module needs to exist, honestly. Maybe it could be
//! merged into [`geometry`](crate::geometry), but it's different from the code
//! there, in that its mostly concerned with interfacing with an external
//! library.
//!
//! Not sure. Maybe in the next prototype, this code will land somewhere else.
pub mod triangulate;

View File

@ -1,3 +1,7 @@
//! # Converting faces into triangle meshes
//!
//! See [triangulate].
use std::{
collections::{BTreeSet, VecDeque},
mem,
@ -12,6 +16,16 @@ use crate::{
topology::face::Face,
};
/// # Convert a face into a triangle mesh
///
/// So far, this is pretty limited, since all faces are assumed to be flat, and
/// the half-edges that bound them are all straight. But it does support concave
/// faces (and this ability is also used to support holes).
///
/// Once surfaces learn to generate a bunch of points to approximate their
/// geometry, it shouldn't be too hard to expand the code here to adapt that,
/// which would add support for curved surfaces. This is already using Delaunay
/// under the hood, so it wouldn't be a big step.
pub fn triangulate(face: &Face) -> TriMesh {
let points = points(face);
let triangles = triangles(&points);

View File

@ -1,3 +1,8 @@
//! # Various geometry tools
//!
//! These are distinct from the core b-rep representation, for which this module
//! is a dependency.
mod sketch;
mod surface;
mod tri_mesh;

View File

@ -10,11 +10,28 @@ use crate::{
},
};
/// # A 2D sketch, which one way to create faces
///
/// So far, sketches are pretty limited: They are just a bunch of ordered
/// points. Those points can be converted into the straight half-edges that
/// bound a face.
///
/// You could create this struct manually, but there's also a [`From`]
/// implementation that can create an instance of this struct from any iterator
/// that yields points.
///
/// The next step here, would be to add support for curved edges. But this would
/// need to be supported on the topology side first.
pub struct Sketch {
pub points: Vec<Point<2>>,
}
impl Sketch {
/// # Convert the sketch into a face
///
/// The `surface` parameter defines the plane which is then used to convert
/// the 2D sketch into a 3D face. In the future, more surfaces than just
/// planes would be supported, but we're not there yet.
pub fn to_face(&self, surface: Handle<Surface>) -> Face {
let mut vertices_by_local_point: BTreeMap<_, Vec<_>> = BTreeMap::new();
let vertices = self

View File

@ -1,9 +1,30 @@
use crate::math::{Plane, Point, Vector};
/// # A trait for encoding surface geometry
///
/// So far, this is mostly cosmetic, as the only implementor is [`Plane`]. I've
/// started extracting the interface of that into this trait though, as a first
/// step towards eventually supporting other kinds of surfaces.
///
/// I'd expect that this trait would need to be expanded before that can be
/// fully realized.
pub trait SurfaceGeometry {
/// # Convert a surface-local point to 3D
fn point_from_local(&self, point: Point<2>) -> Point<3>;
/// # Project a 3D point into the surface
fn project_point(&self, point: Point<3>) -> Point<2>;
/// # Flip the surface
///
/// Maybe this can later merge with [`SurfaceGeometry::translate`] into a
/// more general `transform` method.
fn flip(&self) -> Box<dyn SurfaceGeometry>;
/// # Translate the surface
///
/// I expect this to transform into a more general `transform` method at
/// some point. But so far, I haven't needed much more than this.
fn translate(&self, offset: Vector<3>) -> Box<dyn SurfaceGeometry>;
}

View File

@ -1,5 +1,15 @@
use super::Triangle;
/// # A triangle mesh
///
/// Triangle meshes are the uniform intermediate representation for geometry.
/// The idea here is to have a single representation that is both (relatively)
/// easy to generate and to operate on.
///
/// This is only intended as an _intermediate_ representation though! This isn't
/// fully worked out yet in this experiment, but the idea is to keep the
/// original objects that generated the triangle mesh around, so you can always
/// generate a more accurate triangle mesh, if needed.
#[derive(Debug)]
pub struct TriMesh {
pub triangles: Vec<MeshTriangle>,
@ -12,15 +22,24 @@ impl TriMesh {
}
}
/// # Merge this triangle mesh with another
///
/// This just creates a new triangle mesh that has all the triangles from
/// both meshes. Nothing more fancy than that, so far!
pub fn merge(mut self, other: Self) -> Self {
self.triangles.extend(other.triangles);
self
}
/// # Iterate over all the triangles in the mesh
pub fn all_triangles(&self) -> impl Iterator<Item = Triangle<3>> {
self.triangles.iter().map(|triangle| triangle.inner)
}
/// # Iterate over the triangles in the mesh that are not marked internal
///
/// See [`MeshTriangle`] for an explanation of internal and external
/// triangles.
pub fn external_triangles(&self) -> impl Iterator<Item = Triangle<3>> {
self.triangles.iter().filter_map(|triangle| {
(!triangle.is_internal).then_some(triangle.inner)
@ -28,6 +47,18 @@ impl TriMesh {
}
}
/// # A triangle in a triangle mesh
///
/// This is just a regular triangle, with an additional flag to mark it as
/// internal.
///
/// Faces only ever have a single boundary. Holes are realized by having this
/// boundary touch itself in one place, where it connects the inside and the
/// outside.
///
/// The half-edges where that happens are marked as "internal", and so are any
/// triangles created from them. This method can be used to filter those out,
/// for example for export to external file formats.
#[derive(Debug)]
pub struct MeshTriangle {
pub inner: Triangle<3>,

View File

@ -7,12 +7,16 @@ use crate::{
use super::{MeshTriangle, TriMesh};
/// # A triangle
///
/// This should probably move to [`math`](crate::math). Not sure!
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Triangle<const D: usize> {
pub points: [Point<D>; 3],
}
impl<const D: usize> Triangle<D> {
/// # Compute the center point of the triangle
pub fn center(&self) -> Point<D> {
let [a, b, c] = self.points;
let coords = (a.coords + b.coords + c.coords) / 3.;

View File

@ -1,3 +1,13 @@
//! # Fornjot - Experiment 2024-12-09
//!
//! Please check the accompanying `README.md` for context and high-level
//! documentation.
//!
//! As for the details, you're in the right place! I'd start with the [`model`],
//! [`topology`], [`operations`], and [`geometry`] modules, roughly in that
//! order. Those are core to the CAD stuff, while the rest is mostly there for
//! support.
#![allow(clippy::module_inception)]
mod app;

View File

@ -1,3 +1,8 @@
//! # Generic math types
//!
//! I'm not go document the types in here any further. It's just regular old
//! math, nothing special.
mod bivector;
mod plane;
mod point;

View File

@ -6,6 +6,10 @@ use crate::{
topology::surface::Surface,
};
/// # The function that creates the current test model, a cube
///
/// Nothing really special about this. It's just the current test case that I'm
/// using to develop the rest.
pub fn model() -> HandleAny {
let top = {
let sketch = Sketch::from([

View File

@ -2,11 +2,30 @@ use std::{cmp::Ordering, fmt, ops::Deref, rc::Rc};
use super::{HandleAny, Object};
/// # A typed handle to an object
///
/// Handles provide a layer of identity to objects, enabling the same object to
/// be shared from multiple locations in the object graph.
///
/// Right now, this doesn't make much of a difference, but eventually it's going
/// to be important for various validation checks. (See the validation stuff in
/// the current mainline code for more information on that.)
///
/// The longer-term idea here, is to use this as a reference to an object that
/// is stored in a way that makes this object performant to access. Right now,
/// we just allocate all objects within [`Rc`] though, as a placeholder.
pub struct Handle<T> {
inner: Rc<T>,
}
impl<T> Handle<T> {
/// # Create a new handle
///
/// Eventually, this type probably won't have a public constructor, and
/// you'll create a `Handle` via some kind of collection/arena thing.
///
/// For now, objects just live on the heap, in reference-counted ([`Rc`])
/// allocations.
pub fn new(inner: T) -> Self {
Self {
inner: Rc::new(inner),
@ -18,10 +37,12 @@ impl<T> Handle<T>
where
T: Object + 'static,
{
/// # Create an untyped handle that refers to the same object
pub fn to_any(&self) -> HandleAny {
self.clone().into_any()
}
/// # Convert this handle into an untyped one that refers to the same object
pub fn into_any(self) -> HandleAny {
HandleAny { inner: self.inner }
}

View File

@ -4,6 +4,12 @@ use crate::geometry::TriMesh;
use super::Object;
/// # An untyped handle that can be used to abstract over objects
///
/// Can be used wherever you need to iterate over objects of various types.
///
/// See documentation of `Handle` for more context on handles and object
/// storage.
#[derive(Clone)]
pub struct HandleAny {
pub(super) inner: Rc<dyn Object>,

View File

@ -4,6 +4,16 @@ use crate::geometry::TriMesh;
use super::HandleAny;
/// # A trait that is implemented by all "objects", whatever those are
///
/// This trait is the problem child of this experiment. I wanted to use it to
/// create a much more detailed and interactive view of objects, but this ended
/// up as just a simple tree that is rendered next to the object.
///
/// It's probably safe to ignore most of the stuff here. My current plan is to
/// strip this down to its essentials, completely remove the object tree from
/// the debug view, and experiment with other means of providing visibility into
/// how shapes are structured and constructed.
pub trait Object {
fn display(&self, f: &mut fmt::Formatter) -> fmt::Result;
fn tri_mesh(&self) -> TriMesh;

View File

@ -6,6 +6,11 @@ use crate::{
},
};
/// # Extension trait for objects that can be connected
///
/// At this point, this is only implemented for faces, to connect to of them,
/// creating a solid. It's conceivable to also implement it for half-edges, for
/// example, to connect those into a face.
pub trait ConnectExt {
/// # Connect two faces by creating a side wall of faces from their vertices
///

View File

@ -3,7 +3,12 @@ use crate::{
topology::{face::Face, surface::Surface},
};
/// # Extension trait for objects that can be flipped
pub trait FlipExt {
/// # Flip a face or surface
///
/// This might be subsumed by a more general "transform" operation later.
/// Not sure!
fn flip(&self) -> Self;
}

View File

@ -1,3 +1,5 @@
//! # Various operations that can create and transform objects
pub mod connect;
pub mod flip;
pub mod sweep;

View File

@ -6,6 +6,10 @@ use crate::{
use super::{connect::ConnectExt, flip::FlipExt, translate::TranslateExt};
/// # Extension trait for sweeping things
///
/// Right now, this is only implemented for faces, but it could also get
/// implemented for half-edges or solids later.
pub trait SweepExt {
/// # Sweep a face along a path, creating a solid
///

View File

@ -6,6 +6,12 @@ use crate::{
},
};
/// # Extension trait for things that can be translated
///
/// This is the most versatile operation right now, as it's implemented for many
/// different types of objects.
///
/// I expect this to morph into a more general "transform" operation over time.
pub trait TranslateExt {
fn translate(&self, offset: impl Into<Vector<3>>) -> Self;
}

View File

@ -1,3 +1,29 @@
//! # Rendering infrastructure
//!
//! This has mostly been inherited from the previous experiment, with some
//! extensions. The remainder of this documtation is mostly copied from that
//! previous experiment.
//!
//! 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 pipeline;
mod renderer;

View File

@ -10,6 +10,17 @@ use crate::{
use super::{half_edge::HalfEdge, surface::Surface, vertex::Vertex};
/// # A face
///
/// Faces are defined by a surface (which, so far, is always a plane) and a
/// cycle of half-edges that bound the face on that surface.
///
/// Faces are the boundary of any solid. Solids can touch themselves, however,
/// to connect their external boundary to cavities on the inside, or enclose a
/// hole through the solid.
///
/// The faces in parts of the boundary where solids touch themselves are called
/// "internal".
#[derive(Debug)]
pub struct Face {
pub surface: Handle<Surface>,
@ -18,6 +29,10 @@ pub struct Face {
}
impl Face {
/// # Create a new face from its component parts
///
/// The more interesting way to create a face would be via a
/// [`Sketch`](crate::geometry::Sketch).
pub fn new(
surface: Handle<Surface>,
half_edges: impl IntoIterator<Item = Handle<HalfEdge>>,
@ -30,6 +45,11 @@ impl Face {
}
}
/// # Iterate over the half-edges of the face
///
/// In addition to the [`HalfEdge`] itself, which contains the vertex where
/// it starts, the vertex where the half-edge ends (the start vertex of the
/// next half-edge) is also provided.
pub fn half_edges_with_end_vertex(
&self,
) -> impl Iterator<Item = (&Handle<HalfEdge>, &Handle<Vertex>)> {

View File

@ -7,6 +7,16 @@ use crate::{
use super::vertex::Vertex;
/// # A half-edge
///
/// Half-edges bound faces. A half-edge only contains the vertex where it
/// starts. The end vertex is implicit (it is the start vertex of the next
/// half-edge in the same face). By doing it like this, each half-edge "owns"
/// its vertex, which simplifies the object graph, making it easier to change.
///
/// Since a face only has a single boundary, that boundary needs to touch itself
/// to connect the outside of the face with any holes on the inside. The
/// half-edges that touch other half-edges are marked as "internal".
pub struct HalfEdge {
pub start: Handle<Vertex>,
pub is_internal: bool,

View File

@ -1,3 +1,9 @@
//! # Topological b-rep primitives
//!
//! These are just some basics so far, to get something started with flat faces
//! and straight edges. I expect this to grow over the next experiments, as more
//! advanced geometry starts being supported.
pub mod face;
pub mod half_edge;
pub mod solid;

View File

@ -7,12 +7,19 @@ use crate::{
use super::face::Face;
/// # A solid
///
/// Solids are 3D objects that are bounded by faces.
#[derive(Clone)]
pub struct Solid {
faces: Vec<Handle<Face>>,
}
impl Solid {
/// # Create a solid from its component parts
///
/// Check out [`operations`](crate::operations) for more interesting ways to
/// create solids.
pub fn new(faces: impl IntoIterator<Item = Handle<Face>>) -> Self {
Self {
faces: faces.into_iter().collect(),

View File

@ -2,6 +2,13 @@ use std::fmt;
use crate::geometry::SurfaceGeometry;
/// # A surface
///
/// Surfaces are infinite 2D objects in 3D space. They are what defines faces,
/// which are bounded sections on a surface.
///
/// Surfaces own a reference to an implementation of `SurfaceGeometry`, which is
/// what defines them. So far, only planes are supported though.
pub struct Surface {
pub geometry: Box<dyn SurfaceGeometry>,
}

View File

@ -6,6 +6,7 @@ use crate::{
object::{HandleAny, Object},
};
/// # A vertex
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Vertex {
pub point: Point<3>,

View File

@ -5,6 +5,10 @@ use crate::{
object::{HandleAny, Object},
};
/// # This is just some connecting tissue between CAD objects and the renderer
///
/// This is part of the code that didn't work out as I hoped. I expect to remove
/// it in future experiments.
#[derive(Clone)]
pub struct OperationView {
operation: HandleAny,