/* * Noise.cs * Authors: August Zinsser * * Copyright August Zinsser 2007 * This program is distributed under the terms of the GNU General Public License */ using System; using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework; namespace Pina3D { /// /// Generates a multi-dimensional field of solid noise. Range is the canonical random number [0,1). Note that the origin will always have /// a value of 0.5f. /// public class PseudoRandomMap { protected Vector3[] mGradientHash; // Stores hashcodes for a random 3D vectors protected int[] mIndices; // Used to look into the GradientField to produce a pseudorandom Gradient Field protected Vector3? mSize; // Mod lookups by these numbers to make the field tesselate /// /// Create a new arbitrarily dimensional field of seeded solid noise. The first 3 Dimensions /// will tesselate seamlessly after the specified number of grid points. /// /// Maximum value possible (min is 0) /// Seed for the pseudorandom number generator /// Number of grid points in the X axis /// Number of grid points in the Y axis /// Number of grid points in the Z axis public PseudoRandomMap(int seed, int xSize, int ySize, int zSize) : this(seed) { mSize = new Vector3(xSize, ySize, zSize); } /// /// Create a new arbitrarily dimensional field of seeded solid noise. /// /// public PseudoRandomMap(int seed) { Random rand = new Random(seed); // Build a random permutation of indices int n = 512; mIndices = new int[n]; List availableIndices = new List(n); for (int i = 0; i < n; i++) availableIndices.Add(i); for (int i = 0; i < n; i++) { int take = (int)(rand.NextDouble() * availableIndices.Count); mIndices[i] = availableIndices[take]; availableIndices.RemoveAt(take); } // Construct data that maps to the Gradient Field mGradientHash = new Vector3[n]; for (int i = 0; i < n; i++) { // Calculate points within the unit sphere and then get the unit vector to those points // Throwing away the "corners" of the unit cube ensures that all directions have equal probability. float lengthSqrd = 2; while (lengthSqrd > 1) { mGradientHash[i] = new Vector3((float)rand.NextDouble() * 2f - 1, (float)rand.NextDouble() * 2f - 1, (float)rand.NextDouble() * 2f - 1); lengthSqrd = mGradientHash[i].LengthSquared(); } mGradientHash[i].Normalize(); } } /// /// Returns the bicubic-interpolated value at the specified coordinates. /// public virtual float LookUp(float x, float y, float z) { // Wrap the coords if possible (creates seamless maps) if (mSize.HasValue) { x %= mSize.Value.X; y %= mSize.Value.Y; z %= mSize.Value.Z; } // Find the 8 nearest grid points int x0 = (int)Math.Floor(x); int x1 = x0 + 1; int y0 = (int)Math.Floor(y); int y1 = y0 + 1; int z0 = (int)Math.Floor(z); int z1 = z0 + 1; // Calculate Solid Noise (similar to Perlin's algorithm) // For each neighbor, find the dot product of the current point (x,y) and the gradient of said neighbor to offset the local max/mins and // get a magnitude for the current point. The final value is the weighted sum (using cubic interpolation) of the 4 nearest neighbors' // calculated magnitude. float result = GetWeightedValue(x - x0, y - y0, z - z0, GetGradientVector(x0, y0, z0)) + GetWeightedValue(x - x1, y - y0, z - z0, GetGradientVector(x1, y0, z0)) + GetWeightedValue(x - x0, y - y1, z - z0, GetGradientVector(x0, y1, z0)) + GetWeightedValue(x - x1, y - y1, z - z0, GetGradientVector(x1, y1, z0)) + GetWeightedValue(x - x0, y - y0, z - z1, GetGradientVector(x0, y0, z1)) + GetWeightedValue(x - x1, y - y0, z - z1, GetGradientVector(x1, y0, z1)) + GetWeightedValue(x - x0, y - y1, z - z1, GetGradientVector(x0, y1, z1)) + GetWeightedValue(x - x1, y - y1, z - z1, GetGradientVector(x1, y1, z1)); // Interpolate (again) the result to increase contrast return Logistic(result); } /// /// Returns the bicubic-interpolated value at the specified coordinates. /// public virtual float LookUp(float x, float y) { return LookUp(x, y, 0); } /// /// Returns the bicubic-interpolated value at the specified coordinates. /// public virtual float LookUp(float x) { return LookUp(x, 0, 0); } /* * Helper Functions */ private float GetWeightedValue(float u, float v, Vector2 value) { return CubicWeight(u) * CubicWeight(v) * (Vector2.Dot(value, new Vector2(u, v))); } private float GetWeightedValue(float u, float v, float w, Vector3 value) { return CubicWeight(u) * CubicWeight(v) * CubicWeight(w) * (Vector3.Dot(value, new Vector3(u, v, w))); } /// /// Bicubic Convolution Algorithm. Weights each point for bicubic interpolation. /// /// /// private float CubicWeight(float t) { t = (float)Math.Abs(t); float a = 0f; // Edge-cases are not handled in LookUp so moving a away from 0 exaggerates artifacts on the gridlines if (t <= 1) return (float)((a + 2) * Math.Pow(t, 3) - (a + 3) * Math.Pow(t, 2)) + 1; else if (t <= 2) return (float)(a * Math.Pow(t, 3) - 5 * a * Math.Pow(t, 2) + 8 * a * t - 4 * a); else return 0; } /// /// Logistic Function, effectifly pushing the parameter closer to the endpoints /// /// /// private float Logistic(float t) { t *= 6; t = 1f / (1f + (float)Math.Pow(MathHelper.E, -1 * t)); return t; } private Vector3 GetGradientVector(int x, int y, int z) { return mGradientHash[GetWrappedIndex(x + GetWrappedIndex(y + GetWrappedIndex(z)))]; } private int GetWrappedIndex(int i) { int index = i % mIndices.Length; if (index < 0) index += mIndices.Length; return mIndices[index]; } } /// /// Combines random maps of decreasing frequencies and amplitude to create fractal-like maps of perlin noise. /// public class PerlinNoise : PseudoRandomMap { float mInverseAmplitude; // Multiply results by this number to scale sampled spots down to the (-1,1) range float mInversePersistence; // Relates the amplitude to the frequency of the current iteration int mMaxIterations; // How many times subdivision may occur float mAmplitudeThreshold; // Cutoff for adding subdivisions /// /// High persistence creates smoother fields, and low persistence creates rockier fields. This value must be > 1, and default /// value is 2 (values <= 1 are ignored). /// public float Persistence { set { if (value > 1) mInversePersistence = 1 / value; } } /// /// Create a new arbitrarily dimensional field of seeded perlin noise. /// /// public PerlinNoise(int seed) : base(seed) { mInversePersistence = .5f; mMaxIterations = 10; mAmplitudeThreshold = .05f; // Find the maximum value (the sum of the max of each iteration) and then take the inverse mInverseAmplitude = 0; float currentAmplitude = 1; for (int i = 0; i < mMaxIterations && currentAmplitude >= mAmplitudeThreshold; i++) { currentAmplitude = (float)Math.Pow(mInversePersistence, i); mInverseAmplitude += currentAmplitude; } mInverseAmplitude = 1f / mInverseAmplitude; } /// /// Return the bicubic-interpolated value at the specified coordinates. /// public override float LookUp(float x, float y, float z) { float result = 0; float amplitude = 1; for (int i = 0; i < mMaxIterations && amplitude >= mAmplitudeThreshold; i++) { float frequency = (float)Math.Pow(2, i); amplitude = (float)Math.Pow(mInversePersistence, i); result += base.LookUp(x * frequency, y * frequency, z * frequency) * amplitude; } // Interpolate the result to increase contrast return result * mInverseAmplitude; } /// /// Performs a spline interpolation between -1 and 1 to increase contrast /// /// /// private float SplineInterpolate(float t, float slope) { return (2 * (float)Math.Pow(t, 3) - 3 * (float)Math.Pow(t, 2)); } } }