Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge to master #187

Merged
merged 31 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4832de7
Add support for loading Wavefront .obj files
jdahlstrom Dec 19, 2023
9f6399a
Add vertex transform and normal generation methods
jdahlstrom Dec 24, 2023
9c85d02
Demonstrate OBJ file loading in solids
jdahlstrom Dec 24, 2023
35710a2
Move Normal type aliases to core, add to prelude
jdahlstrom Jul 21, 2024
87f105e
Improve doc comments
jdahlstrom Jul 22, 2024
f6508c8
Change ApproxEq epsilon to depend on the fp feature enabled
jdahlstrom Jul 22, 2024
6748c57
Enable reciprocal sqrt and vector normalize with all fp features
jdahlstrom Jul 22, 2024
e67f870
Update minifb from 0.25 to 0.27
jdahlstrom Jul 31, 2024
69288fd
Add As(Mut)Slice2 impl for &(mut) Buf2
jdahlstrom Aug 1, 2024
3840b78
Fix some Clippy warnings
jdahlstrom Aug 2, 2024
7fb81eb
Replace impl that triggered a lint on Rust 1.80
jdahlstrom Aug 6, 2024
099e875
Change generic parameters of VecN and vecN
jdahlstrom Aug 6, 2024
2980afc
Impl Affine for i32 and u32, Linear for i32
jdahlstrom Aug 6, 2024
655bec8
Express scalar bounds in terms of Affine/Linear instead of operators
jdahlstrom Aug 6, 2024
60c52ac
Allow vectors that are merely affine, not linear
jdahlstrom Aug 6, 2024
9679d31
Loosen Self bound of add/sub operator impls to Affine
jdahlstrom Aug 6, 2024
6ddf148
Loosen Self bound in Index(Mut) impl to Affine
jdahlstrom Aug 7, 2024
4b740c3
Give camera mode its own setter method
jdahlstrom Aug 8, 2024
450b4ae
Add builder type to make rendering calls easier to grasp
jdahlstrom Aug 9, 2024
67a67c9
Change crates demo to use Batch for rendering
jdahlstrom Aug 11, 2024
a986519
Clean up solids demo a lot
jdahlstrom Aug 9, 2024
dfe277a
Clean up stats code
jdahlstrom Aug 10, 2024
182db3c
Generalize some Vector methods to any Linear scalar
jdahlstrom Aug 7, 2024
5b9deac
Constify Matrix methods
jdahlstrom Aug 7, 2024
d65ac47
Change random distributions to not contain the RNG
jdahlstrom Aug 8, 2024
4a3122a
Impl Distrib for pairs of distributions
jdahlstrom Aug 8, 2024
de2a144
Rename Distrib::iter() to samples()
jdahlstrom Aug 8, 2024
2c5d61c
Improve rand doc comments, add examples
jdahlstrom Aug 8, 2024
2015937
Change FragmentShader to take Frag<V> instead of generic fragment
jdahlstrom Aug 1, 2024
6f85ca6
Update README
jdahlstrom Aug 14, 2024
c784a8c
Add a demo of sprite rendering
jdahlstrom Dec 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,19 @@ Done:
* Fully customizable rasterization stage
* Collecting rendering performance data
* Reading and writing pnm image files
* Reading and writing Wavefront .obj files
* Cubic Bezier curves and splines
* Simple random number generation and distributions
* Frontend for the minifb crate
* Frontend for wasm and minifb

In progress:

= Sprite and text primitives
= Simple bitmap font support
= Spherical, cube, etc UV mapping
= Procedural noise generation
= Reading and writing Wavefront .obj files
= Frontends for sdl2, softbuffer, ncurses, wasm

= Frontends for sdl2, softbuffer, ncurses

Planned:

Expand All @@ -82,29 +83,35 @@ Planned:

================================ Organization ================================

retrofire is split into several crates:
retrofire is split into several packages:

* core: math, renderer, utilities; no-std compatible
* front: frontends for writing simple graphical applications
* geom: geometric shapes and mesh builders
* geom: geometric shapes, mesh builders, model loading
* demos: binaries showcasing retrofire features

