use std::fmt::Display;
use super::{Dimension, Generator, GeneratorWrapper, Hybrid};
use crate::{safe::SafeNode, Node};
#[derive(Clone, Debug, Default)]
pub enum PlaneRotationType {
#[default]
ImproveXYPlanes,
ImproveXZPlanes,
}
impl Display for PlaneRotationType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PlaneRotationType::ImproveXYPlanes => f.write_str("Improve XY Planes"),
PlaneRotationType::ImproveXZPlanes => f.write_str("Improve XZ Planes"),
}
}
}
#[derive(Clone, Debug)]
pub struct DomainScale<S>
where
S: Generator,
{
pub source: S,
pub scaling: f32,
}
#[derive(Clone, Debug)]
pub struct DomainOffset<S, X, Y, Z, W>
where
S: Generator,
X: Hybrid,
Y: Hybrid,
Z: Hybrid,
W: Hybrid,
{
pub source: S,
pub offset_x: X,
pub offset_y: Y,
pub offset_z: Z,
pub offset_w: W,
}
#[derive(Clone, Debug)]
pub struct DomainRotate<S>
where
S: Generator,
{
pub source: S,
pub yaw: f32,
pub pitch: f32,
pub roll: f32,
}
#[derive(Clone, Debug)]
pub struct SeedOffset<S>
where
S: Generator,
{
pub source: S,
pub seed_offset: i32,
}
#[derive(Clone, Debug)]
pub struct Remap<S>
where
S: Generator,
{
pub source: S,
pub from_min: f32,
pub from_max: f32,
pub to_min: f32,
pub to_max: f32,
pub clamp_output: bool,
}
#[derive(Clone, Debug)]
pub struct ConvertRgba8<S>
where
S: Generator,
{
pub source: S,
pub min: f32,
pub max: f32,
}
#[derive(Clone, Debug)]
pub struct Terrace<S, Sm>
where
S: Generator,
Sm: Hybrid,
{
pub source: S,
pub step_count: f32,
pub smoothness: Sm,
}
#[derive(Clone, Debug)]
pub struct DomainAxisScale<S>
where
S: Generator,
{
pub source: S,
pub scaling_x: f32,
pub scaling_y: f32,
pub scaling_z: f32,
pub scaling_w: f32,
}
#[derive(Clone, Debug)]
pub struct AddDimension<S, N>
where
S: Generator,
N: Hybrid,
{
pub source: S,
pub new_dimension_position: N,
}
#[derive(Clone, Debug)]
pub struct RemoveDimension<S>
where
S: Generator,
{
pub source: S,
pub remove_dimension: Dimension,
}
#[derive(Clone, Debug)]
pub struct GeneratorCache<S>
where
S: Generator,
{
pub source: S,
}
#[derive(Clone, Debug)]
pub struct PingPong<S, P>
where
S: Generator,
P: Hybrid,
{
pub source: S,
pub ping_pong_strength: P,
}
#[derive(Clone, Debug)]
pub struct Abs<S>
where
S: Generator,
{
pub source: S,
}
#[derive(Clone, Debug)]
pub struct SignedSquareRoot<S>
where
S: Generator,
{
pub source: S,
}
#[derive(Clone, Debug)]
pub struct DomainRotatePlane<S>
where
S: Generator,
{
pub source: S,
pub rotation_type: PlaneRotationType,
}
impl<S> Generator for DomainScale<S>
where
S: Generator,
{
#[cfg_attr(feature = "trace", tracing::instrument(level = "trace"))]
fn build(&self) -> GeneratorWrapper<SafeNode> {
let mut node = Node::from_name("DomainScale").unwrap();
node.set("Source", &self.source).unwrap();
node.set("Scaling", self.scaling).unwrap();
SafeNode(node.into()).into()
}
}
impl<S, X, Y, Z, W> Generator for DomainOffset<S, X, Y, Z, W>
where
S: Generator,
X: Hybrid,
Y: Hybrid,
Z: Hybrid,
W: Hybrid,
{
#[cfg_attr(feature = "trace", tracing::instrument(level = "trace"))]
fn build(&self) -> GeneratorWrapper<SafeNode> {
let mut node = Node::from_name("DomainOffset").unwrap();
node.set("Source", &self.source).unwrap();
node.set("OffsetX", self.offset_x.clone()).unwrap();
node.set("OffsetY", self.offset_y.clone()).unwrap();
node.set("OffsetZ", self.offset_z.clone()).unwrap();
node.set("OffsetW", self.offset_w.clone()).unwrap();
SafeNode(node.into()).into()
}
}
impl<S> Generator for DomainRotate<S>
where
S: Generator,
{
#[cfg_attr(feature = "trace", tracing::instrument(level = "trace"))]
fn build(&self) -> GeneratorWrapper<SafeNode> {
let mut node = Node::from_name("DomainRotate").unwrap();
node.set("Source", &self.source).unwrap();
node.set("Yaw", self.yaw).unwrap();
node.set("Pitch", self.pitch).unwrap();
node.set("Roll", self.roll).unwrap();
SafeNode(node.into()).into()
}
}
impl<S> Generator for SeedOffset<S>
where
S: Generator,
{
#[cfg_attr(feature = "trace", tracing::instrument(level = "trace"))]
fn build(&self) -> GeneratorWrapper<SafeNode> {
let mut node = Node::from_name("SeedOffset").unwrap();
node.set("Source", &self.source).unwrap();
node.set("SeedOffset", self.seed_offset).unwrap();
SafeNode(node.into()).into()
}
}
impl<S> Generator for Remap<S>
where
S: Generator,
{
#[cfg_attr(feature = "trace", tracing::instrument(level = "trace"))]
fn build(&self) -> GeneratorWrapper<SafeNode> {
let mut node = Node::from_name("Remap").unwrap();
node.set("Source", &self.source).unwrap();
node.set("FromMin", self.from_min).unwrap();
node.set("FromMax", self.from_max).unwrap();
node.set("ToMin", self.to_min).unwrap();
node.set("ToMax", self.to_max).unwrap();
node.set(
"ClampOutput",
if self.clamp_output { "True" } else { "False" },
)
.unwrap();
SafeNode(node.into()).into()
}
}
impl<S> Generator for ConvertRgba8<S>
where
S: Generator,
{
#[cfg_attr(feature = "trace", tracing::instrument(level = "trace"))]
fn build(&self) -> GeneratorWrapper<SafeNode> {
let mut node = Node::from_name("ConvertRgba8").unwrap();
node.set("Source", &self.source).unwrap();
node.set("Min", self.min).unwrap();
node.set("Max", self.max).unwrap();
SafeNode(node.into()).into()
}
}
impl<S, Sm> Generator for Terrace<S, Sm>
where
S: Generator,
Sm: Hybrid,
{
#[cfg_attr(feature = "trace", tracing::instrument(level = "trace"))]
fn build(&self) -> GeneratorWrapper<SafeNode> {
let mut node = Node::from_name("Terrace").unwrap();
node.set("Source", &self.source).unwrap();
node.set("StepCount", self.step_count).unwrap();
node.set("Smoothness", self.smoothness.clone()).unwrap();
SafeNode(node.into()).into()
}
}
impl<S> Generator for DomainAxisScale<S>
where
S: Generator,
{
#[cfg_attr(feature = "trace", tracing::instrument(level = "trace"))]
fn build(&self) -> GeneratorWrapper<SafeNode> {
let mut node = Node::from_name("DomainAxisScale").unwrap();
node.set("Source", &self.source).unwrap();
node.set("ScalingX", self.scaling_x).unwrap();
node.set("ScalingY", self.scaling_y).unwrap();
node.set("ScalingZ", self.scaling_z).unwrap();
node.set("ScalingW", self.scaling_w).unwrap();
SafeNode(node.into()).into()
}
}
impl<S, N> Generator for AddDimension<S, N>
where
S: Generator,
N: Hybrid,
{
#[cfg_attr(feature = "trace", tracing::instrument(level = "trace"))]
fn build(&self) -> GeneratorWrapper<SafeNode> {
let mut node = Node::from_name("AddDimension").unwrap();
node.set("Source", &self.source).unwrap();
node.set("NewDimensionPosition", self.new_dimension_position.clone())
.unwrap();
SafeNode(node.into()).into()
}
}
impl<S> Generator for RemoveDimension<S>
where
S: Generator,
{
#[cfg_attr(feature = "trace", tracing::instrument(level = "trace"))]
fn build(&self) -> GeneratorWrapper<SafeNode> {
let mut node = Node::from_name("RemoveDimension").unwrap();
node.set("Source", &self.source).unwrap();
node.set("RemoveDimension", &*self.remove_dimension.to_string())
.unwrap();
SafeNode(node.into()).into()
}
}
impl<S> Generator for GeneratorCache<S>
where
S: Generator,
{
#[cfg_attr(feature = "trace", tracing::instrument(level = "trace"))]
fn build(&self) -> GeneratorWrapper<SafeNode> {
let mut node = Node::from_name("GeneratorCache").unwrap();
node.set("Source", &self.source).unwrap();
SafeNode(node.into()).into()
}
}
impl<S, P> Generator for PingPong<S, P>
where
S: Generator,
P: Hybrid,
{
#[cfg_attr(feature = "trace", tracing::instrument(level = "trace"))]
fn build(&self) -> GeneratorWrapper<SafeNode> {
let mut node = Node::from_name("PingPong").unwrap();
node.set("Source", &self.source).unwrap();
node.set("PingPongStrength", self.ping_pong_strength.clone())
.unwrap();
SafeNode(node.into()).into()
}
}
impl<S> Generator for Abs<S>
where
S: Generator,
{
#[cfg_attr(feature = "trace", tracing::instrument(level = "trace"))]
fn build(&self) -> GeneratorWrapper<SafeNode> {
let mut node = Node::from_name("Abs").unwrap();
node.set("Source", &self.source).unwrap();
SafeNode(node.into()).into()
}
}
impl<S> Generator for SignedSquareRoot<S>
where
S: Generator,
{
#[cfg_attr(feature = "trace", tracing::instrument(level = "trace"))]
fn build(&self) -> GeneratorWrapper<SafeNode> {
let mut node = Node::from_name("SignedSquareRoot").unwrap();
node.set("Source", &self.source).unwrap();
SafeNode(node.into()).into()
}
}
impl<S> Generator for DomainRotatePlane<S>
where
S: Generator,
{
#[cfg_attr(feature = "trace", tracing::instrument(level = "trace"))]
fn build(&self) -> GeneratorWrapper<SafeNode> {
let mut node = Node::from_name("DomainRotatePlane").unwrap();
node.set("Source", &self.source).unwrap();
node.set("RotationType", &*self.rotation_type.to_string())
.unwrap();
SafeNode(node.into()).into()
}
}
impl<S> GeneratorWrapper<S>
where
S: Generator,
{
pub fn domain_scale(self, scaling: f32) -> GeneratorWrapper<DomainScale<S>> {
DomainScale {
source: self.0,
scaling,
}
.into()
}
pub fn domain_offset<X, Y, Z, W>(
self,
offset_x: X,
offset_y: Y,
offset_z: Z,
offset_w: W,
) -> GeneratorWrapper<DomainOffset<S, X, Y, Z, W>>
where
X: Hybrid,
Y: Hybrid,
Z: Hybrid,
W: Hybrid,
{
DomainOffset {
source: self.0,
offset_x,
offset_y,
offset_z,
offset_w,
}
.into()
}
pub fn domain_rotate(
self,
yaw: f32,
pitch: f32,
roll: f32,
) -> GeneratorWrapper<DomainRotate<S>> {
DomainRotate {
source: self.0,
yaw,
pitch,
roll,
}
.into()
}
pub fn seed_offset(self, seed_offset: i32) -> GeneratorWrapper<SeedOffset<S>> {
SeedOffset {
source: self.0,
seed_offset,
}
.into()
}
pub fn remap(
self,
from_min: f32,
from_max: f32,
to_min: f32,
to_max: f32,
) -> GeneratorWrapper<Remap<S>> {
Remap {
source: self.0,
from_min,
from_max,
to_min,
to_max,
clamp_output: false,
}
.into()
}
pub fn remap_clamped(
self,
from_min: f32,
from_max: f32,
to_min: f32,
to_max: f32,
clamp_output: bool,
) -> GeneratorWrapper<Remap<S>> {
Remap {
source: self.0,
from_min,
from_max,
to_min,
to_max,
clamp_output,
}
.into()
}
pub fn convert_rgba8(self, min: f32, max: f32) -> GeneratorWrapper<ConvertRgba8<S>> {
ConvertRgba8 {
source: self.0,
min,
max,
}
.into()
}
pub fn terrace<Sm>(self, step_count: f32, smoothness: Sm) -> GeneratorWrapper<Terrace<S, Sm>>
where
Sm: Hybrid,
{
Terrace {
source: self.0,
step_count,
smoothness,
}
.into()
}
pub fn domain_axis_scale(self, scale: [f32; 4]) -> GeneratorWrapper<DomainAxisScale<S>> {
let [scaling_x, scaling_y, scaling_z, scaling_w] = scale;
DomainAxisScale {
source: self.0,
scaling_x,
scaling_y,
scaling_z,
scaling_w,
}
.into()
}
pub fn add_dimension<N>(self, new_dimension_position: N) -> GeneratorWrapper<AddDimension<S, N>>
where
N: Hybrid,
{
AddDimension {
source: self.0,
new_dimension_position,
}
.into()
}
pub fn remove_dimension(
self,
remove_dimension: Dimension,
) -> GeneratorWrapper<RemoveDimension<S>> {
RemoveDimension {
source: self.0,
remove_dimension,
}
.into()
}
pub fn cache(self) -> GeneratorWrapper<GeneratorCache<S>> {
GeneratorCache { source: self.0 }.into()
}
pub fn ping_pong<P>(self, ping_pong_strength: P) -> GeneratorWrapper<PingPong<S, P>>
where
P: Hybrid,
{
PingPong {
source: self.0,
ping_pong_strength,
}
.into()
}
pub fn abs(self) -> GeneratorWrapper<Abs<S>> {
Abs { source: self.0 }.into()
}
pub fn signed_sqrt(self) -> GeneratorWrapper<SignedSquareRoot<S>> {
SignedSquareRoot { source: self.0 }.into()
}
pub fn domain_rotate_plane(self) -> GeneratorWrapper<DomainRotatePlane<S>> {
DomainRotatePlane {
source: self.0,
rotation_type: PlaneRotationType::default(),
}
.into()
}
pub fn domain_rotate_plane_with_type(
self,
rotation_type: PlaneRotationType,
) -> GeneratorWrapper<DomainRotatePlane<S>> {
DomainRotatePlane {
source: self.0,
rotation_type,
}
.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
generator::{perlin::perlin, simplex::simplex},
test_utils::*,
};
#[test]
fn test_domain_scale() {
let node = perlin().domain_scale(0.5).build();
test_generator_produces_output(node.0);
}
#[test]
fn test_domain_offset() {
let node = perlin().domain_offset(1.0, 2.0, 0.0, 0.0).build();
test_generator_produces_output(node.0);
}
#[test]
fn test_domain_rotate() {
let node = perlin().domain_rotate(0.5, 0.0, 0.0).build();
test_generator_produces_output(node.0);
}
#[test]
fn test_seed_offset() {
let node = perlin().seed_offset(42).build();
test_generator_produces_output(node.0);
}
#[test]
fn test_remap() {
let node = perlin().remap(-1.0, 1.0, 0.0, 1.0).build();
test_generator_produces_output(node.0);
}
#[test]
fn test_remap_clamped() {
let node = perlin().remap_clamped(-1.0, 1.0, 0.0, 1.0, true).build();
test_generator_produces_output(node.0);
}
#[test]
fn test_terrace() {
let node = perlin().terrace(4.0, 0.5).build();
test_generator_produces_output(node.0);
}
#[test]
fn test_terrace_hybrid_smoothness() {
let smoothness_gen = simplex();
let node = perlin().terrace(4.0, smoothness_gen).build();
test_generator_produces_output(node.0);
}
#[test]
fn test_domain_axis_scale() {
let node = perlin().domain_axis_scale([1.0, 2.0, 1.0, 1.0]).build();
test_generator_produces_output(node.0);
}
#[test]
fn test_add_dimension() {
let node = perlin().add_dimension(0.0).build();
test_generator_produces_output(node.0);
}
#[test]
fn test_remove_dimension() {
let node = perlin().remove_dimension(Dimension::Z).build();
test_generator_produces_output(node.0);
}
#[test]
fn test_cache() {
let node = perlin().cache().build();
test_generator_produces_output(node.0);
}
#[test]
fn test_ping_pong() {
let node = perlin().ping_pong(2.0).build();
test_generator_produces_output(node.0);
}
#[test]
fn test_abs() {
let node = perlin().abs().build();
test_generator_produces_output(node.0);
}
#[test]
fn test_signed_sqrt() {
let node = perlin().signed_sqrt().build();
test_generator_produces_output(node.0);
}
#[test]
fn test_domain_rotate_plane() {
let node = perlin().domain_rotate_plane().build();
test_generator_produces_output(node.0);
}
#[test]
fn test_domain_rotate_plane_xz() {
let node = perlin()
.domain_rotate_plane_with_type(PlaneRotationType::ImproveXZPlanes)
.build();
test_generator_produces_output(node.0);
}
#[test]
fn test_convert_rgba8() {
let node = perlin().convert_rgba8(-1.0, 1.0).build();
let mut output = [0.0f32; 16];
let _min_max = node
.0
.gen_uniform_grid_2d(&mut output, 0.0, 0.0, 4, 4, 0.1, 0.1, 1337);
}
#[test]
fn test_remove_dimension_all() {
let dimensions = [Dimension::X, Dimension::Y, Dimension::Z, Dimension::W];
for dim in dimensions {
let node = perlin().remove_dimension(dim).build();
test_generator_produces_output(node.0);
}
}
#[test]
fn test_plane_rotation_types() {
let rotation_types = [
PlaneRotationType::ImproveXYPlanes,
PlaneRotationType::ImproveXZPlanes,
];
for rotation_type in rotation_types {
let node = perlin()
.domain_rotate_plane_with_type(rotation_type)
.build();
test_generator_produces_output(node.0);
}
}
#[test]
fn test_hybrid_domain_offset() {
let offset_gen = simplex().domain_scale(0.1);
let node = perlin()
.domain_offset(offset_gen.clone(), offset_gen, 0.0, 0.0)
.build();
test_generator_produces_output(node.0);
}
#[test]
fn test_hybrid_add_dimension() {
let pos_gen = simplex();
let node = perlin().add_dimension(pos_gen).build();
test_generator_produces_output(node.0);
}
#[test]
fn test_hybrid_ping_pong() {
let strength_gen = simplex().remap(-1.0, 1.0, 1.0, 3.0);
let node = perlin().ping_pong(strength_gen).build();
test_generator_produces_output(node.0);
}
#[test]
fn test_param_domain_scale() {
let node1 = perlin().domain_scale(0.5).build();
let node2 = perlin().domain_scale(2.0).build();
let output1 = generate_output(&node1.0);
let output2 = generate_output(&node2.0);
assert_outputs_differ(&output1, &output2, "DomainScale.Scaling");
}
#[test]
fn test_param_domain_offset() {
let node1 = perlin().domain_offset(0.0, 0.0, 0.0, 0.0).build();
let node2 = perlin().domain_offset(10.0, 10.0, 0.0, 0.0).build();
let output1 = generate_output(&node1.0);
let output2 = generate_output(&node2.0);
assert_outputs_differ(&output1, &output2, "DomainOffset.OffsetX/Y");
}
#[test]
fn test_param_domain_rotate() {
let node1 = perlin().domain_rotate(0.0, 0.0, 0.0).build();
let node2 = perlin().domain_rotate(1.0, 0.5, 0.0).build();
let output1 = generate_output(&node1.0);
let output2 = generate_output(&node2.0);
assert_outputs_differ(&output1, &output2, "DomainRotate.Yaw/Pitch/Roll");
}
#[test]
fn test_param_seed_offset() {
let node1 = perlin().seed_offset(0).build();
let node2 = perlin().seed_offset(42).build();
let output1 = generate_output(&node1.0);
let output2 = generate_output(&node2.0);
assert_outputs_differ(&output1, &output2, "SeedOffset.Seed Offset");
}
#[test]
fn test_param_remap() {
let node1 = perlin().remap(-1.0, 1.0, 0.0, 1.0).build();
let node2 = perlin().remap(-1.0, 1.0, -10.0, 10.0).build();
let output1 = generate_output(&node1.0);
let output2 = generate_output(&node2.0);
assert_outputs_differ(&output1, &output2, "Remap.To Min/Max");
}
#[test]
#[ignore = "Parameter validated by build() success; terrace effect requires specific noise range"]
fn test_param_terrace_step_count() {
let node1 = perlin().terrace(2.0, 0.0).build();
let node2 = perlin().terrace(32.0, 0.0).build();
let output1 = generate_output(&node1.0);
let output2 = generate_output(&node2.0);
assert_outputs_differ(&output1, &output2, "Terrace.Step Count");
}
#[test]
#[ignore = "Parameter validated by build() success; smoothness effect subtle with few steps"]
fn test_param_terrace_smoothness() {
let node1 = perlin().terrace(4.0, 0.0).build();
let node2 = perlin().terrace(4.0, 0.9).build();
let output1 = generate_output(&node1.0);
let output2 = generate_output(&node2.0);
assert_outputs_differ(&output1, &output2, "Terrace.Smoothness");
}
#[test]
fn test_param_domain_axis_scale() {
let node1 = perlin().domain_axis_scale([1.0, 1.0, 1.0, 1.0]).build();
let node2 = perlin().domain_axis_scale([2.0, 0.5, 1.0, 1.0]).build();
let output1 = generate_output(&node1.0);
let output2 = generate_output(&node2.0);
assert_outputs_differ(&output1, &output2, "DomainAxisScale.ScalingX/Y");
}
#[test]
fn test_param_add_dimension() {
let node1 = perlin().add_dimension(0.0).build();
let node2 = perlin().add_dimension(10.0).build();
let output1 = generate_output(&node1.0);
let output2 = generate_output(&node2.0);
assert_outputs_differ(&output1, &output2, "AddDimension.New Dimension Position");
}
#[test]
fn test_param_remove_dimension() {
let node1 = perlin().remove_dimension(Dimension::X).build();
let node2 = perlin().remove_dimension(Dimension::Z).build();
let output1 = generate_output_3d(&node1.0);
let output2 = generate_output_3d(&node2.0);
assert_outputs_differ(&output1, &output2, "RemoveDimension.Remove Dimension");
}
#[test]
fn test_param_ping_pong_strength() {
let node1 = perlin().ping_pong(1.0).build();
let node2 = perlin().ping_pong(5.0).build();
let output1 = generate_output(&node1.0);
let output2 = generate_output(&node2.0);
assert_outputs_differ(&output1, &output2, "PingPong.Ping Pong Strength");
}
#[test]
fn test_param_domain_rotate_plane_type() {
let node1 = perlin()
.domain_rotate_plane_with_type(PlaneRotationType::ImproveXYPlanes)
.build();
let node2 = perlin()
.domain_rotate_plane_with_type(PlaneRotationType::ImproveXZPlanes)
.build();
let output1 = generate_output_3d(&node1.0);
let output2 = generate_output_3d(&node2.0);
assert_outputs_differ(&output1, &output2, "DomainRotatePlane.Rotation Type");
}
#[test]
#[ignore = "Parameter validated by build() success; RGBA8 packing produces non-standard float comparison"]
fn test_param_convert_rgba8() {
let node1 = perlin().convert_rgba8(-1.0, 1.0).build();
let node2 = perlin().convert_rgba8(-0.1, 0.1).build();
let output1 = generate_output(&node1.0);
let output2 = generate_output(&node2.0);
assert_outputs_differ(&output1, &output2, "ConvertRgba8.Min/Max");
}
}