/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2007-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.renderer3d.utils; import java.util.Random; /** * Utility with various math functions. * * @author Hans H�ggstr�m */ public final class MathUtils { //====================================================================== // Public Constants /** * Multiply with this to convert an angle from degrees to radians */ public static final float DEGREES_TO_RADIANS = (float) ( Math.PI / 180.0 ); /** * Multiply with this to convert an angle from radians to degrees */ public static final float RADIANS_TO_DEGREES = (float) ( 180.0 / Math.PI ); /** * The golden ratio. * <p/> * See e.g. <a href="http://en.wikipedia.org/wiki/Golden_ratio">Wikipedia on Golden Ratio</a>. */ public static final float GOLDEN_RATIO = 1.618033989f; //====================================================================== // Private Constants private static final Random theTempRandom = new Random(); //====================================================================== // Public Methods //---------------------------------------------------------------------- // Static Methods /** * @param value * * @return the input value, clamped to the 0..1 range. */ public static float clampToZeroToOne( float value ) { if ( value < 0 ) { value = 0; } if ( value > 1 ) { value = 1; } return value; } /** * Clamps the given value to the -1..1 range. */ public static float clampToMinusOneToOne( float value ) { if ( value < -1 ) { value = -1; } if ( value > 1 ) { value = 1; } return value; } /** * Does a linear interpolation. * * @param t when 0, the result is a, when 1, the result is b. * @param a value at start of range * @param b value at end of range * * @return an interpolated value between a and b (or beyond), with the relative position t. */ public static float interpolate( float t, float a, float b ) { return a + t * ( b - a ); } /** * Does a linear interpolation using doubles * * @param t when 0, the result is a, when 1, the result is b. * @param a value at start of range * @param b value at end of range * * @return an interpolated value between a and b (or beyond), with the relative position t. */ public static double interpolate( double t, double a, double b ) { return a + t * ( b - a ); } /** * Calculates a linearily interpolated value, given a start value and position, an end value and position, * and the position to get the value at. * <p/> * First calculates the relative position, then does a normal linear interpolation between the start and end value, * using the relative position as the interpolation factor. * * @param position * @param startPosition * @param endPosition * @param startValue * @param endValue * * @return the interpolated value */ public static double interpolate( final double position, final double startPosition, final double endPosition, final double startValue, final double endValue ) { final double relativePosition = ( position - startPosition ) / ( endPosition - startPosition ); return startValue + relativePosition * ( endValue - startValue ); } /** * Calculates a linearily interpolated value, given a start value and position, an end value and position, * and the position to get the value at. * <p/> * If the position is outside start or end position, it is treated as if it was at the start or end position respectively. * <p/> * First calculates the relative position, then does a normal linear interpolation between the start and end value, * using the relative position as the interpolation factor. * * @param position * @param startPosition * @param endPosition * @param startValue * @param endValue */ public static double interpolateClamp( final double position, final double startPosition, final double endPosition, final double startValue, final double endValue ) { // Clamp double p = position; if ( p < startPosition ) { p = startPosition; } else if ( p > endPosition ) { p = endPosition; } return interpolate( p, startPosition, endPosition, startValue, endValue ); } /** * Calculates a smoothly (cosine) interpolated value, given a start value, an end value, * and the position to get the value at. * * @param position * @param startPosition * @param endPosition * @param startValue * @param endValue */ public static float interpolateSmoothly( final float position, final float startPosition, final float endPosition, final float startValue, final float endValue ) { final float relativePosition = ( position - startPosition ) / ( endPosition - startPosition ); // Clamp values at edges float result; if ( relativePosition <= 0 ) { result = startValue; } else if ( relativePosition >= 1 ) { result = endValue; } else { // Cosine interpolation final double relativeSmoothPosition = ( 1.0 - Math.cos( relativePosition * Math.PI ) ) / 2.0; result = (float) ( startValue * ( 1.0 - relativeSmoothPosition ) + endValue * relativeSmoothPosition ); } return result; } /** * Wraps the specified value, so that it is in the range 0 to rangeEnd - 1, * * @param value the value to wrap * @param rangeEnd end of range, non inclusive * * @return the wrapped value. */ public static int wrapToRange( final int value, final int rangeEnd ) { int pos = value % rangeEnd; if ( pos < 0 ) { pos += rangeEnd; } return pos; } /** * @return the smallest of the given numbers */ public static int min( final int... numbers ) { if ( numbers.length == 0 ) { throw new IllegalArgumentException( "at least one parameter is required" ); } int min = numbers[ 0 ]; for ( int i = 1; i < numbers.length; i++ ) { int number = numbers[ i ]; if ( number < min ) { min = number; } } return min; } /** * @return the largest of the given numbers */ public static int max( final int... numbers ) { if ( numbers.length == 0 ) { throw new IllegalArgumentException( "at least one parameter is required" ); } int max = numbers[ 0 ]; for ( int i = 1; i < numbers.length; i++ ) { int number = numbers[ i ]; if ( number > max ) { max = number; } } return max; } /** * Calculates an integer based on a floating point value, selecting one of the closest integer numbers randomly, * weighted by their relative closeness to the floating point number. * * @param random a random number generator to use * @param realNumber the fractional value * * @return one of the two integers closest to the realNumber, weighted by their relative closeness. */ public static int roundFloatToIntStatistically( final Random random, final float realNumber ) { // Get the floor value int result = roundDown( realNumber ); // Check if we should return the ceiling value instead if ( random.nextFloat() < calculateProbabilityOfAddingOne( realNumber, result ) ) { result += 1; } return result; } /** * Round the number down to the closest integer */ public static int roundDown( final float realNumber ) { return (int) Math.round( Math.floor( realNumber ) ); } /** * @param value the value to clamp. * @param minimum lower boundary of the range, inclusive. * @param maximum upper boundary of the range, inclusive. * * @return the value clamped to the specified range. */ @SuppressWarnings( { "AssignmentToMethodParameter" } ) public static int clamp( int value, final int minimum, final int maximum ) { if ( minimum > maximum ) { throw new IllegalArgumentException( "The minimum " + minimum + " is larger than the maximum " + maximum + ", possible bug?" ); } if ( value < minimum ) { value = minimum; } else if ( value > maximum ) { value = maximum; } return value; } /** * @param value the value to clamp. * @param minimum lower boundary of the range, inclusive. * @param maximum upper boundary of the range, inclusive. * * @return the value clamped to the specified range. */ @SuppressWarnings( { "AssignmentToMethodParameter" } ) public static double clampDouble( double value, final double minimum, final double maximum ) { if ( minimum > maximum ) { throw new IllegalArgumentException( "The minimum " + minimum + " is larger than the maximum " + maximum + ", possible bug?" ); } if ( value < minimum ) { value = minimum; } else if ( value > maximum ) { value = maximum; } return value; } /** * @param value the value to test * @param lowerInclusive lower boundary value, inclusive. * @param upperExclusive upper boundary value, exclusive. * * @return true if the value is inside the bounds, false if not. */ public static boolean isInsideBounds( final int value, final int lowerInclusive, final int upperExclusive ) { if ( lowerInclusive > upperExclusive ) { throw new IllegalArgumentException( "The lower boundary " + lowerInclusive + " is larger than the upper boundary " + upperExclusive + ", possible bug?" ); } return value >= lowerInclusive && value < upperExclusive; } public static float getRandomNumberUsingTwoFloatSeeds( final float firstSeed, final float secondSeed ) { seedRandom( theTempRandom, (long) ( firstSeed * 7237 ), (long) ( secondSeed * 5317 ) ); return theTempRandom.nextFloat(); /* Previus implementation: // Combine the two floats into one long theTempRandom.setSeed( (long)(firstSeed*7237) ^ (long)(secondSeed * 5317) ); // Make sure we get a fairly random value by reading away a few values and self-seeding the RNG theTempRandom.nextLong(); theTempRandom.setSeed( theTempRandom.nextLong() ); theTempRandom.nextLong(); return theTempRandom.nextFloat(); */ } public static long getRandomSeedUsingTwoIntSeeds( final int firstSeed, final int secondSeed ) { seedRandom( theTempRandom, (long) ( firstSeed * 7237 ), (long) ( secondSeed * 5317 ) ); return theTempRandom.nextLong(); } /** * Seeds the specified random number generator, and make the start position a bit more scrambled by reading off a few random values. * * @param random * @param seed */ public static void seedRandom( final Random random, final long seed ) { random.setSeed( seed ); random.nextLong(); random.nextLong(); random.setSeed( random.nextLong() ); random.nextLong(); random.nextLong(); random.nextLong(); } /** * Seeds the specified random number generator with the combination of two different random seeds. * Also reads off a few random values, to get the random number generator into a bit more random state. * * @param random * @param firstSeed * @param secondSeed */ public static void seedRandom( final Random random, final long firstSeed, final long secondSeed ) { // TODO: check on the net if someone has some good algorithm for this.. random.setSeed( firstSeed ); random.nextLong(); random.nextLong(); random.setSeed( random.nextLong() ^ secondSeed ); random.nextLong(); random.nextLong(); random.nextLong(); } /** * Seeds the specified random number generator with the combination of three different random seeds. * Also reads off a few random values, to get the random number generator into a bit more random state. * * @param random * @param firstSeed * @param secondSeed */ public static void seedRandom( final Random random, final long firstSeed, final long secondSeed, final long thirdSeed ) { random.setSeed( firstSeed ); random.nextLong(); random.setSeed( random.nextLong() ^ secondSeed ); random.nextLong(); random.setSeed( random.nextLong() ^ thirdSeed ); random.nextLong(); random.nextLong(); } /** * Rolls the specified integer number, by adding some offset to it, and wrapping the number around to start from * the beginning of the range if it grew past the end of the range. * <p/> * E.g. with a rangeEnd of 10, and a start value of 7, if the rollOffset is 5, the result will be 2. * * @param originalValue the value to start from when adding the rollOffset. * @param rangeEnd maximum value, non inclusive. * @param rollOffset an offset added to the originalValue. Can be any positive or negative integer. * * @return the rolled value. Will be in the range [0, rangeEnd - 1]. */ public static int rollInRange( final int originalValue, final int rangeEnd, final int rollOffset ) { if ( rangeEnd <= 0 ) { throw new IllegalArgumentException( "The rangeEnd parameter (" + rangeEnd + ") should not be smaller than the range start (0)" ); } final int rolledValue = ( originalValue + rangeEnd + ( rollOffset % rangeEnd ) ) % rangeEnd; assert rolledValue >= 0 && rolledValue < rangeEnd : "Rolled value out of permitted range. " + rolledValue; return rolledValue; } /** * @return True if (x,y) is inside the axis aligned rectangle defined by the points (x1,y1) and (x2,y2), false otherwise. */ public static boolean isInsideRectangle( final double x, final double y, final double x1, final double y1, final double x2, final double y2 ) { return x >= x1 && x < x2 && y >= y1 && y < y2; } //====================================================================== // Protected Methods protected static float calculateProbabilityOfAddingOne( final float realNumber, final int result ) { return realNumber - (float) result; } //====================================================================== // Private Methods private MathUtils() { } }