/* Copyright 2018 Johannes Boczek Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ use std::f64; use std::fs::File; use std::io::Result as IoResult; use worley_noise::WorleyNoise; use gif::{ Frame, Encoder, ExtensionData, Repeat }; const WIDTH: u16 = 250; const HEIGHT: u16 = 250; const SCALE: f64 = 0.025; const MAX_DEPTH: f64 = 3.0; const DEPTH_STEP: f64 = 0.05; const COLORS: [(u8, u8, u8); 5] = [(0, 50, 100), (10, 75, 125), (25, 125, 150), (175, 225, 250), (250, 255, 255)]; const OUTPUT_PATH: &'static str = "noise.gif"; /// Create a color table from an array of base colors fn create_color_table(colors: &[(u8, u8, u8)]) -> Vec<(u8, u8, u8)> { let mut table = Vec::new(); for i in 0 .. colors.len() - 1 { let (current_red, current_green, current_blue) = colors[i]; let (next_red, next_green, next_blue) = colors[i + 1]; let (diff_red, diff_green, diff_blue) = ( next_red as i16 - current_red as i16, next_green as i16 - current_green as i16, next_blue as i16 - current_blue as i16 ); let diff_max = diff_red.abs() .max(diff_green.abs()) .max(diff_blue.abs()); let (change_red, change_green, change_blue) = ( diff_red as f64 / diff_max as f64, diff_green as f64 / diff_max as f64, diff_blue as f64 / diff_max as f64 ); for j in 0 .. diff_max { let red = (current_red as f64 + (change_red * j as f64)) .min(255.0) .max(0.0) as u8; let green = (current_green as f64 + (change_green * j as f64)) .min(255.0) .max(0.0) as u8; let blue = (current_blue as f64 + (change_blue * j as f64)) .min(255.0) .max(0.0) as u8; table.push((red, green, blue)); } } table } /// Generate noise values and store them as a GIF file fn main() -> IoResult<()> { let capacity = (WIDTH as f64 * HEIGHT as f64 * MAX_DEPTH * SCALE) as usize; let mut noise = WorleyNoise::with_cache_capacity(capacity); let color_table = create_color_table(&COLORS); let mut encoder = Encoder::new(File::create(OUTPUT_PATH)?, WIDTH, HEIGHT, &[]).unwrap(); let mut z = 0.0; let mut z_progress = 0.0; //Keeps track of when to clear the cache so we don't use tons of memory /* * We want to generate multiple images so we can animate them */ while z < MAX_DEPTH { let mut points = Vec::with_capacity(capacity); /* * Fill sample points */ for y in 0 .. HEIGHT { for x in 0 .. WIDTH { points.push((x as f64 * SCALE, y as f64 * SCALE, z as f64)); } } /* * Value function: Subtract smallest from second smallest value * Generates a crystal-like pattern */ noise.set_value_function(|distances| { let mut min = f64::MAX; let mut second_min = f64::MAX; for &distance in distances.iter() { if distance < min { second_min = min; min = distance; } else if distance < second_min { second_min = distance; } } min }); /* * Sample points, find maximum for normalization */ let values = noise.values_3d(&points); let max = values.iter().fold(0.0, |a, b| f64::max(a, *b)); let mut color_values = Vec::with_capacity(values.len() * 3); /* * Normalize values and map them to colors using the color table */ for val in values { let val = (val / max).abs().min(1.0); let index = (val * (color_table.len() - 1) as f64) as usize; let (red, green, blue) = color_table[index]; color_values.push(red); color_values.push(green); color_values.push(blue); } /* * Create and encode a GIF frame */ let frame = Frame::from_rgb(WIDTH, HEIGHT, &color_values); encoder.write_frame(&frame).unwrap(); z += DEPTH_STEP; z_progress += DEPTH_STEP; /* * If we are more than 2.0 cubes deep we clear the cache to free up memory */ if z_progress > 2.0 { noise.clear_cache(); z_progress = 0.0; } } /* * Make the GIF loop */ encoder.write_extension(ExtensionData::Repetitions(Repeat::Infinite)).unwrap(); Ok(()) }