///|
/// Deterministic procedural motion primitives. They are seeded explicitly so
/// a preview and a final export produce identical camera shake and particles.
pub struct NoiseSource2 {
seed : Int
frequency : Double
octaves : Int
persistence : Double
lacunarity : Double
} derive(Debug)
///|
pub fn noise_source2(seed : Int) -> NoiseSource2 {
{ seed, frequency: 1.0, octaves: 4, persistence: 0.5, lacunarity: 2.0 }
}
///|
pub fn NoiseSource2::with_frequency(
self : NoiseSource2,
frequency : Double,
) -> NoiseSource2 {
{
seed: self.seed,
frequency: frequency.max(0.000001),
octaves: self.octaves,
persistence: self.persistence,
lacunarity: self.lacunarity,
}
}
///|
pub fn NoiseSource2::with_octaves(
self : NoiseSource2,
octaves : Int,
) -> NoiseSource2 {
{
seed: self.seed,
frequency: self.frequency,
octaves: octaves.max(1).min(16),
persistence: self.persistence,
lacunarity: self.lacunarity,
}
}
///|
pub fn NoiseSource2::with_persistence(
self : NoiseSource2,
persistence : Double,
) -> NoiseSource2 {
{
seed: self.seed,
frequency: self.frequency,
octaves: self.octaves,
persistence: clamp01(persistence),
lacunarity: self.lacunarity,
}
}
///|
pub fn NoiseSource2::with_lacunarity(
self : NoiseSource2,
lacunarity : Double,
) -> NoiseSource2 {
{
seed: self.seed,
frequency: self.frequency,
octaves: self.octaves,
persistence: self.persistence,
lacunarity: lacunarity.max(1.0),
}
}
///|
pub fn NoiseSource2::seed(self : NoiseSource2) -> Int {
self.seed
}
///|
pub fn NoiseSource2::frequency(self : NoiseSource2) -> Double {
self.frequency
}
///|
pub fn NoiseSource2::octaves(self : NoiseSource2) -> Int {
self.octaves
}
///|
fn noise_hash(seed : Int, x : Int, y : Int) -> Double {
let raw = seed + x * 374761393 + y * 668265263
let folded = (raw * (raw * 1274126177 + 60493) + 1442695041) % 2147483647
let positive = if folded < 0 { folded + 2147483647 } else { folded }
positive.to_double() / 2147483647.0 * 2.0 - 1.0
}
///|
fn smooth_noise_curve(value : Double) -> Double {
value * value * (3.0 - 2.0 * value)
}
///|
fn lattice_noise(seed : Int, x : Double, y : Double) -> Double {
let floor_x = @math.floor(x)
let floor_y = @math.floor(y)
let fraction_x = smooth_noise_curve(x - floor_x)
let fraction_y = smooth_noise_curve(y - floor_y)
let ix = floor_x.to_int()
let iy = floor_y.to_int()
let bottom_left = noise_hash(seed, ix, iy)
let bottom_right = noise_hash(seed, ix + 1, iy)
let top_left = noise_hash(seed, ix, iy + 1)
let top_right = noise_hash(seed, ix + 1, iy + 1)
let bottom = bottom_left + (bottom_right - bottom_left) * fraction_x
let top = top_left + (top_right - top_left) * fraction_x
bottom + (top - bottom) * fraction_y
}
///|
pub fn NoiseSource2::value(
self : NoiseSource2,
x : Double,
y : Double,
) -> Double {
lattice_noise(self.seed, x * self.frequency, y * self.frequency)
}
///|
pub fn NoiseSource2::fractal(
self : NoiseSource2,
x : Double,
y : Double,
) -> Double {
let mut amplitude = 1.0
let mut frequency = self.frequency
let mut total = 0.0
let mut weight = 0.0
for octave in 0.. Array[Double] {
let values : Array[Double] = []
for index in 0.. Point2 {
let step = epsilon.max(0.000000000001)
let dx = (self.fractal(x + step, y) - self.fractal(x - step, y)) /
(2.0 * step)
let dy = (self.fractal(x, y + step) - self.fractal(x, y - step)) /
(2.0 * step)
point2(x=dx, y=dy)
}
///|
pub struct JitterSequence2 {
source : NoiseSource2
amplitude : Point2
frequency : Double
phase : Point2
} derive(Debug)
///|
pub fn jitter_sequence2(seed : Int, amplitude : Point2) -> JitterSequence2 {
{
source: noise_source2(seed),
amplitude,
frequency: 1.0,
phase: point2(x=0.0, y=0.0),
}
}
///|
pub fn JitterSequence2::with_frequency(
self : JitterSequence2,
frequency : Double,
) -> JitterSequence2 {
{
source: self.source,
amplitude: self.amplitude,
frequency: frequency.max(0.000001),
phase: self.phase,
}
}
///|
pub fn JitterSequence2::with_phase(
self : JitterSequence2,
phase : Point2,
) -> JitterSequence2 {
{
source: self.source,
amplitude: self.amplitude,
frequency: self.frequency,
phase,
}
}
///|
pub fn JitterSequence2::sample(self : JitterSequence2, time : Double) -> Point2 {
let x_noise = self.source.fractal(time * self.frequency + self.phase.x(), 0.0)
let y_noise = self.source.fractal(
time * self.frequency + self.phase.y(),
17.0,
)
point2(x=x_noise * self.amplitude.x(), y=y_noise * self.amplitude.y())
}
///|
pub fn JitterSequence2::sample_many(
self : JitterSequence2,
start : Double,
step : Double,
count : Int,
) -> Array[Point2] {
let values : Array[Point2] = []
for index in 0.. ProceduralMotion2 {
{ base, velocity, jitter, envelope }
}
///|
pub fn ProceduralMotion2::sample(
self : ProceduralMotion2,
time : Double,
normalized_time : Double,
) -> Point2 {
let noise = self.jitter.sample(time)
let envelope = self.envelope.apply(clamp01(normalized_time))
point2(
x=self.base.x() + self.velocity.x() * time + noise.x() * envelope,
y=self.base.y() + self.velocity.y() * time + noise.y() * envelope,
)
}
///|
pub fn ProceduralMotion2::sample_many(
self : ProceduralMotion2,
start : Double,
end : Double,
count : Int,
) -> Array[Point2] {
let values : Array[Point2] = []
let actual = count.max(1)
for index in 0.. Array[ShakeFalloff2] {
[ConstantShake, LinearShake, SmoothShake, ExponentialShake(2.0)]
}
///|
pub fn shake_falloff_value(mode : ShakeFalloff2, ratio : Double) -> Double {
let value = clamp01(ratio)
match mode {
ConstantShake => 1.0
LinearShake => 1.0 - value
SmoothShake => 1.0 - value * value * (3.0 - 2.0 * value)
ExponentialShake(power) => @math.pow(1.0 - value, power.max(0.000001))
}
}
///|
pub fn apply_shake_falloff(
value : Point2,
mode : ShakeFalloff2,
ratio : Double,
) -> Point2 {
let scale = shake_falloff_value(mode, ratio)
point2(x=value.x() * scale, y=value.y() * scale)
}
///|
pub fn deterministic_jitter(seed : Int, index : Int) -> Double {
noise_hash(seed, index, 0)
}