package nodebox.function; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import nodebox.graphics.Point; import nodebox.util.Geometry; import nodebox.util.MathUtils; import nodebox.util.waves.*; import java.util.*; import static com.google.common.base.Preconditions.checkArgument; import static nodebox.util.MathUtils.clamp; /** * Basic math function library. */ public class MathFunctions { public static final FunctionLibrary LIBRARY; public static final String OVERFLOW_WRAP = "wrap"; public static final String OVERFLOW_MIRROR = "mirror"; public static final String OVERFLOW_CLAMP = "clamp"; public static final String OVERFLOW_IGNORE = "ignore"; public static final String WAVE_SINE = "sine"; public static final String WAVE_SQUARE = "square"; public static final String WAVE_TRIANGLE = "triangle"; public static final String WAVE_SAWTOOTH = "sawtooth"; static { LIBRARY = JavaLibrary.ofClass("math", MathFunctions.class, "number", "integer", "makeBoolean", "negate", "abs", "add", "subtract", "multiply", "divide", "mod", "sqrt", "pow", "log", "sum", "average", "compare", "logicOperator", "min", "max", "ceil", "floor", "runningTotal", "even", "odd", "makeNumbers", "randomNumbers", "round", "sample", "range", "radians", "degrees", "angle", "distance", "coordinates", "reflect", "sin", "cos", "pi", "e", "convertRange", "wave"); } public static double number(double n) { return n; } public static long integer(long value) { return value; } public static boolean makeBoolean(boolean value) { return value; } public static double add(double n1, double n2) { return n1 + n2; } public static double subtract(double n1, double n2) { return n1 - n2; } public static double multiply(double n1, double n2) { return n1 * n2; } public static double divide(double n1, double n2) { checkArgument(n2 != 0, "Divider cannot be zero."); return n1 / n2; } public static double mod(double n1, double n2) { checkArgument(n2 != 0, "Divider cannot be zero."); return n1 % n2; } public static double sqrt(double n) { return Math.sqrt(n); } public static double pow(double n1, double n2) { return Math.pow(n1, n2); } public static double log(double n) { checkArgument(n != 0, "Value cannot be zero."); return Math.log(n); } /** * Return true if the given number is even. * * @param n The number to check. * @return true if even */ public static boolean even(double n) { return n % 2 == 0; } /** * Return true if the given number is not even. * * @param n The number to check. * @return true if odd */ public static boolean odd(double n) { return n % 2 != 0; } public static double negate(double n) { return -n; } public static double abs(double n) { return Math.abs(n); } private static boolean noValues(Iterable<?> values) { return values == null || Iterables.isEmpty(values); } public static double sum(Iterable<Double> numbers) { if (noValues(numbers)) return 0.0; double sum = 0; for (Double d : numbers) { sum += d; } return sum; } public static double average(Iterable<Double> numbers) { if (noValues(numbers)) return 0.0; double sum = 0; double counter = 0; for (Double d : numbers) { sum += d; counter++; } return sum / counter; } public static double max(Iterable<Double> numbers) { if (noValues(numbers)) return 0.0; double max = Iterables.getFirst(numbers, 0.0); for (Double d : numbers) { max = Math.max(max, d); } return max; } public static double min(Iterable<Double> numbers) { if (noValues(numbers)) return 0.0; double min = Iterables.getFirst(numbers, 0.0); for (Double d : numbers) { min = Math.min(min, d); } return min; } public static double ceil(double n) { return Math.ceil(n); } public static double floor(double n) { return Math.floor(n); } @SuppressWarnings("unchecked") public static boolean compare(Comparable o1, Comparable o2, String comparator) { int comparison = o1.compareTo(o2); if (comparator.equals("<")) { return comparison < 0; } else if (comparator.equals(">")) { return comparison > 0; } else if (comparator.equals("<=")) { return comparison <= 0; } else if (comparator.equals(">=")) { return comparison >= 0; } else if (comparator.equals("==")) { return comparison == 0; } else if (comparator.equals("!=")) { return comparison != 0; } else { throw new IllegalArgumentException("unknown comparison operation " + comparator); } } public static boolean logicOperator(Boolean b1, Boolean b2, String comparator){ if (comparator.equals("or")) { return b1 || b2; } else if (comparator.equals("and")) { return b1 && b2; } else if (comparator.equals("xor")) { return b1 ^ b2; } else { throw new IllegalArgumentException("unknown logical operation " ); } } public static List<Double> makeNumbers(String s, String separator) { if (s == null || s.length() == 0) { return ImmutableList.of(); } Iterable<String> parts; if (separator == null || separator.isEmpty()) parts = Splitter.fixedLength(1).split(s); else parts = Splitter.on(separator).split(s); ArrayList<Double> numbers = new ArrayList<Double>(); for (String part : parts) { numbers.add(Double.parseDouble(part)); } return ImmutableList.copyOf(numbers); } public static List<Double> randomNumbers(long amount, double start, double end, long seed) { Random r = MathUtils.randomFromSeed(seed); ImmutableList.Builder<Double> numbers = ImmutableList.builder(); for (int i = 0; i < amount; i++) { double v = start + (r.nextDouble() * (end - start)); numbers.add(v); } return numbers.build(); } public static long round(double a) { return Math.round(a); } public static List<Double> sample(final long amount, final double start, final double end) { if (amount == 0) return ImmutableList.of(); if (amount == 1) return ImmutableList.of(start + (end - start) / 2); // The step is the range divided by amount - 1, because we also want the end value. // If I wouldn't use amount - 1, we fall one value short of the end. // E.g. if amount = 3 between 0-100, I want 0.0, 50.0, 100.0. final double step = (end - start) / (amount - 1); ImmutableList.Builder<Double> b = ImmutableList.builder(); for (long i = 0; i < amount; i++) { b.add(start + step * i); } return b.build(); } public static List<Double> range(final double start, final double end, final double step) { if (step == 0 || start == end || (start < end && step < 0) || (start > end && step > 0)) return ImmutableList.of(); else { return ImmutableList.copyOf(new Iterable<Double>() { public Iterator<Double> iterator() { return new RangeIterator(start, end, step); } }); } } public static List<Double> runningTotal(Iterable<Double> numbers) { if (noValues(numbers)) return ImmutableList.of(0.0); double currentTotal = 0; ImmutableList.Builder<Double> b = ImmutableList.builder(); for (Double d : numbers) { b.add(currentTotal); currentTotal += d; } return b.build(); } private static final class RangeIterator implements Iterator<Double> { private final double start; private final double end; private final double step; private double next; private RangeIterator(double start, double end, double step) { this.start = start; this.end = end; this.step = step; this.next = this.start; } public boolean hasNext() { if (step > 0) return next < end; else return next > end; } public Double next() { if (Thread.currentThread().isInterrupted()) throw new RuntimeException("interrupt"); //if (Thread.interrupted()) throw new RuntimeException("interrupt"); if (!hasNext()) throw new NoSuchElementException(); double result = next; next += step; return result; } public void remove() { throw new UnsupportedOperationException(); } } public static double radians(double degrees) { return Geometry.radians(degrees); } public static double degrees(double radians) { return Geometry.degrees(radians); } /** * Calculate the angle between two points. * * @param p1 The first point. * @param p2 The second point. * @return The angle in radians. */ public static double angle(Point p1, Point p2) { return Geometry.angle(p1.x, p1.y, p2.x, p2.y); } /** * The distance between two points. */ public static double distance(Point p1, Point p2) { return Geometry.distance(p1.x, p1.y, p2.x, p2.y); } /** * The location of a point based on angle and distance. */ public static Point coordinates(Point p, double angle, double distance) { double x = p.x + Math.cos(radians(angle)) * distance; double y = p.y + Math.sin(radians(angle)) * distance; return new Point(x, y); } /** * The reflection of a point through an origin point. */ public static Point reflect(Point p1, Point p2, double angle, double distance) { distance *= distance(p1, p2); angle += angle(p1, p2); return coordinates(p1, angle, distance); } public static double sin(double n) { return Math.sin(n); } public static double cos(double n) { return Math.cos(n); } public static double pi() { return Math.PI; } public static double e() { return Math.E; } public static double convertRange(double value, double srcMin, double srcMax, double targetMin, double targetMax, String overflowMethod) { if (overflowMethod.equals(OVERFLOW_WRAP)) { value = srcMin + value % (srcMax - srcMin); } else if (overflowMethod.equals(OVERFLOW_MIRROR)) { double rest = value % (srcMax - srcMin); if ((int) (value / (srcMax - srcMin)) % 2 == 1) value = srcMax - rest; else value = srcMin + rest; } else if (overflowMethod.equals(OVERFLOW_CLAMP)) { value = clamp(value, srcMin, srcMax); } // Convert value to 0.0-1.0 range. try { value = (value - srcMin) / (srcMax - srcMin); } catch (ArithmeticException e) { value = srcMin; } // Convert value to target range. return targetMin + value * (targetMax - targetMin); } public static double wave(double min, double max, double period, double offset, String waveType) { float fmin = (float) min; float fmax = (float) max; float fperiod = (float) period; AbstractWave wave; if (waveType.equals(WAVE_TRIANGLE)) wave = TriangleWave.from(fmin, fmax, fperiod); else if (waveType.equals(WAVE_SQUARE)) wave = SquareWave.from(fmin, fmax, fperiod); else if (waveType.equals(WAVE_SAWTOOTH)) wave = SawtoothWave.from(fmin, fmax, fperiod); else wave = SineWave.from(fmin, fmax, fperiod); return wave.getValueAt((float) offset); } }