eml-stdlib 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eml_stdlib/__init__.py +81 -0
- eml_stdlib/ballistics/air_density.eml +73 -0
- eml_stdlib/ballistics/ballistic_solver.eml +102 -0
- eml_stdlib/ballistics/coriolis.eml +60 -0
- eml_stdlib/ballistics/drag.eml +44 -0
- eml_stdlib/ballistics/gravity.eml +52 -0
- eml_stdlib/ballistics/muzzle_velocity.eml +42 -0
- eml_stdlib/ballistics/spin_drift.eml +60 -0
- eml_stdlib/ballistics/time_of_flight.eml +79 -0
- eml_stdlib/ballistics/wind.eml +58 -0
- eml_stdlib/biology/abilities/ballistic_armor.eml +61 -0
- eml_stdlib/biology/abilities/bite_force.eml +48 -0
- eml_stdlib/biology/abilities/chromatophore.eml +50 -0
- eml_stdlib/biology/abilities/discharge.eml +45 -0
- eml_stdlib/biology/abilities/elastic_energy.eml +42 -0
- eml_stdlib/biology/abilities/locomotion.eml +50 -0
- eml_stdlib/biology/abilities/muscle_power.eml +47 -0
- eml_stdlib/biology/abilities/sprint.eml +36 -0
- eml_stdlib/biology/abilities/texture_match.eml +49 -0
- eml_stdlib/biology/abilities/toxin.eml +44 -0
- eml_stdlib/biology/cone_l.eml +36 -0
- eml_stdlib/biology/cone_s.eml +32 -0
- eml_stdlib/biology/cryptochrome.eml +44 -0
- eml_stdlib/biology/geomagnetic_field.eml +32 -0
- eml_stdlib/biology/goldman_equation.eml +41 -0
- eml_stdlib/biology/hill_equation.eml +65 -0
- eml_stdlib/biology/hodgkin_huxley.eml +108 -0
- eml_stdlib/biology/michaelis_menten.eml +65 -0
- eml_stdlib/biology/plume_diffusion.eml +47 -0
- eml_stdlib/biology/receptor_binding.eml +35 -0
- eml_stdlib/biology/rod_sensitivity.eml +38 -0
- eml_stdlib/biology/tapetum.eml +70 -0
- eml_stdlib/carriers/acoustic_cloak.eml +56 -0
- eml_stdlib/carriers/ferron_emission.eml +55 -0
- eml_stdlib/carriers/ferron_logic.eml +45 -0
- eml_stdlib/carriers/ferron_propagation.eml +69 -0
- eml_stdlib/carriers/mach_zehnder.eml +52 -0
- eml_stdlib/carriers/magnon_dispersion.eml +57 -0
- eml_stdlib/carriers/magnon_logic.eml +45 -0
- eml_stdlib/carriers/optical_neuron.eml +55 -0
- eml_stdlib/carriers/phonon_bandgap.eml +54 -0
- eml_stdlib/carriers/ring_resonator.eml +48 -0
- eml_stdlib/carriers/spin_torque.eml +61 -0
- eml_stdlib/carriers/thermal_rectifier.eml +63 -0
- eml_stdlib/catalog.json +4209 -0
- eml_stdlib/circuits/buck_converter.eml +74 -0
- eml_stdlib/circuits/cmos_inverter.eml +75 -0
- eml_stdlib/circuits/h_bridge.eml +69 -0
- eml_stdlib/circuits/ldo.eml +66 -0
- eml_stdlib/circuits/mosfet_iv.eml +59 -0
- eml_stdlib/circuits/pll_loop.eml +63 -0
- eml_stdlib/circuits/rc_filter.eml +98 -0
- eml_stdlib/circuits/voltage_divider.eml +68 -0
- eml_stdlib/control/bangbang.eml +34 -0
- eml_stdlib/control/deadband.eml +28 -0
- eml_stdlib/control/hysteresis.eml +28 -0
- eml_stdlib/control/kalman_1d.eml +53 -0
- eml_stdlib/control/lqr_1d.eml +47 -0
- eml_stdlib/control/pid.eml +38 -0
- eml_stdlib/gaming/animation/bounce.eml +39 -0
- eml_stdlib/gaming/animation/ease_in.eml +27 -0
- eml_stdlib/gaming/animation/ease_in_out.eml +29 -0
- eml_stdlib/gaming/animation/ease_out.eml +27 -0
- eml_stdlib/gaming/animation/ik_2bone.eml +57 -0
- eml_stdlib/gaming/animation/spring.eml +39 -0
- eml_stdlib/gaming/audio/distance_attenuation.eml +50 -0
- eml_stdlib/gaming/audio/doppler.eml +39 -0
- eml_stdlib/gaming/audio/lowpass_audio.eml +32 -0
- eml_stdlib/gaming/audio/reverb_delay.eml +41 -0
- eml_stdlib/gaming/camera/dof_blur.eml +30 -0
- eml_stdlib/gaming/camera/fov_projection.eml +47 -0
- eml_stdlib/gaming/camera/orbit.eml +48 -0
- eml_stdlib/gaming/camera/shake.eml +20 -0
- eml_stdlib/gaming/lighting/ambient_occlusion.eml +40 -0
- eml_stdlib/gaming/lighting/area_light.eml +50 -0
- eml_stdlib/gaming/lighting/fog_exp.eml +28 -0
- eml_stdlib/gaming/lighting/fog_exp2.eml +30 -0
- eml_stdlib/gaming/lighting/point_light.eml +33 -0
- eml_stdlib/gaming/lighting/spot_light.eml +43 -0
- eml_stdlib/gaming/noise/fbm.eml +65 -0
- eml_stdlib/gaming/noise/perlin_2d.eml +65 -0
- eml_stdlib/gaming/noise/perlin_3d.eml +63 -0
- eml_stdlib/gaming/noise/simplex_2d.eml +67 -0
- eml_stdlib/gaming/noise/turbulence.eml +46 -0
- eml_stdlib/gaming/noise/value_noise.eml +45 -0
- eml_stdlib/gaming/noise/voronoi_2d.eml +83 -0
- eml_stdlib/gaming/noise/white_noise.eml +47 -0
- eml_stdlib/gaming/noise/worley.eml +79 -0
- eml_stdlib/gaming/particles/drag.eml +30 -0
- eml_stdlib/gaming/particles/emitter_radial.eml +35 -0
- eml_stdlib/gaming/particles/fade.eml +36 -0
- eml_stdlib/gaming/particles/gravity_particle.eml +32 -0
- eml_stdlib/gaming/particles/size_over_life.eml +28 -0
- eml_stdlib/gaming/physics/aabb_overlap.eml +38 -0
- eml_stdlib/gaming/physics/buoyancy.eml +43 -0
- eml_stdlib/gaming/physics/circle_collision.eml +46 -0
- eml_stdlib/gaming/physics/impulse_resolve.eml +39 -0
- eml_stdlib/gaming/physics/rigid_body_2d.eml +61 -0
- eml_stdlib/gaming/physics/spring_damper.eml +30 -0
- eml_stdlib/gaming/physics/verlet.eml +29 -0
- eml_stdlib/gaming/shading/fresnel.eml +57 -0
- eml_stdlib/gaming/shading/matcap.eml +34 -0
- eml_stdlib/gaming/shading/pbr_diffuse.eml +41 -0
- eml_stdlib/gaming/shading/pbr_specular.eml +75 -0
- eml_stdlib/gaming/shading/subsurface.eml +52 -0
- eml_stdlib/gaming/shading/toon.eml +30 -0
- eml_stdlib/gaming/terrain/biome_select.eml +41 -0
- eml_stdlib/gaming/terrain/cliff_detect.eml +35 -0
- eml_stdlib/gaming/terrain/erosion_hydraulic.eml +57 -0
- eml_stdlib/gaming/terrain/erosion_thermal.eml +44 -0
- eml_stdlib/gaming/terrain/heightmap.eml +33 -0
- eml_stdlib/gaming/textures/brick.eml +27 -0
- eml_stdlib/gaming/textures/caustics.eml +25 -0
- eml_stdlib/gaming/textures/checkerboard.eml +27 -0
- eml_stdlib/gaming/textures/fire.eml +26 -0
- eml_stdlib/gaming/textures/marble.eml +24 -0
- eml_stdlib/gaming/textures/rust.eml +23 -0
- eml_stdlib/gaming/textures/water_surface.eml +49 -0
- eml_stdlib/gaming/textures/wood.eml +41 -0
- eml_stdlib/math/abs.eml +45 -0
- eml_stdlib/math/atan.eml +32 -0
- eml_stdlib/math/atan2.eml +61 -0
- eml_stdlib/math/bessel_j0.eml +65 -0
- eml_stdlib/math/cos.eml +42 -0
- eml_stdlib/math/cosh.eml +24 -0
- eml_stdlib/math/erf.eml +66 -0
- eml_stdlib/math/exp.eml +49 -0
- eml_stdlib/math/gamma.eml +55 -0
- eml_stdlib/math/gaussian.eml +18 -0
- eml_stdlib/math/log.eml +59 -0
- eml_stdlib/math/pow.eml +51 -0
- eml_stdlib/math/relu.eml +63 -0
- eml_stdlib/math/sigmoid.eml +21 -0
- eml_stdlib/math/sin.eml +37 -0
- eml_stdlib/math/sinh.eml +24 -0
- eml_stdlib/math/softmax.eml +87 -0
- eml_stdlib/math/sqrt.eml +46 -0
- eml_stdlib/math/tan.eml +36 -0
- eml_stdlib/math/tanh.eml +54 -0
- eml_stdlib/ml/attention.eml +70 -0
- eml_stdlib/ml/cross_entropy.eml +85 -0
- eml_stdlib/ml/gelu.eml +51 -0
- eml_stdlib/ml/layernorm.eml +49 -0
- eml_stdlib/ml/leaky_relu.eml +51 -0
- eml_stdlib/ml/relu.eml +63 -0
- eml_stdlib/ml/rotary_embed.eml +64 -0
- eml_stdlib/ml/silu.eml +40 -0
- eml_stdlib/ml/softmax.eml +87 -0
- eml_stdlib/physics/coulomb.eml +48 -0
- eml_stdlib/physics/damped_oscillator.eml +20 -0
- eml_stdlib/physics/diffusion.eml +45 -0
- eml_stdlib/physics/gravity.eml +60 -0
- eml_stdlib/physics/hooke.eml +56 -0
- eml_stdlib/physics/navier_stokes_1d.eml +57 -0
- eml_stdlib/physics/planck_radiation.eml +77 -0
- eml_stdlib/physics/stefan_boltzmann.eml +75 -0
- eml_stdlib/physics/wave_propagation.eml +52 -0
- eml_stdlib/quantum/cnot.eml +72 -0
- eml_stdlib/quantum/grover_oracle.eml +69 -0
- eml_stdlib/quantum/hadamard.eml +73 -0
- eml_stdlib/quantum/pauli_x.eml +29 -0
- eml_stdlib/quantum/pauli_z.eml +29 -0
- eml_stdlib/quantum/phase_gate.eml +76 -0
- eml_stdlib/quantum/rotation_rz.eml +92 -0
- eml_stdlib/sensors/accelerometer.eml +59 -0
- eml_stdlib/sensors/hall_effect.eml +42 -0
- eml_stdlib/sensors/photodetector.eml +58 -0
- eml_stdlib/sensors/rtd.eml +57 -0
- eml_stdlib/sensors/strain_gauge.eml +63 -0
- eml_stdlib/sensors/thermistor.eml +71 -0
- eml_stdlib/signal/bandpass.eml +39 -0
- eml_stdlib/signal/chirp.eml +42 -0
- eml_stdlib/signal/dft_single.eml +30 -0
- eml_stdlib/signal/fft_butterfly.eml +36 -0
- eml_stdlib/signal/highpass.eml +45 -0
- eml_stdlib/signal/lowpass.eml +49 -0
- eml_stdlib/signal/matched_filter.eml +48 -0
- eml_stdlib/signal/notch_filter.eml +47 -0
- eml_stdlib/signal/window_hamming.eml +47 -0
- eml_stdlib/signal/window_hann.eml +50 -0
- eml_stdlib-0.4.0.dist-info/METADATA +453 -0
- eml_stdlib-0.4.0.dist-info/RECORD +185 -0
- eml_stdlib-0.4.0.dist-info/WHEEL +5 -0
- eml_stdlib-0.4.0.dist-info/licenses/LICENSE +21 -0
- eml_stdlib-0.4.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// ambient_occlusion.eml -- hemisphere-sampling AO approximation.
|
|
2
|
+
//
|
|
3
|
+
// SSAO-style scalar: how much of the upper hemisphere is occluded
|
|
4
|
+
// by nearby geometry. The kernel here computes a per-sample
|
|
5
|
+
// occlusion contribution (occluded if the sample is behind the
|
|
6
|
+
// surface plane), letting the caller average across the kernel.
|
|
7
|
+
|
|
8
|
+
module ambient_occlusion;
|
|
9
|
+
|
|
10
|
+
// Single-sample contribution: returns 1.0 if occluded, 0.0 if
|
|
11
|
+
// visible. `sample_depth` and `surface_depth` are world-space
|
|
12
|
+
// depths; `radius` is the AO kernel radius for range-fading.
|
|
13
|
+
@verify(lean, theorem = "ao_sample_in_unit_interval")
|
|
14
|
+
fn sample_contribution(sample_depth: Real, surface_depth: Real,
|
|
15
|
+
radius: Real) -> Real
|
|
16
|
+
where chain_order <= 0,
|
|
17
|
+
domain: radius > 0.0
|
|
18
|
+
ensures (result >= 0.0)
|
|
19
|
+
ensures (result <= 1.0)
|
|
20
|
+
{
|
|
21
|
+
if sample_depth >= surface_depth {
|
|
22
|
+
0.0
|
|
23
|
+
} else {
|
|
24
|
+
// Range fade: closer occluders count more.
|
|
25
|
+
let range: Real = clamp(1.0 - (surface_depth - sample_depth) / radius, 0.0, 1.0);
|
|
26
|
+
range
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Final AO factor (1 = fully lit, 0 = fully occluded).
|
|
31
|
+
@verify(lean, theorem = "ao_factor_in_unit_interval")
|
|
32
|
+
fn ao_factor(occlusion_sum: Real, num_samples: Real) -> Real
|
|
33
|
+
where chain_order <= 0,
|
|
34
|
+
domain: num_samples > 0.0
|
|
35
|
+
requires (occlusion_sum >= 0.0)
|
|
36
|
+
ensures (result >= 0.0)
|
|
37
|
+
ensures (result <= 1.0)
|
|
38
|
+
{
|
|
39
|
+
clamp(1.0 - occlusion_sum / num_samples, 0.0, 1.0)
|
|
40
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// area_light.eml -- "Most Representative Point" approximation.
|
|
2
|
+
//
|
|
3
|
+
// For a disc / sphere / quad area light, the cheap real-time trick
|
|
4
|
+
// is to pick the single point on the area that best represents
|
|
5
|
+
// the light's contribution to a given surface point — usually the
|
|
6
|
+
// closest point on the area to the reflection ray. We then shade
|
|
7
|
+
// using that point as a single-point light.
|
|
8
|
+
//
|
|
9
|
+
// This module ships only the closest-point math; the shading then
|
|
10
|
+
// uses point_light + cook_torrance.
|
|
11
|
+
|
|
12
|
+
module area_light;
|
|
13
|
+
|
|
14
|
+
@verify(lean, theorem = "closest_on_sphere_within_radius")
|
|
15
|
+
fn closest_on_sphere(center_x: Real, center_y: Real, center_z: Real,
|
|
16
|
+
radius_m: Real,
|
|
17
|
+
ref_x: Real, ref_y: Real, ref_z: Real) -> Real
|
|
18
|
+
where chain_order <= 1,
|
|
19
|
+
domain: radius_m >= 0.0
|
|
20
|
+
ensures (result >= 0.0)
|
|
21
|
+
{
|
|
22
|
+
// We return the magnitude of the offset from the centre to the
|
|
23
|
+
// reference, clamped to the sphere's radius. The full vector is
|
|
24
|
+
// reconstructed by the caller from the unit direction × this
|
|
25
|
+
// magnitude — keeping this kernel scalar.
|
|
26
|
+
let dx: Real = ref_x - center_x;
|
|
27
|
+
let dy: Real = ref_y - center_y;
|
|
28
|
+
let dz: Real = ref_z - center_z;
|
|
29
|
+
let d: Real = sqrt(dx * dx + dy * dy + dz * dz);
|
|
30
|
+
clamp(d, 0.0, radius_m)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Solid-angle attenuation for a sphere area light: I_received
|
|
34
|
+
// scales with the visible portion of the sphere from the surface
|
|
35
|
+
// point. Closed form for a sphere of radius R at distance D:
|
|
36
|
+
//
|
|
37
|
+
// solid_angle = 2π * (1 - sqrt(1 - (R/D)²))
|
|
38
|
+
//
|
|
39
|
+
// Returns the fraction of the hemisphere covered, ∈ [0, 1].
|
|
40
|
+
@verify(lean, theorem = "sphere_solid_angle_in_unit_interval")
|
|
41
|
+
fn solid_angle_fraction(radius_m: Real, distance_m: Real) -> Real
|
|
42
|
+
where chain_order <= 1,
|
|
43
|
+
domain: radius_m >= 0.0 && distance_m > 0.0
|
|
44
|
+
ensures (result >= 0.0)
|
|
45
|
+
ensures (result <= 1.0)
|
|
46
|
+
{
|
|
47
|
+
let ratio: Real = clamp(radius_m / distance_m, 0.0, 1.0);
|
|
48
|
+
let cos_half_angle: Real = sqrt(1.0 - ratio * ratio);
|
|
49
|
+
clamp(0.5 * (1.0 - cos_half_angle), 0.0, 1.0)
|
|
50
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// fog_exp.eml -- exponential fog (Beer's law).
|
|
2
|
+
//
|
|
3
|
+
// transmittance(d) = exp(-density * d)
|
|
4
|
+
// fog_factor = 1 - transmittance
|
|
5
|
+
//
|
|
6
|
+
// Output ∈ [0, 1]: 0 = no fog, 1 = fully obscured.
|
|
7
|
+
|
|
8
|
+
module fog_exp;
|
|
9
|
+
|
|
10
|
+
@verify(lean, theorem = "fog_exp_in_unit_interval")
|
|
11
|
+
fn fog_factor(density: Real, distance_m: Real) -> Real
|
|
12
|
+
where chain_order <= 1,
|
|
13
|
+
domain: density >= 0.0 && distance_m >= 0.0
|
|
14
|
+
ensures (result >= 0.0)
|
|
15
|
+
ensures (result <= 1.0)
|
|
16
|
+
{
|
|
17
|
+
1.0 - exp(-density * distance_m)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@verify(lean, theorem = "fog_exp_transmittance_in_unit_interval")
|
|
21
|
+
fn transmittance(density: Real, distance_m: Real) -> Real
|
|
22
|
+
where chain_order <= 1,
|
|
23
|
+
domain: density >= 0.0 && distance_m >= 0.0
|
|
24
|
+
ensures (result >= 0.0)
|
|
25
|
+
ensures (result <= 1.0)
|
|
26
|
+
{
|
|
27
|
+
exp(-density * distance_m)
|
|
28
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// fog_exp2.eml -- squared-exponential fog.
|
|
2
|
+
//
|
|
3
|
+
// transmittance(d) = exp(-(density * d)²)
|
|
4
|
+
//
|
|
5
|
+
// Sharper transition than fog_exp; favoured by older fixed-function
|
|
6
|
+
// pipelines. Output ∈ [0, 1].
|
|
7
|
+
|
|
8
|
+
module fog_exp2;
|
|
9
|
+
|
|
10
|
+
@verify(lean, theorem = "fog_exp2_in_unit_interval")
|
|
11
|
+
fn fog_factor(density: Real, distance_m: Real) -> Real
|
|
12
|
+
where chain_order <= 1,
|
|
13
|
+
domain: density >= 0.0 && distance_m >= 0.0
|
|
14
|
+
ensures (result >= 0.0)
|
|
15
|
+
ensures (result <= 1.0)
|
|
16
|
+
{
|
|
17
|
+
let d: Real = density * distance_m;
|
|
18
|
+
1.0 - exp(-(d * d))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@verify(lean, theorem = "fog_exp2_transmittance_in_unit_interval")
|
|
22
|
+
fn transmittance(density: Real, distance_m: Real) -> Real
|
|
23
|
+
where chain_order <= 1,
|
|
24
|
+
domain: density >= 0.0 && distance_m >= 0.0
|
|
25
|
+
ensures (result >= 0.0)
|
|
26
|
+
ensures (result <= 1.0)
|
|
27
|
+
{
|
|
28
|
+
let d: Real = density * distance_m;
|
|
29
|
+
exp(-(d * d))
|
|
30
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// point_light.eml -- inverse-square falloff with radius clamp.
|
|
2
|
+
//
|
|
3
|
+
// Physical: I(d) = I_0 / d²
|
|
4
|
+
// Game: clamp by a "max range" so distant lights don't add
|
|
5
|
+
// vanishingly small contributions.
|
|
6
|
+
//
|
|
7
|
+
// attenuation(d, r) = (1 / (1 + (d/r)²))² * smooth_cutoff(d/r)
|
|
8
|
+
|
|
9
|
+
module point_light;
|
|
10
|
+
|
|
11
|
+
@verify(lean, theorem = "point_light_attenuation_in_unit_interval")
|
|
12
|
+
fn attenuation(distance_m: Real, range_m: Real) -> Real
|
|
13
|
+
where chain_order <= 0,
|
|
14
|
+
domain: range_m > 0.0 && distance_m >= 0.0
|
|
15
|
+
ensures (result >= 0.0)
|
|
16
|
+
ensures (result <= 1.0)
|
|
17
|
+
{
|
|
18
|
+
let dr: Real = distance_m / range_m;
|
|
19
|
+
let inv: Real = 1.0 / (1.0 + dr * dr * 4.0);
|
|
20
|
+
let smooth: Real = clamp(1.0 - dr * dr * dr * dr, 0.0, 1.0);
|
|
21
|
+
clamp(inv * inv * smooth, 0.0, 1.0)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Pure 1/r² for physically-based falloff (no clamp). Caller is
|
|
25
|
+
// responsible for not querying at d = 0.
|
|
26
|
+
@verify(lean, theorem = "point_light_inv_sq_positive")
|
|
27
|
+
fn inverse_square(distance_m: Real, intensity: Real) -> Real
|
|
28
|
+
where chain_order <= 0,
|
|
29
|
+
domain: distance_m > 0.0 && intensity >= 0.0
|
|
30
|
+
ensures (result >= 0.0)
|
|
31
|
+
{
|
|
32
|
+
intensity / (distance_m * distance_m)
|
|
33
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// spot_light.eml -- point light × cone attenuation.
|
|
2
|
+
//
|
|
3
|
+
// spot(d, r, cos θ, cos_inner, cos_outer)
|
|
4
|
+
// = point_attenuation(d, r) * smooth_cone(cos θ, cos_inner, cos_outer)
|
|
5
|
+
//
|
|
6
|
+
// `cos θ` is the angle between the light's forward direction and
|
|
7
|
+
// the vector to the lit surface. `cos_inner` and `cos_outer` define
|
|
8
|
+
// the falloff cone.
|
|
9
|
+
|
|
10
|
+
module spot_light;
|
|
11
|
+
|
|
12
|
+
use stdlib::gaming::lighting::point_light;
|
|
13
|
+
|
|
14
|
+
@verify(lean, theorem = "spot_cone_in_unit_interval")
|
|
15
|
+
fn cone_attenuation(cos_theta: Real, cos_inner: Real, cos_outer: Real) -> Real
|
|
16
|
+
where chain_order <= 0
|
|
17
|
+
requires (cos_inner >= cos_outer)
|
|
18
|
+
ensures (result >= 0.0)
|
|
19
|
+
ensures (result <= 1.0)
|
|
20
|
+
{
|
|
21
|
+
if cos_theta >= cos_inner {
|
|
22
|
+
1.0
|
|
23
|
+
} else if cos_theta <= cos_outer {
|
|
24
|
+
0.0
|
|
25
|
+
} else {
|
|
26
|
+
let t: Real = (cos_theta - cos_outer) / (cos_inner - cos_outer + 1.0e-7);
|
|
27
|
+
let s: Real = t * t * (3.0 - 2.0 * t);
|
|
28
|
+
clamp(s, 0.0, 1.0)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@verify(lean, theorem = "spot_total_in_unit_interval")
|
|
33
|
+
fn attenuation(distance_m: Real, range_m: Real,
|
|
34
|
+
cos_theta: Real, cos_inner: Real, cos_outer: Real) -> Real
|
|
35
|
+
where chain_order <= 0,
|
|
36
|
+
domain: range_m > 0.0
|
|
37
|
+
requires (cos_inner >= cos_outer)
|
|
38
|
+
ensures (result >= 0.0)
|
|
39
|
+
ensures (result <= 1.0)
|
|
40
|
+
{
|
|
41
|
+
point_light::attenuation(distance_m, range_m)
|
|
42
|
+
* cone_attenuation(cos_theta, cos_inner, cos_outer)
|
|
43
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// fbm.eml -- fractal Brownian motion (octave stacker).
|
|
2
|
+
//
|
|
3
|
+
// Sums multiple octaves of a base noise function, each at twice
|
|
4
|
+
// the frequency and half the amplitude of the previous:
|
|
5
|
+
//
|
|
6
|
+
// fbm(p) = Σ_{i=0..N-1} amplitude_i * noise(p * frequency_i)
|
|
7
|
+
// amplitude_i = persistence^i, frequency_i = lacunarity^i
|
|
8
|
+
//
|
|
9
|
+
// Standard parameters: persistence = 0.5, lacunarity = 2.0, N = 6.
|
|
10
|
+
//
|
|
11
|
+
// Output is normalised to [-1, 1]. Chain order matches the base
|
|
12
|
+
// noise (1 for Perlin) since octaves compose additively.
|
|
13
|
+
|
|
14
|
+
module fbm;
|
|
15
|
+
|
|
16
|
+
use stdlib::gaming::noise::perlin_2d;
|
|
17
|
+
|
|
18
|
+
const PERSISTENCE_DEFAULT: Real = 0.5
|
|
19
|
+
const LACUNARITY_DEFAULT: Real = 2.0
|
|
20
|
+
const OCTAVES_DEFAULT: Real = 6.0
|
|
21
|
+
|
|
22
|
+
@verify(lean, theorem = "fbm_in_signed_unit_band")
|
|
23
|
+
fn fbm2(x: Real, y: Real, octaves: Real,
|
|
24
|
+
persistence: Real, lacunarity: Real) -> Real
|
|
25
|
+
where chain_order <= 1,
|
|
26
|
+
domain: octaves >= 1.0 && persistence > 0.0 && lacunarity > 1.0
|
|
27
|
+
ensures (result >= -1.0)
|
|
28
|
+
ensures (result <= 1.0)
|
|
29
|
+
{
|
|
30
|
+
// Unrolled fixed-octave sum. The Forge optimiser folds
|
|
31
|
+
// persistence^i and lacunarity^i to constants at compile time.
|
|
32
|
+
let p0: Real = perlin_2d::perlin2(x, y);
|
|
33
|
+
let p1: Real = perlin_2d::perlin2(x * lacunarity, y * lacunarity);
|
|
34
|
+
let p2: Real = perlin_2d::perlin2(x * lacunarity * lacunarity,
|
|
35
|
+
y * lacunarity * lacunarity);
|
|
36
|
+
let p3: Real = perlin_2d::perlin2(x * lacunarity * lacunarity * lacunarity,
|
|
37
|
+
y * lacunarity * lacunarity * lacunarity);
|
|
38
|
+
let a0: Real = 1.0;
|
|
39
|
+
let a1: Real = persistence;
|
|
40
|
+
let a2: Real = persistence * persistence;
|
|
41
|
+
let a3: Real = persistence * persistence * persistence;
|
|
42
|
+
let total: Real = a0 + a1 + a2 + a3;
|
|
43
|
+
let raw: Real = (a0 * p0 + a1 * p1 + a2 * p2 + a3 * p3) / total;
|
|
44
|
+
clamp(raw, -1.0, 1.0)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Default-parameters convenience.
|
|
48
|
+
@verify(lean, theorem = "fbm_default_in_signed_unit_band")
|
|
49
|
+
fn fbm2_default(x: Real, y: Real) -> Real
|
|
50
|
+
where chain_order <= 1
|
|
51
|
+
ensures (result >= -1.0)
|
|
52
|
+
ensures (result <= 1.0)
|
|
53
|
+
{
|
|
54
|
+
fbm2(x, y, OCTAVES_DEFAULT, PERSISTENCE_DEFAULT, LACUNARITY_DEFAULT)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 0..1 unit-interval scaling for direct UV use.
|
|
58
|
+
@verify(lean, theorem = "fbm_unit_in_unit_interval")
|
|
59
|
+
fn fbm2_unit(x: Real, y: Real) -> Real
|
|
60
|
+
where chain_order <= 1
|
|
61
|
+
ensures (result >= 0.0)
|
|
62
|
+
ensures (result <= 1.0)
|
|
63
|
+
{
|
|
64
|
+
0.5 * (fbm2_default(x, y) + 1.0)
|
|
65
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// perlin_2d.eml -- classic 2D Perlin noise.
|
|
2
|
+
//
|
|
3
|
+
// Lattice-based gradient noise. At each integer-grid corner we
|
|
4
|
+
// pick a pseudo-random gradient vector; the noise value at point
|
|
5
|
+
// p is the smoothstep-blended dot product of (p - corner) with
|
|
6
|
+
// each corner's gradient. Output is in [-1, 1] (typically scaled
|
|
7
|
+
// to [0, 1] by the caller via 0.5 * (n + 1)).
|
|
8
|
+
//
|
|
9
|
+
// Chain order 1.
|
|
10
|
+
|
|
11
|
+
module perlin_2d;
|
|
12
|
+
|
|
13
|
+
use stdlib::gaming::noise::white_noise;
|
|
14
|
+
|
|
15
|
+
// Pseudo-gradient: pick one of 4 unit-axis vectors based on the
|
|
16
|
+
// hash. Using axis-aligned gradients keeps chain order at 1 (one
|
|
17
|
+
// trig call inside white_noise::hash2). For higher quality switch
|
|
18
|
+
// to the 8-direction Perlin gradient set; the shape of the proof
|
|
19
|
+
// obligation is identical.
|
|
20
|
+
fn _grad_dot(xi: Real, yi: Real, xf: Real, yf: Real) -> Real
|
|
21
|
+
where chain_order <= 1
|
|
22
|
+
{
|
|
23
|
+
let h: Real = white_noise::hash2(xi, yi);
|
|
24
|
+
let q: Real = floor(h * 4.0); // 0..3
|
|
25
|
+
// 0=+x, 1=+y, 2=-x, 3=-y
|
|
26
|
+
if q < 1.0 { xf }
|
|
27
|
+
else if q < 2.0 { yf }
|
|
28
|
+
else if q < 3.0 { -xf }
|
|
29
|
+
else { -yf }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@verify(lean, theorem = "perlin2_in_signed_unit_band")
|
|
33
|
+
fn perlin2(x: Real, y: Real) -> Real
|
|
34
|
+
where chain_order <= 1
|
|
35
|
+
ensures (result >= -1.0)
|
|
36
|
+
ensures (result <= 1.0)
|
|
37
|
+
{
|
|
38
|
+
let xi: Real = floor(x);
|
|
39
|
+
let yi: Real = floor(y);
|
|
40
|
+
let xf: Real = x - xi;
|
|
41
|
+
let yf: Real = y - yi;
|
|
42
|
+
|
|
43
|
+
let n00: Real = _grad_dot(xi, yi, xf, yf);
|
|
44
|
+
let n10: Real = _grad_dot(xi + 1.0, yi, xf - 1.0, yf);
|
|
45
|
+
let n01: Real = _grad_dot(xi, yi + 1.0, xf, yf - 1.0);
|
|
46
|
+
let n11: Real = _grad_dot(xi + 1.0, yi + 1.0, xf - 1.0, yf - 1.0);
|
|
47
|
+
|
|
48
|
+
// Quintic ease (Ken Perlin's improved smoothstep).
|
|
49
|
+
let u: Real = xf * xf * xf * (xf * (xf * 6.0 - 15.0) + 10.0);
|
|
50
|
+
let v: Real = yf * yf * yf * (yf * (yf * 6.0 - 15.0) + 10.0);
|
|
51
|
+
|
|
52
|
+
let l0: Real = n00 + u * (n10 - n00);
|
|
53
|
+
let l1: Real = n01 + u * (n11 - n01);
|
|
54
|
+
clamp(l0 + v * (l1 - l0), -1.0, 1.0)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 0..1 scaled variant for height-map / texture authoring.
|
|
58
|
+
@verify(lean, theorem = "perlin2_unit_in_unit_interval")
|
|
59
|
+
fn perlin2_unit(x: Real, y: Real) -> Real
|
|
60
|
+
where chain_order <= 1
|
|
61
|
+
ensures (result >= 0.0)
|
|
62
|
+
ensures (result <= 1.0)
|
|
63
|
+
{
|
|
64
|
+
0.5 * (perlin2(x, y) + 1.0)
|
|
65
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// perlin_3d.eml -- 3D Perlin noise for volumetric effects.
|
|
2
|
+
//
|
|
3
|
+
// Same recipe as perlin_2d but on the cubic lattice; 8 corner
|
|
4
|
+
// gradients blended via the quintic-smoothstep weights along three
|
|
5
|
+
// axes. Used for 3D fog, cloud volumes, and time-varying surface
|
|
6
|
+
// noise (treat the third axis as time).
|
|
7
|
+
|
|
8
|
+
module perlin_3d;
|
|
9
|
+
|
|
10
|
+
use stdlib::gaming::noise::white_noise;
|
|
11
|
+
|
|
12
|
+
fn _grad_dot3(xi: Real, yi: Real, zi: Real,
|
|
13
|
+
xf: Real, yf: Real, zf: Real) -> Real
|
|
14
|
+
where chain_order <= 1
|
|
15
|
+
{
|
|
16
|
+
let h: Real = white_noise::hash3(xi, yi, zi);
|
|
17
|
+
let q: Real = floor(h * 6.0); // 0..5
|
|
18
|
+
// 6-direction axis-aligned gradient set.
|
|
19
|
+
if q < 1.0 { xf }
|
|
20
|
+
else if q < 2.0 { -xf }
|
|
21
|
+
else if q < 3.0 { yf }
|
|
22
|
+
else if q < 4.0 { -yf }
|
|
23
|
+
else if q < 5.0 { zf }
|
|
24
|
+
else { -zf }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@verify(lean, theorem = "perlin3_in_signed_unit_band")
|
|
28
|
+
fn perlin3(x: Real, y: Real, z: Real) -> Real
|
|
29
|
+
where chain_order <= 1
|
|
30
|
+
ensures (result >= -1.0)
|
|
31
|
+
ensures (result <= 1.0)
|
|
32
|
+
{
|
|
33
|
+
let xi: Real = floor(x);
|
|
34
|
+
let yi: Real = floor(y);
|
|
35
|
+
let zi: Real = floor(z);
|
|
36
|
+
let xf: Real = x - xi;
|
|
37
|
+
let yf: Real = y - yi;
|
|
38
|
+
let zf: Real = z - zi;
|
|
39
|
+
|
|
40
|
+
// Quintic ease per axis.
|
|
41
|
+
let u: Real = xf * xf * xf * (xf * (xf * 6.0 - 15.0) + 10.0);
|
|
42
|
+
let v: Real = yf * yf * yf * (yf * (yf * 6.0 - 15.0) + 10.0);
|
|
43
|
+
let w: Real = zf * zf * zf * (zf * (zf * 6.0 - 15.0) + 10.0);
|
|
44
|
+
|
|
45
|
+
let n000: Real = _grad_dot3(xi, yi, zi, xf, yf, zf);
|
|
46
|
+
let n100: Real = _grad_dot3(xi + 1.0, yi, zi, xf - 1.0, yf, zf);
|
|
47
|
+
let n010: Real = _grad_dot3(xi, yi + 1.0, zi, xf, yf - 1.0, zf);
|
|
48
|
+
let n110: Real = _grad_dot3(xi + 1.0, yi + 1.0, zi, xf - 1.0, yf - 1.0, zf);
|
|
49
|
+
let n001: Real = _grad_dot3(xi, yi, zi + 1.0, xf, yf, zf - 1.0);
|
|
50
|
+
let n101: Real = _grad_dot3(xi + 1.0, yi, zi + 1.0, xf - 1.0, yf, zf - 1.0);
|
|
51
|
+
let n011: Real = _grad_dot3(xi, yi + 1.0, zi + 1.0, xf, yf - 1.0, zf - 1.0);
|
|
52
|
+
let n111: Real = _grad_dot3(xi + 1.0, yi + 1.0, zi + 1.0, xf - 1.0, yf - 1.0, zf - 1.0);
|
|
53
|
+
|
|
54
|
+
let x00: Real = n000 + u * (n100 - n000);
|
|
55
|
+
let x10: Real = n010 + u * (n110 - n010);
|
|
56
|
+
let x01: Real = n001 + u * (n101 - n001);
|
|
57
|
+
let x11: Real = n011 + u * (n111 - n011);
|
|
58
|
+
|
|
59
|
+
let y0: Real = x00 + v * (x10 - x00);
|
|
60
|
+
let y1: Real = x01 + v * (x11 - x01);
|
|
61
|
+
|
|
62
|
+
clamp(y0 + w * (y1 - y0), -1.0, 1.0)
|
|
63
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// simplex_2d.eml -- 2D simplex noise.
|
|
2
|
+
//
|
|
3
|
+
// Ken Perlin's simplex noise is faster than Perlin (3 corners vs
|
|
4
|
+
// 4 in 2D, 4 vs 8 in 3D) and free of the directional-axis
|
|
5
|
+
// artefacts the cubic lattice produces.
|
|
6
|
+
//
|
|
7
|
+
// The skewed coordinate transform maps (x, y) into a triangular
|
|
8
|
+
// simplex grid; the contribution from each of the three nearest
|
|
9
|
+
// simplex vertices is summed with a smooth radial falloff.
|
|
10
|
+
//
|
|
11
|
+
// Chain order 1 (sqrt + hash inside white_noise).
|
|
12
|
+
|
|
13
|
+
module simplex_2d;
|
|
14
|
+
|
|
15
|
+
use stdlib::gaming::noise::white_noise;
|
|
16
|
+
|
|
17
|
+
const F2: Real = 0.366025404 // (sqrt(3) - 1) / 2
|
|
18
|
+
const G2: Real = 0.211324865 // (3 - sqrt(3)) / 6
|
|
19
|
+
|
|
20
|
+
fn _grad_dot(xi: Real, yi: Real, dx: Real, dy: Real) -> Real
|
|
21
|
+
where chain_order <= 1
|
|
22
|
+
{
|
|
23
|
+
let h: Real = white_noise::hash2(xi, yi);
|
|
24
|
+
let q: Real = floor(h * 8.0);
|
|
25
|
+
let theta: Real = q * 0.78539816; // π/4 increments → 8 directions
|
|
26
|
+
cos(theta) * dx + sin(theta) * dy
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@verify(lean, theorem = "simplex2_in_signed_unit_band")
|
|
30
|
+
fn simplex2(x: Real, y: Real) -> Real
|
|
31
|
+
where chain_order <= 1
|
|
32
|
+
ensures (result >= -1.0)
|
|
33
|
+
ensures (result <= 1.0)
|
|
34
|
+
{
|
|
35
|
+
// Skew the input space to the simplex grid.
|
|
36
|
+
let s: Real = (x + y) * F2;
|
|
37
|
+
let xi: Real = floor(x + s);
|
|
38
|
+
let yi: Real = floor(y + s);
|
|
39
|
+
let t: Real = (xi + yi) * G2;
|
|
40
|
+
let x0: Real = x - (xi - t);
|
|
41
|
+
let y0: Real = y - (yi - t);
|
|
42
|
+
|
|
43
|
+
// Pick which of two simplex triangles we're in.
|
|
44
|
+
let i1: Real = if x0 > y0 { 1.0 } else { 0.0 };
|
|
45
|
+
let j1: Real = if x0 > y0 { 0.0 } else { 1.0 };
|
|
46
|
+
|
|
47
|
+
let x1: Real = x0 - i1 + G2;
|
|
48
|
+
let y1: Real = y0 - j1 + G2;
|
|
49
|
+
let x2: Real = x0 - 1.0 + 2.0 * G2;
|
|
50
|
+
let y2: Real = y0 - 1.0 + 2.0 * G2;
|
|
51
|
+
|
|
52
|
+
fn _contribution(xi_local: Real, yi_local: Real, dx: Real, dy: Real) -> Real
|
|
53
|
+
where chain_order <= 1
|
|
54
|
+
{
|
|
55
|
+
let t_sq: Real = 0.5 - dx * dx - dy * dy;
|
|
56
|
+
if t_sq <= 0.0 { 0.0 } else {
|
|
57
|
+
let t4: Real = t_sq * t_sq * t_sq * t_sq;
|
|
58
|
+
t4 * _grad_dot(xi_local, yi_local, dx, dy)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let n0: Real = _contribution(xi, yi, x0, y0);
|
|
63
|
+
let n1: Real = _contribution(xi + i1, yi + j1, x1, y1);
|
|
64
|
+
let n2: Real = _contribution(xi + 1.0, yi + 1.0, x2, y2);
|
|
65
|
+
|
|
66
|
+
clamp(70.0 * (n0 + n1 + n2), -1.0, 1.0)
|
|
67
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// turbulence.eml -- absolute-value FBM for cloud / marble veining.
|
|
2
|
+
//
|
|
3
|
+
// Identical to fbm but each octave's contribution is |noise(...)|
|
|
4
|
+
// instead of noise(...). Produces cusped ridges instead of smooth
|
|
5
|
+
// dunes; the visual signature of marble, lightning, and storm-cloud
|
|
6
|
+
// turbulence.
|
|
7
|
+
//
|
|
8
|
+
// Output ∈ [0, 1]. Chain order 1.
|
|
9
|
+
|
|
10
|
+
module turbulence;
|
|
11
|
+
|
|
12
|
+
use stdlib::gaming::noise::perlin_2d;
|
|
13
|
+
|
|
14
|
+
const PERSISTENCE_DEFAULT: Real = 0.5
|
|
15
|
+
const LACUNARITY_DEFAULT: Real = 2.0
|
|
16
|
+
const OCTAVES_DEFAULT: Real = 6.0
|
|
17
|
+
|
|
18
|
+
@verify(lean, theorem = "turbulence_in_unit_interval")
|
|
19
|
+
fn turbulence2(x: Real, y: Real, persistence: Real, lacunarity: Real) -> Real
|
|
20
|
+
where chain_order <= 1,
|
|
21
|
+
domain: persistence > 0.0 && lacunarity > 1.0
|
|
22
|
+
ensures (result >= 0.0)
|
|
23
|
+
ensures (result <= 1.0)
|
|
24
|
+
{
|
|
25
|
+
let p0: Real = abs(perlin_2d::perlin2(x, y));
|
|
26
|
+
let p1: Real = abs(perlin_2d::perlin2(x * lacunarity, y * lacunarity));
|
|
27
|
+
let p2: Real = abs(perlin_2d::perlin2(x * lacunarity * lacunarity,
|
|
28
|
+
y * lacunarity * lacunarity));
|
|
29
|
+
let p3: Real = abs(perlin_2d::perlin2(x * lacunarity * lacunarity * lacunarity,
|
|
30
|
+
y * lacunarity * lacunarity * lacunarity));
|
|
31
|
+
let a0: Real = 1.0;
|
|
32
|
+
let a1: Real = persistence;
|
|
33
|
+
let a2: Real = persistence * persistence;
|
|
34
|
+
let a3: Real = persistence * persistence * persistence;
|
|
35
|
+
let total: Real = a0 + a1 + a2 + a3;
|
|
36
|
+
clamp((a0 * p0 + a1 * p1 + a2 * p2 + a3 * p3) / total, 0.0, 1.0)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@verify(lean, theorem = "turbulence_default_in_unit_interval")
|
|
40
|
+
fn turbulence2_default(x: Real, y: Real) -> Real
|
|
41
|
+
where chain_order <= 1
|
|
42
|
+
ensures (result >= 0.0)
|
|
43
|
+
ensures (result <= 1.0)
|
|
44
|
+
{
|
|
45
|
+
turbulence2(x, y, PERSISTENCE_DEFAULT, LACUNARITY_DEFAULT)
|
|
46
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// value_noise.eml -- interpolated lattice noise.
|
|
2
|
+
//
|
|
3
|
+
// Sample an integer-grid hash at four corners of a 2D cell, then
|
|
4
|
+
// bilinearly interpolate via the smoothstep ease curve so the
|
|
5
|
+
// result is C¹ continuous. Chain order 1 (one fract / smoothstep
|
|
6
|
+
// composition; the lattice hash is already chain-0 internally).
|
|
7
|
+
|
|
8
|
+
module value_noise;
|
|
9
|
+
|
|
10
|
+
use stdlib::gaming::noise::white_noise;
|
|
11
|
+
|
|
12
|
+
@verify(lean, theorem = "value_noise_in_unit_interval")
|
|
13
|
+
fn value2(x: Real, y: Real) -> Real
|
|
14
|
+
where chain_order <= 1
|
|
15
|
+
ensures (result >= 0.0)
|
|
16
|
+
ensures (result <= 1.0)
|
|
17
|
+
{
|
|
18
|
+
let xi: Real = floor(x);
|
|
19
|
+
let yi: Real = floor(y);
|
|
20
|
+
let xf: Real = x - xi;
|
|
21
|
+
let yf: Real = y - yi;
|
|
22
|
+
|
|
23
|
+
let v00: Real = white_noise::hash2(xi, yi);
|
|
24
|
+
let v10: Real = white_noise::hash2(xi + 1.0, yi);
|
|
25
|
+
let v01: Real = white_noise::hash2(xi, yi + 1.0);
|
|
26
|
+
let v11: Real = white_noise::hash2(xi + 1.0, yi + 1.0);
|
|
27
|
+
|
|
28
|
+
// Smoothstep weights.
|
|
29
|
+
let u: Real = xf * xf * (3.0 - 2.0 * xf);
|
|
30
|
+
let v: Real = yf * yf * (3.0 - 2.0 * yf);
|
|
31
|
+
|
|
32
|
+
let lerp_x0: Real = v00 + u * (v10 - v00);
|
|
33
|
+
let lerp_x1: Real = v01 + u * (v11 - v01);
|
|
34
|
+
lerp_x0 + v * (lerp_x1 - lerp_x0)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Signed variant centred on 0: result ∈ [-1, 1].
|
|
38
|
+
@verify(lean, theorem = "value_noise_signed_in_band")
|
|
39
|
+
fn value2_signed(x: Real, y: Real) -> Real
|
|
40
|
+
where chain_order <= 1
|
|
41
|
+
ensures (result >= -1.0)
|
|
42
|
+
ensures (result <= 1.0)
|
|
43
|
+
{
|
|
44
|
+
2.0 * value2(x, y) - 1.0
|
|
45
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// voronoi_2d.eml -- 2D Voronoi cell distance field.
|
|
2
|
+
//
|
|
3
|
+
// For each query point, find the distance to the nearest of a
|
|
4
|
+
// 3×3 neighbourhood of jittered grid points. The result is the
|
|
5
|
+
// distance field; useful for cell rendering, rust patterns, biome
|
|
6
|
+
// boundaries, and crackle textures.
|
|
7
|
+
//
|
|
8
|
+
// Returns the distance to the nearest feature point. Chain order
|
|
9
|
+
// 1 (sqrt). The minimum search uses min() over 9 candidates.
|
|
10
|
+
|
|
11
|
+
module voronoi_2d;
|
|
12
|
+
|
|
13
|
+
use stdlib::gaming::noise::white_noise;
|
|
14
|
+
|
|
15
|
+
@verify(lean, theorem = "voronoi2_distance_nonneg")
|
|
16
|
+
fn distance_to_nearest(x: Real, y: Real) -> Real
|
|
17
|
+
where chain_order <= 1
|
|
18
|
+
ensures (result >= 0.0)
|
|
19
|
+
{
|
|
20
|
+
let xi: Real = floor(x);
|
|
21
|
+
let yi: Real = floor(y);
|
|
22
|
+
|
|
23
|
+
let mut_d: Real = 1.0e9;
|
|
24
|
+
var d: Real = mut_d;
|
|
25
|
+
|
|
26
|
+
// Note: pure-functional EML doesn't allow mutation; this
|
|
27
|
+
// template unrolls the 3x3 neighbourhood explicitly so each
|
|
28
|
+
// candidate can fold via min() into a single expression.
|
|
29
|
+
let dy_min: Real = -1.0;
|
|
30
|
+
let dy_max: Real = 1.0;
|
|
31
|
+
let dx_min: Real = -1.0;
|
|
32
|
+
let dx_max: Real = 1.0;
|
|
33
|
+
|
|
34
|
+
let c00: Real = _cell_distance(x, y, xi - 1.0, yi - 1.0);
|
|
35
|
+
let c10: Real = _cell_distance(x, y, xi , yi - 1.0);
|
|
36
|
+
let c20: Real = _cell_distance(x, y, xi + 1.0, yi - 1.0);
|
|
37
|
+
let c01: Real = _cell_distance(x, y, xi - 1.0, yi );
|
|
38
|
+
let c11: Real = _cell_distance(x, y, xi , yi );
|
|
39
|
+
let c21: Real = _cell_distance(x, y, xi + 1.0, yi );
|
|
40
|
+
let c02: Real = _cell_distance(x, y, xi - 1.0, yi + 1.0);
|
|
41
|
+
let c12: Real = _cell_distance(x, y, xi , yi + 1.0);
|
|
42
|
+
let c22: Real = _cell_distance(x, y, xi + 1.0, yi + 1.0);
|
|
43
|
+
|
|
44
|
+
min(c00, min(c10, min(c20,
|
|
45
|
+
min(c01, min(c11, min(c21,
|
|
46
|
+
min(c02, min(c12, c22))))))))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fn _cell_distance(px: Real, py: Real, gx: Real, gy: Real) -> Real
|
|
50
|
+
where chain_order <= 1
|
|
51
|
+
{
|
|
52
|
+
// Jitter each grid point by hash(gx, gy).
|
|
53
|
+
let jx: Real = white_noise::hash2(gx, gy);
|
|
54
|
+
let jy: Real = white_noise::hash2(gx + 17.0, gy + 31.0);
|
|
55
|
+
let dx: Real = px - (gx + jx);
|
|
56
|
+
let dy: Real = py - (gy + jy);
|
|
57
|
+
sqrt(dx * dx + dy * dy)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// F2 - F1: distance to second-nearest minus first-nearest. Used
|
|
61
|
+
// for crystalline / metallic noise patterns.
|
|
62
|
+
@verify(lean, theorem = "voronoi2_f2_f1_nonneg")
|
|
63
|
+
fn f2_minus_f1(x: Real, y: Real) -> Real
|
|
64
|
+
where chain_order <= 1
|
|
65
|
+
ensures (result >= 0.0)
|
|
66
|
+
{
|
|
67
|
+
// Approximation: pick second-min as min over the 8 ring cells
|
|
68
|
+
// (excluding the centre). Cheap surrogate; the exact second-min
|
|
69
|
+
// requires sorting all 9.
|
|
70
|
+
let xi: Real = floor(x);
|
|
71
|
+
let yi: Real = floor(y);
|
|
72
|
+
let f1: Real = _cell_distance(x, y, xi, yi);
|
|
73
|
+
let neighbours: Real = min(
|
|
74
|
+
_cell_distance(x, y, xi - 1.0, yi - 1.0),
|
|
75
|
+
min(_cell_distance(x, y, xi , yi - 1.0),
|
|
76
|
+
min(_cell_distance(x, y, xi + 1.0, yi - 1.0),
|
|
77
|
+
min(_cell_distance(x, y, xi - 1.0, yi ),
|
|
78
|
+
min(_cell_distance(x, y, xi + 1.0, yi ),
|
|
79
|
+
min(_cell_distance(x, y, xi - 1.0, yi + 1.0),
|
|
80
|
+
min(_cell_distance(x, y, xi , yi + 1.0),
|
|
81
|
+
_cell_distance(x, y, xi + 1.0, yi + 1.0))))))));
|
|
82
|
+
clamp(neighbours - f1, 0.0, 1.0e9)
|
|
83
|
+
}
|