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,47 @@
|
|
|
1
|
+
// white_noise.eml -- hash-based pseudo-random scalar in [0, 1).
|
|
2
|
+
//
|
|
3
|
+
// The single primitive every higher-order noise module relies on:
|
|
4
|
+
// a deterministic, repeatable, fast hash from integer lattice
|
|
5
|
+
// coordinates to a uniform float. Chain order 0 — pure integer
|
|
6
|
+
// arithmetic + sin-based fold.
|
|
7
|
+
//
|
|
8
|
+
// Standard fract-of-sin trick (used in countless GLSL shaders):
|
|
9
|
+
//
|
|
10
|
+
// hash(x, y) = fract(sin(dot((x,y), (12.9898, 78.233))) * 43758.5453)
|
|
11
|
+
//
|
|
12
|
+
// The Lean obligation captures the unit-interval invariant.
|
|
13
|
+
|
|
14
|
+
module white_noise;
|
|
15
|
+
|
|
16
|
+
const HASH_SCALE: Real = 43758.5453
|
|
17
|
+
|
|
18
|
+
@verify(lean, theorem = "white_noise_2d_in_unit_interval")
|
|
19
|
+
fn hash2(x: Real, y: Real) -> Real
|
|
20
|
+
where chain_order <= 1
|
|
21
|
+
ensures (result >= 0.0)
|
|
22
|
+
ensures (result < 1.0)
|
|
23
|
+
{
|
|
24
|
+
let s: Real = sin(x * 12.9898 + y * 78.233) * HASH_SCALE;
|
|
25
|
+
s - floor(s)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@verify(lean, theorem = "white_noise_3d_in_unit_interval")
|
|
29
|
+
fn hash3(x: Real, y: Real, z: Real) -> Real
|
|
30
|
+
where chain_order <= 1
|
|
31
|
+
ensures (result >= 0.0)
|
|
32
|
+
ensures (result < 1.0)
|
|
33
|
+
{
|
|
34
|
+
let s: Real = sin(x * 12.9898 + y * 78.233 + z * 37.719) * HASH_SCALE;
|
|
35
|
+
s - floor(s)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Boundary witness: hash(0,0) is finite. Used by callers that want
|
|
39
|
+
// a deterministic seed value for unit-tests.
|
|
40
|
+
@verify(lean, theorem = "white_noise_at_origin_finite")
|
|
41
|
+
fn hash2_at_origin() -> Real
|
|
42
|
+
where chain_order <= 0
|
|
43
|
+
ensures (result == 0.0)
|
|
44
|
+
{
|
|
45
|
+
let s: Real = sin(0.0) * HASH_SCALE;
|
|
46
|
+
s - floor(s)
|
|
47
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// worley.eml -- cellular / Worley noise for organic patterns.
|
|
2
|
+
//
|
|
3
|
+
// Same scaffolding as voronoi_2d (3×3 neighbourhood search) but
|
|
4
|
+
// the output is the squared distance to the nearest feature point
|
|
5
|
+
// (cheaper — skips the sqrt) and a configurable distance metric.
|
|
6
|
+
//
|
|
7
|
+
// Distance metric: 0 = Euclidean²
|
|
8
|
+
// 1 = Manhattan
|
|
9
|
+
// 2 = Chebyshev (max-component)
|
|
10
|
+
// Chain order 0 in the squared / Manhattan / Chebyshev paths.
|
|
11
|
+
|
|
12
|
+
module worley;
|
|
13
|
+
|
|
14
|
+
use stdlib::gaming::noise::white_noise;
|
|
15
|
+
|
|
16
|
+
@verify(lean, theorem = "worley_squared_nonneg")
|
|
17
|
+
fn squared_distance(x: Real, y: Real) -> Real
|
|
18
|
+
where chain_order <= 0
|
|
19
|
+
ensures (result >= 0.0)
|
|
20
|
+
{
|
|
21
|
+
let xi: Real = floor(x);
|
|
22
|
+
let yi: Real = floor(y);
|
|
23
|
+
|
|
24
|
+
let c00: Real = _cell_sq_dist(x, y, xi - 1.0, yi - 1.0);
|
|
25
|
+
let c10: Real = _cell_sq_dist(x, y, xi , yi - 1.0);
|
|
26
|
+
let c20: Real = _cell_sq_dist(x, y, xi + 1.0, yi - 1.0);
|
|
27
|
+
let c01: Real = _cell_sq_dist(x, y, xi - 1.0, yi );
|
|
28
|
+
let c11: Real = _cell_sq_dist(x, y, xi , yi );
|
|
29
|
+
let c21: Real = _cell_sq_dist(x, y, xi + 1.0, yi );
|
|
30
|
+
let c02: Real = _cell_sq_dist(x, y, xi - 1.0, yi + 1.0);
|
|
31
|
+
let c12: Real = _cell_sq_dist(x, y, xi , yi + 1.0);
|
|
32
|
+
let c22: Real = _cell_sq_dist(x, y, xi + 1.0, yi + 1.0);
|
|
33
|
+
|
|
34
|
+
min(c00, min(c10, min(c20,
|
|
35
|
+
min(c01, min(c11, min(c21,
|
|
36
|
+
min(c02, min(c12, c22))))))))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fn _cell_sq_dist(px: Real, py: Real, gx: Real, gy: Real) -> Real
|
|
40
|
+
where chain_order <= 0
|
|
41
|
+
{
|
|
42
|
+
let jx: Real = white_noise::hash2(gx, gy);
|
|
43
|
+
let jy: Real = white_noise::hash2(gx + 17.0, gy + 31.0);
|
|
44
|
+
let dx: Real = px - (gx + jx);
|
|
45
|
+
let dy: Real = py - (gy + jy);
|
|
46
|
+
dx * dx + dy * dy
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Manhattan distance variant — characteristic blocky / chamfered
|
|
50
|
+
// cell appearance.
|
|
51
|
+
@verify(lean, theorem = "worley_manhattan_nonneg")
|
|
52
|
+
fn manhattan_distance(x: Real, y: Real) -> Real
|
|
53
|
+
where chain_order <= 0
|
|
54
|
+
ensures (result >= 0.0)
|
|
55
|
+
{
|
|
56
|
+
let xi: Real = floor(x);
|
|
57
|
+
let yi: Real = floor(y);
|
|
58
|
+
|
|
59
|
+
let c11: Real = _cell_man_dist(x, y, xi , yi );
|
|
60
|
+
let c00: Real = _cell_man_dist(x, y, xi - 1.0, yi - 1.0);
|
|
61
|
+
let c10: Real = _cell_man_dist(x, y, xi , yi - 1.0);
|
|
62
|
+
let c20: Real = _cell_man_dist(x, y, xi + 1.0, yi - 1.0);
|
|
63
|
+
let c01: Real = _cell_man_dist(x, y, xi - 1.0, yi );
|
|
64
|
+
let c21: Real = _cell_man_dist(x, y, xi + 1.0, yi );
|
|
65
|
+
let c02: Real = _cell_man_dist(x, y, xi - 1.0, yi + 1.0);
|
|
66
|
+
let c12: Real = _cell_man_dist(x, y, xi , yi + 1.0);
|
|
67
|
+
let c22: Real = _cell_man_dist(x, y, xi + 1.0, yi + 1.0);
|
|
68
|
+
min(c00, min(c10, min(c20,
|
|
69
|
+
min(c01, min(c11, min(c21,
|
|
70
|
+
min(c02, min(c12, c22))))))))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fn _cell_man_dist(px: Real, py: Real, gx: Real, gy: Real) -> Real
|
|
74
|
+
where chain_order <= 0
|
|
75
|
+
{
|
|
76
|
+
let jx: Real = white_noise::hash2(gx, gy);
|
|
77
|
+
let jy: Real = white_noise::hash2(gx + 17.0, gy + 31.0);
|
|
78
|
+
abs(px - (gx + jx)) + abs(py - (gy + jy))
|
|
79
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// drag.eml -- exponential velocity decay (linear drag model).
|
|
2
|
+
//
|
|
3
|
+
// v(t) = v_0 * exp(-k * t)
|
|
4
|
+
//
|
|
5
|
+
// k is the drag coefficient (1/s). For quadratic drag use the
|
|
6
|
+
// stdlib::ballistics::drag kernel instead.
|
|
7
|
+
|
|
8
|
+
module particle_drag;
|
|
9
|
+
|
|
10
|
+
@verify(lean, theorem = "particle_drag_velocity_nonneg")
|
|
11
|
+
fn velocity(v_0: Real, k_drag: Real, t_s: Real) -> Real
|
|
12
|
+
where chain_order <= 1,
|
|
13
|
+
domain: k_drag >= 0.0 && t_s >= 0.0
|
|
14
|
+
requires (v_0 >= 0.0)
|
|
15
|
+
ensures (result >= 0.0)
|
|
16
|
+
ensures (result <= v_0)
|
|
17
|
+
{
|
|
18
|
+
v_0 * exp(-k_drag * t_s)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Distance covered from t=0 to t=T: x(T) = v_0/k * (1 - exp(-k*T))
|
|
22
|
+
@verify(lean, theorem = "particle_drag_distance_nonneg")
|
|
23
|
+
fn distance_covered(v_0: Real, k_drag: Real, t_s: Real) -> Real
|
|
24
|
+
where chain_order <= 1,
|
|
25
|
+
domain: k_drag > 0.0 && t_s >= 0.0
|
|
26
|
+
requires (v_0 >= 0.0)
|
|
27
|
+
ensures (result >= 0.0)
|
|
28
|
+
{
|
|
29
|
+
(v_0 / k_drag) * (1.0 - exp(-k_drag * t_s))
|
|
30
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// emitter_radial.eml -- radial particle emitter velocity.
|
|
2
|
+
//
|
|
3
|
+
// v = direction * (speed + random_in([-spread, +spread]))
|
|
4
|
+
//
|
|
5
|
+
// Caller supplies the unit `direction` and a per-particle random
|
|
6
|
+
// scalar ∈ [-1, 1]; the kernel returns the velocity magnitude.
|
|
7
|
+
|
|
8
|
+
module emitter_radial;
|
|
9
|
+
|
|
10
|
+
@verify(lean, theorem = "emitter_speed_nonneg_when_spread_le_speed")
|
|
11
|
+
fn velocity_magnitude(speed: Real, spread: Real, random_unit: Real) -> Real
|
|
12
|
+
where chain_order <= 0
|
|
13
|
+
requires (speed >= 0.0)
|
|
14
|
+
requires (spread >= 0.0)
|
|
15
|
+
requires (random_unit >= -1.0)
|
|
16
|
+
requires (random_unit <= 1.0)
|
|
17
|
+
requires (spread <= speed)
|
|
18
|
+
ensures (result >= 0.0)
|
|
19
|
+
{
|
|
20
|
+
speed + spread * random_unit
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Emit position offset: random point on a disc of radius `disc_r`,
|
|
24
|
+
// returned as a magnitude (caller uses the random angle).
|
|
25
|
+
@verify(lean, theorem = "emitter_disc_offset_in_band")
|
|
26
|
+
fn disc_offset(disc_r: Real, random_unit: Real) -> Real
|
|
27
|
+
where chain_order <= 0,
|
|
28
|
+
domain: disc_r >= 0.0
|
|
29
|
+
requires (random_unit >= 0.0)
|
|
30
|
+
requires (random_unit <= 1.0)
|
|
31
|
+
ensures (result >= 0.0)
|
|
32
|
+
ensures (result <= disc_r)
|
|
33
|
+
{
|
|
34
|
+
disc_r * sqrt(clamp(random_unit, 0.0, 1.0))
|
|
35
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// fade.eml -- linear alpha fade over a particle's lifetime.
|
|
2
|
+
//
|
|
3
|
+
// alpha(t) = clamp(1 - t/lifetime, 0, 1)
|
|
4
|
+
|
|
5
|
+
module fade;
|
|
6
|
+
|
|
7
|
+
@verify(lean, theorem = "fade_alpha_in_unit_interval")
|
|
8
|
+
fn alpha(t_s: Real, lifetime_s: Real) -> Real
|
|
9
|
+
where chain_order <= 0,
|
|
10
|
+
domain: lifetime_s > 0.0 && t_s >= 0.0
|
|
11
|
+
ensures (result >= 0.0)
|
|
12
|
+
ensures (result <= 1.0)
|
|
13
|
+
{
|
|
14
|
+
clamp(1.0 - t_s / lifetime_s, 0.0, 1.0)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Fade-in then fade-out: ramp up over t < fade_in, hold, ramp down
|
|
18
|
+
// from t > lifetime - fade_out.
|
|
19
|
+
@verify(lean, theorem = "fade_in_out_in_unit_interval")
|
|
20
|
+
fn alpha_in_out(t_s: Real, lifetime_s: Real,
|
|
21
|
+
fade_in_s: Real, fade_out_s: Real) -> Real
|
|
22
|
+
where chain_order <= 0,
|
|
23
|
+
domain: lifetime_s > 0.0 && t_s >= 0.0
|
|
24
|
+
requires (fade_in_s >= 0.0)
|
|
25
|
+
requires (fade_out_s >= 0.0)
|
|
26
|
+
ensures (result >= 0.0)
|
|
27
|
+
ensures (result <= 1.0)
|
|
28
|
+
{
|
|
29
|
+
if t_s < fade_in_s {
|
|
30
|
+
clamp(t_s / max(fade_in_s, 1.0e-6), 0.0, 1.0)
|
|
31
|
+
} else if t_s > lifetime_s - fade_out_s {
|
|
32
|
+
clamp((lifetime_s - t_s) / max(fade_out_s, 1.0e-6), 0.0, 1.0)
|
|
33
|
+
} else {
|
|
34
|
+
1.0
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// gravity_particle.eml -- ballistic motion under gravity.
|
|
2
|
+
//
|
|
3
|
+
// y(t) = y_0 + v_0 t - ½ g t²
|
|
4
|
+
// vy(t) = v_0 - g t
|
|
5
|
+
|
|
6
|
+
module gravity_particle;
|
|
7
|
+
|
|
8
|
+
const G_EARTH: Real = 9.80665
|
|
9
|
+
|
|
10
|
+
@verify(lean, theorem = "gravity_particle_position_signed")
|
|
11
|
+
fn position(y_0: Real, v_0_y: Real, g: Real, t_s: Real) -> Real
|
|
12
|
+
where chain_order <= 0,
|
|
13
|
+
domain: g >= 0.0 && t_s >= 0.0
|
|
14
|
+
{
|
|
15
|
+
y_0 + v_0_y * t_s - 0.5 * g * t_s * t_s
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@verify(lean, theorem = "gravity_particle_velocity_signed")
|
|
19
|
+
fn velocity(v_0_y: Real, g: Real, t_s: Real) -> Real
|
|
20
|
+
where chain_order <= 0,
|
|
21
|
+
domain: g >= 0.0 && t_s >= 0.0
|
|
22
|
+
{
|
|
23
|
+
v_0_y - g * t_s
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@verify(lean, theorem = "gravity_particle_position_earth_signed")
|
|
27
|
+
fn position_earth(y_0: Real, v_0_y: Real, t_s: Real) -> Real
|
|
28
|
+
where chain_order <= 0,
|
|
29
|
+
domain: t_s >= 0.0
|
|
30
|
+
{
|
|
31
|
+
y_0 + v_0_y * t_s - 0.5 * G_EARTH * t_s * t_s
|
|
32
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// size_over_life.eml -- particle radius as a function of age.
|
|
2
|
+
//
|
|
3
|
+
// r(t) = r_0 + growth * t (linear)
|
|
4
|
+
// r(t) = r_0 * (t/lifetime)^p (power-shape)
|
|
5
|
+
|
|
6
|
+
module size_over_life;
|
|
7
|
+
|
|
8
|
+
@verify(lean, theorem = "size_linear_nonneg")
|
|
9
|
+
fn radius_linear(r_0: Real, growth: Real, t_s: Real) -> Real
|
|
10
|
+
where chain_order <= 0
|
|
11
|
+
requires (r_0 >= 0.0)
|
|
12
|
+
requires (t_s >= 0.0)
|
|
13
|
+
requires (r_0 + growth * t_s >= 0.0)
|
|
14
|
+
ensures (result >= 0.0)
|
|
15
|
+
{
|
|
16
|
+
r_0 + growth * t_s
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@verify(lean, theorem = "size_power_nonneg")
|
|
20
|
+
fn radius_power(r_0: Real, lifetime_s: Real, t_s: Real, exponent: Real) -> Real
|
|
21
|
+
where chain_order <= 1,
|
|
22
|
+
domain: lifetime_s > 0.0 && t_s >= 0.0
|
|
23
|
+
requires (r_0 >= 0.0)
|
|
24
|
+
requires (exponent >= 0.0)
|
|
25
|
+
ensures (result >= 0.0)
|
|
26
|
+
{
|
|
27
|
+
r_0 * pow(clamp(t_s / lifetime_s, 0.0, 1.0), exponent)
|
|
28
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// aabb_overlap.eml -- axis-aligned bounding box intersection test.
|
|
2
|
+
//
|
|
3
|
+
// Returns 1.0 if the two AABBs overlap, 0.0 otherwise. Inputs:
|
|
4
|
+
// (min, max) for each box on each axis.
|
|
5
|
+
|
|
6
|
+
module aabb_overlap;
|
|
7
|
+
|
|
8
|
+
@verify(lean, theorem = "aabb2_overlap_in_zero_one")
|
|
9
|
+
fn overlap_2d(a_min_x: Real, a_max_x: Real, a_min_y: Real, a_max_y: Real,
|
|
10
|
+
b_min_x: Real, b_max_x: Real, b_min_y: Real, b_max_y: Real) -> Real
|
|
11
|
+
where chain_order <= 0
|
|
12
|
+
requires (a_min_x <= a_max_x)
|
|
13
|
+
requires (a_min_y <= a_max_y)
|
|
14
|
+
requires (b_min_x <= b_max_x)
|
|
15
|
+
requires (b_min_y <= b_max_y)
|
|
16
|
+
ensures (result >= 0.0)
|
|
17
|
+
ensures (result <= 1.0)
|
|
18
|
+
{
|
|
19
|
+
if a_max_x < b_min_x { 0.0 }
|
|
20
|
+
else if a_min_x > b_max_x { 0.0 }
|
|
21
|
+
else if a_max_y < b_min_y { 0.0 }
|
|
22
|
+
else if a_min_y > b_max_y { 0.0 }
|
|
23
|
+
else { 1.0 }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Penetration depth on a single axis (signed; sign indicates side).
|
|
27
|
+
@verify(lean, theorem = "aabb_penetration_depth_signed")
|
|
28
|
+
fn penetration_depth_x(a_min_x: Real, a_max_x: Real,
|
|
29
|
+
b_min_x: Real, b_max_x: Real) -> Real
|
|
30
|
+
where chain_order <= 0
|
|
31
|
+
requires (a_min_x <= a_max_x)
|
|
32
|
+
requires (b_min_x <= b_max_x)
|
|
33
|
+
{
|
|
34
|
+
let right_overlap: Real = a_max_x - b_min_x;
|
|
35
|
+
let left_overlap: Real = b_max_x - a_min_x;
|
|
36
|
+
if abs(right_overlap) < abs(left_overlap) { right_overlap }
|
|
37
|
+
else { -left_overlap }
|
|
38
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// buoyancy.eml -- Archimedes' principle for water / fluid bodies.
|
|
2
|
+
//
|
|
3
|
+
// F_buoyancy = rho_fluid * g * V_submerged
|
|
4
|
+
//
|
|
5
|
+
// rho_fluid in kg/m³, g in m/s², V_submerged in m³. Positive value
|
|
6
|
+
// means upward (opposing gravity).
|
|
7
|
+
|
|
8
|
+
module buoyancy;
|
|
9
|
+
|
|
10
|
+
const RHO_WATER: Real = 1000.0 // kg/m³
|
|
11
|
+
const G_EARTH: Real = 9.80665
|
|
12
|
+
|
|
13
|
+
@verify(lean, theorem = "buoyancy_force_nonneg")
|
|
14
|
+
fn force(rho_fluid: Real, g: Real, volume_submerged: Real) -> Real
|
|
15
|
+
where chain_order <= 0,
|
|
16
|
+
domain: rho_fluid >= 0.0 && g >= 0.0 && volume_submerged >= 0.0
|
|
17
|
+
ensures (result >= 0.0)
|
|
18
|
+
{
|
|
19
|
+
rho_fluid * g * volume_submerged
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@verify(lean, theorem = "buoyancy_water_force_nonneg")
|
|
23
|
+
fn force_in_water(volume_submerged: Real) -> Real
|
|
24
|
+
where chain_order <= 0,
|
|
25
|
+
domain: volume_submerged >= 0.0
|
|
26
|
+
ensures (result >= 0.0)
|
|
27
|
+
{
|
|
28
|
+
RHO_WATER * G_EARTH * volume_submerged
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Volume of a sphere of radius r submerged to depth d (d in [0, 2r]).
|
|
32
|
+
// V = π * d² * (3r - d) / 3
|
|
33
|
+
@verify(lean, theorem = "sphere_submerged_volume_in_band")
|
|
34
|
+
fn sphere_submerged_volume(radius: Real, depth: Real) -> Real
|
|
35
|
+
where chain_order <= 0,
|
|
36
|
+
domain: radius >= 0.0
|
|
37
|
+
requires (depth >= 0.0)
|
|
38
|
+
requires (depth <= 2.0 * radius)
|
|
39
|
+
ensures (result >= 0.0)
|
|
40
|
+
{
|
|
41
|
+
let pi: Real = 3.141592653589793;
|
|
42
|
+
pi * depth * depth * (3.0 * radius - depth) / 3.0
|
|
43
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// circle_collision.eml -- circle-vs-circle intersection test.
|
|
2
|
+
//
|
|
3
|
+
// collides = (centre_distance < r1 + r2)
|
|
4
|
+
// penetration_depth = (r1 + r2) - centre_distance
|
|
5
|
+
// collision_normal = (b - a).normalized()
|
|
6
|
+
|
|
7
|
+
module circle_collision;
|
|
8
|
+
|
|
9
|
+
@verify(lean, theorem = "circle_collide_in_zero_one")
|
|
10
|
+
fn collides(ax: Real, ay: Real, ar: Real,
|
|
11
|
+
bx: Real, by: Real, br: Real) -> Real
|
|
12
|
+
where chain_order <= 1,
|
|
13
|
+
domain: ar >= 0.0 && br >= 0.0
|
|
14
|
+
ensures (result >= 0.0)
|
|
15
|
+
ensures (result <= 1.0)
|
|
16
|
+
{
|
|
17
|
+
let dx: Real = bx - ax;
|
|
18
|
+
let dy: Real = by - ay;
|
|
19
|
+
let d: Real = sqrt(dx * dx + dy * dy);
|
|
20
|
+
if d < ar + br { 1.0 } else { 0.0 }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@verify(lean, theorem = "circle_penetration_signed")
|
|
24
|
+
fn penetration_depth(ax: Real, ay: Real, ar: Real,
|
|
25
|
+
bx: Real, by: Real, br: Real) -> Real
|
|
26
|
+
where chain_order <= 1,
|
|
27
|
+
domain: ar >= 0.0 && br >= 0.0
|
|
28
|
+
{
|
|
29
|
+
let dx: Real = bx - ax;
|
|
30
|
+
let dy: Real = by - ay;
|
|
31
|
+
let d: Real = sqrt(dx * dx + dy * dy);
|
|
32
|
+
(ar + br) - d
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Returns 1.0 / d safely, used by callers building the collision
|
|
36
|
+
// normal as (b - a) / d.
|
|
37
|
+
@verify(lean, theorem = "circle_inv_distance_finite")
|
|
38
|
+
fn inv_distance(ax: Real, ay: Real, bx: Real, by: Real) -> Real
|
|
39
|
+
where chain_order <= 1
|
|
40
|
+
ensures (result >= 0.0)
|
|
41
|
+
{
|
|
42
|
+
let dx: Real = bx - ax;
|
|
43
|
+
let dy: Real = by - ay;
|
|
44
|
+
let d: Real = sqrt(dx * dx + dy * dy);
|
|
45
|
+
1.0 / max(d, 1.0e-6)
|
|
46
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// impulse_resolve.eml -- collision response with restitution.
|
|
2
|
+
//
|
|
3
|
+
// For two bodies a, b with masses m_a, m_b, velocities v_a, v_b
|
|
4
|
+
// along the contact normal, restitution e ∈ [0, 1]:
|
|
5
|
+
//
|
|
6
|
+
// v_rel = v_a - v_b (closing speed)
|
|
7
|
+
// j = -(1 + e) * v_rel / (1/m_a + 1/m_b) (impulse magnitude)
|
|
8
|
+
// v_a_new = v_a + j / m_a
|
|
9
|
+
// v_b_new = v_b - j / m_b
|
|
10
|
+
|
|
11
|
+
module impulse_resolve;
|
|
12
|
+
|
|
13
|
+
@verify(lean, theorem = "impulse_magnitude_signed")
|
|
14
|
+
fn impulse_magnitude(v_a: Real, v_b: Real,
|
|
15
|
+
m_a: Real, m_b: Real, restitution: Real) -> Real
|
|
16
|
+
where chain_order <= 0,
|
|
17
|
+
domain: m_a > 0.0 && m_b > 0.0
|
|
18
|
+
requires (restitution >= 0.0)
|
|
19
|
+
requires (restitution <= 1.0)
|
|
20
|
+
{
|
|
21
|
+
let v_rel: Real = v_a - v_b;
|
|
22
|
+
-(1.0 + restitution) * v_rel / (1.0 / m_a + 1.0 / m_b)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@verify(lean, theorem = "impulse_apply_a_signed")
|
|
26
|
+
fn velocity_a_after(v_a: Real, impulse_j: Real, m_a: Real) -> Real
|
|
27
|
+
where chain_order <= 0,
|
|
28
|
+
domain: m_a > 0.0
|
|
29
|
+
{
|
|
30
|
+
v_a + impulse_j / m_a
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@verify(lean, theorem = "impulse_apply_b_signed")
|
|
34
|
+
fn velocity_b_after(v_b: Real, impulse_j: Real, m_b: Real) -> Real
|
|
35
|
+
where chain_order <= 0,
|
|
36
|
+
domain: m_b > 0.0
|
|
37
|
+
{
|
|
38
|
+
v_b - impulse_j / m_b
|
|
39
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// rigid_body_2d.eml -- semi-implicit Euler step for a 2D rigid body.
|
|
2
|
+
//
|
|
3
|
+
// State: (x, y, theta, vx, vy, omega).
|
|
4
|
+
// Forces: linear (fx, fy) + torque tau.
|
|
5
|
+
//
|
|
6
|
+
// v_new = v + (f / m) * dt
|
|
7
|
+
// p_new = p + v_new * dt (semi-implicit Euler — stable)
|
|
8
|
+
//
|
|
9
|
+
// Each component shipped as its own kernel so callers can chain
|
|
10
|
+
// only what they need on the GPU.
|
|
11
|
+
|
|
12
|
+
module rigid_body_2d;
|
|
13
|
+
|
|
14
|
+
@verify(lean, theorem = "rb2d_velocity_x_signed")
|
|
15
|
+
fn integrate_vx(vx: Real, fx: Real, mass_kg: Real, dt: Real) -> Real
|
|
16
|
+
where chain_order <= 0,
|
|
17
|
+
domain: mass_kg > 0.0 && dt >= 0.0
|
|
18
|
+
{
|
|
19
|
+
vx + (fx / mass_kg) * dt
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@verify(lean, theorem = "rb2d_velocity_y_signed")
|
|
23
|
+
fn integrate_vy(vy: Real, fy: Real, mass_kg: Real, dt: Real) -> Real
|
|
24
|
+
where chain_order <= 0,
|
|
25
|
+
domain: mass_kg > 0.0 && dt >= 0.0
|
|
26
|
+
{
|
|
27
|
+
vy + (fy / mass_kg) * dt
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@verify(lean, theorem = "rb2d_angular_velocity_signed")
|
|
31
|
+
fn integrate_omega(omega: Real, tau: Real, inertia: Real, dt: Real) -> Real
|
|
32
|
+
where chain_order <= 0,
|
|
33
|
+
domain: inertia > 0.0 && dt >= 0.0
|
|
34
|
+
{
|
|
35
|
+
omega + (tau / inertia) * dt
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Position update — uses already-updated velocity (semi-implicit).
|
|
39
|
+
@verify(lean, theorem = "rb2d_position_x_signed")
|
|
40
|
+
fn integrate_x(x: Real, vx_new: Real, dt: Real) -> Real
|
|
41
|
+
where chain_order <= 0,
|
|
42
|
+
domain: dt >= 0.0
|
|
43
|
+
{
|
|
44
|
+
x + vx_new * dt
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@verify(lean, theorem = "rb2d_position_y_signed")
|
|
48
|
+
fn integrate_y(y: Real, vy_new: Real, dt: Real) -> Real
|
|
49
|
+
where chain_order <= 0,
|
|
50
|
+
domain: dt >= 0.0
|
|
51
|
+
{
|
|
52
|
+
y + vy_new * dt
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@verify(lean, theorem = "rb2d_angle_signed")
|
|
56
|
+
fn integrate_theta(theta: Real, omega_new: Real, dt: Real) -> Real
|
|
57
|
+
where chain_order <= 0,
|
|
58
|
+
domain: dt >= 0.0
|
|
59
|
+
{
|
|
60
|
+
theta + omega_new * dt
|
|
61
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// spring_damper.eml -- mass-on-spring force model.
|
|
2
|
+
//
|
|
3
|
+
// F = -k * (x - x_rest) - c * v
|
|
4
|
+
//
|
|
5
|
+
// k is stiffness (N/m), c the damping (N·s/m), x the current
|
|
6
|
+
// position, v the current velocity. Used for soft-body bones,
|
|
7
|
+
// suspension, follow cameras.
|
|
8
|
+
|
|
9
|
+
module spring_damper;
|
|
10
|
+
|
|
11
|
+
@verify(lean, theorem = "spring_damper_force_signed")
|
|
12
|
+
fn force(x: Real, x_rest: Real, v: Real,
|
|
13
|
+
k_stiffness: Real, c_damping: Real) -> Real
|
|
14
|
+
where chain_order <= 0
|
|
15
|
+
requires (k_stiffness >= 0.0)
|
|
16
|
+
requires (c_damping >= 0.0)
|
|
17
|
+
{
|
|
18
|
+
-k_stiffness * (x - x_rest) - c_damping * v
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Critical-damping coefficient for a given mass + stiffness:
|
|
22
|
+
// c_crit = 2 * sqrt(m * k)
|
|
23
|
+
@verify(lean, theorem = "critical_damping_nonneg")
|
|
24
|
+
fn critical_damping(mass_kg: Real, k_stiffness: Real) -> Real
|
|
25
|
+
where chain_order <= 1,
|
|
26
|
+
domain: mass_kg >= 0.0 && k_stiffness >= 0.0
|
|
27
|
+
ensures (result >= 0.0)
|
|
28
|
+
{
|
|
29
|
+
2.0 * sqrt(mass_kg * k_stiffness)
|
|
30
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// verlet.eml -- Verlet position-only integration step (cloth/rope).
|
|
2
|
+
//
|
|
3
|
+
// x_new = 2*x - x_prev + a * dt²
|
|
4
|
+
//
|
|
5
|
+
// No explicit velocity state — velocity is implicit in (x - x_prev).
|
|
6
|
+
// Stable for stiff cloth + rope simulations where energy must be
|
|
7
|
+
// approximately conserved over many steps.
|
|
8
|
+
|
|
9
|
+
module verlet;
|
|
10
|
+
|
|
11
|
+
@verify(lean, theorem = "verlet_position_signed")
|
|
12
|
+
fn step(x: Real, x_prev: Real, a: Real, dt: Real) -> Real
|
|
13
|
+
where chain_order <= 0,
|
|
14
|
+
domain: dt >= 0.0
|
|
15
|
+
{
|
|
16
|
+
2.0 * x - x_prev + a * dt * dt
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Constraint: distance between two points must remain at rest_length.
|
|
20
|
+
// Returns the displacement that should be applied (split between
|
|
21
|
+
// the two endpoints by the caller, weighted by inverse mass).
|
|
22
|
+
@verify(lean, theorem = "verlet_constraint_signed")
|
|
23
|
+
fn distance_constraint_correction(current_distance: Real,
|
|
24
|
+
rest_length: Real) -> Real
|
|
25
|
+
where chain_order <= 0,
|
|
26
|
+
domain: rest_length >= 0.0
|
|
27
|
+
{
|
|
28
|
+
current_distance - rest_length
|
|
29
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// fresnel.eml -- Schlick's approximation to the Fresnel term.
|
|
2
|
+
//
|
|
3
|
+
// F = F₀ + (1 - F₀) * (1 - cos θ)⁵
|
|
4
|
+
//
|
|
5
|
+
// F₀ is the reflectance at normal incidence (0.04 for dielectrics,
|
|
6
|
+
// material-specific for metals). cos θ = max(0, n · v).
|
|
7
|
+
|
|
8
|
+
module fresnel;
|
|
9
|
+
|
|
10
|
+
@verify(lean, theorem = "fresnel_schlick_in_band")
|
|
11
|
+
fn schlick(f0: Real, cos_theta: Real) -> Real
|
|
12
|
+
where chain_order <= 0
|
|
13
|
+
requires (f0 >= 0.0)
|
|
14
|
+
requires (f0 <= 1.0)
|
|
15
|
+
ensures (result >= 0.0)
|
|
16
|
+
ensures (result <= 1.0)
|
|
17
|
+
{
|
|
18
|
+
let c: Real = clamp(1.0 - cos_theta, 0.0, 1.0);
|
|
19
|
+
let c5: Real = c * c * c * c * c;
|
|
20
|
+
clamp(f0 + (1.0 - f0) * c5, 0.0, 1.0)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Boundary witness: at grazing angle (cos θ = 0) Schlick collapses
|
|
24
|
+
// to 1. At normal incidence (cos θ = 1) it collapses to f0.
|
|
25
|
+
@verify(lean, theorem = "fresnel_at_grazing_is_one")
|
|
26
|
+
fn schlick_at_grazing(f0: Real) -> Real
|
|
27
|
+
where chain_order <= 0
|
|
28
|
+
requires (f0 >= 0.0)
|
|
29
|
+
requires (f0 <= 1.0)
|
|
30
|
+
ensures (result == 1.0)
|
|
31
|
+
{
|
|
32
|
+
f0 + (1.0 - f0) * 1.0
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@verify(lean, theorem = "fresnel_at_normal_incidence_is_f0")
|
|
36
|
+
fn schlick_at_normal(f0: Real) -> Real
|
|
37
|
+
where chain_order <= 0
|
|
38
|
+
requires (f0 >= 0.0)
|
|
39
|
+
requires (f0 <= 1.0)
|
|
40
|
+
ensures (result == f0)
|
|
41
|
+
{
|
|
42
|
+
f0 + (1.0 - f0) * 0.0
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Convenience: F₀ for a material with the given index of refraction
|
|
46
|
+
// vs air.
|
|
47
|
+
// F₀ = ((n - 1) / (n + 1))²
|
|
48
|
+
@verify(lean, theorem = "f0_from_ior_in_unit_interval")
|
|
49
|
+
fn f0_from_ior(n_material: Real) -> Real
|
|
50
|
+
where chain_order <= 0,
|
|
51
|
+
domain: n_material > 0.0
|
|
52
|
+
ensures (result >= 0.0)
|
|
53
|
+
ensures (result <= 1.0)
|
|
54
|
+
{
|
|
55
|
+
let r: Real = (n_material - 1.0) / (n_material + 1.0);
|
|
56
|
+
clamp(r * r, 0.0, 1.0)
|
|
57
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// matcap.eml -- normal-based environment look-up (matcap shading).
|
|
2
|
+
//
|
|
3
|
+
// A "material capture" texture stores the entire lighting + shading
|
|
4
|
+
// of a sphere from the camera's point of view. To shade a fragment,
|
|
5
|
+
// project its view-space normal into the matcap's UV space:
|
|
6
|
+
//
|
|
7
|
+
// uv = 0.5 * normal_view.xy + 0.5
|
|
8
|
+
//
|
|
9
|
+
// The texture sample returns the final colour. This module only
|
|
10
|
+
// computes the UV; the caller does the texture lookup.
|
|
11
|
+
|
|
12
|
+
module matcap;
|
|
13
|
+
|
|
14
|
+
@verify(lean, theorem = "matcap_uv_in_unit_interval")
|
|
15
|
+
fn uv_u(normal_view_x: Real) -> Real
|
|
16
|
+
where chain_order <= 0
|
|
17
|
+
requires (normal_view_x >= -1.0)
|
|
18
|
+
requires (normal_view_x <= 1.0)
|
|
19
|
+
ensures (result >= 0.0)
|
|
20
|
+
ensures (result <= 1.0)
|
|
21
|
+
{
|
|
22
|
+
0.5 * normal_view_x + 0.5
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@verify(lean, theorem = "matcap_uv_v_in_unit_interval")
|
|
26
|
+
fn uv_v(normal_view_y: Real) -> Real
|
|
27
|
+
where chain_order <= 0
|
|
28
|
+
requires (normal_view_y >= -1.0)
|
|
29
|
+
requires (normal_view_y <= 1.0)
|
|
30
|
+
ensures (result >= 0.0)
|
|
31
|
+
ensures (result <= 1.0)
|
|
32
|
+
{
|
|
33
|
+
0.5 * normal_view_y + 0.5
|
|
34
|
+
}
|