├── .github
└── workflows
│ ├── publish-rust-docs.yml
│ └── rust.yml
├── .gitignore
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── rust-toolchain.toml
└── src
├── attribute
├── bound_error.rs
├── color.rs
├── distance.rs
├── mod.rs
├── normal.rs
├── support_function.rs
├── tangent.rs
└── uv.rs
├── bound_tester.rs
├── context
├── items
│ ├── mod.rs
│ └── position.rs
├── mod.rs
└── traits
│ ├── mod.rs
│ ├── register.rs
│ ├── register_item.rs
│ ├── register_items.rs
│ ├── register_items_uncons.rs
│ ├── registers.rs
│ └── registers_uncons.rs
├── field
├── field_operator
│ ├── arity
│ │ ├── extrude.rs
│ │ ├── extrude_interior.rs
│ │ ├── mod.rs
│ │ ├── slice.rs
│ │ └── sweep.rs
│ ├── boolean
│ │ ├── intersection.rs
│ │ ├── mod.rs
│ │ ├── subtraction.rs
│ │ └── union.rs
│ ├── checker.rs
│ ├── color_normal.rs
│ ├── color_tangent.rs
│ ├── color_uv.rs
│ ├── composite.rs
│ ├── coordinate_system
│ │ ├── cartesian_to_spherical.rs
│ │ ├── mod.rs
│ │ └── spherical_to_cartesian.rs
│ ├── displace.rs
│ ├── displace_proxy.rs
│ ├── elongate.rs
│ ├── gradient
│ │ ├── gradient_central_diff.rs
│ │ ├── gradient_tetrahedron.rs
│ │ ├── gradient_uv.rs
│ │ └── mod.rs
│ ├── hollow.rs
│ ├── isosurface.rs
│ ├── isosurface_proxy.rs
│ ├── mod.rs
│ ├── normalize.rs
│ ├── proxy
│ │ ├── mod.rs
│ │ ├── proxy_color.rs
│ │ ├── proxy_normal.rs
│ │ ├── proxy_tangent.rs
│ │ └── proxy_uv.rs
│ ├── raycast
│ │ ├── mod.rs
│ │ ├── raytrace.rs
│ │ ├── sphere_trace_lipschitz.rs
│ │ └── sphere_trace_naive.rs
│ ├── reflect
│ │ ├── axial_reflect.rs
│ │ ├── mod.rs
│ │ └── reflect.rs
│ ├── repeat
│ │ ├── mod.rs
│ │ ├── repeat_count.rs
│ │ └── repeat_infinite.rs
│ ├── scale_uv.rs
│ ├── sided.rs
│ ├── smooth_boolean
│ │ ├── mod.rs
│ │ ├── smooth_intersection.rs
│ │ ├── smooth_subtraction.rs
│ │ └── smooth_union.rs
│ ├── stretch.rs
│ ├── transform
│ │ ├── mod.rs
│ │ ├── rotate.rs
│ │ ├── scale.rs
│ │ └── translate.rs
│ ├── triplanar_uv.rs
│ └── twist.rs
├── metric
│ ├── chebyshev.rs
│ ├── euclidean.rs
│ ├── mod.rs
│ ├── superellipse.rs
│ └── taxicab.rs
├── mod.rs
├── shape
│ ├── composite.rs
│ ├── mod.rs
│ ├── octahedron.rs
│ ├── plane.rs
│ ├── squircle.rs
│ └── superellipsoid.rs
└── traits
│ ├── field.rs
│ ├── field_attribute.rs
│ ├── field_attribute_register.rs
│ ├── field_attributes.rs
│ ├── field_attributes_register.rs
│ ├── field_attributes_register_cons.rs
│ ├── field_register.rs
│ ├── field_registers.rs
│ ├── field_registers_uncons.rs
│ ├── fields.rs
│ ├── fields_register.rs
│ ├── fields_registers.rs
│ ├── fields_registers_uncons.rs
│ ├── fields_uncons_registers_uncons.rs
│ └── mod.rs
├── lib.rs
└── prelude.rs
/.github/workflows/publish-rust-docs.yml:
--------------------------------------------------------------------------------
1 | name: Publish Rust Docs
2 | run-name: ${{ github.actor }} is publishing documentation 🚀
3 | on:
4 | push:
5 |
6 | permissions:
7 | contents: write
8 |
9 | jobs:
10 | publish-cargo-doc:
11 | uses: Shfty/github-actions/.github/workflows/publish-rust-docs.yml@master
12 |
--------------------------------------------------------------------------------
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | name: Rust
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v3
19 | # - name: Install dependencies
20 | # run: sudo apt install librust-alsa-sys-dev libudev-dev
21 | - name: Build
22 | run: cargo build --verbose
23 | - name: Run tests
24 | run: cargo test --verbose
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .cargo
2 | target
3 | Cargo.lock
4 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rust-gpu-sdf"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [features]
7 | default = []
8 | glam = ["rust-gpu-bridge/glam"]
9 | spirv-std = ["rust-gpu-bridge/spirv-std"]
10 | bevy = ["dep:bevy"]
11 |
12 |
13 | [dependencies]
14 | rust-gpu-bridge = { git = "https://github.com/bevy-rust-gpu/rust-gpu-bridge", tag = "v0.5.0" }
15 | type-fields = { git = "https://github.com/bevy-rust-gpu/type-fields", tag = "prerelease" }
16 |
17 | bevy = { version = "0.10.0", optional = true }
18 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # rust-gpu-sdf
4 |
5 | [](https://bevy-rust-gpu.github.io/rust-gpu-sdf/rust_gpu_sdf/)
6 |
7 | A no-std signed distance field library.
8 | Usable on the CPU in regular Rust, or on the GPU by way of `rust-gpu`.
9 |
10 |
--------------------------------------------------------------------------------
/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "nightly-2023-03-04"
3 | components = ["rust-src", "rustc-dev", "llvm-tools-preview"]
4 | # commit_hash = 44cfafe2fafe816395d3acc434663a45d5178c41
5 |
6 | # Whenever changing the nightly channel, update the commit hash above, and make
7 | # sure to change REQUIRED_TOOLCHAIN in crates/rustc_codegen_spirv/src/build.rs also.
8 |
--------------------------------------------------------------------------------
/src/attribute/bound_error.rs:
--------------------------------------------------------------------------------
1 | //! Error term quantifying the bound-ness of a distance function.
2 | //!
3 | //! A distance function can be considered a correct distance field
4 | //! if its derivative is uniformly 1.
5 | //! If this does not hold, it is instead considered a distance bound.
6 | //!
7 | //! In practical terms, this equates to any stretching, squashing,
8 | //! incorrectly-sharp edges, or other discontinuities in an evaluated field.
9 | //!
10 | //! This can only be tested for, but the accuracy of the test is determined
11 | //! by the accuracy of the field's derivative function.
12 | //!
13 | //! This creates issues when testing fields whose derivatives
14 | //! are calculated using local differencing, as the process
15 | //! innately smooths off discontinuities relative to its epsilon factor.
16 | //!
17 | //! To avoid this, we combine the gradient at a given point in the field
18 | //! with a distance evaluation to produce a support vector;
19 | //! i.e. the vector from the evaluated position
20 | //! to the nearest point on the implicit surface.
21 | //!
22 | //! In a correct distance field, summing the evaluated position
23 | //! and support vector will result in a new position whose
24 | //! evaluated distance is almost zero w.r.t. floating-point precision.
25 | //!
26 | //! This is still subject to its own error term relative to the accuracy of the
27 | //! gradient function, but is more robust than the derivative approach,
28 | //! and able to catch more common bound cases.
29 |
30 | use core::{
31 | marker::PhantomData,
32 | ops::{Add, Mul},
33 | };
34 |
35 | use crate::{
36 | impl_passthrough_op_1,
37 | prelude::{
38 | items::position::Position, AttrColor, AttrDistance, AttrNormal, AttrSupport, AttrTangent,
39 | AttrUv, Attribute, Distance, Field, FieldOperator, Operator, Support, SupportFunction,
40 | },
41 | };
42 |
43 | /// Bound error term
44 | #[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
45 | #[repr(C)]
46 | pub struct ErrorTerm {
47 | pub support: Support,
48 | pub error: Distance,
49 | }
50 |
51 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
52 | pub struct AttrBoundError {
53 | _phantom: PhantomData,
54 | }
55 |
56 | impl Attribute for AttrBoundError
57 | where
58 | Dim: Default,
59 | {
60 | type Input = Position;
61 | type Output = ErrorTerm;
62 | }
63 |
64 | /// Bound error wrapper operator
65 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
66 | #[repr(C)]
67 | pub struct BoundErrorOp;
68 |
69 | impl FieldOperator> for BoundErrorOp
70 | where
71 | Sdf: Field> + Field>,
72 | Input: Default + Clone + Add + Mul,
73 | {
74 | fn operator(
75 | &self,
76 | sdf: &Sdf,
77 | p: &Position,
78 | ) -> as Attribute>::Output {
79 | let mut out = ErrorTerm::default();
80 |
81 | let support = Field::>::field(sdf, p);
82 | let sv = support.support_vector();
83 | out.support = support;
84 | out.error = Field::>::field(sdf, &((*p).clone() + sv));
85 | out
86 | }
87 | }
88 |
89 | impl_passthrough_op_1!(BoundErrorOp, AttrDistance, Dim);
90 | impl_passthrough_op_1!(BoundErrorOp, AttrNormal, Dim);
91 | impl_passthrough_op_1!(BoundErrorOp, AttrTangent, Dim);
92 | impl_passthrough_op_1!(BoundErrorOp, AttrUv, Dim);
93 | impl_passthrough_op_1!(BoundErrorOp, AttrColor, Dim);
94 |
95 | /// Bound error wrapper
96 | pub type BoundError = Operator>;
97 |
--------------------------------------------------------------------------------
/src/attribute/color.rs:
--------------------------------------------------------------------------------
1 | use core::marker::PhantomData;
2 |
3 | use rust_gpu_bridge::glam::Vec4;
4 |
5 | use crate::{
6 | impl_newtype,
7 | prelude::{items::position::Position, Field},
8 | };
9 |
10 | use super::Attribute;
11 |
12 | #[repr(C)]
13 | pub struct AttrColor {
14 | _phantom: PhantomData,
15 | }
16 |
17 | impl Default for AttrColor {
18 | fn default() -> Self {
19 | AttrColor {
20 | _phantom: Default::default(),
21 | }
22 | }
23 | }
24 |
25 | impl Clone for AttrColor {
26 | fn clone(&self) -> Self {
27 | AttrColor {
28 | _phantom: self._phantom.clone(),
29 | }
30 | }
31 | }
32 |
33 | impl Copy for AttrColor {}
34 |
35 | impl Attribute for AttrColor {
36 | type Input = Position;
37 | type Output = Color;
38 | }
39 |
40 | impl Field> for Vec4 {
41 | fn field(&self, _: &Position) -> Color {
42 | Color(*self)
43 | }
44 | }
45 |
46 | impl_newtype!(
47 | #[derive(Default, Copy, Clone, PartialEq)]
48 | pub struct Color(Vec4);
49 | );
50 |
--------------------------------------------------------------------------------
/src/attribute/distance.rs:
--------------------------------------------------------------------------------
1 | use core::marker::PhantomData;
2 |
3 | use crate::{
4 | impl_newtype,
5 | prelude::{items::position::Position, Field},
6 | };
7 |
8 | use super::Attribute;
9 |
10 | #[repr(C)]
11 | pub struct AttrDistance {
12 | _phantom: PhantomData,
13 | }
14 |
15 | impl Default for AttrDistance {
16 | fn default() -> Self {
17 | AttrDistance {
18 | _phantom: Default::default(),
19 | }
20 | }
21 | }
22 |
23 | impl Clone for AttrDistance {
24 | fn clone(&self) -> Self {
25 | AttrDistance {
26 | _phantom: self._phantom.clone(),
27 | }
28 | }
29 | }
30 |
31 | impl Copy for AttrDistance {}
32 |
33 | impl Attribute for AttrDistance {
34 | type Input = Position;
35 | type Output = Distance;
36 | }
37 |
38 | impl Field> for f32 {
39 | fn field(&self, _: &Position) -> Distance {
40 | Distance(*self)
41 | }
42 | }
43 |
44 | impl_newtype!(
45 | #[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
46 | pub struct Distance(f32);
47 | );
48 |
--------------------------------------------------------------------------------
/src/attribute/mod.rs:
--------------------------------------------------------------------------------
1 | //! Attributes whose corresponding data type can be evaluated via field function
2 |
3 | use type_fields::t_funk::{hlist::ToTList, tlist::ToHList};
4 |
5 | pub trait Attribute {
6 | type Input;
7 | type Output;
8 | }
9 |
10 | /// An attribute whose input satisfies a given lifetime.
11 | pub trait AttributeRef<'a>: Attribute {
12 | type InputRef: 'a;
13 | }
14 |
15 | impl<'a, T> AttributeRef<'a> for T
16 | where
17 | T: Attribute,
18 | T::Input: 'a,
19 | {
20 | type InputRef = T::Input;
21 | }
22 |
23 | /// A list of `Attribute`s
24 | ///
25 | /// Extension trait of Attribute;
26 | /// applied over `(LHS, RHS)` and `(LHS, ())` to recurse
27 | /// through arbitrarly-long cons list impls.
28 | pub trait Attributes {
29 | type Input;
30 | type Output;
31 | }
32 |
33 | impl Attributes for (LHS, RHS)
34 | where
35 | LHS: Attribute,
36 | RHS: Attributes,
37 | {
38 | type Input = LHS::Input;
39 | type Output = (LHS::Output, RHS::Output);
40 | }
41 |
42 | impl Attributes for (LHS, ())
43 | where
44 | LHS: Attribute,
45 | {
46 | type Input = LHS::Input;
47 | type Output = (LHS::Output, ());
48 | }
49 |
50 | /// A list of `Attribute`s whose input satisfies a given lifetime.
51 | pub trait AttributesRef<'a>: Attributes {
52 | type InputRef: 'a;
53 | }
54 |
55 | impl<'a, T> AttributesRef<'a> for T
56 | where
57 | T: Attributes,
58 | T::Input: 'a,
59 | {
60 | type InputRef = T::Input;
61 | }
62 |
63 | /// A cons list of `Attribute`s
64 | pub trait ConsAttributes<'a>: ToHList {
65 | type ConsAttr: Attributes;
66 | type AttrInput: 'a;
67 | type AttrOutput: ToTList;
68 | type UnconsOutput;
69 | }
70 |
71 | impl<'a, T> ConsAttributes<'a> for T
72 | where
73 | T: ToHList,
74 | T::HList: AttributesRef<'a>,
75 | ::Output: ToTList,
76 | {
77 | type ConsAttr = T::HList;
78 | type AttrInput = >::InputRef;
79 | type AttrOutput = ::Output;
80 | type UnconsOutput = <::Output as ToTList>::TList;
81 | }
82 |
83 | pub mod color;
84 | pub mod distance;
85 | pub mod normal;
86 | pub mod tangent;
87 | pub mod uv;
88 |
89 | pub mod support_function;
90 |
91 | pub mod bound_error;
92 |
--------------------------------------------------------------------------------
/src/attribute/normal.rs:
--------------------------------------------------------------------------------
1 | use core::marker::PhantomData;
2 |
3 | use rust_gpu_bridge::glam::{Vec2, Vec3};
4 |
5 | use crate::{
6 | default, impl_newtype,
7 | prelude::{items::position::Position, Field},
8 | };
9 |
10 | use super::Attribute;
11 |
12 | #[repr(C)]
13 | pub struct AttrNormal(PhantomData);
14 |
15 | impl Default for AttrNormal {
16 | fn default() -> Self {
17 | AttrNormal(default())
18 | }
19 | }
20 |
21 | impl Clone for AttrNormal {
22 | fn clone(&self) -> Self {
23 | AttrNormal(self.0.clone())
24 | }
25 | }
26 |
27 | impl Copy for AttrNormal {}
28 |
29 | impl Attribute for AttrNormal {
30 | type Input = Position;
31 | type Output = Normal;
32 | }
33 |
34 | impl Field> for f32 {
35 | fn field(&self, _: &Position) -> Normal {
36 | Normal(*self)
37 | }
38 | }
39 |
40 | impl Field> for Vec2 {
41 | fn field(&self, _: &Position) -> Normal {
42 | Normal(*self)
43 | }
44 | }
45 |
46 | impl Field> for Vec3 {
47 | fn field(&self, _: &Position) -> Normal {
48 | Normal(*self)
49 | }
50 | }
51 |
52 | impl_newtype!(
53 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
54 | pub struct Normal(Dim);
55 | );
56 |
--------------------------------------------------------------------------------
/src/attribute/support_function.rs:
--------------------------------------------------------------------------------
1 | //! Vector to nearest surface
2 |
3 | use core::{marker::PhantomData, ops::Mul};
4 |
5 | use rust_gpu_bridge::IsNormalized;
6 |
7 | use crate::{
8 | impl_passthrough_op_1,
9 | prelude::{
10 | items::position::Position, AttrColor, AttrDistance, AttrNormal, AttrTangent, AttrUv,
11 | Attribute, Distance, Field, FieldOperator, Operator,
12 | },
13 | };
14 |
15 | /// Support function attribute marker
16 | #[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
17 | #[repr(C)]
18 | pub struct Support {
19 | pub normal: Dim,
20 | pub distance: Distance,
21 | }
22 |
23 | impl Support {
24 | pub fn support_vector(&self) -> Dim
25 | where
26 | Dim: Clone + Mul,
27 | {
28 | self.normal.clone() * -*self.distance
29 | }
30 | }
31 |
32 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
33 | pub struct AttrSupport {
34 | _phantom: PhantomData,
35 | }
36 |
37 | impl Attribute for AttrSupport
38 | where
39 | Dim: Default,
40 | {
41 | type Input = Position;
42 | type Output = Support;
43 | }
44 |
45 | /// Support function wrapper operator
46 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
47 | #[repr(C)]
48 | pub struct SupportFunctionOp;
49 |
50 | impl FieldOperator> for SupportFunctionOp
51 | where
52 | Sdf: Field> + Field>,
53 | Dim: Default + Clone + Mul + IsNormalized,
54 | {
55 | fn operator(&self, sdf: &Sdf, p: &Position) -> as Attribute>::Output {
56 | let mut out = Support::default();
57 |
58 | // Calculate normal
59 | let n = (*Field::>::field(sdf, p)).clone();
60 |
61 | // Skip samples where normal is not valid
62 | // (ex. the center of a sphere)
63 | if !n.clone().is_normalized() {
64 | return out;
65 | }
66 |
67 | // Calculate distance
68 | let d = Field::>::field(sdf, p);
69 |
70 | // Write into output
71 | out.normal = n;
72 | out.distance = d;
73 |
74 | out
75 | }
76 | }
77 |
78 | impl_passthrough_op_1!(SupportFunctionOp, AttrDistance, Dim);
79 | impl_passthrough_op_1!(SupportFunctionOp, AttrNormal, Dim);
80 | impl_passthrough_op_1!(SupportFunctionOp, AttrTangent, Dim);
81 | impl_passthrough_op_1!(SupportFunctionOp, AttrUv, Dim);
82 | impl_passthrough_op_1!(SupportFunctionOp, AttrColor, Dim);
83 |
84 | /// Support function wrapper
85 | pub type SupportFunction = Operator;
86 |
--------------------------------------------------------------------------------
/src/attribute/tangent.rs:
--------------------------------------------------------------------------------
1 | use core::marker::PhantomData;
2 |
3 | use rust_gpu_bridge::glam::{Vec2, Vec3};
4 |
5 | use crate::{
6 | default, impl_newtype,
7 | prelude::{items::position::Position, Field},
8 | };
9 |
10 | use super::Attribute;
11 |
12 | #[repr(C)]
13 | pub struct AttrTangent(PhantomData);
14 |
15 | impl Default for AttrTangent {
16 | fn default() -> Self {
17 | AttrTangent(default())
18 | }
19 | }
20 |
21 | impl Clone for AttrTangent {
22 | fn clone(&self) -> Self {
23 | AttrTangent(self.0.clone())
24 | }
25 | }
26 |
27 | impl Copy for AttrTangent {}
28 |
29 | impl Attribute for AttrTangent {
30 | type Input = Position;
31 | type Output = Tangent;
32 | }
33 |
34 | impl Field> for f32 {
35 | fn field(&self, _: &Position) -> Tangent {
36 | Tangent(*self)
37 | }
38 | }
39 |
40 | impl Field> for Vec2 {
41 | fn field(&self, _: &Position) -> Tangent {
42 | Tangent(*self)
43 | }
44 | }
45 |
46 | impl Field> for Vec3 {
47 | fn field(&self, _: &Position) -> Tangent {
48 | Tangent(*self)
49 | }
50 | }
51 |
52 | impl_newtype!(
53 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
54 | pub struct Tangent(Dim);
55 | );
56 |
--------------------------------------------------------------------------------
/src/attribute/uv.rs:
--------------------------------------------------------------------------------
1 | use core::marker::PhantomData;
2 |
3 | use rust_gpu_bridge::glam::Vec2;
4 |
5 | use crate::{
6 | impl_newtype,
7 | prelude::{items::position::Position, Field},
8 | };
9 |
10 | use super::Attribute;
11 |
12 | #[repr(C)]
13 | pub struct AttrUv {
14 | _phantom: PhantomData,
15 | }
16 |
17 | impl Default for AttrUv {
18 | fn default() -> Self {
19 | AttrUv {
20 | _phantom: Default::default(),
21 | }
22 | }
23 | }
24 |
25 | impl Clone for AttrUv {
26 | fn clone(&self) -> Self {
27 | AttrUv {
28 | _phantom: self._phantom.clone(),
29 | }
30 | }
31 | }
32 |
33 | impl Copy for AttrUv {}
34 |
35 | impl Attribute for AttrUv {
36 | type Input = Position;
37 | type Output = Uv;
38 | }
39 |
40 | impl Field> for Vec2 {
41 | fn field(&self, _: &Position) -> Uv {
42 | Uv(*self)
43 | }
44 | }
45 |
46 | impl_newtype!(
47 | #[derive(Default, Copy, Clone, PartialEq)]
48 | pub struct Uv(Vec2);
49 | );
50 |
--------------------------------------------------------------------------------
/src/bound_tester.rs:
--------------------------------------------------------------------------------
1 | //! Utility type for testing the bound error term of a distance function
2 |
3 | use core::ops::RangeInclusive;
4 |
5 | use rust_gpu_bridge::{
6 | glam::{Vec2, Vec3},
7 | Abs,
8 | };
9 |
10 | use crate::prelude::{
11 | default, AttrBoundError, AttrDistance, AttrNormal, BoundError, Field,
12 | FieldAttribute, SupportFunction,
13 | };
14 |
15 | /// Asserts that the provided distance function is a field rather than a bound
16 | #[derive(Debug, Clone, PartialEq)]
17 | #[repr(C)]
18 | pub struct BoundTester {
19 | pub sdf: Sdf,
20 | pub samples: RangeInclusive,
21 | pub step: f32,
22 | pub epsilon: f32,
23 | }
24 |
25 | impl Default for BoundTester
26 | where
27 | Sdf: Default,
28 | {
29 | fn default() -> Self {
30 | BoundTester {
31 | sdf: default(),
32 | samples: -20..=20,
33 | step: 10.0 / 20.0,
34 | epsilon: 0.00001,
35 | }
36 | }
37 | }
38 |
39 | impl BoundTester
40 | where
41 | Sdf: Field> + Field> + Default + Clone + 'static,
42 | {
43 | pub fn is_field_2d(self) -> bool {
44 | !self.is_bound_2d()
45 | }
46 |
47 | pub fn is_bound_2d(self) -> bool {
48 | // Iterate over a regular grid
49 | for x in self.samples.clone() {
50 | for y in self.samples.clone() {
51 | // Compose sample coordinate
52 | let pos = Vec2::new(x as f32, y as f32) * self.step;
53 |
54 | // Calculate error term
55 | let error_term = BoundError {
56 | target: SupportFunction {
57 | target: self.sdf.clone(),
58 | ..default()
59 | },
60 | ..Default::default()
61 | }
62 | .field_attribute::>(&pos.into());
63 |
64 | // Skip samples with no valid support function
65 | if error_term.support.normal == Vec2::ZERO {
66 | continue;
67 | }
68 |
69 | assert!(
70 | error_term.error.abs() <= self.epsilon,
71 | "Encountered error {:?} at point {:}, {:} with {:?} and normal {:}, {}",
72 | error_term.error,
73 | pos.x,
74 | pos.y,
75 | error_term.support.distance,
76 | error_term.support.normal.x,
77 | error_term.support.normal.y
78 | );
79 | }
80 | }
81 |
82 | false
83 | }
84 | }
85 |
86 | impl BoundTester
87 | where
88 | Sdf: Field> + Field> + Default + Clone + 'static,
89 | {
90 | pub fn is_field_3d(self) -> bool {
91 | !self.is_bound_3d()
92 | }
93 |
94 | pub fn is_bound_3d(self) -> bool {
95 | // Iterate over a regular grid
96 | for x in self.samples.clone() {
97 | for y in self.samples.clone() {
98 | for z in self.samples.clone() {
99 | // Compose sample coordinate
100 | let pos = Vec3::new(x as f32, y as f32, z as f32) * self.step;
101 |
102 | // Calculate error term
103 | let error_term = BoundError {
104 | target: SupportFunction {
105 | target: self.sdf.clone(),
106 | ..default()
107 | },
108 | ..Default::default()
109 | }
110 | .field_attribute::>(&pos.into());
111 |
112 | // Skip samples with no valid support function
113 | if error_term.support.normal == Vec3::ZERO {
114 | continue;
115 | }
116 |
117 | assert!(
118 | error_term.error.abs() <= self.epsilon,
119 | "Encountered error {:?} at point {:}, {:}, {:} with {:?} and normal {:}, {:}, {:}",
120 | error_term.error,
121 | pos.x,
122 | pos.y,
123 | pos.z,
124 | error_term.support.distance,
125 | error_term.support.normal.x,
126 | error_term.support.normal.y,
127 | error_term.support.normal.z
128 | );
129 | }
130 | }
131 | }
132 |
133 | false
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/context/items/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod position;
2 |
3 |
--------------------------------------------------------------------------------
/src/context/items/position.rs:
--------------------------------------------------------------------------------
1 | use crate::impl_newtype;
2 |
3 | impl_newtype!(
4 | #[derive(Debug, Default, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
5 | pub struct Position(Dim);
6 | );
7 |
--------------------------------------------------------------------------------
/src/context/mod.rs:
--------------------------------------------------------------------------------
1 | //! Type-level data access
2 |
3 | pub mod traits;
4 | pub mod items;
5 |
6 | /// Marker denoting the left side of a cons cell
7 | pub enum Car {}
8 |
9 | /// Marker denoting the right side of a cons cell
10 | pub enum Cdr {}
11 |
12 | /// Marker denoting self
13 | pub enum This {}
14 |
--------------------------------------------------------------------------------
/src/context/traits/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod register;
2 | pub mod register_item;
3 | pub mod register_items;
4 | pub mod register_items_uncons;
5 | pub mod registers;
6 | pub mod registers_uncons;
7 |
--------------------------------------------------------------------------------
/src/context/traits/register.rs:
--------------------------------------------------------------------------------
1 | use crate::prelude::{Car, Cdr, This};
2 |
3 | /// Fetch `T` by type from a cons list.
4 | pub trait Register: Sized {
5 | fn register(self) -> T;
6 | }
7 |
8 | impl Register<(Cdr, Inner), T> for (LHS, RHS)
9 | where
10 | RHS: Register,
11 | {
12 | fn register(self) -> T {
13 | self.1.register()
14 | }
15 | }
16 |
17 | impl Register<(Car, ()), LHS> for (LHS, RHS) {
18 | fn register(self) -> LHS {
19 | self.0
20 | }
21 | }
22 |
23 | impl<'a, T> Register for T
24 | where
25 | T: 'a,
26 | {
27 | fn register(self) -> T {
28 | self
29 | }
30 | }
31 |
32 | #[cfg(all(not(feature = "spirv-std"), test))]
33 | mod test {
34 | use type_fields::t_funk::tlist::ToHList;
35 |
36 | use crate::prelude::Register;
37 |
38 | #[test]
39 | pub fn test_context() {
40 | let context = (1, 2.0, "three").to_hlist();
41 |
42 | let _int = 0usize.register();
43 | let _float = 0.0.register();
44 | let _string = "hello".register();
45 |
46 | let _int = Register::<_, usize>::register(context);
47 | let _float = Register::<_, f32>::register(context);
48 | let _string = Register::<_, &str>::register(context);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/context/traits/register_item.rs:
--------------------------------------------------------------------------------
1 | use crate::prelude::Register;
2 |
3 | /// Fetch `T` by type from a cons list.
4 | ///
5 | /// Moves `T` into the function position.
6 | pub trait RegisterItem: Sized {
7 | fn item(self) -> Item
8 | where
9 | Self: Register;
10 | }
11 |
12 | impl RegisterItem for T {
13 | fn item(self) -> Item
14 | where
15 | T: Register,
16 | {
17 | self.register()
18 | }
19 | }
20 |
21 | #[cfg(all(not(feature = "spirv-std"), test))]
22 | mod test {
23 | use type_fields::t_funk::tlist::ToHList;
24 |
25 | use crate::prelude::RegisterItem;
26 |
27 | #[test]
28 | pub fn test_context_item() {
29 | let context = (1, 2.0, "three").to_hlist();
30 |
31 | let _int = 0usize.item::();
32 | let _float = 0.0.item::();
33 | let _string = "hello".item::<&str>();
34 |
35 | let _int = context.item::();
36 | let _float = context.item::();
37 | let _string = context.item::<&str>();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/context/traits/register_items.rs:
--------------------------------------------------------------------------------
1 | use type_fields::t_funk::{hlist::ToTList, tlist::ToHList};
2 |
3 | use crate::prelude::Registers;
4 |
5 | /// Fetch multiple items by type from a cons list.
6 | ///
7 | /// Moves `Items` into the function position.
8 | pub trait RegisterItems: Sized {
9 | fn context_items(self) -> >::Type
10 | where
11 | Items: ToHList,
12 | Self: Registers,
13 | >::Type: ToTList;
14 | }
15 |
16 | impl RegisterItems for T {
17 | fn context_items(self) -> >::Type
18 | where
19 | Items: ToHList,
20 | Self: Registers,
21 | {
22 | self.registers()
23 | }
24 | }
25 |
26 | #[cfg(all(not(feature = "spirv-std"), test))]
27 | mod test {
28 | use type_fields::t_funk::tlist::ToHList;
29 |
30 | use crate::prelude::RegisterItems;
31 |
32 | #[test]
33 | pub fn test_context_items() {
34 | let context = (1, 2.0, "three").to_hlist();
35 |
36 | let (_int, ()) = 0usize.context_items::<(usize,)>();
37 | let (_float, ()) = 0.0.context_items::<(f32,)>();
38 | let (_string, ()) = "hello".context_items::<(&str,)>();
39 |
40 | let (_string, ()) = context.context_items::<(&str,)>();
41 | let (_string, (_float, ())) = context.context_items::<(&str, f32)>();
42 | let (_string, (_float, (_int, ()))) = context.context_items::<(&str, f32, usize)>();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/context/traits/register_items_uncons.rs:
--------------------------------------------------------------------------------
1 | use type_fields::t_funk::{hlist::ToTList, tlist::ToHList};
2 |
3 | use crate::prelude::Registers;
4 |
5 | use super::registers_uncons::RegistersUncons;
6 |
7 | /// Fetch multiple items by type from a cons list and uncons them before return.
8 | ///
9 | /// Moves `Items` into the function position.
10 | pub trait RegisterItemsUncons: Sized {
11 | fn context_items_uncons(
12 | self,
13 | ) -> <>::Type as ToTList>::TList
14 | where
15 | Self: Registers,
16 | Items: ToHList,
17 | Self::Type: ToTList;
18 | }
19 |
20 | impl RegisterItemsUncons for T {
21 | fn context_items_uncons(
22 | self,
23 | ) -> <>::Type as ToTList>::TList
24 | where
25 | Self: Registers,
26 | Items: ToHList,
27 | >::Type: ToTList,
28 | {
29 | RegistersUncons::::registers_uncons(self)
30 | }
31 | }
32 |
33 | #[cfg(all(not(feature = "spirv-std"), test))]
34 | mod test {
35 | use type_fields::t_funk::tlist::ToHList;
36 |
37 | use crate::prelude::RegisterItemsUncons;
38 |
39 | #[test]
40 | pub fn test_context_items() {
41 | let context = (1, 2.0, "three").to_hlist();
42 |
43 | let (_int,) = 0usize.context_items_uncons::<(usize,)>();
44 | let (_float,) = 0.0.context_items_uncons::<(f32,)>();
45 | let (_string,) = "hello".context_items_uncons::<(&str,)>();
46 |
47 | let (_string,) = context.context_items_uncons::<(&str,)>();
48 | let (_string, _float) = context.context_items_uncons::<(&str, f32)>();
49 | let (_string, _float, _int) = context.context_items_uncons::<(&str, f32, usize)>();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/context/traits/registers.rs:
--------------------------------------------------------------------------------
1 | use crate::prelude::{Register, This};
2 |
3 | /// Fetch multiple items by type from a cons list.
4 | pub trait Registers {
5 | type Type;
6 |
7 | fn registers(self) -> Self::Type;
8 | }
9 |
10 | impl Registers<(LState, RState), (LHS, RHS)> for T
11 | where
12 | T: Clone + Register + Registers,
13 | {
14 | type Type = (LHS, >::Type);
15 |
16 | fn registers(self) -> Self::Type {
17 | (self.clone().register(), self.registers())
18 | }
19 | }
20 |
21 | impl Registers for T {
22 | type Type = T;
23 |
24 | fn registers(self) -> Self::Type {
25 | self
26 | }
27 | }
28 |
29 | impl Registers<(), ()> for T {
30 | type Type = ();
31 |
32 | fn registers(self) -> Self::Type {
33 | ()
34 | }
35 | }
36 |
37 | #[cfg(all(not(feature = "spirv-std"), test))]
38 | mod test {
39 | use type_fields::t_funk::tlist::ToHList;
40 |
41 | use crate::prelude::Registers;
42 |
43 | #[test]
44 | pub fn test_contexts() {
45 | let context = (1, 2.0, "three").to_hlist();
46 |
47 | /*
48 | let _int = 0usize.contexts();
49 | let _float = 0.0.contexts();
50 | let _string = "hello".contexts();
51 | */
52 |
53 | let _int = Registers::<_, (usize, ())>::registers(0usize);
54 | let _float = Registers::<_, (f32, ())>::registers(0.0);
55 | let _string = Registers::<_, (&str, ())>::registers("hello");
56 |
57 | let (_string, (_float, (_int, ()))) =
58 | Registers::<_, (&str, (f32, (usize, ())))>::registers(context);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/context/traits/registers_uncons.rs:
--------------------------------------------------------------------------------
1 | use type_fields::t_funk::{hlist::ToTList, tlist::ToHList};
2 |
3 | use crate::prelude::Registers;
4 |
5 | /// Fetch multiple items by type from a cons list and uncons them before return.
6 | pub trait RegistersUncons: Registers
7 | where
8 | Items: ToHList,
9 | {
10 | fn registers_uncons(self) -> ::TList;
11 | }
12 |
13 | impl RegistersUncons for T
14 | where
15 | Self: Registers,
16 | Items: ToHList,
17 | {
18 | fn registers_uncons(self) -> ::TList {
19 | self.registers().to_tlist()
20 | }
21 | }
22 |
23 | #[cfg(all(not(feature = "spirv-std"), test))]
24 | mod test {
25 | use type_fields::t_funk::tlist::ToHList;
26 |
27 | use crate::prelude::RegistersUncons;
28 |
29 | #[test]
30 | pub fn test_contexts_uncons() {
31 | let context = (1, 2.0, "three").to_hlist();
32 |
33 | let (_int,) = RegistersUncons::<_, (usize,)>::registers_uncons(0usize);
34 | let (_float,) = RegistersUncons::<_, (f32,)>::registers_uncons(0.0);
35 | let (_string,) = RegistersUncons::<_, (&str,)>::registers_uncons("hello");
36 |
37 | let (_string,) = RegistersUncons::<_, (&str,)>::registers_uncons(context);
38 | let (_string, _float) = RegistersUncons::<_, (&str, f32)>::registers_uncons(context);
39 | let (_string, _float, _int) =
40 | RegistersUncons::<_, (&str, f32, usize)>::registers_uncons(context);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/field/field_operator/arity/extrude.rs:
--------------------------------------------------------------------------------
1 | //! Extrude a 2D distance field into 3D.
2 |
3 | use rust_gpu_bridge::{
4 | glam::{Vec2, Vec3, Vec3Swizzles},
5 | Abs, Sign,
6 | };
7 | use type_fields::macros::Field;
8 |
9 | use crate::prelude::{
10 | items::position::Position, AttrDistance, AttrNormal, AttrUv, Field, FieldOperator, Normal,
11 | Operator,
12 | };
13 |
14 | /// Extrude a 2D distance field into 3D.
15 | #[derive(Default, Copy, Clone, PartialEq, Field)]
16 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))]
17 | #[repr(C)]
18 | pub struct ExtrudeOp {
19 | pub axis: Vec3,
20 | pub depth: f32,
21 | }
22 |
23 | impl FieldOperator> for ExtrudeOp
24 | where
25 | Sdf: Field>,
26 | {
27 | fn operator(
28 | &self,
29 | sdf: &Sdf,
30 | input: &Position,
31 | ) -> as crate::prelude::Attribute>::Output {
32 | let d = *sdf.field(&input.x.into());
33 | let w = Vec2::new(d, input.y.abs() - self.depth);
34 | (w.x.max(w.y).min(0.0) + w.max(Vec2::ZERO).length()).into()
35 | }
36 | }
37 |
38 | impl FieldOperator> for ExtrudeOp
39 | where
40 | Sdf: Field>,
41 | {
42 | fn operator(
43 | &self,
44 | sdf: &Sdf,
45 | input: &Position,
46 | ) -> as crate::prelude::Attribute>::Output {
47 | let d = *sdf.field(&input.truncate().into());
48 | let w = Vec2::new(d, input.z.abs() - self.depth);
49 | (w.x.max(w.y).min(0.0) + w.max(Vec2::ZERO).length()).into()
50 | }
51 | }
52 |
53 | impl FieldOperator> for ExtrudeOp
54 | where
55 | Sdf: Field>,
56 | {
57 | fn operator(&self, sdf: &Sdf, p: &Position) -> Normal {
58 | let d = *sdf.field(&p.x.into());
59 | let w = Vec2::new(d, p.y.abs() - self.depth);
60 | let s = p.y.sign();
61 |
62 | let g = w.x.max(w.y);
63 | let q = w.max(Vec2::ZERO);
64 | let l = q.length();
65 |
66 | let m = s
67 | * (if g > 0.0 {
68 | q / l
69 | } else {
70 | if w.x > w.y {
71 | Vec2::X
72 | } else {
73 | Vec2::Y
74 | }
75 | });
76 |
77 | m.into()
78 | }
79 | }
80 |
81 | impl FieldOperator> for ExtrudeOp
82 | where
83 | Sdf: Field>,
84 | {
85 | fn operator(&self, sdf: &Sdf, p: &Position) -> Normal {
86 | let d = sdf.field(&p.xy().into());
87 | if p.z.abs() > p.xy().length() * 0.5 {
88 | Vec3::new(0.0, 0.0, p.z.sign())
89 | } else {
90 | d.extend(0.0)
91 | }
92 | .normalize()
93 | .into()
94 | }
95 | }
96 |
97 | impl FieldOperator> for ExtrudeOp
98 | where
99 | Sdf: crate::prelude::Field>,
100 | {
101 | fn operator(
102 | &self,
103 | sdf: &Sdf,
104 | p: &Position,
105 | ) -> as crate::prelude::Attribute>::Output {
106 | (*sdf.field(&p.x.into()) + Vec2::new(0.0, p.y.abs())).into()
107 | }
108 | }
109 |
110 | impl FieldOperator> for ExtrudeOp
111 | where
112 | Sdf: crate::prelude::Field>,
113 | {
114 | fn operator(
115 | &self,
116 | sdf: &Sdf,
117 | p: &Position,
118 | ) -> as crate::prelude::Attribute>::Output {
119 | (*sdf.field(&p.truncate().into()) + Vec2::new(0.0, p.z.abs())).into()
120 | }
121 | }
122 |
123 | /// Extrude a 2D distance field into 3D.
124 | pub type Extrude = Operator;
125 |
126 | impl Extrude {
127 | pub fn axis(&mut self) -> &mut Vec3 {
128 | self.op().axis()
129 | }
130 |
131 | pub fn depth(&mut self) -> &mut f32 {
132 | self.op().depth()
133 | }
134 | }
135 |
136 | #[cfg(all(not(feature = "spirv-std"), test))]
137 | pub mod tests {
138 | use crate::{
139 | prelude::{BoundTester, Circle, Extrude, Point, Sphere},
140 | test_op_attrs_2d, test_op_attrs_3d,
141 | };
142 |
143 | #[test]
144 | fn test_extrude_2d() {
145 | assert!(BoundTester::>::default().is_field_2d());
146 | }
147 |
148 | #[test]
149 | fn test_extrude_3d() {
150 | assert!(BoundTester::>::default().is_field_3d());
151 | }
152 |
153 | test_op_attrs_2d!(Extrude::);
154 | test_op_attrs_3d!(Extrude::);
155 | }
156 |
--------------------------------------------------------------------------------
/src/field/field_operator/arity/extrude_interior.rs:
--------------------------------------------------------------------------------
1 | //! Extrude a 2D distance field into 3D, using its interior distance to determine depth.
2 |
3 | use rust_gpu_bridge::{
4 | glam::{Vec2, Vec3},
5 | Abs,
6 | };
7 | use type_fields::macros::Field;
8 |
9 | use crate::prelude::{
10 | items::position::Position, AttrDistance, Field, FieldOperator, AttrNormal, Operator, AttrUv,
11 | };
12 |
13 | /// Extrude a 2D distance field into 3D, using its interior distance to determine depth.
14 | /// NOTE: The present implementation is a bound, not a field
15 | /// TODO: Refactor to use a 1D FieldFunction to describe Z curvature
16 | #[derive(Default, Copy, Clone, PartialEq, Field)]
17 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))]
18 | #[repr(C)]
19 | pub struct ExtrudeInteriorOp {
20 | pub depth: f32,
21 | }
22 |
23 | impl FieldOperator> for ExtrudeInteriorOp
24 | where
25 | Sdf: Field>,
26 | {
27 | fn operator(
28 | &self,
29 | sdf: &Sdf,
30 | p: &Position,
31 | ) -> as crate::prelude::Attribute>::Output {
32 | let d = *sdf.field(&p.x.into());
33 | let w = Vec2::new(d, p.y.abs() + d.min(0.0) * self.depth);
34 | let exterior = w.max(Vec2::ZERO).length();
35 | let interior = w.x.max(w.y).min(0.0);
36 | (interior + exterior).into()
37 | }
38 | }
39 |
40 | impl FieldOperator> for ExtrudeInteriorOp
41 | where
42 | Sdf: Field>,
43 | {
44 | fn operator(
45 | &self,
46 | sdf: &Sdf,
47 | p: &Position,
48 | ) ->