// MIT License - Copyright (c) 2023 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Core.Extension { using System; using System.Collections.Generic; using Helper; using Random; using UnityEngine; using UnityEngine.Pool; using WallstopStudios.UnityHelpers.Utils; /// /// Provides extension methods for generating random Unity types (vectors, quaternions, colors) using the IRandom interface. /// /// /// Thread Safety: All methods are thread-safe if the IRandom implementation provided is thread-safe. /// Performance: Most methods are O(1). NextSubset is O(n) where n is the collection size. /// public static class RandomExtensions { /// /// Returns an index sampled from the provided weights (unnormalized). Negative weights are treated as zero. /// public static int NextWeightedIndex(this IRandom random, IReadOnlyList weights) { if (weights == null) { throw new ArgumentNullException(nameof(weights)); } if (weights.Count == 0) { throw new ArgumentException("Weights cannot be empty", nameof(weights)); } double total = 0; for (int i = 0; i < weights.Count; i++) { if (weights[i] > 0) { total += weights[i]; } } if (total <= 0) { throw new ArgumentException("Sum of weights must be > 0", nameof(weights)); } double r = random.NextDouble() * total; double acc = 0; for (int i = 0; i < weights.Count; i++) { float w = weights[i]; if (w <= 0) { continue; } acc += w; if (r <= acc) { return i; } } return weights.Count - 1; } /// /// Returns an element sampled according to the given weights list. Throws if lengths mismatch. /// public static T NextWeightedElement( this IRandom random, IReadOnlyList items, IReadOnlyList weights ) { if (items == null) { throw new ArgumentNullException(nameof(items)); } if (weights == null) { throw new ArgumentNullException(nameof(weights)); } if (items.Count != weights.Count) { throw new ArgumentException( "Items and weights length must match.", nameof(weights) ); } int idx = random.NextWeightedIndex(weights); return items[idx]; } /// /// Generates a random 2D vector with components in the range [-amplitude, amplitude]. /// /// The random number generator to use. /// The maximum absolute value for each component (must be positive). /// A Vector2 with x and y components each in [-amplitude, amplitude]. /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - two random number generations. /// Allocations: No heap allocations (Vector2 is a value type). /// Edge Cases: Negative amplitude is normalized via absolute value. Zero amplitude returns Vector2.zero. /// public static Vector2 NextVector2(this IRandom random, float amplitude) { float range = Mathf.Abs(amplitude); if (range <= 0f) { return Vector2.zero; } return random.NextVector2(-range, range); } /// /// Randomly selects an element from a collection with no exclusions. /// /// The type of elements in the collection. /// The random number generator to use. /// The collection to select from. /// A randomly selected element from values. /// /// Null Handling: Will throw NullReferenceException if random or values is null. /// Thread Safety: Thread-safe if random is thread-safe and values is not modified during execution. /// Performance: O(1) for lists/arrays, O(n) for general enumerables. /// Allocations: Zero allocation for this overload. Materializes non-list/collection enumerables to pooled list. /// Edge Cases: Empty values collection will cause NextOf to fail. /// public static T NextOfExcept(this IRandom random, IEnumerable values) { if (values == null) { throw new ArgumentNullException(nameof(values)); } return random.NextOf(values); } /// /// Randomly selects an element from a collection, excluding one specified value. /// /// The type of elements in the collection. /// The random number generator to use. /// The collection to select from. /// The value to exclude from selection. /// A randomly selected element from values that is not the excluded value. /// /// Null Handling: Will throw NullReferenceException if random or values is null. /// Thread Safety: Thread-safe if random is thread-safe and values is not modified during execution. /// Performance: O(n) where n is collection size. /// Allocations: Zero allocation - uses pooled collections internally. /// Edge Cases: Throws if all values are excluded. Empty values collection will fail. /// public static T NextOfExcept(this IRandom random, IEnumerable values, T exception1) { if (values == null) { throw new ArgumentNullException(nameof(values)); } if (values is IReadOnlyList source) { return NextOfExceptCore(random, source, exception1); } using PooledResource> lease = Buffers.List.Get(out List materializedList); materializedList.AddRange(values); return NextOfExceptCore(random, materializedList, exception1); } /// /// Randomly selects an element from a collection, excluding two specified values. /// /// The type of elements in the collection. /// The random number generator to use. /// The collection to select from. /// The first value to exclude from selection. /// The second value to exclude from selection. /// A randomly selected element from values that is not one of the excluded values. /// /// Null Handling: Will throw NullReferenceException if random or values is null. /// Thread Safety: Thread-safe if random is thread-safe and values is not modified during execution. /// Performance: O(n) where n is collection size. /// Allocations: Zero allocation - uses pooled collections internally. /// Edge Cases: Throws if all values are excluded. Empty values collection will fail. /// public static T NextOfExcept( this IRandom random, IEnumerable values, T exception1, T exception2 ) { if (values == null) { throw new ArgumentNullException(nameof(values)); } if (values is IReadOnlyList source) { return NextOfExceptCore(random, source, exception1, exception2); } using PooledResource> lease = Buffers.List.Get(out List materializedList); materializedList.AddRange(values); return NextOfExceptCore(random, materializedList, exception1, exception2); } /// /// Randomly selects an element from a collection, excluding three specified values. /// /// The type of elements in the collection. /// The random number generator to use. /// The collection to select from. /// The first value to exclude from selection. /// The second value to exclude from selection. /// The third value to exclude from selection. /// A randomly selected element from values that is not one of the excluded values. /// /// Null Handling: Will throw NullReferenceException if random or values is null. /// Thread Safety: Thread-safe if random is thread-safe and values is not modified during execution. /// Performance: O(n) where n is collection size. /// Allocations: Zero allocation - uses pooled collections internally. /// Edge Cases: Throws if all values are excluded. Empty values collection will fail. /// public static T NextOfExcept( this IRandom random, IEnumerable values, T exception1, T exception2, T exception3 ) { if (values == null) { throw new ArgumentNullException(nameof(values)); } if (values is IReadOnlyList source) { return NextOfExceptCore(random, source, exception1, exception2, exception3); } using PooledResource> lease = Buffers.List.Get(out List materializedList); materializedList.AddRange(values); return NextOfExceptCore(random, materializedList, exception1, exception2, exception3); } /// /// Randomly selects an element from a collection, excluding specified exception values via IEnumerable. /// /// The type of elements in the collection. /// The random number generator to use. /// The collection to select from. /// An enumerable of values to exclude from selection. /// A randomly selected element from values that is not in exceptions. /// /// Null Handling: Will throw NullReferenceException if random or values is null. Null exceptions treated as empty. /// Thread Safety: Thread-safe if random is thread-safe and values is not modified during execution. /// Performance: O(n*k) where n is collection size and k is exceptions count. /// Allocations: Uses pooled collections internally. Does not allocate params array. /// Edge Cases: Throws if all values are excluded. Empty values collection will fail. /// public static T NextOfExcept( this IRandom random, IEnumerable values, IEnumerable exceptions ) { if (values == null) { throw new ArgumentNullException(nameof(values)); } if (values is IReadOnlyList source) { return NextOfExceptCore(random, source, exceptions); } using PooledResource> lease = Buffers.List.Get(out List materializedList); materializedList.AddRange(values); return NextOfExceptCore(random, materializedList, exceptions); } /// /// Randomly selects an element from a collection, excluding specified exception values. /// /// The type of elements in the collection. /// The random number generator to use. /// The collection to select from. /// Values to exclude from selection. /// A randomly selected element from values that is not in exceptions. /// /// Null Handling: Will throw NullReferenceException if random or values is null. /// Thread Safety: Thread-safe if random is thread-safe and values is not modified during execution. /// Performance: O(k*n) worst case where k is number of exceptions and n is selection attempts. /// Allocations: This params overload allocates an array on each call. Prefer the specific 0-3 arg overloads /// or the IEnumerable overload for zero-allocation hot paths. /// Edge Cases: Throws if all values are excluded. Empty values collection will cause NextOf to fail. /// public static T NextOfExcept( this IRandom random, IEnumerable values, params T[] exceptions ) { if (values == null) { throw new ArgumentNullException(nameof(values)); } if (values is IReadOnlyList source) { return NextOfExceptCore(random, source, exceptions); } using PooledResource> lease = Buffers.List.Get(out List materializedList); materializedList.AddRange(values); return NextOfExceptCore(random, materializedList, exceptions); } private static T NextOfExceptCore(IRandom random, IReadOnlyList source, T exception1) { if (source.Count == 0) { throw new ArgumentException("Collection cannot be empty", nameof(source)); } EqualityComparer comparer = EqualityComparer.Default; using PooledArray pooled = SystemArrayPool.Get(source.Count, out T[] buffer); int n = 0; for (int i = 0; i < source.Count; ++i) { T v = source[i]; if (!comparer.Equals(v, exception1)) { buffer[n++] = v; } } if (n == 0) { throw new ArgumentException("All values are excluded", nameof(exception1)); } return n == 1 ? buffer[0] : buffer[random.Next(n)]; } private static T NextOfExceptCore( IRandom random, IReadOnlyList source, T exception1, T exception2 ) { if (source.Count == 0) { throw new ArgumentException("Collection cannot be empty", nameof(source)); } EqualityComparer comparer = EqualityComparer.Default; using PooledArray pooled = SystemArrayPool.Get(source.Count, out T[] buffer); int n = 0; for (int i = 0; i < source.Count; ++i) { T v = source[i]; if (!comparer.Equals(v, exception1) && !comparer.Equals(v, exception2)) { buffer[n++] = v; } } if (n == 0) { throw new ArgumentException("All values are excluded", nameof(exception1)); } return n == 1 ? buffer[0] : buffer[random.Next(n)]; } private static T NextOfExceptCore( IRandom random, IReadOnlyList source, T exception1, T exception2, T exception3 ) { if (source.Count == 0) { throw new ArgumentException("Collection cannot be empty", nameof(source)); } EqualityComparer comparer = EqualityComparer.Default; using PooledArray pooled = SystemArrayPool.Get(source.Count, out T[] buffer); int n = 0; for (int i = 0; i < source.Count; ++i) { T v = source[i]; if ( !comparer.Equals(v, exception1) && !comparer.Equals(v, exception2) && !comparer.Equals(v, exception3) ) { buffer[n++] = v; } } if (n == 0) { throw new ArgumentException("All values are excluded", nameof(exception1)); } return n == 1 ? buffer[0] : buffer[random.Next(n)]; } private static T NextOfExceptCore( IRandom random, IReadOnlyList source, IEnumerable exceptions ) { if (source.Count == 0) { throw new ArgumentException("Collection cannot be empty", nameof(source)); } if (exceptions == null) { return random.NextOf(source); } using PooledResource> excludeLease = Buffers.HashSet.Get( out HashSet exclude ); if (exceptions is IReadOnlyList exceptionList) { for (int i = 0; i < exceptionList.Count; ++i) { exclude.Add(exceptionList[i]); } } else { foreach (T exception in exceptions) { exclude.Add(exception); } } if (exclude.Count == 0) { return random.NextOf(source); } using PooledArray pooled = SystemArrayPool.Get(source.Count, out T[] buffer); int n = 0; for (int i = 0; i < source.Count; ++i) { T v = source[i]; if (!exclude.Contains(v)) { buffer[n++] = v; } } if (n == 0) { throw new ArgumentException("All values are excluded", nameof(exceptions)); } return n == 1 ? buffer[0] : buffer[random.Next(n)]; } private static T NextOfExceptCore( IRandom random, IReadOnlyList source, T[] exceptions ) { if (source.Count == 0) { throw new ArgumentException("Collection cannot be empty", nameof(source)); } if (exceptions == null || exceptions.Length == 0) { return random.NextOf(source); } using PooledResource> excludeLease = Buffers.HashSet.Get( out HashSet exclude ); for (int i = 0; i < exceptions.Length; ++i) { exclude.Add(exceptions[i]); } using PooledArray pooled = SystemArrayPool.Get(source.Count, out T[] buffer); int n = 0; for (int i = 0; i < source.Count; ++i) { T v = source[i]; if (!exclude.Contains(v)) { buffer[n++] = v; } } if (n == 0) { throw new ArgumentException("All values are excluded", nameof(exceptions)); } return n == 1 ? buffer[0] : buffer[random.Next(n)]; } /// /// Generates a random 2D vector with components in the specified range. /// /// The random number generator to use. /// The minimum value for each component (inclusive). /// The maximum value for each component (exclusive). /// A Vector2 with x and y components each in [minAmplitude, maxAmplitude). /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - two random number generations. /// Allocations: No heap allocations. /// Edge Cases: If minAmplitude >= maxAmplitude, behavior depends on IRandom.NextFloat implementation. /// public static Vector2 NextVector2( this IRandom random, float minAmplitude, float maxAmplitude ) { float x = random.NextFloat(minAmplitude, maxAmplitude); float y = random.NextFloat(minAmplitude, maxAmplitude); return new Vector2(x, y); } /// /// Generates a random 2D point uniformly distributed within a circular area. /// /// The random number generator to use. /// The radius of the circle. /// The center of the circle (default: Vector2.zero). /// A Vector2 uniformly distributed within the circle defined by origin and range. /// /// Null Handling: Will throw NullReferenceException if random is null. Null origin defaults to Vector2.zero. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - uses square root for uniform distribution. /// Allocations: No heap allocations. /// Edge Cases: Negative range is normalized via absolute value. Zero range returns origin. /// public static Vector2 NextVector2InRange( this IRandom random, float range, Vector2? origin = null ) { float radius = Mathf.Abs(range); if (radius <= 0f) { return origin ?? Vector2.zero; } return Helpers.GetRandomPointInCircle(origin ?? Vector2.zero, radius, random); } /// /// Generates a random 3D vector with components in the range [-amplitude, amplitude]. /// /// The random number generator to use. /// The maximum absolute value for each component. /// A Vector3 with x, y, and z components each in [-amplitude, amplitude]. /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - three random number generations. /// Allocations: No heap allocations. /// Edge Cases: Negative amplitude is normalized via absolute value. Zero amplitude returns Vector3.zero. /// public static Vector3 NextVector3(this IRandom random, float amplitude) { float range = Mathf.Abs(amplitude); if (range <= 0f) { return Vector3.zero; } return random.NextVector3(-range, range); } /// /// Generates a random 3D vector with components in the specified range. /// /// The random number generator to use. /// The minimum value for each component (inclusive). /// The maximum value for each component (exclusive). /// A Vector3 with x, y, and z components each in [minAmplitude, maxAmplitude). /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - three random number generations. /// Allocations: No heap allocations. /// Edge Cases: If minAmplitude >= maxAmplitude, behavior depends on IRandom.NextFloat implementation. /// public static Vector3 NextVector3( this IRandom random, float minAmplitude, float maxAmplitude ) { float z = random.NextFloat(minAmplitude, maxAmplitude); Vector3 result = random.NextVector2(minAmplitude, maxAmplitude); result.z = z; return result; } /// /// Generates a random 3D point uniformly distributed within a spherical volume. /// /// The random number generator to use. /// The radius of the sphere. /// The center of the sphere (default: Vector3.zero). /// A Vector3 uniformly distributed within the sphere defined by origin and range. /// /// Null Handling: Will throw NullReferenceException if random is null. Null origin defaults to Vector3.zero. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - uses cube root for uniform distribution. /// Allocations: No heap allocations. /// Edge Cases: Negative range is normalized via absolute value. Zero range returns origin. /// public static Vector3 NextVector3InRange( this IRandom random, float range, Vector3? origin = null ) { float radius = Mathf.Abs(range); if (radius <= 0f) { return origin ?? Vector3.zero; } return Helpers.GetRandomPointInSphere(origin ?? Vector3.zero, radius, random); } /// /// Generates a random 3D point uniformly distributed on the surface of a sphere using Marsaglia's method. /// /// The random number generator to use. /// The radius of the sphere. /// The center of the sphere (default: Vector3.zero). /// A Vector3 on the surface of the sphere with exact distance 'radius' from center. /// /// Null Handling: Will throw NullReferenceException if random is null. Null center defaults to Vector3.zero. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) average case - Marsaglia rejection sampling averages ~1.3 iterations. Uses square root. /// Allocations: No heap allocations. /// Edge Cases: Very small radius (near zero) works correctly. Negative radius is treated as its absolute value. /// public static Vector3 NextVector3OnSphere( this IRandom random, float radius, Vector3? center = null ) { const int MaxAttempts = 128; const float MinLengthSquared = 0.0001f; float radiusMagnitude = Mathf.Abs(radius); if (radiusMagnitude <= 0f) { return center ?? Vector3.zero; } for (int attempt = 0; attempt < MaxAttempts; ++attempt) { float x = random.NextFloat(-1f, 1f); float y = random.NextFloat(-1f, 1f); float z = random.NextFloat(-1f, 1f); float lengthSquared = x * x + y * y + z * z; if ( !float.IsFinite(lengthSquared) || lengthSquared > 1f || lengthSquared < MinLengthSquared ) { continue; } float invLength = radiusMagnitude / Mathf.Sqrt(lengthSquared); Vector3 sampled = new(x * invLength, y * invLength, z * invLength); return center.HasValue ? sampled + center.Value : sampled; } Vector3 fallback = new(radiusMagnitude, 0f, 0f); return center.HasValue ? fallback + center.Value : fallback; } /// /// Generates a random 3D point uniformly distributed within a spherical volume. /// /// The random number generator to use. /// The radius of the sphere. /// The center of the sphere (default: Vector3.zero). /// A Vector3 uniformly distributed within the sphere defined by center and radius. /// /// Null Handling: Will throw NullReferenceException if random is null. Null center defaults to Vector3.zero. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - uses cube root for uniform volumetric distribution. /// Allocations: No heap allocations. /// Edge Cases: Negative radius is treated as its absolute value. Zero radius returns center. /// public static Vector3 NextVector3InSphere( this IRandom random, float radius, Vector3? center = null ) { return Helpers.GetRandomPointInSphere(center ?? Vector3.zero, radius, random); } /// /// Generates a uniformly distributed random rotation quaternion using Shoemake's algorithm. /// /// The random number generator to use. /// A uniformly distributed rotation quaternion (all orientations equally likely). /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - involves square roots and trigonometric functions. /// Allocations: No heap allocations. /// Edge Cases: Produces normalized quaternions. Algorithm based on Shoemake, "Uniform Random Rotations", Graphics Gems III. /// public static Quaternion NextQuaternion(this IRandom random) { // Uniform random rotation using Shoemake's algorithm float u1 = Helpers.ClampUnitInterval(random.NextFloat()); float u2 = Helpers.ClampUnitInterval(random.NextFloat()); float u3 = Helpers.ClampUnitInterval(random.NextFloat()); float sqrt1MinusU1 = Mathf.Sqrt(1f - u1); float sqrtU1 = Mathf.Sqrt(u1); float twoPiU2 = 2f * Mathf.PI * u2; float twoPiU3 = 2f * Mathf.PI * u3; return new Quaternion( sqrt1MinusU1 * Mathf.Sin(twoPiU2), sqrt1MinusU1 * Mathf.Cos(twoPiU2), sqrtU1 * Mathf.Sin(twoPiU3), sqrtU1 * Mathf.Cos(twoPiU3) ); } /// /// Generates a random rotation around a specified axis within an angle range. /// /// The random number generator to use. /// The axis to rotate around (will be normalized). /// The minimum rotation angle in degrees (inclusive). /// The maximum rotation angle in degrees (exclusive). /// A quaternion representing a rotation around axis by a random angle in [minAngle, maxAngle). /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - involves vector normalization and quaternion construction. /// Allocations: No heap allocations. /// Edge Cases: Zero-length axis will produce undefined results from normalization. /// public static Quaternion NextQuaternionAxisAngle( this IRandom random, Vector3 axis, float minAngle, float maxAngle ) { float angle = random.NextFloat(minAngle, maxAngle); return Quaternion.AngleAxis(angle, axis.normalized); } /// /// Generates a random rotation that would make an object "look" in a random direction. /// /// The random number generator to use. /// A quaternion representing a look rotation toward a random 3D direction. /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - involves sphere sampling and look rotation calculation. /// Allocations: No heap allocations. /// Edge Cases: The "up" direction for LookRotation is always Vector3.up, which may cause issues near poles. /// public static Quaternion NextQuaternionLookRotation(this IRandom random) { Vector3 direction = random.NextDirection3D(); return Quaternion.LookRotation(direction); } /// /// Generates a random color with RGB components uniformly distributed in [0, 1]. /// /// The random number generator to use. /// If true, alpha is random [0, 1]; if false, alpha is 1.0 (opaque). /// A random Color with all components in [0, 1]. /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - three or four random float generations. /// Allocations: No heap allocations. /// Edge Cases: None - all values are clamped to valid Color range. /// public static Color NextColor(this IRandom random, bool randomAlpha = false) { float r = random.NextFloat(); float g = random.NextFloat(); float b = random.NextFloat(); float a = randomAlpha ? random.NextFloat() : 1f; return new Color(r, g, b, a); } /// /// Generates a random color within a specified variance range from a base color in HSV space. /// /// The random number generator to use. /// The base color to vary from. /// The maximum hue deviation (0-1 scale, wraps around). /// The maximum saturation deviation (clamped to [0, 1]). /// The maximum value/brightness deviation (clamped to [0, 1]). /// A color randomly varied from baseColor within the specified HSV ranges, with HDR enabled. /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - involves RGB-to-HSV conversion, random generation, and HSV-to-RGB conversion. /// Allocations: No heap allocations. /// Edge Cases: Hue wraps around at boundaries (0 and 1 are adjacent). Saturation and value are clamped. /// public static Color NextColorInRange( this IRandom random, Color baseColor, float hueVariance, float saturationVariance, float valueVariance ) { Color.RGBToHSV(baseColor, out float h, out float s, out float v); h += random.NextFloat(-hueVariance, hueVariance); h = Mathf.Repeat(h, 1f); // Wrap hue to [0, 1] s = Mathf.Clamp01(s + random.NextFloat(-saturationVariance, saturationVariance)); v = Mathf.Clamp01(v + random.NextFloat(-valueVariance, valueVariance)); return Color.HSVToRGB(h, s, v, true); } /// /// Generates a random 32-bit color with RGB components uniformly distributed in [0, 255]. /// /// The random number generator to use. /// If true, alpha is random [0, 255]; if false, alpha is 255 (opaque). /// A random Color32 with byte-precision components. /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - three or four random byte generations. /// Allocations: No heap allocations. /// Edge Cases: None - all byte values are valid. /// public static Color32 NextColor32(this IRandom random, bool randomAlpha = false) { byte r = random.NextByte(); byte g = random.NextByte(); byte b = random.NextByte(); byte a = randomAlpha ? random.NextByte() : (byte)255; return new Color32(r, g, b, a); } /// /// Generates a random 2D integer vector with components in the range [-amplitude, amplitude). /// /// The random number generator to use. /// The maximum absolute value for each component. /// A Vector2Int with x and y components each in [-amplitude, amplitude). /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - two random integer generations. /// Allocations: No heap allocations. /// Edge Cases: Negative amplitude is normalized via absolute value. Zero amplitude returns Vector2Int.zero. /// public static Vector2Int NextVector2Int(this IRandom random, int amplitude) { int range = Mathf.Abs(amplitude); if (range == 0) { return Vector2Int.zero; } return random.NextVector2Int(-range, range); } /// /// Generates a random 2D integer vector with components in the specified range. /// /// The random number generator to use. /// The minimum value for each component (inclusive). /// The maximum value for each component (exclusive). /// A Vector2Int with x and y components each in [minAmplitude, maxAmplitude). /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - two random integer generations. /// Allocations: No heap allocations. /// Edge Cases: If minAmplitude >= maxAmplitude, behavior depends on IRandom.Next implementation. /// public static Vector2Int NextVector2Int( this IRandom random, int minAmplitude, int maxAmplitude ) { int x = random.Next(minAmplitude, maxAmplitude); int y = random.Next(minAmplitude, maxAmplitude); return new Vector2Int(x, y); } /// /// Generates a random 2D integer vector with components independently bounded by min and max vectors. /// /// The random number generator to use. /// The minimum bounds (inclusive) for each component. /// The maximum bounds (exclusive) for each component. /// A Vector2Int with x in [min.x, max.x) and y in [min.y, max.y). /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - two random integer generations. /// Allocations: No heap allocations. /// Edge Cases: If any min component >= corresponding max component, behavior depends on IRandom.Next. /// public static Vector2Int NextVector2Int(this IRandom random, Vector2Int min, Vector2Int max) { int x = random.Next(min.x, max.x); int y = random.Next(min.y, max.y); return new Vector2Int(x, y); } /// /// Generates a random 3D integer vector with components in the range [-amplitude, amplitude). /// /// The random number generator to use. /// The maximum absolute value for each component. /// A Vector3Int with x, y, and z components each in [-amplitude, amplitude). /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - three random integer generations. /// Allocations: No heap allocations. /// Edge Cases: Negative amplitude is normalized via absolute value. Zero amplitude returns Vector3Int.zero. /// public static Vector3Int NextVector3Int(this IRandom random, int amplitude) { int range = Mathf.Abs(amplitude); if (range == 0) { return Vector3Int.zero; } return random.NextVector3Int(-range, range); } /// /// Generates a random 3D integer vector with components in the specified range. /// /// The random number generator to use. /// The minimum value for each component (inclusive). /// The maximum value for each component (exclusive). /// A Vector3Int with x, y, and z components each in [minAmplitude, maxAmplitude). /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - three random integer generations. /// Allocations: No heap allocations. /// Edge Cases: If minAmplitude >= maxAmplitude, behavior depends on IRandom.Next implementation. /// public static Vector3Int NextVector3Int( this IRandom random, int minAmplitude, int maxAmplitude ) { int x = random.Next(minAmplitude, maxAmplitude); int y = random.Next(minAmplitude, maxAmplitude); int z = random.Next(minAmplitude, maxAmplitude); return new Vector3Int(x, y, z); } /// /// Generates a random 3D integer vector with components independently bounded by min and max vectors. /// /// The random number generator to use. /// The minimum bounds (inclusive) for each component. /// The maximum bounds (exclusive) for each component. /// A Vector3Int with components independently ranged: x in [min.x, max.x), y in [min.y, max.y), z in [min.z, max.z). /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - three random integer generations. /// Allocations: No heap allocations. /// Edge Cases: If any min component >= corresponding max component, behavior depends on IRandom.Next. /// public static Vector3Int NextVector3Int(this IRandom random, Vector3Int min, Vector3Int max) { int x = random.Next(min.x, max.x); int y = random.Next(min.y, max.y); int z = random.Next(min.z, max.z); return new Vector3Int(x, y, z); } /// /// Generates a uniformly distributed random 2D unit direction vector. /// /// The random number generator to use. /// A normalized Vector2 pointing in a random direction (magnitude 1.0). /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - one random generation and two trigonometric functions. /// Allocations: No heap allocations. /// Edge Cases: Always returns a normalized vector with magnitude 1.0. /// public static Vector2 NextDirection2D(this IRandom random) { float angle = random.NextFloat(0f, 2f * Mathf.PI); return new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)); } /// /// Generates a uniformly distributed random 3D unit direction vector. /// /// The random number generator to use. /// A normalized Vector3 pointing in a random direction (magnitude 1.0). /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) average - uses Marsaglia sphere sampling. /// Allocations: No heap allocations. /// Edge Cases: Always returns a normalized vector with magnitude 1.0. /// public static Vector3 NextDirection3D(this IRandom random) { return random.NextVector3OnSphere(1f, Vector3.zero); } /// /// Generates a random angle in degrees within the specified range. /// /// The random number generator to use. /// The minimum angle in degrees (inclusive, default: 0). /// The maximum angle in degrees (exclusive, default: 360). /// A random angle in degrees within [min, max). /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - single random float generation. /// Allocations: No heap allocations. /// Edge Cases: Does not normalize angle to [0, 360) - can return negative or >360 values if range allows. /// public static float NextAngle(this IRandom random, float min = 0f, float max = 360f) { return random.NextFloat(min, max); } /// /// Generates a random 2D point uniformly distributed within a rectangle. /// /// The random number generator to use. /// The bounding rectangle to generate points within. /// A Vector2 uniformly distributed within the rect bounds. /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - two random float generations. /// Allocations: No heap allocations. /// Edge Cases: Works with negative or inverted rectangles. Zero-area rectangles return the min corner. /// public static Vector2 NextVector2InRect(this IRandom random, Rect rect) { float xMin = Mathf.Min(rect.xMin, rect.xMax); float xMax = Mathf.Max(rect.xMin, rect.xMax); float yMin = Mathf.Min(rect.yMin, rect.yMax); float yMax = Mathf.Max(rect.yMin, rect.yMax); float x = xMax - xMin <= 0f ? xMin : random.NextFloat(xMin, xMax); float y = yMax - yMin <= 0f ? yMin : random.NextFloat(yMin, yMax); return new Vector2(x, y); } /// /// Generates a random 3D point uniformly distributed within an axis-aligned bounding box. /// /// The random number generator to use. /// The bounding box to generate points within. /// A Vector3 uniformly distributed within the bounds. /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - three random float generations. /// Allocations: No heap allocations. /// Edge Cases: Degenerate (zero-volume) bounds return the center point. /// public static Vector3 NextVector3InBounds(this IRandom random, Bounds bounds) { Vector3 size = bounds.size; if (size.x <= 0f || size.y <= 0f || size.z <= 0f) { return bounds.center; } Vector3 min = bounds.min; Vector3 max = bounds.max; float x = random.NextFloat(min.x, max.x); float y = random.NextFloat(min.y, max.y); float z = random.NextFloat(min.z, max.z); return new Vector3(x, y, z); } /// /// Selects a random item from a weighted collection where each item has an associated probability weight. /// /// The type of items in the collection. /// The random number generator to use. /// A collection of (item, weight) tuples where weight determines selection probability. /// A randomly selected item, with probability proportional to its weight relative to total weight. /// /// Thrown if collection is empty, any weight is negative, or total weight is zero or negative. /// /// /// Null Handling: Will throw NullReferenceException if random or weighted is null. /// Thread Safety: Thread-safe if random is thread-safe and weighted is not modified during execution. /// Performance: O(n) where n is the number of items - iterates twice (sum weights, then select). /// Allocations: Materializes non-list collections to array on first pass. /// Edge Cases: Due to floating point precision, may return last item if randomValue equals totalWeight. /// public static T NextWeighted( this IRandom random, IEnumerable<(T item, float weight)> weighted ) { if (weighted is IReadOnlyList<(T, float)> items) { return NextWeightedCore(random, items); } // Materialize enumerable to pooled list - AddRange is preferred for performance: // it checks for ICollection and pre-allocates, and uses Array.Copy for arrays/lists using PooledResource> lease = Buffers<(T, float)>.List.Get( out List<(T, float)> materializedList ); materializedList.AddRange(weighted); return NextWeightedCore(random, materializedList); } private static T NextWeightedCore(IRandom random, IReadOnlyList<(T, float)> items) { if (items.Count == 0) { throw new ArgumentException("Weighted collection cannot be empty", nameof(items)); } float totalWeight = 0f; for (int i = 0; i < items.Count; ++i) { float weight = items[i].Item2; if (weight < 0f) { throw new ArgumentException("Weights cannot be negative", nameof(items)); } totalWeight += weight; } if (totalWeight <= 0f) { throw new ArgumentException( "Total weight must be greater than zero", nameof(items) ); } float randomValue = random.NextFloat(0f, totalWeight); float cumulative = 0f; for (int i = 0; i < items.Count; ++i) { (T item, float weight) = items[i]; cumulative += weight; if (randomValue < cumulative) { return item; } } // Fallback due to floating point precision return items[items.Count - 1].Item1; } /// /// Selects a random index from an array of weights, where each weight determines selection probability. /// /// The random number generator to use. /// An array of weights where each element determines the probability of selecting that index. /// A random index in [0, weights.Length), with probability proportional to weights[i] / totalWeight. /// /// Thrown if weights is null/empty, any weight is negative, or total weight is zero or negative. /// /// /// Null Handling: Throws ArgumentException if weights is null. /// Thread Safety: Thread-safe if random is thread-safe and weights array is not modified during execution. /// Performance: O(n) where n is weights.Length - iterates to sum weights, then to select index. /// Allocations: No heap allocations. /// Edge Cases: Due to floating point precision, may return last index if randomValue equals totalWeight. /// public static int NextWeightedIndex(this IRandom random, float[] weights) { if (weights == null || weights.Length == 0) { throw new ArgumentException( "Weights array cannot be null or empty", nameof(weights) ); } float totalWeight = 0f; for (int i = 0; i < weights.Length; ++i) { float weight = weights[i]; if (weight < 0f) { throw new ArgumentException("Weights cannot be negative", nameof(weights)); } totalWeight += weight; } if (totalWeight <= 0f) { throw new ArgumentException( "Total weight must be greater than zero", nameof(weights) ); } float randomValue = random.NextFloat(0f, totalWeight); float cumulative = 0f; for (int i = 0; i < weights.Length; ++i) { cumulative += weights[i]; if (randomValue < cumulative) { return i; } } // Fallback due to floating point precision return weights.Length - 1; } /// /// Generates a random boolean with a specified probability of being true. /// /// The random number generator to use. /// The probability [0, 1] that the result will be true. /// True with probability 'probability', false otherwise. /// Thrown if probability is not in [0, 1]. /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - single random float generation and comparison. /// Allocations: No heap allocations. /// Edge Cases: probability=0 always returns false, probability=1 always returns true. /// public static bool NextBool(this IRandom random, float probability) { if (probability < 0f || probability > 1f) { throw new ArgumentOutOfRangeException( nameof(probability), "Probability must be between 0 and 1" ); } return random.NextFloat() < probability; } /// /// Generates a random sign (1 or -1) with equal probability. /// /// The random number generator to use. /// Either 1 or -1, each with 50% probability. /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - single random boolean generation. /// Allocations: No heap allocations. /// Edge Cases: None - always returns exactly 1 or -1. /// public static int NextSign(this IRandom random) { return random.NextBool() ? 1 : -1; } /// /// Generates a random float centered around a value with specified variance. /// /// The random number generator to use. /// The center value of the range. /// The maximum deviation from center (can be positive or negative). /// A random float in [center - variance, center + variance). /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - arithmetic and single random generation. /// Allocations: No heap allocations. /// Edge Cases: Negative variance inverts the range. Zero variance returns center. /// public static float NextFloatAround(this IRandom random, float center, float variance) { if (variance <= 0f) { return center; } return random.NextFloat(center - variance, center + variance); } /// /// Generates a random integer centered around a value with specified variance. /// /// The random number generator to use. /// The center value of the range. /// The maximum deviation from center (inclusive). /// A random integer in [center - variance, center + variance]. /// /// Null Handling: Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe. /// Performance: O(1) - arithmetic and single random generation. /// Allocations: No heap allocations. /// Edge Cases: Note the +1 adjustment makes upper bound inclusive. Negative variance inverts range. /// public static int NextIntAround(this IRandom random, int center, int variance) { if (variance <= 0) { return center; } return random.Next(center - variance, center + variance + 1); } /// /// Selects a random subset of items from a collection using reservoir sampling for uniform distribution. /// /// The type of items in the collection. /// The random number generator to use. /// The collection to select from. /// The number of items to select. /// An array containing 'count' randomly selected items from the collection, with uniform probability. /// Thrown if items is null. /// Thrown if count is negative. /// Thrown if count exceeds the number of items. /// /// Null Handling: Throws ArgumentNullException if items is null. Will throw NullReferenceException if random is null. /// Thread Safety: Thread-safe if random is thread-safe and items is not modified during execution. /// Performance: O(n) where n is items.Count - uses reservoir sampling algorithm. Materializes non-list collections. /// Allocations: Uses pooled array for result (returned to pool when disposed). Materializes IEnumerable to array/list. /// Edge Cases: count=0 returns empty enumerable. Uses Algorithm R (reservoir sampling) for uniform selection probability. /// The returned array is pooled and will be returned to the pool - caller should not hold reference long-term. /// public static IEnumerable NextSubset( this IRandom random, IEnumerable items, int count ) { if (items == null) { throw new ArgumentNullException(nameof(items)); } if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count), "Count cannot be negative"); } if (items is IReadOnlyList itemsList) { if (count > itemsList.Count) { throw new ArgumentException( "Count cannot exceed the number of items", nameof(count) ); } if (count == 0) { return Array.Empty(); } return NextSubsetIterator(random, itemsList, count); } // Materialize enumerable to pooled list - AddRange is preferred for performance: // it checks for ICollection and pre-allocates, and uses Array.Copy for arrays/lists using PooledResource> lease = Buffers.List.Get(out List materializedList); materializedList.AddRange(items); if (count > materializedList.Count) { throw new ArgumentException( "Count cannot exceed the number of items", nameof(count) ); } if (count == 0) { return Array.Empty(); } return NextSubsetIterator(random, materializedList, count); } private static IEnumerable NextSubsetIterator( IRandom random, IReadOnlyList items, int count ) { using PooledArray arrayBuffer = SystemArrayPool.Get(count, out T[] result); for (int i = 0; i < count; ++i) { result[i] = items[i]; } for (int i = count; i < items.Count; ++i) { int j = random.Next(0, i + 1); if (j < count) { result[j] = items[i]; } } for (int i = 0; i < count; ++i) { yield return result[i]; } } } }