src/math.lisp @ ea581753e904

Add range functions
author Steve Losh <steve@stevelosh.com>
date Sun, 27 Sep 2020 13:47:35 -0400
parents de9d10a9b4b5
children 1b8a1cc764c4
(in-package :losh.math)


;;;; Math ---------------------------------------------------------------------
(defconstant tau (* pi 2)) ; fuck a pi

(defconstant tau/2 (* tau 1/2))
(defconstant 1/2tau (* tau 1/2))

(defconstant tau/4 (* tau 1/4))
(defconstant 1/4tau (* tau 1/4))
(defconstant 2/4tau (* tau 2/4))
(defconstant 3/4tau (* tau 3/4))

(defconstant tau/8 (* tau 1/8))
(defconstant 1/8tau (* tau 1/8))
(defconstant 2/8tau (* tau 2/8))
(defconstant 3/8tau (* tau 3/8))
(defconstant 4/8tau (* tau 4/8))
(defconstant 5/8tau (* tau 5/8))
(defconstant 6/8tau (* tau 6/8))
(defconstant 7/8tau (* tau 7/8))


(defun-inline degrees (radians)
  "Convert `radians` into degrees.

  The result will be the same type as `tau` and `pi`.

  "
  (* radians (/ 360 tau)))

(defun-inline radians (degrees)
  "Convert `degrees` into radians.

  The result will be the same type as `tau` and `pi`.

  "
  (* degrees (/ tau 360)))


(defun-inline square (x)
  (* x x))

(defun-inline dividesp (n divisor)
  "Return whether `n` is evenly divisible by `divisor`.

  The value returned will be the quotient when true, `nil` otherwise.

  "
  (multiple-value-bind (quotient remainder) (floor n divisor)
    (when (zerop remainder)
      quotient)))


(declaim (ftype (function (real real real)
                          (values real &optional))
                norm lerp precise-lerp clamp))

(declaim (ftype (function (real real real real real)
                          (values real &optional))
                map-range))


(defun-inline norm (min max val)
  "Normalize `val` to a number between `0` and `1` (maybe).

  If `val` is between `max` and `min`, the result will be a number between `0`
  and `1`.

  If `val` lies outside of the range, it'll be still be scaled and will end up
  outside the 0/1 range.

  "
  (/ (- val min)
     (- max min)))

(defun-inline lerp (from to n)
  "Lerp together `from` and `to` by factor `n`.

  You might want `precise-lerp` instead.

  "
  (+ from
     (* n (- to from))))

(defun-inline precise-lerp (from to n)
  "Lerp together `from` and `to` by factor `n`, precisely.

  Vanilla lerp does not guarantee `(lerp from to 0.0)` will return exactly
  `from` due to floating-point errors.  This version will return exactly `from`
  when given a `n` of `0.0`, at the cost of an extra multiplication.

  "
  (+ (* (- 1 n) from)
     (* n to)))

(defun-inline map-range (source-from source-to dest-from dest-to source-val)
  "Map `source-val` from the source range to the destination range.

  Example:

    ;          source    dest        value
    (map-range 0.0 1.0   10.0 20.0   0.2)
    => 12.0

  "
  (lerp dest-from dest-to
        (norm source-from source-to source-val)))

(defun-inline clamp (from to value)
  "Clamp `value` between `from` and `to`."
  (let ((max (max from to))
        (min (min from to)))
    (cond
      ((> value max) max)
      ((< value min) min)
      (t value))))

(defun-inline in-range-p (low value high)
  "Return whether `low` <= `value` < `high`."
  (and (<= low value)
       (< value high)))


(defun-inline digit (position integer &optional (base 10))
  "Return the value of the digit at `position` in `integer`.

  Examples:

    (digit 0 135) ; => 5
    (digit 1 135) ; => 3
    (digit 2 135) ; => 1

    (digit 0 #xD4 16) ; => 4
    (digit 1 #xD4 16) ; => 13

  "
  (-<> integer
    (floor <> (expt base position))
    (mod <> base)))