stoe/maths/vector.lisp

325 lines
11 KiB
Common Lisp

#|
This file is a part of stoe project.
Copyright (c) 2015 Renaud Casenave-Péré (renaud@casenave-pere.fr)
|#
(uiop:define-package :stoe/maths/vector
(:use :cl :alexandria :stoe/maths/types)
(:export #:vref
#:with-swizzle
#:vec-null
#:make-vector
#:vec #:vec2 #:vec3 #:vec4
#:v+ #:v- #:v* #:v/
#:vlengthsq #:vlength
#:normalize #:safe-normalize))
(in-package :stoe/maths/vector)
(defmethod dimensions ((v vect)) (array-dimension (slot-value v 'array) 0))
(defmethod dimensions ((v int2)) 2)
(defmethod dimensions ((v int3)) 3)
(defmethod dimensions ((v int4)) 4)
(defmethod dimensions ((v float2)) 2)
(defmethod dimensions ((v float3)) 3)
(defmethod dimensions ((v float4)) 4)
(defmethod element-type ((v vect)) (array-element-type (slot-value v 'array)))
(defmethod element-type ((v int2)) 'fixnum)
(defmethod element-type ((v int3)) 'fixnum)
(defmethod element-type ((v int4)) 'fixnum)
(defmethod element-type ((v float2)) 'single-float)
(defmethod element-type ((v float3)) 'single-float)
(defmethod element-type ((v float4)) 'single-float)
(defun vref (v subscript)
(aref (slot-value v 'array) subscript))
(defun set-vref (v subscript x)
(setf (aref (slot-value v 'array) subscript) x))
(defsetf vref set-vref)
(defgeneric fill-vector (v attr subscript))
(defmethod fill-vector (v attr subscript)
(setf (vref v subscript) attr)
(1+ subscript))
(defmethod fill-vector (v (attr vect) subscript)
(loop for i from 0 below (dimensions attr)
for j from subscript
do (setf (vref v j) (vref attr i))
finally (return (1+ j))))
(defun vect-type (dim type)
(case dim
(2 (case type (single-float 'float2) (fixnum 'int2) (otherwise 'vect)))
(3 (case type (single-float 'float3) (fixnum 'int3) (otherwise 'vect)))
(4 (case type (single-float 'float4) (fixnum 'int4) (otherwise 'vect)))
(otherwise 'vect)))
(defun vec-null (dim type)
(make-instance (vect-type dim type)
:array (make-array dim :element-type type)))
(defun make-vector (dim type &rest attribs)
(let ((v (vec-null dim type)))
(loop with i = 0
for attr in attribs
do (setf i (fill-vector v attr i)))
v))
(defun make-displaced-vector (array &optional (index 0) dim)
(let ((dim (or dim (array-dimension array 0)))
(type (array-element-type array)))
(make-instance (vect-type dim type)
:array (make-array dim :element-type type :displaced-to array
:displaced-index-offset index))))
(defmacro vec (&rest attribs)
(once-only ((attrib (first attribs)))
(let ((dim (list '+ 0)) type)
(loop for attr in attribs
do (progn
(unless type
(setf type (cond
((floatp attr) 'single-float)
((integerp attr) 'fixnum))))
(if (numberp attr)
(setf (cadr dim) (1+ (cadr dim)))
(setf dim (append dim (list `(dimensions ,attr)))))))
`(make-vector ,(if (eq (cddr dim) nil) (cadr dim) dim)
,(if type
`',type
`(element-type ,attrib))
,attrib ,@(rest attribs)))))
(defmacro vec2 (&rest attribs)
(once-only ((attrib (first attribs)))
(let (type)
(loop for attr in attribs
do (unless type
(setf type (cond
((floatp attr) 'single-float)
((integerp attr) 'fixnum)))))
`(make-vector 2 ,(if type
`',type
`(element-type ,attrib))
,@attribs))))
(defmacro vec3 (&rest attribs)
(once-only ((attrib (first attribs)))
(let (type)
(loop for attr in attribs
do (unless type
(setf type (cond
((floatp attr) 'single-float)
((integerp attr) 'fixnum)))))
`(make-vector 3 ,(if type
`',type
`(element-type ,attrib))
,@attribs))))
(defmacro vec4 (&rest attribs)
(once-only ((attrib (first attribs)))
(let (type)
(loop for attr in attribs
do (unless type
(setf type (cond
((floatp attr) 'single-float)
((integerp attr) 'fixnum)))))
`(make-vector 4 ,(if type
`',type
`(element-type ,attrib))
,@attribs))))
(defmacro defswizzle (attribs)
(labels ((index (attr)
(case attr
(#\X 0)
(#\Y 1)
(#\Z 2)
(#\W 3)))
(ref-vect (v dim x neutral)
(if (or (numberp dim) (= (index x) 0))
(if (or (= (index x) 0) (> dim (index x)))
(list 'vref v (index x))
neutral)
`(if (> ,dim ,(index x))
(vref ,v ,(index x))
,neutral))))
(let* ((name (symbol-name attribs))
(len (length name)))
`(progn
(defgeneric ,attribs (v))
(defmethod ,attribs ((v vect))
,(if (< len 2)
(ref-vect 'v '(dimensions v) (char name 0) '(coerce 0 (element-type v)))
`(make-vector ,len (element-type v)
,@(loop for x across name
collect (ref-vect 'v '(dimensions v) x '(coerce 0 (element-type v)))))))
,@(loop for cls in '(int2 int3 int4 float2 float3 float4)
for type in '(fixnum fixnum fixnum single-float single-float single-float)
for dim in '(2 3 4 2 3 4)
for neutral in '(0 0 0 0.0 0.0 0.0)
collect (list 'defmethod attribs `((v ,cls))
(if (< len 2)
(ref-vect 'v dim (char name 0) neutral)
`(make-vector ,len ',type
,@(loop for x across name
collect (ref-vect 'v dim x neutral))))))
(export ',attribs)))))
(defswizzle x)
(defswizzle y)
(defswizzle z)
(defswizzle w)
(defswizzle xy)
(defswizzle xz)
(defswizzle xw)
(defswizzle yz)
(defswizzle yw)
(defswizzle zw)
(defswizzle xyz)
(defswizzle xyw)
(defswizzle xzw)
(defswizzle yzw)
(defswizzle xyzw)
(defswizzle xyxy)
(defswizzle wzyx)
(defmacro with-swizzle (attr-list v &body body)
"Binds a list of variables with x y z w or some swizzled vector for use in BODY"
`(let ,(mapcar (lambda (attr)
(let* ((var (if (listp attr) (first attr) attr))
(sym (if (listp attr) (second attr) attr)))
(list var `(,sym ,v))))
attr-list)
,@body))
(defgeneric vadd (v1 v2))
(defmethod vadd ((v vect) (s number))
(let ((vec (make-vector (dimensions v) (element-type v))))
(loop for i from 0 below (dimensions v)
do (setf (vref vec i) (+ (vref v i) s)))
vec))
(defmethod vadd ((s number) (v vect))
(let ((vec (make-vector (dimensions v) (element-type v))))
(loop for i from 0 below (dimensions v)
do (setf (vref vec i) (+ s (vref v i))))
vec))
(defmethod vadd ((v1 vect) (v2 vect))
(let ((vec (make-vector (dimensions v1) (element-type v1))))
(loop for i from 0 below (dimensions v1)
do (setf (vref vec i) (+ (vref v1 i) (vref v2 i))))
vec))
(defmethod vadd ((v1 float2) (v2 float2))
(make-vector 2 'single-float
(+ (x v1) (x v2))
(+ (y v1) (y v2))))
(defun v+ (&rest v-list)
(reduce #'vadd v-list))
(defgeneric vsub (v1 v2))
(defmethod vsub ((v vect) (s number))
(let ((vec (make-vector (dimensions v) (element-type v))))
(loop for i from 0 below (dimensions v)
do (setf (vref vec i) (- (vref v i) s)))
vec))
(defmethod vsub ((s number) (v vect))
(let ((vec (make-vector (dimensions v) (element-type v))))
(loop for i from 0 below (dimensions v)
do (setf (vref vec i) (- s (vref v i))))
vec))
(defmethod vsub ((v1 vect) (v2 vect))
(let ((vec (make-vector (dimensions v1) (element-type v1))))
(loop for i from 0 below (dimensions v1)
do (setf (vref vec i) (- (vref v1 i) (vref v2 i))))
vec))
(defmethod vsub ((v1 float2) (v2 float2))
(make-vector 2 'single-float
(- (x v1) (x v2))
(- (y v1) (y v2))))
(defun v- (&rest v-list)
(let ((v (first v-list))
(r (rest v-list)))
(if (null r)
(vsub (vec-null (dimensions v) (element-type v)) v)
(reduce #'vsub v-list))))
(defgeneric vmul (v1 v2))
(defmethod vmul ((v vect) (s number))
(let ((vec (make-vector (dimensions v) (element-type v))))
(loop for i from 0 below (dimensions v)
do (setf (vref vec i) (* (vref v i) s)))
vec))
(defmethod vmul ((s number) (v vect))
(let ((vec (make-vector (dimensions v) (element-type v))))
(loop for i from 0 below (dimensions v)
do (setf (vref vec i) (* s (vref v i))))
vec))
(defun v* (&rest v-list)
(reduce #'vmul v-list))
(defgeneric vdiv (v1 v2))
(defmethod vdiv ((v vect) (s number))
(let ((vec (make-vector (dimensions v) (element-type v))))
(loop for i from 0 below (dimensions v)
do (setf (vref vec i) (/ (vref v i) s)))
vec))
(defmethod vdiv ((s number) (v vect))
(let ((vec (make-vector (dimensions v) (element-type v))))
(loop for i from 0 below (dimensions v)
do (setf (vref vec i) (/ s (vref v i))))
vec))
(defun v/ (&rest v-list)
(reduce #'vdiv v-list))
(defgeneric dot (v1 v2))
(defmethod dot ((v1 vect) (v2 vect))
(loop for i below (dimensions v1)
sum (* (vref v1 i) (vref v2 i))))
(defgeneric cross (v1 v2))
(defmethod cross ((v1 int3) (v2 int3))
(let ((vec (make-vector 3 'fixnum)))
(setf (vref vec 0) (- (* (vref v1 1) (vref v2 2))
(* (vref v2 1) (vref v1 2))))
(setf (vref vec 1) (- (* (vref v1 2) (vref v2 0))
(* (vref v2 2) (vref v1 0))))
(setf (vref vec 2) (- (* (vref v1 0) (vref v2 1))
(* (vref v2 0) (vref v1 1))))))
(defmethod cross ((v1 float3) (v2 float3))
(let ((vec (make-vector 3 'single-float)))
(setf (vref vec 0) (- (* (vref v1 1) (vref v2 2))
(* (vref v2 1) (vref v1 2))))
(setf (vref vec 1) (- (* (vref v1 2) (vref v2 0))
(* (vref v2 2) (vref v1 0))))
(setf (vref vec 2) (- (* (vref v1 0) (vref v2 1))
(* (vref v2 0) (vref v1 1))))))
(defgeneric vlengthsq (v))
(defmethod vlengthsq ((v vect))
(reduce #'+ (map 'list (lambda (x) (* x x)) (slot-value v 'array))))
(defun vlength (v)
(sqrt (vlengthsq v)))
(defun normalize (v)
(v/ v (vlength v)))
(defun safe-normalize (v &optional default)
(let ((lensq (vlengthsq v)))
(if (zerop lensq)
(or default v)
(normalize v))))