GameLib is a collection of libraries for creating applications in Cakelisp.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

359 lines
14 KiB

;; Math.cake: Common game math
;; I've only exposed math functions I've actually used. This doesn't mean the functionality is
;; limited. Take a look at the underlying 3rd party math library for full functionality.
;; Everything is wrapped because I've /already/ needed to dump a math library I no longer liked, but
;; it was too late in the project to reasonably switch.
;; This wrapper is coded in such a way that there is no runtime overhead from wrapping, i.e.
;; Cakelisp macros are used to insert the underlying function names when possible
(set-cakelisp-option cakelisp-src-dir "Dependencies/cakelisp/src")
(import &comptime-only "Dependencies.cake")
(c-import "<stdio.h>")
;; TODO: Make this an "infect" to the importer rather than having to be global
(add-c-search-directory-global "Dependencies/Handmade-Math")
;; TODO: Remove once importing Math.cake no longer changes all compile commands
(add-build-config-label "HandmadeMath")
(c-define-local HANDMADE_MATH_IMPLEMENTATION)
(c-import &with-decls "HandmadeMath.h"
;; Must also include in source file so HANDMADE_MATH_IMPLEMENTATION is output here
&with-defs "HandmadeMath.h")
;;
;; Fundamental
;;
(defun interpolate-range (start-A float end-A float
start-B float end-B float B-value float
&return float)
(var interpolate-to float (/ (- B-value start-B) (- end-B start-B)))
(return (+ (* interpolate-to (- end-A start-A)) start-A)))
;; TODO: abs(), min(), max(), clamp()
;;
;; Trigonometry
;;
(var-global g-pi (const float) HMM_PI32)
(def-c-function-alias degrees-to-radians HMM_ToRadians) ;; (degrees float &return float)
(defun radians-to-degrees (angle-radians float &return float)
(return (/ (* angle-radians 180.f) g-pi)))
;;
;; Vectors
;;
(def-type-alias-global vec2 hmm_vec2)
(def-type-alias-global vec3 hmm_vec3)
(def-type-alias-global vec4 hmm_vec4)
(def-c-function-alias vec2-length HMM_LengthVec2) ;; (a vec2 &return float)
(def-c-function-alias vec2-normalize HMM_NormalizeVec2) ;; (a vec2 &return vec2)
(def-c-function-alias vec2-add HMM_AddVec2) ;; (a vec2 b vec2 &return vec2)
(def-c-function-alias vec2-subtract HMM_SubtractVec2) ;; (a vec2 b vec2 &return vec2)
(def-c-function-alias vec2-multiply HMM_MultiplyVec2) ;; (a vec2 b vec2 &return vec2)
(var-global g-up-axis (const vec3) (array 0.f 1.f 0.f))
(def-c-function-alias vec3-length HMM_LengthVec3) ;; (a vec3 &return float)
(def-c-function-alias vec3-normalize HMM_NormalizeVec3) ;; (a vec3 &return vec3)
(def-c-function-alias vec3-scale HMM_MultiplyVec3f) ;; (a vec3 &return vec3)
(def-c-function-alias vec3-add HMM_AddVec3) ;; (a vec3 &return vec3)
(def-c-function-alias vec3-subtract HMM_SubtractVec3) ;; (a vec3 &return vec3)
(def-c-function-alias vec3-multiply HMM_MultiplyVec3) ;; (a vec3 b vec3 &return vec3)
;; (def-c-function-alias vec3-vec3-angle HMM_LengthVec3)
;; (def-c-function-alias vec3-rotate-by-mat4 HMM_LengthVec3)
;; (def-c-function-alias vec3-translate-by-mat4 HMM_LengthVec3)
(def-c-function-alias vec4-create HMM_Vec4) ;; (x float y float z float w float &return vec4)
(defun vec3-scale-add-vec3 (vec-to-scale vec3 scale float vec-to-add vec3 &return vec3)
(return (vec3-add vec-to-add (vec3-scale vec-to-scale scale))))
;; Factor 0 to 1
(defun vec2-interpolate (factor float from vec2 to vec2 &return vec2)
(return (array
(interpolate-range (vec-x from) (vec-x to)
0.f 1.f factor)
(interpolate-range (vec-y from) (vec-y to)
0.f 1.f factor))))
;; Factor 0 to 1
(defun vec3-interpolate (factor float from vec3 to vec3 &return vec3)
(return
(array
(interpolate-range (vec-x from) (vec-x to)
0.f 1.f factor)
(interpolate-range (vec-y from) (vec-y to)
0.f 1.f factor)
(interpolate-range (vec-z from) (vec-z to)
0.f 1.f factor))))
(defun vec2-is-zero (vec vec2 &return bool)
(return (and (= 0.f (vec-x vec))
(= 0.f (vec-y vec)))))
(defun vec3-is-zero (vec vec3 &return bool)
(return (and (= 0.f (vec-x vec))
(= 0.f (vec-y vec))
(= 0.f (vec-z vec)))))
(defun vec2-is-within-aabb (point-to-test vec2 upper-left vec2 size vec2 &return bool)
(var bottom-right vec2 (vec2-add upper-left size))
(return
(and
(>= (vec-x point-to-test) (vec-x upper-left))
(>= (vec-y point-to-test) (vec-y upper-left))
(< (vec-x point-to-test) (vec-x bottom-right))
(< (vec-y point-to-test) (vec-y bottom-right)))))
;; Expand vector to each of its components
;; TODO: Change to any, prevent multiple eval?
(defmacro vec-xy (vec symbol)
(tokenize-push output
(field (token-splice vec) X)
(field (token-splice vec) Y))
(return true))
;; Expand vector to each of its components
;; TODO: Change to any, prevent multiple eval?
(defmacro vec-xyz (vec symbol)
(tokenize-push output
(field (token-splice vec) X)
(field (token-splice vec) Y)
(field (token-splice vec) Z))
(return true))
;; Expand vector to each of its components
;; TODO: Change to any, prevent multiple eval?
(defmacro vec-xyzw (vec symbol)
(tokenize-push output
(field (token-splice vec) X)
(field (token-splice vec) Y)
(field (token-splice vec) Z)
(field (token-splice vec) W))
(return true))
(defmacro vec-x (vec any)
(tokenize-push output
(field (token-splice vec) X))
(return true))
(defmacro vec-y (vec any)
(tokenize-push output
(field (token-splice vec) Y))
(return true))
(defmacro vec-z (vec any)
(tokenize-push output
(field (token-splice vec) Z))
(return true))
;;
;; Matrices
;; Note HandmadeMath stores matrices with "row major" storage, but mathematically you should
;; consider each vector as a column, because vectors are multiplied as 4x1 column vectors. See:
;; - https://fgiesen.wordpress.com/2012/02/12/row-major-vs-column-major-row-vectors-vs-column-vectors/
;; - http://steve.hollasch.net/cgindex/math/matrix/column-vec.html
;; - https://github.com/HandmadeMath/Handmade-Math/issues/124
;;
;; (at 3 my-mat) still gets the position vector because they are stored contiguously
;;
;; For column vectors, their 4x1 interpretation means you need to post-multiply them, e.g.:
;; (second-transform-matrix first-transform-matrix) * vec
;; Put another way, multiply matrices in reverse order from the desired order, e.g.:
;; (mat4-multiply-vec4 (mat4-multiply translate-mat rotate-mat) my-vec4)
;; ...where you want to rotate the object itself, then translate it
(def-type-alias-global mat4 hmm_mat4)
(def-c-function-alias mat4-diagonal HMM_Mat4d) ;; (diagonal-value vec3 &return mat4)
;; TODO: inline?
(defmacro mat4-identity ()
(tokenize-push output (mat4-diagonal 1.f))
(return true))
;; Must multiply in reverse order of desired transformation (first arg = last transform operation)
(def-c-function-alias mat4-multiply HMM_MultiplyMat4) ;; (left mat4 right mat4 &return mat4)
(def-c-function-alias mat4-translate HMM_Translate) ;; (translation vec3 &return mat4)
(def-c-function-alias mat4-rotate-degrees HMM_Rotate) ;; (angle-degrees float axis vec3 &return mat4)
(def-c-function-alias mat4-look-at HMM_LookAt) ;; (eye vec3 center vec3 up vec3 &return mat4)
;; (defun mat4-inverse (mat (* mat4)))
;; Notated as post-multiply to match column-vector interpretation (4x1 requires post-multiply)
(def-c-function-alias mat4-multiply-vec4 HMM_MultiplyMat4ByVec4) ;; (mat mat4 vector vec4 &return vec4)
;; Automatically converts vec3 to vec4 and back
;; TODO: Inline?
(defun mat4-transform-vec3 (mat mat4 vec vec3 &return vec3)
(var temp-vec4 vec4
(mat4-multiply-vec4 mat
(vec4-create (vec-xyz vec) 1.f)))
(var converted-vec3 vec3 (array (vec-xyz temp-vec4)))
(return converted-vec3))
;; TODO: Change to any, prevent multiple eval
(defmacro mat4-xyzw (mat symbol vec-index symbol)
(tokenize-push output
(at (token-splice vec-index) 0 (token-splice mat))
(at (token-splice vec-index) 1 (token-splice mat))
(at (token-splice vec-index) 2 (token-splice mat))
(at (token-splice vec-index) 3 (token-splice mat)))
(return true))
;; This prints in "column major", "column vector" notation.
;; This is standard in math, but not computer graphics, D3D, and various books on my shelf. However,
;; from what I can tell it is going to be more "standard" to notate them this way, so I'll adapt
(defun mat4-print (mat mat4)
(var vec-index int 0)
(while (< vec-index 4)
(printf "%d [%f %f %f %f]\n"
vec-index
(at 0 vec-index mat)
(at 1 vec-index mat)
(at 2 vec-index mat)
(at 3 vec-index mat))
(incr vec-index)))
;;
;; Quaternions
;;
(def-type-alias quaternion hmm_quaternion)
;;
;; Macros/helpers (TODO move)
;;
(defgenerator c-define-local (define-name symbol)
(var define-statement (const ([] CStatementOperation))
(array
(array Keyword "#define" -1)
(array Expression null 1)
;; Bit of a hack
(array Keyword "\n" -1)))
(return (CStatementOutput environment context tokens startTokenIndex
define-statement (array-size define-statement)
output)))
;; When encountering references of (alias), output C function invocation underlyingFuncName()
;; Note that this circumvents the reference system in order to reduce compile-time cost of using
;; aliased functions. This will probably have to be fixed eventually
(defgenerator output-aliased-c-function-invocation (&optional &rest arguments any)
(var invocation-name (& (const std::string)) (field (at (+ 1 startTokenIndex) tokens) contents))
;; TODO Hack: If I was referenced directly, don't do anything, because it's only for dependencies
(when (= 0 (call-on compare invocation-name
"output-aliased-c-function-invocation"))
(return true))
(get-or-create-comptime-var c-function-aliases (<> std::unordered_map std::string std::string))
(def-type-alias FunctionAliasMap (<> std::unordered_map std::string std::string))
(var alias-func-pair (in FunctionAliasMap iterator)
(call-on-ptr find c-function-aliases invocation-name))
(unless (!= alias-func-pair (call-on-ptr end c-function-aliases))
(ErrorAtToken (at (+ 1 startTokenIndex) tokens)
"unknown function alias. This is likely a code error, as it should never have " \
"gotten this far")
(return false))
(var underlying-func-name (& (const std::string)) (path alias-func-pair > second))
;; (Logf "found %s, outputting %s\n" (call-on c_str invocation-name) (call-on c_str underlying-func-name))
(if arguments
(block
(var invocation-statement (const ([] CStatementOperation))
(array
(array KeywordNoSpace (call-on c_str underlying-func-name) -1)
(array OpenParen null -1)
(array ExpressionList null 1)
(array CloseParen null -1)
(array SmartEndStatement null -1)))
(return (CStatementOutput environment context tokens startTokenIndex
invocation-statement (array-size invocation-statement)
output)))
(block
(var invocation-statement (const ([] CStatementOperation))
(array
(array KeywordNoSpace (call-on c_str underlying-func-name) -1)
(array OpenParen null -1)
(array CloseParen null -1)
(array SmartEndStatement null -1)))
(return (CStatementOutput environment context tokens startTokenIndex
invocation-statement (array-size invocation-statement)
output)))))
;; When encountering references of (alias), output C function invocation underlyingFuncName()
;; output-aliased-c-function-invocation actually does the work
(defgenerator def-c-function-alias (alias (ref symbol) underlying-func-name (ref symbol))
;; TODO Hack: Invoke this to create a dependency on it, so by the time we make the
;; alias, we can set the generators table to it
(output-aliased-c-function-invocation)
(get-or-create-comptime-var c-function-aliases (<> std::unordered_map std::string std::string))
(set (at (field alias contents) (deref c-function-aliases)) (field underlying-func-name contents))
;; (Logf "aliasing %s to %s\n" (call-on c_str (field underlying-func-name contents))
;; (call-on c_str (field alias contents)))
;; Upen encountering an invocation of our alias, run the aliased function output
;; In case the function already has references, resolve them now. Future invocations will be
;; handled immediately (because it'll be in the generators list)
(var evaluated-success bool
(registerEvaluateGenerator environment (call-on c_str (field alias contents))
(at "output-aliased-c-function-invocation" (field environment generators))))
(return evaluated-success))
;;
;; Testing
;;
(comptime-cond
('auto-test ;; Wrapped so we don't require compiling all the macros if unused
(defun test--math (&return int)
(printf "%f radians = %f degrees = %f radians\n"
g-pi (radians-to-degrees g-pi) (degrees-to-radians 180.f))
(var my-vec vec3 (array 1.f 2.f 3.f))
(printf "%f %f %f\n" (vec-xyz my-vec))
(var vec-a vec3 (array 0.f 1.f 2.f))
(printf "%f %f %f magnitude = %f\n" (vec-xyz vec-a) (vec3-length vec-a))
(printf "\nTranslation matrix:\n")
(var mat-a mat4 (mat4-identity))
(mat4-print (mat4-multiply mat-a (mat4-translate (array 1.f 2.f 3.f))))
(printf "\n45 degrees about the Y axis matrix:\n")
(mat4-print (mat4-rotate-degrees 45.f g-up-axis))
;; Demonstrate rotating, then translating. Due to column-vector interpretation, multiplication
;; order is reversed
(printf "\n45 degrees about the Y axis, then translate:\n")
(var transformation-mat mat4
(mat4-multiply (mat4-translate (array 1.f 2.f 3.f))
(mat4-rotate-degrees 45.f g-up-axis)))
(mat4-print transformation-mat)
(var transformed-vec vec3 (mat4-transform-vec3 transformation-mat (array 1.f 0.f 0.f)))
(printf "%f %f %f\n" (vec-xyz transformed-vec))
(return 0))))
;;
;; Building
;;
(add-dependency-git-submodule clone-handmade-math
"https://github.com/HandmadeMath/Handmade-Math"
"Dependencies/Handmade-Math")