================================ Dependencies ================================

The minimum supported Rust version is the current stable, at least for now.

The `core` package has no external non-optional dependencies. It requires only
`alloc`, unless the optional feature `std` is enabled.
The `core` package only requires `alloc` and has no non-optional external
dependencies. However, because `no_std` lacks most floating-point functions,
the package is not fully functional unless either the `std`, `libm`, or `mm`
feature is enabled. Activating `std` additionally enables APIs that do I/O.

The `front` package can be used to write simple games and demos. It contains
simple window abstractions, one using the `minifb` crate and the other running
in the browser via WebAssembly.

The `front` crate contains a simple window abstraction wrapping the `minifb`
crate. It can be used to write simple games and demos.
The `geom` package has no external dependencies. It only requires `alloc`;
activating the optional feature `std` enables APIs doing I/O.

The `demos` crate contains sample applications using `front` and `minifb` to
exhibit various features of retrofire.
The `demos` package contains sample applications using `front` to exhibit
various features of retrofire.

================================== License ===================================

Copyright 2020-2023 Johannes Dahlström. retrofire is licensed under either of:
Copyright 2020-2024 Johannes Dahlström. retrofire is licensed under either of:

* Apache License, Version 2.0
(LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
Expand Down
7 changes: 7 additions & 0 deletions core/src/geom.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Basic geometric primitives.

use crate::math::vec::{Vec2, Vec3};

pub use mesh::Mesh;

pub mod mesh;
Expand All @@ -21,6 +23,11 @@ pub struct Tri<V>(pub [V; 3]);
#[repr(transparent)]
pub struct Plane<V>(pub(crate) V);

/// A surface normal.
// TODO Use distinct type rather than alias
pub type Normal3 = Vec3;
pub type Normal2 = Vec2;

pub const fn vertex<P, A>(pos: P, attrib: A) -> Vertex<P, A> {
Vertex { pos, attrib }
}
169 changes: 152 additions & 17 deletions core/src/geom/mesh.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,51 @@
//! Triangle meshes.

use core::fmt::{Debug, Formatter};
use core::{
fmt::{Debug, Formatter},
iter::zip,
};

use alloc::{vec, vec::Vec};

use crate::math::space::Real;
use crate::math::Vec3;
use crate::math::{
mat::{Mat4x4, RealToReal},
space::Linear,
vec::Vec3,
};
use crate::render::Model;

use super::{vertex, Tri};
use super::{vertex, Normal3, Tri};

/// Convenience type alias for a mesh vertex.
pub type Vertex<A, Sp = Real<3, Model>> = super::Vertex<Vec3<Sp>, A>;
pub type Vertex<A, B = Model> = super::Vertex<Vec3<B>, A>;

/// A triangle mesh.
///
/// An object made of flat polygonal faces that typically form a contiguous
/// surface without holes or boundaries, so that every face shares each of its
/// edges with another face. By using many faces, complex curved shapes can be
/// approximated.
/// edges with another face. For instance, a cube can be represented by a mesh
/// with 8 vertices and 12 faces. By using many faces, complex curved shapes
/// can be approximated.
#[derive(Clone)]
pub struct Mesh<Attrib, Space = Real<3, Model>> {
pub struct Mesh<Attrib, Basis = Model> {
/// The faces of the mesh, with each face a triplet of indices
/// to the `verts` vector. Several faces can share a vertex.
pub faces: Vec<Tri<usize>>,
/// The vertices of the mesh.
pub verts: Vec<Vertex<Attrib, Space>>,
pub verts: Vec<Vertex<Attrib, Basis>>,
}

#[derive(Clone, Debug)]
pub struct Builder<Attrib = ()> {
pub mesh: Mesh<Attrib>,
/// A builder type for creating meshes.
#[derive(Clone)]
pub struct Builder<Attrib = (), Basis = Model> {
pub mesh: Mesh<Attrib, Basis>,
}

impl<A, S> Mesh<A, S> {
//
// Inherent impls
//

impl<A, B> Mesh<A, B> {
/// Creates a new triangle mesh with the given faces and vertices.
///
/// Each face in `faces` is a triplet of indices, referring to
Expand All @@ -59,14 +71,14 @@ impl<A, S> Mesh<A, S> {
/// ];
///
/// // Create a mesh with a tetrahedral shape
/// let tetra = Mesh::new(faces, verts);
/// let tetra: Mesh<()> = Mesh::new(faces, verts);
/// ```
/// # Panics
/// If any of the vertex indices in `faces` ≥ `verts.len()`.
pub fn new<F, V>(faces: F, verts: V) -> Self
where
F: IntoIterator<Item = Tri<usize>>,
V: IntoIterator<Item = Vertex<A, S>>,
V: IntoIterator<Item = Vertex<A, B>>,
{
let faces: Vec<_> = faces.into_iter().collect();
let verts: Vec<_> = verts.into_iter().collect();
Expand All @@ -86,15 +98,29 @@ impl<A> Mesh<A> {
pub fn builder() -> Builder<A> {
Builder::default()
}

/// Consumes `self` and returns a mesh builder with the faces and vertices
/// of `self`.
pub fn into_builder(self) -> Builder<A> {
Builder { mesh: self }
}
}

impl<A> Builder<A> {
/// Appends a face with the given vertex indices.
///
/// Invalid indices (referring to vertices not yet added) are permitted,
/// as long as all indices are valid when the [`build`][Builder::build]
/// method is called.
pub fn push_face(&mut self, a: usize, b: usize, c: usize) {
self.mesh.faces.push(Tri([a, b, c]));
}

/// Appends all the faces yielded by the given iterator.
///
/// The faces may include invalid vertex indices (referring to vertices
/// not yet added) are permitted, as long as all indices are valid when
/// the [`build`][Builder::build] method is called.
pub fn push_faces<Fs>(&mut self, faces: Fs)
where
Fs: IntoIterator<Item = [usize; 3]>,
Expand Down Expand Up @@ -126,6 +152,74 @@ impl<A> Builder<A> {
}
}

impl Builder<()> {
/// Applies the given transform to the position of each vertex.
///
/// This is an eager operation, that is, only vertices *currently*
/// added to the builder are transformed.
pub fn transform(
self,
tf: &Mat4x4<RealToReal<3, Model, Model>>,
) -> Builder<()> {
let mesh = Mesh {
faces: self.mesh.faces,
verts: self
.mesh
.verts
.into_iter()
.map(|v| vertex(tf.apply(&v.pos), v.attrib))
.collect(),
};
mesh.into_builder()
}

/// Computes a vertex normal for each vertex as an area-weighted average
/// of normals of the faces adjacent to it.
///
/// The algorithm is as follows:
/// 1. Initialize the normal of each vertex to **0**
/// 1. For each face:
/// 1. Take the cross product of two of the face's edge vectors
/// 2. Add the result to the normal of each of the face's vertices.
/// 3. Normalize each vertex normal to unit length.
///
/// This is an eager operation, that is, only vertices *currently* added
/// to the builder are transformed. The attribute type of the result is
/// `Normal3`; the vertex type it accepts is changed accordingly.
pub fn with_vertex_normals(self) -> Builder<Normal3> {
let Mesh { verts, faces } = self.mesh;

// Compute weighted face normals...
let face_normals = faces.iter().map(|Tri(vs)| {
// TODO If n-gonal faces are supported some day,
// the cross product is not proportional to area anymore
let [a, b, c] = vs.map(|i| verts[i].pos);
(b - a).cross(&(c - a)).to()
});
// ...initialize vertex normals to zero...
let mut verts: Vec<_> = verts
.iter()
.map(|v| vertex(v.pos, Normal3::zero()))
.collect();
// ...accumulate normals...
for (&Tri(vs), n) in zip(&faces, face_normals) {
for i in vs {
verts[i].attrib += n;
}
}
// ...and normalize to unit length.
for v in &mut verts {
v.attrib = v.attrib.normalize();
}

Mesh::new(faces, verts).into_builder()
}
}

//
// Foreign trait impls
//

impl<A: Debug, S: Debug + Default> Debug for Mesh<A, S> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Mesh")
Expand All @@ -135,29 +229,43 @@ impl<A: Debug, S: Debug + Default> Debug for Mesh<A, S> {
}
}

impl<A: Debug, S: Debug + Default> Debug for Builder<A, S> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Builder")
.field("faces", &self.mesh.faces)
.field("verts", &self.mesh.verts)
.finish()
}
}

impl<A, S> Default for Mesh<A, S> {
/// Returns an empty mesh.
fn default() -> Self {
Self { faces: vec![], verts: vec![] }
}
}

impl<A> Default for Builder<A> {
/// Returns an empty builder.
fn default() -> Self {
Self { mesh: Mesh::default() }
}
}

#[cfg(test)]
mod tests {
use core::f32::consts::FRAC_1_SQRT_2;

use crate::geom::vertex;
use crate::math::vec3;
use crate::prelude::splat;

use super::*;

#[test]
#[should_panic]
fn mesh_new_panics_if_vertex_index_oob() {
_ = Mesh::new(
let _: Mesh<()> = Mesh::new(
[Tri([0, 1, 2]), Tri([1, 2, 3])],
[
vertex(vec3(0.0, 0.0, 0.0), ()),
Expand All @@ -177,7 +285,34 @@ mod tests {
(vec3(1.0, 1.0, 1.0), ()),
(vec3(2.0, 2.0, 2.0), ()),
]);

_ = b.build();
}

#[test]
fn vertex_normal_generation() {
// TODO Doesn't test weighting by area

let mut b = Mesh::builder();
b.push_faces([[0, 2, 1], [0, 1, 3], [0, 3, 2]]);
b.push_verts([
(vec3(0.0, 0.0, 0.0), ()),
(vec3(1.0, 0.0, 0.0), ()),
(vec3(0.0, 1.0, 0.0), ()),
(vec3(0.0, 0.0, 1.0), ()),
]);
let b = b.with_vertex_normals();

const SQRT_3: f32 = 1.7320508076;

let expected = [
splat(-1.0 / SQRT_3),
vec3(0.0, -FRAC_1_SQRT_2, -FRAC_1_SQRT_2),
vec3(-FRAC_1_SQRT_2, 0.0, -FRAC_1_SQRT_2),
vec3(-FRAC_1_SQRT_2, -FRAC_1_SQRT_2, 0.0),
];

for i in 0..4 {
crate::assert_approx_eq!(b.mesh.verts[i].attrib, expected[i]);
}
}
}
9 changes: 3 additions & 6 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
//! Makes available items requiring I/O, timekeeping, or any floating-point
//! functions not included in `core`. In particular this means trigonometric
//! and transcendental functions.
//!
//!
//! If this feature is disabled, the crate only depends on `alloc`.
//!
//! * `libm`:
Expand Down Expand Up @@ -61,13 +61,10 @@ pub mod prelude {
rand::Distrib,
space::{Affine, Linear},
vary::{lerp, Vary},
vec::{
splat, vec2, vec3, vec4, Vec2, Vec2i, Vec2u, Vec3, Vec3i, Vec4,
Vec4i, Vector,
},
vec::{splat, vec2, vec3, Vec2, Vec2i, Vec2u, Vec3, Vec3i, Vector},
};

pub use crate::geom::{vertex, Mesh, Tri, Vertex};
pub use crate::geom::{vertex, Mesh, Normal2, Normal3, Tri, Vertex};

pub use crate::render::{raster::Frag, shader::Shader};

Expand Down
4 changes: 2 additions & 2 deletions core/src/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ pub use approx::ApproxEq;
pub use mat::{Mat3x3, Mat4x4, Matrix};
pub use space::{Affine, Linear};
pub use vary::{lerp, Vary};
pub use vec::{vec2, vec3, vec4};
pub use vec::{Vec2, Vec2i, Vec3, Vec3i, Vec4, Vec4i, Vector};
pub use vec::{vec2, vec3};
pub use vec::{Vec2, Vec2i, Vec3, Vec3i, Vector};

pub mod angle;
pub mod approx;
Expand Down
Loading
Loading