├── .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 | [![Documentation](https://img.shields.io/badge/docs-API-blue)](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 | ) -> as crate::prelude::Attribute>::Output { 49 | let d = *sdf.field(&p.truncate().into()); 50 | let w = Vec2::new(d, p.z.abs() + d.min(0.0) * self.depth); 51 | let exterior = w.max(Vec2::ZERO).length(); 52 | let interior = w.x.max(w.y).min(0.0); 53 | (interior + exterior).into() 54 | } 55 | } 56 | 57 | impl FieldOperator> for ExtrudeInteriorOp 58 | where 59 | Sdf: Field>, 60 | { 61 | fn operator(&self, sdf: &Sdf, p: &Position) -> as crate::prelude::Attribute>::Output { 62 | let d = *sdf.field(&p.x.into()); 63 | Vec2::new(d, 1.0).normalize().into() 64 | } 65 | } 66 | 67 | impl FieldOperator> for ExtrudeInteriorOp 68 | where 69 | Sdf: Field>, 70 | { 71 | fn operator(&self, sdf: &Sdf, p: &Position) -> as crate::prelude::Attribute>::Output { 72 | let d = *sdf.field(&p.truncate().into()); 73 | d.extend(1.0).normalize().into() 74 | } 75 | } 76 | 77 | impl FieldOperator> for ExtrudeInteriorOp 78 | where 79 | Sdf: crate::prelude::Field>, 80 | { 81 | fn operator(&self, sdf: &Sdf, p: &Position) -> as crate::prelude::Attribute>::Output { 82 | sdf.field(&p.truncate().into()) 83 | } 84 | } 85 | 86 | /// Uniformly scale a distance field. 87 | pub type ExtrudeInterior = Operator; 88 | 89 | impl ExtrudeInterior { 90 | pub fn depth(&mut self) -> &mut f32 { 91 | self.op().depth() 92 | } 93 | } 94 | 95 | #[cfg(all(not(feature = "spirv-std"), test))] 96 | pub mod tests { 97 | use crate::{ 98 | prelude::{BoundTester, Circle, ExtrudeInterior, Point}, 99 | test_op_attrs_3d, 100 | }; 101 | 102 | #[test] 103 | fn test_extrude_interior() { 104 | assert!(BoundTester::>::default().is_field_3d()); 105 | } 106 | 107 | test_op_attrs_3d!(ExtrudeInterior::); 108 | } 109 | -------------------------------------------------------------------------------- /src/field/field_operator/arity/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod slice; 2 | pub mod extrude; 3 | pub mod extrude_interior; 4 | pub mod sweep; 5 | 6 | -------------------------------------------------------------------------------- /src/field/field_operator/arity/slice.rs: -------------------------------------------------------------------------------- 1 | //! Take a 2D slice of a 3D field 2 | 3 | use rust_gpu_bridge::glam::{Vec2, Vec3}; 4 | use type_fields::macros::Field; 5 | 6 | use crate::{ 7 | impl_passthrough_op_1, 8 | prelude::{ 9 | items::position::Position, AttrColor, AttrDistance, AttrNormal, AttrTangent, AttrUv, 10 | Distance, Field, FieldOperator, Normal, Operator, Tangent, Uv, 11 | }, 12 | }; 13 | 14 | /// Take a 2D slice of a 3D field 15 | #[derive(Copy, Clone, PartialEq, Field)] 16 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 17 | #[repr(C)] 18 | pub struct SliceOp { 19 | pub u: Vec3, 20 | pub v: Vec3, 21 | } 22 | 23 | impl Default for SliceOp { 24 | fn default() -> Self { 25 | SliceOp { 26 | u: Vec3::X, 27 | v: Vec3::Y, 28 | } 29 | } 30 | } 31 | 32 | impl FieldOperator> for SliceOp 33 | where 34 | Sdf: Field>, 35 | { 36 | fn operator(&self, sdf: &Sdf, p: &Position) -> Distance { 37 | let u = self.u * p.x; 38 | let v = self.v * p.y; 39 | sdf.field(&(u + v).into()).into() 40 | } 41 | } 42 | 43 | impl FieldOperator> for SliceOp 44 | where 45 | Sdf: Field>, 46 | { 47 | fn operator(&self, sdf: &Sdf, p: &Position) -> Normal { 48 | let u = self.u * p.x; 49 | let v = self.v * p.y; 50 | let n = sdf.field(&(u + v).into()); 51 | Vec2::new(n.dot(self.u), n.dot(self.v)).normalize().into() 52 | } 53 | } 54 | 55 | impl FieldOperator> for SliceOp 56 | where 57 | Sdf: Field>, 58 | { 59 | fn operator(&self, sdf: &Sdf, p: &Position) -> Tangent { 60 | let u = self.u * p.x; 61 | let v = self.v * p.y; 62 | let n = sdf.field(&(u + v).into()); 63 | Vec2::new(n.dot(self.u), n.dot(self.v)).normalize().into() 64 | } 65 | } 66 | 67 | impl FieldOperator> for SliceOp 68 | where 69 | Sdf: Field>, 70 | { 71 | fn operator(&self, sdf: &Sdf, p: &Position) -> Uv { 72 | let u = self.u * p.x; 73 | let v = self.v * p.y; 74 | sdf.field(&(u + v).into()) 75 | } 76 | } 77 | 78 | impl_passthrough_op_1!(SliceOp, AttrColor, Dim); 79 | 80 | /// Take a 2D slice of a 3D field 81 | pub type Slice = Operator; 82 | 83 | impl Slice { 84 | pub fn u(&mut self) -> &mut Vec3 { 85 | &mut self.op().u 86 | } 87 | 88 | pub fn v(&mut self) -> &mut Vec3 { 89 | &mut self.op().v 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/field/field_operator/arity/sweep.rs: -------------------------------------------------------------------------------- 1 | //! Create a 3D distance field by sweeping a 2D distance field 2 | //! around the perimiter of another 2D distance field 3 | 4 | use rust_gpu_bridge::glam::{Vec2, Vec3, Vec3Swizzles}; 5 | use type_fields::macros::Field; 6 | 7 | use crate::prelude::{AttrDistance, Field, FieldOperator, AttrNormal, Operator, AttrUv, items::position::Position, Normal, Uv}; 8 | 9 | /// Create a 3D distance field by sweeping a 2D distance field 10 | /// around the perimiter of another 2D distance field 11 | #[derive(Debug, Default, Copy, Clone, PartialEq, Field)] 12 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 13 | #[repr(C)] 14 | pub struct SweepOp; 15 | 16 | impl FieldOperator<(Core, Shell), AttrDistance> for SweepOp 17 | where 18 | Core: Field>, 19 | Shell: Field>, 20 | { 21 | fn operator( 22 | &self, 23 | (core, shell): &(Core, Shell), 24 | p: &Position, 25 | ) -> as crate::prelude::Attribute>::Output { 26 | let q = *core.field(&p.x.into()); 27 | shell.field(&q.into()) 28 | } 29 | } 30 | 31 | impl FieldOperator<(Core, Shell), AttrDistance> for SweepOp 32 | where 33 | Core: Field>, 34 | Shell: Field>, 35 | { 36 | fn operator( 37 | &self, 38 | (core, shell): &(Core, Shell), 39 | p: &Position, 40 | ) -> as crate::prelude::Attribute>::Output { 41 | let q = Vec2::new(*core.field(&p.truncate().into()), p.z); 42 | shell.field(&q.into()) 43 | } 44 | } 45 | 46 | impl FieldOperator<(Core, Shell), AttrNormal> for SweepOp 47 | where 48 | Core: Field>, 49 | Shell: Field>, 50 | { 51 | fn operator(&self, (core, shell): &(Core, Shell), input: &Position) -> Normal { 52 | let q = Vec2::new(*core.field(&input.truncate().into()), input.z); 53 | let n = shell.field(&q.into()); 54 | let w = input.xy().normalize() * n.x; 55 | Vec3::new(w.x, w.y, n.y).into() 56 | } 57 | } 58 | 59 | impl FieldOperator<(Core, Shell), AttrUv> for SweepOp 60 | where 61 | Core: Field> + Field>, 62 | Shell: Field>, 63 | { 64 | fn operator(&self, (core, shell): &(Core, Shell), input: &Position) -> Uv { 65 | let dist_core = *Field::>::field(core, &input.truncate().into()); 66 | let uv_core = Field::>::field(core, &input.truncate().into()); 67 | let q = Vec2::new(dist_core, input.z); 68 | let uv_shell = shell.field(&q.into()); 69 | Vec2::new(uv_core.x, uv_shell.x + uv_shell.y).into() 70 | } 71 | } 72 | 73 | /// Uniformly scale a distance field. 74 | pub type Sweep = Operator; 75 | 76 | impl Sweep { 77 | pub fn core(&mut self) -> &mut Core { 78 | &mut self.target().0 79 | } 80 | 81 | pub fn shell(&mut self) -> &mut Shell { 82 | &mut self.target().1 83 | } 84 | } 85 | 86 | #[cfg(all(not(feature = "spirv-std"), test))] 87 | pub mod tests { 88 | use crate::{ 89 | prelude::{BoundTester, Circle, Point, Sweep}, 90 | test_op_attrs_3d, 91 | }; 92 | 93 | #[test] 94 | fn test_sweep() { 95 | assert!(BoundTester::>::default().is_field_3d()); 96 | } 97 | 98 | test_op_attrs_3d!(Sweep::); 99 | } 100 | -------------------------------------------------------------------------------- /src/field/field_operator/boolean/intersection.rs: -------------------------------------------------------------------------------- 1 | //! Compute the boolean intersection of two distance fields. 2 | 3 | use type_fields::macros::Field; 4 | 5 | use crate::prelude::{ 6 | items::position::Position, AttrDistance, Field, FieldOperator, AttrNormal, Operator, AttrUv, Distance, Normal, Uv, 7 | }; 8 | 9 | /// Compute the boolean intersection of two distance fields. 10 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Field)] 11 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 12 | #[repr(C)] 13 | pub struct IntersectionOp; 14 | 15 | impl FieldOperator<(SdfA, SdfB), AttrDistance> for IntersectionOp 16 | where 17 | SdfA: Field>, 18 | SdfB: Field>, 19 | { 20 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), input: &Position) -> Distance { 21 | sdf_a.field(input).max(*sdf_b.field(input)).into() 22 | } 23 | } 24 | 25 | impl FieldOperator<(SdfA, SdfB), AttrNormal> for IntersectionOp 26 | where 27 | SdfA: Field>, 28 | SdfA: Field>, 29 | SdfB: Field>, 30 | SdfB: Field>, 31 | { 32 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), input: &Position) -> Normal { 33 | let dist_a = Field::>::field(sdf_a, input); 34 | let dist_b = Field::>::field(sdf_b, input); 35 | 36 | let n = if dist_a > dist_b { 37 | Field::>::field(sdf_a, input) 38 | } else { 39 | Field::>::field(sdf_b, input) 40 | }; 41 | 42 | n 43 | } 44 | } 45 | 46 | impl FieldOperator<(SdfA, SdfB), AttrUv> for IntersectionOp 47 | where 48 | SdfA: Field>, 49 | SdfA: Field>, 50 | SdfB: Field>, 51 | SdfB: Field>, 52 | { 53 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), input: &Position) -> Uv { 54 | let dist_a = Field::>::field(sdf_a, input); 55 | let dist_b = Field::>::field(sdf_b, input); 56 | 57 | if dist_a > dist_b { 58 | Field::>::field(sdf_a, input) 59 | } else { 60 | Field::>::field(sdf_b, input) 61 | } 62 | } 63 | } 64 | 65 | /// Compute the boolean intersection of two distance fields. 66 | pub type Intersection = Operator; 67 | 68 | impl Intersection { 69 | pub fn sdf_a(&mut self) -> &mut SdfA { 70 | &mut self.target().0 71 | } 72 | 73 | pub fn sdf_b(&mut self) -> &mut SdfB { 74 | &mut self.target().1 75 | } 76 | } 77 | 78 | #[cfg(all(not(feature = "spirv-std"), test))] 79 | pub mod test { 80 | use crate::{ 81 | prelude::{Cube, Intersection, Point, Sphere}, 82 | test_op_attrs, 83 | }; 84 | 85 | #[test] 86 | fn test_intersection() { 87 | Intersection::::default(); 88 | } 89 | 90 | test_op_attrs!(Intersection::); 91 | } 92 | -------------------------------------------------------------------------------- /src/field/field_operator/boolean/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod union; 2 | pub mod subtraction; 3 | pub mod intersection; 4 | -------------------------------------------------------------------------------- /src/field/field_operator/boolean/subtraction.rs: -------------------------------------------------------------------------------- 1 | //! Compute the boolean subtraction of two distance fields. 2 | 3 | use core::ops::Neg; 4 | 5 | use type_fields::macros::Field; 6 | 7 | use crate::prelude::{ 8 | items::position::Position, AttrDistance, AttrNormal, AttrUv, Distance, Field, FieldOperator, 9 | Normal, Operator, Uv, 10 | }; 11 | 12 | /// Compute the boolean subtraction of two distance fields. 13 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Field)] 14 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 15 | #[repr(C)] 16 | pub struct SubtractionOp; 17 | 18 | impl FieldOperator<(SdfA, SdfB), AttrDistance> for SubtractionOp 19 | where 20 | SdfA: Field>, 21 | SdfB: Field>, 22 | { 23 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), p: &Position) -> Distance { 24 | sdf_a.field(p).neg().max(*sdf_b.field(p)).into() 25 | } 26 | } 27 | 28 | impl FieldOperator<(SdfA, SdfB), AttrNormal> for SubtractionOp 29 | where 30 | SdfA: Field>, 31 | SdfA: Field>, 32 | SdfB: Field>, 33 | SdfB: Field>, 34 | { 35 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), input: &Position) -> Normal { 36 | let dist_a = *Field::>::field(sdf_a, input); 37 | let dist_b = *Field::>::field(sdf_b, input); 38 | 39 | if -dist_a > dist_b { 40 | Field::>::field(sdf_a, input) 41 | } else { 42 | Field::>::field(sdf_b, input) 43 | } 44 | } 45 | } 46 | 47 | impl FieldOperator<(SdfA, SdfB), AttrUv> for SubtractionOp 48 | where 49 | SdfA: Field>, 50 | SdfA: Field>, 51 | SdfB: Field>, 52 | SdfB: Field>, 53 | { 54 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), p: &Position) -> Uv { 55 | let dist_a = *Field::>::field(sdf_a, p); 56 | let dist_b = *Field::>::field(sdf_b, p); 57 | 58 | if -dist_a > dist_b { 59 | Field::>::field(sdf_a, p) 60 | } else { 61 | Field::>::field(sdf_b, p) 62 | } 63 | } 64 | } 65 | 66 | /// Compute the boolean subtraction of two distance fields. 67 | pub type Subtraction = Operator; 68 | 69 | impl Subtraction { 70 | pub fn sdf_a(&mut self) -> &mut SdfA { 71 | &mut self.target().0 72 | } 73 | 74 | pub fn sdf_b(&mut self) -> &mut SdfB { 75 | &mut self.target().1 76 | } 77 | } 78 | 79 | #[cfg(all(not(feature = "spirv-std"), test))] 80 | pub mod test { 81 | use crate::{ 82 | prelude::{Cube, Point, Sphere, Subtraction}, 83 | test_op_attrs, 84 | }; 85 | 86 | #[test] 87 | fn test_subtraction() { 88 | Subtraction::::default(); 89 | } 90 | 91 | test_op_attrs!(Subtraction::); 92 | } 93 | -------------------------------------------------------------------------------- /src/field/field_operator/boolean/union.rs: -------------------------------------------------------------------------------- 1 | //! Compute the boolean union of two distance fields. 2 | 3 | use type_fields::macros::Field; 4 | 5 | use crate::prelude::{ 6 | items::position::Position, AttrDistance, AttrNormal, AttrUv, Distance, Field, FieldOperator, 7 | Normal, Operator, Uv, 8 | }; 9 | 10 | /// Compute the boolean union of two distance fields. 11 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Field)] 12 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 13 | #[repr(C)] 14 | pub struct UnionOp; 15 | 16 | impl FieldOperator<(SdfA, SdfB), AttrDistance> for UnionOp 17 | where 18 | SdfA: Field>, 19 | SdfB: Field>, 20 | { 21 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), p: &Position) -> Distance { 22 | sdf_a.field(p).min(*sdf_b.field(p)).into() 23 | } 24 | } 25 | 26 | impl FieldOperator<(SdfA, SdfB), AttrNormal> for UnionOp 27 | where 28 | SdfA: Field>, 29 | SdfA: Field>, 30 | SdfB: Field>, 31 | SdfB: Field>, 32 | { 33 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), input: &Position) -> Normal { 34 | let dist_a = Field::>::field(sdf_a, input); 35 | let dist_b = Field::>::field(sdf_b, input); 36 | 37 | if dist_a < dist_b { 38 | Field::>::field(sdf_a, input) 39 | } else { 40 | Field::>::field(sdf_b, input) 41 | } 42 | } 43 | } 44 | 45 | impl FieldOperator<(SdfA, SdfB), AttrUv> for UnionOp 46 | where 47 | SdfA: Field>, 48 | SdfA: Field>, 49 | SdfB: Field>, 50 | SdfB: Field>, 51 | { 52 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), input: &Position) -> Uv { 53 | let dist_a = Field::>::field(sdf_a, input); 54 | let dist_b = Field::>::field(sdf_b, input); 55 | 56 | if dist_a < dist_b { 57 | Field::>::field(sdf_a, input) 58 | } else { 59 | Field::>::field(sdf_a, input) 60 | } 61 | } 62 | } 63 | 64 | /// Compute the boolean union of two distance fields. 65 | pub type Union = Operator; 66 | 67 | impl Union { 68 | pub fn sdf_a(&mut self) -> &mut SdfA { 69 | &mut self.target().0 70 | } 71 | 72 | pub fn sdf_b(&mut self) -> &mut SdfB { 73 | &mut self.target().1 74 | } 75 | } 76 | 77 | #[cfg(all(not(feature = "spirv-std"), test))] 78 | pub mod test { 79 | use crate::{ 80 | prelude::{Cube, Point, Sphere, Union}, 81 | test_op_attrs, 82 | }; 83 | 84 | #[test] 85 | fn test_union() { 86 | Union::::default(); 87 | } 88 | 89 | test_op_attrs!(Union::); 90 | } 91 | -------------------------------------------------------------------------------- /src/field/field_operator/checker.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | impl_passthrough_op_1, 3 | prelude::{AttrColor, AttrDistance, Field, AttrNormal, Raycast, AttrTangent, AttrUv, items::position::Position}, 4 | }; 5 | use rust_gpu_bridge::{glam::Vec3, Mod}; 6 | 7 | use super::{FieldOperator, Operator}; 8 | 9 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 10 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 11 | pub struct CheckerOp; 12 | 13 | impl FieldOperator> for CheckerOp 14 | where 15 | Sdf: Field>, 16 | Input: Mod, 17 | { 18 | fn operator(&self, sdf: &Sdf, p: &Position) -> as crate::prelude::Attribute>::Output { 19 | let uv = sdf.field(p); 20 | let checker = uv.round(); 21 | let checker = (checker.x + checker.y).modulo(2.0) / 2.0; 22 | Vec3::splat(checker).extend(1.0).into() 23 | } 24 | } 25 | 26 | impl_passthrough_op_1!(CheckerOp, AttrDistance, Dim); 27 | impl_passthrough_op_1!(CheckerOp, AttrNormal, Dim); 28 | impl_passthrough_op_1!(CheckerOp, AttrTangent, Dim); 29 | impl_passthrough_op_1!(CheckerOp, AttrUv, Dim); 30 | impl_passthrough_op_1!(CheckerOp, Raycast,); 31 | 32 | pub type Checker = Operator; 33 | -------------------------------------------------------------------------------- /src/field/field_operator/color_normal.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Add, Mul}; 2 | 3 | use rust_gpu_bridge::{glam::Vec4, Splat, ToVec}; 4 | 5 | use crate::{ 6 | impl_passthrough_op_1, 7 | prelude::{ 8 | items::position::Position, AttrColor, AttrDistance, AttrNormal, AttrTangent, AttrUv, Color, 9 | Field, Raycast, 10 | }, 11 | }; 12 | 13 | use super::{FieldOperator, Operator}; 14 | 15 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 16 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 17 | pub struct ColorNormalOp; 18 | 19 | impl FieldOperator> for ColorNormalOp 20 | where 21 | Sdf: Field>, 22 | Input: Clone + Add + Mul + Splat + ToVec, 23 | { 24 | fn operator(&self, sdf: &Sdf, p: &Position) -> Color { 25 | let normal = (*sdf.field(p)).clone(); 26 | let normal = normal * Input::splat(0.5) + Input::splat(0.5); 27 | let mut color = normal.to_vec(); 28 | color.w = 1.0; 29 | color.into() 30 | } 31 | } 32 | 33 | impl_passthrough_op_1!(ColorNormalOp, AttrDistance, Dim); 34 | impl_passthrough_op_1!(ColorNormalOp, AttrNormal, Dim); 35 | impl_passthrough_op_1!(ColorNormalOp, AttrTangent, Dim); 36 | impl_passthrough_op_1!(ColorNormalOp, AttrUv, Dim); 37 | impl_passthrough_op_1!(ColorNormalOp, Raycast,); 38 | 39 | pub type ColorNormal = Operator; 40 | -------------------------------------------------------------------------------- /src/field/field_operator/color_tangent.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Add, Mul}; 2 | 3 | use rust_gpu_bridge::{glam::Vec4, Splat, ToVec}; 4 | 5 | use crate::{ 6 | impl_passthrough_op_1, 7 | prelude::{AttrColor, AttrDistance, Field, AttrNormal, Raycast, AttrTangent, AttrUv, items::position::Position, Color}, 8 | }; 9 | 10 | use super::{FieldOperator, Operator}; 11 | 12 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 13 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 14 | pub struct ColorTangentOp; 15 | 16 | impl FieldOperator> for ColorTangentOp 17 | where 18 | Sdf: Field>, 19 | Input: Clone + Add + Mul + Splat + ToVec, 20 | { 21 | fn operator(&self, sdf: &Sdf, p: &Position) -> Color { 22 | let tangent = (*sdf.field(p)).clone(); 23 | let tangent = tangent * Input::splat(0.5) + Input::splat(0.5); 24 | let mut color = tangent.to_vec(); 25 | color.w = 1.0; 26 | color.into() 27 | } 28 | } 29 | 30 | impl_passthrough_op_1!(ColorTangentOp, AttrDistance, Dim); 31 | impl_passthrough_op_1!(ColorTangentOp, AttrNormal, Dim); 32 | impl_passthrough_op_1!(ColorTangentOp, AttrTangent, Dim); 33 | impl_passthrough_op_1!(ColorTangentOp, AttrUv, Dim); 34 | impl_passthrough_op_1!(ColorTangentOp, Raycast,); 35 | 36 | pub type ColorTangent = Operator; 37 | 38 | -------------------------------------------------------------------------------- /src/field/field_operator/color_uv.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Add, Mul}; 2 | 3 | use rust_gpu_bridge::{glam::Vec4, Splat, ToVec}; 4 | 5 | use crate::{ 6 | impl_passthrough_op_1, 7 | prelude::{AttrColor, AttrDistance, Field, AttrNormal, Raycast, AttrTangent, AttrUv, items::position::Position, Color}, 8 | }; 9 | 10 | use super::{FieldOperator, Operator}; 11 | 12 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 13 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 14 | pub struct ColorUvOp; 15 | 16 | impl FieldOperator> for ColorUvOp 17 | where 18 | Sdf: Field>, 19 | Input: Add + Mul + Splat + ToVec, 20 | { 21 | fn operator(&self, sdf: &Sdf, p: &Position) -> Color { 22 | let uv = sdf.field(p); 23 | uv.extend(0.0).extend(1.0).into() 24 | } 25 | } 26 | 27 | impl_passthrough_op_1!(ColorUvOp, AttrDistance, Dim); 28 | impl_passthrough_op_1!(ColorUvOp, AttrNormal, Dim); 29 | impl_passthrough_op_1!(ColorUvOp, AttrTangent, Dim); 30 | impl_passthrough_op_1!(ColorUvOp, AttrUv, Dim); 31 | impl_passthrough_op_1!(ColorUvOp, Raycast,); 32 | 33 | pub type ColorUv = Operator; 34 | -------------------------------------------------------------------------------- /src/field/field_operator/composite.rs: -------------------------------------------------------------------------------- 1 | //! Operators composed from other operators. 2 | 3 | use crate::prelude::{Hollow, Isosurface}; 4 | 5 | /// Converts a solid shape into a hollow one with the given surface depth. 6 | pub type Onion = Isosurface>; 7 | 8 | impl Onion { 9 | pub fn radius(&mut self) -> &mut f32 { 10 | self.op().delta() 11 | } 12 | } 13 | 14 | #[cfg(all(not(feature = "spirv-std"), test))] 15 | pub mod tests { 16 | use type_fields::field::Field; 17 | 18 | use crate::{ 19 | prelude::{Circle, Onion}, 20 | test_op_attrs, 21 | }; 22 | 23 | #[test] 24 | fn test_onion() { 25 | Onion::::default().with(Onion::radius, f32::default()); 26 | } 27 | 28 | test_op_attrs!(Onion::); 29 | } 30 | -------------------------------------------------------------------------------- /src/field/field_operator/coordinate_system/cartesian_to_spherical.rs: -------------------------------------------------------------------------------- 1 | use rust_gpu_bridge::{ 2 | glam::{Vec2, Vec3, Vec3Swizzles}, 3 | Acos, Atan2, Sign, 4 | }; 5 | 6 | use crate::prelude::{Attribute, Field, FieldOperator, Operator, items::position::Position}; 7 | 8 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 9 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 10 | #[repr(C)] 11 | pub struct CartesianToPolarOp; 12 | 13 | impl FieldOperator for CartesianToPolarOp 14 | where 15 | Sdf: Field, 16 | Attr: Attribute>, 17 | { 18 | fn operator(&self, sdf: &Sdf, p: &Position) -> Attr::Output { 19 | let r = p.length(); 20 | let a = p.x.atan2(p.y); 21 | sdf.field(&Vec2::new(a, r).into()) 22 | } 23 | } 24 | 25 | pub type CartesianToPolar = Operator; 26 | 27 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 28 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 29 | #[repr(C)] 30 | pub struct CartesianToSphericalOp; 31 | 32 | impl FieldOperator for CartesianToSphericalOp 33 | where 34 | Sdf: Field, 35 | Attr: Attribute>, 36 | { 37 | fn operator(&self, sdf: &Sdf, p: &Position) -> Attr::Output { 38 | let r = p.length(); 39 | let i = (p.z / r).acos(); 40 | let a = p.y.sign() * (p.x / p.xy().length()).acos(); 41 | sdf.field(&Vec3::new(i, r, a).into()) 42 | } 43 | } 44 | 45 | pub type CartesianToSpherical = Operator; 46 | 47 | #[cfg(all(not(feature = "spirv-std"), test))] 48 | pub mod test { 49 | use crate::{ 50 | prelude::{CartesianToSpherical, Point}, 51 | test_op_attrs_2d, test_op_attrs_3d, 52 | }; 53 | 54 | use super::CartesianToPolar; 55 | 56 | #[test] 57 | fn test_cartesian_to_polar() { 58 | CartesianToPolar::::default(); 59 | } 60 | 61 | #[test] 62 | fn test_cartesian_to_spherical() { 63 | CartesianToSpherical::::default(); 64 | } 65 | 66 | test_op_attrs_2d!(CartesianToPolar::); 67 | test_op_attrs_3d!(CartesianToSpherical::); 68 | } 69 | -------------------------------------------------------------------------------- /src/field/field_operator/coordinate_system/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cartesian_to_spherical; 2 | pub mod spherical_to_cartesian; 3 | -------------------------------------------------------------------------------- /src/field/field_operator/coordinate_system/spherical_to_cartesian.rs: -------------------------------------------------------------------------------- 1 | use rust_gpu_bridge::{ 2 | glam::{Vec2, Vec3}, 3 | Cos, Sin, 4 | }; 5 | 6 | use crate::prelude::{Attribute, Field, FieldOperator, Operator, items::position::Position}; 7 | 8 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 9 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 10 | #[repr(C)] 11 | pub struct PolarToCartesianOp; 12 | 13 | impl FieldOperator for PolarToCartesianOp 14 | where 15 | Sdf: Field, 16 | Attr: Attribute>, 17 | { 18 | fn operator(&self, sdf: &Sdf, p: &Position) -> Attr::Output { 19 | let x = p.y * p.x.cos(); 20 | let y = p.y * p.x.sin(); 21 | sdf.field(&Vec2::new(y, x).into()) 22 | } 23 | } 24 | 25 | pub type PolarToCartesian = Operator; 26 | 27 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 28 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 29 | #[repr(C)] 30 | pub struct SphericalToCartesianOp; 31 | 32 | impl FieldOperator for SphericalToCartesianOp 33 | where 34 | Sdf: Field, 35 | Attr: Attribute>, 36 | { 37 | fn operator(&self, sdf: &Sdf, p: &Position) -> Attr::Output { 38 | let x = p.y * p.x.sin() * p.z.cos(); 39 | let y = p.y * p.x.sin() * p.z.sin(); 40 | let z = p.y * p.x.cos(); 41 | sdf.field(&Vec3::new(x, y, z).into()) 42 | } 43 | } 44 | 45 | pub type SphericalToCartesian = Operator; 46 | 47 | #[cfg(all(not(feature = "spirv-std"), test))] 48 | pub mod test { 49 | use crate::{ 50 | prelude::{Point, SphericalToCartesian}, 51 | test_op_attrs_2d, test_op_attrs_3d, 52 | }; 53 | 54 | use super::PolarToCartesian; 55 | 56 | #[test] 57 | fn test_polar_to_cartesian() { 58 | PolarToCartesian::::default(); 59 | } 60 | 61 | #[test] 62 | fn test_spherical_to_cartesian() { 63 | SphericalToCartesian::::default(); 64 | } 65 | 66 | test_op_attrs_2d!(PolarToCartesian::); 67 | test_op_attrs_3d!(SphericalToCartesian::); 68 | } 69 | -------------------------------------------------------------------------------- /src/field/field_operator/displace.rs: -------------------------------------------------------------------------------- 1 | //! Displace the output of a distance field using the output of another distance field. 2 | 3 | use type_fields::macros::Field; 4 | 5 | use crate::{ 6 | impl_passthrough_op_2, 7 | prelude::{ 8 | items::position::Position, AttrColor, AttrDistance, AttrNormal, AttrTangent, AttrUv, 9 | Distance, Field, FieldOperator, Operator, 10 | }, 11 | }; 12 | 13 | /// Displace the output of a distance field using the output of another distance field. 14 | #[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Field)] 15 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 16 | #[repr(C)] 17 | pub struct DisplaceOp { 18 | pub delta: f32, 19 | } 20 | 21 | impl FieldOperator> for DisplaceOp 22 | where 23 | SdfA: Field>, 24 | Input: Clone, 25 | { 26 | fn operator(&self, sdf_a: &SdfA, input: &Position) -> Distance { 27 | sdf_a.field(input) + self.delta 28 | } 29 | } 30 | 31 | impl_passthrough_op_2!(DisplaceOp, AttrNormal, 0, SdfA, Dim); 32 | impl_passthrough_op_2!(DisplaceOp, AttrTangent, 0, SdfA, Dim); 33 | impl_passthrough_op_2!(DisplaceOp, AttrUv, 0, SdfA, Dim); 34 | impl_passthrough_op_2!(DisplaceOp, AttrColor, 0, SdfA, Dim); 35 | 36 | /// Displace the output of a distance field using the output of another distance field. 37 | pub type Displace = Operator; 38 | 39 | impl Displace { 40 | pub fn delta(&mut self) -> &mut f32 { 41 | self.op().delta() 42 | } 43 | } 44 | 45 | #[cfg(all(not(feature = "spirv-std"), test))] 46 | pub mod tests { 47 | use crate::{ 48 | prelude::{Cube, DisplaceProxy, Point, Sphere}, 49 | test_op_attrs, 50 | }; 51 | use type_fields::field::Field; 52 | 53 | #[test] 54 | fn test_displace() { 55 | DisplaceProxy::::default().with(DisplaceProxy::displace, Sphere::default()); 56 | } 57 | 58 | test_op_attrs!(DisplaceProxy::); 59 | } 60 | -------------------------------------------------------------------------------- /src/field/field_operator/displace_proxy.rs: -------------------------------------------------------------------------------- 1 | //! Displace the output of a distance field using the output of another distance field. 2 | 3 | use type_fields::macros::Field; 4 | 5 | use crate::{ 6 | impl_passthrough_op_2, 7 | prelude::{ 8 | items::position::Position, AttrColor, AttrDistance, AttrNormal, AttrTangent, AttrUv, 9 | Distance, Field, FieldOperator, Operator, 10 | }, 11 | }; 12 | 13 | /// Displace the output of a distance field using the output of another distance field. 14 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Field)] 15 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 16 | #[repr(C)] 17 | pub struct DisplaceProxyOp; 18 | 19 | impl FieldOperator<(SdfA, SdfB), AttrDistance> for DisplaceProxyOp 20 | where 21 | SdfA: Field>, 22 | SdfB: Field>, 23 | Input: Clone, 24 | { 25 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), input: &Position) -> Distance { 26 | sdf_a.field(input) + *sdf_b.field(input) 27 | } 28 | } 29 | 30 | impl_passthrough_op_2!(DisplaceProxyOp, AttrNormal, 0, SdfA, Dim); 31 | impl_passthrough_op_2!(DisplaceProxyOp, AttrTangent, 0, SdfA, Dim); 32 | impl_passthrough_op_2!(DisplaceProxyOp, AttrUv, 0, SdfA, Dim); 33 | impl_passthrough_op_2!(DisplaceProxyOp, AttrColor, 0, SdfA, Dim); 34 | 35 | /// Displace the output of a distance field using the output of another distance field. 36 | pub type DisplaceProxy = Operator; 37 | 38 | impl DisplaceProxy { 39 | pub fn sdf(&mut self) -> &mut SdfA { 40 | &mut self.target.0 41 | } 42 | 43 | pub fn displace(&mut self) -> &mut SdfB { 44 | &mut self.target.1 45 | } 46 | } 47 | 48 | #[cfg(all(not(feature = "spirv-std"), test))] 49 | pub mod tests { 50 | use crate::{ 51 | prelude::{Cube, DisplaceProxy, Point, Sphere}, 52 | test_op_attrs, 53 | }; 54 | use type_fields::field::Field; 55 | 56 | #[test] 57 | fn test_displace() { 58 | DisplaceProxy::::default().with(DisplaceProxy::displace, Sphere::default()); 59 | } 60 | 61 | test_op_attrs!(DisplaceProxy::); 62 | } 63 | -------------------------------------------------------------------------------- /src/field/field_operator/gradient/gradient_central_diff.rs: -------------------------------------------------------------------------------- 1 | use rust_gpu_bridge::glam::{Vec2, Vec3}; 2 | use type_fields::macros::Field; 3 | 4 | use crate::{ 5 | impl_passthrough_op_1, 6 | prelude::{ 7 | items::position::Position, AttrColor, AttrDistance, AttrNormal, AttrTangent, AttrUv, Field, 8 | FieldOperator, Normalize, Operator, Raycast, 9 | }, 10 | }; 11 | 12 | #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Field)] 13 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 14 | #[repr(C)] 15 | pub struct GradientCentralDiffOp { 16 | pub epsilon: f32, 17 | } 18 | 19 | impl Default for GradientCentralDiffOp { 20 | fn default() -> Self { 21 | GradientCentralDiffOp { epsilon: 0.001 } 22 | } 23 | } 24 | 25 | impl FieldOperator> for GradientCentralDiffOp 26 | where 27 | Sdf: Field>, 28 | { 29 | fn operator( 30 | &self, 31 | sdf: &Sdf, 32 | input: &Position, 33 | ) -> as crate::prelude::Attribute>::Output { 34 | (*sdf.field(&(**input + self.epsilon).into()) 35 | - *sdf.field(&(**input - self.epsilon).into())) 36 | .into() 37 | } 38 | } 39 | 40 | impl FieldOperator> for GradientCentralDiffOp 41 | where 42 | Sdf: Field>, 43 | { 44 | fn operator( 45 | &self, 46 | sdf: &Sdf, 47 | input: &Position, 48 | ) -> as crate::prelude::Attribute>::Output { 49 | Vec2::new( 50 | *sdf.field(&Vec2::new(input.x + self.epsilon, input.y).into()) 51 | - *sdf.field(&Vec2::new(input.x - self.epsilon, input.y).into()), 52 | *sdf.field(&Vec2::new(input.x, input.y + self.epsilon).into()) 53 | - *sdf.field(&Vec2::new(input.x, input.y - self.epsilon).into()), 54 | ).into() 55 | } 56 | } 57 | 58 | impl FieldOperator> for GradientCentralDiffOp 59 | where 60 | Sdf: Field>, 61 | { 62 | fn operator( 63 | &self, 64 | sdf: &Sdf, 65 | p: &Position, 66 | ) -> as crate::prelude::Attribute>::Output { 67 | Vec3::new( 68 | *sdf.field(&Vec3::new(p.x + self.epsilon, p.y, p.z).into()) 69 | - *sdf.field(&Vec3::new(p.x - self.epsilon, p.y, p.z).into()), 70 | *sdf.field(&Vec3::new(p.x, p.y + self.epsilon, p.z).into()) 71 | - *sdf.field(&Vec3::new(p.x, p.y - self.epsilon, p.z).into()), 72 | *sdf.field(&Vec3::new(p.x, p.y, p.z + self.epsilon).into()) 73 | - *sdf.field(&Vec3::new(p.x, p.y, p.z - self.epsilon).into()), 74 | ).into() 75 | } 76 | } 77 | 78 | impl_passthrough_op_1!(GradientCentralDiffOp, AttrDistance::, Dim); 79 | impl_passthrough_op_1!(GradientCentralDiffOp, AttrTangent, Dim); 80 | impl_passthrough_op_1!(GradientCentralDiffOp, AttrUv, Dim); 81 | impl_passthrough_op_1!(GradientCentralDiffOp, AttrColor, Dim); 82 | impl_passthrough_op_1!(GradientCentralDiffOp, Raycast,); 83 | 84 | pub type GradientCentralDiff = Operator; 85 | 86 | impl GradientCentralDiff { 87 | pub fn epsilon(&mut self) -> &mut f32 { 88 | self.op().epsilon() 89 | } 90 | } 91 | 92 | pub type NormalCentralDiff = Normalize>; 93 | 94 | impl NormalCentralDiff { 95 | pub fn sdf(&mut self) -> &mut Sdf { 96 | self.target().target() 97 | } 98 | 99 | pub fn epsilon(&mut self) -> &mut f32 { 100 | self.target().epsilon() 101 | } 102 | } 103 | 104 | #[cfg(all(not(feature = "spirv-std"), test))] 105 | pub mod tests { 106 | use crate::{prelude::Point, test_op_attrs}; 107 | 108 | use super::GradientCentralDiff; 109 | 110 | #[test] 111 | fn test_gradient_central_diff() { 112 | GradientCentralDiff::::default(); 113 | } 114 | 115 | test_op_attrs!(GradientCentralDiff::); 116 | } 117 | -------------------------------------------------------------------------------- /src/field/field_operator/gradient/gradient_tetrahedron.rs: -------------------------------------------------------------------------------- 1 | use rust_gpu_bridge::glam::{Vec2, Vec2Swizzles, Vec3}; 2 | use type_fields::macros::Field; 3 | 4 | use crate::{ 5 | impl_passthrough_op_1, 6 | prelude::{ 7 | items::position::Position, AttrColor, AttrDistance, AttrNormal, AttrTangent, AttrUv, Field, 8 | FieldOperator, Normalize, Operator, Raycast, 9 | }, 10 | }; 11 | 12 | #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Field)] 13 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 14 | #[repr(C)] 15 | pub struct GradientTetrahedronOp { 16 | pub epsilon: f32, 17 | } 18 | 19 | impl Default for GradientTetrahedronOp { 20 | fn default() -> Self { 21 | GradientTetrahedronOp { epsilon: 0.001 } 22 | } 23 | } 24 | 25 | impl FieldOperator> for GradientTetrahedronOp 26 | where 27 | Sdf: Field>, 28 | { 29 | fn operator( 30 | &self, 31 | sdf: &Sdf, 32 | input: &Position, 33 | ) -> as crate::prelude::Attribute>::Output { 34 | (*sdf.field(&(**input + self.epsilon).into()) 35 | - *sdf.field(&(**input - self.epsilon).into())) 36 | .into() 37 | } 38 | } 39 | 40 | impl FieldOperator> for GradientTetrahedronOp 41 | where 42 | Sdf: Field>, 43 | { 44 | fn operator( 45 | &self, 46 | sdf: &Sdf, 47 | p: &Position, 48 | ) -> as crate::prelude::Attribute>::Output { 49 | let k = Vec2::new(1.0, -1.0); 50 | (k.xy() * *sdf.field(&(**p + k.xy() * self.epsilon).into()) 51 | + k.yy() * *sdf.field(&(**p + k.yy() * self.epsilon).into()) 52 | + k.yx() * *sdf.field(&(**p + k.yx() * self.epsilon).into()) 53 | + k.xx() * *sdf.field(&(**p + k.xx() * self.epsilon).into())) 54 | .into() 55 | } 56 | } 57 | 58 | impl FieldOperator> for GradientTetrahedronOp 59 | where 60 | Sdf: Field>, 61 | { 62 | fn operator( 63 | &self, 64 | sdf: &Sdf, 65 | p: &Position, 66 | ) -> as crate::prelude::Attribute>::Output { 67 | let k = Vec2::new(1.0, -1.0); 68 | (k.xyy() * *sdf.field(&(**p + k.xyy() * self.epsilon).into()) 69 | + k.yyx() * *sdf.field(&(**p + k.yyx() * self.epsilon).into()) 70 | + k.yxy() * *sdf.field(&(**p + k.yxy() * self.epsilon).into()) 71 | + k.xxx() * *sdf.field(&(**p + k.xxx() * self.epsilon).into())) 72 | .into() 73 | } 74 | } 75 | 76 | impl_passthrough_op_1!(GradientTetrahedronOp, AttrDistance, Dim); 77 | impl_passthrough_op_1!(GradientTetrahedronOp, AttrTangent, Dim); 78 | impl_passthrough_op_1!(GradientTetrahedronOp, AttrUv, Dim); 79 | impl_passthrough_op_1!(GradientTetrahedronOp, AttrColor, Dim); 80 | impl_passthrough_op_1!(GradientTetrahedronOp, Raycast,); 81 | 82 | pub type GradientTetrahedron = Operator; 83 | 84 | impl GradientTetrahedron { 85 | pub fn epsilon(&mut self) -> &mut f32 { 86 | self.op().epsilon() 87 | } 88 | } 89 | 90 | pub type NormalTetrahedron = Normalize>; 91 | 92 | impl NormalTetrahedron { 93 | pub fn sdf(&mut self) -> &mut Sdf { 94 | self.target().target() 95 | } 96 | 97 | pub fn epsilon(&mut self) -> &mut f32 { 98 | self.target().epsilon() 99 | } 100 | } 101 | 102 | #[cfg(all(not(feature = "spirv-std"), test))] 103 | pub mod tests { 104 | use crate::{prelude::Point, test_op_attrs}; 105 | 106 | use super::GradientTetrahedron; 107 | 108 | #[test] 109 | fn test_gradient_tetrahedron() { 110 | GradientTetrahedron::::default(); 111 | } 112 | 113 | test_op_attrs!(GradientTetrahedron::); 114 | } 115 | -------------------------------------------------------------------------------- /src/field/field_operator/gradient/gradient_uv.rs: -------------------------------------------------------------------------------- 1 | use rust_gpu_bridge::glam::{Vec2, Vec2Swizzles, Vec3}; 2 | use type_fields::macros::Field; 3 | 4 | use crate::{ 5 | impl_passthrough_op_1, 6 | prelude::{ 7 | items::position::Position, AttrColor, AttrDistance, AttrNormal, AttrTangent, AttrUv, Field, 8 | FieldOperator, Normalize, Operator, Raycast, 9 | }, 10 | }; 11 | 12 | /// Calculate a 3D gradient given a 2D UV 13 | #[derive(Copy, Clone, PartialEq, Field)] 14 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 15 | #[repr(C)] 16 | pub struct UvGradientOp { 17 | pub axis: Vec2, 18 | pub epsilon: f32, 19 | } 20 | 21 | impl Default for UvGradientOp { 22 | fn default() -> Self { 23 | UvGradientOp { 24 | axis: Vec2::X, 25 | epsilon: 0.00001, 26 | } 27 | } 28 | } 29 | 30 | impl FieldOperator> for UvGradientOp 31 | where 32 | Sdf: Field>, 33 | { 34 | fn operator( 35 | &self, 36 | sdf: &Sdf, 37 | input: &Position, 38 | ) -> as crate::prelude::Attribute>::Output { 39 | let k = Vec2::new(1.0, -1.0); 40 | (k.xyy() 41 | * sdf 42 | .field(&(**input + k.xyy() * self.epsilon).into()) 43 | .dot(self.axis) 44 | + k.yyx() 45 | * sdf 46 | .field(&(**input + k.yyx() * self.epsilon).into()) 47 | .dot(self.axis) 48 | + k.yxy() 49 | * sdf 50 | .field(&(**input + k.yxy() * self.epsilon).into()) 51 | .dot(self.axis) 52 | + k.xxx() 53 | * sdf 54 | .field(&(**input + k.xxx() * self.epsilon).into()) 55 | .dot(self.axis)) 56 | .into() 57 | } 58 | } 59 | 60 | impl_passthrough_op_1!(UvGradientOp, AttrDistance, Dim); 61 | impl_passthrough_op_1!(UvGradientOp, AttrNormal, Dim); 62 | impl_passthrough_op_1!(UvGradientOp, AttrUv, Dim); 63 | impl_passthrough_op_1!(UvGradientOp, AttrColor, Dim); 64 | impl_passthrough_op_1!(UvGradientOp, Raycast,); 65 | 66 | pub type UvGradient = Operator; 67 | 68 | impl UvGradient { 69 | pub fn axis(&mut self) -> &mut Vec2 { 70 | self.op().axis() 71 | } 72 | 73 | pub fn epsilon(&mut self) -> &mut f32 { 74 | self.op().epsilon() 75 | } 76 | } 77 | 78 | pub type UvTangent = Normalize>; 79 | 80 | impl UvTangent { 81 | pub fn sdf(&mut self) -> &mut Sdf { 82 | self.target().target() 83 | } 84 | 85 | pub fn axis(&mut self) -> &mut Vec2 { 86 | self.target().axis() 87 | } 88 | 89 | pub fn epsilon(&mut self) -> &mut f32 { 90 | self.target().epsilon() 91 | } 92 | } 93 | 94 | #[cfg(all(not(feature = "spirv-std"), test))] 95 | pub mod tests { 96 | use crate::{prelude::Point, test_op_attrs}; 97 | 98 | use super::UvGradient; 99 | 100 | #[test] 101 | fn test_gradient_tetrahedron() { 102 | UvGradient::::default(); 103 | } 104 | 105 | test_op_attrs!(UvGradient::); 106 | } 107 | -------------------------------------------------------------------------------- /src/field/field_operator/gradient/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod gradient_central_diff; 2 | pub mod gradient_tetrahedron; 3 | pub mod gradient_uv; 4 | -------------------------------------------------------------------------------- /src/field/field_operator/hollow.rs: -------------------------------------------------------------------------------- 1 | //! Convert a solid shape into a hollow one with an infinitely thin surface. 2 | 3 | use core::ops::Mul; 4 | 5 | use rust_gpu_bridge::{Abs, Sign}; 6 | use type_fields::macros::Field; 7 | 8 | use crate::{ 9 | impl_passthrough_op_1, 10 | prelude::{ 11 | items::position::Position, AttrColor, AttrDistance, AttrNormal, AttrTangent, AttrUv, 12 | Distance, Field, FieldOperator, Normal, Operator, Tangent, Uv, 13 | }, 14 | }; 15 | 16 | /// Convert a solid shape into a hollow one with an infinitely thin surface. 17 | #[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Field)] 18 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 19 | #[repr(C)] 20 | pub struct HollowOp; 21 | 22 | impl FieldOperator> for HollowOp 23 | where 24 | Sdf: Field>, 25 | { 26 | fn operator(&self, sdf: &Sdf, input: &Position) -> Distance { 27 | sdf.field(input).abs().into() 28 | } 29 | } 30 | 31 | impl FieldOperator> for HollowOp 32 | where 33 | Sdf: Field>, 34 | Sdf: Field>, 35 | Input: Clone + Mul, 36 | { 37 | fn operator(&self, sdf: &Sdf, input: &Position) -> Normal { 38 | let d = >>::field(sdf, input); 39 | let s = d.sign(); 40 | >>::field(sdf, &((*input).clone() * s)) 41 | } 42 | } 43 | 44 | impl FieldOperator> for HollowOp 45 | where 46 | Sdf: Field>, 47 | Sdf: Field>, 48 | Dim: Clone + Mul, 49 | { 50 | fn operator(&self, sdf: &Sdf, input: &Position) -> Tangent { 51 | let d = >>::field(sdf, input); 52 | let s = d.sign(); 53 | >>::field(sdf, &((*input).clone() * s)) 54 | } 55 | } 56 | 57 | impl FieldOperator> for HollowOp 58 | where 59 | Sdf: Field>, 60 | Sdf: Field>, 61 | Input: Clone + Mul, 62 | { 63 | fn operator(&self, sdf: &Sdf, input: &Position) -> Uv { 64 | let d = >>::field(sdf, input); 65 | let s = d.sign(); 66 | >>::field(sdf, &((*input).clone() * s)) 67 | } 68 | } 69 | 70 | impl_passthrough_op_1!(HollowOp, AttrColor, Dim); 71 | 72 | /// Convert a solid shape into a hollow one with an infinitely thin surface. 73 | pub type Hollow = Operator; 74 | 75 | #[cfg(all(not(feature = "spirv-std"), test))] 76 | pub mod tests { 77 | use crate::{prelude::Point, test_op_attrs}; 78 | 79 | use super::Hollow; 80 | 81 | #[test] 82 | fn test_gradient_tetrahedron() { 83 | Hollow::::default(); 84 | } 85 | 86 | test_op_attrs!(Hollow::); 87 | } 88 | -------------------------------------------------------------------------------- /src/field/field_operator/isosurface.rs: -------------------------------------------------------------------------------- 1 | //! Shift the isosurface of a distance field by a given amount. 2 | 3 | use core::ops::Div; 4 | 5 | use type_fields::macros::Field; 6 | 7 | use crate::{ 8 | impl_passthrough_op_1, 9 | prelude::{ 10 | items::position::Position, AttrColor, AttrDistance, AttrNormal, AttrTangent, AttrUv, 11 | Distance, Field, FieldOperator, Operator, 12 | }, 13 | }; 14 | 15 | /// Shift the isosurface of a distance field by a given amount. 16 | #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Field)] 17 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 18 | #[cfg_attr(feature = "bevy", derive(bevy::reflect::TypeUuid))] 19 | #[cfg_attr(feature = "bevy", uuid = "d588f817-4e15-4b1e-b98c-dc2b0d47f719")] 20 | #[repr(C)] 21 | pub struct IsosurfaceOp { 22 | pub delta: f32, 23 | } 24 | 25 | impl Default for IsosurfaceOp { 26 | fn default() -> Self { 27 | IsosurfaceOp { delta: 1.0 } 28 | } 29 | } 30 | 31 | impl FieldOperator> for IsosurfaceOp 32 | where 33 | SdfA: Field>, 34 | Input: Clone, 35 | { 36 | fn operator(&self, sdf_a: &SdfA, input: &Position) -> Distance { 37 | sdf_a.field(input) - self.delta 38 | } 39 | } 40 | 41 | impl_passthrough_op_1!(IsosurfaceOp, AttrNormal, Dim); 42 | impl_passthrough_op_1!(IsosurfaceOp, AttrTangent, Dim); 43 | 44 | impl FieldOperator> for IsosurfaceOp 45 | where 46 | SdfA: crate::prelude::Field>, 47 | Input: Clone + Div, 48 | { 49 | fn operator( 50 | &self, 51 | sdf_a: &SdfA, 52 | input: &Position, 53 | ) -> as crate::prelude::Attribute>::Output { 54 | let p = (*input).clone() / self.delta; 55 | sdf_a.field(&p) 56 | } 57 | } 58 | 59 | impl_passthrough_op_1!(IsosurfaceOp, AttrColor, Dim); 60 | 61 | /// Add an arbitrary radius to a distance field. 62 | pub type Isosurface = Operator; 63 | 64 | #[cfg(all(not(feature = "spirv-std"), test))] 65 | pub mod test { 66 | use crate::{ 67 | prelude::{Isosurface, Point}, 68 | test_op_attrs, 69 | }; 70 | 71 | #[test] 72 | fn test_isosurface() { 73 | Isosurface::::default(); 74 | } 75 | 76 | test_op_attrs!(Isosurface::); 77 | } 78 | -------------------------------------------------------------------------------- /src/field/field_operator/isosurface_proxy.rs: -------------------------------------------------------------------------------- 1 | //! Shift the isosurface of a distance field by a given amount. 2 | 3 | use core::ops::Div; 4 | 5 | use type_fields::macros::Field; 6 | 7 | use crate::{ 8 | impl_passthrough_op_2, 9 | prelude::{ 10 | items::position::Position, AttrColor, AttrDistance, AttrNormal, AttrTangent, AttrUv, 11 | DisplaceProxyOp, Distance, Field, FieldOperator, Operator, 12 | }, 13 | }; 14 | 15 | /// Shift the isosurface of a distance field by a given amount. 16 | #[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Field)] 17 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 18 | #[repr(C)] 19 | pub struct IsosurfaceProxyOp; 20 | 21 | impl FieldOperator<(SdfA, SdfB), AttrDistance> for IsosurfaceProxyOp 22 | where 23 | SdfA: Field>, 24 | SdfB: Field>, 25 | Input: Clone, 26 | { 27 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), p: &Position) -> Distance { 28 | let d1 = sdf_a.field(p); 29 | let d2 = sdf_b.field(p); 30 | d1 - *d2 31 | } 32 | } 33 | 34 | impl_passthrough_op_2!(IsosurfaceProxyOp, AttrNormal, 0, SdfA, Dim); 35 | impl_passthrough_op_2!(IsosurfaceProxyOp, AttrTangent, 0, SdfA, Dim); 36 | 37 | impl FieldOperator<(SdfA, SdfB), AttrUv> for IsosurfaceProxyOp 38 | where 39 | SdfA: crate::prelude::Field>, 40 | SdfB: crate::prelude::Field>, 41 | Input: Clone + Div, 42 | { 43 | fn operator( 44 | &self, 45 | (sdf_a, sdf_b): &(SdfA, SdfB), 46 | input: &Position, 47 | ) -> as crate::prelude::Attribute>::Output { 48 | let p = (*input).clone() / *sdf_b.field(input); 49 | sdf_a.field(&p) 50 | } 51 | } 52 | 53 | impl_passthrough_op_2!(IsosurfaceProxyOp, AttrColor, 0, SdfA, Dim); 54 | 55 | /// Add an arbitrary radius to a distance field. 56 | pub type IsosurfaceProxy = Operator; 57 | 58 | #[cfg(all(not(feature = "spirv-std"), test))] 59 | pub mod test { 60 | use crate::{ 61 | prelude::{IsosurfaceProxy, Point}, 62 | test_op_attrs, 63 | }; 64 | 65 | #[test] 66 | fn test_isosurface() { 67 | IsosurfaceProxy::::default(); 68 | } 69 | 70 | test_op_attrs!(IsosurfaceProxy::); 71 | } 72 | -------------------------------------------------------------------------------- /src/field/field_operator/mod.rs: -------------------------------------------------------------------------------- 1 | //! Types that modify a distance field. 2 | 3 | pub mod checker; 4 | pub mod color_normal; 5 | pub mod color_tangent; 6 | pub mod color_uv; 7 | pub mod composite; 8 | pub mod displace; 9 | pub mod displace_proxy; 10 | pub mod elongate; 11 | pub mod hollow; 12 | pub mod isosurface; 13 | pub mod isosurface_proxy; 14 | pub mod normalize; 15 | pub mod scale_uv; 16 | pub mod sided; 17 | pub mod stretch; 18 | pub mod triplanar_uv; 19 | pub mod twist; 20 | 21 | pub mod arity; 22 | pub mod boolean; 23 | pub mod coordinate_system; 24 | pub mod gradient; 25 | pub mod proxy; 26 | pub mod raycast; 27 | pub mod reflect; 28 | pub mod repeat; 29 | pub mod smooth_boolean; 30 | pub mod transform; 31 | 32 | use crate::prelude::{Attribute, Field}; 33 | 34 | /// Modifies the input / output of a [`FieldAttribute`]. 35 | pub trait FieldOperator 36 | where 37 | Attr: Attribute, 38 | { 39 | fn operator(&self, sdf: &Sdf, input: &Attr::Input) -> Attr::Output; 40 | } 41 | 42 | /// Applies a [`FieldOperator`] to a [`FieldAttribute`]. 43 | #[derive( 44 | Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Hash, type_fields::macros::Field, 45 | )] 46 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 47 | #[cfg_attr(feature = "bevy", derive(bevy::reflect::TypeUuid))] 48 | #[cfg_attr(feature = "bevy", uuid = "d588f817-4e15-4b1e-b98c-dc2b0d47f719")] 49 | #[repr(C)] 50 | pub struct Operator { 51 | pub target: Sdf, 52 | pub op: Op, 53 | } 54 | 55 | impl Field for Operator 56 | where 57 | Op: FieldOperator, 58 | Attr: Attribute, 59 | { 60 | fn field(&self, p: &Attr::Input) -> Attr::Output { 61 | self.op.operator(&self.target, p) 62 | } 63 | } 64 | 65 | #[cfg(feature = "glam")] 66 | pub mod boxed { 67 | extern crate alloc; 68 | use alloc::boxed::Box; 69 | 70 | use crate::prelude::{Attribute, FieldOperator}; 71 | 72 | impl FieldOperator for Box> 73 | where 74 | Attr: Attribute, 75 | { 76 | fn operator( 77 | &self, 78 | sdf: &Sdf, 79 | input: &::Input, 80 | ) -> ::Output { 81 | self.as_ref().operator(sdf, input) 82 | } 83 | } 84 | } 85 | 86 | #[cfg(all(not(feature = "spirv-std"), test))] 87 | pub mod test { 88 | use type_fields::field::Field; 89 | 90 | use crate::prelude::{IsosurfaceProxyOp, Operator, Point}; 91 | 92 | #[test] 93 | fn test_operator() { 94 | Operator::::default() 95 | .with(Operator::target, Point::default()) 96 | .with(Operator::op, IsosurfaceProxyOp::default()); 97 | } 98 | } 99 | 100 | #[macro_export] 101 | macro_rules! impl_passthrough_op_1 { 102 | ($ty:ty, $attr:ty, $($gen:tt)*) => { 103 | impl FieldOperator for $ty 104 | where 105 | Sdf: crate::prelude::Field<$attr>, 106 | { 107 | fn operator( 108 | &self, 109 | sdf: &Sdf, 110 | input: &<$attr as crate::prelude::Attribute>::Input, 111 | ) -> <$attr as crate::prelude::Attribute>::Output { 112 | sdf.field(input) 113 | } 114 | } 115 | }; 116 | } 117 | 118 | #[macro_export] 119 | macro_rules! impl_passthrough_op_2 { 120 | ($ty:ty, $attr:ty, $field:tt, $sdf:ident $($gen:tt)*) => { 121 | impl FieldOperator<(SdfA, SdfB), $attr> for $ty 122 | where 123 | $sdf: crate::prelude::Field<$attr>, 124 | { 125 | fn operator( 126 | &self, 127 | sdf: &(SdfA, SdfB), 128 | input: &<$attr as crate::prelude::Attribute>::Input, 129 | ) -> <$attr as crate::prelude::Attribute>::Output { 130 | sdf.$field.field(input) 131 | } 132 | } 133 | }; 134 | } 135 | 136 | #[macro_export] 137 | macro_rules! test_op_attrs { 138 | ($ty:ty) => { 139 | crate::test_op_attrs_1d!($ty); 140 | crate::test_op_attrs_2d!($ty); 141 | crate::test_op_attrs_3d!($ty); 142 | }; 143 | } 144 | 145 | #[macro_export] 146 | macro_rules! test_op_attrs_1d { 147 | ($ty:ty) => { 148 | crate::test_op_attrs_impl!($ty, test_attrs_1d, crate::prelude::Position, [crate::prelude::AttrDistance, crate::prelude::AttrNormal, crate::prelude::AttrUv]); 149 | }; 150 | } 151 | 152 | #[macro_export] 153 | macro_rules! test_op_attrs_2d { 154 | ($ty:ty) => { 155 | crate::test_op_attrs_impl!($ty, test_attrs_2d, crate::prelude::Position, [crate::prelude::AttrDistance, crate::prelude::AttrNormal, crate::prelude::AttrUv]); 156 | }; 157 | } 158 | 159 | #[macro_export] 160 | macro_rules! test_op_attrs_3d { 161 | ($ty:ty) => { 162 | crate::test_op_attrs_impl!($ty, test_attrs_3d, crate::prelude::Position, [crate::prelude::AttrDistance, crate::prelude::AttrNormal, crate::prelude::AttrUv]); 163 | }; 164 | } 165 | 166 | #[macro_export] 167 | macro_rules! test_op_attrs_impl { 168 | ($ty:ty, $ident:ident, $pos:ty, [$($attrs:ty),+]) => { 169 | #[test] 170 | fn $ident() { 171 | let f = <$ty>::default(); 172 | $( 173 | let _ = crate::prelude::Field::<$attrs>::field(&f, &<$pos>::default()); 174 | )* 175 | } 176 | }; 177 | } 178 | -------------------------------------------------------------------------------- /src/field/field_operator/normalize.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | impl_passthrough_op_1, 3 | prelude::{ 4 | items::position::Position, AttrColor, AttrDistance, AttrNormal, AttrTangent, AttrUv, Field, 5 | Normal, Raycast, Tangent, 6 | }, 7 | }; 8 | 9 | use super::{FieldOperator, Operator}; 10 | 11 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 12 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 13 | #[repr(C)] 14 | pub struct NormalizeOp; 15 | 16 | impl FieldOperator> for NormalizeOp 17 | where 18 | Sdf: Field>, 19 | Input: Clone + rust_gpu_bridge::Normalize, 20 | { 21 | fn operator(&self, sdf: &Sdf, p: &Position) -> Normal { 22 | (*sdf.field(p)).clone().normalize().into() 23 | } 24 | } 25 | 26 | impl FieldOperator> for NormalizeOp 27 | where 28 | Sdf: Field>, 29 | Input: Clone + rust_gpu_bridge::Normalize, 30 | { 31 | fn operator(&self, sdf: &Sdf, p: &Position) -> Tangent { 32 | (*sdf.field(p)).clone().normalize().into() 33 | } 34 | } 35 | 36 | impl_passthrough_op_1!(NormalizeOp, AttrDistance, Dim); 37 | impl_passthrough_op_1!(NormalizeOp, AttrUv, Dim); 38 | impl_passthrough_op_1!(NormalizeOp, AttrColor, Dim); 39 | impl_passthrough_op_1!(NormalizeOp, Raycast,); 40 | 41 | pub type Normalize = Operator; 42 | 43 | #[cfg(all(not(feature = "spirv-std"), test))] 44 | pub mod test { 45 | use crate::{prelude::Point, test_op_attrs}; 46 | 47 | use super::Normalize; 48 | 49 | #[test] 50 | fn test_normalize() { 51 | Normalize::::default(); 52 | } 53 | 54 | test_op_attrs!(Normalize::); 55 | } 56 | -------------------------------------------------------------------------------- /src/field/field_operator/proxy/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod proxy_color; 2 | pub mod proxy_normal; 3 | pub mod proxy_tangent; 4 | pub mod proxy_uv; 5 | -------------------------------------------------------------------------------- /src/field/field_operator/proxy/proxy_color.rs: -------------------------------------------------------------------------------- 1 | //! Override the colors of an SDF with the colors of another SDF 2 | 3 | use rust_gpu_bridge::glam::Vec4; 4 | use type_fields::macros::Field; 5 | 6 | use crate::{ 7 | impl_passthrough_op_2, 8 | prelude::{AttrColor, AttrDistance, FieldOperator, AttrNormal, Operator, Raycast, AttrTangent, AttrUv, items::position::Position}, impl_passthrough_op_1, 9 | }; 10 | 11 | /// Override the colors of an SDF with the colors of another SDF 12 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Field)] 13 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 14 | #[repr(C)] 15 | pub struct ProxyColorOp; 16 | 17 | impl_passthrough_op_2!(ProxyColorOp, AttrDistance, 0, SdfA, Dim); 18 | impl_passthrough_op_2!(ProxyColorOp, AttrNormal, 0, SdfA, Dim); 19 | impl_passthrough_op_2!(ProxyColorOp, AttrTangent, 0, SdfA, Dim); 20 | impl_passthrough_op_2!(ProxyColorOp, AttrUv, 0, SdfA, Dim); 21 | impl_passthrough_op_2!(ProxyColorOp, AttrColor, 1, SdfB, Dim); 22 | impl_passthrough_op_2!(ProxyColorOp, Raycast, 0, SdfA); 23 | 24 | pub type ProxyColor = Operator; 25 | 26 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 27 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 28 | #[repr(C)] 29 | pub struct WhiteOp; 30 | 31 | impl_passthrough_op_1!(WhiteOp, AttrDistance, Dim); 32 | impl_passthrough_op_1!(WhiteOp, AttrNormal, Dim); 33 | impl_passthrough_op_1!(WhiteOp, AttrTangent, Dim); 34 | impl_passthrough_op_1!(WhiteOp, AttrUv, Dim); 35 | 36 | impl crate::prelude::FieldOperator> for WhiteOp { 37 | fn operator( 38 | &self, 39 | _: &Sdf, 40 | _: &Position, 41 | ) -> as crate::prelude::Attribute>::Output { 42 | Vec4::ONE.into() 43 | } 44 | } 45 | 46 | impl_passthrough_op_2!(WhiteOp, Raycast, 0, SdfA); 47 | 48 | pub type White = Operator; 49 | 50 | #[cfg(all(not(feature = "spirv-std"), test))] 51 | pub mod test { 52 | use crate::{ 53 | prelude::{Point, ProxyColor}, 54 | test_op_attrs, 55 | }; 56 | 57 | #[test] 58 | fn test_sdf_color() { 59 | ProxyColor::::default(); 60 | } 61 | 62 | test_op_attrs!(ProxyColor::); 63 | } 64 | -------------------------------------------------------------------------------- /src/field/field_operator/proxy/proxy_normal.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | impl_passthrough_op_2, 3 | prelude::{AttrColor, AttrDistance, FieldOperator, AttrNormal, Operator, AttrTangent, AttrUv, Raycast}, 4 | }; 5 | 6 | /// Override the normals of an SDF with the normals of another SDF 7 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 8 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 9 | #[repr(C)] 10 | pub struct ProxyNormalOp; 11 | 12 | impl_passthrough_op_2!(ProxyNormalOp, AttrDistance, 0, SdfA, Dim); 13 | impl_passthrough_op_2!(ProxyNormalOp, AttrNormal, 1, SdfB, Dim); 14 | impl_passthrough_op_2!(ProxyNormalOp, AttrTangent, 0, SdfA, Dim); 15 | impl_passthrough_op_2!(ProxyNormalOp, AttrUv, 0, SdfA, Dim); 16 | impl_passthrough_op_2!(ProxyNormalOp, AttrColor, 0, SdfA, Dim); 17 | impl_passthrough_op_2!(ProxyNormalOp, Raycast, 0, SdfA); 18 | 19 | pub type ProxyNormal = Operator; 20 | 21 | #[cfg(all(not(feature = "spirv-std"), test))] 22 | pub mod test { 23 | use crate::{prelude::{Point, ProxyNormal}, test_op_attrs}; 24 | 25 | #[test] 26 | fn test_sdf_normal() { 27 | ProxyNormal::::default(); 28 | } 29 | 30 | test_op_attrs!(ProxyNormal::); 31 | } 32 | -------------------------------------------------------------------------------- /src/field/field_operator/proxy/proxy_tangent.rs: -------------------------------------------------------------------------------- 1 | use type_fields::macros::Field; 2 | 3 | use crate::{ 4 | impl_passthrough_op_2, 5 | prelude::{AttrColor, AttrDistance, FieldOperator, AttrNormal, Operator, Raycast, AttrTangent, AttrUv}, 6 | }; 7 | 8 | /// Override the tangents of an SDF with the tangents of another SDF 9 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Field)] 10 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 11 | #[repr(C)] 12 | pub struct ProxyTangentOp; 13 | 14 | impl_passthrough_op_2!(ProxyTangentOp, AttrDistance, 0, SdfA, Dim); 15 | impl_passthrough_op_2!(ProxyTangentOp, AttrNormal, 0, SdfA, Dim); 16 | impl_passthrough_op_2!(ProxyTangentOp, AttrTangent, 1, SdfB, Dim); 17 | impl_passthrough_op_2!(ProxyTangentOp, AttrUv, 0, SdfA, Dim); 18 | impl_passthrough_op_2!(ProxyTangentOp, AttrColor, 0, SdfA, Dim); 19 | impl_passthrough_op_2!(ProxyTangentOp, Raycast, 0, SdfA); 20 | 21 | pub type ProxyTangent = Operator; 22 | 23 | #[cfg(all(not(feature = "spirv-std"), test))] 24 | pub mod test { 25 | use crate::{ 26 | prelude::{Point, ProxyTangent}, 27 | test_op_attrs, 28 | }; 29 | 30 | #[test] 31 | fn test_sdf_tangent() { 32 | ProxyTangent::::default(); 33 | } 34 | 35 | test_op_attrs!(ProxyTangent::); 36 | } 37 | -------------------------------------------------------------------------------- /src/field/field_operator/proxy/proxy_uv.rs: -------------------------------------------------------------------------------- 1 | //! Override the UVs of an SDF with the UVs of another SDF 2 | 3 | use type_fields::macros::Field; 4 | 5 | use crate::{ 6 | impl_passthrough_op_2, 7 | prelude::{AttrColor, AttrDistance, FieldOperator, AttrNormal, Operator, Raycast, AttrTangent, AttrUv}, 8 | }; 9 | 10 | /// Override the UVs of an SDF with the UVs of another SDF 11 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Field)] 12 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 13 | #[repr(C)] 14 | pub struct ProxyUvOp; 15 | 16 | impl_passthrough_op_2!(ProxyUvOp, AttrDistance, 0, SdfA, Dim); 17 | impl_passthrough_op_2!(ProxyUvOp, AttrNormal, 0, SdfA, Dim); 18 | impl_passthrough_op_2!(ProxyUvOp, AttrTangent, 0, SdfA, Dim); 19 | impl_passthrough_op_2!(ProxyUvOp, AttrUv, 1, SdfB, Dim); 20 | impl_passthrough_op_2!(ProxyUvOp, AttrColor, 0, SdfA, Dim); 21 | impl_passthrough_op_2!(ProxyUvOp, Raycast, 0, SdfA); 22 | 23 | pub type ProxyUv = Operator; 24 | 25 | #[cfg(all(not(feature = "spirv-std"), test))] 26 | pub mod test { 27 | use crate::{ 28 | prelude::{Point, ProxyUv}, 29 | test_op_attrs, 30 | }; 31 | 32 | #[test] 33 | fn test_sdf_uv() { 34 | ProxyUv::::default(); 35 | } 36 | 37 | test_op_attrs!(ProxyUv::); 38 | } 39 | -------------------------------------------------------------------------------- /src/field/field_operator/raycast/mod.rs: -------------------------------------------------------------------------------- 1 | //! Operators dedicating to visualizing 3D distance functions via ray intersection 2 | 3 | pub mod raytrace; 4 | pub mod sphere_trace_lipschitz; 5 | pub mod sphere_trace_naive; 6 | 7 | use rust_gpu_bridge::glam::Vec3; 8 | 9 | use crate::{default, prelude::Attribute}; 10 | 11 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 12 | pub struct Raycast; 13 | 14 | impl Attribute for Raycast { 15 | type Input = RaycastInput; 16 | type Output = RaycastOutput; 17 | } 18 | 19 | #[derive(Copy, Clone, PartialEq)] 20 | #[repr(C)] 21 | pub struct RaycastInput { 22 | pub eye: Vec3, 23 | pub dir: Vec3, 24 | pub start: f32, 25 | pub end: f32, 26 | } 27 | 28 | impl Default for RaycastInput { 29 | fn default() -> Self { 30 | RaycastInput { 31 | eye: Vec3::ZERO, 32 | dir: -Vec3::Z, 33 | start: 0.0, 34 | end: 1000.0, 35 | } 36 | } 37 | } 38 | 39 | #[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] 40 | #[repr(C)] 41 | pub struct RaycastOutput { 42 | /// Minimum distance encountered between the ray and shape 43 | pub closest_dist: f32, 44 | /// Time at which the closest distance was encountered 45 | pub closest_t: f32, 46 | /// The amount of steps taken by this march 47 | pub steps: u32, 48 | } 49 | 50 | impl Default for RaycastOutput { 51 | fn default() -> Self { 52 | RaycastOutput { 53 | closest_dist: f32::MAX, 54 | closest_t: f32::MAX, 55 | steps: default(), 56 | } 57 | } 58 | } 59 | 60 | impl RaycastOutput { 61 | /// Notify the output that a step was taken at time `t` 62 | /// with a resulting distance of `dist` 63 | pub fn march_step(&mut self, t: f32, dist: f32) { 64 | if dist < self.closest_dist { 65 | self.closest_dist = dist; 66 | self.closest_t = t; 67 | } 68 | } 69 | 70 | /// Notify the output that marching ended in a hit at step `step` 71 | pub fn march_hit(&mut self, step: u32) { 72 | self.steps = step; 73 | } 74 | 75 | /// Notify the output that marching ended in a miss at step `step` 76 | pub fn march_miss(&mut self, step: u32) { 77 | self.steps = step; 78 | } 79 | 80 | pub fn hit(&self) -> bool { 81 | self.closest_dist <= 0.0 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/field/field_operator/raycast/raytrace.rs: -------------------------------------------------------------------------------- 1 | //! Analytical raytracer. 2 | 3 | use rust_gpu_bridge::glam::Vec3; 4 | use type_fields::macros::Field; 5 | 6 | use crate::{ 7 | impl_passthrough_op_1, 8 | prelude::{ 9 | AttrColor, AttrDistance, FieldOperator, AttrNormal, Operator, Raycast, RaycastInput, RaycastOutput, 10 | AttrTangent, AttrUv, 11 | }, 12 | }; 13 | 14 | /// Compute the intersection between self and the given ray 15 | pub trait RayIntersection { 16 | fn intersect(&self, eye: Vec3, dir: Vec3) -> Option; 17 | } 18 | 19 | /// Analytical raytracer. 20 | /// 21 | /// Evaluates the [`RayIntersection`] of the provided type. 22 | #[derive(Default, Copy, Clone, PartialEq, Field)] 23 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 24 | #[repr(C)] 25 | pub struct RaytraceOp; 26 | 27 | impl FieldOperator for RaytraceOp 28 | where 29 | Sdf: RayIntersection, 30 | { 31 | fn operator( 32 | &self, 33 | sdf: &Sdf, 34 | input: &RaycastInput, 35 | ) -> ::Output { 36 | let mut out = RaycastOutput::default(); 37 | 38 | out.steps = 1; 39 | 40 | if let Some(t) = sdf.intersect(input.eye + input.dir * input.start, input.dir) { 41 | out.closest_t = t; 42 | out.closest_dist = 0.0; 43 | } 44 | 45 | out 46 | } 47 | } 48 | 49 | impl_passthrough_op_1!(RaytraceOp, AttrDistance, Pos,); 50 | impl_passthrough_op_1!(RaytraceOp, AttrNormal, Pos,); 51 | impl_passthrough_op_1!(RaytraceOp, AttrTangent, Pos,); 52 | impl_passthrough_op_1!(RaytraceOp, AttrUv, Pos,); 53 | impl_passthrough_op_1!(RaytraceOp, AttrColor, Pos,); 54 | 55 | pub type Raytrace = Operator; 56 | -------------------------------------------------------------------------------- /src/field/field_operator/raycast/sphere_trace_lipschitz.rs: -------------------------------------------------------------------------------- 1 | use rust_gpu_bridge::{glam::Vec3, Abs}; 2 | use type_fields::macros::Field; 3 | 4 | use crate::{ 5 | impl_passthrough_op_1, 6 | prelude::{ 7 | AttrColor, AttrDistance, Field, FieldOperator, AttrNormal, Operator, Raycast, RaycastOutput, AttrTangent, 8 | AttrUv, 9 | }, 10 | }; 11 | 12 | use super::RaycastInput; 13 | 14 | /// Sphere tracer that operates with respect to a precomputed Lipschitz bound. 15 | /// 16 | /// Costs 1 extra divide per step compared to [`SphereTraceNaive`], 17 | /// but results in overall faster intersections. 18 | /// 19 | /// Note: The precomputed lipschitz bound `k` must be correct in respect to the 20 | /// provided SDF for accurate behaviour; incorrect values will result in visual artifacting. 21 | #[derive(Copy, Clone, PartialEq, Field)] 22 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 23 | #[repr(C)] 24 | pub struct SphereTraceLipschitzOp { 25 | pub epsilon: f32, 26 | pub frac_1_k: f32, 27 | } 28 | 29 | impl Default for SphereTraceLipschitzOp { 30 | fn default() -> Self { 31 | SphereTraceLipschitzOp { 32 | epsilon: 0.0001, 33 | frac_1_k: 1.0 / (SphereTraceLipschitzOp::::falloff_k(1.0, 3.0) * 3.0), 34 | } 35 | } 36 | } 37 | 38 | impl SphereTraceLipschitzOp { 39 | // Computes the global lipschitz bound of the falloff function 40 | // e: energy 41 | // R: radius 42 | fn falloff_k(e: f32, r: f32) -> f32 { 43 | 1.72 * e.abs() / r 44 | } 45 | } 46 | 47 | impl FieldOperator for SphereTraceLipschitzOp 48 | where 49 | Sdf: Field>, 50 | { 51 | fn operator(&self, sdf: &Sdf, input: &RaycastInput) -> RaycastOutput { 52 | let mut out = RaycastOutput::default(); 53 | 54 | let mut t = input.start; 55 | for i in 0..MAX_STEPS { 56 | let pos = input.eye + input.dir * t; 57 | let dist = sdf.field(&pos.into()); 58 | 59 | out.march_step(t, *dist); 60 | 61 | if *dist < 0.0 { 62 | out.march_hit(i); 63 | break; 64 | } 65 | 66 | t += self.epsilon.max(dist.abs() * self.frac_1_k); 67 | 68 | if t > input.end { 69 | out.march_miss(i); 70 | break; 71 | } 72 | } 73 | 74 | out 75 | } 76 | } 77 | 78 | impl_passthrough_op_1!(SphereTraceLipschitzOp, AttrDistance, Pos, const MAX_STEPS: u32); 79 | impl_passthrough_op_1!(SphereTraceLipschitzOp, AttrNormal, Pos, const MAX_STEPS: u32); 80 | impl_passthrough_op_1!(SphereTraceLipschitzOp, AttrTangent, Pos, const MAX_STEPS: u32); 81 | impl_passthrough_op_1!(SphereTraceLipschitzOp, AttrUv, Pos, const MAX_STEPS: u32); 82 | impl_passthrough_op_1!(SphereTraceLipschitzOp, AttrColor, Pos, const MAX_STEPS: u32); 83 | 84 | pub type SphereTraceLipschitz = 85 | Operator, Sdf>; 86 | 87 | impl SphereTraceLipschitz { 88 | pub fn epsilon(&mut self) -> &mut f32 { 89 | self.op().epsilon() 90 | } 91 | 92 | pub fn frac_1_k(&mut self) -> &mut f32 { 93 | self.op().frac_1_k() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/field/field_operator/raycast/sphere_trace_naive.rs: -------------------------------------------------------------------------------- 1 | use rust_gpu_bridge::{glam::Vec3, Abs}; 2 | use type_fields::macros::Field; 3 | 4 | use crate::{ 5 | impl_passthrough_op_1, 6 | prelude::{ 7 | AttrColor, AttrDistance, Field, FieldOperator, AttrNormal, Operator, RaycastInput, Raycast, 8 | RaycastOutput, AttrTangent, AttrUv, 9 | }, 10 | }; 11 | 12 | /// Basic sphere tracer. 13 | /// 14 | /// Marches along a ray, sampling the provided SDF at each step to determine 15 | /// a minimum safe distance for the following iteration. 16 | #[derive(Copy, Clone, PartialEq, Field)] 17 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 18 | #[repr(C)] 19 | pub struct SphereTraceNaiveOp { 20 | pub epsilon: f32, 21 | } 22 | 23 | impl Default for SphereTraceNaiveOp { 24 | fn default() -> Self { 25 | SphereTraceNaiveOp { epsilon: 0.0001 } 26 | } 27 | } 28 | 29 | impl FieldOperator 30 | for SphereTraceNaiveOp 31 | where 32 | Sdf: Field>, 33 | { 34 | fn operator( 35 | &self, 36 | sdf: &Sdf, 37 | input: &RaycastInput, 38 | ) -> ::Output { 39 | let mut out = RaycastOutput::default(); 40 | 41 | let mut t = input.start; 42 | 43 | for step in 0..MAX_STEPS { 44 | let pos = input.eye + input.dir * t; 45 | let dist = sdf.field(&pos.into()); 46 | 47 | out.march_step(t, *dist); 48 | 49 | if *dist < 0.0 { 50 | out.march_hit(step); 51 | break; 52 | } 53 | 54 | t += self.epsilon.max(dist.abs()); 55 | 56 | if t > input.end { 57 | out.march_miss(step); 58 | break; 59 | } 60 | } 61 | 62 | out 63 | } 64 | } 65 | 66 | impl_passthrough_op_1!(SphereTraceNaiveOp, AttrDistance, Pos, const MAX_STEPS: u32); 67 | impl_passthrough_op_1!(SphereTraceNaiveOp, AttrNormal, Pos, const MAX_STEPS: u32); 68 | impl_passthrough_op_1!(SphereTraceNaiveOp, AttrTangent, Pos, const MAX_STEPS: u32); 69 | impl_passthrough_op_1!(SphereTraceNaiveOp, AttrUv, Pos, const MAX_STEPS: u32); 70 | impl_passthrough_op_1!(SphereTraceNaiveOp, AttrColor, Pos, const MAX_STEPS: u32); 71 | 72 | pub type SphereTraceNaive = Operator, Sdf>; 73 | 74 | impl SphereTraceNaive { 75 | pub fn epsilon(&mut self) -> &mut f32 { 76 | self.op().epsilon() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/field/field_operator/reflect/axial_reflect.rs: -------------------------------------------------------------------------------- 1 | //! Reflect a distance field about an arbitrary axis. 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, Attribute, AttrDistance, Field, FieldOperator, AttrNormal, Operator, AttrUv, Distance, Normal, Uv, 11 | }; 12 | 13 | pub const AXIS_X: usize = 1; 14 | pub const AXIS_Y: usize = 2; 15 | pub const AXIS_Z: usize = 4; 16 | 17 | pub const AXIS_XY: usize = AXIS_X | AXIS_Y; 18 | pub const AXIS_XYZ: usize = AXIS_XY | AXIS_Z; 19 | 20 | /// Cheaply reflect a distance field about X / Y / Z using a const axis bitmask. 21 | /// NOTE: Will produce a bound unless any geometry crossing 22 | /// the reflecting planes is already a field w.r.t. its reflection. 23 | #[derive(Debug, Default, Copy, Clone, PartialEq, Field)] 24 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 25 | #[repr(C)] 26 | pub struct AxialReflectOp; 27 | 28 | impl FieldOperator for AxialReflectOp 29 | where 30 | Attr: Attribute>, 31 | Sdf: Field, 32 | { 33 | fn operator(&self, sdf: &Sdf, input: &Position) -> Attr::Output { 34 | let mut input = **input; 35 | 36 | if AXIS & AXIS_X > 0 { 37 | input = input.abs(); 38 | } 39 | 40 | sdf.field(&input.into()) 41 | } 42 | } 43 | 44 | impl FieldOperator> for AxialReflectOp 45 | where 46 | Sdf: Field>, 47 | { 48 | fn operator(&self, sdf: &Sdf, input: &Position) -> Distance { 49 | let mut input = **input; 50 | 51 | if AXIS & AXIS_X > 0 { 52 | input.x = input.x.abs(); 53 | } 54 | 55 | if AXIS & AXIS_Y > 0 { 56 | input.y = input.y.abs(); 57 | } 58 | 59 | sdf.field(&input.into()) 60 | } 61 | } 62 | 63 | impl FieldOperator> for AxialReflectOp 64 | where 65 | Sdf: Field>, 66 | { 67 | fn operator(&self, sdf: &Sdf, p: &Position) -> Normal { 68 | let mut pa = **p; 69 | 70 | if AXIS & AXIS_X > 0 { 71 | pa.x = p.x.abs(); 72 | } 73 | 74 | if AXIS & AXIS_Y > 0 { 75 | pa.y = p.y.abs(); 76 | } 77 | 78 | let mut n = sdf.field(&pa.into()); 79 | 80 | if AXIS & AXIS_X > 0 && p.x < 0.0 { 81 | n.x *= -1.0; 82 | } 83 | 84 | if AXIS & AXIS_Y > 0 && p.y < 0.0 { 85 | n.y *= -1.0; 86 | } 87 | 88 | n 89 | } 90 | } 91 | 92 | impl FieldOperator> for AxialReflectOp 93 | where 94 | Sdf: Field>, 95 | { 96 | fn operator(&self, sdf: &Sdf, p: &Position) -> Uv { 97 | let mut pa = *p; 98 | 99 | if AXIS & AXIS_X > 0 { 100 | pa.x = p.x.abs(); 101 | } 102 | 103 | if AXIS & AXIS_Y > 0 { 104 | pa.y = p.y.abs(); 105 | } 106 | 107 | let mut n = sdf.field(&pa); 108 | 109 | if AXIS & AXIS_X > 0 && p.x < 0.0 { 110 | n.x *= -1.0; 111 | } 112 | 113 | if AXIS & AXIS_Y > 0 && p.y < 0.0 { 114 | n.y *= -1.0; 115 | } 116 | 117 | n 118 | } 119 | } 120 | 121 | impl FieldOperator> for AxialReflectOp 122 | where 123 | Sdf: Field>, 124 | { 125 | fn operator(&self, sdf: &Sdf, p: &Position) -> Distance { 126 | let mut p = *p; 127 | 128 | if AXIS & AXIS_X > 0 { 129 | p.x = p.x.abs(); 130 | } 131 | 132 | if AXIS & AXIS_Y > 0 { 133 | p.y = p.y.abs(); 134 | } 135 | 136 | if AXIS & AXIS_Z > 0 { 137 | p.z = p.z.abs(); 138 | } 139 | 140 | sdf.field(&p) 141 | } 142 | } 143 | 144 | impl FieldOperator> for AxialReflectOp 145 | where 146 | Sdf: Field>, 147 | { 148 | fn operator(&self, sdf: &Sdf, p: &Position) -> Normal { 149 | let mut pa = *p; 150 | 151 | if AXIS & AXIS_X > 0 { 152 | pa.x = p.x.abs(); 153 | } 154 | 155 | if AXIS & AXIS_Y > 0 { 156 | pa.y = p.y.abs(); 157 | } 158 | 159 | if AXIS & AXIS_Z > 0 { 160 | pa.z = p.z.abs(); 161 | } 162 | 163 | let mut n = sdf.field(&pa); 164 | 165 | if AXIS & AXIS_X > 0 && p.x < 0.0 { 166 | n.x *= -1.0; 167 | } 168 | 169 | if AXIS & AXIS_Y > 0 && p.y < 0.0 { 170 | n.y *= -1.0; 171 | } 172 | 173 | if AXIS & AXIS_Z > 0 && p.z < 0.0 { 174 | n.z *= -1.0; 175 | } 176 | 177 | n 178 | } 179 | } 180 | 181 | impl FieldOperator> for AxialReflectOp 182 | where 183 | Sdf: Field>, 184 | { 185 | fn operator(&self, sdf: &Sdf, p: &Position) -> Uv { 186 | let mut pa = *p; 187 | 188 | if AXIS & AXIS_X > 0 { 189 | pa.x = p.x.abs(); 190 | } 191 | 192 | if AXIS & AXIS_Y > 0 { 193 | pa.y = p.y.abs(); 194 | } 195 | 196 | if AXIS & AXIS_Z > 0 { 197 | pa.z = p.z.abs(); 198 | } 199 | 200 | let mut n = sdf.field(&pa); 201 | 202 | if AXIS & AXIS_X > 0 && p.x < 0.0 { 203 | n.x *= -1.0; 204 | } 205 | 206 | if AXIS & AXIS_Y > 0 && p.y < 0.0 { 207 | n.y *= -1.0; 208 | } 209 | 210 | n 211 | } 212 | } 213 | 214 | /// Reflect a distance field about X / Y / Z 215 | pub type AxialReflect = Operator, Sdf>; 216 | 217 | #[cfg(all(not(feature = "spirv-std"), test))] 218 | pub mod tests { 219 | use crate::{ 220 | prelude::{Circle, Cube}, 221 | test_op_attrs, 222 | }; 223 | 224 | use super::{AxialReflect, AXIS_XYZ}; 225 | 226 | #[test] 227 | fn test_axial_reflect() { 228 | AxialReflect::::default(); 229 | } 230 | 231 | test_op_attrs!(AxialReflect::); 232 | } 233 | -------------------------------------------------------------------------------- /src/field/field_operator/reflect/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod axial_reflect; 2 | pub mod reflect; 3 | -------------------------------------------------------------------------------- /src/field/field_operator/reflect/reflect.rs: -------------------------------------------------------------------------------- 1 | //! Reflect a distance field about an arbitrary axis. 2 | 3 | use core::ops::{Mul, Sub}; 4 | 5 | use rust_gpu_bridge::{ 6 | glam::{Vec2, Vec3, Vec3Swizzles}, 7 | Dot, IsNormalized, Mix, Reflect as ReflectTrait, Splat, Step, 8 | }; 9 | use type_fields::macros::Field; 10 | 11 | use crate::prelude::{ 12 | items::position::Position, AttrDistance, AttrNormal, AttrUv, Distance, Field, FieldOperator, 13 | Normal, Operator, Uv, 14 | }; 15 | 16 | /// Reflect a distance field about an arbitrary axis. 17 | #[derive(Debug, Copy, Clone, PartialEq, Field)] 18 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 19 | #[repr(C)] 20 | pub struct ReflectOp { 21 | pub axis: Dim, 22 | } 23 | 24 | impl Default for ReflectOp { 25 | fn default() -> Self { 26 | ReflectOp { axis: 1.0 } 27 | } 28 | } 29 | 30 | impl Default for ReflectOp { 31 | fn default() -> Self { 32 | ReflectOp { axis: Vec2::X } 33 | } 34 | } 35 | 36 | impl Default for ReflectOp { 37 | fn default() -> Self { 38 | ReflectOp { axis: Vec3::X } 39 | } 40 | } 41 | 42 | impl FieldOperator> for ReflectOp 43 | where 44 | Sdf: Field>, 45 | Input: Clone + Sub + Mul + IsNormalized + Dot, 46 | { 47 | fn operator(&self, sdf: &Sdf, input: &Position) -> Distance { 48 | assert!( 49 | self.axis.clone().is_normalized(), 50 | "ReflectOp axis must be normalized" 51 | ); 52 | 53 | let q = input.clone() 54 | - self.axis.clone() * (**input).clone().dot(self.axis.clone()).min(0.0) * 2.0; 55 | 56 | sdf.field(&q) 57 | } 58 | } 59 | 60 | impl FieldOperator> for ReflectOp 61 | where 62 | Sdf: Field>, 63 | Input: Clone 64 | + Sub 65 | + Mul 66 | + IsNormalized 67 | + Dot 68 | + ReflectTrait 69 | + Mix 70 | + Splat, 71 | { 72 | fn operator(&self, sdf: &Sdf, input: &Position) -> Normal { 73 | assert!( 74 | self.axis.clone().is_normalized(), 75 | "ReflectOp axis must be normalized" 76 | ); 77 | 78 | let c = (**input).clone().dot(self.axis.clone()); 79 | let pc = self.axis.clone() * c.min(0.0) * 2.0; 80 | let q = (**input).clone() - pc; 81 | 82 | let n = (*sdf.field(&q.into())).clone(); 83 | n.clone() 84 | .mix(n.reflect(self.axis.clone()), Input::splat(c.step(0.0))) 85 | .into() 86 | } 87 | } 88 | 89 | impl FieldOperator> for ReflectOp 90 | where 91 | Sdf: Field>, 92 | { 93 | fn operator(&self, sdf: &Sdf, p: &Position) -> Uv { 94 | assert!( 95 | self.axis.is_normalized(), 96 | "ReflectOp axis must be normalized" 97 | ); 98 | 99 | let c = p; 100 | let pc = c.min(0.0) * 2.0; 101 | let q = **p - pc; 102 | 103 | let n = *sdf.field(&q.into()); 104 | n.mix(n * Vec2::new(-1.0, 1.0), Vec2::splat(c.step(0.0))) 105 | .into() 106 | } 107 | } 108 | 109 | impl FieldOperator> for ReflectOp 110 | where 111 | Sdf: Field>, 112 | { 113 | fn operator(&self, sdf: &Sdf, p: &Position) -> Uv { 114 | assert!( 115 | self.axis.is_normalized(), 116 | "ReflectOp axis must be normalized" 117 | ); 118 | 119 | let c = p.dot(self.axis); 120 | let pc = self.axis * c.min(0.0) * 2.0; 121 | let q = **p - pc; 122 | 123 | let n = sdf.field(&q.into()); 124 | n.mix(n.reflect(self.axis), Vec2::splat(c.step(0.0))).into() 125 | } 126 | } 127 | 128 | impl FieldOperator> for ReflectOp 129 | where 130 | Sdf: Field>, 131 | { 132 | fn operator(&self, sdf: &Sdf, p: &Position) -> Uv { 133 | assert!( 134 | self.axis.is_normalized(), 135 | "ReflectOp axis must be normalized" 136 | ); 137 | 138 | let c = p.dot(self.axis); 139 | let pc = self.axis * c.min(0.0) * 2.0; 140 | let q = **p - pc; 141 | 142 | let n = sdf.field(&q.into()); 143 | n.mix(n.reflect(self.axis.xy()), Vec2::splat(c.step(0.0))) 144 | .into() 145 | } 146 | } 147 | 148 | /// Reflect a distance field about an arbitrary axis. 149 | pub type Reflect = Operator, Sdf>; 150 | 151 | impl Reflect { 152 | pub fn axis(&mut self) -> &mut Dim { 153 | &mut self.op.axis 154 | } 155 | } 156 | 157 | #[cfg(all(not(feature = "spirv-std"), test))] 158 | pub mod test { 159 | use rust_gpu_bridge::glam::{Vec2, Vec3}; 160 | use type_fields::field::Field; 161 | 162 | use crate::{ 163 | prelude::{Point, Sphere}, 164 | test_op_attrs_1d, test_op_attrs_2d, test_op_attrs_3d, 165 | }; 166 | 167 | use super::Reflect; 168 | 169 | #[test] 170 | fn test_reflect() { 171 | Reflect::<_, Sphere>::default().with(Reflect::axis, Vec3::default()); 172 | } 173 | 174 | test_op_attrs_1d!(Reflect::); 175 | test_op_attrs_2d!(Reflect::); 176 | test_op_attrs_3d!(Reflect::); 177 | } 178 | -------------------------------------------------------------------------------- /src/field/field_operator/repeat/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod repeat_count; 2 | pub mod repeat_infinite; 3 | -------------------------------------------------------------------------------- /src/field/field_operator/repeat/repeat_count.rs: -------------------------------------------------------------------------------- 1 | //! Operators for repeating distance fields across a domain. 2 | 3 | use core::ops::{Div, Mul, Neg, Sub}; 4 | 5 | use rust_gpu_bridge::{ 6 | glam::{Vec2, Vec3}, 7 | Clamp, Round, 8 | }; 9 | use type_fields::macros::Field; 10 | 11 | use crate::prelude::{Attribute, Field, FieldOperator, Operator, Position}; 12 | 13 | /// Repeat a distance field a set number of times in one or more axes. 14 | #[derive(Debug, Copy, Clone, PartialEq, Field)] 15 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 16 | #[repr(C)] 17 | pub struct RepeatCountOp { 18 | pub period: Dim, 19 | pub count: Dim, 20 | } 21 | 22 | impl Default for RepeatCountOp { 23 | fn default() -> Self { 24 | RepeatCountOp { 25 | period: 1.0, 26 | count: 1.0, 27 | } 28 | } 29 | } 30 | 31 | impl Default for RepeatCountOp { 32 | fn default() -> Self { 33 | RepeatCountOp { 34 | period: Vec2::ONE, 35 | count: Vec2::ONE, 36 | } 37 | } 38 | } 39 | 40 | impl Default for RepeatCountOp { 41 | fn default() -> Self { 42 | RepeatCountOp { 43 | period: Vec3::ONE, 44 | count: Vec3::ONE, 45 | } 46 | } 47 | } 48 | 49 | impl FieldOperator for RepeatCountOp 50 | where 51 | Attr: Attribute>, 52 | Sdf: Field, 53 | Dim: Clone 54 | + Div 55 | + Neg 56 | + Mul 57 | + Sub 58 | + Round 59 | + Clamp, 60 | { 61 | fn operator(&self, sdf: &Sdf, p: &Position) -> Attr::Output { 62 | let q = p.clone() 63 | - self.period.clone() 64 | * ((**p).clone() / self.period.clone()) 65 | .round() 66 | .clamp(-self.count.clone(), self.count.clone()); 67 | sdf.field(&q) 68 | } 69 | } 70 | 71 | /// Repeat a distance field a set number of times in one or more axes. 72 | pub type RepeatCount = Operator, Sdf>; 73 | 74 | impl RepeatCount { 75 | pub fn period(&mut self) -> &mut Dim { 76 | &mut self.op.period 77 | } 78 | 79 | pub fn count(&mut self) -> &mut Dim { 80 | &mut self.op.count 81 | } 82 | } 83 | 84 | #[cfg(all(not(feature = "spirv-std"), test))] 85 | pub mod tests { 86 | use rust_gpu_bridge::glam::{Vec2, Vec3}; 87 | use type_fields::field::Field; 88 | 89 | use crate::{ 90 | prelude::{Point, RepeatCount, Sphere}, 91 | test_op_attrs_1d, test_op_attrs_2d, test_op_attrs_3d, 92 | }; 93 | 94 | #[test] 95 | fn test_repeat_count() { 96 | RepeatCount::<_, Sphere>::default() 97 | .with(RepeatCount::period, Vec3::default()) 98 | .with(RepeatCount::count, Vec3::default()); 99 | } 100 | 101 | test_op_attrs_1d!(RepeatCount::); 102 | test_op_attrs_2d!(RepeatCount::); 103 | test_op_attrs_3d!(RepeatCount::); 104 | } 105 | -------------------------------------------------------------------------------- /src/field/field_operator/repeat/repeat_infinite.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Add, Mul, Sub}; 2 | 3 | use rust_gpu_bridge::{ 4 | glam::{Vec2, Vec3}, 5 | Mod, 6 | }; 7 | use type_fields::macros::Field; 8 | 9 | use crate::prelude::{Attribute, Field, FieldOperator, Operator, Position}; 10 | 11 | /// Repeat a distance field infinitely in one or more axes. 12 | #[derive(Debug, Copy, Clone, PartialEq, Field)] 13 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 14 | #[repr(C)] 15 | pub struct RepeatInfiniteOp { 16 | pub period: Dim, 17 | } 18 | 19 | impl Default for RepeatInfiniteOp { 20 | fn default() -> Self { 21 | RepeatInfiniteOp { period: 1.0 } 22 | } 23 | } 24 | 25 | impl Default for RepeatInfiniteOp { 26 | fn default() -> Self { 27 | RepeatInfiniteOp { period: Vec2::ONE } 28 | } 29 | } 30 | 31 | impl Default for RepeatInfiniteOp { 32 | fn default() -> Self { 33 | RepeatInfiniteOp { period: Vec3::ONE } 34 | } 35 | } 36 | 37 | impl FieldOperator for RepeatInfiniteOp 38 | where 39 | Attr: Attribute>, 40 | Sdf: Field, 41 | Input: Add 42 | + Add 43 | + Sub 44 | + Mul 45 | + Mul 46 | + Mod 47 | + Clone, 48 | { 49 | fn operator(&self, sdf: &Sdf, p: &Position) -> Attr::Output { 50 | let q = ((**p).clone().add(0.5).mul(self.period.clone())) 51 | .modulo(self.period.clone()) 52 | .sub(self.period.clone().mul(0.5)); 53 | sdf.field(&q.into()) 54 | } 55 | } 56 | 57 | /// Repeat a distance field infinitely in one or more axes. 58 | pub type RepeatInfinite = Operator, Sdf>; 59 | 60 | impl RepeatInfinite { 61 | pub fn period(&mut self) -> &mut Dim { 62 | &mut self.op.period 63 | } 64 | } 65 | 66 | #[cfg(all(not(feature = "spirv-std"), test))] 67 | pub mod tests { 68 | use rust_gpu_bridge::glam::{Vec2, Vec3}; 69 | use type_fields::field::Field; 70 | 71 | use crate::{ 72 | prelude::{Point, RepeatInfinite, Sphere}, 73 | test_op_attrs_1d, test_op_attrs_2d, test_op_attrs_3d, 74 | }; 75 | 76 | #[test] 77 | fn test_repeat_infinite() { 78 | RepeatInfinite::<_, Sphere>::default().with(RepeatInfinite::period, Vec3::default()); 79 | } 80 | 81 | test_op_attrs_1d!(RepeatInfinite::); 82 | test_op_attrs_2d!(RepeatInfinite::); 83 | test_op_attrs_3d!(RepeatInfinite::); 84 | } 85 | -------------------------------------------------------------------------------- /src/field/field_operator/scale_uv.rs: -------------------------------------------------------------------------------- 1 | use rust_gpu_bridge::glam::Vec2; 2 | 3 | use crate::{ 4 | impl_passthrough_op_1, 5 | prelude::{ 6 | items::position::Position, AttrColor, AttrDistance, AttrNormal, AttrTangent, AttrUv, Field, 7 | Raycast, 8 | }, 9 | }; 10 | 11 | use super::{FieldOperator, Operator}; 12 | 13 | #[derive(Copy, Clone, PartialEq, type_fields::macros::Field)] 14 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 15 | pub struct ScaleUvOp { 16 | pub scale: Vec2, 17 | } 18 | 19 | impl Default for ScaleUvOp { 20 | fn default() -> Self { 21 | ScaleUvOp { 22 | scale: Vec2::ONE * 10.0, 23 | } 24 | } 25 | } 26 | 27 | impl FieldOperator> for ScaleUvOp 28 | where 29 | Sdf: Field>, 30 | { 31 | fn operator( 32 | &self, 33 | sdf: &Sdf, 34 | p: &Position, 35 | ) -> as crate::prelude::Attribute>::Output { 36 | let uv = *sdf.field(p); 37 | (uv * self.scale).into() 38 | } 39 | } 40 | 41 | impl_passthrough_op_1!(ScaleUvOp, AttrDistance, Dim); 42 | impl_passthrough_op_1!(ScaleUvOp, AttrNormal, Dim); 43 | impl_passthrough_op_1!(ScaleUvOp, AttrTangent, Dim); 44 | impl_passthrough_op_1!(ScaleUvOp, AttrColor, Dim); 45 | impl_passthrough_op_1!(ScaleUvOp, Raycast,); 46 | 47 | pub type ScaleUv = Operator; 48 | -------------------------------------------------------------------------------- /src/field/field_operator/sided.rs: -------------------------------------------------------------------------------- 1 | //! Given an infinitely-thin surface, 2 | //! divide space into interior and exterior based on axis. 3 | 4 | use core::ops::Mul; 5 | 6 | use rust_gpu_bridge::{ 7 | glam::{Vec2, Vec3}, 8 | Dot, Sign, 9 | }; 10 | use type_fields::macros::Field; 11 | 12 | use crate::prelude::{ 13 | items::position::Position, AttrDistance, AttrNormal, AttrUv, Distance, Field, Normal, Uv, 14 | }; 15 | 16 | use super::{FieldOperator, Operator}; 17 | 18 | /// Given an infinitely-thin surface, 19 | /// divide space into interior and exterior based on axis. 20 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Field)] 21 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 22 | #[repr(C)] 23 | pub struct SidedOp { 24 | pub axis: Dim, 25 | } 26 | 27 | impl Default for SidedOp { 28 | fn default() -> Self { 29 | SidedOp { axis: 1.0 } 30 | } 31 | } 32 | 33 | impl Default for SidedOp { 34 | fn default() -> Self { 35 | SidedOp { axis: Vec2::Y } 36 | } 37 | } 38 | 39 | impl Default for SidedOp { 40 | fn default() -> Self { 41 | SidedOp { axis: Vec3::Y } 42 | } 43 | } 44 | 45 | impl FieldOperator> for SidedOp 46 | where 47 | Sdf: Field>, 48 | Input: Clone + Mul + Sign + Dot, 49 | { 50 | fn operator(&self, sdf: &Sdf, p: &Position) -> Distance { 51 | (*sdf.field(p) * (**p).clone().dot(self.axis.clone()).sign()).into() 52 | } 53 | } 54 | 55 | impl FieldOperator> for SidedOp 56 | where 57 | Sdf: Field>, 58 | Input: Clone + Dot + Mul, 59 | { 60 | fn operator(&self, sdf: &Sdf, p: &Position) -> Normal { 61 | let n = (*sdf.field(p)).clone(); 62 | (n * (**p).clone().dot(self.axis.clone()).sign()).into() 63 | } 64 | } 65 | 66 | impl FieldOperator> for SidedOp 67 | where 68 | Sdf: Field>, 69 | Input: Clone + Dot + Mul, 70 | { 71 | fn operator(&self, sdf: &Sdf, p: &Position) -> Uv { 72 | ((*sdf.field(p)).clone() * (**p).clone().dot(self.axis.clone()).sign()).into() 73 | } 74 | } 75 | 76 | pub type Sided = Operator, Sdf>; 77 | 78 | /// Given an infinitely-thin surface, 79 | /// divide space into interior and exterior based on axis. 80 | impl Sided { 81 | pub fn axis(&mut self) -> &mut Dim { 82 | &mut self.op.axis 83 | } 84 | } 85 | 86 | #[cfg(all(not(feature = "spirv-std"), test))] 87 | pub mod test { 88 | use rust_gpu_bridge::glam::{Vec2, Vec3}; 89 | use type_fields::field::Field; 90 | 91 | use crate::{ 92 | prelude::{Line, Point, Sided}, 93 | test_op_attrs_1d, test_op_attrs_2d, test_op_attrs_3d, 94 | }; 95 | 96 | #[test] 97 | fn test_sided() { 98 | Sided::<_, Line>::default().with(Sided::axis, Vec3::default()); 99 | } 100 | 101 | test_op_attrs_1d!(Sided::); 102 | test_op_attrs_2d!(Sided::); 103 | test_op_attrs_3d!(Sided::); 104 | } 105 | -------------------------------------------------------------------------------- /src/field/field_operator/smooth_boolean/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod smooth_intersection; 2 | pub mod smooth_subtraction; 3 | pub mod smooth_union; 4 | -------------------------------------------------------------------------------- /src/field/field_operator/smooth_boolean/smooth_intersection.rs: -------------------------------------------------------------------------------- 1 | //! Compute the blended boolean intersection of two distance fields. 2 | 3 | use core::ops::{Add, Div, Mul, Sub}; 4 | 5 | use rust_gpu_bridge::{glam::Vec2, Clamp, Mix, Normalize, Saturate, Splat, Step}; 6 | use type_fields::macros::Field; 7 | 8 | use crate::prelude::{ 9 | items::position::Position, AttrDistance, AttrNormal, AttrTangent, AttrUv, Distance, Field, 10 | FieldOperator, Normal, Operator, Tangent, Uv, 11 | }; 12 | 13 | /// Compute the blended boolean intersection of two distance fields. 14 | #[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Field)] 15 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 16 | #[repr(C)] 17 | pub struct SmoothIntersectionOp { 18 | pub k: f32, 19 | } 20 | 21 | impl FieldOperator<(SdfA, SdfB), AttrDistance> for SmoothIntersectionOp 22 | where 23 | SdfA: Field>, 24 | SdfB: Field>, 25 | Input: Clone, 26 | { 27 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), p: &Position) -> Distance { 28 | let d1 = *sdf_a.field(p); 29 | let d2 = *sdf_b.field(p); 30 | let h = (0.5 - 0.5 * (d2 - d1) / self.k).clamp(0.0, 1.0); 31 | d2.mix(d1, h).add(self.k.mul(h).mul(1.0 - h)).into() 32 | } 33 | } 34 | 35 | impl FieldOperator<(SdfA, SdfB), AttrNormal> for SmoothIntersectionOp 36 | where 37 | SdfA: Field>, 38 | SdfA: Field>, 39 | SdfB: Field>, 40 | SdfB: Field>, 41 | Dim: Clone 42 | + Sub 43 | + Div 44 | + Mul 45 | + Mul 46 | + Add 47 | + Add 48 | + Clamp 49 | + Mix 50 | + Saturate 51 | + Normalize 52 | + Splat, 53 | { 54 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), p: &Position) -> Normal { 55 | let d1 = (*Field::>::field(sdf_a, p)).clone(); 56 | let d2 = (*Field::>::field(sdf_b, p)).clone(); 57 | 58 | let h = (d2 - d1).div(self.k).mul(0.5).sub(0.5).saturate(); 59 | 60 | let n1 = (*Field::>::field(sdf_a, p)).clone(); 61 | let n2 = (*Field::>::field(sdf_b, p)).clone(); 62 | 63 | n2.mix(n1, Dim::splat(h)).normalize().into() 64 | } 65 | } 66 | 67 | impl FieldOperator<(SdfA, SdfB), AttrTangent> for SmoothIntersectionOp 68 | where 69 | SdfA: Field>, 70 | SdfA: Field>, 71 | SdfB: Field>, 72 | SdfB: Field>, 73 | Input: Clone 74 | + Sub 75 | + Div 76 | + Mul 77 | + Mul 78 | + Add 79 | + Add 80 | + Clamp 81 | + Mix 82 | + Saturate 83 | + Normalize 84 | + Splat, 85 | { 86 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), p: &Position) -> Tangent { 87 | let d1 = (*Field::>::field(sdf_a, p)).clone(); 88 | let d2 = (*Field::>::field(sdf_b, p)).clone(); 89 | 90 | let h = (d2 - d1).div(self.k).mul(0.5).sub(0.5).saturate(); 91 | 92 | let n1 = (*Field::>::field(sdf_a, p)).clone(); 93 | let n2 = (*Field::>::field(sdf_b, p)).clone(); 94 | 95 | n2.mix(n1, Input::splat(h)).normalize().into() 96 | } 97 | } 98 | 99 | impl FieldOperator<(SdfA, SdfB), AttrUv> for SmoothIntersectionOp 100 | where 101 | SdfA: Field>, 102 | SdfA: Field>, 103 | SdfB: Field>, 104 | SdfB: Field>, 105 | Input: Clone 106 | + Sub 107 | + Div 108 | + Mul 109 | + Mul 110 | + Add 111 | + Add 112 | + Clamp 113 | + Mix 114 | + Saturate 115 | + Normalize 116 | + Splat, 117 | { 118 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), p: &Position) -> Uv { 119 | let d1 = (*Field::>::field(sdf_a, p)).clone(); 120 | let d2 = (*Field::>::field(sdf_b, p)).clone(); 121 | 122 | let h = (d2 - d1).div(self.k).mul(0.5).sub(0.5).saturate(); 123 | 124 | let uv1 = *Field::>::field(sdf_a, p); 125 | let uv2 = *Field::>::field(sdf_b, p); 126 | 127 | uv2.mix(uv1, Vec2::splat(h.step(0.5))).into() 128 | } 129 | } 130 | 131 | /// Compute the blended boolean intersection of two distance fields. 132 | pub type SmoothIntersection = Operator; 133 | 134 | impl SmoothIntersection { 135 | pub fn sdf_a(&mut self) -> &mut SdfA { 136 | &mut self.target().0 137 | } 138 | 139 | pub fn sdf_b(&mut self) -> &mut SdfB { 140 | &mut self.target().1 141 | } 142 | 143 | pub fn k(&mut self) -> &mut f32 { 144 | self.op().k() 145 | } 146 | } 147 | 148 | #[cfg(all(not(feature = "spirv-std"), test))] 149 | pub mod test { 150 | use type_fields::field::Field; 151 | 152 | use crate::{ 153 | prelude::{Cube, Point, SmoothIntersection, Sphere}, 154 | test_op_attrs, 155 | }; 156 | 157 | #[test] 158 | fn test_smooth_intersection() { 159 | SmoothIntersection::::default().with(SmoothIntersection::k, f32::default()); 160 | } 161 | 162 | test_op_attrs!(SmoothIntersection::); 163 | } 164 | -------------------------------------------------------------------------------- /src/field/field_operator/smooth_boolean/smooth_subtraction.rs: -------------------------------------------------------------------------------- 1 | //! Compute the blended boolean subtraction of two distance fields. 2 | use core::ops::{Add, Div, Mul, Sub}; 3 | 4 | use rust_gpu_bridge::{glam::Vec2, Clamp, Mix, Normalize, Saturate, Splat, Step}; 5 | use type_fields::macros::Field; 6 | 7 | use crate::prelude::{ 8 | items::position::Position, AttrDistance, AttrNormal, AttrTangent, AttrUv, Distance, Field, 9 | FieldOperator, Normal, Operator, Tangent, Uv, 10 | }; 11 | 12 | /// Compute the blended boolean subtraction of two distance fields. 13 | #[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Field)] 14 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 15 | #[repr(C)] 16 | pub struct SmoothSubtractionOp { 17 | pub k: f32, 18 | } 19 | 20 | impl FieldOperator<(SdfA, SdfB), AttrDistance> for SmoothSubtractionOp 21 | where 22 | SdfA: Field>, 23 | SdfB: Field>, 24 | Input: Clone, 25 | { 26 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), p: &Position) -> Distance { 27 | let d1 = *sdf_a.field(p); 28 | let d2 = *sdf_b.field(p); 29 | let h = (0.5 - 0.5 * (d2 + d1) / self.k).clamp(0.0, 1.0); 30 | d2.mix(-d1, h).add(self.k.mul(h).mul(1.0 - h)).into() 31 | } 32 | } 33 | 34 | impl FieldOperator<(SdfA, SdfB), AttrNormal> for SmoothSubtractionOp 35 | where 36 | SdfA: Field>, 37 | SdfA: Field>, 38 | SdfB: Field>, 39 | SdfB: Field>, 40 | Input: Clone 41 | + Sub 42 | + Div 43 | + Mul 44 | + Mul 45 | + Add 46 | + Add 47 | + Clamp 48 | + Mix 49 | + Saturate 50 | + Normalize 51 | + Splat, 52 | { 53 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), p: &Position) -> Normal { 54 | let d1 = (*Field::>::field(sdf_a, p)).clone(); 55 | let d2 = (*Field::>::field(sdf_b, p)).clone(); 56 | 57 | let h = (d2 + d1).div(self.k).mul(0.5).sub(0.5).saturate(); 58 | 59 | let n1 = (*Field::>::field(sdf_a, p)).clone(); 60 | let n2 = (*Field::>::field(sdf_b, p)).clone(); 61 | 62 | n2.mix(n1.mul(-1.0), Input::splat(h)).normalize().into() 63 | } 64 | } 65 | 66 | impl FieldOperator<(SdfA, SdfB), AttrTangent> for SmoothSubtractionOp 67 | where 68 | SdfA: Field>, 69 | SdfA: Field>, 70 | SdfB: Field>, 71 | SdfB: Field>, 72 | Input: Clone 73 | + Sub 74 | + Div 75 | + Mul 76 | + Mul 77 | + Add 78 | + Add 79 | + Clamp 80 | + Mix 81 | + Saturate 82 | + Normalize 83 | + Splat, 84 | { 85 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), p: &Position) -> Tangent { 86 | let d1 = *Field::>::field(sdf_a, p); 87 | let d2 = *Field::>::field(sdf_b, p); 88 | 89 | let h = (d2.clone() + d1.clone()) 90 | .div(self.k) 91 | .mul(0.5) 92 | .sub(0.5) 93 | .saturate(); 94 | 95 | let t1 = (*Field::>::field(sdf_a, p)).clone(); 96 | let t2 = (*Field::>::field(sdf_b, p)).clone(); 97 | 98 | t2.mix(t1.mul(-1.0), Input::splat(h)).normalize().into() 99 | } 100 | } 101 | 102 | impl FieldOperator<(SdfA, SdfB), AttrUv> for SmoothSubtractionOp 103 | where 104 | SdfA: Field>, 105 | SdfA: Field>, 106 | SdfB: Field>, 107 | SdfB: Field>, 108 | Input: Clone 109 | + Sub 110 | + Div 111 | + Mul 112 | + Mul 113 | + Add 114 | + Add 115 | + Clamp 116 | + Mix 117 | + Saturate 118 | + Normalize 119 | + Splat, 120 | { 121 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), p: &Position) -> Uv { 122 | let d1 = *Field::>::field(sdf_a, p); 123 | let d2 = *Field::>::field(sdf_b, p); 124 | 125 | let h = (d2.clone() + d1.clone()) 126 | .div(self.k) 127 | .mul(0.5) 128 | .sub(0.5) 129 | .saturate(); 130 | 131 | let uv1 = *Field::>::field(sdf_a, p); 132 | let uv2 = *Field::>::field(sdf_b, p); 133 | 134 | uv2.mix(uv1.mul(-1.0), Vec2::splat(h.step(0.5))).into() 135 | } 136 | } 137 | 138 | /// Compute the blended boolean subtraction of two distance fields. 139 | pub type SmoothSubtraction = Operator; 140 | 141 | impl SmoothSubtraction { 142 | pub fn sdf_a(&mut self) -> &mut SdfA { 143 | &mut self.target().0 144 | } 145 | 146 | pub fn sdf_b(&mut self) -> &mut SdfB { 147 | &mut self.target().1 148 | } 149 | 150 | pub fn k(&mut self) -> &mut f32 { 151 | &mut self.op().k 152 | } 153 | } 154 | 155 | #[cfg(all(not(feature = "spirv-std"), test))] 156 | pub mod test { 157 | use type_fields::field::Field; 158 | 159 | use crate::{ 160 | prelude::{Cube, Point, SmoothSubtraction, Sphere}, 161 | test_op_attrs, 162 | }; 163 | 164 | #[test] 165 | fn test_smooth_subtraction() { 166 | SmoothSubtraction::::default().with(SmoothSubtraction::k, f32::default()); 167 | } 168 | 169 | test_op_attrs!(SmoothSubtraction::); 170 | } 171 | -------------------------------------------------------------------------------- /src/field/field_operator/smooth_boolean/smooth_union.rs: -------------------------------------------------------------------------------- 1 | //! Compute the blended boolean union of two distance fields. 2 | 3 | use core::ops::{Add, Div, Mul, Sub}; 4 | 5 | use rust_gpu_bridge::{Clamp, Mix, Normalize, Saturate, Splat}; 6 | use type_fields::macros::Field; 7 | 8 | use crate::prelude::{ 9 | items::position::Position, AttrDistance, AttrNormal, AttrTangent, AttrUv, Distance, Field, 10 | FieldOperator, Normal, Operator, Tangent, Uv, 11 | }; 12 | 13 | /// Compute the blended boolean union of two distance fields. 14 | #[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Field)] 15 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 16 | #[repr(C)] 17 | pub struct SmoothUnionOp { 18 | pub k: f32, 19 | } 20 | 21 | impl FieldOperator<(SdfA, SdfB), AttrDistance> for SmoothUnionOp 22 | where 23 | SdfA: Field>, 24 | SdfB: Field>, 25 | Input: Clone, 26 | { 27 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), input: &Position) -> Distance { 28 | let d1 = *sdf_a.field(input); 29 | let d2 = *sdf_b.field(input); 30 | let h = (0.5 + 0.5 * (d2 - d1) / self.k).clamp(0.0, 1.0); 31 | d2.mix(d1, h).sub(self.k * h * (1.0 - h)).into() 32 | } 33 | } 34 | 35 | impl FieldOperator<(SdfA, SdfB), AttrNormal> for SmoothUnionOp 36 | where 37 | SdfA: Field>, 38 | SdfA: Field>, 39 | SdfB: Field>, 40 | SdfB: Field>, 41 | Input: Clone 42 | + Sub 43 | + Div 44 | + Mul 45 | + Mul 46 | + Add 47 | + Clamp 48 | + Mix 49 | + Saturate 50 | + Normalize 51 | + Splat, 52 | { 53 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), input: &Position) -> Normal { 54 | let d1 = *Field::>::field(sdf_a, input); 55 | let d2 = *Field::>::field(sdf_b, input); 56 | 57 | let h = ((d2 - d1).div(self.k).mul(0.5).add(0.5)).saturate(); 58 | 59 | let n1 = (*Field::>::field(sdf_a, input)).clone(); 60 | let n2 = (*Field::>::field(sdf_b, input)).clone(); 61 | 62 | n2.mix(n1, Input::splat(h)).normalize().into() 63 | } 64 | } 65 | 66 | impl FieldOperator<(SdfA, SdfB), AttrTangent> for SmoothUnionOp 67 | where 68 | SdfA: Field>, 69 | SdfA: Field>, 70 | SdfB: Field>, 71 | SdfB: Field>, 72 | Dim: Clone 73 | + Sub 74 | + Div 75 | + Mul 76 | + Mul 77 | + Add 78 | + Clamp 79 | + Mix 80 | + Saturate 81 | + Normalize 82 | + Splat, 83 | { 84 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), p: &Position) -> Tangent { 85 | let d1 = *Field::>::field(sdf_a, p); 86 | let d2 = *Field::>::field(sdf_b, p); 87 | let h = ((d2 - d1).div(self.k).mul(0.5).add(0.5)).saturate(); 88 | 89 | let t1 = (*Field::>::field(sdf_a, p)).clone(); 90 | let t2 = (*Field::>::field(sdf_b, p)).clone(); 91 | 92 | t2.mix(t1, Dim::splat(h)).normalize().into() 93 | } 94 | } 95 | 96 | impl FieldOperator<(SdfA, SdfB), AttrUv> for SmoothUnionOp 97 | where 98 | SdfA: Field>, 99 | SdfA: Field>, 100 | SdfB: Field>, 101 | SdfB: Field>, 102 | Input: Clone 103 | + Sub 104 | + Div 105 | + Mul 106 | + Mul 107 | + Add 108 | + Clamp 109 | + Mix 110 | + Saturate 111 | + Normalize 112 | + Splat, 113 | { 114 | fn operator(&self, (sdf_a, sdf_b): &(SdfA, SdfB), input: &Position) -> Uv { 115 | let d1 = *Field::>::field(sdf_a, input); 116 | let d2 = *Field::>::field(sdf_b, input); 117 | 118 | let h = ((d2 - d1).div(self.k).mul(0.5).add(0.5)).saturate(); 119 | 120 | let uv1 = Field::>::field(sdf_a, input); 121 | let uv2 = Field::>::field(sdf_b, input); 122 | 123 | if h > 0.5 { 124 | uv1 125 | } else { 126 | uv2 127 | } 128 | } 129 | } 130 | 131 | /// Compute the blended boolean union of two distance fields. 132 | pub type SmoothUnion = Operator; 133 | 134 | impl SmoothUnion { 135 | pub fn sdf_a(&mut self) -> &mut SdfA { 136 | &mut self.target().0 137 | } 138 | 139 | pub fn sdf_b(&mut self) -> &mut SdfB { 140 | &mut self.target().1 141 | } 142 | 143 | pub fn k(&mut self) -> &mut f32 { 144 | &mut self.op.k 145 | } 146 | } 147 | 148 | #[cfg(all(not(feature = "spirv-std"), test))] 149 | pub mod test { 150 | use type_fields::field::Field; 151 | 152 | use crate::{ 153 | prelude::{Cube, Point, SmoothUnion, Sphere}, 154 | test_op_attrs, 155 | }; 156 | 157 | #[test] 158 | fn test_smooth_union() { 159 | SmoothUnion::::default().with(SmoothUnion::k, f32::default()); 160 | } 161 | 162 | test_op_attrs!(SmoothUnion::); 163 | } 164 | -------------------------------------------------------------------------------- /src/field/field_operator/stretch.rs: -------------------------------------------------------------------------------- 1 | //! Stretch a shape along an arbitrary axis, preserving exterior geometry as caps. 2 | 3 | use core::ops::{Mul, Sub}; 4 | 5 | use rust_gpu_bridge::{ 6 | glam::{Vec2, Vec3}, 7 | Dot, IsNormalized, 8 | }; 9 | use type_fields::macros::Field; 10 | 11 | use crate::prelude::{items::position::Position, Attribute, Field, FieldOperator, Operator}; 12 | 13 | /// Extrude a shape infinitely along an arbitrary axis. 14 | #[derive(Debug, Copy, Clone, PartialEq, Field)] 15 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 16 | #[repr(C)] 17 | pub struct StretchInfiniteOp { 18 | pub dir: Dim, 19 | } 20 | 21 | impl Default for StretchInfiniteOp { 22 | fn default() -> Self { 23 | StretchInfiniteOp { dir: 1.0 } 24 | } 25 | } 26 | 27 | impl Default for StretchInfiniteOp { 28 | fn default() -> Self { 29 | StretchInfiniteOp { dir: Vec2::X } 30 | } 31 | } 32 | 33 | impl Default for StretchInfiniteOp { 34 | fn default() -> Self { 35 | StretchInfiniteOp { dir: Vec3::X } 36 | } 37 | } 38 | 39 | impl FieldOperator for StretchInfiniteOp 40 | where 41 | Attr: Attribute>, 42 | Sdf: Field, 43 | Input: Clone + Mul + Sub + IsNormalized + Dot, 44 | { 45 | fn operator(&self, sdf: &Sdf, input: &Position) -> Attr::Output { 46 | assert!( 47 | self.dir.clone().is_normalized(), 48 | "ExtrudeInfiniteOp dir must be normalized" 49 | ); 50 | let q = (*input).clone() - self.dir.clone() * (**input).clone().dot(self.dir.clone()); 51 | sdf.field(&q) 52 | } 53 | } 54 | 55 | /// Extrude a shape infinitely along an arbitrary axis. 56 | pub type StretchInfinite = Operator, Sdf>; 57 | 58 | impl StretchInfinite { 59 | pub fn dir(&mut self) -> &mut Dim { 60 | &mut self.op.dir 61 | } 62 | } 63 | 64 | /// Extrude a shape by an arbitrary distance along an arbitrary axis, preserving exterior geometry as caps. 65 | #[derive(Debug, Copy, Clone, PartialEq, Field)] 66 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 67 | #[repr(C)] 68 | pub struct StretchDistOp { 69 | pub dir: Dim, 70 | pub dist: f32, 71 | } 72 | 73 | impl Default for StretchDistOp { 74 | fn default() -> Self { 75 | StretchDistOp { 76 | dir: 1.0, 77 | dist: 1.0, 78 | } 79 | } 80 | } 81 | 82 | impl Default for StretchDistOp { 83 | fn default() -> Self { 84 | StretchDistOp { 85 | dir: Vec2::X, 86 | dist: 1.0, 87 | } 88 | } 89 | } 90 | 91 | impl Default for StretchDistOp { 92 | fn default() -> Self { 93 | StretchDistOp { 94 | dir: Vec3::X, 95 | dist: 1.0, 96 | } 97 | } 98 | } 99 | 100 | impl FieldOperator for StretchDistOp 101 | where 102 | Attr: Attribute>, 103 | Sdf: Field, 104 | Input: Clone + Mul + Sub + Dot, 105 | { 106 | fn operator(&self, sdf: &Sdf, input: &Position) -> Attr::Output { 107 | let q = (*input).clone() 108 | - (self.dir.clone() 109 | * (**input) 110 | .clone() 111 | .dot(self.dir.clone()) 112 | .clamp(-self.dist, self.dist)); 113 | sdf.field(&q) 114 | } 115 | } 116 | 117 | /// Extrude a shape by an arbitrary distance along an arbitrary axis, preserving exterior geometry as caps. 118 | pub type StretchDist = Operator, Sdf>; 119 | 120 | impl StretchDist { 121 | pub fn dir(&mut self) -> &mut Dim { 122 | &mut self.op.dir 123 | } 124 | 125 | pub fn dist(&mut self) -> &mut f32 { 126 | &mut self.op.dist 127 | } 128 | } 129 | 130 | #[cfg(all(not(feature = "spirv-std"), test))] 131 | pub mod test_stretch_infinite { 132 | use rust_gpu_bridge::glam::{Vec2, Vec3}; 133 | use type_fields::field::Field; 134 | 135 | use crate::{ 136 | prelude::{Cube, Point, StretchInfinite}, 137 | test_op_attrs_1d, test_op_attrs_2d, test_op_attrs_3d, 138 | }; 139 | 140 | #[test] 141 | fn test_stretch_infinite() { 142 | StretchInfinite::<_, Cube>::default().with(StretchInfinite::dir, Vec3::default()); 143 | } 144 | 145 | test_op_attrs_1d!(StretchInfinite::); 146 | test_op_attrs_2d!(StretchInfinite::); 147 | test_op_attrs_3d!(StretchInfinite::); 148 | } 149 | 150 | #[cfg(all(not(feature = "spirv-std"), test))] 151 | pub mod test_stretch_dist { 152 | use rust_gpu_bridge::glam::{Vec2, Vec3}; 153 | use type_fields::field::Field; 154 | 155 | use crate::{ 156 | prelude::{Cube, Point, StretchDist}, 157 | test_op_attrs_1d, test_op_attrs_2d, test_op_attrs_3d, 158 | }; 159 | 160 | #[test] 161 | fn test_stretch_dist() { 162 | StretchDist::<_, Cube>::default() 163 | .with(StretchDist::dir, Vec3::default()) 164 | .with(StretchDist::dist, f32::default()); 165 | } 166 | 167 | test_op_attrs_1d!(StretchDist::); 168 | test_op_attrs_2d!(StretchDist::); 169 | test_op_attrs_3d!(StretchDist::); 170 | } 171 | -------------------------------------------------------------------------------- /src/field/field_operator/transform/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod rotate; 2 | pub mod scale; 3 | pub mod translate; 4 | 5 | use rust_gpu_bridge::glam::Quat; 6 | 7 | use crate::prelude::{Rotate3d, Scale, Translate}; 8 | 9 | /// Translate, rotate, and scale the wrapped SDF. 10 | pub type Transform = Translate>>; 11 | 12 | impl Transform { 13 | pub fn rotation(&mut self) -> &mut Quat { 14 | &mut self.target.op.rotation 15 | } 16 | 17 | pub fn scale(&mut self) -> &mut f32 { 18 | &mut self.target.target.op.scale 19 | } 20 | } 21 | 22 | #[cfg(all(not(feature = "spirv-std"), test))] 23 | pub mod tests { 24 | use rust_gpu_bridge::glam::{Quat, Vec3}; 25 | use type_fields::field::Field; 26 | 27 | use crate::{ 28 | prelude::{Cube, Sphere, Transform}, 29 | test_op_attrs_3d, 30 | }; 31 | 32 | #[test] 33 | fn test_transform() { 34 | Transform::::default() 35 | .with(Transform::rotation, Quat::default()) 36 | .with(Transform::scale, f32::default()); 37 | } 38 | 39 | test_op_attrs_3d!(Transform::); 40 | } 41 | -------------------------------------------------------------------------------- /src/field/field_operator/transform/rotate.rs: -------------------------------------------------------------------------------- 1 | //! Rotate a distance field. 2 | use core::fmt::Debug; 3 | 4 | use rust_gpu_bridge::glam::{Quat, Vec2, Vec3}; 5 | use type_fields::macros::Field; 6 | 7 | use crate::prelude::{items::position::Position, Attribute, Field, FieldOperator, Operator}; 8 | 9 | /// Rotate a distance field. 10 | #[derive(Debug, Default, Copy, Clone, PartialEq, Field)] 11 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 12 | #[repr(C)] 13 | pub struct Rotate2dOp { 14 | pub angle: f32, 15 | } 16 | 17 | impl FieldOperator for Rotate2dOp 18 | where 19 | Attr: Attribute>, 20 | Sdf: Field, 21 | { 22 | fn operator(&self, sdf: &Sdf, p: &Position) -> Attr::Output { 23 | sdf.field(&Vec2::from_angle(-self.angle).rotate(**p).into()) 24 | } 25 | } 26 | 27 | /// Rotate a 3D distance field. 28 | pub type Rotate2d = Operator; 29 | 30 | impl Rotate2d { 31 | pub fn angle(&mut self) -> &mut f32 { 32 | &mut self.op.angle 33 | } 34 | } 35 | 36 | /// Rotate a distance field. 37 | #[derive(Default, Copy, Clone, PartialEq, Field)] 38 | #[repr(C)] 39 | pub struct Rotate3dOp { 40 | pub rotation: Quat, 41 | } 42 | 43 | #[cfg(not(feature = "spirv-std"))] 44 | impl Debug for Rotate3dOp { 45 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 46 | self.rotation.fmt(f) 47 | } 48 | } 49 | 50 | impl FieldOperator for Rotate3dOp 51 | where 52 | Attr: Attribute>, 53 | Sdf: Field, 54 | { 55 | fn operator(&self, sdf: &Sdf, p: &Position) -> Attr::Output { 56 | sdf.field(&(self.rotation.inverse() * **p).into()) 57 | } 58 | } 59 | 60 | /// Rotate a distance field. 61 | pub type Rotate3d = Operator; 62 | 63 | impl Rotate3d { 64 | pub fn rotation(&mut self) -> &mut Quat { 65 | &mut self.op.rotation 66 | } 67 | } 68 | 69 | #[cfg(all(not(feature = "spirv-std"), test))] 70 | pub mod test { 71 | use rust_gpu_bridge::glam::Quat; 72 | use type_fields::field::Field; 73 | 74 | use crate::{ 75 | prelude::{Cube, Point, Rotate2d, Rotate3d, Square}, 76 | test_op_attrs_2d, test_op_attrs_3d, 77 | }; 78 | 79 | #[test] 80 | fn test_rotate_2d() { 81 | Rotate2d::::default().with(Rotate2d::angle, f32::default()); 82 | } 83 | 84 | #[test] 85 | fn test_rotate_3d() { 86 | Rotate3d::::default().with(Rotate3d::rotation, Quat::default()); 87 | } 88 | 89 | test_op_attrs_2d!(Rotate2d::); 90 | test_op_attrs_3d!(Rotate3d::); 91 | } 92 | -------------------------------------------------------------------------------- /src/field/field_operator/transform/scale.rs: -------------------------------------------------------------------------------- 1 | //! Uniformly scale a distance field. 2 | 3 | use core::ops::{Div, Mul}; 4 | 5 | use type_fields::macros::Field; 6 | 7 | use crate::prelude::{Attribute, Field, FieldOperator, Operator}; 8 | 9 | /// Uniformly scale a distance field. 10 | #[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Field)] 11 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 12 | #[repr(C)] 13 | pub struct ScaleOp { 14 | pub scale: f32, 15 | } 16 | 17 | impl FieldOperator for ScaleOp 18 | where 19 | Attr: Attribute, 20 | Sdf: Field, 21 | Attr::Input: Clone + Div, 22 | Attr::Output: Mul, 23 | { 24 | fn operator(&self, sdf: &Sdf, input: &Attr::Input) -> Attr::Output { 25 | sdf.field(&(input.clone() / self.scale)).mul(self.scale) 26 | } 27 | } 28 | 29 | /// Uniformly scale a distance field. 30 | pub type Scale = Operator; 31 | 32 | impl Scale { 33 | pub fn scale(&mut self) -> &mut f32 { 34 | &mut self.op.scale 35 | } 36 | } 37 | 38 | #[cfg(all(not(feature = "spirv-std"), test))] 39 | pub mod test { 40 | use type_fields::field::Field; 41 | 42 | use crate::{ 43 | prelude::{Cube, Point, Scale}, 44 | test_op_attrs, 45 | }; 46 | 47 | #[test] 48 | fn test_scale() { 49 | Scale::::default().with(Scale::scale, f32::default()); 50 | } 51 | 52 | test_op_attrs!(Scale::); 53 | } 54 | -------------------------------------------------------------------------------- /src/field/field_operator/transform/translate.rs: -------------------------------------------------------------------------------- 1 | //! Apply a positional translation to a distance field. 2 | 3 | use core::ops::Sub; 4 | 5 | use type_fields::macros::Field; 6 | 7 | use crate::prelude::{items::position::Position, Attribute, Field, FieldOperator, Operator}; 8 | 9 | /// Apply a positional translation to a distance field. 10 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Field)] 11 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 12 | #[repr(C)] 13 | pub struct TranslateOp { 14 | pub translation: Dim, 15 | } 16 | 17 | impl FieldOperator for TranslateOp 18 | where 19 | Attr: Attribute>, 20 | Sdf: Field, 21 | Input: Clone + Sub, 22 | { 23 | fn operator(&self, sdf: &Sdf, p: &Position) -> Attr::Output { 24 | sdf.field(&(p.clone() - self.translation.clone()).into()) 25 | } 26 | } 27 | 28 | /// Apply a positional translation to a distance field. 29 | pub type Translate = Operator, Sdf>; 30 | 31 | impl Translate { 32 | pub fn translation(&mut self) -> &mut Dim { 33 | &mut self.op.translation 34 | } 35 | } 36 | 37 | #[cfg(all(not(feature = "spirv-std"), test))] 38 | pub mod test { 39 | use rust_gpu_bridge::glam::{Vec2, Vec3}; 40 | use type_fields::field::Field; 41 | 42 | use crate::{ 43 | prelude::{Point, Sphere, Translate}, 44 | test_op_attrs_1d, test_op_attrs_2d, test_op_attrs_3d, 45 | }; 46 | 47 | #[test] 48 | fn test_translation() { 49 | Translate::<_, Sphere>::default().with(Translate::translation, Vec3::default()); 50 | } 51 | 52 | test_op_attrs_1d!(Translate::); 53 | test_op_attrs_2d!(Translate::); 54 | test_op_attrs_3d!(Translate::); 55 | } 56 | -------------------------------------------------------------------------------- /src/field/field_operator/triplanar_uv.rs: -------------------------------------------------------------------------------- 1 | use rust_gpu_bridge::{ 2 | glam::{Vec3, Vec3Swizzles}, 3 | Pow, 4 | }; 5 | use type_fields::macros::Field; 6 | 7 | use crate::{ 8 | impl_passthrough_op_1, 9 | prelude::{AttrColor, AttrDistance, Field, AttrNormal, AttrTangent, AttrUv, items::position::Position}, 10 | }; 11 | 12 | use super::{FieldOperator, Operator}; 13 | 14 | /// Apply triplanar UV mapping to the provided SDF 15 | #[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Field)] 16 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 17 | #[repr(C)] 18 | pub struct TriplanarUvOp { 19 | pub k: f32, 20 | } 21 | 22 | impl FieldOperator> for TriplanarUvOp 23 | where 24 | Sdf: Field>, 25 | { 26 | fn operator(&self, sdf: &Sdf, input: &Position) -> as crate::prelude::Attribute>::Output { 27 | let front = input.xy(); 28 | let side = input.zy(); 29 | let top = input.xz(); 30 | 31 | let weights = sdf 32 | .field(input) 33 | .abs() 34 | .pow(Vec3::splat(self.k)) 35 | .normalize(); 36 | 37 | (front * weights.z + side * weights.x + top * weights.y).into() 38 | } 39 | } 40 | 41 | impl_passthrough_op_1!(TriplanarUvOp, AttrDistance, Dim); 42 | impl_passthrough_op_1!(TriplanarUvOp, AttrNormal, Dim); 43 | impl_passthrough_op_1!(TriplanarUvOp, AttrTangent, Dim); 44 | impl_passthrough_op_1!(TriplanarUvOp, AttrColor, Dim); 45 | 46 | pub type TriplanarUv = Operator; 47 | 48 | impl TriplanarUv { 49 | pub fn k(&mut self) -> &mut f32 { 50 | self.op().k() 51 | } 52 | } 53 | 54 | #[cfg(all(not(feature = "spirv-std"), test))] 55 | pub mod test { 56 | use crate::{ 57 | prelude::{Point, Sphere}, 58 | test_op_attrs_3d, 59 | }; 60 | 61 | use super::TriplanarUv; 62 | 63 | #[test] 64 | fn test_triplanar_uv() { 65 | TriplanarUv::::default(); 66 | } 67 | 68 | test_op_attrs_3d!(TriplanarUv::); 69 | } 70 | -------------------------------------------------------------------------------- /src/field/field_operator/twist.rs: -------------------------------------------------------------------------------- 1 | //! Twist a distance field around an arbitrary axis. 2 | 3 | use rust_gpu_bridge::glam::{Quat, Vec2, Vec3}; 4 | use type_fields::macros::Field; 5 | 6 | use crate::prelude::{items::position::Position, Attribute, Field}; 7 | 8 | use super::{FieldOperator, Operator}; 9 | 10 | /// Twist a distance field around an arbitrary axis. 11 | #[derive(Debug, Copy, Clone, PartialEq, Field)] 12 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 13 | #[repr(C)] 14 | pub struct TwistOp { 15 | pub axis_pos: Dim, 16 | pub axis_rot: Dim, 17 | pub k: f32, 18 | } 19 | 20 | impl Default for TwistOp { 21 | fn default() -> Self { 22 | TwistOp { 23 | axis_pos: Vec2::Y, 24 | axis_rot: Vec2::Y, 25 | k: 1.0, 26 | } 27 | } 28 | } 29 | 30 | impl Default for TwistOp { 31 | fn default() -> Self { 32 | TwistOp { 33 | axis_pos: Vec3::Y, 34 | axis_rot: Vec3::Y, 35 | k: 1.0, 36 | } 37 | } 38 | } 39 | 40 | impl FieldOperator for TwistOp 41 | where 42 | Attr: Attribute>, 43 | Sdf: Field, 44 | { 45 | fn operator(&self, sdf: &Sdf, input: &Position) -> Attr::Output { 46 | let q = Vec2::from_angle(self.k * self.axis_pos.dot(**input)).rotate(**input); 47 | sdf.field(&q.into()) 48 | } 49 | } 50 | 51 | impl FieldOperator for TwistOp 52 | where 53 | Attr: Attribute>, 54 | Sdf: Field, 55 | { 56 | fn operator(&self, sdf: &Sdf, p: &Position) -> Attr::Output { 57 | let q = Quat::from_axis_angle(self.axis_rot, self.k * self.axis_pos.dot(**p)) * **p; 58 | sdf.field(&q.into()) 59 | } 60 | } 61 | 62 | /// Twist a distance field around an arbitrary axis. 63 | pub type Twist = Operator, Sdf>; 64 | 65 | impl Twist { 66 | pub fn axis_pos(&mut self) -> &mut Dim { 67 | &mut self.op.axis_pos 68 | } 69 | 70 | pub fn axis_rot(&mut self) -> &mut Dim { 71 | &mut self.op.axis_rot 72 | } 73 | 74 | pub fn k(&mut self) -> &mut f32 { 75 | &mut self.op.k 76 | } 77 | } 78 | 79 | #[cfg(all(not(feature = "spirv-std"), test))] 80 | pub mod test { 81 | use rust_gpu_bridge::glam::{Vec2, Vec3}; 82 | use type_fields::field::Field; 83 | 84 | use crate::{ 85 | prelude::{Point, Torus, Twist}, 86 | test_op_attrs_2d, test_op_attrs_3d, 87 | }; 88 | 89 | #[test] 90 | fn test_twist() { 91 | Twist::<_, Torus>::default() 92 | .with(Twist::axis_pos, Vec3::default()) 93 | .with(Twist::axis_rot, Vec3::default()) 94 | .with(Twist::k, f32::default()); 95 | } 96 | 97 | test_op_attrs_2d!(Twist::); 98 | test_op_attrs_3d!(Twist::); 99 | } 100 | -------------------------------------------------------------------------------- /src/field/metric/chebyshev.rs: -------------------------------------------------------------------------------- 1 | //! Chebyshev distance metric. 2 | 3 | use rust_gpu_bridge::{ 4 | glam::{Vec2, Vec3}, 5 | Abs, Mix, Sign, Step, 6 | }; 7 | 8 | use crate::prelude::{AttrDistance, Field, AttrNormal, items::position::Position, Distance, Normal}; 9 | 10 | /// Chebyshev distance metric. 11 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 12 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 13 | #[repr(C)] 14 | pub struct ChebyshevMetric; 15 | 16 | impl Field> for ChebyshevMetric { 17 | fn field(&self, p: &Position) -> Distance { 18 | p.abs().into() 19 | } 20 | } 21 | 22 | impl Field> for ChebyshevMetric { 23 | fn field(&self, p: &Position) -> Distance { 24 | p.x.abs().max(p.y.abs()).into() 25 | } 26 | } 27 | 28 | impl Field> for ChebyshevMetric { 29 | fn field(&self, p: &Position) -> Distance { 30 | p.x.abs().max(p.y.abs()).max(p.z.abs()).into() 31 | } 32 | } 33 | 34 | impl Field> for ChebyshevMetric { 35 | fn field(&self, p: &Position) -> Normal { 36 | p.sign().into() 37 | } 38 | } 39 | 40 | impl Field> for ChebyshevMetric { 41 | fn field(&self, p: &Position) -> Normal { 42 | let a = p.abs(); 43 | let s = p.sign(); 44 | 45 | (Vec2::X * s.x).mix(Vec2::Y * s.y, Vec2::splat(a.x.step(a.y))).into() 46 | } 47 | } 48 | 49 | impl Field> for ChebyshevMetric { 50 | fn field(&self, p: &Position) -> Normal { 51 | let a = p.abs(); 52 | let s = p.sign(); 53 | 54 | (Vec3::X * s.x) 55 | .mix(Vec3::Z * s.z, Vec3::splat(a.x.step(a.z))) 56 | .mix( 57 | (Vec3::Y * s.y).mix(Vec3::Z * s.z, Vec3::splat(a.y.step(a.z))), 58 | Vec3::splat(a.x.step(a.y)), 59 | ) 60 | .into() 61 | } 62 | } 63 | 64 | #[cfg(all(not(feature = "spirv-std"), test))] 65 | pub mod test { 66 | use rust_gpu_bridge::glam::{Vec2, Vec3}; 67 | 68 | use crate::prelude::{BoundTester, ChebyshevMetric}; 69 | 70 | #[test] 71 | #[should_panic] 72 | pub fn test_chebyshev_metric_2d() { 73 | assert!(BoundTester::::default().is_field_2d()); 74 | } 75 | 76 | #[test] 77 | #[should_panic] 78 | pub fn test_chebyshev_metric_3d() { 79 | assert!(BoundTester::::default().is_field_3d()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/field/metric/euclidean.rs: -------------------------------------------------------------------------------- 1 | //! Euclidean distance metric. 2 | 3 | use rust_gpu_bridge::{ 4 | glam::{Vec2, Vec3}, 5 | Asin, Atan2, Length, Normalize, 6 | }; 7 | 8 | use crate::prelude::{ 9 | items::position::Position, AttrDistance, AttrNormal, AttrUv, Distance, Field, Normal, Uv, 10 | }; 11 | 12 | /// Euclidian distance metric. 13 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 14 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 15 | #[cfg_attr(feature = "bevy", derive(bevy::reflect::TypeUuid))] 16 | #[cfg_attr(feature = "bevy", uuid = "9c0e79ee-c437-47aa-9230-d3c13f3bacb8")] 17 | #[repr(C)] 18 | pub struct EuclideanMetric; 19 | 20 | impl Field> for EuclideanMetric 21 | where 22 | Input: Clone + Length, 23 | { 24 | fn field(&self, input: &Position) -> Distance { 25 | (**input).clone().length().into() 26 | } 27 | } 28 | 29 | impl Field> for EuclideanMetric 30 | where 31 | Dim: Default + Clone + PartialEq + Normalize, 32 | { 33 | fn field(&self, input: &Position) -> Normal { 34 | let d = Dim::default(); 35 | if **input == d { 36 | return d.into(); 37 | } 38 | 39 | (**input).clone().normalize().into() 40 | } 41 | } 42 | 43 | impl Field> for EuclideanMetric { 44 | fn field(&self, p: &Position) -> Uv { 45 | Vec2::new(**p, 0.0).into() 46 | } 47 | } 48 | 49 | impl Field> for EuclideanMetric { 50 | fn field(&self, p: &Position) -> Uv { 51 | Vec2::new((p.x.atan2(p.y) / core::f32::consts::TAU) + 0.5, p.length()).into() 52 | } 53 | } 54 | 55 | impl Field> for EuclideanMetric { 56 | fn field(&self, p: &Position) -> Uv { 57 | Vec2::new( 58 | (p.x.atan2(p.z) / core::f32::consts::TAU) + 0.5, 59 | (p.y.asin() / core::f32::consts::PI) + 0.5, 60 | ) 61 | .into() 62 | } 63 | } 64 | 65 | #[cfg(all(not(feature = "spirv-std"), test))] 66 | pub mod test { 67 | use crate::prelude::{BoundTester, EuclideanMetric}; 68 | 69 | #[test] 70 | fn test_euclidean_metric_2d() { 71 | assert!(BoundTester::::default().is_field_2d()); 72 | } 73 | 74 | #[test] 75 | fn test_euclidean_metric_3d() { 76 | assert!(BoundTester::::default().is_field_3d()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/field/metric/mod.rs: -------------------------------------------------------------------------------- 1 | //! Distance metrics. 2 | 3 | pub mod chebyshev; 4 | pub mod euclidean; 5 | pub mod taxicab; 6 | pub mod superellipse; 7 | -------------------------------------------------------------------------------- /src/field/metric/superellipse.rs: -------------------------------------------------------------------------------- 1 | use rust_gpu_bridge::{glam::Vec2, Abs, Pow, Sign}; 2 | use type_fields::macros::Field; 3 | 4 | use crate::prelude::{ 5 | items::position::Position, AttrDistance, AttrNormal, Distance, Field, Normal, 6 | }; 7 | 8 | #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Field)] 9 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 10 | #[repr(C)] 11 | pub struct Superellipse { 12 | pub n: f32, 13 | } 14 | 15 | impl Default for Superellipse { 16 | fn default() -> Self { 17 | Superellipse { n: 1.0 } 18 | } 19 | } 20 | 21 | impl Field> for Superellipse { 22 | fn field(&self, p: &Position) -> Distance { 23 | (p.x.abs().pow(self.n) + p.y.abs().pow(self.n)) 24 | .pow(1.0 / self.n) 25 | .into() 26 | } 27 | } 28 | 29 | impl Field> for Superellipse { 30 | fn field(&self, p: &Position) -> Normal { 31 | (p.abs().pow(Vec2::splat(self.n)).normalize() * p.sign()).into() 32 | } 33 | } 34 | 35 | #[cfg(all(not(feature = "spirv-std"), test))] 36 | pub mod test { 37 | use crate::prelude::BoundTester; 38 | 39 | use super::Superellipse; 40 | 41 | #[test] 42 | #[should_panic] 43 | fn test_lame_curve() { 44 | assert!(BoundTester::::default().is_field_2d()) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/field/metric/taxicab.rs: -------------------------------------------------------------------------------- 1 | //! Taxicab distance metric. 2 | 3 | use core::ops::Add; 4 | 5 | use rust_gpu_bridge::{ 6 | glam::{Vec2, Vec3}, 7 | Abs, Normalize, Sign, 8 | }; 9 | 10 | use crate::prelude::{ 11 | items::position::Position, AttrDistance, AttrNormal, Distance, Field, Normal, 12 | }; 13 | 14 | /// Taxicab distance metric. 15 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 16 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 17 | #[repr(C)] 18 | pub struct TaxicabMetric; 19 | 20 | impl Field> for TaxicabMetric { 21 | fn field(&self, p: &Position) -> Distance { 22 | p.abs().into() 23 | } 24 | } 25 | 26 | impl Field> for TaxicabMetric { 27 | fn field(&self, p: &Position) -> Distance { 28 | p.x.abs().add(p.y.abs()).into() 29 | } 30 | } 31 | 32 | impl Field> for TaxicabMetric { 33 | fn field(&self, p: &Position) -> Distance { 34 | p.x.abs().add(p.y.abs()).add(p.z.abs()).into() 35 | } 36 | } 37 | 38 | impl Field> for TaxicabMetric 39 | where 40 | Input: Clone + Sign + Normalize, 41 | { 42 | fn field(&self, input: &Position) -> Normal { 43 | (**input).clone().sign().normalize().into() 44 | } 45 | } 46 | 47 | #[cfg(all(not(feature = "spirv-std"), test))] 48 | pub mod test { 49 | use crate::prelude::{BoundTester, TaxicabMetric}; 50 | 51 | #[test] 52 | #[should_panic] 53 | pub fn test_taxicab_metric_2d() { 54 | assert!(BoundTester::::default().is_field_2d()); 55 | } 56 | 57 | #[test] 58 | #[should_panic] 59 | pub fn test_taxicab_metric_3d() { 60 | assert!(BoundTester::::default().is_field_3d()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/field/mod.rs: -------------------------------------------------------------------------------- 1 | //! Function associating an attribute value with a point in space. 2 | 3 | pub mod metric; 4 | pub mod shape; 5 | 6 | pub mod field_operator; 7 | 8 | pub mod traits; 9 | 10 | #[cfg(feature = "glam")] 11 | pub mod boxed { 12 | extern crate alloc; 13 | use alloc::boxed::Box; 14 | 15 | use crate::prelude::{Attribute, Field}; 16 | 17 | impl Field for Box> 18 | where 19 | Attr: Attribute, 20 | { 21 | fn field(&self, input: &::Input) -> ::Output { 22 | self.as_ref().field(input) 23 | } 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/field/shape/mod.rs: -------------------------------------------------------------------------------- 1 | //! Distance field shapes. 2 | 3 | pub mod composite; 4 | pub mod octahedron; 5 | pub mod plane; 6 | pub mod superellipsoid; 7 | pub mod squircle; 8 | -------------------------------------------------------------------------------- /src/field/shape/octahedron.rs: -------------------------------------------------------------------------------- 1 | //! An octahedron. 2 | 3 | use rust_gpu_bridge::{glam::Vec3, Normalize, Sign}; 4 | use type_fields::macros::Field; 5 | 6 | use crate::prelude::{ 7 | items::position::Position, AttrDistance, AttrNormal, Distance, Field, Normal, 8 | }; 9 | 10 | /// An octahedron. 11 | #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Field)] 12 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 13 | #[repr(C)] 14 | pub struct Octahedron { 15 | pub size: f32, 16 | } 17 | 18 | impl Default for Octahedron { 19 | fn default() -> Self { 20 | Octahedron { size: 1.0 } 21 | } 22 | } 23 | 24 | impl Field> for Octahedron { 25 | fn field(&self, p: &Position) -> Distance { 26 | // Axial reflection 27 | let p = p.abs(); 28 | 29 | // Signed distance minus size 30 | let m = p.x + p.y + p.z - self.size; 31 | 32 | let q = if 3.0 * p.x < m { 33 | p 34 | } else if 3.0 * p.y < m { 35 | Vec3::new(p.y, p.z, p.x) 36 | } else if 3.0 * p.z < m { 37 | Vec3::new(p.z, p.x, p.y) 38 | } else { 39 | return (m * 0.57735027).into(); 40 | }; 41 | 42 | let k = (0.5 * (q.z - q.y + self.size)).clamp(0.0, self.size); 43 | 44 | let j = Vec3::new(q.x, q.y - self.size + k, q.z - k); 45 | 46 | // Euclidean metric 47 | j.length().into() 48 | } 49 | } 50 | 51 | impl Field> for Octahedron 52 | where 53 | Dim: Clone + Sign + Normalize, 54 | { 55 | fn field(&self, p: &Position) -> Normal { 56 | (**p).clone().sign().normalize().into() 57 | } 58 | } 59 | 60 | #[cfg(all(not(feature = "spirv-std"), test))] 61 | pub mod test { 62 | use crate::prelude::BoundTester; 63 | 64 | use super::Octahedron; 65 | 66 | #[test] 67 | fn test_octahedron() { 68 | assert!(BoundTester::::default().is_field_3d()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/field/shape/plane.rs: -------------------------------------------------------------------------------- 1 | //! A plane. 2 | use core::ops::Neg; 3 | 4 | use rust_gpu_bridge::{ 5 | glam::{Vec2, Vec3}, 6 | Abs, 7 | }; 8 | use type_fields::macros::Field; 9 | 10 | use crate::prelude::{AttrDistance, Field, AttrNormal, items::position::Position, Distance, Normal}; 11 | 12 | /// A plane. 13 | #[derive(Debug, Copy, Clone, PartialEq, Field)] 14 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 15 | #[repr(C)] 16 | pub struct Plane { 17 | pub dir: Dim, 18 | } 19 | 20 | impl Default for Plane { 21 | fn default() -> Self { 22 | Plane { dir: 1.0 } 23 | } 24 | } 25 | 26 | impl Default for Plane { 27 | fn default() -> Self { 28 | Plane { dir: Vec2::Y } 29 | } 30 | } 31 | 32 | impl Default for Plane { 33 | fn default() -> Self { 34 | Plane { dir: Vec3::Y } 35 | } 36 | } 37 | 38 | impl Field> for Plane { 39 | fn field(&self, p: &Position) -> Distance { 40 | assert!(self.dir.abs() == 1.0, "Plane dir must be normalized"); 41 | (**p * -self.dir).into() 42 | } 43 | } 44 | 45 | impl Field> for Plane { 46 | fn field(&self, p: &Position) -> Distance { 47 | assert!(self.dir.is_normalized(), "Plane dir must be normalized"); 48 | p.dot(-self.dir).into() 49 | } 50 | } 51 | 52 | impl Field> for Plane { 53 | fn field(&self, p: &Position) -> Distance { 54 | assert!(self.dir.is_normalized(), "Plane dir must be normalized"); 55 | p.dot(-self.dir).into() 56 | } 57 | } 58 | 59 | impl Field> for Plane 60 | where 61 | Dim: Clone + Neg, 62 | { 63 | fn field(&self, _: &Position) -> Normal { 64 | self.dir.clone().neg().into() 65 | } 66 | } 67 | 68 | #[cfg(all(not(feature = "spirv-std"), test))] 69 | pub mod test { 70 | use crate::prelude::BoundTester; 71 | 72 | use super::Plane; 73 | 74 | #[test] 75 | pub fn test_plane_2d() { 76 | assert!(BoundTester::>::default().is_field_2d()) 77 | } 78 | 79 | #[test] 80 | pub fn test_plane_3d() { 81 | assert!(BoundTester::>::default().is_field_3d()) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/field/shape/squircle.rs: -------------------------------------------------------------------------------- 1 | use rust_gpu_bridge::{ 2 | glam::{Vec2, Vec2Swizzles}, 3 | Abs, Acos, Cos, Pow, Sign, Sin, Sqrt, 4 | }; 5 | 6 | use crate::prelude::{AttrDistance, Field, items::position::Position, Distance}; 7 | 8 | // Inigo Quilez' quadratic circle 9 | // Appears to be a superellipse / lame curve with n = 1.0 / 0.75 10 | // Can be generalized to 3D as a superellipsoid 11 | // 12 | // Desmos decomposition: https://www.desmos.com/calculator/i9cgthn0ls 13 | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 14 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 15 | #[repr(C)] 16 | pub struct Squircle; 17 | 18 | impl Field> for Squircle { 19 | fn field(&self, p: &Position) -> Distance { 20 | // Axial reflection 21 | let mut p = p.abs(); 22 | 23 | // Cheap diagonal mirror 24 | if p.y > p.x { 25 | p = p.yx() 26 | } 27 | 28 | // Diagonal X maximum 29 | let a = p.x - p.y; 30 | 31 | // Diagonal Y minimum 32 | let b = p.x + p.y; 33 | 34 | // Diagonal Y maximum 35 | let c = (2.0 * b - 1.0) / 3.0; 36 | 37 | // Semicircle at (0.5, 0.5) 38 | let h = a * a + c * c * c; 39 | 40 | let t = if h >= 0.0 { 41 | // Appears identical to h in graph plot, maybe field related 42 | let h = h.sqrt(); 43 | // Uneven circular curve 44 | (h - a).sign() * (h - a).abs().pow(1.0 / 3.0) - (h + a).pow(1.0 / 3.0) 45 | } else { 46 | // Negative Y minimum 47 | let z = (-c).sqrt(); 48 | // Uneven tangent curve 49 | let v = (a / (c * z)).acos() / 3.0; 50 | // Uneven tangent curve, repeating w/different frequency 51 | -z * (v.cos() + v.sin() * 1.732050808) 52 | } * 0.5; 53 | 54 | // Bounded quadradic curve 55 | let w = Vec2::new(-t, t) + 0.75 - t * t - p; 56 | 57 | // Quadratic curve sign 58 | let s = (a * a * 0.5 + b - 1.5).sign(); 59 | 60 | // Final curve w / sign 61 | (w.length() * s).into() 62 | } 63 | } 64 | 65 | #[cfg(all(not(feature = "spirv-std"), test))] 66 | pub mod test { 67 | use crate::prelude::{BoundTester, NormalTetrahedron}; 68 | 69 | use super::Squircle; 70 | 71 | #[test] 72 | pub fn test_squircle() { 73 | assert!(BoundTester::>::default().is_field_2d()) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/field/shape/superellipsoid.rs: -------------------------------------------------------------------------------- 1 | use rust_gpu_bridge::{glam::Vec3, Abs, Pow, Sign}; 2 | use type_fields::macros::Field; 3 | 4 | use crate::prelude::{items::position::Position, AttrDistance, Field, AttrNormal, Distance, Normal}; 5 | 6 | // TODO: Apply pow(1.0 / foo) to un-exponentiate distance after axes are summed, 7 | // as per Superellipse 8 | #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Field)] 9 | #[cfg_attr(feature = "glam", derive(rust_gpu_bridge::Named))] 10 | #[repr(C)] 11 | pub struct Superellipsoid { 12 | pub e1: f32, 13 | pub e2: f32, 14 | } 15 | 16 | impl Default for Superellipsoid { 17 | fn default() -> Self { 18 | Superellipsoid { e1: 1.0, e2: 1.0 } 19 | } 20 | } 21 | 22 | impl Field> for Superellipsoid { 23 | fn field(&self, p: &Position) -> Distance { 24 | let d = (p.x.abs().pow(self.e1) + p.y.abs().pow(self.e2)).pow(self.e2 / self.e1) 25 | + p.z.abs().pow(self.e1); 26 | 27 | (d - 1.0).into() 28 | } 29 | } 30 | 31 | impl Field> for Superellipsoid { 32 | fn field(&self, p: &Position) -> Normal { 33 | let pa = p.abs(); 34 | let pp = pa.pow(Vec3::new(self.e1, self.e2, self.e1)); 35 | (pp.normalize() * p.sign()).into() 36 | } 37 | } 38 | 39 | #[cfg(all(not(feature = "spirv-std"), test))] 40 | pub mod test { 41 | use crate::prelude::BoundTester; 42 | 43 | use super::Superellipsoid; 44 | 45 | #[test] 46 | #[should_panic] 47 | fn test_superellipsoid() { 48 | assert!(BoundTester::::default().is_field_3d()) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/field/traits/field.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::Attribute; 2 | 3 | /// Evalute the attribute `Attr` of a field function. 4 | pub trait Field 5 | where 6 | Attr: Attribute, 7 | { 8 | fn field(&self, input: &Attr::Input) -> Attr::Output; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/field/traits/field_attribute.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::{Attribute, Field}; 2 | 3 | /// Evalute the attribute `Attr` of a field function. 4 | /// 5 | /// Moves `Attr` into the function position. 6 | pub trait FieldAttribute { 7 | fn field_attribute(&self, input: &Attr::Input) -> Attr::Output 8 | where 9 | Self: Field, 10 | Attr: Attribute; 11 | } 12 | 13 | impl FieldAttribute for T { 14 | fn field_attribute(&self, input: &Attr::Input) -> Attr::Output 15 | where 16 | Self: Field, 17 | Attr: Attribute, 18 | { 19 | self.field(input) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/field/traits/field_attribute_register.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::{AttributeRef, Register, FieldRegister}; 2 | 3 | /// Evalute the attribute `Attr` of a field function, drawing input from `Ctx`. 4 | /// 5 | /// Moves `Attr` into the function position. 6 | pub trait FieldAttributeRegister<'a, Ctx, State> { 7 | fn field_attribute_register(&self, p: Ctx) -> Attr::Output 8 | where 9 | Self: FieldRegister<'a, Attr, Ctx, State>, 10 | Attr: AttributeRef<'a>, 11 | Ctx: Register; 12 | } 13 | 14 | impl<'a, T, Ctx, State> FieldAttributeRegister<'a, Ctx, State> for T { 15 | fn field_attribute_register(&self, ctx: Ctx) -> Attr::Output 16 | where 17 | T: FieldRegister<'a, Attr, Ctx, State>, 18 | Attr: AttributeRef<'a>, 19 | Ctx: Register, 20 | { 21 | self.field_register(ctx) 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/field/traits/field_attributes.rs: -------------------------------------------------------------------------------- 1 | use type_fields::t_funk::hlist::ToTList; 2 | 3 | use crate::prelude::{Attributes, ConsAttributes, Fields}; 4 | 5 | /// Evalute multiple attributes of a field function. 6 | /// 7 | /// Moves `Attrs` into the function position. 8 | pub trait FieldAttributes { 9 | fn field_attributes<'a, Attrs>( 10 | &self, 11 | input: &::Input, 12 | ) -> Attrs::UnconsOutput 13 | where 14 | Self: Fields, 15 | Attrs: ConsAttributes<'a>; 16 | } 17 | 18 | impl FieldAttributes for T { 19 | fn field_attributes<'a, Attr>( 20 | &self, 21 | input: &::Input, 22 | ) -> Attr::UnconsOutput 23 | where 24 | Self: Fields, 25 | Attr: ConsAttributes<'a>, 26 | { 27 | self.fields(input).to_tlist() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/field/traits/field_attributes_register.rs: -------------------------------------------------------------------------------- 1 | use type_fields::t_funk::hlist::ToTList; 2 | 3 | use crate::prelude::{ConsAttributes, FieldsRegister, Register}; 4 | 5 | /// Evalute multiple attributes of a field function, drawing input from `Ctx`. 6 | /// 7 | /// Moves `Attrs` into the function position. 8 | pub trait FieldAttributesRegister<'a, Ctx, State> { 9 | fn field_attributes_register(&self, ctx: Ctx) -> Attr::UnconsOutput 10 | where 11 | Self: FieldsRegister<'a, Ctx, State, Attr::HList>, 12 | Ctx: Register, 13 | Attr: ConsAttributes<'a>; 14 | } 15 | 16 | impl<'a, T, Ctx, State> FieldAttributesRegister<'a, Ctx, State> for T { 17 | fn field_attributes_register(&self, ctx: Ctx) -> Attr::UnconsOutput 18 | where 19 | Self: FieldsRegister<'a, Ctx, State, Attr::HList>, 20 | Ctx: Register, 21 | Attr: ConsAttributes<'a>, 22 | { 23 | self.fields_register(ctx).to_tlist() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/field/traits/field_attributes_register_cons.rs: -------------------------------------------------------------------------------- 1 | use type_fields::t_funk::{tlist::AsHListRef, hlist::ToTList}; 2 | 3 | use crate::prelude::{ConsAttributes, FieldsRegister, FieldsRegisters, Register, Registers}; 4 | 5 | /// Evalute multiple attributes of a field function, 6 | /// drawing input from `Ctx` and `Uncons`ing the result. 7 | /// 8 | /// Moves `Attrs` into the function position. 9 | pub trait FieldAttributesRegisterCons<'a, Ctx, State> 10 | where 11 | Ctx: AsHListRef, 12 | { 13 | fn field_attributes_register_cons(&self, ctx: &'a Ctx) -> Attrs::UnconsOutput 14 | where 15 | Self: FieldsRegister<'a, Ctx::HListRef<'a>, State, Attrs::HList>, 16 | Ctx::HListRef<'a>: Register, 17 | Attrs: ConsAttributes<'a>; 18 | } 19 | 20 | impl<'a, T, Ctx, State> FieldAttributesRegisterCons<'a, Ctx, State> for T 21 | where 22 | Ctx: AsHListRef, 23 | { 24 | fn field_attributes_register_cons(&self, ctx: &'a Ctx) -> Attr::UnconsOutput 25 | where 26 | Self: FieldsRegister<'a, Ctx::HListRef<'a>, State, Attr::HList>, 27 | Ctx::HListRef<'a>: Register, 28 | Attr: ConsAttributes<'a>, 29 | { 30 | self.fields_register(ctx.as_hlist_ref()).to_tlist() 31 | } 32 | } 33 | 34 | /// Evalute multiple attributes of a field function, 35 | /// drawing multiple inputs from `Ctx` and `Uncons`ing the result. 36 | /// 37 | /// Moves `Attrs` into the function position. 38 | pub trait FieldAttributesRegistersCons<'a, Ctx, State> 39 | where 40 | Ctx: AsHListRef, 41 | { 42 | fn field_attributes_registers_cons(&self, ctx: &'a Ctx) -> Attrs::UnconsOutput 43 | where 44 | Self: FieldsRegisters<'a, Ctx::HListRef<'a>, State, Attrs::HList>, 45 | Attrs: ConsAttributes<'a>, 46 | Ctx::HListRef<'a>: Registers; 47 | } 48 | 49 | impl<'a, T, Ctx, State> FieldAttributesRegistersCons<'a, Ctx, State> for T 50 | where 51 | Ctx: AsHListRef, 52 | { 53 | fn field_attributes_registers_cons(&self, ctx: &'a Ctx) -> Attrs::UnconsOutput 54 | where 55 | Self: FieldsRegisters<'a, Ctx::HListRef<'a>, State, Attrs::HList>, 56 | Attrs: ConsAttributes<'a>, 57 | Ctx::HListRef<'a>: Registers, 58 | { 59 | self.fields_registers(ctx.as_hlist_ref()).to_tlist() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/field/traits/field_register.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::{AttributeRef, Register, Field}; 2 | 3 | /// Evalute the attribute `Attr` of a field function, drawing input from `Ctx`. 4 | pub trait FieldRegister<'a, Attr, Ctx, State>: Field 5 | where 6 | Attr: AttributeRef<'a>, 7 | Ctx: Register, 8 | { 9 | fn field_register(&self, ctx: Ctx) -> Attr::Output; 10 | } 11 | 12 | impl<'a, T, Attr, Ctx, State> FieldRegister<'a, Attr, Ctx, State> for T 13 | where 14 | Self: Field, 15 | Attr: AttributeRef<'a>, 16 | Ctx: Register, 17 | { 18 | fn field_register(&self, ctx: Ctx) -> Attr::Output { 19 | self.field(ctx.register()) 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/field/traits/field_registers.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::{AttributeRef, Registers, Field}; 2 | 3 | pub trait FieldRegisters<'a, Attr, Ctx, State>: Field 4 | where 5 | Attr: AttributeRef<'a>, 6 | Ctx: Registers, 7 | { 8 | fn field_registers(&self, ctx: Ctx) -> Attr::Output; 9 | } 10 | 11 | impl<'a, T, Attr, Ctx, State> FieldRegisters<'a, Attr, Ctx, State> for T 12 | where 13 | Self: Field, 14 | Attr: AttributeRef<'a>, 15 | Ctx: Registers, 16 | { 17 | fn field_registers(&self, ctx: Ctx) -> Attr::Output { 18 | self.field(&ctx.registers()) 19 | } 20 | } 21 | 22 | #[cfg(all(not(feature = "spirv-std"), test))] 23 | mod test { 24 | use core::marker::PhantomData; 25 | 26 | use type_fields::t_funk::tlist::AsHListRef; 27 | 28 | use crate::prelude::{Attribute, Registers, Field, FieldRegisters}; 29 | 30 | #[test] 31 | pub fn test_field_contexts() { 32 | let context = (1usize, 2.0, "three").as_hlist_ref(); 33 | 34 | let (_float, (_int, ())) = Registers::<_, (&f32, (&usize, ()))>::registers(context); 35 | 36 | pub struct TestContexts; 37 | 38 | #[derive(Debug, Default, Copy, Clone)] 39 | pub struct AttrTestContexts<'a>(PhantomData<&'a ()>); 40 | 41 | impl<'a> Attribute for AttrTestContexts<'a> { 42 | type Input = (&'a f32, (&'a usize, ())); 43 | type Output = (usize, f32); 44 | } 45 | 46 | impl Field> for TestContexts { 47 | fn field( 48 | &self, 49 | (float, (int, ())): &::Input, 50 | ) -> ::Output { 51 | (**int, **float) 52 | } 53 | } 54 | 55 | let _out = TestContexts.field_registers(context); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/field/traits/field_registers_uncons.rs: -------------------------------------------------------------------------------- 1 | use type_fields::t_funk::{hlist::ToTList, tlist::ToHList}; 2 | 3 | use crate::prelude::{AttributeRef, Field, RegistersUncons}; 4 | 5 | pub trait FieldRegistersUncons<'a, Attr, Ctx, State>: Field 6 | where 7 | Attr: AttributeRef<'a>, 8 | Attr::InputRef: ToHList, 9 | ::HList: ToTList, 10 | Ctx: RegistersUncons::HList>, 11 | { 12 | fn field_registers_uncons(&self, ctx: Ctx) -> Attr::Output; 13 | } 14 | 15 | impl<'a, T, Attr, Ctx, State> FieldRegistersUncons<'a, Attr, Ctx, State> for T 16 | where 17 | Self: Field, 18 | Attr: AttributeRef<'a>, 19 | Attr::InputRef: ToHList, 20 | ::HList: ToTList, 21 | Ctx: RegistersUncons::HList>, 22 | { 23 | fn field_registers_uncons(&self, ctx: Ctx) -> Attr::Output { 24 | self.field(&ctx.registers_uncons()) 25 | } 26 | } 27 | 28 | #[cfg(all(not(feature = "spirv-std"), test))] 29 | mod test { 30 | use core::marker::PhantomData; 31 | 32 | use type_fields::t_funk::tlist::AsHListRef; 33 | 34 | use crate::prelude::{Attribute, Field, FieldRegistersUncons, RegistersUncons}; 35 | 36 | #[test] 37 | pub fn test_field_contexts_uncons() { 38 | let context = (1usize, 2.0, "three").as_hlist_ref(); 39 | 40 | let (_float, _int) = RegistersUncons::<_, (&f32, &usize)>::registers_uncons(context); 41 | 42 | pub struct TestContextsUncons; 43 | 44 | #[derive(Debug, Default, Copy, Clone)] 45 | pub struct AttrTestContextsUncons<'a>(PhantomData<&'a ()>); 46 | 47 | impl<'a> Attribute for AttrTestContextsUncons<'a> { 48 | type Input = (&'a f32, &'a usize); 49 | type Output = (usize, f32); 50 | } 51 | 52 | impl Field> for TestContextsUncons { 53 | fn field( 54 | &self, 55 | (float, int): &::Input, 56 | ) -> ::Output { 57 | (**int, **float) 58 | } 59 | } 60 | 61 | let _out = TestContextsUncons.field_registers_uncons(context); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/field/traits/fields.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::{Attribute, Attributes, Field}; 2 | 3 | /// Function associating several attribute values with a point in space. 4 | /// 5 | /// Extension trait of `Field`; 6 | /// impls over `(LHS, RHS)` and `(LHS, ())` to allow recursive 7 | /// evaluation of `Attribute` cons lists. 8 | pub trait Fields 9 | where 10 | Attrs: Attributes, 11 | { 12 | fn fields(&self, input: &Attrs::Input) -> Attrs::Output; 13 | } 14 | 15 | impl Fields<(LHS, RHS)> for T 16 | where 17 | T: Field + Fields, 18 | LHS: Attribute, 19 | RHS: Attributes, 20 | { 21 | fn fields( 22 | &self, 23 | input: &<(LHS, RHS) as Attributes>::Input, 24 | ) -> <(LHS, RHS) as Attributes>::Output { 25 | (self.field(input), self.fields(input)) 26 | } 27 | } 28 | 29 | impl Fields<(LHS, ())> for T 30 | where 31 | T: Field, 32 | LHS: Attribute, 33 | { 34 | fn fields( 35 | &self, 36 | input: &<(LHS, ()) as Attributes>::Input, 37 | ) -> <(LHS, ()) as Attributes>::Output { 38 | (self.field(input), ()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/field/traits/fields_register.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::{AttributesRef, Register, Fields}; 2 | 3 | /// Function associating an attribute value with a point in space. 4 | /// 5 | /// API extension trait of `Fields`; 6 | /// moves the `Attr` generic into the function position, 7 | /// and obscures the `attr` parameter using `Attribute`'s `Default` constraint 8 | pub trait FieldsRegister<'a, Ctx, State, Attr>: Fields 9 | where 10 | Attr: AttributesRef<'a>, 11 | Ctx: Register, 12 | { 13 | fn fields_register(&self, p: Ctx) -> Attr::Output; 14 | } 15 | 16 | impl<'a, T, Ctx, State, Attrs> FieldsRegister<'a, Ctx, State, Attrs> for T 17 | where 18 | Self: Fields, 19 | Attrs: AttributesRef<'a>, 20 | Ctx: Register, 21 | { 22 | fn fields_register(&self, ctx: Ctx) -> Attrs::Output { 23 | self.fields(ctx.register()) 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/field/traits/fields_registers.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::{AttributesRef, Registers, Fields}; 2 | 3 | /// Evalute multiple attributes of a field function, drawing input from `Ctx`. 4 | pub trait FieldsRegisters<'a, Ctx, State, Attrs>: Fields 5 | where 6 | Attrs: AttributesRef<'a>, 7 | Ctx: Registers, 8 | { 9 | fn fields_registers(&self, ctx: Ctx) -> Attrs::Output; 10 | } 11 | 12 | impl<'a, T, Attrs, Ctx, State> FieldsRegisters<'a, Ctx, State, Attrs> for T 13 | where 14 | Self: Fields, 15 | Attrs: AttributesRef<'a>, 16 | Ctx: Registers, 17 | { 18 | fn fields_registers(&self, ctx: Ctx) -> Attrs::Output { 19 | self.fields(&ctx.registers()) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/field/traits/fields_registers_uncons.rs: -------------------------------------------------------------------------------- 1 | use type_fields::t_funk::{hlist::ToTList, tlist::ToHList}; 2 | 3 | use crate::prelude::{Attributes, AttributesRef, Fields, RegistersUncons}; 4 | 5 | /// Evalute multiple attributes of a field function, 6 | /// drawing input from `Ctx` and `Uncons`ing the result. 7 | pub trait FieldsRegistersUncons<'a, Attrs, Ctx, State>: Fields 8 | where 9 | Attrs: ToHList, 10 | Attrs::HList: AttributesRef<'a>, 11 | >::InputRef: ToHList, 12 | <>::InputRef as ToHList>::HList: 13 | ToTList>::InputRef>, 14 | ::Output: ToTList, 15 | Ctx: RegistersUncons< 16 | State, 17 | >::InputRef, 18 | Type = <>::InputRef as ToHList>::HList, 19 | >, 20 | { 21 | fn fields_registers_uncons(&self, ctx: Ctx) -> ::Output; 22 | } 23 | 24 | impl<'a, T, Attrs, Ctx, State> FieldsRegistersUncons<'a, Attrs, Ctx, State> for T 25 | where 26 | Self: Fields, 27 | Attrs: ToHList, 28 | Attrs::HList: AttributesRef<'a>, 29 | >::InputRef: ToHList, 30 | <>::InputRef as ToHList>::HList: 31 | ToTList>::InputRef>, 32 | ::Output: ToTList, 33 | Ctx: RegistersUncons< 34 | State, 35 | >::InputRef, 36 | Type = <>::InputRef as ToHList>::HList, 37 | >, 38 | { 39 | fn fields_registers_uncons(&self, ctx: Ctx) -> ::Output { 40 | self.fields(&ctx.registers_uncons()) 41 | } 42 | } 43 | 44 | #[cfg(all(not(feature = "spirv-std"), test))] 45 | mod test { 46 | use core::marker::PhantomData; 47 | 48 | use type_fields::t_funk::tlist::AsHListRef; 49 | 50 | use crate::prelude::{Attribute, Field, FieldOperator, FieldsRegistersUncons, Operator}; 51 | 52 | #[test] 53 | pub fn test_fields_contexts_uncons() { 54 | let context = (1usize, 2.0, "three").as_hlist_ref(); 55 | 56 | #[derive(Default)] 57 | pub struct TestFieldsContextsUncons; 58 | 59 | #[derive(Debug, Default, Copy, Clone)] 60 | pub struct AttrTestFieldsContextsUncons<'a>(PhantomData<&'a ()>); 61 | 62 | impl<'a> Attribute for AttrTestFieldsContextsUncons<'a> { 63 | type Input = (&'a f32, &'a usize); 64 | type Output = (usize, f32); 65 | } 66 | 67 | impl Field> for TestFieldsContextsUncons { 68 | fn field( 69 | &self, 70 | (float, int): &::Input, 71 | ) -> ::Output { 72 | (**int, **float) 73 | } 74 | } 75 | 76 | #[derive(Default)] 77 | pub struct TestFieldsContextsUncons2; 78 | 79 | #[derive(Debug, Default, Copy, Clone)] 80 | pub struct AttrTestFieldsContextsUncons2<'a>(PhantomData<&'a ()>); 81 | 82 | impl<'a> Attribute for AttrTestFieldsContextsUncons2<'a> { 83 | type Input = (&'a f32, &'a usize); 84 | type Output = (&'static str, u8); 85 | } 86 | 87 | impl FieldOperator> for TestFieldsContextsUncons2 { 88 | fn operator( 89 | &self, 90 | _: &Sdf, 91 | (float, _): & as Attribute>::Input, 92 | ) -> as Attribute>::Output { 93 | ("hello", **float as u8) 94 | } 95 | } 96 | 97 | impl<'a, Sdf> FieldOperator> for TestFieldsContextsUncons2 98 | where 99 | Sdf: Field>, 100 | { 101 | fn operator( 102 | &self, 103 | sdf: &Sdf, 104 | input: & as Attribute>::Input, 105 | ) -> as Attribute>::Output { 106 | sdf.field(input) 107 | } 108 | } 109 | 110 | let ((_int, _float), ((_string, _smallint), ())) = FieldsRegistersUncons::< 111 | (AttrTestFieldsContextsUncons, AttrTestFieldsContextsUncons2), 112 | _, 113 | _, 114 | >::fields_registers_uncons( 115 | &Operator::::default(), 116 | context, 117 | ); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/field/traits/fields_uncons_registers_uncons.rs: -------------------------------------------------------------------------------- 1 | use type_fields::t_funk::{hlist::ToTList, tlist::ToHList}; 2 | 3 | use crate::prelude::{Attributes, AttributesRef, FieldsRegistersUncons, RegistersUncons}; 4 | 5 | pub trait FieldsUnconsRegistersUncons<'a, Attrs, Ctx, State>: 6 | FieldsRegistersUncons<'a, Attrs, Ctx, State> 7 | where 8 | Attrs: ToHList, 9 | Attrs::HList: AttributesRef<'a>, 10 | >::InputRef: ToHList, 11 | <>::InputRef as ToHList>::HList: 12 | ToTList>::InputRef>, 13 | ::Output: ToTList, 14 | Ctx: RegistersUncons< 15 | State, 16 | >::InputRef, 17 | Type = <>::InputRef as ToHList>::HList, 18 | >, 19 | { 20 | fn fields_uncons_registers_uncons( 21 | &self, 22 | ctx: Ctx, 23 | ) -> <::Output as ToTList>::TList; 24 | } 25 | 26 | impl<'a, T, Attrs, Ctx, State> FieldsUnconsRegistersUncons<'a, Attrs, Ctx, State> for T 27 | where 28 | Self: FieldsRegistersUncons<'a, Attrs, Ctx, State>, 29 | Attrs: ToHList, 30 | Attrs::HList: AttributesRef<'a>, 31 | >::InputRef: ToHList, 32 | <>::InputRef as ToHList>::HList: 33 | ToTList>::InputRef>, 34 | ::Output: ToTList, 35 | Ctx: RegistersUncons< 36 | State, 37 | >::InputRef, 38 | Type = <>::InputRef as ToHList>::HList, 39 | >, 40 | { 41 | fn fields_uncons_registers_uncons( 42 | &self, 43 | ctx: Ctx, 44 | ) -> <::Output as ToTList>::TList { 45 | self.fields(&ctx.registers_uncons()).to_tlist() 46 | } 47 | } 48 | 49 | #[cfg(all(not(feature = "spirv-std"), test))] 50 | mod test { 51 | use core::marker::PhantomData; 52 | 53 | use type_fields::t_funk::tlist::AsHListRef; 54 | 55 | use crate::prelude::{ 56 | fields_uncons_registers_uncons::FieldsUnconsRegistersUncons, Attribute, Field, 57 | FieldOperator, Operator, 58 | }; 59 | 60 | #[test] 61 | pub fn test_fields_uncons_registers_uncons() { 62 | let context = (1usize, 2.0, "three").as_hlist_ref(); 63 | 64 | #[derive(Default)] 65 | pub struct TestFieldsContextsUncons; 66 | 67 | #[derive(Debug, Default, Copy, Clone)] 68 | pub struct AttrTestFieldsContextsUncons<'a>(PhantomData<&'a ()>); 69 | 70 | impl<'a> Attribute for AttrTestFieldsContextsUncons<'a> { 71 | type Input = (&'a f32, &'a usize); 72 | type Output = (usize, f32); 73 | } 74 | 75 | impl Field> for TestFieldsContextsUncons { 76 | fn field( 77 | &self, 78 | (float, int): &::Input, 79 | ) -> ::Output { 80 | (**int, **float) 81 | } 82 | } 83 | 84 | #[derive(Default)] 85 | pub struct TestFieldsContextsUncons2; 86 | 87 | #[derive(Debug, Default, Copy, Clone)] 88 | pub struct AttrTestFieldsContextsUncons2<'a>(PhantomData<&'a ()>); 89 | 90 | impl<'a> Attribute for AttrTestFieldsContextsUncons2<'a> { 91 | type Input = (&'a f32, &'a usize); 92 | type Output = (&'static str, u8); 93 | } 94 | 95 | impl FieldOperator> for TestFieldsContextsUncons2 { 96 | fn operator( 97 | &self, 98 | _: &Sdf, 99 | (float, _): & as Attribute>::Input, 100 | ) -> as Attribute>::Output { 101 | ("hello", **float as u8) 102 | } 103 | } 104 | 105 | impl<'a, Sdf> FieldOperator> for TestFieldsContextsUncons2 106 | where 107 | Sdf: Field>, 108 | { 109 | fn operator( 110 | &self, 111 | sdf: &Sdf, 112 | input: & as Attribute>::Input, 113 | ) -> as Attribute>::Output { 114 | sdf.field(input) 115 | } 116 | } 117 | 118 | let ((_int, _float), (_string, _smallint)) = FieldsUnconsRegistersUncons::< 119 | (AttrTestFieldsContextsUncons, AttrTestFieldsContextsUncons2), 120 | _, 121 | _, 122 | >::fields_uncons_registers_uncons( 123 | &Operator::::default(), 124 | context, 125 | ); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/field/traits/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod field; 2 | pub mod field_attribute; 3 | pub mod field_attribute_register; 4 | pub mod field_attributes; 5 | pub mod field_attributes_register; 6 | pub mod field_attributes_register_cons; 7 | pub mod field_register; 8 | pub mod field_registers; 9 | pub mod field_registers_uncons; 10 | pub mod fields; 11 | pub mod fields_register; 12 | pub mod fields_registers; 13 | pub mod fields_registers_uncons; 14 | pub mod fields_uncons_registers_uncons; 15 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::{ 2 | attribute::{ 3 | bound_error::*, color::*, distance::*, normal::*, support_function::*, tangent::*, uv::*, *, 4 | }, 5 | bound_tester::*, 6 | context::{ 7 | items::{position::*, *}, 8 | traits::{ 9 | register::*, register_item::*, register_items::*, register_items_uncons::*, 10 | registers::*, registers_uncons::*, *, 11 | }, 12 | *, 13 | }, 14 | field::{ 15 | field_operator::{ 16 | arity::{extrude::*, extrude_interior::*, slice::*, sweep::*, *}, 17 | boolean::{intersection::*, subtraction::*, union::*, *}, 18 | checker::*, 19 | color_normal::*, 20 | color_tangent::*, 21 | color_uv::*, 22 | composite::*, 23 | coordinate_system::{cartesian_to_spherical::*, spherical_to_cartesian::*, *}, 24 | displace::*, 25 | displace_proxy::*, 26 | elongate::*, 27 | gradient::{gradient_central_diff::*, gradient_tetrahedron::*, gradient_uv::*, *}, 28 | hollow::*, 29 | isosurface::*, 30 | isosurface_proxy::*, 31 | normalize::*, 32 | proxy::{proxy_color::*, proxy_normal::*, proxy_tangent::*, proxy_uv::*}, 33 | raycast::{raytrace::*, sphere_trace_lipschitz::*, sphere_trace_naive::*, *}, 34 | reflect::{axial_reflect::*, reflect::*, *}, 35 | repeat::{repeat_count::*, repeat_infinite::*, *}, 36 | scale_uv::*, 37 | sided::*, 38 | smooth_boolean::{smooth_intersection::*, smooth_subtraction::*, smooth_union::*, *}, 39 | stretch::*, 40 | transform::{rotate::*, scale::*, translate::*, *}, 41 | triplanar_uv::*, 42 | twist::*, 43 | *, 44 | }, 45 | metric::{chebyshev::*, euclidean::*, superellipse::*, taxicab::*, *}, 46 | shape::{composite::*, octahedron::*, plane::*, squircle::*, *}, 47 | traits::{ 48 | field::*, field_attribute::*, field_attribute_register::*, field_attributes::*, 49 | field_attributes_register_cons::*, field_register::*, field_registers::*, 50 | field_registers_uncons::*, fields::*, fields_register::*, fields_registers::*, 51 | fields_registers_uncons::*, fields_uncons_registers_uncons::*, *, 52 | }, 53 | *, 54 | }, 55 | *, 56 | }; 57 | --------------------------------------------------------------------------------