├── .github └── workflows │ └── test.yaml ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.markdown ├── command.make ├── dub.json ├── gl3n ├── aabb.d ├── ext │ ├── hsv.d │ └── matrixstack.d ├── frustum.d ├── interpolate.d ├── linalg.d ├── math.d ├── plane.d └── util.d ├── index.d ├── modules.ddoc └── settings.ddoc /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Run all D Tests 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | name: Dub Tests 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest, windows-latest, macOS-latest] 10 | dc: [dmd-latest, ldc-latest, dmd-2.085.0, ldc-1.17.0] 11 | exclude: 12 | - { os: macOS-latest, dc: dmd-2.085.0 } 13 | 14 | runs-on: ${{ matrix.os }} 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Install D compiler 19 | uses: dlang-community/setup-dlang@v1 20 | with: 21 | compiler: ${{ matrix.dc }} 22 | 23 | - name: Run tests 24 | run: dub -q test 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | doc/ 3 | ddoc/ 4 | gl3n.pc 5 | import/ 6 | lib/ 7 | *.kdev* 8 | *~ 9 | .dub/ 10 | __test__library__ 11 | *.rf 12 | lt* 13 | dub.selections.json 14 | gl3n-test-library 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "bootDoc"] 2 | path = bootDoc 3 | url = git://github.com/JakobOvrum/bootDoc.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright: David Herberth, 2011-2021 2 | 3 | License: MIT 4 | 5 | Copyright (c) 2011, David Herberth. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # https://github.com/bioinfornatics/MakefileForD 2 | export PROJECT_NAME = gl3n 3 | export AUTHOR = David Herberth 4 | export DESCRIPTION = OpenGL Maths for D (not glm for D but better). 5 | export REPO_SRC_DIR = 6 | export LOGO_SRC = 7 | export MAJOR_VERSION = 1 8 | export MINOR_VERSION = 0 9 | export PATCH_VERSION = 0 10 | export PROJECT_VERSION = $(MAJOR_VERSION).$(MINOR_VERSION).$(PATCH_VERSION) 11 | export LICENSE = MIT 12 | export ROOT_SOURCE_DIR = gl3n 13 | DDOCFILES = modules.ddoc settings.ddoc bootDoc$(PATH_SEP)bootdoc.ddoc 14 | 15 | # include some command 16 | include command.make 17 | 18 | SOURCES = $(getSource) 19 | OBJECTS = $(patsubst %.d,$(BUILD_PATH)$(PATH_SEP)%.o, $(SOURCES)) 20 | PICOBJECTS = $(patsubst %.d,$(BUILD_PATH)$(PATH_SEP)%.pic.o,$(SOURCES)) 21 | HEADERS = $(patsubst %.d,$(IMPORT_PATH)$(PATH_SEP)%.di, $(SOURCES)) 22 | DOCUMENTATIONS = $(patsubst %.d,$(DOC_PATH)$(PATH_SEP)%.html, $(SOURCES)) 23 | DDOCUMENTATIONS = $(patsubst %.d,$(DDOC_PATH)$(PATH_SEP)%.html, $(SOURCES)) 24 | DDOC_FLAGS = $(foreach macro,$(DDOCFILES), $(DDOC_MACRO)$(macro)) 25 | space := 26 | space += 27 | 28 | stripBugfix = $(subst $(space),.,$(strip $(wordlist 1, 2, $(subst ., ,$(1))))) 29 | 30 | define make-lib 31 | $(MKDIR) $(DLIB_PATH) 32 | $(AR) rcs $(DLIB_PATH)$(PATH_SEP)$@ $^ 33 | $(RANLIB) $(DLIB_PATH)$(PATH_SEP)$@ 34 | endef 35 | 36 | ############# BUILD ############# 37 | all: static-lib header doc pkgfile-static 38 | @echo ------------------ Building $^ done 39 | all-shared: shared-lib header doc pkgfile-shared 40 | @echo ------------------ Building $^ done 41 | 42 | .PHONY : pkgfile 43 | .PHONY : doc 44 | .PHONY : ddoc 45 | .PHONY : clean 46 | 47 | static-lib: $(STATIC_LIBNAME) 48 | 49 | shared-lib: $(SHARED_LIBNAME) 50 | 51 | header: $(HEADERS) 52 | 53 | doc: $(DOCUMENTATIONS) 54 | @echo ------------------ Building Doc done 55 | 56 | ddoc: settings.ddoc $(DDOCUMENTATIONS) 57 | $(DC) $(DDOC_FLAGS) index.d $(DF)$(DDOC_PATH)$(PATH_SEP)index.html 58 | @echo ------------------ Building DDoc done 59 | 60 | geany-tag: 61 | @echo ------------------ Building geany tag 62 | $(MKDIR) geany_config 63 | geany -c geany_config -g $(PROJECT_NAME).d.tags $(SOURCES) 64 | 65 | pkgfile-shared: 66 | @echo ------------------ Building pkg-config file 67 | @echo "# Package Information for pkg-config" > $(PKG_CONFIG_FILE) 68 | @echo "# Author: $(AUTHOR)" >> $(PKG_CONFIG_FILE) 69 | @echo "# Created: `date`" >> $(PKG_CONFIG_FILE) 70 | @echo "# Licence: $(LICENSE)" >> $(PKG_CONFIG_FILE) 71 | @echo >> $(PKG_CONFIG_FILE) 72 | @echo prefix=$(PREFIX) >> $(PKG_CONFIG_FILE) 73 | @echo exec_prefix=$(PREFIX) >> $(PKG_CONFIG_FILE) 74 | @echo libdir=$(LIB_DIR) >> $(PKG_CONFIG_FILE) 75 | @echo includedir=$(INCLUDE_DIR) >> $(PKG_CONFIG_FILE) 76 | @echo >> $(PKG_CONFIG_FILE) 77 | @echo Name: "$(PROJECT_NAME)" >> $(PKG_CONFIG_FILE) 78 | @echo Description: "$(DESCRIPTION)" >> $(PKG_CONFIG_FILE) 79 | @echo Version: "$(PROJECT_VERSION)" >> $(PKG_CONFIG_FILE) 80 | @echo Libs: $(LINKERFLAG)-l$(PROJECT_NAME)-$(COMPILER) >> $(PKG_CONFIG_FILE) 81 | @echo Cflags: -I$(INCLUDE_DIR)$(PATH_SEP)$(PROJECT_NAME) $(LDCFLAGS)>> $(PKG_CONFIG_FILE) 82 | @echo >> $(PKG_CONFIG_FILE) 83 | 84 | pkgfile-static: 85 | @echo ------------------ Building pkg-config file 86 | @echo "# Package Information for pkg-config" > $(PKG_CONFIG_FILE) 87 | @echo "# Author: $(AUTHOR)" >> $(PKG_CONFIG_FILE) 88 | @echo "# Created: `date`" >> $(PKG_CONFIG_FILE) 89 | @echo "# Licence: $(LICENSE)" >> $(PKG_CONFIG_FILE) 90 | @echo >> $(PKG_CONFIG_FILE) 91 | @echo prefix=$(PREFIX) >> $(PKG_CONFIG_FILE) 92 | @echo exec_prefix=$(PREFIX) >> $(PKG_CONFIG_FILE) 93 | @echo libdir=$(LIB_DIR) >> $(PKG_CONFIG_FILE) 94 | @echo includedir=$(INCLUDE_DIR) >> $(PKG_CONFIG_FILE) 95 | @echo >> $(PKG_CONFIG_FILE) 96 | @echo Name: "$(PROJECT_NAME)" >> $(PKG_CONFIG_FILE) 97 | @echo Description: "$(DESCRIPTION)" >> $(PKG_CONFIG_FILE) 98 | @echo Version: "$(PROJECT_VERSION)" >> $(PKG_CONFIG_FILE) 99 | @echo Libs: $(LIB_DIR)$(PATH_SEP)$(STATIC_LIBNAME) >> $(PKG_CONFIG_FILE) 100 | @echo Cflags: -I$(INCLUDE_DIR)$(PATH_SEP)$(PROJECT_NAME) $(LDCFLAGS)>> $(PKG_CONFIG_FILE) 101 | @echo >> $(PKG_CONFIG_FILE) 102 | 103 | settings.ddoc: 104 | @echo "PROJECTNAME = $(PROJECT_NAME)" > settings.ddoc 105 | @echo "LINKPREFIX = $(LINKERFLAG)" >> settings.ddoc 106 | @echo "REPOSRCDIR = $(REPO_SRC_DIR)" >> settings.ddoc 107 | @echo "ROOT = $(ROOT_SOURCE_DIR)" >> settings.ddoc 108 | @echo "LOGOSRC = $(LOGO_SRC)" >> settings.ddoc 109 | @echo "LOGOALT = $(PROJECT_NAME)" >> settings.ddoc 110 | 111 | # For build lib need create object files and after run make-lib 112 | $(STATIC_LIBNAME): $(OBJECTS) 113 | @echo ------------------ Building static library 114 | $(make-lib) 115 | 116 | # For build shared lib need create shared object files 117 | $(SHARED_LIBNAME): $(PICOBJECTS) 118 | @echo ------------------ Building shared library 119 | $(MKDIR) $(DLIB_PATH) 120 | $(DC) -shared $(SONAME_FLAG) $@.$(MAJOR_VERSION) $(OUTPUT)$(DLIB_PATH)$(PATH_SEP)$@.$(PROJECT_VERSION) $^ 121 | #$(CC) -l$(PHOBOS) -l$(DRUNTIME) -shared -Wl,-soname,$@.$(MAJOR_VERSION) -o $(DLIB_PATH)$(PATH_SEP)$@.$(PROJECT_VERSION) $^ 122 | 123 | .PHONY: output_directories 124 | output_directories: 125 | mkdir -p $(dir $(OBJECTS)) 126 | 127 | # create object files 128 | $(BUILD_PATH)$(PATH_SEP)%.o : %.d output_directories 129 | $(DC) $(DCFLAGS) $(DCFLAGS_LINK) $(DCFLAGS_IMPORT) -c $< $(OUTPUT)$@ 130 | 131 | # create shared object files 132 | $(BUILD_PATH)$(PATH_SEP)%.pic.o : %.d 133 | $(DC) $(DCFLAGS) $(DCFLAGS_LINK) $(FPIC) $(DCFLAGS_IMPORT) -c $< $(OUTPUT)$@ 134 | 135 | # Generate Header files 136 | $(IMPORT_PATH)$(PATH_SEP)%.di : %.d 137 | $(DC) $(DCFLAGS) $(DCFLAGS_LINK) $(DCFLAGS_IMPORT) -c $(NO_OBJ) $< $(HF)$@ 138 | 139 | # Generate Documentation 140 | $(DOC_PATH)$(PATH_SEP)%.html : %.d 141 | $(DC) $(DCFLAGS) $(DCFLAGS_LINK) $(DCFLAGS_IMPORT) -c $(NO_OBJ) $< $(DF)$@ 142 | 143 | # Generate ddoc Documentation 144 | $(DDOC_PATH)$(PATH_SEP)%.html : %.d 145 | $(DC) $(DCFLAGS) $(DCFLAGS_LINK) $(DCFLAGS_IMPORT) -c $(NO_OBJ) $(DDOC_FLAGS) $< $(DF)$@ 146 | 147 | ############# CLEAN ############# 148 | clean: clean-objects clean-static-lib clean-doc clean-header clean-pkgfile 149 | @echo ------------------ Cleaning $^ done 150 | 151 | clean-shared: clean-shared-objects clean-shared-lib 152 | @echo ------------------ Cleaning $^ done 153 | 154 | clean-objects: 155 | $(RM) $(OBJECTS) 156 | @echo ------------------ Cleaning objects done 157 | 158 | clean-shared-objects: 159 | $(RM) $(PICOBJECTS) 160 | @echo ------------------ Cleaning shared-object done 161 | 162 | clean-static-lib: 163 | $(RM) $(DLIB_PATH)$(PATH_SEP)$(STATIC_LIBNAME) 164 | @echo ------------------ Cleaning static-lib done 165 | 166 | clean-shared-lib: 167 | $(RM) $(DLIB_PATH)$(PATH_SEP)$(SHARED_LIBNAME).$(PROJECT_VERSION) 168 | @echo ------------------ Cleaning shared-lib done 169 | 170 | clean-header: 171 | $(RM) $(HEADERS) 172 | @echo ------------------ Cleaning header done 173 | 174 | clean-doc: 175 | $(RM) $(DOCUMENTATIONS) 176 | $(RM) $(DOC_PATH) 177 | @echo ------------------ Cleaning doc done 178 | 179 | clean-ddoc: 180 | $(RM) $(DDOC_PATH)$(PATH_SEP)index.html 181 | $(RM) $(DDOCUMENTATIONS) 182 | $(RM) $(DDOC_PATH)$(PATH_SEP)$(PROJECT_NAME) 183 | $(RM) $(DDOC_PATH) 184 | @echo ------------------ Cleaning ddoc done 185 | 186 | clean-geany-tag: 187 | $(RM) geany_config $(PROJECT_NAME).d.tags 188 | @echo ------------------ Cleaning geany tag done 189 | 190 | clean-pkgfile: 191 | $(RM) $(PKG_CONFIG_FILE) 192 | @echo ------------------ Cleaning pkgfile done 193 | 194 | ############# INSTALL ############# 195 | 196 | install: install-static-lib install-doc install-header install-pkgfile 197 | @echo ------------------ Installing $^ done 198 | 199 | install-shared: install-shared-lib install-doc install-header install-pkgfile 200 | @echo ------------------ Installing $^ done 201 | 202 | install-static-lib: 203 | $(MKDIR) $(DESTDIR)$(LIB_DIR) 204 | $(CP) $(DLIB_PATH)$(PATH_SEP)$(STATIC_LIBNAME) $(DESTDIR)$(LIB_DIR) 205 | @echo ------------------ Installing static-lib done 206 | 207 | install-shared-lib: 208 | $(MKDIR) $(DESTDIR)$(LIB_DIR) 209 | $(CP) $(DLIB_PATH)$(PATH_SEP)$(SHARED_LIBNAME).$(PROJECT_VERSION) $(DESTDIR)$(LIB_DIR) 210 | cd $(DESTDIR)$(LIB_DIR)$(PATH_SEP) && $(LN) $(SHARED_LIBNAME).$(PROJECT_VERSION) $(SHARED_LIBNAME).$(MAJOR_VERSION) 211 | cd $(DESTDIR)$(LIB_DIR)$(PATH_SEP) && $(LN) $(SHARED_LIBNAME).$(MAJOR_VERSION) $(SHARED_LIBNAME) 212 | @echo ------------------ Installing shared-lib done 213 | 214 | install-header: 215 | $(MKDIR) $(DESTDIR)$(INCLUDE_DIR) 216 | $(CP) $(IMPORT_PATH)$(PATH_SEP)$(PROJECT_NAME) $(DESTDIR)$(INCLUDE_DIR) 217 | @echo ------------------ Installing header done 218 | 219 | install-doc: 220 | $(MKDIR) $(DESTDIR)$(DATA_DIR)$(PATH_SEP)doc$(PATH_SEP)$(PROJECT_NAME)$(PATH_SEP)normal_doc$(PATH_SEP) 221 | $(CP) $(DOC_PATH)$(PATH_SEP)* $(DESTDIR)$(DATA_DIR)$(PATH_SEP)doc$(PATH_SEP)$(PROJECT_NAME)$(PATH_SEP)normal_doc$(PATH_SEP) 222 | @echo ------------------ Installing doc done 223 | 224 | install-ddoc: 225 | $(MKDIR) $(DESTDIR)$(DATA_DIR)$(PATH_SEP)doc$(PATH_SEP)$(PROJECT_NAME)$(PATH_SEP)cute_doc$(PATH_SEP) 226 | $(CP) $(DDOC_PATH)$(PATH_SEP)* $(DESTDIR)$(DATA_DIR)$(PATH_SEP)doc$(PATH_SEP)$(PROJECT_NAME)$(PATH_SEP)cute_doc$(PATH_SEP) 227 | @echo ------------------ Installing ddoc done 228 | 229 | install-geany-tag: 230 | $(MKDIR) $(DESTDIR)$(DATA_DIR)$(PATH_SEP)geany$(PATH_SEP)tags$(PATH_SEP) 231 | $(CP) $(PROJECT_NAME).d.tags $(DESTDIR)$(DATA_DIR)$(PATH_SEP)geany$(PATH_SEP)tags$(PATH_SEP) 232 | @echo ------------------ Installing geany tag done 233 | 234 | install-pkgfile: 235 | $(MKDIR) $(DESTDIR)$(PKGCONFIG_DIR) 236 | $(CP) $(PKG_CONFIG_FILE) $(DESTDIR)$(PKGCONFIG_DIR)$(PATH_SEP)$(PROJECT_NAME).pc 237 | @echo ------------------ Installing pkgfile done 238 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Gl3n 2 | ==== 3 | 4 | 5 | [![Build Status](https://travis-ci.org/Dav1dde/gl3n.svg?branch=master)](https://travis-ci.org/Dav1dde/gl3n) 6 | 7 | 8 | gl3n provides all the math you need to work with OpenGL. Currently gl3n supports: 9 | 10 | * linear algebra 11 | * vectors 12 | * matrices 13 | * quaternions 14 | * geometry 15 | * axis aligned bounding boxes 16 | * planes 17 | * frustum (use it with care, not a 100% tested) 18 | * interpolation 19 | * linear interpolation (lerp) 20 | * spherical linear interpolation (slerp) 21 | * hermite interpolation 22 | * catmull rom interpolation 23 | * colors - hsv to rgb and rgb to hsv conversion 24 | * nearly all GLSL defined functions (according to spec 4.1) 25 | * the power of D, e.g. dynamic swizzling, templated types (vectors, matrices, quaternions), impressive constructors and more! 26 | 27 | License 28 | ======= 29 | 30 | gl3n is MIT licensed, which allows you to use it everywhere you want it. 31 | 32 | Documentation 33 | ============= 34 | 35 | gl3n uses ddoc for documentation. You can build it easily with the Makefile: 36 | 37 | make ddoc 38 | 39 | But there is of course also an [online documentation](http://dav1dde.github.com/gl3n/) available. 40 | 41 | Installation 42 | ============ 43 | 44 | On Linux you can build gl3n for yourself with: 45 | 46 | make 47 | make install 48 | 49 | # archlinux structure: 50 | make PREFIX=/usr 51 | make install PREFIX=/usr 52 | 53 | Or for debian based systems you can use the .deb packages provided by the 54 | [d-apt repository](http://code.google.com/p/d-apt/wiki/APT_Repository). 55 | If you want to use gl3n on Fedora, you can use your 56 | [package manager](https://apps.fedoraproject.org/packages/gl3n-devel) to install it! 57 | 58 | 59 | On Windows you can also use the Makefile, but you need e.g. [cygwin](http://www.cygwin.com/) to run it. 60 | Otherwise you can use the raw .d files and include them into your project (-I flag). 61 | 62 | 63 | If you want to use gl3n in your project, simply include the sources or use git submodules! 64 | 65 | 66 | 67 | Examples 68 | ======== 69 | 70 | ```D 71 | vec4 v4 = vec4(1.0f, vec3(2.0f, 3.0f, 4.0f)); 72 | vec4 v4_2 = vec4(1.0f, vec4(1.0f, 2.0f, 3.0f, 4.0f).xyz); // "dynamic" swizzling with opDispatch 73 | vec4 v4_3 = v4_2.xxyz; // opDispatch returns a static array which you can pass directly to the ctor of a vector! 74 | 75 | vec3 v3 = my_3dvec.rgb; 76 | vec3 foo = v4.xyzzzwzyyxw.xyz // not useful but possible! 77 | 78 | mat4 m4fv = mat4.translation(-0.5f, -0.54f, 0.42f).rotatex(PI).rotatez(PI/2); 79 | glUniformMatrix4fv(location, 1, GL_TRUE, m4fv.value_ptr); // yes they are row major! 80 | 81 | alias Matrix!(double, 4, 4) mat4d; 82 | mat4d projection; 83 | glGetDoublev(GL_PROJECTION_MATRIX, projection.value_ptr); 84 | 85 | mat3 inv_view = view.rotation; 86 | mat3 inv_view = mat3(view); 87 | 88 | mat4 m4 = mat4(vec4(1.0f, 2.0f, 3.0f, 4.0f), 5.0f, 6.0f, 7.0f, 8.0f, vec4(...) ...); 89 | ``` 90 | 91 | ```D 92 | void strafe_left(float delta) { // A 93 | vec3 vcross = cross(up, forward).normalized; 94 | _position = _position + (vcross*delta); 95 | } 96 | 97 | void strafe_right(float delta) { // D 98 | vec3 vcross = cross(up, forward).normalized; 99 | _position = _position - (vcross*delta); 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /command.make: -------------------------------------------------------------------------------- 1 | ifdef SystemRoot 2 | OS = "Windows" 3 | STATIC_LIB_EXT = .lib 4 | DYNAMIC_LIB_EXT = .dll 5 | PATH_SEP =\ 6 | message = @(echo $1) 7 | SHELL = cmd.exe 8 | Filter = %/linux/%.d %/darwin/%.d %/freebsd/%.d %/solaris/%.d 9 | getSource =$(shell dir $(ROOT_SOURCE_DIR) /s /b) 10 | else 11 | SHELL = sh 12 | PATH_SEP =/ 13 | getSource =$(shell find $(ROOT_SOURCE_DIR) -name "*.d") 14 | ifeq ($(shell uname), Linux) 15 | OS = "Linux" 16 | STATIC_LIB_EXT = .a 17 | DYNAMIC_LIB_EXT = .so 18 | message = @(echo \033[31m $1 \033[0;0m1) 19 | Filter = %/win32/%.d %/darwin/%.d %/freebsd/%.d %/solaris/%.d 20 | else ifeq ($(shell uname), Solaris) 21 | STATIC_LIB_EXT = .a 22 | DYNAMIC_LIB_EXT = .so 23 | OS = "Solaris" 24 | message = @(echo \033[31m $1 \033[0;0m1) 25 | Filter = %/win32/%.d %/linux/%.d %/darwin/%.d %/freebsd/%.d 26 | else ifeq ($(shell uname),Freebsd) 27 | STATIC_LIB_EXT = .a 28 | DYNAMIC_LIB_EXT = .so 29 | OS = "Freebsd" 30 | message = @(echo \033[31m $1 \033[0;0m1) 31 | Filter = %/win32/%.d %/linux/%.d %/darwin/%.d %/solaris/%.d 32 | else ifeq ($(shell uname),Darwin) 33 | STATIC_LIB_EXT = .a 34 | DYNAMIC_LIB_EXT = .so 35 | OS = "Darwin" 36 | message = @(echo \033[31m $1 \033[0;0m1) 37 | Filter = %/win32/%.d %/linux/%.d %/freebsd/%.d %/solaris/%.d 38 | endif 39 | endif 40 | 41 | # Define command for copy, remove and create file/dir 42 | ifeq ($(OS),"Windows") 43 | RM = del /Q 44 | CP = copy /Y 45 | MKDIR = mkdir 46 | MV = move 47 | LN = mklink 48 | else ifeq ($(OS),"Linux") 49 | RM = rm -fr 50 | CP = cp -fr 51 | MKDIR = mkdir -p 52 | MV = mv 53 | LN = ln -s 54 | else ifeq ($(OS),"Freebsd") 55 | RM = rm -fr 56 | CP = cp -fr 57 | MKDIR = mkdir -p 58 | MV = mv 59 | LN = ln -s 60 | else ifeq ($(OS),"Solaris") 61 | RM = rm -fr 62 | CP = cp -fr 63 | MKDIR = mkdir -p 64 | MV = mv 65 | LN = ln -s 66 | else ifeq ($(OS),"Darwin") 67 | RM = rm -fr 68 | CP = cp -fr 69 | MKDIR = mkdir -p 70 | MV = mv 71 | LN = ln -s 72 | endif 73 | 74 | # If compiler is not define try to find it 75 | ifndef DC 76 | ifneq ($(strip $(shell which dmd 2>/dev/null)),) 77 | DC=dmd 78 | else ifneq ($(strip $(shell which ldc 2>/dev/null)),) 79 | DC=ldc 80 | else ifneq ($(strip $(shell which ldc2 2>/dev/null)),) 81 | DC=ldc2 82 | else 83 | DC=gdc 84 | endif 85 | endif 86 | 87 | # Define flag for gdc other 88 | ifeq ($(DC),gdc) 89 | DCFLAGS = -O2 90 | LINKERFLAG= -Wl, 91 | OUTPUT = -o 92 | HF = -fintfc-file= 93 | DF = -fdoc-file= 94 | NO_OBJ = -fsyntax-only 95 | DDOC_MACRO= -fdoc-inc= 96 | else 97 | DCFLAGS = -O -d 98 | LINKERFLAG= -L 99 | OUTPUT = -of 100 | HF = -Hf 101 | DF = -Df 102 | NO_OBJ = -o- 103 | DDOC_MACRO= 104 | endif 105 | 106 | #define a suffix lib who inform is build with which compiler, name of phobos lib 107 | ifeq ($(DC),gdc) 108 | COMPILER = gdc 109 | VERSION = -fversion 110 | SONAME_FLAG = $(LINKERFLAG) -soname 111 | PHOBOS = gphobos2 112 | DRUNTIME = gdruntime 113 | else ifeq ($(DC),gdmd) 114 | COMPILER = gdc 115 | VERSION = -fversion 116 | SONAME_FLAG = $(LINKERFLAG) -soname 117 | PHOBOS = gphobos2 118 | DRUNTIME = gdruntime 119 | else ifeq ($(DC),ldc) 120 | COMPILER = ldc 121 | VERSION = -d-version 122 | SONAME_FLAG = -soname 123 | PHOBOS = phobos-ldc 124 | DRUNTIME = druntime-ldc 125 | else ifeq ($(DC),ldc2) 126 | COMPILER = ldc 127 | VERSION = -d-version 128 | SONAME_FLAG = -soname 129 | PHOBOS = phobos-ldc 130 | DRUNTIME = druntime-ldc 131 | else ifeq ($(DC),ldmd) 132 | COMPILER = ldc 133 | VERSION = -d-version 134 | SONAME_FLAG = -soname 135 | PHOBOS = phobos2-ldc 136 | DRUNTIME = druntime-ldc 137 | else ifeq ($(DC),dmd) 138 | COMPILER = dmd 139 | VERSION = -version 140 | SONAME_FLAG = $(LINKERFLAG)-soname 141 | PHOBOS = phobos2 142 | DRUNTIME = druntime 143 | else ifeq ($(DC),dmd2) 144 | COMPILER = dmd 145 | VERSION = -d-version 146 | SONAME_FLAG = $(LINKERFLAG)-soname 147 | PHOBOS = phobos2 148 | DRUNTIME = druntime 149 | endif 150 | 151 | # Define relocation model for ldc or other 152 | ifneq (,$(findstring ldc,$(DC))) 153 | FPIC = -relocation-model=pic 154 | else 155 | FPIC = -fPIC 156 | endif 157 | 158 | # Add -ldl flag for linux 159 | ifeq ($(OS),"Linux") 160 | LDCFLAGS += $(LINKERFLAG)-ldl 161 | endif 162 | 163 | # If model are not given take the same as current system 164 | ifndef ARCH 165 | ifeq ($(OS),"Windows") 166 | ifeq ($(PROCESSOR_ARCHITECTURE), x86) 167 | ARCH = x86 168 | else 169 | ARCH = x86_64 170 | endif 171 | else 172 | ARCH = $(shell arch 2>/dev/null || uname -m) 173 | endif 174 | endif 175 | ifndef MODEL 176 | ifeq ($(ARCH), x86_64) 177 | MODEL = 64 178 | else 179 | MODEL = 32 180 | endif 181 | endif 182 | 183 | ifeq ($(MODEL), 64) 184 | DCFLAGS += -m64 185 | LDCFLAGS += -m64 186 | else 187 | DCFLAGS += -m32 188 | LDCFLAGS += -m32 189 | endif 190 | 191 | ifndef DESTDIR 192 | DESTDIR = 193 | endif 194 | 195 | # Define var PREFIX, BIN_DIR, LIB_DIR, INCLUDE_DIR, DATA_DIR 196 | ifndef PREFIX 197 | ifeq ($(OS),"Windows") 198 | PREFIX = $(PROGRAMFILES) 199 | else ifeq ($(OS), "Linux") 200 | PREFIX = /usr/local 201 | else ifeq ($(OS), "Darwin") 202 | PREFIX = /usr/local 203 | endif 204 | endif 205 | 206 | ifndef BIN_DIR 207 | ifeq ($(OS), "Windows") 208 | BIN_DIR = $(PROGRAMFILES)\$(PROJECT_NAME)\bin 209 | else ifeq ($(OS), "Linux") 210 | BIN_DIR = $(PREFIX)/bin 211 | else ifeq ($(OS), "Darwin") 212 | BIN_DIR = $(PREFIX)/bin 213 | endif 214 | endif 215 | ifndef LIB_DIR 216 | ifeq ($(OS), "Windows") 217 | LIB_DIR = $(PREFIX)\$(PROJECT_NAME)\lib 218 | else ifeq ($(OS), "Linux") 219 | LIB_DIR = $(PREFIX)/lib 220 | else ifeq ($(OS), "Darwin") 221 | LIB_DIR = $(PREFIX)/lib 222 | endif 223 | endif 224 | 225 | ifndef INCLUDE_DIR 226 | ifeq ($(OS), "Windows") 227 | INCLUDE_DIR = $(PROGRAMFILES)\$(PROJECT_NAME)\import 228 | else 229 | INCLUDE_DIR = $(PREFIX)/include/d/ 230 | endif 231 | endif 232 | 233 | ifndef DATA_DIR 234 | ifeq ($(OS), "Windows") 235 | DATA_DIR = $(PROGRAMFILES)\$(PROJECT_NAME)\data 236 | else 237 | DATA_DIR = $(PREFIX)/share 238 | endif 239 | endif 240 | 241 | ifndef PKGCONFIG_DIR 242 | ifeq ($(OS), "Windows") 243 | PKGCONFIG_DIR = $(PROGRAMFILES)\$(PROJECT_NAME)\data 244 | else 245 | PKGCONFIG_DIR = $(DATA_DIR)/pkgconfig 246 | endif 247 | endif 248 | 249 | ifndef CC 250 | CC = gcc 251 | endif 252 | 253 | DLIB_PATH = ./lib 254 | IMPORT_PATH = ./import 255 | DOC_PATH = ./doc 256 | DDOC_PATH = ./ddoc 257 | BUILD_PATH = ./build 258 | 259 | DCFLAGS_IMPORT = 260 | DCFLAGS_LINK = $(LDCFLAGS) 261 | 262 | STATIC_LIBNAME = lib$(PROJECT_NAME)-$(COMPILER)$(STATIC_LIB_EXT) 263 | SHARED_LIBNAME = lib$(PROJECT_NAME)-$(COMPILER)$(DYNAMIC_LIB_EXT) 264 | 265 | PKG_CONFIG_FILE = $(PROJECT_NAME).pc 266 | 267 | MAKE = make 268 | AR = ar 269 | ARFLAGS = rcs 270 | RANLIB = ranlib 271 | 272 | export AR 273 | export ARCH 274 | export ARFLAGS 275 | export BIN_DIR 276 | export BUILD_PATH 277 | export CC 278 | export COMPILER 279 | export CP 280 | export DATA_DIR 281 | export DC 282 | export DF 283 | export DCFLAGS 284 | export DCFLAGS_IMPORT 285 | export DCFLAGS_LINK 286 | export DESTDIR 287 | export DLIB_PATH 288 | export DOC_PATH 289 | export DDOC_PATH 290 | export DYNAMIC_LIB_EXT 291 | export FixPath 292 | export HF 293 | export INCLUDE_DIR 294 | export IMPORT_PATH 295 | export LDCFLAGS 296 | export FPIC 297 | export LIBNAME 298 | export LIB_DIR 299 | export LINKERFLAG 300 | export message 301 | export MAKE 302 | export MKDIR 303 | export MODEL 304 | export MV 305 | export OUTPUT 306 | export OS 307 | export PATH_SEP 308 | export PKG_CONFIG_FILE 309 | export PREFIX 310 | export RANLIB 311 | export RM 312 | export STATIC_LIB_EXT 313 | export SONAME 314 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gl3n", 3 | "description": "OpenGL Maths for D (not glm for D but better).", 4 | "homepage": "http://dav1dde.github.com/gl3n/", 5 | "copyright": "Copyright © 2011-2021, David Herberth", 6 | "license": "MIT", 7 | "targetType": "staticLibrary", 8 | "targetName": "gl3n", 9 | "sourcePaths": ["gl3n"], 10 | "importPaths": ["."], 11 | "authors": [ 12 | "David Herberth" 13 | ], 14 | "dependencies": { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gl3n/aabb.d: -------------------------------------------------------------------------------- 1 | module gl3n.aabb; 2 | 3 | private { 4 | import gl3n.linalg : Vector; 5 | import gl3n.math : almost_equal; 6 | import gl3n.util : TupleRange; 7 | 8 | static import std.compiler; 9 | } 10 | 11 | 12 | /// Base template for all AABB-types. 13 | /// Params: 14 | /// type = all values get stored as this type 15 | struct AABBT(type, uint dimension_ = 3) { 16 | alias type at; /// Holds the internal type of the AABB. 17 | alias Vector!(at, dimension_) vec; /// Convenience alias to the corresponding vector type. 18 | alias dimension = dimension_; 19 | static assert(dimension > 0, "0 dimensional AABB don't exist."); 20 | 21 | vec min = vec(cast(at)0.0); /// The minimum of the AABB (e.g. vec(0, 0, 0)). 22 | vec max = vec(cast(at)0.0); /// The maximum of the AABB (e.g. vec(1, 1, 1)). 23 | 24 | @safe pure nothrow: 25 | 26 | /// Constructs the AABB. 27 | /// Params: 28 | /// min = minimum of the AABB 29 | /// max = maximum of the AABB 30 | this(vec min, vec max) { 31 | this.min = min; 32 | this.max = max; 33 | } 34 | 35 | /// Constructs the AABB around N points (all points will be part of the AABB). 36 | static AABBT from_points(vec[] points) { 37 | AABBT res; 38 | 39 | if(points.length == 0) { 40 | return res; 41 | } 42 | 43 | res.min = points[0]; 44 | res.max = points[0]; 45 | foreach(v; points[1..$]) { 46 | res.expand(v); 47 | } 48 | 49 | return res; 50 | } 51 | 52 | // Convenience function to get a dimension sized vector for unittests 53 | version(unittest) 54 | private static vec sizedVec(T)(T[] values){ 55 | at[] ret; 56 | foreach(i; 0..dimension) 57 | ret ~= cast(at)values[i]; 58 | return vec(ret); 59 | } 60 | 61 | unittest { 62 | alias AABB = AABBT!(at, dimension); 63 | 64 | AABB a = AABB(sizedVec([0.0, 1.0, 2.0, 3.0]), sizedVec([1.0, 2.0, 3.0, 4.0])); 65 | assert(a.min == sizedVec([0.0, 1.0, 2.0, 3.0])); 66 | assert(a.max == sizedVec([1.0, 2.0, 3.0, 4.0])); 67 | 68 | a = AABB.from_points([ 69 | sizedVec([1.0, 0.0, 1.0, 5.0]), 70 | sizedVec([0.0, 2.0, 3.0, 3.0]), 71 | sizedVec([1.0, 0.0, 4.0, 4.0])]); 72 | assert(a.min == sizedVec([0.0, 0.0, 1.0, 3.0])); 73 | assert(a.max == sizedVec([1.0, 2.0, 4.0, 5.0])); 74 | 75 | a = AABB.from_points([sizedVec([1.0, 1.0, 1.0, 1.0]), sizedVec([2.0, 2.0, 2.0, 2.0])]); 76 | assert(a.min == sizedVec([1.0, 1.0, 1.0, 1.0])); 77 | assert(a.max == sizedVec([2.0, 2.0, 2.0, 2.0])); 78 | } 79 | 80 | /// Expands the AABB by another AABB. 81 | void expand(AABBT b) { 82 | foreach(i; TupleRange!(0, dimension)) { 83 | if(min.vector[i] > b.min.vector[i]) min.vector[i] = b.min.vector[i]; 84 | if(max.vector[i] < b.max.vector[i]) max.vector[i] = b.max.vector[i]; 85 | } 86 | } 87 | 88 | /// Expands the AABB, so that $(I v) is part of the AABB. 89 | void expand(vec v) { 90 | foreach(i; TupleRange!(0, dimension)) { 91 | if(min.vector[i] > v.vector[i]) min.vector[i] = v.vector[i]; 92 | if(max.vector[i] < v.vector[i]) max.vector[i] = v.vector[i]; 93 | } 94 | } 95 | 96 | unittest { 97 | alias AABB = AABBT!(at, dimension); 98 | 99 | AABB a = AABB(sizedVec([1.0, 1.0, 1.0, 1.0]), sizedVec([2.0, 4.0, 2.0, 4.0])); 100 | AABB b = AABB(sizedVec([2.0, 1.0, 2.0, 1.0]), sizedVec([3.0, 3.0, 3.0, 3.0])); 101 | 102 | AABB c; 103 | c.expand(a); 104 | c.expand(b); 105 | assert(c.min == sizedVec([0.0, 0.0, 0.0, 0.0])); 106 | assert(c.max == sizedVec([3.0, 4.0, 3.0, 4.0])); 107 | 108 | c.expand(sizedVec([12.0, 2.0, 0.0, 1.0])); 109 | assert(c.min == sizedVec([0.0, 0.0, 0.0, 0.0])); 110 | assert(c.max == sizedVec([12.0, 4.0, 3.0, 4.0])); 111 | } 112 | 113 | /// Returns true if the AABBs intersect. 114 | /// This also returns true if one AABB lies inside another. 115 | bool intersects(AABBT box) const { 116 | foreach(i; TupleRange!(0, dimension)) { 117 | if(min.vector[i] >= box.max.vector[i] || max.vector[i] <= box.min.vector[i]) 118 | return false; 119 | } 120 | return true; 121 | } 122 | 123 | unittest { 124 | alias AABB = AABBT!(at, dimension); 125 | 126 | assert(AABB(sizedVec([0.0, 0.0, 0.0, 0.0]), sizedVec([1.0, 1.0, 1.0, 1.0])).intersects( 127 | AABB(sizedVec([0.5, 0.5, 0.5, 0.5]), sizedVec([3.0, 3.0, 3.0, 3.0])))); 128 | 129 | assert(AABB(sizedVec([0.0, 0.0, 0.0, 0.0]), sizedVec([1.0, 1.0, 1.0, 1.0])).intersects( 130 | AABB(sizedVec([0.5, 0.5, 0.5, 0.5]), sizedVec([1.7, 1.7, 1.7, 1.7])))); 131 | 132 | assert(!AABB(sizedVec([0.0, 0.0, 0.0, 0.0]), sizedVec([1.0, 1.0, 1.0, 1.0])).intersects( 133 | AABB(sizedVec([2.5, 2.5, 2.5, 2.5]), sizedVec([3.0, 3.0, 3.0, 3.0])))); 134 | } 135 | 136 | /// Returns the extent of the AABB (also sometimes called size). 137 | @property vec extent() const { 138 | return max - min; 139 | } 140 | 141 | /// Returns the half extent. 142 | @property vec half_extent() const { 143 | return (max - min) / 2; 144 | } 145 | 146 | unittest { 147 | alias AABB = AABBT!(at, dimension); 148 | 149 | AABB a = AABB(sizedVec([0.0, 0.0, 0.0, 0.0]), sizedVec([10.0, 10.0, 10.0, 10.0])); 150 | assert(a.extent == sizedVec([10.0, 10.0, 10.0, 10.0])); 151 | assert(a.half_extent == a.extent / 2); 152 | 153 | AABB b = AABB(sizedVec([2.0, 2.0, 2.0, 2.0]), sizedVec([10.0, 10.0, 10.0, 10.0])); 154 | assert(b.extent == sizedVec([8.0, 8.0, 8.0, 8.0])); 155 | assert(b.half_extent == b.extent / 2); 156 | } 157 | 158 | /// Returns the area of the AABB. 159 | static if(dimension <= 3) { 160 | @property real area() const { 161 | vec e = extent; 162 | 163 | static if(dimension == 1) { 164 | return 0; 165 | } else static if(dimension == 2) { 166 | return e.x * e.y; 167 | } else static if(dimension == 3) { 168 | return 2.0 * (e.x * e.y + e.x * e.z + e.y * e.z); 169 | } else { 170 | static assert(dimension <= 3, "area() not supported for aabb of dimension > 3"); 171 | } 172 | } 173 | 174 | unittest { 175 | alias AABB = AABBT!(at, dimension); 176 | AABB a = AABB(sizedVec([0.0, 0.0, 0.0, 0.0]), sizedVec([1.0, 1.0, 1.0, 1.0])); 177 | switch (dimension) { 178 | case 1: assert(a.area == 0); break; 179 | case 2: assert(a.area == 1); break; 180 | case 3: assert(a.area == 6); break; 181 | default: assert(0); 182 | } 183 | 184 | 185 | AABB b = AABB(sizedVec([2.0, 2.0, 2.0, 2.0]), sizedVec([10.0, 10.0, 10.0, 10.0])); 186 | switch (dimension) { 187 | case 1: assert(b.area == 0); break; 188 | case 2: assert(b.area == 64); break; 189 | case 3: assert(b.area == 384); break; 190 | default: assert(0); 191 | } 192 | 193 | AABB c = AABB(sizedVec([2.0, 4.0, 6.0, 6.0]), sizedVec([10.0, 10.0, 10.0, 10.0])); 194 | switch (dimension) { 195 | case 1: assert(c.area == 0); break; 196 | case 2: assert(almost_equal(c.area, 48.0)); break; 197 | case 3: assert(almost_equal(c.area, 208.0)); break; 198 | default: assert(0); 199 | } 200 | } 201 | 202 | } 203 | 204 | /// Returns the center of the AABB. 205 | @property vec center() const { 206 | return (max + min) / 2; 207 | } 208 | 209 | unittest { 210 | alias AABB = AABBT!(at, dimension); 211 | 212 | AABB a = AABB(sizedVec([4.0, 4.0, 4.0, 4.0]), sizedVec([10.0, 10.0, 10.0, 10.0])); 213 | assert(a.center == sizedVec([7.0, 7.0, 7.0, 7.0])); 214 | } 215 | 216 | /// Returns all vertices of the AABB, basically one vec per corner. 217 | @property vec[] vertices() const { 218 | vec[] res; 219 | res.length = 2 ^^ dimension; 220 | foreach(i; TupleRange!(0, 2^^dimension)) { 221 | foreach(dim ; TupleRange!(0, dimension)) { 222 | res[i].vector[dim] = (i & (1 << dim)) ? max.vector[dim] : min.vector[dim]; 223 | } 224 | } 225 | return res; 226 | } 227 | 228 | static if(std.compiler.version_major > 2 || std.compiler.version_minor >= 69) unittest { 229 | import std.algorithm.comparison : isPermutation; 230 | alias AABB = AABBT!(at, dimension); 231 | 232 | AABB a = AABB(sizedVec([1.0, 1.0, 1.0, 1.0]), sizedVec([2.0, 2.0, 2.0, 2.0])); 233 | switch (dimension) { 234 | case 1: assert(isPermutation(a.vertices, [ 235 | sizedVec([1.0]), 236 | sizedVec([2.0]), 237 | ])); 238 | break; 239 | case 2: assert(isPermutation(a.vertices, [ 240 | sizedVec([1.0, 1.0]), 241 | sizedVec([1.0, 2.0]), 242 | sizedVec([2.0, 1.0]), 243 | sizedVec([2.0, 2.0]), 244 | ])); 245 | break; 246 | case 3: assert(isPermutation(a.vertices, [ 247 | sizedVec([1.0, 1.0, 1.0]), 248 | sizedVec([1.0, 2.0, 1.0]), 249 | sizedVec([2.0, 1.0, 1.0]), 250 | sizedVec([2.0, 2.0, 1.0]), 251 | sizedVec([1.0, 1.0, 2.0]), 252 | sizedVec([1.0, 2.0, 2.0]), 253 | sizedVec([2.0, 1.0, 2.0]), 254 | sizedVec([2.0, 2.0, 2.0]), 255 | ])); 256 | break; 257 | case 4: assert(isPermutation(a.vertices, [ 258 | sizedVec([1.0, 1.0, 1.0, 1.0]), 259 | sizedVec([1.0, 2.0, 1.0, 1.0]), 260 | sizedVec([2.0, 1.0, 1.0, 1.0]), 261 | sizedVec([2.0, 2.0, 1.0, 1.0]), 262 | sizedVec([1.0, 1.0, 2.0, 1.0]), 263 | sizedVec([1.0, 2.0, 2.0, 1.0]), 264 | sizedVec([2.0, 1.0, 2.0, 1.0]), 265 | sizedVec([2.0, 2.0, 2.0, 1.0]), 266 | sizedVec([1.0, 1.0, 1.0, 2.0]), 267 | sizedVec([1.0, 2.0, 1.0, 2.0]), 268 | sizedVec([2.0, 1.0, 1.0, 2.0]), 269 | sizedVec([2.0, 2.0, 1.0, 2.0]), 270 | sizedVec([1.0, 1.0, 2.0, 2.0]), 271 | sizedVec([1.0, 2.0, 2.0, 2.0]), 272 | sizedVec([2.0, 1.0, 2.0, 2.0]), 273 | sizedVec([2.0, 2.0, 2.0, 2.0]), 274 | ])); 275 | break; 276 | default: assert(0); 277 | } 278 | } 279 | 280 | bool opEquals(AABBT other) const { 281 | return other.min == min && other.max == max; 282 | } 283 | 284 | unittest { 285 | alias AABB = AABBT!(at, dimension); 286 | assert(AABB(sizedVec([1.0, 12.0, 14.0, 16.0]), sizedVec([33.0, 222.0, 342.0, 1231.0])) == 287 | AABB(sizedVec([1.0, 12.0, 14.0, 16.0]), sizedVec([33.0, 222.0, 342.0, 1231.0]))); 288 | } 289 | } 290 | 291 | alias AABBT!(float, 3) AABB3; 292 | alias AABBT!(float, 2) AABB2; 293 | 294 | alias AABB3 AABB; 295 | 296 | 297 | unittest { 298 | import gl3n.util : TypeTuple; 299 | alias TypeTuple!(ubyte, byte, short, ushort, int, uint, float, double) Types; 300 | foreach(type; Types) 301 | { 302 | foreach(dim; TupleRange!(1, 5)) 303 | { 304 | { 305 | alias AABBT!(type,dim) aabbTestType; 306 | auto instance = AABBT!(type,dim)(); 307 | } 308 | } 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /gl3n/ext/hsv.d: -------------------------------------------------------------------------------- 1 | module gl3n.ext.hsv; 2 | 3 | private { 4 | import std.conv : to; 5 | 6 | import gl3n.linalg : vec3, vec4; 7 | import gl3n.math : min, max, floor; 8 | 9 | version(unittest) { 10 | import gl3n.math : almost_equal; 11 | } 12 | } 13 | 14 | /// Converts a 3 dimensional color-vector from the RGB to the HSV colorspace. 15 | /// The function assumes that each component is in the range [0, 1]. 16 | @safe pure nothrow vec3 rgb2hsv(vec3 inp) { 17 | vec3 ret = vec3(0.0f, 0.0f, 0.0f); 18 | 19 | float h_max = max(inp.r, inp.g, inp.b); 20 | float h_min = min(inp.r, inp.g, inp.b); 21 | float delta = h_max - h_min; 22 | 23 | 24 | // h 25 | if(delta == 0.0f) { 26 | ret.x = 0.0f; 27 | } else if(inp.r == h_max) { 28 | ret.x = (inp.g - inp.b) / delta; // h 29 | } else if(inp.g == h_max) { 30 | ret.x = 2 + (inp.b - inp.r) / delta; // h 31 | } else { 32 | ret.x = 4 + (inp.r - inp.g) / delta; // h 33 | } 34 | 35 | ret.x = ret.x * 60; 36 | if(ret.x < 0) { 37 | ret.x = ret.x + 360; 38 | } 39 | 40 | // s 41 | if(h_max == 0.0f) { 42 | ret.y = 0.0f; 43 | } else { 44 | ret.y = delta / h_max; 45 | } 46 | 47 | // v 48 | ret.z = h_max; 49 | 50 | return ret; 51 | } 52 | 53 | /// Converts a 4 dimensional color-vector from the RGB to the HSV colorspace. 54 | /// The alpha value is not touched. This function also assumes that each component is in the range [0, 1]. 55 | @safe pure nothrow vec4 rgb2hsv(vec4 inp) { 56 | return vec4(rgb2hsv(vec3(inp.rgb)), inp.a); 57 | } 58 | 59 | unittest { 60 | assert(rgb2hsv(vec3(0.0f, 0.0f, 0.0f)) == vec3(0.0f, 0.0f, 0.0f)); 61 | assert(rgb2hsv(vec3(1.0f, 1.0f, 1.0f)) == vec3(0.0f, 0.0f, 1.0f)); 62 | 63 | vec3 hsv = rgb2hsv(vec3(100.0f/255.0f, 100.0f/255.0f, 100.0f/255.0f)); 64 | assert(hsv.x == 0.0f && hsv.y == 0.0f && almost_equal(hsv.z, 0.392157, 0.000001)); 65 | 66 | assert(rgb2hsv(vec3(0.0f, 0.0f, 1.0f)) == vec3(240.0f, 1.0f, 1.0f)); 67 | } 68 | 69 | /// Converts a 3 dimensional color-vector from the HSV to the RGB colorspace. 70 | /// RGB colors will be in the range [0, 1]. 71 | /// This function is not marked es pure, since it depends on std.math.floor, which 72 | /// is also not pure. 73 | @safe nothrow vec3 hsv2rgb(vec3 inp) { 74 | if(inp.y == 0.0f) { // s 75 | return vec3(inp.zzz); // v 76 | } else { 77 | float var_h = inp.x * 6; 78 | float var_i = to!float(floor(var_h)); 79 | float var_1 = inp.z * (1 - inp.y); 80 | float var_2 = inp.z * (1 - inp.y * (var_h - var_i)); 81 | float var_3 = inp.z * (1 - inp.y * (1 - (var_h - var_i))); 82 | 83 | if(var_i == 0.0f) return vec3(inp.z, var_3, var_1); 84 | else if(var_i == 1.0f) return vec3(var_2, inp.z, var_1); 85 | else if(var_i == 2.0f) return vec3(var_1, inp.z, var_3); 86 | else if(var_i == 3.0f) return vec3(var_1, var_2, inp.z); 87 | else if(var_i == 4.0f) return vec3(var_3, var_1, inp.z); 88 | else return vec3(inp.z, var_1, var_2); 89 | } 90 | } 91 | 92 | /// Converts a 4 dimensional color-vector from the HSV to the RGB colorspace. 93 | /// The alpha value is not touched and the resulting RGB colors will be in the range [0, 1]. 94 | @safe nothrow vec4 hsv2rgb(vec4 inp) { 95 | return vec4(hsv2rgb(vec3(inp.xyz)), inp.w); 96 | } 97 | 98 | unittest { 99 | assert(hsv2rgb(vec3(0.0f, 0.0f, 0.0f)) == vec3(0.0f, 0.0f, 0.0f)); 100 | assert(hsv2rgb(vec3(0.0f, 0.0f, 1.0f)) == vec3(1.0f, 1.0f, 1.0f)); 101 | 102 | vec3 rgb = hsv2rgb(vec3(0.0f, 0.0f, 0.392157f)); 103 | assert(rgb == vec3(0.392157f, 0.392157f, 0.392157f)); 104 | 105 | assert(hsv2rgb(vec3(300.0f, 1.0f, 1.0f)) == vec3(1.0f, 0.0f, 1.0f)); 106 | } -------------------------------------------------------------------------------- /gl3n/ext/matrixstack.d: -------------------------------------------------------------------------------- 1 | module gl3n.ext.matrixstack; 2 | 3 | private { 4 | import gl3n.util : is_matrix; 5 | } 6 | 7 | 8 | /// A matrix stack similiar to OpenGLs glPushMatrix/glPopMatrix 9 | struct MatrixStack(T) if(is_matrix!T) { 10 | alias T Matrix; /// Holds the internal matrix type 11 | 12 | Matrix top = Matrix.identity; /// The top matrix, the one you work with 13 | private Matrix[] stack; 14 | private size_t _top_pos = 0; 15 | 16 | alias top this; 17 | 18 | /// If the stack is too small to hold more items, 19 | /// space for $(B realloc_interval) more elements will be allocated 20 | size_t realloc_interval = 8; 21 | 22 | deprecated("Use matrixStack() instead.") 23 | @disable this(); 24 | 25 | /// Sets the stacks initial size to $(B depth) elements 26 | deprecated("Use matrixStack() instead.") 27 | this(size_t depth) pure nothrow { 28 | stack = new Matrix[](depth); 29 | } 30 | 31 | /// Sets the top matrix 32 | void set(Matrix matrix) pure nothrow { 33 | top = matrix; 34 | } 35 | 36 | /// Pushes the top matrix on the stack and keeps a copy as the new top matrix 37 | void push() pure nothrow { 38 | if(stack.length <= _top_pos) { 39 | stack.length += realloc_interval; 40 | } 41 | 42 | stack[_top_pos++] = top; 43 | } 44 | 45 | /// Pushes the top matrix on the stack and sets $(B matrix) as the new top matrix. 46 | void push(Matrix matrix) pure nothrow { 47 | push(); 48 | top = matrix; 49 | } 50 | 51 | /// Pops a matrix from the stack and sets it as top matrix. 52 | /// Also returns a reference to the new top matrix. 53 | ref Matrix pop() pure nothrow 54 | in { assert(_top_pos >= 1, "popped too often from matrix stack"); } 55 | do { 56 | top = stack[--_top_pos]; 57 | return top; 58 | } 59 | } 60 | 61 | /// Constructs a new stack with an initial size of $(B depth) elements 62 | MatrixStack!T matrixStack(T)(size_t depth = 16) pure nothrow { 63 | typeof(return) res = MatrixStack!T.init; 64 | res.stack.length = depth; 65 | return res; 66 | } 67 | 68 | unittest { 69 | import gl3n.linalg : mat4; 70 | 71 | static assert(!__traits(compiles, {auto m = MatrixStack!mat4();})); 72 | static assert(!__traits(compiles, {MatrixStack!mat4 m;})); 73 | auto m1 = matrixStack!mat4(); 74 | assert(m1.stack.length == 16); 75 | auto m2 = matrixStack!mat4(20); 76 | assert(m2.stack.length == 20); 77 | 78 | assert(m1.top == mat4.identity); 79 | assert(m1._top_pos == 0); 80 | } 81 | 82 | unittest { 83 | import gl3n.linalg : mat4; 84 | 85 | auto ms = matrixStack!mat4(); 86 | // just a few tests to make sure it forwards correctly to Matrix 87 | static assert(__traits(hasMember, ms, "make_identity")); 88 | static assert(__traits(hasMember, ms, "transpose")); 89 | static assert(__traits(hasMember, ms, "invert")); 90 | static assert(__traits(hasMember, ms, "scale")); 91 | static assert(__traits(hasMember, ms, "rotate")); 92 | 93 | assert(ms.top == mat4.identity); 94 | assert(ms == ms.top); // make sure there is an proper alias this 95 | ms.push(); 96 | 97 | auto m1 = mat4(1, 0, 0, 0, 98 | 0, 0, 0, 0, 99 | 0, 0, 1, 0, 100 | 0, 0, 0, 1); 101 | 102 | ms.set(m1); 103 | assert(ms.top == m1); 104 | assert(ms == ms.top); 105 | ms.push(); 106 | 107 | assert(ms.top == m1); 108 | ms.top = ms.translate(0, 3, 2); 109 | ms.push(mat4.identity); 110 | 111 | assert(ms.top == mat4.identity); 112 | ms.push(); 113 | 114 | ms.pop(); 115 | assert(ms.top == mat4.identity); 116 | 117 | ms.pop(); 118 | assert(ms.top == mat4(m1).translate(0, 3, 2)); 119 | 120 | ms.pop(); 121 | assert(ms.top == m1); 122 | 123 | ms.pop(); 124 | assert(ms.top == mat4.identity); 125 | } 126 | -------------------------------------------------------------------------------- /gl3n/frustum.d: -------------------------------------------------------------------------------- 1 | /// Note: this module is not completly tested! 2 | /// Use with special care, results might be wrong. 3 | 4 | module gl3n.frustum; 5 | 6 | private { 7 | import gl3n.linalg : vec3, mat4, dot; 8 | import gl3n.math : abs, cradians; 9 | import gl3n.aabb : AABB; 10 | import gl3n.plane : Plane; 11 | } 12 | 13 | enum { 14 | OUTSIDE = 0, /// Used as flag to indicate if the object intersects with the frustum. 15 | INSIDE, /// ditto 16 | INTERSECT /// ditto 17 | } 18 | 19 | /// 20 | struct Frustum { 21 | enum { 22 | LEFT, /// Used to access the planes array. 23 | RIGHT, /// ditto 24 | BOTTOM, /// ditto 25 | TOP, /// ditto 26 | NEAR, /// ditto 27 | FAR /// ditto 28 | } 29 | 30 | Plane[6] planes; /// Holds all 6 planes of the frustum. 31 | 32 | @safe pure nothrow: 33 | 34 | @property ref inout(Plane) left() inout return { return planes[LEFT]; } 35 | @property ref inout(Plane) right() inout return { return planes[RIGHT]; } 36 | @property ref inout(Plane) bottom() inout return { return planes[BOTTOM]; } 37 | @property ref inout(Plane) top() inout return { return planes[TOP]; } 38 | @property ref inout(Plane) near() inout return { return planes[NEAR]; } 39 | @property ref inout(Plane) far() inout return { return planes[FAR]; } 40 | 41 | /// Constructs the frustum from a model-view-projection matrix. 42 | /// Params: 43 | /// mvp = a model-view-projection matrix 44 | this(mat4 mvp) { 45 | mvp.transpose(); // we store the matrix row-major 46 | 47 | planes = [ 48 | // left 49 | Plane(mvp[0][3] + mvp[0][0], 50 | mvp[1][3] + mvp[1][0], 51 | mvp[2][3] + mvp[2][0], 52 | mvp[3][3] + mvp[3][0]), 53 | 54 | // right 55 | Plane(mvp[0][3] - mvp[0][0], 56 | mvp[1][3] - mvp[1][0], 57 | mvp[2][3] - mvp[2][0], 58 | mvp[3][3] - mvp[3][0]), 59 | 60 | // bottom 61 | Plane(mvp[0][3] + mvp[0][1], 62 | mvp[1][3] + mvp[1][1], 63 | mvp[2][3] + mvp[2][1], 64 | mvp[3][3] + mvp[3][1]), 65 | // top 66 | Plane(mvp[0][3] - mvp[0][1], 67 | mvp[1][3] - mvp[1][1], 68 | mvp[2][3] - mvp[2][1], 69 | mvp[3][3] - mvp[3][1]), 70 | // near 71 | Plane(mvp[0][3] + mvp[0][2], 72 | mvp[1][3] + mvp[1][2], 73 | mvp[2][3] + mvp[2][2], 74 | mvp[3][3] + mvp[3][2]), 75 | // far 76 | Plane(mvp[0][3] - mvp[0][2], 77 | mvp[1][3] - mvp[1][2], 78 | mvp[2][3] - mvp[2][2], 79 | mvp[3][3] - mvp[3][2]) 80 | ]; 81 | 82 | normalize(); 83 | } 84 | 85 | /// Constructs the frustum from 6 planes. 86 | /// Params: 87 | /// planes = the 6 frustum planes in the order: left, right, bottom, top, near, far. 88 | this(Plane[6] planes) { 89 | this.planes = planes; 90 | normalize(); 91 | } 92 | 93 | private void normalize() { 94 | foreach(ref e; planes) { 95 | e.normalize(); 96 | } 97 | } 98 | 99 | /// Checks if the $(I aabb) intersects with the frustum. 100 | /// Returns OUTSIDE (= 0), INSIDE (= 1) or INTERSECT (= 2). 101 | int intersects(AABB aabb) const { 102 | vec3 hextent = aabb.half_extent; 103 | vec3 center = aabb.center; 104 | 105 | int result = INSIDE; 106 | foreach(plane; planes) { 107 | float d = dot(center, plane.normal); 108 | float r = dot(hextent, abs(plane.normal)); 109 | 110 | if(d + r < -plane.d) { 111 | // outside 112 | return OUTSIDE; 113 | } 114 | if(d - r < -plane.d) { 115 | result = INTERSECT; 116 | } 117 | } 118 | 119 | return result; 120 | } 121 | 122 | unittest { 123 | mat4 view = mat4.look_at(vec3(0), vec3(0, 0, 1), vec3(0, 1, 0)); 124 | enum aspect = 4.0/3.0; 125 | enum fov = 60; 126 | enum near = 1; 127 | enum far = 100; 128 | mat4 proj = mat4.perspective(aspect, 1.0, fov, near, far); 129 | auto f = Frustum(proj * view); 130 | assert(f.intersects(AABB(vec3(0, 0, 1), vec3(0, 0, 1))) == INSIDE); 131 | assert(f.intersects(AABB(vec3(-1), vec3(1))) == INTERSECT); 132 | assert(f.intersects(AABB(vec3(-1), vec3(0.99))) == OUTSIDE); 133 | assert(f.intersects(AABB(vec3(-1000), vec3(1000))) == INTERSECT); 134 | assert(f.intersects(AABB(vec3(0, 0, -1000), vec3(1, 1, 1000))) == INTERSECT); 135 | assert(f.intersects(AABB(vec3(-1000, 0, 0), vec3(1000, 0.1, 0.1))) == OUTSIDE); 136 | for(int i = near; i < far; i += 10) { 137 | assert(f.intersects(AABB(vec3(0, 0, i), vec3(0.1, 0.1, i + 1))) == INSIDE); 138 | assert(f.intersects(AABB(vec3(0, 0, -i), vec3(0.1, 0.1, -(i + 1)))) == OUTSIDE); 139 | } 140 | import std.math : tan; 141 | float c = aspect * far / tan(cradians!fov); 142 | assert(f.intersects(AABB(vec3(c, 0, 99), vec3(c + 1, 1, 101))) == INTERSECT); 143 | assert(f.intersects(AABB(vec3(c - 4, 0, 98), vec3(c - 2, 1, 99.99))) == INSIDE); 144 | assert(f.intersects(AABB(vec3(c, 0, 100), vec3(c + 1, 0, 101))) == OUTSIDE); 145 | 146 | proj = mat4.orthographic(-aspect, aspect, -1.0, 1.0, 0, far); 147 | f = Frustum(proj * view); 148 | assert(f.intersects(AABB(vec3(0, 0, 1), vec3(0, 0, 1))) == INSIDE); 149 | assert(f.intersects(AABB(vec3(-1), vec3(1))) == INTERSECT); 150 | assert(f.intersects(AABB(vec3(-1), vec3(0.01))) == INTERSECT); 151 | assert(f.intersects(AABB(vec3(0, 0, far - 5), vec3(1, 1, far))) == INSIDE); 152 | assert(f.intersects(AABB(vec3(0, 0, far - 5), vec3(1, 1, far + 5))) == INTERSECT); 153 | assert(f.intersects(AABB(vec3(-1000, 0, -0.01), vec3(1000, 1, 0))) == INTERSECT); 154 | assert(f.intersects(AABB(vec3(-1000, 0, -0.02), vec3(1000, 1, -0.01))) == OUTSIDE); 155 | } 156 | 157 | /// Returns true if the $(I aabb) intersects with the frustum or is inside it. 158 | bool opBinaryRight(string s : "in")(AABB aabb) const { 159 | return intersects(aabb) > 0; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /gl3n/interpolate.d: -------------------------------------------------------------------------------- 1 | /** 2 | gl3n.interpolate 3 | 4 | Authors: David Herberth 5 | License: MIT 6 | */ 7 | 8 | 9 | module gl3n.interpolate; 10 | 11 | private { 12 | import gl3n.linalg : Vector, dot, vec2, vec3, vec4, quat; 13 | import gl3n.util : is_vector, is_quaternion; 14 | import gl3n.math : almost_equal, acos, sin, sqrt, clamp, PI; 15 | import std.conv : to; 16 | } 17 | 18 | @safe pure nothrow: 19 | 20 | /// Interpolates linear between two points, also known as lerp. 21 | T interp(T)(T a, T b, float t) { 22 | return a * (1 - t) + b * t; 23 | } 24 | alias interp interp_linear; /// ditto 25 | alias interp lerp; /// ditto 26 | alias interp mix; /// ditto 27 | 28 | 29 | /// Interpolates spherical between to vectors or quaternions, also known as slerp. 30 | T interp_spherical(T)(T a, T b, float t) if(is_vector!T || is_quaternion!T) { 31 | static if(is_vector!T) { 32 | real theta = acos(dot(a, b)); 33 | } else { 34 | real theta = acos( 35 | // this is a workaround, acos returning -nan on certain values near +/-1 36 | clamp(a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z, -1, 1) 37 | ); 38 | } 39 | 40 | if(almost_equal(theta, 0)) { 41 | return a; 42 | } else if(almost_equal(theta, PI)) { // 180°? 43 | return interp(a, b, t); 44 | } else { // slerp 45 | real sintheta = sin(theta); 46 | return (sin((1.0-t)*theta)/sintheta)*a + (sin(t*theta)/sintheta)*b; 47 | } 48 | } 49 | alias interp_spherical slerp; /// ditto 50 | 51 | 52 | /// Normalized quaternion linear interpolation. 53 | quat nlerp(quat a, quat b, float t) { 54 | // TODO: tests 55 | float dot = a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z; 56 | 57 | quat result; 58 | if(dot < 0) { // Determine the "shortest route"... 59 | result = a - (b + a) * t; // use -b instead of b 60 | } else { 61 | result = a + (b - a) * t; 62 | } 63 | result.normalize(); 64 | 65 | return result; 66 | } 67 | 68 | unittest { 69 | vec2 v2_1 = vec2(1.0f); 70 | vec2 v2_2 = vec2(0.0f); 71 | vec3 v3_1 = vec3(1.0f); 72 | vec3 v3_2 = vec3(0.0f); 73 | vec4 v4_1 = vec4(1.0f); 74 | vec4 v4_2 = vec4(0.0f); 75 | 76 | assert(interp(v2_1, v2_2, 0.5f).vector == [0.5f, 0.5f]); 77 | assert(interp(v2_1, v2_2, 0.0f) == v2_1); 78 | assert(interp(v2_1, v2_2, 1.0f) == v2_2); 79 | assert(interp(v3_1, v3_2, 0.5f).vector == [0.5f, 0.5f, 0.5f]); 80 | assert(interp(v3_1, v3_2, 0.0f) == v3_1); 81 | assert(interp(v3_1, v3_2, 1.0f) == v3_2); 82 | assert(interp(v4_1, v4_2, 0.5f).vector == [0.5f, 0.5f, 0.5f, 0.5f]); 83 | assert(interp(v4_1, v4_2, 0.0f) == v4_1); 84 | assert(interp(v4_1, v4_2, 1.0f) == v4_2); 85 | 86 | real r1 = 0.0; 87 | real r2 = 1.0; 88 | assert(interp(r1, r2, 0.5f) == 0.5); 89 | assert(interp(r1, r2, 0.0f) == r1); 90 | assert(interp(r1, r2, 1.0f) == r2); 91 | 92 | assert(interp(0.0, 1.0, 0.5f) == 0.5); 93 | assert(interp(0.0, 1.0, 0.0f) == 0.0); 94 | assert(interp(0.0, 1.0, 1.0f) == 1.0); 95 | 96 | assert(interp(0.0f, 1.0f, 0.5f) == 0.5f); 97 | assert(interp(0.0f, 1.0f, 0.0f) == 0.0f); 98 | assert(interp(0.0f, 1.0f, 1.0f) == 1.0f); 99 | 100 | quat q1 = quat(1.0f, 1.0f, 1.0f, 1.0f); 101 | quat q2 = quat(0.0f, 0.0f, 0.0f, 0.0f); 102 | 103 | assert(interp(q1, q2, 0.0f).quaternion == q1.quaternion); 104 | assert(interp(q1, q2, 0.5f).quaternion == [0.5f, 0.5f, 0.5f, 0.5f]); 105 | assert(interp(q1, q2, 1.0f).quaternion == q2.quaternion); 106 | 107 | assert(interp_spherical(v2_1, v2_2, 0.0).vector == v2_1.vector); 108 | assert(interp_spherical(v2_1, v2_2, 1.0).vector == v2_2.vector); 109 | assert(interp_spherical(v3_1, v3_2, 0.0).vector == v3_1.vector); 110 | assert(interp_spherical(v3_1, v3_2, 1.0).vector == v3_2.vector); 111 | assert(interp_spherical(v4_1, v4_2, 0.0).vector == v4_1.vector); 112 | assert(interp_spherical(v4_1, v4_2, 1.0).vector == v4_2.vector); 113 | 114 | assert(interp_spherical(q1, q2, 0.0f).quaternion == q1.quaternion); 115 | assert(interp_spherical(q1, q2, 1.0f).quaternion == q2.quaternion); 116 | } 117 | 118 | /// Nearest interpolation of two points. 119 | T interp_nearest(T)(T x, T y, float t) { 120 | if(t < 0.5f) { return x; } 121 | else { return y; } 122 | } 123 | 124 | unittest { 125 | assert(interp_nearest(0.0, 1.0, 0.5f) == 1.0); 126 | assert(interp_nearest(0.0, 1.0, 0.4f) == 0.0); 127 | assert(interp_nearest(0.0, 1.0, 0.6f) == 1.0); 128 | } 129 | 130 | /// Catmull-rom interpolation between four points. 131 | T interp_catmullrom(T)(T p0, T p1, T p2, T p3, float t) { 132 | return 0.5f * ((2 * p1) + 133 | (-p0 + p2) * t + 134 | (2 * p0 - 5 * p1 + 4 * p2 - p3) * t^^2 + 135 | (-p0 + 3 * p1 - 3 * p2 + p3) * t^^3); 136 | } 137 | 138 | /// Catmull-derivatives of the interpolation between four points. 139 | T catmullrom_derivative(T)(T p0, T p1, T p2, T p3, float t) { 140 | return 0.5f * ((2 * p1) + 141 | (-p0 + p2) + 142 | 2 * (2 * p0 - 5 * p1 + 4 * p2 - p3) * t + 143 | 3 * (-p0 + 3 * p1 - 3 * p2 + p3) * t^^2); 144 | } 145 | 146 | /// Hermite interpolation (cubic hermite spline). 147 | T interp_hermite(T)(T x, T tx, T y, T ty, float t) { 148 | float h1 = 2 * t^^3 - 3 * t^^2 + 1; 149 | float h2 = -2* t^^3 + 3 * t^^2; 150 | float h3 = t^^3 - 2 * t^^2 + t; 151 | float h4 = t^^3 - t^^2; 152 | return h1 * x + h3 * tx + h2 * y + h4 * ty; 153 | } -------------------------------------------------------------------------------- /gl3n/linalg.d: -------------------------------------------------------------------------------- 1 | /** 2 | gl3n.linalg 3 | 4 | Special thanks to: 5 | $(UL 6 | $(LI Tomasz Stachowiak (h3r3tic): allowed me to use parts of $(LINK2 https://bitbucket.org/h3r3tic/boxen/src/default/src/xf/omg, omg).) 7 | $(LI Jakob Øvrum (jA_cOp): improved the code a lot!) 8 | $(LI Florian Boesch (___doc__): helps me to understand opengl/complex maths better, see: $(LINK http://codeflow.org/).) 9 | $(LI #D on freenode: answered general questions about D.) 10 | ) 11 | 12 | Authors: David Herberth 13 | License: MIT 14 | 15 | Note: All methods marked with pure are weakly pure since, they all access an instance member. 16 | All static methods are strongly pure. 17 | */ 18 | 19 | 20 | module gl3n.linalg; 21 | 22 | private { 23 | import std.math : isNaN, isInfinity; 24 | import std.conv : to; 25 | import std.traits : isIntegral, isFloatingPoint, isStaticArray, isDynamicArray, isImplicitlyConvertible, isArray; 26 | import std.string : format, rightJustify; 27 | import std.array : join; 28 | import std.algorithm : max, min, reduce; 29 | import gl3n.math : clamp, PI, sqrt, sin, cos, acos, tan, asin, atan2, almost_equal; 30 | import gl3n.util : is_vector, is_matrix, is_quaternion, TupleRange; 31 | } 32 | 33 | version(NoReciprocalMul) { 34 | private enum rmul = false; 35 | } else { 36 | private enum rmul = true; 37 | } 38 | 39 | /// Base template for all vector-types. 40 | /// Params: 41 | /// type = all values get stored as this type 42 | /// dimension = specifies the dimension of the vector, can be 1, 2, 3 or 4 43 | /// Examples: 44 | /// --- 45 | /// alias Vector!(int, 3) vec3i; 46 | /// alias Vector!(float, 4) vec4; 47 | /// alias Vector!(real, 2) vec2r; 48 | /// --- 49 | struct Vector(type, int dimension_) { 50 | static assert(dimension > 0, "0 dimensional vectors don't exist."); 51 | 52 | alias type vt; /// Holds the internal type of the vector. 53 | static const int dimension = dimension_; ///Holds the dimension of the vector. 54 | 55 | vt[dimension] vector; /// Holds all coordinates, length conforms dimension. 56 | 57 | /// Returns a pointer to the coordinates. 58 | @property auto value_ptr() const { return vector.ptr; } 59 | 60 | /// Returns the current vector formatted as string, useful for printing the vector. 61 | @property string as_string() { 62 | return format("%s", vector); 63 | } 64 | alias as_string toString; /// ditto 65 | 66 | @safe pure nothrow: 67 | /// 68 | @property ref inout(vt) get_(char coord)() inout { 69 | return vector[coord_to_index!coord]; 70 | } 71 | 72 | alias get_!'x' x; /// static properties to access the values. 73 | alias x u; /// ditto 74 | alias x s; /// ditto 75 | alias x r; /// ditto 76 | static if(dimension >= 2) { 77 | alias get_!'y' y; /// ditto 78 | alias y v; /// ditto 79 | alias y t; /// ditto 80 | alias y g; /// ditto 81 | } 82 | static if(dimension >= 3) { 83 | alias get_!'z' z; /// ditto 84 | alias z b; /// ditto 85 | alias z p; /// ditto 86 | } 87 | static if(dimension >= 4) { 88 | alias get_!'w' w; /// ditto 89 | alias w a; /// ditto 90 | alias w q; /// ditto 91 | } 92 | 93 | static if(dimension == 2) { 94 | enum Vector e1 = Vector(1.to!vt, 0.to!vt); /// canonical basis for Euclidian space 95 | enum Vector e2 = Vector(0.to!vt, 1.to!vt); /// ditto 96 | } else static if(dimension == 3) { 97 | enum Vector e1 = Vector(1.to!vt, 0.to!vt, 0.to!vt); /// canonical basis for Euclidian space 98 | enum Vector e2 = Vector(0.to!vt, 1.to!vt, 0.to!vt); /// ditto 99 | enum Vector e3 = Vector(0.to!vt, 0.to!vt, 1.to!vt); /// ditto 100 | } else static if(dimension == 4) { 101 | enum Vector e1 = Vector(1.to!vt, 0.to!vt, 0.to!vt, 0.to!vt); /// canonical basis for Euclidian space 102 | enum Vector e2 = Vector(0.to!vt, 1.to!vt, 0.to!vt, 0.to!vt); /// ditto 103 | enum Vector e3 = Vector(0.to!vt, 0.to!vt, 1.to!vt, 0.to!vt); /// ditto 104 | enum Vector e4 = Vector(0.to!vt, 0.to!vt, 0.to!vt, 1.to!vt); /// ditto 105 | } 106 | 107 | unittest { 108 | assert(vec2.e1.vector == [1.0, 0.0]); 109 | assert(vec2.e2.vector == [0.0, 1.0]); 110 | 111 | assert(vec3.e1.vector == [1.0, 0.0, 0.0]); 112 | assert(vec3.e2.vector == [0.0, 1.0, 0.0]); 113 | assert(vec3.e3.vector == [0.0, 0.0, 1.0]); 114 | 115 | assert(vec4.e1.vector == [1.0, 0.0, 0.0, 0.0]); 116 | assert(vec4.e2.vector == [0.0, 1.0, 0.0, 0.0]); 117 | assert(vec4.e3.vector == [0.0, 0.0, 1.0, 0.0]); 118 | assert(vec4.e4.vector == [0.0, 0.0, 0.0, 1.0]); 119 | } 120 | 121 | static void isCompatibleVectorImpl(int d)(Vector!(vt, d) vec) if(d <= dimension) { 122 | } 123 | 124 | template isCompatibleVector(T) { 125 | enum isCompatibleVector = is(typeof(isCompatibleVectorImpl(T.init))); 126 | } 127 | 128 | static void isCompatibleMatrixImpl(int r, int c)(Matrix!(vt, r, c) m) { 129 | } 130 | 131 | template isCompatibleMatrix(T) { 132 | enum isCompatibleMatrix = is(typeof(isCompatibleMatrixImpl(T.init))); 133 | } 134 | 135 | private void construct(int i, T, Tail...)(T head, Tail tail) { 136 | static if(i >= dimension) { 137 | static assert(false, "Too many arguments passed to constructor"); 138 | } else static if(is(T : vt)) { 139 | vector[i] = head; 140 | construct!(i + 1)(tail); 141 | } else static if(isDynamicArray!T) { 142 | static assert((Tail.length == 0) && (i == 0), "dynamic array can not be passed together with other arguments"); 143 | vector[] = head[]; 144 | } else static if(isStaticArray!T) { 145 | vector[i .. i + T.length] = head[]; 146 | construct!(i + T.length)(tail); 147 | } else static if(isCompatibleVector!T) { 148 | vector[i .. i + T.dimension] = head.vector[]; 149 | construct!(i + T.dimension)(tail); 150 | } else { 151 | static assert(false, "Vector constructor argument must be of type " ~ vt.stringof ~ " or Vector, not " ~ T.stringof); 152 | } 153 | } 154 | 155 | private void construct(int i)() { // terminate 156 | static assert(i == dimension, "Not enough arguments passed to constructor"); 157 | } 158 | 159 | /// Constructs the vector. 160 | /// If a single value is passed the vector, the vector will be cleared with this value. 161 | /// If a vector with a higher dimension is passed the vector will hold the first values up to its dimension. 162 | /// If mixed types are passed they will be joined together (allowed types: vector, static array, $(I vt)). 163 | /// Examples: 164 | /// --- 165 | /// vec4 v4 = vec4(1.0f, vec2(2.0f, 3.0f), 4.0f); 166 | /// vec3 v3 = vec3(v4); // v3 = vec3(1.0f, 2.0f, 3.0f); 167 | /// vec2 v2 = v3.xy; // swizzling returns a static array. 168 | /// vec3 v3_2 = vec3(1.0f); // vec3 v3_2 = vec3(1.0f, 1.0f, 1.0f); 169 | /// --- 170 | this(Args...)(Args args) { 171 | construct!(0)(args); 172 | } 173 | 174 | /// ditto 175 | this(T)(T vec) if(is_vector!T && is(T.vt : vt) && (T.dimension >= dimension)) { 176 | foreach(i; TupleRange!(0, dimension)) { 177 | vector[i] = vec.vector[i]; 178 | } 179 | } 180 | 181 | /// ditto 182 | this()(vt value) { 183 | clear(value); 184 | } 185 | 186 | /// Returns true if all values are not nan and finite, otherwise false. 187 | @property bool isFinite() const { 188 | static if(isIntegral!type) { 189 | return true; 190 | } 191 | else { 192 | foreach(v; vector) { 193 | if(isNaN(v) || isInfinity(v)) { 194 | return false; 195 | } 196 | } 197 | return true; 198 | } 199 | } 200 | deprecated("Use isFinite instead of ok") alias ok = isFinite; 201 | 202 | /// Sets all values of the vector to value. 203 | void clear(vt value) { 204 | foreach(i; TupleRange!(0, dimension)) { 205 | vector[i] = value; 206 | } 207 | } 208 | 209 | unittest { 210 | vec3 vec_clear; 211 | assert(!vec_clear.isFinite); 212 | vec_clear.clear(1.0f); 213 | assert(vec_clear.isFinite); 214 | assert(vec_clear.vector == [1.0f, 1.0f, 1.0f]); 215 | assert(vec_clear.vector == vec3(1.0f).vector); 216 | vec_clear.clear(float.infinity); 217 | assert(!vec_clear.isFinite); 218 | vec_clear.clear(float.nan); 219 | assert(!vec_clear.isFinite); 220 | vec_clear.clear(1.0f); 221 | assert(vec_clear.isFinite); 222 | 223 | vec4 b = vec4(1.0f, vec_clear); 224 | assert(b.isFinite); 225 | assert(b.vector == [1.0f, 1.0f, 1.0f, 1.0f]); 226 | assert(b.vector == vec4(1.0f).vector); 227 | 228 | vec2 v2_1 = vec2(vec2(0.0f, 1.0f)); 229 | assert(v2_1.vector == [0.0f, 1.0f]); 230 | 231 | vec2 v2_2 = vec2(1.0f, 1.0f); 232 | assert(v2_2.vector == [1.0f, 1.0f]); 233 | 234 | vec3 v3 = vec3(v2_1, 2.0f); 235 | assert(v3.vector == [0.0f, 1.0f, 2.0f]); 236 | 237 | vec4 v4_1 = vec4(1.0f, vec2(2.0f, 3.0f), 4.0f); 238 | assert(v4_1.vector == [1.0f, 2.0f, 3.0f, 4.0f]); 239 | assert(vec3(v4_1).vector == [1.0f, 2.0f, 3.0f]); 240 | assert(vec2(vec3(v4_1)).vector == [1.0f, 2.0f]); 241 | assert(vec2(vec3(v4_1)).vector == vec2(v4_1).vector); 242 | assert(v4_1.vector == vec4([1.0f, 2.0f, 3.0f, 4.0f]).vector); 243 | 244 | vec4 v4_2 = vec4(vec2(1.0f, 2.0f), vec2(3.0f, 4.0f)); 245 | assert(v4_2.vector == [1.0f, 2.0f, 3.0f, 4.0f]); 246 | assert(vec3(v4_2).vector == [1.0f, 2.0f, 3.0f]); 247 | assert(vec2(vec3(v4_2)).vector == [1.0f, 2.0f]); 248 | assert(vec2(vec3(v4_2)).vector == vec2(v4_2).vector); 249 | assert(v4_2.vector == vec4([1.0f, 2.0f, 3.0f, 4.0f]).vector); 250 | 251 | float[2] f2 = [1.0f, 2.0f]; 252 | float[3] f3 = [1.0f, 2.0f, 3.0f]; 253 | float[4] f4 = [1.0f, 2.0f, 3.0f, 4.0f]; 254 | assert(vec2(1.0f, 2.0f).vector == vec2(f2).vector); 255 | assert(vec3(1.0f, 2.0f, 3.0f).vector == vec3(f3).vector); 256 | assert(vec3(1.0f, 2.0f, 3.0f).vector == vec3(f2, 3.0f).vector); 257 | assert(vec4(1.0f, 2.0f, 3.0f, 4.0f).vector == vec4(f4).vector); 258 | assert(vec4(1.0f, 2.0f, 3.0f, 4.0f).vector == vec4(f3, 4.0f).vector); 259 | assert(vec4(1.0f, 2.0f, 3.0f, 4.0f).vector == vec4(f2, 3.0f, 4.0f).vector); 260 | // useful for: "vec4 v4 = […]" or "vec4 v4 = other_vector.rgba" 261 | 262 | assert(vec3(vec3i(1, 2, 3)) == vec3(1.0, 2.0, 3.0)); 263 | assert(vec3d(vec3(1.0, 2.0, 3.0)) == vec3d(1.0, 2.0, 3.0)); 264 | 265 | static assert(!__traits(compiles, vec3(0.0f, 0.0f))); 266 | static assert(!__traits(compiles, vec4(0.0f, 0.0f, 0.0f))); 267 | static assert(!__traits(compiles, vec4(0.0f, vec2(0.0f, 0.0f)))); 268 | static assert(!__traits(compiles, vec4(vec3(0.0f, 0.0f, 0.0f)))); 269 | } 270 | 271 | template coord_to_index(char c) { 272 | static if((c == 'x') || (c == 'r') || (c == 'u') || (c == 's')) { 273 | enum coord_to_index = 0; 274 | } else static if((c == 'y') || (c == 'g') || (c == 'v') || (c == 't')) { 275 | enum coord_to_index = 1; 276 | } else static if((c == 'z') || (c == 'b') || (c == 'p')) { 277 | static assert(dimension >= 3, "the " ~ c ~ " property is only available on vectors with a third dimension."); 278 | enum coord_to_index = 2; 279 | } else static if((c == 'w') || (c == 'a') || (c == 'q')) { 280 | static assert(dimension >= 4, "the " ~ c ~ " property is only available on vectors with a fourth dimension."); 281 | enum coord_to_index = 3; 282 | } else { 283 | static assert(false, "accepted coordinates are x, s, r, u, y, g, t, v, z, p, b, w, q and a not " ~ c ~ "."); 284 | } 285 | } 286 | 287 | static if(dimension == 2) { void set(vt x, vt y) { vector[0] = x; vector[1] = y; } } 288 | static if(dimension == 3) { void set(vt x, vt y, vt z) { vector[0] = x; vector[1] = y; vector[2] = z; } } 289 | static if(dimension == 4) { void set(vt x, vt y, vt z, vt w) { vector[0] = x; vector[1] = y; vector[2] = z; vector[3] = w; } } 290 | 291 | /// Updates the vector with the values from other. 292 | void update(Vector!(vt, dimension) other) { 293 | vector = other.vector; 294 | } 295 | 296 | unittest { 297 | vec2 v2 = vec2(1.0f, 2.0f); 298 | assert(v2.x == 1.0f); 299 | assert(v2.y == 2.0f); 300 | v2.x = 3.0f; 301 | v2.x += 1; 302 | v2.x -= 1; 303 | assert(v2.vector == [3.0f, 2.0f]); 304 | v2.y = 4.0f; 305 | v2.y += 1; 306 | v2.y -= 1; 307 | assert(v2.vector == [3.0f, 4.0f]); 308 | assert((v2.x == 3.0f) && (v2.x == v2.u) && (v2.x == v2.s) && (v2.x == v2.r)); 309 | assert(v2.y == 4.0f); 310 | assert((v2.y == 4.0f) && (v2.y == v2.v) && (v2.y == v2.t) && (v2.y == v2.g)); 311 | v2.set(0.0f, 1.0f); 312 | assert(v2.vector == [0.0f, 1.0f]); 313 | v2.update(vec2(3.0f, 4.0f)); 314 | assert(v2.vector == [3.0f, 4.0f]); 315 | 316 | vec3 v3 = vec3(1.0f, 2.0f, 3.0f); 317 | assert(v3.x == 1.0f); 318 | assert(v3.y == 2.0f); 319 | assert(v3.z == 3.0f); 320 | v3.x = 3.0f; 321 | v3.x += 1; 322 | v3.x -= 1; 323 | assert(v3.vector == [3.0f, 2.0f, 3.0f]); 324 | v3.y = 4.0f; 325 | v3.y += 1; 326 | v3.y -= 1; 327 | assert(v3.vector == [3.0f, 4.0f, 3.0f]); 328 | v3.z = 5.0f; 329 | v3.z += 1; 330 | v3.z -= 1; 331 | assert(v3.vector == [3.0f, 4.0f, 5.0f]); 332 | assert((v3.x == 3.0f) && (v3.x == v3.s) && (v3.x == v3.r)); 333 | assert((v3.y == 4.0f) && (v3.y == v3.t) && (v3.y == v3.g)); 334 | assert((v3.z == 5.0f) && (v3.z == v3.p) && (v3.z == v3.b)); 335 | v3.set(0.0f, 1.0f, 2.0f); 336 | assert(v3.vector == [0.0f, 1.0f, 2.0f]); 337 | v3.update(vec3(3.0f, 4.0f, 5.0f)); 338 | assert(v3.vector == [3.0f, 4.0f, 5.0f]); 339 | 340 | vec4 v4 = vec4(1.0f, 2.0f, vec2(3.0f, 4.0f)); 341 | assert(v4.x == 1.0f); 342 | assert(v4.y == 2.0f); 343 | assert(v4.z == 3.0f); 344 | assert(v4.w == 4.0f); 345 | v4.x = 3.0f; 346 | v4.x += 1; 347 | v4.x -= 1; 348 | assert(v4.vector == [3.0f, 2.0f, 3.0f, 4.0f]); 349 | v4.y = 4.0f; 350 | v4.y += 1; 351 | v4.y -= 1; 352 | assert(v4.vector == [3.0f, 4.0f, 3.0f, 4.0f]); 353 | v4.z = 5.0f; 354 | v4.z += 1; 355 | v4.z -= 1; 356 | assert(v4.vector == [3.0f, 4.0f, 5.0f, 4.0f]); 357 | v4.w = 6.0f; 358 | v4.w += 1; 359 | v4.w -= 1; 360 | assert(v4.vector == [3.0f, 4.0f, 5.0f, 6.0f]); 361 | assert((v4.x == 3.0f) && (v4.x == v4.s) && (v4.x == v4.r)); 362 | assert((v4.y == 4.0f) && (v4.y == v4.t) && (v4.y == v4.g)); 363 | assert((v4.z == 5.0f) && (v4.z == v4.p) && (v4.z == v4.b)); 364 | assert((v4.w == 6.0f) && (v4.w == v4.q) && (v4.w == v4.a)); 365 | v4.set(0.0f, 1.0f, 2.0f, 3.0f); 366 | assert(v4.vector == [0.0f, 1.0f, 2.0f, 3.0f]); 367 | v4.update(vec4(3.0f, 4.0f, 5.0f, 6.0f)); 368 | assert(v4.vector == [3.0f, 4.0f, 5.0f, 6.0f]); 369 | } 370 | 371 | private void dispatchImpl(int i, string s, int size)(ref vt[size] result) const { 372 | static if(s.length > 0) { 373 | result[i] = vector[coord_to_index!(s[0])]; 374 | dispatchImpl!(i + 1, s[1..$])(result); 375 | } 376 | } 377 | 378 | /// Implements dynamic swizzling. 379 | /// Returns: a Vector 380 | @property Vector!(vt, s.length) opDispatch(string s)() const { 381 | vt[s.length] ret; 382 | dispatchImpl!(0, s)(ret); 383 | Vector!(vt, s.length) ret_vec; 384 | ret_vec.vector = ret; 385 | return ret_vec; 386 | } 387 | 388 | unittest { 389 | vec2 v2 = vec2(1.0f, 2.0f); 390 | assert(v2.xytsy == [1.0f, 2.0f, 2.0f, 1.0f, 2.0f]); 391 | 392 | assert(vec3(1.0f, 2.0f, 3.0f).xybzyr == [1.0f, 2.0f, 3.0f, 3.0f, 2.0f, 1.0f]); 393 | assert(vec4(v2, 3.0f, 4.0f).xyzwrgbastpq == [1.0f, 2.0f, 3.0f, 4.0f, 394 | 1.0f, 2.0f, 3.0f, 4.0f, 395 | 1.0f, 2.0f, 3.0f, 4.0f]); 396 | assert(vec4(v2, 3.0f, 4.0f).wgyzax == [4.0f, 2.0f, 2.0f, 3.0f, 4.0f, 1.0f]); 397 | assert(vec4(v2.xyst).vector == [1.0f, 2.0f, 1.0f, 2.0f]); 398 | } 399 | 400 | /// Returns the squared magnitude of the vector. 401 | @property real magnitude_squared() const { 402 | real temp = 0; 403 | 404 | foreach(index; TupleRange!(0, dimension)) { 405 | temp += vector[index]^^2; 406 | } 407 | 408 | return temp; 409 | } 410 | 411 | /// Returns the magnitude of the vector. 412 | @property real magnitude() const { 413 | return sqrt(magnitude_squared); 414 | } 415 | 416 | alias magnitude_squared length_squared; /// ditto 417 | alias magnitude length; /// ditto 418 | 419 | /// Normalizes the vector. 420 | void normalize() { 421 | real len = length; 422 | 423 | if(len != 0) { 424 | foreach(index; TupleRange!(0, dimension)) { 425 | vector[index] = cast(type)(vector[index]/len); 426 | } 427 | } 428 | } 429 | 430 | /// Returns a normalized copy of the current vector. 431 | @property Vector normalized() const { 432 | Vector ret; 433 | ret.update(this); 434 | ret.normalize(); 435 | return ret; 436 | } 437 | 438 | Vector opUnary(string op : "-")() const { 439 | Vector ret; 440 | 441 | foreach(index; TupleRange!(0, dimension)) { 442 | ret.vector[index] = -vector[index]; 443 | } 444 | 445 | return ret; 446 | } 447 | 448 | unittest { 449 | assert(vec2(1.0f, 1.0f) == -vec2(-1.0f, -1.0f)); 450 | assert(vec2(-1.0f, 1.0f) == -vec2(1.0f, -1.0f)); 451 | 452 | assert(-vec3(1.0f, 1.0f, 1.0f) == vec3(-1.0f, -1.0f, -1.0f)); 453 | assert(-vec3(-1.0f, 1.0f, -1.0f) == vec3(1.0f, -1.0f, 1.0f)); 454 | 455 | assert(vec4(1.0f, 1.0f, 1.0f, 1.0f) == -vec4(-1.0f, -1.0f, -1.0f, -1.0f)); 456 | assert(vec4(-1.0f, 1.0f, -1.0f, 1.0f) == -vec4(1.0f, -1.0f, 1.0f, -1.0f)); 457 | } 458 | 459 | // let the math begin! 460 | Vector opBinary(string op : "*")(vt r) const { 461 | Vector ret; 462 | 463 | foreach(index; TupleRange!(0, dimension)) { 464 | ret.vector[index] = vector[index] * r; 465 | } 466 | 467 | return ret; 468 | } 469 | 470 | Vector opBinary(string op : "/")(vt r) const { 471 | Vector ret; 472 | 473 | foreach(index; TupleRange!(0, dimension)) { 474 | ret.vector[index] = cast(vt)(vector[index] / r); 475 | } 476 | 477 | return ret; 478 | } 479 | 480 | Vector opBinary(string op)(Vector r) const if((op == "+") || (op == "-")) { 481 | Vector ret; 482 | 483 | foreach(index; TupleRange!(0, dimension)) { 484 | ret.vector[index] = mixin("cast(vt)(vector[index]" ~ op ~ "r.vector[index])"); 485 | } 486 | 487 | return ret; 488 | } 489 | 490 | vt opBinary(string op : "*")(Vector r) const { 491 | return dot(this, r); 492 | } 493 | 494 | // vector * matrix (for matrix * vector -> struct Matrix) 495 | Vector!(vt, T.cols) opBinary(string op : "*", T)(T inp) const if(isCompatibleMatrix!T && (T.rows == dimension)) { 496 | Vector!(vt, T.cols) ret; 497 | ret.clear(0); 498 | 499 | foreach(c; TupleRange!(0, T.cols)) { 500 | foreach(r; TupleRange!(0, T.rows)) { 501 | ret.vector[c] += vector[r] * inp.matrix[r][c]; 502 | } 503 | } 504 | 505 | return ret; 506 | } 507 | 508 | auto opBinaryRight(string op, T)(T inp) const if(!is_vector!T && !is_matrix!T && !is_quaternion!T) { 509 | return this.opBinary!(op)(inp); 510 | } 511 | 512 | unittest { 513 | vec2 v2 = vec2(1.0f, 3.0f); 514 | auto v2times2 = 2 * v2; 515 | assert((v2*2.5f).vector == [2.5f, 7.5f]); 516 | assert((v2+vec2(3.0f, 1.0f)).vector == [4.0f, 4.0f]); 517 | assert((v2-vec2(1.0f, 3.0f)).vector == [0.0f, 0.0f]); 518 | assert((v2*vec2(2.0f, 2.0f)) == 8.0f); 519 | 520 | vec3 v3 = vec3(1.0f, 3.0f, 5.0f); 521 | assert((v3*2.5f).vector == [2.5f, 7.5f, 12.5f]); 522 | assert((v3+vec3(3.0f, 1.0f, -1.0f)).vector == [4.0f, 4.0f, 4.0f]); 523 | assert((v3-vec3(1.0f, 3.0f, 5.0f)).vector == [0.0f, 0.0f, 0.0f]); 524 | assert((v3*vec3(2.0f, 2.0f, 2.0f)) == 18.0f); 525 | 526 | vec4 v4 = vec4(1.0f, 3.0f, 5.0f, 7.0f); 527 | assert((v4*2.5f).vector == [2.5f, 7.5f, 12.5f, 17.5]); 528 | assert((v4+vec4(3.0f, 1.0f, -1.0f, -3.0f)).vector == [4.0f, 4.0f, 4.0f, 4.0f]); 529 | assert((v4-vec4(1.0f, 3.0f, 5.0f, 7.0f)).vector == [0.0f, 0.0f, 0.0f, 0.0f]); 530 | assert((v4*vec4(2.0f, 2.0f, 2.0f, 2.0f)) == 32.0f); 531 | 532 | mat2 m2 = mat2(1.0f, 2.0f, 3.0f, 4.0f); 533 | vec2 v2_2 = vec2(2.0f, 2.0f); 534 | assert((v2_2*m2).vector == [8.0f, 12.0f]); 535 | 536 | mat3 m3 = mat3(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f); 537 | vec3 v3_2 = vec3(2.0f, 2.0f, 2.0f); 538 | assert((v3_2*m3).vector == [24.0f, 30.0f, 36.0f]); 539 | } 540 | 541 | void opOpAssign(string op : "*")(vt r) { 542 | foreach(index; TupleRange!(0, dimension)) { 543 | vector[index] *= r; 544 | } 545 | } 546 | 547 | void opOpAssign(string op : "/")(vt r) { 548 | foreach(index; TupleRange!(0, dimension)) { 549 | vector[index] /= r; 550 | } 551 | } 552 | 553 | void opOpAssign(string op)(Vector r) if((op == "+") || (op == "-")) { 554 | foreach(index; TupleRange!(0, dimension)) { 555 | mixin("vector[index]" ~ op ~ "= r.vector[index];"); 556 | } 557 | } 558 | 559 | unittest { 560 | vec2 v2 = vec2(1.0f, 3.0f); 561 | v2 *= 2.5f; 562 | assert(v2.vector == [2.5f, 7.5f]); 563 | v2 -= vec2(2.5f, 7.5f); 564 | assert(v2.vector == [0.0f, 0.0f]); 565 | v2 += vec2(1.0f, 3.0f); 566 | assert(v2.vector == [1.0f, 3.0f]); 567 | assert(almost_equal(v2.length, sqrt(10.0f))); 568 | assert(v2.length_squared == 10.0f); 569 | assert((v2.magnitude == v2.length) && (v2.magnitude_squared == v2.length_squared)); 570 | v2 /= 2.0f; 571 | assert(v2.vector == [0.5f, 1.5f]); 572 | assert(almost_equal(v2.normalized, vec2(1.0f/sqrt(10.0f), 3.0f/sqrt(10.0f)))); 573 | 574 | vec3 v3 = vec3(1.0f, 3.0f, 5.0f); 575 | v3 *= 2.5f; 576 | assert(v3.vector == [2.5f, 7.5f, 12.5f]); 577 | v3 -= vec3(2.5f, 7.5f, 12.5f); 578 | assert(v3.vector == [0.0f, 0.0f, 0.0f]); 579 | v3 += vec3(1.0f, 3.0f, 5.0f); 580 | assert(v3.vector == [1.0f, 3.0f, 5.0f]); 581 | assert(almost_equal(v3.length, sqrt(35.0f))); 582 | assert(v3.length_squared == 35.0f); 583 | assert((v3.magnitude == v3.length) && (v3.magnitude_squared == v3.length_squared)); 584 | v3 /= 2.0f; 585 | assert(v3.vector == [0.5f, 1.5f, 2.5f]); 586 | assert(almost_equal(v3.normalized, vec3(1.0f/sqrt(35.0f), 3.0f/sqrt(35.0f), 5.0f/sqrt(35.0f)))); 587 | 588 | vec4 v4 = vec4(1.0f, 3.0f, 5.0f, 7.0f); 589 | v4 *= 2.5f; 590 | assert(v4.vector == [2.5f, 7.5f, 12.5f, 17.5]); 591 | v4 -= vec4(2.5f, 7.5f, 12.5f, 17.5f); 592 | assert(v4.vector == [0.0f, 0.0f, 0.0f, 0.0f]); 593 | v4 += vec4(1.0f, 3.0f, 5.0f, 7.0f); 594 | assert(v4.vector == [1.0f, 3.0f, 5.0f, 7.0f]); 595 | assert(almost_equal(v4.length, sqrt(84.0f))); 596 | assert(v4.length_squared == 84.0f); 597 | assert((v4.magnitude == v4.length) && (v4.magnitude_squared == v4.length_squared)); 598 | v4 /= 2.0f; 599 | assert(v4.vector == [0.5f, 1.5f, 2.5f, 3.5f]); 600 | assert(almost_equal(v4.normalized, vec4(1.0f/sqrt(84.0f), 3.0f/sqrt(84.0f), 5.0f/sqrt(84.0f), 7.0f/sqrt(84.0f)))); 601 | } 602 | 603 | int opCmp(ref const Vector vec) const { 604 | foreach(i, a; vector) { 605 | if(a < vec.vector[i]) { 606 | return -1; 607 | } else if(a > vec.vector[i]) { 608 | return 1; 609 | } 610 | } 611 | 612 | // Vectors are the same 613 | return 0; 614 | } 615 | 616 | bool opEquals(T)(const T vec) const if(!isArray!T && T.dimension == dimension) { 617 | return vector == vec.vector; 618 | } 619 | 620 | bool opEquals(T)(const(T)[] array) const if(!isArray!T && !is_vector!T) { 621 | if(array.length != dimension) { 622 | return false; 623 | } 624 | 625 | foreach(index; TupleRange!(0, dimension)) { 626 | if(vector[index] != array[index]) { 627 | return false; 628 | } 629 | } 630 | 631 | return true; 632 | } 633 | 634 | bool opCast(T : bool)() const { 635 | return isFinite; 636 | } 637 | 638 | unittest { 639 | assert(vec2(1.0f, 2.0f) == vec2(1.0f, 2.0f)); 640 | assert(vec2(1.0f, 2.0f) != vec2(1.0f, 1.0f)); 641 | assert(vec2(1.0f, 2.0f) == vec2d(1.0, 2.0)); 642 | assert(vec2(1.0f, 2.0f) != vec2d(1.0, 1.0)); 643 | assert(vec2(1.0f, 2.0f) == vec2(1.0f, 2.0f).vector); 644 | assert(vec2(1.0f, 2.0f) != vec2(1.0f, 1.0f).vector); 645 | assert(vec2(1.0f, 2.0f) == vec2d(1.0, 2.0).vector); 646 | assert(vec2(1.0f, 2.0f) != vec2d(1.0, 1.0).vector); 647 | 648 | assert(vec3(1.0f, 2.0f, 3.0f) == vec3(1.0f, 2.0f, 3.0f)); 649 | assert(vec3(1.0f, 2.0f, 3.0f) != vec3(1.0f, 2.0f, 2.0f)); 650 | assert(vec3(1.0f, 2.0f, 3.0f) == vec3d(1.0, 2.0, 3.0)); 651 | assert(vec3(1.0f, 2.0f, 3.0f) != vec3d(1.0, 2.0, 2.0)); 652 | assert(vec3(1.0f, 2.0f, 3.0f) == vec3(1.0f, 2.0f, 3.0f).vector); 653 | assert(vec3(1.0f, 2.0f, 3.0f) != vec3(1.0f, 2.0f, 2.0f).vector); 654 | assert(vec3(1.0f, 2.0f, 3.0f) == vec3d(1.0, 2.0, 3.0).vector); 655 | assert(vec3(1.0f, 2.0f, 3.0f) != vec3d(1.0, 2.0, 2.0).vector); 656 | 657 | assert(vec4(1.0f, 2.0f, 3.0f, 4.0f) == vec4(1.0f, 2.0f, 3.0f, 4.0f)); 658 | assert(vec4(1.0f, 2.0f, 3.0f, 4.0f) != vec4(1.0f, 2.0f, 3.0f, 3.0f)); 659 | assert(vec4(1.0f, 2.0f, 3.0f, 4.0f) == vec4d(1.0, 2.0, 3.0, 4.0)); 660 | assert(vec4(1.0f, 2.0f, 3.0f, 4.0f) != vec4d(1.0, 2.0, 3.0, 3.0)); 661 | assert(vec4(1.0f, 2.0f, 3.0f, 4.0f) == vec4(1.0f, 2.0f, 3.0f, 4.0f).vector); 662 | assert(vec4(1.0f, 2.0f, 3.0f, 4.0f) != vec4(1.0f, 2.0f, 3.0f, 3.0f).vector); 663 | assert(vec4(1.0f, 2.0f, 3.0f, 4.0f) == vec4d(1.0, 2.0, 3.0, 4.0).vector); 664 | assert(vec4(1.0f, 2.0f, 3.0f, 4.0f) != vec4d(1.0, 2.0, 3.0, 3.0).vector); 665 | 666 | assert(!(vec4(float.nan))); 667 | if(vec4(1.0f)) { } 668 | else { assert(false); } 669 | } 670 | 671 | } 672 | 673 | /// Calculates the product between two vectors. 674 | T.vt dot(T)(const T veca, const T vecb) @safe pure nothrow if(is_vector!T) { 675 | T.vt temp = 0; 676 | 677 | foreach(index; TupleRange!(0, T.dimension)) { 678 | temp += veca.vector[index] * vecb.vector[index]; 679 | } 680 | 681 | return temp; 682 | } 683 | 684 | /// Calculates the cross product of two 3-dimensional vectors. 685 | T cross(T)(const T veca, const T vecb) @safe pure nothrow if(is_vector!T && (T.dimension == 3)) { 686 | return T(veca.y * vecb.z - vecb.y * veca.z, 687 | veca.z * vecb.x - vecb.z * veca.x, 688 | veca.x * vecb.y - vecb.x * veca.y); 689 | } 690 | 691 | /// Calculates the distance between two vectors. 692 | T.vt distance(T)(const T veca, const T vecb) @safe pure nothrow if(is_vector!T) { 693 | return (veca - vecb).length; 694 | } 695 | 696 | unittest { 697 | // dot is already tested in Vector.opBinary, so no need for testing with more vectors 698 | vec3 v1 = vec3(1.0f, 2.0f, -3.0f); 699 | vec3 v2 = vec3(1.0f, 3.0f, 2.0f); 700 | 701 | assert(dot(v1, v2) == 1.0f); 702 | assert(dot(v1, v2) == (v1 * v2)); 703 | assert(dot(v1, v2) == dot(v2, v1)); 704 | assert((v1 * v2) == (v1 * v2)); 705 | 706 | assert(cross(v1, v2).vector == [13.0f, -5.0f, 1.0f]); 707 | assert(cross(v2, v1).vector == [-13.0f, 5.0f, -1.0f]); 708 | 709 | assert(distance(vec2(0.0f, 0.0f), vec2(0.0f, 10.0f)) == 10.0); 710 | } 711 | 712 | /// reflect a vector using a surface normal 713 | T reflect(T)(const T vec, const T norm) @safe pure nothrow if(is_vector!T) { 714 | return (2 * (vec * norm) * norm) - vec; 715 | } 716 | 717 | unittest 718 | { 719 | assert(vec2(1,1).reflect(vec2(0,1)) == vec2(-1,1)); 720 | assert(vec2(-1,1).reflect(vec2(0,1)) == vec2(1,1)); 721 | assert(vec2(2,1).reflect(vec2(0,1)) == vec2(-2,1)); 722 | 723 | assert(vec3(1,1,1).reflect(vec3(0,1,0)) == vec3(-1,1,-1)); 724 | } 725 | 726 | /// Pre-defined vector types, the number represents the dimension and the last letter the type (none = float, d = double, i = int). 727 | alias Vector!(float, 2) vec2; 728 | alias Vector!(float, 3) vec3; /// ditto 729 | alias Vector!(float, 4) vec4; /// ditto 730 | 731 | alias Vector!(double, 2) vec2d; /// ditto 732 | alias Vector!(double, 3) vec3d; /// ditto 733 | alias Vector!(double, 4) vec4d; /// ditto 734 | 735 | alias Vector!(int, 2) vec2i; /// ditto 736 | alias Vector!(int, 3) vec3i; /// ditto 737 | alias Vector!(int, 4) vec4i; /// ditto 738 | 739 | /*alias Vector!(ubyte, 2) vec2ub; 740 | alias Vector!(ubyte, 3) vec3ub; 741 | alias Vector!(ubyte, 4) vec4ub;*/ 742 | 743 | 744 | /// Base template for all matrix-types. 745 | /// Params: 746 | /// type = all values get stored as this type 747 | /// rows_ = rows of the matrix 748 | /// cols_ = columns of the matrix 749 | /// Examples: 750 | /// --- 751 | /// alias Matrix!(float, 4, 4) mat4; 752 | /// alias Matrix!(double, 3, 4) mat34d; 753 | /// alias Matrix!(real, 2, 2) mat2r; 754 | /// --- 755 | struct Matrix(type, int rows_, int cols_) if((rows_ > 0) && (cols_ > 0)) { 756 | alias type mt; /// Holds the internal type of the matrix; 757 | static const int rows = rows_; /// Holds the number of rows; 758 | static const int cols = cols_; /// Holds the number of columns; 759 | 760 | /// Holds the matrix $(RED row-major) in memory. 761 | mt[cols][rows] matrix; // In C it would be mt[rows][cols], D does it like this: (mt[foo])[bar] 762 | alias matrix this; 763 | 764 | unittest { 765 | mat2 m2 = mat2(0.0f, 1.0f, 2.0f, 3.0f); 766 | assert(m2[0][0] == 0.0f); 767 | assert(m2[0][1] == 1.0f); 768 | assert(m2[1][0] == 2.0f); 769 | assert(m2[1][1] == 3.0f); 770 | m2[0..1] = [2.0f, 2.0f]; 771 | assert(m2 == [[2.0f, 2.0f], [2.0f, 3.0f]]); 772 | 773 | mat3 m3 = mat3(0.0f, 0.1f, 0.2f, 1.0f, 1.1f, 1.2f, 2.0f, 2.1f, 2.2f); 774 | assert(m3[0][1] == 0.1f); 775 | assert(m3[2][0] == 2.0f); 776 | assert(m3[1][2] == 1.2f); 777 | m3[0][0..$] = 0.0f; 778 | assert(m3 == [[0.0f, 0.0f, 0.0f], 779 | [1.0f, 1.1f, 1.2f], 780 | [2.0f, 2.1f, 2.2f]]); 781 | 782 | mat4 m4 = mat4(0.0f, 0.1f, 0.2f, 0.3f, 783 | 1.0f, 1.1f, 1.2f, 1.3f, 784 | 2.0f, 2.1f, 2.2f, 2.3f, 785 | 3.0f, 3.1f, 3.2f, 3.3f); 786 | assert(m4[0][3] == 0.3f); 787 | assert(m4[1][1] == 1.1f); 788 | assert(m4[2][0] == 2.0f); 789 | assert(m4[3][2] == 3.2f); 790 | m4[2][1..3] = [1.0f, 2.0f]; 791 | assert(m4 == [[0.0f, 0.1f, 0.2f, 0.3f], 792 | [1.0f, 1.1f, 1.2f, 1.3f], 793 | [2.0f, 1.0f, 2.0f, 2.3f], 794 | [3.0f, 3.1f, 3.2f, 3.3f]]); 795 | 796 | } 797 | 798 | /// Returns the pointer to the stored values as OpenGL requires it. 799 | /// Note this will return a pointer to a $(RED row-major) matrix, 800 | /// $(RED this means you've to set the transpose argument to GL_TRUE when passing it to OpenGL). 801 | /// Examples: 802 | /// --- 803 | /// // 3rd argument = GL_TRUE 804 | /// glUniformMatrix4fv(programs.main.model, 1, GL_TRUE, mat4.translation(-0.5f, -0.5f, 1.0f).value_ptr); 805 | /// --- 806 | @property auto value_ptr() const { return matrix[0].ptr; } 807 | 808 | /// Returns the current matrix formatted as flat string. 809 | @property string as_string() { 810 | return format("%s", matrix); 811 | } 812 | alias as_string toString; /// ditto 813 | 814 | /// Returns the current matrix as pretty formatted string. 815 | @property string as_pretty_string() { 816 | string fmtr = "%s"; 817 | 818 | size_t rjust = max(format(fmtr, reduce!(max)(matrix[])).length, 819 | format(fmtr, reduce!(min)(matrix[])).length) - 1; 820 | 821 | string[] outer_parts; 822 | foreach(mt[] row; matrix) { 823 | string[] inner_parts; 824 | foreach(mt col; row) { 825 | inner_parts ~= rightJustify(format(fmtr, col), rjust); 826 | } 827 | outer_parts ~= " [" ~ join(inner_parts, ", ") ~ "]"; 828 | } 829 | 830 | return "[" ~ join(outer_parts, "\n")[1..$] ~ "]"; 831 | } 832 | alias as_pretty_string toPrettyString; /// ditto 833 | 834 | @safe pure nothrow: 835 | static void isCompatibleMatrixImpl(int r, int c)(Matrix!(mt, r, c) m) { 836 | } 837 | 838 | template isCompatibleMatrix(T) { 839 | enum isCompatibleMatrix = is(typeof(isCompatibleMatrixImpl(T.init))); 840 | } 841 | 842 | static void isCompatibleVectorImpl(int d)(Vector!(mt, d) vec) { 843 | } 844 | 845 | template isCompatibleVector(T) { 846 | enum isCompatibleVector = is(typeof(isCompatibleVectorImpl(T.init))); 847 | } 848 | 849 | private void construct(int i, T, Tail...)(T head, Tail tail) { 850 | static if(i >= rows*cols) { 851 | static assert(false, "Too many arguments passed to constructor"); 852 | } else static if(is(T : mt)) { 853 | matrix[i / cols][i % cols] = head; 854 | construct!(i + 1)(tail); 855 | } else static if(is(T == Vector!(mt, cols))) { 856 | static if(i % cols == 0) { 857 | matrix[i / cols] = head.vector; 858 | construct!(i + T.dimension)(tail); 859 | } else { 860 | static assert(false, "Can't convert Vector into the matrix. Maybe it doesn't align to the columns correctly or dimension doesn't fit"); 861 | } 862 | } else static if(isDynamicArray!T) { 863 | foreach(j; 0..cols*rows) 864 | matrix[j / cols][j % cols] = head[j]; 865 | } else { 866 | static assert(false, "Matrix constructor argument must be of type " ~ mt.stringof ~ " or Vector, not " ~ T.stringof); 867 | } 868 | } 869 | 870 | private void construct(int i)() { // terminate 871 | static assert(i == rows*cols, "Not enough arguments passed to constructor"); 872 | } 873 | 874 | /// Constructs the matrix: 875 | /// If a single value is passed, the matrix will be cleared with this value (each column in each row will contain this value). 876 | /// If a matrix with more rows and columns is passed, the matrix will be the upper left nxm matrix. 877 | /// If a matrix with less rows and columns is passed, the passed matrix will be stored in the upper left of an identity matrix. 878 | /// It's also allowed to pass vectors and scalars at a time, but the vectors dimension must match the number of columns and align correctly. 879 | /// Examples: 880 | /// --- 881 | /// mat2 m2 = mat2(0.0f); // mat2 m2 = mat2(0.0f, 0.0f, 0.0f, 0.0f); 882 | /// mat3 m3 = mat3(m2); // mat3 m3 = mat3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f); 883 | /// mat3 m3_2 = mat3(vec3(1.0f, 2.0f, 3.0f), 4.0f, 5.0f, 6.0f, vec3(7.0f, 8.0f, 9.0f)); 884 | /// mat4 m4 = mat4.identity; // just an identity matrix 885 | /// mat3 m3_3 = mat3(m4); // mat3 m3_3 = mat3.identity 886 | /// --- 887 | this(Args...)(Args args) { 888 | construct!(0)(args); 889 | } 890 | 891 | /// ditto 892 | this(T)(T mat) if(is_matrix!T && (T.cols >= cols) && (T.rows >= rows)) { 893 | foreach(r; TupleRange!(0, rows)) { 894 | foreach(c; TupleRange!(0, cols)) { 895 | matrix[r][c] = mat.matrix[r][c]; 896 | } 897 | } 898 | } 899 | 900 | /// ditto 901 | this(T)(T mat) if(is_matrix!T && (T.cols < cols) && (T.rows < rows)) { 902 | make_identity(); 903 | 904 | foreach(r; TupleRange!(0, T.rows)) { 905 | foreach(c; TupleRange!(0, T.cols)) { 906 | matrix[r][c] = mat.matrix[r][c]; 907 | } 908 | } 909 | } 910 | 911 | /// ditto 912 | this()(mt value) { 913 | clear(value); 914 | } 915 | 916 | /// Returns true if all values are not nan and finite, otherwise false. 917 | @property bool isFinite() const { 918 | static if(isIntegral!type) { 919 | return true; 920 | } 921 | else { 922 | foreach(row; matrix) { 923 | foreach(col; row) { 924 | if(isNaN(col) || isInfinity(col)) { 925 | return false; 926 | } 927 | } 928 | } 929 | return true; 930 | } 931 | 932 | } 933 | deprecated("Use isFinite instead of ok") alias ok = isFinite; 934 | 935 | /// Sets all values of the matrix to value (each column in each row will contain this value). 936 | void clear(mt value) { 937 | foreach(r; TupleRange!(0, rows)) { 938 | foreach(c; TupleRange!(0, cols)) { 939 | matrix[r][c] = value; 940 | } 941 | } 942 | } 943 | 944 | unittest { 945 | mat2 m2 = mat2(1.0f, 1.0f, vec2(2.0f, 2.0f)); 946 | assert(m2.matrix == [[1.0f, 1.0f], [2.0f, 2.0f]]); 947 | m2.clear(3.0f); 948 | assert(m2.matrix == [[3.0f, 3.0f], [3.0f, 3.0f]]); 949 | assert(m2.isFinite); 950 | m2.clear(float.nan); 951 | assert(!m2.isFinite); 952 | m2.clear(float.infinity); 953 | assert(!m2.isFinite); 954 | m2.clear(0.0f); 955 | assert(m2.isFinite); 956 | 957 | mat3 m3 = mat3(1.0f); 958 | assert(m3.matrix == [[1.0f, 1.0f, 1.0f], 959 | [1.0f, 1.0f, 1.0f], 960 | [1.0f, 1.0f, 1.0f]]); 961 | 962 | mat4 m4 = mat4(vec4(1.0f, 1.0f, 1.0f, 1.0f), 963 | 2.0f, 2.0f, 2.0f, 2.0f, 964 | 3.0f, 3.0f, 3.0f, 3.0f, 965 | vec4(4.0f, 4.0f, 4.0f, 4.0f)); 966 | assert(m4.matrix == [[1.0f, 1.0f, 1.0f, 1.0f], 967 | [2.0f, 2.0f, 2.0f, 2.0f], 968 | [3.0f, 3.0f, 3.0f, 3.0f], 969 | [4.0f, 4.0f, 4.0f, 4.0f]]); 970 | assert(mat3(m4).matrix == [[1.0f, 1.0f, 1.0f], 971 | [2.0f, 2.0f, 2.0f], 972 | [3.0f, 3.0f, 3.0f]]); 973 | assert(mat2(mat3(m4)).matrix == [[1.0f, 1.0f], [2.0f, 2.0f]]); 974 | assert(mat2(m4).matrix == mat2(mat3(m4)).matrix); 975 | assert(mat4(mat3(m4)).matrix == [[1.0f, 1.0f, 1.0f, 0.0f], 976 | [2.0f, 2.0f, 2.0f, 0.0f], 977 | [3.0f, 3.0f, 3.0f, 0.0f], 978 | [0.0f, 0.0f, 0.0f, 1.0f]]); 979 | 980 | Matrix!(float, 2, 3) mt1 = Matrix!(float, 2, 3)(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f); 981 | Matrix!(float, 3, 2) mt2 = Matrix!(float, 3, 2)(6.0f, -1.0f, 3.0f, 2.0f, 0.0f, -3.0f); 982 | 983 | assert(mt1.matrix == [[1.0f, 2.0f, 3.0f], [4.0f, 5.0f, 6.0f]]); 984 | assert(mt2.matrix == [[6.0f, -1.0f], [3.0f, 2.0f], [0.0f, -3.0f]]); 985 | 986 | static assert(!__traits(compiles, mat2(1, 2, 1))); 987 | static assert(!__traits(compiles, mat3(1, 2, 3, 1, 2, 3, 1, 2))); 988 | static assert(!__traits(compiles, mat4(1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3))); 989 | 990 | auto m5 = mat2([0.0f,1,2,3]); 991 | assert(m5.matrix == [[0.0f, 1.0f], [2.0f, 3.0f]]); 992 | 993 | auto m6 = Matrix!(int, 2, 3)([0,1,2,3,4,5]); 994 | assert(m6.matrix == [[0, 1, 2], [3, 4, 5]]); 995 | } 996 | 997 | static if(rows == cols) { 998 | /// Makes the current matrix an identity matrix. 999 | void make_identity() { 1000 | clear(0); 1001 | foreach(r; TupleRange!(0, rows)) { 1002 | matrix[r][r] = 1; 1003 | } 1004 | } 1005 | 1006 | /// Returns a identity matrix. 1007 | static @property Matrix identity() { 1008 | Matrix ret; 1009 | ret.clear(0); 1010 | 1011 | foreach(r; TupleRange!(0, rows)) { 1012 | ret.matrix[r][r] = 1; 1013 | } 1014 | 1015 | return ret; 1016 | } 1017 | 1018 | /// Transposes the current matrix; 1019 | void transpose() { 1020 | matrix = transposed().matrix; 1021 | } 1022 | 1023 | unittest { 1024 | mat2 m2 = mat2(1.0f); 1025 | m2.transpose(); 1026 | assert(m2.matrix == mat2(1.0f).matrix); 1027 | m2.make_identity(); 1028 | assert(m2.matrix == [[1.0f, 0.0f], 1029 | [0.0f, 1.0f]]); 1030 | m2.transpose(); 1031 | assert(m2.matrix == [[1.0f, 0.0f], 1032 | [0.0f, 1.0f]]); 1033 | assert(m2.matrix == m2.identity.matrix); 1034 | 1035 | mat3 m3 = mat3(1.1f, 1.2f, 1.3f, 1036 | 2.1f, 2.2f, 2.3f, 1037 | 3.1f, 3.2f, 3.3f); 1038 | m3.transpose(); 1039 | assert(m3.matrix == [[1.1f, 2.1f, 3.1f], 1040 | [1.2f, 2.2f, 3.2f], 1041 | [1.3f, 2.3f, 3.3f]]); 1042 | 1043 | mat4 m4 = mat4(2.0f); 1044 | m4.transpose(); 1045 | assert(m4.matrix == mat4(2.0f).matrix); 1046 | m4.make_identity(); 1047 | assert(m4.matrix == [[1.0f, 0.0f, 0.0f, 0.0f], 1048 | [0.0f, 1.0f, 0.0f, 0.0f], 1049 | [0.0f, 0.0f, 1.0f, 0.0f], 1050 | [0.0f, 0.0f, 0.0f, 1.0f]]); 1051 | assert(m4.matrix == m4.identity.matrix); 1052 | } 1053 | 1054 | } 1055 | 1056 | /// Returns a transposed copy of the matrix. 1057 | @property Matrix!(mt, cols, rows) transposed() const { 1058 | typeof(return) ret; 1059 | 1060 | foreach(r; TupleRange!(0, rows)) { 1061 | foreach(c; TupleRange!(0, cols)) { 1062 | ret.matrix[c][r] = matrix[r][c]; 1063 | } 1064 | } 1065 | 1066 | return ret; 1067 | } 1068 | 1069 | // transposed already tested in last unittest 1070 | 1071 | 1072 | static if((rows == 2) && (cols == 2)) { 1073 | @property mt det() const { 1074 | return (matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]); 1075 | } 1076 | 1077 | private Matrix invert(ref Matrix mat) const { 1078 | static if(isFloatingPoint!mt && rmul) { 1079 | mt d = 1 / det; 1080 | 1081 | mat.matrix = [[matrix[1][1]*d, -matrix[0][1]*d], 1082 | [-matrix[1][0]*d, matrix[0][0]*d]]; 1083 | } else { 1084 | mt d = det; 1085 | 1086 | mat.matrix = [[matrix[1][1]/d, -matrix[0][1]/d], 1087 | [-matrix[1][0]/d, matrix[0][0]/d]]; 1088 | } 1089 | 1090 | return mat; 1091 | } 1092 | 1093 | static Matrix scaling(mt x, mt y) { 1094 | Matrix ret = Matrix.identity; 1095 | 1096 | ret.matrix[0][0] = x; 1097 | ret.matrix[1][1] = y; 1098 | 1099 | return ret; 1100 | } 1101 | 1102 | Matrix scale(mt x, mt y) { 1103 | this = Matrix.scaling(x, y) * this; 1104 | return this; 1105 | } 1106 | 1107 | unittest { 1108 | assert(mat2.scaling(3, 3).matrix == mat2.identity.scale(3, 3).matrix); 1109 | assert(mat2.scaling(3, 3).matrix == [[3.0f, 0.0f], [0.0f, 3.0f]]); 1110 | } 1111 | 1112 | } else static if((rows == 3) && (cols == 3)) { 1113 | @property mt det() const { 1114 | return (matrix[0][0] * matrix[1][1] * matrix[2][2] 1115 | + matrix[0][1] * matrix[1][2] * matrix[2][0] 1116 | + matrix[0][2] * matrix[1][0] * matrix[2][1] 1117 | - matrix[0][2] * matrix[1][1] * matrix[2][0] 1118 | - matrix[0][1] * matrix[1][0] * matrix[2][2] 1119 | - matrix[0][0] * matrix[1][2] * matrix[2][1]); 1120 | } 1121 | 1122 | private Matrix invert(ref Matrix mat) const { 1123 | static if(isFloatingPoint!mt && rmul) { 1124 | mt d = 1 / det; 1125 | enum op = "*"; 1126 | } else { 1127 | mt d = det; 1128 | enum op = "/"; 1129 | } 1130 | 1131 | mixin(` 1132 | mat.matrix = [[(matrix[1][1] * matrix[2][2] - matrix[1][2] * matrix[2][1])`~op~`d, 1133 | (matrix[0][2] * matrix[2][1] - matrix[0][1] * matrix[2][2])`~op~`d, 1134 | (matrix[0][1] * matrix[1][2] - matrix[0][2] * matrix[1][1])`~op~`d], 1135 | [(matrix[1][2] * matrix[2][0] - matrix[1][0] * matrix[2][2])`~op~`d, 1136 | (matrix[0][0] * matrix[2][2] - matrix[0][2] * matrix[2][0])`~op~`d, 1137 | (matrix[0][2] * matrix[1][0] - matrix[0][0] * matrix[1][2])`~op~`d], 1138 | [(matrix[1][0] * matrix[2][1] - matrix[1][1] * matrix[2][0])`~op~`d, 1139 | (matrix[0][1] * matrix[2][0] - matrix[0][0] * matrix[2][1])`~op~`d, 1140 | (matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0])`~op~`d]]; 1141 | `); 1142 | 1143 | return mat; 1144 | } 1145 | } else static if((rows == 4) && (cols == 4)) { 1146 | /// Returns the determinant of the current matrix (2x2, 3x3 and 4x4 matrices). 1147 | @property mt det() const { 1148 | return (matrix[0][3] * matrix[1][2] * matrix[2][1] * matrix[3][0] - matrix[0][2] * matrix[1][3] * matrix[2][1] * matrix[3][0] 1149 | - matrix[0][3] * matrix[1][1] * matrix[2][2] * matrix[3][0] + matrix[0][1] * matrix[1][3] * matrix[2][2] * matrix[3][0] 1150 | + matrix[0][2] * matrix[1][1] * matrix[2][3] * matrix[3][0] - matrix[0][1] * matrix[1][2] * matrix[2][3] * matrix[3][0] 1151 | - matrix[0][3] * matrix[1][2] * matrix[2][0] * matrix[3][1] + matrix[0][2] * matrix[1][3] * matrix[2][0] * matrix[3][1] 1152 | + matrix[0][3] * matrix[1][0] * matrix[2][2] * matrix[3][1] - matrix[0][0] * matrix[1][3] * matrix[2][2] * matrix[3][1] 1153 | - matrix[0][2] * matrix[1][0] * matrix[2][3] * matrix[3][1] + matrix[0][0] * matrix[1][2] * matrix[2][3] * matrix[3][1] 1154 | + matrix[0][3] * matrix[1][1] * matrix[2][0] * matrix[3][2] - matrix[0][1] * matrix[1][3] * matrix[2][0] * matrix[3][2] 1155 | - matrix[0][3] * matrix[1][0] * matrix[2][1] * matrix[3][2] + matrix[0][0] * matrix[1][3] * matrix[2][1] * matrix[3][2] 1156 | + matrix[0][1] * matrix[1][0] * matrix[2][3] * matrix[3][2] - matrix[0][0] * matrix[1][1] * matrix[2][3] * matrix[3][2] 1157 | - matrix[0][2] * matrix[1][1] * matrix[2][0] * matrix[3][3] + matrix[0][1] * matrix[1][2] * matrix[2][0] * matrix[3][3] 1158 | + matrix[0][2] * matrix[1][0] * matrix[2][1] * matrix[3][3] - matrix[0][0] * matrix[1][2] * matrix[2][1] * matrix[3][3] 1159 | - matrix[0][1] * matrix[1][0] * matrix[2][2] * matrix[3][3] + matrix[0][0] * matrix[1][1] * matrix[2][2] * matrix[3][3]); 1160 | } 1161 | 1162 | private Matrix invert(ref Matrix mat) const { 1163 | static if(isFloatingPoint!mt && rmul) { 1164 | mt d = 1 / det; 1165 | enum op = "*"; 1166 | } else { 1167 | mt d = det; 1168 | enum op = "/"; 1169 | } 1170 | 1171 | mixin(` 1172 | mat.matrix = [[(matrix[1][1] * matrix[2][2] * matrix[3][3] + matrix[1][2] * matrix[2][3] * matrix[3][1] + matrix[1][3] * matrix[2][1] * matrix[3][2] 1173 | - matrix[1][1] * matrix[2][3] * matrix[3][2] - matrix[1][2] * matrix[2][1] * matrix[3][3] - matrix[1][3] * matrix[2][2] * matrix[3][1])`~op~`d, 1174 | (matrix[0][1] * matrix[2][3] * matrix[3][2] + matrix[0][2] * matrix[2][1] * matrix[3][3] + matrix[0][3] * matrix[2][2] * matrix[3][1] 1175 | - matrix[0][1] * matrix[2][2] * matrix[3][3] - matrix[0][2] * matrix[2][3] * matrix[3][1] - matrix[0][3] * matrix[2][1] * matrix[3][2])`~op~`d, 1176 | (matrix[0][1] * matrix[1][2] * matrix[3][3] + matrix[0][2] * matrix[1][3] * matrix[3][1] + matrix[0][3] * matrix[1][1] * matrix[3][2] 1177 | - matrix[0][1] * matrix[1][3] * matrix[3][2] - matrix[0][2] * matrix[1][1] * matrix[3][3] - matrix[0][3] * matrix[1][2] * matrix[3][1])`~op~`d, 1178 | (matrix[0][1] * matrix[1][3] * matrix[2][2] + matrix[0][2] * matrix[1][1] * matrix[2][3] + matrix[0][3] * matrix[1][2] * matrix[2][1] 1179 | - matrix[0][1] * matrix[1][2] * matrix[2][3] - matrix[0][2] * matrix[1][3] * matrix[2][1] - matrix[0][3] * matrix[1][1] * matrix[2][2])`~op~`d], 1180 | [(matrix[1][0] * matrix[2][3] * matrix[3][2] + matrix[1][2] * matrix[2][0] * matrix[3][3] + matrix[1][3] * matrix[2][2] * matrix[3][0] 1181 | - matrix[1][0] * matrix[2][2] * matrix[3][3] - matrix[1][2] * matrix[2][3] * matrix[3][0] - matrix[1][3] * matrix[2][0] * matrix[3][2])`~op~`d, 1182 | (matrix[0][0] * matrix[2][2] * matrix[3][3] + matrix[0][2] * matrix[2][3] * matrix[3][0] + matrix[0][3] * matrix[2][0] * matrix[3][2] 1183 | - matrix[0][0] * matrix[2][3] * matrix[3][2] - matrix[0][2] * matrix[2][0] * matrix[3][3] - matrix[0][3] * matrix[2][2] * matrix[3][0])`~op~`d, 1184 | (matrix[0][0] * matrix[1][3] * matrix[3][2] + matrix[0][2] * matrix[1][0] * matrix[3][3] + matrix[0][3] * matrix[1][2] * matrix[3][0] 1185 | - matrix[0][0] * matrix[1][2] * matrix[3][3] - matrix[0][2] * matrix[1][3] * matrix[3][0] - matrix[0][3] * matrix[1][0] * matrix[3][2])`~op~`d, 1186 | (matrix[0][0] * matrix[1][2] * matrix[2][3] + matrix[0][2] * matrix[1][3] * matrix[2][0] + matrix[0][3] * matrix[1][0] * matrix[2][2] 1187 | - matrix[0][0] * matrix[1][3] * matrix[2][2] - matrix[0][2] * matrix[1][0] * matrix[2][3] - matrix[0][3] * matrix[1][2] * matrix[2][0])`~op~`d], 1188 | [(matrix[1][0] * matrix[2][1] * matrix[3][3] + matrix[1][1] * matrix[2][3] * matrix[3][0] + matrix[1][3] * matrix[2][0] * matrix[3][1] 1189 | - matrix[1][0] * matrix[2][3] * matrix[3][1] - matrix[1][1] * matrix[2][0] * matrix[3][3] - matrix[1][3] * matrix[2][1] * matrix[3][0])`~op~`d, 1190 | (matrix[0][0] * matrix[2][3] * matrix[3][1] + matrix[0][1] * matrix[2][0] * matrix[3][3] + matrix[0][3] * matrix[2][1] * matrix[3][0] 1191 | - matrix[0][0] * matrix[2][1] * matrix[3][3] - matrix[0][1] * matrix[2][3] * matrix[3][0] - matrix[0][3] * matrix[2][0] * matrix[3][1])`~op~`d, 1192 | (matrix[0][0] * matrix[1][1] * matrix[3][3] + matrix[0][1] * matrix[1][3] * matrix[3][0] + matrix[0][3] * matrix[1][0] * matrix[3][1] 1193 | - matrix[0][0] * matrix[1][3] * matrix[3][1] - matrix[0][1] * matrix[1][0] * matrix[3][3] - matrix[0][3] * matrix[1][1] * matrix[3][0])`~op~`d, 1194 | (matrix[0][0] * matrix[1][3] * matrix[2][1] + matrix[0][1] * matrix[1][0] * matrix[2][3] + matrix[0][3] * matrix[1][1] * matrix[2][0] 1195 | - matrix[0][0] * matrix[1][1] * matrix[2][3] - matrix[0][1] * matrix[1][3] * matrix[2][0] - matrix[0][3] * matrix[1][0] * matrix[2][1])`~op~`d], 1196 | [(matrix[1][0] * matrix[2][2] * matrix[3][1] + matrix[1][1] * matrix[2][0] * matrix[3][2] + matrix[1][2] * matrix[2][1] * matrix[3][0] 1197 | - matrix[1][0] * matrix[2][1] * matrix[3][2] - matrix[1][1] * matrix[2][2] * matrix[3][0] - matrix[1][2] * matrix[2][0] * matrix[3][1])`~op~`d, 1198 | (matrix[0][0] * matrix[2][1] * matrix[3][2] + matrix[0][1] * matrix[2][2] * matrix[3][0] + matrix[0][2] * matrix[2][0] * matrix[3][1] 1199 | - matrix[0][0] * matrix[2][2] * matrix[3][1] - matrix[0][1] * matrix[2][0] * matrix[3][2] - matrix[0][2] * matrix[2][1] * matrix[3][0])`~op~`d, 1200 | (matrix[0][0] * matrix[1][2] * matrix[3][1] + matrix[0][1] * matrix[1][0] * matrix[3][2] + matrix[0][2] * matrix[1][1] * matrix[3][0] 1201 | - matrix[0][0] * matrix[1][1] * matrix[3][2] - matrix[0][1] * matrix[1][2] * matrix[3][0] - matrix[0][2] * matrix[1][0] * matrix[3][1])`~op~`d, 1202 | (matrix[0][0] * matrix[1][1] * matrix[2][2] + matrix[0][1] * matrix[1][2] * matrix[2][0] + matrix[0][2] * matrix[1][0] * matrix[2][1] 1203 | - matrix[0][0] * matrix[1][2] * matrix[2][1] - matrix[0][1] * matrix[1][0] * matrix[2][2] - matrix[0][2] * matrix[1][1] * matrix[2][0])`~op~`d]]; 1204 | `); 1205 | 1206 | return mat; 1207 | } 1208 | 1209 | // some static fun ... 1210 | // (1) glprogramming.com/red/appendixf.html - ortographic is broken! 1211 | // (2) http://fly.cc.fer.hr/~unreal/theredbook/appendixg.html 1212 | // (3) http://en.wikipedia.org/wiki/Orthographic_projection_(geometry) 1213 | 1214 | static if(isFloatingPoint!mt) { 1215 | static private mt[6] cperspective(mt width, mt height, mt fov, mt near, mt far) 1216 | in { assert(height != 0); } 1217 | do { 1218 | mt aspect = width/height; 1219 | mt top = near * tan(fov*(PI/360.0)); 1220 | mt bottom = -top; 1221 | mt right = top * aspect; 1222 | mt left = -right; 1223 | 1224 | return [left, right, bottom, top, near, far]; 1225 | } 1226 | 1227 | /// Returns a perspective matrix (4x4 and floating-point matrices only). 1228 | static Matrix perspective(mt width, mt height, mt fov, mt near, mt far) { 1229 | mt[6] cdata = cperspective(width, height, fov, near, far); 1230 | return perspective(cdata[0], cdata[1], cdata[2], cdata[3], cdata[4], cdata[5]); 1231 | } 1232 | 1233 | /// ditto 1234 | static Matrix perspective(mt left, mt right, mt bottom, mt top, mt near, mt far) 1235 | in { 1236 | assert(right-left != 0); 1237 | assert(top-bottom != 0); 1238 | assert(far-near != 0); 1239 | } 1240 | do { 1241 | Matrix ret; 1242 | ret.clear(0); 1243 | 1244 | ret.matrix[0][0] = (2*near)/(right-left); 1245 | ret.matrix[0][2] = (right+left)/(right-left); 1246 | ret.matrix[1][1] = (2*near)/(top-bottom); 1247 | ret.matrix[1][2] = (top+bottom)/(top-bottom); 1248 | ret.matrix[2][2] = -(far+near)/(far-near); 1249 | ret.matrix[2][3] = -(2*far*near)/(far-near); 1250 | ret.matrix[3][2] = -1; 1251 | 1252 | return ret; 1253 | } 1254 | 1255 | /// Returns an inverse perspective matrix (4x4 and floating-point matrices only). 1256 | static Matrix perspective_inverse(mt width, mt height, mt fov, mt near, mt far) { 1257 | mt[6] cdata = cperspective(width, height, fov, near, far); 1258 | return perspective_inverse(cdata[0], cdata[1], cdata[2], cdata[3], cdata[4], cdata[5]); 1259 | } 1260 | 1261 | /// ditto 1262 | static Matrix perspective_inverse(mt left, mt right, mt bottom, mt top, mt near, mt far) 1263 | in { 1264 | assert(near != 0); 1265 | assert(far != 0); 1266 | } 1267 | do { 1268 | Matrix ret; 1269 | ret.clear(0); 1270 | 1271 | ret.matrix[0][0] = (right-left)/(2*near); 1272 | ret.matrix[0][3] = (right+left)/(2*near); 1273 | ret.matrix[1][1] = (top-bottom)/(2*near); 1274 | ret.matrix[1][3] = (top+bottom)/(2*near); 1275 | ret.matrix[2][3] = -1; 1276 | ret.matrix[3][2] = -(far-near)/(2*far*near); 1277 | ret.matrix[3][3] = (far+near)/(2*far*near); 1278 | 1279 | return ret; 1280 | } 1281 | 1282 | // (2) and (3) say this one is correct 1283 | /// Returns an orthographic matrix (4x4 and floating-point matrices only). 1284 | static Matrix orthographic(mt left, mt right, mt bottom, mt top, mt near, mt far) 1285 | in { 1286 | assert(right-left != 0); 1287 | assert(top-bottom != 0); 1288 | assert(far-near != 0); 1289 | } 1290 | do { 1291 | Matrix ret; 1292 | ret.clear(0); 1293 | 1294 | ret.matrix[0][0] = 2/(right-left); 1295 | ret.matrix[0][3] = -(right+left)/(right-left); 1296 | ret.matrix[1][1] = 2/(top-bottom); 1297 | ret.matrix[1][3] = -(top+bottom)/(top-bottom); 1298 | ret.matrix[2][2] = -2/(far-near); 1299 | ret.matrix[2][3] = -(far+near)/(far-near); 1300 | ret.matrix[3][3] = 1; 1301 | 1302 | return ret; 1303 | } 1304 | 1305 | // (1) and (2) say this one is correct 1306 | /// Returns an inverse ortographic matrix (4x4 and floating-point matrices only). 1307 | static Matrix orthographic_inverse(mt left, mt right, mt bottom, mt top, mt near, mt far) { 1308 | Matrix ret; 1309 | ret.clear(0); 1310 | 1311 | ret.matrix[0][0] = (right-left)/2; 1312 | ret.matrix[0][3] = (right+left)/2; 1313 | ret.matrix[1][1] = (top-bottom)/2; 1314 | ret.matrix[1][3] = (top+bottom)/2; 1315 | ret.matrix[2][2] = (far-near)/-2; 1316 | ret.matrix[2][3] = (far+near)/2; 1317 | ret.matrix[3][3] = 1; 1318 | 1319 | return ret; 1320 | } 1321 | 1322 | /// Returns a look at matrix (4x4 and floating-point matrices only). 1323 | static Matrix look_at(Vector!(mt, 3) eye, Vector!(mt, 3) target, Vector!(mt, 3) up) { 1324 | alias Vector!(mt, 3) vec3mt; 1325 | vec3mt look_dir = (target - eye).normalized; 1326 | vec3mt up_dir = up.normalized; 1327 | 1328 | vec3mt right_dir = cross(look_dir, up_dir).normalized; 1329 | vec3mt perp_up_dir = cross(right_dir, look_dir); 1330 | 1331 | Matrix ret = Matrix.identity; 1332 | ret.matrix[0][0..3] = right_dir.vector[]; 1333 | ret.matrix[1][0..3] = perp_up_dir.vector[]; 1334 | ret.matrix[2][0..3] = (-look_dir).vector[]; 1335 | 1336 | ret.matrix[0][3] = -dot(eye, right_dir); 1337 | ret.matrix[1][3] = -dot(eye, perp_up_dir); 1338 | ret.matrix[2][3] = dot(eye, look_dir); 1339 | 1340 | return ret; 1341 | } 1342 | 1343 | unittest { 1344 | mt[6] cp = cperspective(600f, 900f, 60f, 1f, 100f); 1345 | assert(cp[4] == 1.0f); 1346 | assert(cp[5] == 100.0f); 1347 | assert(cp[0] == -cp[1]); 1348 | assert((cp[0] < -0.38489f) && (cp[0] > -0.38491f)); 1349 | assert(cp[2] == -cp[3]); 1350 | assert((cp[2] < -0.577349f) && (cp[2] > -0.577351f)); 1351 | 1352 | assert(mat4.perspective(600f, 900f, 60.0, 1.0, 100.0) == mat4.perspective(cp[0], cp[1], cp[2], cp[3], cp[4], cp[5])); 1353 | float[4][4] m4p = mat4.perspective(600f, 900f, 60.0, 1.0, 100.0).matrix; 1354 | assert((m4p[0][0] < 2.598077f) && (m4p[0][0] > 2.598075f)); 1355 | assert(m4p[0][2] == 0.0f); 1356 | assert((m4p[1][1] < 1.732052) && (m4p[1][1] > 1.732050)); 1357 | assert(m4p[1][2] == 0.0f); 1358 | assert((m4p[2][2] < -1.020201) && (m4p[2][2] > -1.020203)); 1359 | assert((m4p[2][3] < -2.020201) && (m4p[2][3] > -2.020203)); 1360 | assert((m4p[3][2] < -0.9f) && (m4p[3][2] > -1.1f)); 1361 | 1362 | float[4][4] m4pi = mat4.perspective_inverse(600f, 900f, 60.0, 1.0, 100.0).matrix; 1363 | assert((m4pi[0][0] < 0.384901) && (m4pi[0][0] > 0.384899)); 1364 | assert(m4pi[0][3] == 0.0f); 1365 | assert((m4pi[1][1] < 0.577351) && (m4pi[1][1] > 0.577349)); 1366 | assert(m4pi[1][3] == 0.0f); 1367 | assert(m4pi[2][3] == -1.0f); 1368 | assert((m4pi[3][2] < -0.494999) && (m4pi[3][2] > -0.495001)); 1369 | assert((m4pi[3][3] < 0.505001) && (m4pi[3][3] > 0.504999)); 1370 | 1371 | // maybe the next tests should be improved 1372 | float[4][4] m4o = mat4.orthographic(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f).matrix; 1373 | assert(m4o == [[1.0f, 0.0f, 0.0f, 0.0f], 1374 | [0.0f, 1.0f, 0.0f, 0.0f], 1375 | [0.0f, 0.0f, -1.0f, 0.0f], 1376 | [0.0f, 0.0f, 0.0f, 1.0f]]); 1377 | 1378 | float[4][4] m4oi = mat4.orthographic_inverse(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f).matrix; 1379 | assert(m4oi == [[1.0f, 0.0f, 0.0f, 0.0f], 1380 | [0.0f, 1.0f, 0.0f, 0.0f], 1381 | [0.0f, 0.0f, -1.0f, 0.0f], 1382 | [0.0f, 0.0f, 0.0f, 1.0f]]); 1383 | 1384 | //TODO: look_at tests 1385 | } 1386 | } 1387 | } 1388 | 1389 | static if((rows == cols) && (rows >= 3) && (rows <= 4)) { 1390 | /// Returns a translation matrix (3x3 and 4x4 matrices). 1391 | static Matrix translation(mt x, mt y, mt z) { 1392 | Matrix ret = Matrix.identity; 1393 | 1394 | ret.matrix[0][cols-1] = x; 1395 | ret.matrix[1][cols-1] = y; 1396 | ret.matrix[2][cols-1] = z; 1397 | 1398 | return ret; 1399 | } 1400 | 1401 | /// ditto 1402 | static Matrix translation(Vector!(mt, 3) v) { 1403 | Matrix ret = Matrix.identity; 1404 | 1405 | ret.matrix[0][cols-1] = v.x; 1406 | ret.matrix[1][cols-1] = v.y; 1407 | ret.matrix[2][cols-1] = v.z; 1408 | 1409 | return ret; 1410 | } 1411 | 1412 | /// Applys a translation on the current matrix and returns $(I this) (3x3 and 4x4 matrices). 1413 | Matrix translate(mt x, mt y, mt z) { 1414 | this = Matrix.translation(x, y, z) * this; 1415 | return this; 1416 | } 1417 | 1418 | /// ditto 1419 | Matrix translate(Vector!(mt, 3) v) { 1420 | this = Matrix.translation(v) * this; 1421 | return this; 1422 | } 1423 | 1424 | /// Returns a scaling matrix (3x3 and 4x4 matrices); 1425 | static Matrix scaling(mt x, mt y, mt z) { 1426 | Matrix ret = Matrix.identity; 1427 | 1428 | ret.matrix[0][0] = x; 1429 | ret.matrix[1][1] = y; 1430 | ret.matrix[2][2] = z; 1431 | 1432 | return ret; 1433 | } 1434 | 1435 | /// Applys a scale to the current matrix and returns $(I this) (3x3 and 4x4 matrices). 1436 | Matrix scale(mt x, mt y, mt z) { 1437 | this = Matrix.scaling(x, y, z) * this; 1438 | return this; 1439 | } 1440 | 1441 | unittest { 1442 | mat3 m3 = mat3.identity; 1443 | assert(m3.translate(1.0f, 2.0f, 3.0f).matrix == mat3.translation(1.0f, 2.0f, 3.0f).matrix); 1444 | assert(mat3.translation(1.0f, 2.0f, 3.0f).matrix == [[1.0f, 0.0f, 1.0f], 1445 | [0.0f, 1.0f, 2.0f], 1446 | [0.0f, 0.0f, 3.0f]]); 1447 | assert(mat3.identity.translate(0.0f, 1.0f, 2.0f).matrix == mat3.translation(0.0f, 1.0f, 2.0f).matrix); 1448 | 1449 | mat3 m31 = mat3.identity; 1450 | assert(m31.translate(vec3(1.0f, 2.0f, 3.0f)).matrix == mat3.translation(vec3(1.0f, 2.0f, 3.0f)).matrix); 1451 | assert(mat3.translation(vec3(1.0f, 2.0f, 3.0f)).matrix == [[1.0f, 0.0f, 1.0f], 1452 | [0.0f, 1.0f, 2.0f], 1453 | [0.0f, 0.0f, 3.0f]]); 1454 | assert(mat3.identity.translate(vec3(0.0f, 1.0f, 2.0f)).matrix == mat3.translation(vec3(0.0f, 1.0f, 2.0f)).matrix); 1455 | 1456 | assert(m3.scaling(0.0f, 1.0f, 2.0f).matrix == mat3.scaling(0.0f, 1.0f, 2.0f).matrix); 1457 | assert(mat3.scaling(0.0f, 1.0f, 2.0f).matrix == [[0.0f, 0.0f, 0.0f], 1458 | [0.0f, 1.0f, 0.0f], 1459 | [0.0f, 0.0f, 2.0f]]); 1460 | assert(mat3.identity.scale(0.0f, 1.0f, 2.0f).matrix == mat3.scaling(0.0f, 1.0f, 2.0f).matrix); 1461 | 1462 | // same tests for 4x4 1463 | 1464 | mat4 m4 = mat4(1.0f); 1465 | assert(m4.translation(1.0f, 2.0f, 3.0f).matrix == mat4.translation(1.0f, 2.0f, 3.0f).matrix); 1466 | assert(mat4.translation(1.0f, 2.0f, 3.0f).matrix == [[1.0f, 0.0f, 0.0f, 1.0f], 1467 | [0.0f, 1.0f, 0.0f, 2.0f], 1468 | [0.0f, 0.0f, 1.0f, 3.0f], 1469 | [0.0f, 0.0f, 0.0f, 1.0f]]); 1470 | assert(mat4.identity.translate(0.0f, 1.0f, 2.0f).matrix == mat4.translation(0.0f, 1.0f, 2.0f).matrix); 1471 | 1472 | assert(m4.scaling(0.0f, 1.0f, 2.0f).matrix == mat4.scaling(0.0f, 1.0f, 2.0f).matrix); 1473 | assert(mat4.scaling(0.0f, 1.0f, 2.0f).matrix == [[0.0f, 0.0f, 0.0f, 0.0f], 1474 | [0.0f, 1.0f, 0.0f, 0.0f], 1475 | [0.0f, 0.0f, 2.0f, 0.0f], 1476 | [0.0f, 0.0f, 0.0f, 1.0f]]); 1477 | assert(mat4.identity.scale(0.0f, 1.0f, 2.0f).matrix == mat4.scaling(0.0f, 1.0f, 2.0f).matrix); 1478 | } 1479 | } 1480 | 1481 | 1482 | static if((rows == cols) && (rows >= 3)) { 1483 | static if(isFloatingPoint!mt) { 1484 | /// Returns an identity matrix with an applied rotate_axis around an arbitrary axis (nxn matrices, n >= 3). 1485 | static Matrix rotation(real alpha, Vector!(mt, 3) axis) { 1486 | Matrix mult = Matrix.identity; 1487 | 1488 | if(axis.length != 1) { 1489 | axis.normalize(); 1490 | } 1491 | 1492 | real cosa = cos(alpha); 1493 | real sina = sin(alpha); 1494 | 1495 | Vector!(mt, 3) temp = (1 - cosa)*axis; 1496 | 1497 | mult.matrix[0][0] = to!mt(cosa + temp.x * axis.x); 1498 | mult.matrix[0][1] = to!mt( temp.x * axis.y + sina * axis.z); 1499 | mult.matrix[0][2] = to!mt( temp.x * axis.z - sina * axis.y); 1500 | mult.matrix[1][0] = to!mt( temp.y * axis.x - sina * axis.z); 1501 | mult.matrix[1][1] = to!mt(cosa + temp.y * axis.y); 1502 | mult.matrix[1][2] = to!mt( temp.y * axis.z + sina * axis.x); 1503 | mult.matrix[2][0] = to!mt( temp.z * axis.x + sina * axis.y); 1504 | mult.matrix[2][1] = to!mt( temp.z * axis.y - sina * axis.x); 1505 | mult.matrix[2][2] = to!mt(cosa + temp.z * axis.z); 1506 | 1507 | return mult; 1508 | } 1509 | 1510 | /// ditto 1511 | static Matrix rotation(real alpha, mt x, mt y, mt z) { 1512 | return Matrix.rotation(alpha, Vector!(mt, 3)(x, y, z)); 1513 | } 1514 | 1515 | /// Returns an identity matrix with an applied rotation around the x-axis (nxn matrices, n >= 3). 1516 | static Matrix xrotation(real alpha) { 1517 | Matrix mult = Matrix.identity; 1518 | 1519 | mt cosamt = to!mt(cos(alpha)); 1520 | mt sinamt = to!mt(sin(alpha)); 1521 | 1522 | mult.matrix[1][1] = cosamt; 1523 | mult.matrix[1][2] = -sinamt; 1524 | mult.matrix[2][1] = sinamt; 1525 | mult.matrix[2][2] = cosamt; 1526 | 1527 | return mult; 1528 | } 1529 | 1530 | /// Returns an identity matrix with an applied rotation around the y-axis (nxn matrices, n >= 3). 1531 | static Matrix yrotation(real alpha) { 1532 | Matrix mult = Matrix.identity; 1533 | 1534 | mt cosamt = to!mt(cos(alpha)); 1535 | mt sinamt = to!mt(sin(alpha)); 1536 | 1537 | mult.matrix[0][0] = cosamt; 1538 | mult.matrix[0][2] = sinamt; 1539 | mult.matrix[2][0] = -sinamt; 1540 | mult.matrix[2][2] = cosamt; 1541 | 1542 | return mult; 1543 | } 1544 | 1545 | /// Returns an identity matrix with an applied rotation around the z-axis (nxn matrices, n >= 3). 1546 | static Matrix zrotation(real alpha) { 1547 | Matrix mult = Matrix.identity; 1548 | 1549 | mt cosamt = to!mt(cos(alpha)); 1550 | mt sinamt = to!mt(sin(alpha)); 1551 | 1552 | mult.matrix[0][0] = cosamt; 1553 | mult.matrix[0][1] = -sinamt; 1554 | mult.matrix[1][0] = sinamt; 1555 | mult.matrix[1][1] = cosamt; 1556 | 1557 | return mult; 1558 | } 1559 | 1560 | Matrix rotate(real alpha, Vector!(mt, 3) axis) { 1561 | this = rotation(alpha, axis) * this; 1562 | return this; 1563 | } 1564 | 1565 | /// Rotates the current matrix around the x-axis and returns $(I this) (nxn matrices, n >= 3). 1566 | Matrix rotatex(real alpha) { 1567 | this = xrotation(alpha) * this; 1568 | return this; 1569 | } 1570 | 1571 | /// Rotates the current matrix around the y-axis and returns $(I this) (nxn matrices, n >= 3). 1572 | Matrix rotatey(real alpha) { 1573 | this = yrotation(alpha) * this; 1574 | return this; 1575 | } 1576 | 1577 | /// Rotates the current matrix around the z-axis and returns $(I this) (nxn matrices, n >= 3). 1578 | Matrix rotatez(real alpha) { 1579 | this = zrotation(alpha) * this; 1580 | return this; 1581 | } 1582 | 1583 | unittest { 1584 | assert(mat4.xrotation(0).matrix == [[1.0f, 0.0f, 0.0f, 0.0f], 1585 | [0.0f, 1.0f, -0.0f, 0.0f], 1586 | [0.0f, 0.0f, 1.0f, 0.0f], 1587 | [0.0f, 0.0f, 0.0f, 1.0f]]); 1588 | assert(mat4.yrotation(0).matrix == [[1.0f, 0.0f, 0.0f, 0.0f], 1589 | [0.0f, 1.0f, 0.0f, 0.0f], 1590 | [0.0f, 0.0f, 1.0f, 0.0f], 1591 | [0.0f, 0.0f, 0.0f, 1.0f]]); 1592 | assert(mat4.zrotation(0).matrix == [[1.0f, -0.0f, 0.0f, 0.0f], 1593 | [0.0f, 1.0f, 0.0f, 0.0f], 1594 | [0.0f, 0.0f, 1.0f, 0.0f], 1595 | [0.0f, 0.0f, 0.0f, 1.0f]]); 1596 | mat4 xro = mat4.identity; 1597 | xro.rotatex(0); 1598 | assert(mat4.xrotation(0).matrix == xro.matrix); 1599 | assert(xro.matrix == mat4.identity.rotatex(0).matrix); 1600 | assert(xro.matrix == mat4.rotation(0, vec3(1.0f, 0.0f, 0.0f)).matrix); 1601 | mat4 yro = mat4.identity; 1602 | yro.rotatey(0); 1603 | assert(mat4.yrotation(0).matrix == yro.matrix); 1604 | assert(yro.matrix == mat4.identity.rotatey(0).matrix); 1605 | assert(yro.matrix == mat4.rotation(0, vec3(0.0f, 1.0f, 0.0f)).matrix); 1606 | mat4 zro = mat4.identity; 1607 | xro.rotatez(0); 1608 | assert(mat4.zrotation(0).matrix == zro.matrix); 1609 | assert(zro.matrix == mat4.identity.rotatez(0).matrix); 1610 | assert(zro.matrix == mat4.rotation(0, vec3(0.0f, 0.0f, 1.0f)).matrix); 1611 | } 1612 | } // isFloatingPoint 1613 | 1614 | 1615 | /// Sets the translation of the matrix (nxn matrices, n >= 3). 1616 | void set_translation(mt[] values...) // intended to be a property 1617 | in { assert(values.length >= (rows-1)); } 1618 | do { 1619 | foreach(r; TupleRange!(0, rows-1)) { 1620 | matrix[r][rows-1] = values[r]; 1621 | } 1622 | } 1623 | 1624 | /// Copyies the translation from mat to the current matrix (nxn matrices, n >= 3). 1625 | void set_translation(Matrix mat) { 1626 | foreach(r; TupleRange!(0, rows-1)) { 1627 | matrix[r][rows-1] = mat.matrix[r][rows-1]; 1628 | } 1629 | } 1630 | 1631 | /// Returns an identity matrix with the current translation applied (nxn matrices, n >= 3).. 1632 | Matrix get_translation() { 1633 | Matrix ret = Matrix.identity; 1634 | 1635 | foreach(r; TupleRange!(0, rows-1)) { 1636 | ret.matrix[r][rows-1] = matrix[r][rows-1]; 1637 | } 1638 | 1639 | return ret; 1640 | } 1641 | 1642 | unittest { 1643 | mat3 m3 = mat3(0.0f, 1.0f, 2.0f, 1644 | 3.0f, 4.0f, 5.0f, 1645 | 6.0f, 7.0f, 1.0f); 1646 | assert(m3.get_translation().matrix == [[1.0f, 0.0f, 2.0f], [0.0f, 1.0f, 5.0f], [0.0f, 0.0f, 1.0f]]); 1647 | m3.set_translation(mat3.identity); 1648 | assert(mat3.identity.matrix == m3.get_translation().matrix); 1649 | m3.set_translation([2.0f, 5.0f]); 1650 | assert(m3.get_translation().matrix == [[1.0f, 0.0f, 2.0f], [0.0f, 1.0f, 5.0f], [0.0f, 0.0f, 1.0f]]); 1651 | assert(mat3.identity.matrix == mat3.identity.get_translation().matrix); 1652 | 1653 | mat4 m4 = mat4(0.0f, 1.0f, 2.0f, 3.0f, 1654 | 4.0f, 5.0f, 6.0f, 7.0f, 1655 | 8.0f, 9.0f, 10.0f, 11.0f, 1656 | 12.0f, 13.0f, 14.0f, 1.0f); 1657 | assert(m4.get_translation().matrix == [[1.0f, 0.0f, 0.0f, 3.0f], 1658 | [0.0f, 1.0f, 0.0f, 7.0f], 1659 | [0.0f, 0.0f, 1.0f, 11.0f], 1660 | [0.0f, 0.0f, 0.0f, 1.0f]]); 1661 | m4.set_translation(mat4.identity); 1662 | assert(mat4.identity.matrix == m4.get_translation().matrix); 1663 | m4.set_translation([3.0f, 7.0f, 11.0f]); 1664 | assert(m4.get_translation().matrix == [[1.0f, 0.0f, 0.0f, 3.0f], 1665 | [0.0f, 1.0f, 0.0f, 7.0f], 1666 | [0.0f, 0.0f, 1.0f, 11.0f], 1667 | [0.0f, 0.0f, 0.0f, 1.0f]]); 1668 | assert(mat4.identity.matrix == mat4.identity.get_translation().matrix); 1669 | } 1670 | 1671 | /// Sets the scale of the matrix (nxn matrices, n >= 3). 1672 | void set_scale(mt[] values...) 1673 | in { assert(values.length >= (rows-1)); } 1674 | do { 1675 | foreach(r; TupleRange!(0, rows-1)) { 1676 | matrix[r][r] = values[r]; 1677 | } 1678 | } 1679 | 1680 | /// Copyies the scale from mat to the current matrix (nxn matrices, n >= 3). 1681 | void set_scale(Matrix mat) { 1682 | foreach(r; TupleRange!(0, rows-1)) { 1683 | matrix[r][r] = mat.matrix[r][r]; 1684 | } 1685 | } 1686 | 1687 | /// Returns an identity matrix with the current scale applied (nxn matrices, n >= 3). 1688 | Matrix get_scale() { 1689 | Matrix ret = Matrix.identity; 1690 | 1691 | foreach(r; TupleRange!(0, rows-1)) { 1692 | ret.matrix[r][r] = matrix[r][r]; 1693 | } 1694 | 1695 | return ret; 1696 | } 1697 | 1698 | unittest { 1699 | mat3 m3 = mat3(0.0f, 1.0f, 2.0f, 1700 | 3.0f, 4.0f, 5.0f, 1701 | 6.0f, 7.0f, 1.0f); 1702 | assert(m3.get_scale().matrix == [[0.0f, 0.0f, 0.0f], [0.0f, 4.0f, 0.0f], [0.0f, 0.0f, 1.0f]]); 1703 | m3.set_scale(mat3.identity); 1704 | assert(mat3.identity.matrix == m3.get_scale().matrix); 1705 | m3.set_scale([0.0f, 4.0f]); 1706 | assert(m3.get_scale().matrix == [[0.0f, 0.0f, 0.0f], [0.0f, 4.0f, 0.0f], [0.0f, 0.0f, 1.0f]]); 1707 | assert(mat3.identity.matrix == mat3.identity.get_scale().matrix); 1708 | 1709 | mat4 m4 = mat4(0.0f, 1.0f, 2.0f, 3.0f, 1710 | 4.0f, 5.0f, 6.0f, 7.0f, 1711 | 8.0f, 9.0f, 10.0f, 11.0f, 1712 | 12.0f, 13.0f, 14.0f, 1.0f); 1713 | assert(m4.get_scale().matrix == [[0.0f, 0.0f, 0.0f, 0.0f], 1714 | [0.0f, 5.0f, 0.0f, 0.0f], 1715 | [0.0f, 0.0f, 10.0f, 0.0f], 1716 | [0.0f, 0.0f, 0.0f, 1.0f]]); 1717 | m4.set_scale(mat4.identity); 1718 | assert(mat4.identity.matrix == m4.get_scale().matrix); 1719 | m4.set_scale([0.0f, 5.0f, 10.0f]); 1720 | assert(m4.get_scale().matrix == [[0.0f, 0.0f, 0.0f, 0.0f], 1721 | [0.0f, 5.0f, 0.0f, 0.0f], 1722 | [0.0f, 0.0f, 10.0f, 0.0f], 1723 | [0.0f, 0.0f, 0.0f, 1.0f]]); 1724 | assert(mat4.identity.matrix == mat4.identity.get_scale().matrix); 1725 | } 1726 | 1727 | /// Copies rot into the upper left corner, the translation (nxn matrices, n >= 3). 1728 | void set_rotation(Matrix!(mt, 3, 3) rot) { 1729 | foreach(r; TupleRange!(0, 3)) { 1730 | foreach(c; TupleRange!(0, 3)) { 1731 | matrix[r][c] = rot[r][c]; 1732 | } 1733 | } 1734 | } 1735 | 1736 | /// Returns an identity matrix with the current rotation applied (nxn matrices, n >= 3). 1737 | Matrix!(mt, 3, 3) get_rotation() { 1738 | Matrix!(mt, 3, 3) ret = Matrix!(mt, 3, 3).identity; 1739 | 1740 | foreach(r; TupleRange!(0, 3)) { 1741 | foreach(c; TupleRange!(0, 3)) { 1742 | ret.matrix[r][c] = matrix[r][c]; 1743 | } 1744 | } 1745 | 1746 | return ret; 1747 | } 1748 | 1749 | unittest { 1750 | mat3 m3 = mat3(0.0f, 1.0f, 2.0f, 1751 | 3.0f, 4.0f, 5.0f, 1752 | 6.0f, 7.0f, 1.0f); 1753 | assert(m3.get_rotation().matrix == [[0.0f, 1.0f, 2.0f], [3.0f, 4.0f, 5.0f], [6.0f, 7.0f, 1.0f]]); 1754 | m3.set_rotation(mat3.identity); 1755 | assert(mat3.identity.matrix == m3.get_rotation().matrix); 1756 | m3.set_rotation(mat3(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 1.0f)); 1757 | assert(m3.get_rotation().matrix == [[0.0f, 1.0f, 2.0f], [3.0f, 4.0f, 5.0f], [6.0f, 7.0f, 1.0f]]); 1758 | assert(mat3.identity.matrix == mat3.identity.get_rotation().matrix); 1759 | 1760 | mat4 m4 = mat4(0.0f, 1.0f, 2.0f, 3.0f, 1761 | 4.0f, 5.0f, 6.0f, 7.0f, 1762 | 8.0f, 9.0f, 10.0f, 11.0f, 1763 | 12.0f, 13.0f, 14.0f, 1.0f); 1764 | assert(m4.get_rotation().matrix == [[0.0f, 1.0f, 2.0f], [4.0f, 5.0f, 6.0f], [8.0f, 9.0f, 10.0f]]); 1765 | m4.set_rotation(mat3.identity); 1766 | assert(mat3.identity.matrix == m4.get_rotation().matrix); 1767 | m4.set_rotation(mat3(0.0f, 1.0f, 2.0f, 4.0f, 5.0f, 6.0f, 8.0f, 9.0f, 10.0f)); 1768 | assert(m4.get_rotation().matrix == [[0.0f, 1.0f, 2.0f], [4.0f, 5.0f, 6.0f], [8.0f, 9.0f, 10.0f]]); 1769 | assert(mat3.identity.matrix == mat4.identity.get_rotation().matrix); 1770 | } 1771 | 1772 | } 1773 | 1774 | static if((rows == cols) && (rows >= 2) && (rows <= 4)) { 1775 | /// Returns an inverted copy of the current matrix (nxn matrices, 2 >= n <= 4). 1776 | @property Matrix inverse() const { 1777 | Matrix mat; 1778 | invert(mat); 1779 | return mat; 1780 | } 1781 | 1782 | /// Inverts the current matrix (nxn matrices, 2 >= n <= 4). 1783 | void invert() { 1784 | // workaround Issue #11238 1785 | // uses a temporary instead of invert(this) 1786 | Matrix temp; 1787 | invert(temp); 1788 | this.matrix = temp.matrix; 1789 | } 1790 | } 1791 | 1792 | unittest { 1793 | mat2 m2 = mat2(1.0f, 2.0f, vec2(3.0f, 4.0f)); 1794 | assert(m2.det == -2.0f); 1795 | assert(m2.inverse.matrix == [[-2.0f, 1.0f], [1.5f, -0.5f]]); 1796 | 1797 | mat3 m3 = mat3(1.0f, -2.0f, 3.0f, 1798 | 7.0f, -1.0f, 0.0f, 1799 | 3.0f, 2.0f, -4.0f); 1800 | assert(m3.det == -1.0f); 1801 | assert(m3.inverse.matrix == [[-4.0f, 2.0f, -3.0f], 1802 | [-28.0f, 13.0f, -21.0f], 1803 | [-17.0f, 8.0f, -13.0f]]); 1804 | 1805 | mat4 m4 = mat4(1.0f, 2.0f, 3.0f, 4.0f, 1806 | -2.0f, 1.0f, 5.0f, -2.0f, 1807 | 2.0f, -1.0f, 7.0f, 1.0f, 1808 | 3.0f, -3.0f, 2.0f, 0.0f); 1809 | assert(m4.det == -8.0f); 1810 | assert(m4.inverse.matrix == [[6.875f, 7.875f, -11.75f, 11.125f], 1811 | [6.625f, 7.625f, -11.25f, 10.375f], 1812 | [-0.375f, -0.375f, 0.75f, -0.625f], 1813 | [-4.5f, -5.5f, 8.0f, -7.5f]]); 1814 | } 1815 | 1816 | private void mms(mt inp, ref Matrix mat) const { // mat * scalar 1817 | for(int r = 0; r < rows; r++) { 1818 | for(int c = 0; c < cols; c++) { 1819 | mat.matrix[r][c] = matrix[r][c] * inp; 1820 | } 1821 | } 1822 | } 1823 | 1824 | private void masm(string op)(Matrix inp, ref Matrix mat) const { // mat + or - mat 1825 | foreach(r; TupleRange!(0, rows)) { 1826 | foreach(c; TupleRange!(0, cols)) { 1827 | mat.matrix[r][c] = mixin("inp.matrix[r][c]" ~ op ~ "matrix[r][c]"); 1828 | } 1829 | } 1830 | } 1831 | 1832 | Matrix!(mt, rows, T.cols) opBinary(string op : "*", T)(T inp) const if(isCompatibleMatrix!T && (T.rows == cols)) { 1833 | Matrix!(mt, rows, T.cols) ret; 1834 | 1835 | foreach(r; TupleRange!(0, rows)) { 1836 | foreach(c; TupleRange!(0, T.cols)) { 1837 | ret.matrix[r][c] = 0; 1838 | 1839 | foreach(c2; TupleRange!(0, cols)) { 1840 | ret.matrix[r][c] += matrix[r][c2] * inp.matrix[c2][c]; 1841 | } 1842 | } 1843 | } 1844 | 1845 | return ret; 1846 | } 1847 | 1848 | Vector!(mt, rows) opBinary(string op : "*", T : Vector!(mt, cols))(T inp) const { 1849 | Vector!(mt, rows) ret; 1850 | ret.clear(0); 1851 | 1852 | foreach(c; TupleRange!(0, cols)) { 1853 | foreach(r; TupleRange!(0, rows)) { 1854 | ret.vector[r] += matrix[r][c] * inp.vector[c]; 1855 | } 1856 | } 1857 | 1858 | return ret; 1859 | } 1860 | 1861 | Matrix opBinary(string op : "*")(mt inp) const { 1862 | Matrix ret; 1863 | mms(inp, ret); 1864 | return ret; 1865 | } 1866 | 1867 | Matrix opBinaryRight(string op : "*")(mt inp) const { 1868 | return this.opBinary!(op)(inp); 1869 | } 1870 | 1871 | Matrix opBinary(string op)(Matrix inp) const if((op == "+") || (op == "-")) { 1872 | Matrix ret; 1873 | masm!(op)(inp, ret); 1874 | return ret; 1875 | } 1876 | 1877 | void opOpAssign(string op : "*")(mt inp) { 1878 | mms(inp, this); 1879 | } 1880 | 1881 | void opOpAssign(string op)(Matrix inp) if((op == "+") || (op == "-")) { 1882 | masm!(op)(inp, this); 1883 | } 1884 | 1885 | void opOpAssign(string op)(Matrix inp) if(op == "*") { 1886 | this = this * inp; 1887 | } 1888 | 1889 | unittest { 1890 | mat2 m2 = mat2(1.0f, 2.0f, 3.0f, 4.0f); 1891 | vec2 v2 = vec2(2.0f, 2.0f); 1892 | assert((m2*2).matrix == [[2.0f, 4.0f], [6.0f, 8.0f]]); 1893 | assert((2*m2).matrix == (m2*2).matrix); 1894 | m2 *= 2; 1895 | assert(m2.matrix == [[2.0f, 4.0f], [6.0f, 8.0f]]); 1896 | assert((m2*v2).vector == [12.0f, 28.0f]); 1897 | assert((v2*m2).vector == [16.0f, 24.0f]); 1898 | assert((m2*m2).matrix == [[28.0f, 40.0f], [60.0f, 88.0f]]); 1899 | assert((m2-m2).matrix == [[0.0f, 0.0f], [0.0f, 0.0f]]); 1900 | assert((m2+m2).matrix == [[4.0f, 8.0f], [12.0f, 16.0f]]); 1901 | m2 += m2; 1902 | assert(m2.matrix == [[4.0f, 8.0f], [12.0f, 16.0f]]); 1903 | m2 -= m2; 1904 | assert(m2.matrix == [[0.0f, 0.0f], [0.0f, 0.0f]]); 1905 | 1906 | mat3 m3 = mat3(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f); 1907 | vec3 v3 = vec3(2.0f, 2.0f, 2.0f); 1908 | assert((m3*2).matrix == [[2.0f, 4.0f, 6.0f], [8.0f, 10.0f, 12.0f], [14.0f, 16.0f, 18.0f]]); 1909 | assert((2*m3).matrix == (m3*2).matrix); 1910 | m3 *= 2; 1911 | assert(m3.matrix == [[2.0f, 4.0f, 6.0f], [8.0f, 10.0f, 12.0f], [14.0f, 16.0f, 18.0f]]); 1912 | assert((m3*v3).vector == [24.0f, 60.0f, 96.0f]); 1913 | assert((v3*m3).vector == [48.0f, 60.0f, 72.0f]); 1914 | assert((m3*m3).matrix == [[120.0f, 144.0f, 168.0f], [264.0f, 324.0f, 384.0f], [408.0f, 504.0f, 600.0f]]); 1915 | assert((m3-m3).matrix == [[0.0f, 0.0f, 0.0f], [0.0f, 0.0f, 0.0f], [0.0f, 0.0f, 0.0f]]); 1916 | assert((m3+m3).matrix == [[4.0f, 8.0f, 12.0f], [16.0f, 20.0f, 24.0f], [28.0f, 32.0f, 36.0f]]); 1917 | m3 += m3; 1918 | assert(m3.matrix == [[4.0f, 8.0f, 12.0f], [16.0f, 20.0f, 24.0f], [28.0f, 32.0f, 36.0f]]); 1919 | m3 -= m3; 1920 | assert(m3.matrix == [[0.0f, 0.0f, 0.0f], [0.0f, 0.0f, 0.0f], [0.0f, 0.0f, 0.0f]]); 1921 | 1922 | // test opOpAssign for matrix multiplication 1923 | auto m4 = mat4.translation(0,1,2); 1924 | m4 *= mat4.translation(0,-1,2); 1925 | assert(m4 == mat4.translation(0,0,4)); 1926 | 1927 | //TODO: tests for mat4, mat34 1928 | } 1929 | 1930 | // opEqual => "alias matrix this;" 1931 | 1932 | bool opCast(T : bool)() const { 1933 | return isFinite; 1934 | } 1935 | 1936 | unittest { 1937 | assert(mat2(1.0f, 2.0f, 1.0f, 1.0f) == mat2(1.0f, 2.0f, 1.0f, 1.0f)); 1938 | assert(mat2(1.0f, 2.0f, 1.0f, 1.0f) != mat2(1.0f, 1.0f, 1.0f, 1.0f)); 1939 | 1940 | assert(mat3(1.0f) == mat3(1.0f)); 1941 | assert(mat3(1.0f) != mat3(2.0f)); 1942 | 1943 | assert(mat4(1.0f) == mat4(1.0f)); 1944 | assert(mat4(1.0f) != mat4(2.0f)); 1945 | 1946 | assert(!(mat4(float.nan))); 1947 | if(mat4(1.0f)) { } 1948 | else { assert(false); } 1949 | } 1950 | 1951 | } 1952 | 1953 | /// Pre-defined matrix types, the first number represents the number of rows 1954 | /// and the second the number of columns, if there's just one it's a nxn matrix. 1955 | /// All of these matrices are floating-point matrices. 1956 | alias Matrix!(float, 2, 2) mat2; 1957 | alias Matrix!(float, 3, 3) mat3; 1958 | alias Matrix!(float, 3, 4) mat34; 1959 | alias Matrix!(float, 4, 4) mat4; 1960 | 1961 | private unittest { 1962 | Matrix!(float, 1, 1) A = 1; 1963 | Matrix!(double, 1, 1) B = 1; 1964 | Matrix!(real, 1, 1) C = 1; 1965 | Matrix!(int, 1, 1) D = 1; 1966 | Matrix!(float, 5, 1) E = 1; 1967 | Matrix!(double, 5, 1) F = 1; 1968 | Matrix!(real, 5, 1) G = 1; 1969 | Matrix!(int, 5, 1) H = 1; 1970 | Matrix!(float, 1, 5) I = 1; 1971 | Matrix!(double, 1, 5) J = 1; 1972 | Matrix!(real, 1, 5) K = 1; 1973 | Matrix!(int, 1, 5) L = 1; 1974 | } 1975 | 1976 | /// Base template for all quaternion-types. 1977 | /// Params: 1978 | /// type = all values get stored as this type 1979 | struct Quaternion(type) { 1980 | alias type qt; /// Holds the internal type of the quaternion. 1981 | 1982 | qt[4] quaternion; /// Holds the w, x, y and z coordinates. 1983 | 1984 | /// Returns a pointer to the quaternion in memory, it starts with the w coordinate. 1985 | @property auto value_ptr() const { return quaternion.ptr; } 1986 | 1987 | /// Returns the current vector formatted as string, useful for printing the quaternion. 1988 | @property string as_string() { 1989 | return format("%s", quaternion); 1990 | } 1991 | alias as_string toString; 1992 | 1993 | @safe pure nothrow: 1994 | @property qt get_(char coord)() const { 1995 | return quaternion[coord_to_index!coord]; 1996 | } 1997 | @property void set_(char coord)(qt value) { 1998 | quaternion[coord_to_index!coord] = value; 1999 | } 2000 | 2001 | alias get_!'w' w; /// static properties to access the values. 2002 | alias set_!'w' w; 2003 | alias get_!'x' x; /// ditto 2004 | alias set_!'x' x; 2005 | alias get_!'y' y; /// ditto 2006 | alias set_!'y' y; 2007 | alias get_!'z' z; /// ditto 2008 | alias set_!'z' z; 2009 | 2010 | /// Constructs the quaternion. 2011 | /// Takes a 4-dimensional vector, where vector.x = the quaternions w coordinate, 2012 | /// or a w coordinate of type $(I qt) and a 3-dimensional vector representing the imaginary part, 2013 | /// or 4 values of type $(I qt). 2014 | this(qt w_, qt x_, qt y_, qt z_) { 2015 | w = w_; 2016 | x = x_; 2017 | y = y_; 2018 | z = z_; 2019 | } 2020 | 2021 | /// ditto 2022 | this(qt w_, Vector!(qt, 3) vec) { 2023 | w = w_; 2024 | quaternion[1..4] = vec.vector[]; 2025 | } 2026 | 2027 | /// ditto 2028 | this(Vector!(qt, 4) vec) { 2029 | quaternion[] = vec.vector[]; 2030 | } 2031 | 2032 | /// Returns true if all values are not nan and finite, otherwise false. 2033 | @property bool isFinite() const { 2034 | foreach(q; quaternion) { 2035 | if(isNaN(q) || isInfinity(q)) { 2036 | return false; 2037 | } 2038 | } 2039 | return true; 2040 | } 2041 | deprecated("Use isFinite instead of ok") alias ok = isFinite; 2042 | 2043 | unittest { 2044 | quat q1 = quat(0.0f, 0.0f, 0.0f, 1.0f); 2045 | assert(q1.quaternion == [0.0f, 0.0f, 0.0f, 1.0f]); 2046 | assert(q1.quaternion == quat(0.0f, 0.0f, 0.0f, 1.0f).quaternion); 2047 | assert(q1.quaternion == quat(0.0f, vec3(0.0f, 0.0f, 1.0f)).quaternion); 2048 | assert(q1.quaternion == quat(vec4(0.0f, 0.0f, 0.0f, 1.0f)).quaternion); 2049 | 2050 | assert(q1.isFinite); 2051 | q1.x = float.infinity; 2052 | assert(!q1.isFinite); 2053 | q1.x = float.nan; 2054 | assert(!q1.isFinite); 2055 | q1.x = 0.0f; 2056 | assert(q1.isFinite); 2057 | } 2058 | 2059 | template coord_to_index(char c) { 2060 | static if(c == 'w') { 2061 | enum coord_to_index = 0; 2062 | } else static if(c == 'x') { 2063 | enum coord_to_index = 1; 2064 | } else static if(c == 'y') { 2065 | enum coord_to_index = 2; 2066 | } else static if(c == 'z') { 2067 | enum coord_to_index = 3; 2068 | } else { 2069 | static assert(false, "accepted coordinates are x, y, z and w not " ~ c ~ "."); 2070 | } 2071 | } 2072 | 2073 | /// Returns the squared magnitude of the quaternion. 2074 | @property real magnitude_squared() const { 2075 | return to!real(w^^2 + x^^2 + y^^2 + z^^2); 2076 | } 2077 | 2078 | /// Returns the magnitude of the quaternion. 2079 | @property real magnitude() const { 2080 | return sqrt(magnitude_squared); 2081 | } 2082 | 2083 | /// Returns an identity quaternion (w=1, x=0, y=0, z=0). 2084 | static @property Quaternion identity() { 2085 | return Quaternion(1, 0, 0, 0); 2086 | } 2087 | 2088 | /// Makes the current quaternion an identity quaternion. 2089 | void make_identity() { 2090 | w = 1; 2091 | x = 0; 2092 | y = 0; 2093 | z = 0; 2094 | } 2095 | 2096 | /// Inverts the quaternion. 2097 | void invert() { 2098 | x = -x; 2099 | y = -y; 2100 | z = -z; 2101 | } 2102 | alias invert conjugate; /// ditto 2103 | 2104 | /// Returns an inverted copy of the current quaternion. 2105 | @property Quaternion inverse() const { 2106 | return Quaternion(w, -x, -y, -z); 2107 | } 2108 | alias inverse conjugated; /// ditto 2109 | 2110 | unittest { 2111 | quat q1 = quat(1.0f, 1.0f, 1.0f, 1.0f); 2112 | 2113 | assert(q1.magnitude == 2.0f); 2114 | assert(q1.magnitude_squared == 4.0f); 2115 | assert(q1.magnitude == quat(0.0f, 0.0f, 2.0f, 0.0f).magnitude); 2116 | 2117 | quat q2 = quat.identity; 2118 | assert(q2.quaternion == [1.0f, 0.0f, 0.0f, 0.0f]); 2119 | assert(q2.x == 0.0f); 2120 | assert(q2.y == 0.0f); 2121 | assert(q2.z == 0.0f); 2122 | assert(q2.w == 1.0f); 2123 | 2124 | assert(q1.inverse.quaternion == [1.0f, -1.0f, -1.0f, -1.0f]); 2125 | q1.invert(); 2126 | assert(q1.quaternion == [1.0f, -1.0f, -1.0f, -1.0f]); 2127 | 2128 | q1.make_identity(); 2129 | assert(q1.quaternion == q2.quaternion); 2130 | 2131 | } 2132 | 2133 | /// Creates a quaternion from a 3x3 matrix. 2134 | /// Params: 2135 | /// matrix = 3x3 matrix (rotation) 2136 | /// Returns: A quaternion representing the rotation (3x3 matrix) 2137 | static Quaternion from_matrix(Matrix!(qt, 3, 3) matrix) { 2138 | Quaternion ret; 2139 | 2140 | auto mat = matrix.matrix; 2141 | qt trace = mat[0][0] + mat[1][1] + mat[2][2]; 2142 | 2143 | if(trace > 0) { 2144 | real s = 0.5 / sqrt(trace + 1.0f); 2145 | 2146 | ret.w = to!qt(0.25 / s); 2147 | ret.x = to!qt((mat[2][1] - mat[1][2]) * s); 2148 | ret.y = to!qt((mat[0][2] - mat[2][0]) * s); 2149 | ret.z = to!qt((mat[1][0] - mat[0][1]) * s); 2150 | } else if((mat[0][0] > mat[1][1]) && (mat[0][0] > mat[2][2])) { 2151 | real s = 2.0 * sqrt(1.0 + mat[0][0] - mat[1][1] - mat[2][2]); 2152 | 2153 | ret.w = to!qt((mat[2][1] - mat[1][2]) / s); 2154 | ret.x = to!qt(0.25f * s); 2155 | ret.y = to!qt((mat[0][1] + mat[1][0]) / s); 2156 | ret.z = to!qt((mat[0][2] + mat[2][0]) / s); 2157 | } else if(mat[1][1] > mat[2][2]) { 2158 | real s = 2.0 * sqrt(1 + mat[1][1] - mat[0][0] - mat[2][2]); 2159 | 2160 | ret.w = to!qt((mat[0][2] - mat[2][0]) / s); 2161 | ret.x = to!qt((mat[0][1] + mat[1][0]) / s); 2162 | ret.y = to!qt(0.25f * s); 2163 | ret.z = to!qt((mat[1][2] + mat[2][1]) / s); 2164 | } else { 2165 | real s = 2.0 * sqrt(1 + mat[2][2] - mat[0][0] - mat[1][1]); 2166 | 2167 | ret.w = to!qt((mat[1][0] - mat[0][1]) / s); 2168 | ret.x = to!qt((mat[0][2] + mat[2][0]) / s); 2169 | ret.y = to!qt((mat[1][2] + mat[2][1]) / s); 2170 | ret.z = to!qt(0.25f * s); 2171 | } 2172 | 2173 | return ret; 2174 | } 2175 | 2176 | /// Returns the quaternion as matrix. 2177 | /// Params: 2178 | /// rows = number of rows of the resulting matrix (min 3) 2179 | /// cols = number of columns of the resulting matrix (min 3) 2180 | Matrix!(qt, rows, cols) to_matrix(int rows, int cols)() const if((rows >= 3) && (cols >= 3)) { 2181 | static if((rows == 3) && (cols == 3)) { 2182 | Matrix!(qt, rows, cols) ret; 2183 | } else { 2184 | Matrix!(qt, rows, cols) ret = Matrix!(qt, rows, cols).identity; 2185 | } 2186 | 2187 | qt xx = x^^2; 2188 | qt xy = x * y; 2189 | qt xz = x * z; 2190 | qt xw = x * w; 2191 | qt yy = y^^2; 2192 | qt yz = y * z; 2193 | qt yw = y * w; 2194 | qt zz = z^^2; 2195 | qt zw = z * w; 2196 | 2197 | ret.matrix[0][0] = 1 - 2 * (yy + zz); 2198 | ret.matrix[0][1] = 2 * (xy - zw); 2199 | ret.matrix[0][2] = 2 * (xz + yw); 2200 | 2201 | ret.matrix[1][0] = 2 * (xy + zw); 2202 | ret.matrix[1][1] = 1 - 2 * (xx + zz); 2203 | ret.matrix[1][2] = 2 * (yz - xw); 2204 | 2205 | ret.matrix[2][0] = 2 * (xz - yw); 2206 | ret.matrix[2][1] = 2 * (yz + xw); 2207 | ret.matrix[2][2] = 1 - 2 * (xx + yy); 2208 | 2209 | return ret; 2210 | } 2211 | 2212 | unittest { 2213 | quat q1 = quat(4.0f, 1.0f, 2.0f, 3.0f); 2214 | 2215 | assert(q1.to_matrix!(3, 3).matrix == [[-25.0f, -20.0f, 22.0f], [28.0f, -19.0f, 4.0f], [-10.0f, 20.0f, -9.0f]]); 2216 | assert(q1.to_matrix!(4, 4).matrix == [[-25.0f, -20.0f, 22.0f, 0.0f], 2217 | [28.0f, -19.0f, 4.0f, 0.0f], 2218 | [-10.0f, 20.0f, -9.0f, 0.0f], 2219 | [0.0f, 0.0f, 0.0f, 1.0f]]); 2220 | assert(quat.identity.to_matrix!(3, 3).matrix == Matrix!(qt, 3, 3).identity.matrix); 2221 | assert(q1.quaternion == quat.from_matrix(q1.to_matrix!(3, 3)).quaternion); 2222 | 2223 | assert(quat(1.0f, 0.0f, 0.0f, 0.0f).quaternion == quat.from_matrix(mat3.identity).quaternion); 2224 | 2225 | quat q2 = quat.from_matrix(mat3(1.0f, 3.0f, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); 2226 | assert(q2.x == 0.0f); 2227 | assert(almost_equal(q2.y, 0.7071067f)); 2228 | assert(almost_equal(q2.z, -1.060660)); 2229 | assert(almost_equal(q2.w, 0.7071067f)); 2230 | } 2231 | 2232 | /// Returns the quaternion as a vec3 (axis / angle representation). 2233 | vec3 to_axis_angle() { 2234 | vec3 ret; 2235 | quat this_normalized = this.normalized(); 2236 | real angle = 2 * acos(this_normalized.w); 2237 | real denominator = sqrt(1.0 - (this_normalized.w)^^2); 2238 | 2239 | if (almost_equal(denominator, 0)) { // Avoid divide by 0 2240 | ret.x = 1; 2241 | ret.y = ret.z = 0; 2242 | } 2243 | else { 2244 | ret.x = x / denominator; 2245 | ret.y = y / denominator; 2246 | ret.z = z / denominator; 2247 | } 2248 | 2249 | return ret * angle; 2250 | } 2251 | unittest { 2252 | // See https://www.energid.com/resources/orientation-calculator 2253 | 2254 | void testPair(quat q, vec3 v) { 2255 | vec3 q2v = q.to_axis_angle(); 2256 | assert(almost_equal(q2v.x, q2v.x) // epsilon = 0.000001f 2257 | && almost_equal(q2v.y, q2v.y) 2258 | && almost_equal(q2v.z, q2v.z), 2259 | "to_axisAngle does not yield a correct vector."); 2260 | } 2261 | 2262 | quat q1 = quat(0.5, 0.5, 0.5, 0.5); 2263 | vec3 v1 = vec3(1.2091996); 2264 | testPair(q1, v1); 2265 | 2266 | q1 = quat(0.1825742, 0.3651484, 0.5477226, 0.7302967); 2267 | v1 = vec3(1.030380653, 1.54557098, 2.060761024); 2268 | testPair(q1, v1); 2269 | 2270 | q1 = quat(0, 0, 0, 1); 2271 | v1 = vec3(0, 0, 0); 2272 | testPair(q1, v1); 2273 | 2274 | q1 = quat(-0.1961161, 0.4358136, 0.8716273, 0.1089534); 2275 | v1 = vec3(-1.220800604, -2.441601488, -0.305200151); 2276 | testPair(q1, v1); 2277 | } 2278 | 2279 | /// Normalizes the current quaternion. 2280 | void normalize() { 2281 | qt m = to!qt(magnitude); 2282 | 2283 | if(m != 0) { 2284 | w = w / m; 2285 | x = x / m; 2286 | y = y / m; 2287 | z = z / m; 2288 | } 2289 | } 2290 | 2291 | /// Returns a normalized copy of the current quaternion. 2292 | Quaternion normalized() const { 2293 | Quaternion ret; 2294 | qt m = to!qt(magnitude); 2295 | 2296 | if(m != 0) { 2297 | ret.w = w / m; 2298 | ret.x = x / m; 2299 | ret.y = y / m; 2300 | ret.z = z / m; 2301 | } else { 2302 | ret = Quaternion(w, x, y, z); 2303 | } 2304 | 2305 | return ret; 2306 | } 2307 | 2308 | unittest { 2309 | quat q1 = quat(1.0f, 2.0f, 3.0f, 4.0f); 2310 | quat q2 = quat(1.0f, 2.0f, 3.0f, 4.0f); 2311 | 2312 | q1.normalize(); 2313 | assert(q1.quaternion == q2.normalized.quaternion); 2314 | //assert(q1.quaternion == q1.normalized.quaternion); 2315 | assert(almost_equal(q1.magnitude, 1.0)); 2316 | } 2317 | 2318 | /// Returns the yaw. 2319 | @property real yaw() const { 2320 | return atan2(to!real(2.0*(w*z + x*y)), to!real(1.0 - 2.0*(y*y + z*z))); 2321 | } 2322 | 2323 | /// Returns the pitch. 2324 | @property real pitch() const { 2325 | return asin(to!real(2.0*(w*y - z*x))); 2326 | } 2327 | 2328 | /// Returns the roll. 2329 | @property real roll() const { 2330 | return atan2(to!real(2.0*(w*x + y*z)), to!real(1.0 - 2.0*(x*x + y*y))); 2331 | } 2332 | 2333 | unittest { 2334 | quat q1 = quat.identity; 2335 | assert(q1.pitch == 0.0f); 2336 | assert(q1.yaw == 0.0f); 2337 | assert(q1.roll == 0.0f); 2338 | 2339 | quat q2 = quat(1.0f, 1.0f, 1.0f, 1.0f); 2340 | assert(almost_equal(q2.yaw, q2.roll)); 2341 | //assert(almost_equal(q2.yaw, 1.570796f)); 2342 | assert(q2.pitch == 0.0f); 2343 | 2344 | quat q3 = quat(0.1f, 1.9f, 2.1f, 1.3f); 2345 | //assert(almost_equal(q3.yaw, 2.4382f)); 2346 | assert(isNaN(q3.pitch)); 2347 | //assert(almost_equal(q3.roll, 1.67719f)); 2348 | } 2349 | 2350 | /// Returns a quaternion with applied rotation around the x-axis. 2351 | static Quaternion xrotation(real alpha) { 2352 | Quaternion ret; 2353 | 2354 | alpha /= 2; 2355 | ret.w = to!qt(cos(alpha)); 2356 | ret.x = to!qt(sin(alpha)); 2357 | ret.y = 0; 2358 | ret.z = 0; 2359 | 2360 | return ret; 2361 | } 2362 | 2363 | /// Returns a quaternion with applied rotation around the y-axis. 2364 | static Quaternion yrotation(real alpha) { 2365 | Quaternion ret; 2366 | 2367 | alpha /= 2; 2368 | ret.w = to!qt(cos(alpha)); 2369 | ret.x = 0; 2370 | ret.y = to!qt(sin(alpha)); 2371 | ret.z = 0; 2372 | 2373 | return ret; 2374 | } 2375 | 2376 | /// Returns a quaternion with applied rotation around the z-axis. 2377 | static Quaternion zrotation(real alpha) { 2378 | Quaternion ret; 2379 | 2380 | alpha /= 2; 2381 | ret.w = to!qt(cos(alpha)); 2382 | ret.x = 0; 2383 | ret.y = 0; 2384 | ret.z = to!qt(sin(alpha)); 2385 | 2386 | return ret; 2387 | } 2388 | 2389 | /// Returns a quaternion with applied rotation around an axis. 2390 | static Quaternion axis_rotation(real alpha, Vector!(qt, 3) axis) { 2391 | if(alpha == 0) { 2392 | return Quaternion.identity; 2393 | } 2394 | Quaternion ret; 2395 | 2396 | alpha /= 2; 2397 | qt sinaqt = to!qt(sin(alpha)); 2398 | 2399 | ret.w = to!qt(cos(alpha)); 2400 | ret.x = axis.x * sinaqt; 2401 | ret.y = axis.y * sinaqt; 2402 | ret.z = axis.z * sinaqt; 2403 | 2404 | return ret; 2405 | } 2406 | 2407 | /// Creates a quaternion from an euler rotation. 2408 | static Quaternion euler_rotation(real roll, real pitch, real yaw) { 2409 | Quaternion ret; 2410 | 2411 | auto cr = cos(roll / 2.0); 2412 | auto cp = cos(pitch / 2.0); 2413 | auto cy = cos(yaw / 2.0); 2414 | auto sr = sin(roll / 2.0); 2415 | auto sp = sin(pitch / 2.0); 2416 | auto sy = sin(yaw / 2.0); 2417 | 2418 | ret.w = cr * cp * cy + sr * sp * sy; 2419 | ret.x = sr * cp * cy - cr * sp * sy; 2420 | ret.y = cr * sp * cy + sr * cp * sy; 2421 | ret.z = cr * cp * sy - sr * sp * cy; 2422 | 2423 | return ret; 2424 | } 2425 | 2426 | unittest { 2427 | enum startPitch = 0.1; 2428 | enum startYaw = -0.2; 2429 | enum startRoll = 0.6; 2430 | 2431 | auto q = quat.euler_rotation(startRoll,startPitch,startYaw); 2432 | 2433 | assert(almost_equal(q.pitch,startPitch)); 2434 | assert(almost_equal(q.yaw,startYaw)); 2435 | assert(almost_equal(q.roll,startRoll)); 2436 | } 2437 | 2438 | /// Rotates the current quaternion around the x-axis and returns $(I this). 2439 | Quaternion rotatex(real alpha) { 2440 | this = xrotation(alpha) * this; 2441 | return this; 2442 | } 2443 | 2444 | /// Rotates the current quaternion around the y-axis and returns $(I this). 2445 | Quaternion rotatey(real alpha) { 2446 | this = yrotation(alpha) * this; 2447 | return this; 2448 | } 2449 | 2450 | /// Rotates the current quaternion around the z-axis and returns $(I this). 2451 | Quaternion rotatez(real alpha) { 2452 | this = zrotation(alpha) * this; 2453 | return this; 2454 | } 2455 | 2456 | /// Rotates the current quaternion around an axis and returns $(I this). 2457 | Quaternion rotate_axis(real alpha, Vector!(qt, 3) axis) { 2458 | this = axis_rotation(alpha, axis) * this; 2459 | return this; 2460 | } 2461 | 2462 | /// Applies an euler rotation to the current quaternion and returns $(I this). 2463 | Quaternion rotate_euler(real heading, real attitude, real bank) { 2464 | this = euler_rotation(heading, attitude, bank) * this; 2465 | return this; 2466 | } 2467 | 2468 | unittest { 2469 | assert(quat.xrotation(PI).quaternion[1..4] == [1.0f, 0.0f, 0.0f]); 2470 | assert(quat.yrotation(PI).quaternion[1..4] == [0.0f, 1.0f, 0.0f]); 2471 | assert(quat.zrotation(PI).quaternion[1..4] == [0.0f, 0.0f, 1.0f]); 2472 | assert((quat.xrotation(PI).w == quat.yrotation(PI).w) && (quat.yrotation(PI).w == quat.zrotation(PI).w)); 2473 | //assert(quat.rotatex(PI).w == to!(quat.qt)(cos(PI))); 2474 | assert(quat.xrotation(PI).quaternion == quat.identity.rotatex(PI).quaternion); 2475 | assert(quat.yrotation(PI).quaternion == quat.identity.rotatey(PI).quaternion); 2476 | assert(quat.zrotation(PI).quaternion == quat.identity.rotatez(PI).quaternion); 2477 | 2478 | assert(quat.axis_rotation(PI, vec3(1.0f, 1.0f, 1.0f)).quaternion[1..4] == [1.0f, 1.0f, 1.0f]); 2479 | assert(quat.axis_rotation(PI, vec3(1.0f, 1.0f, 1.0f)).w == quat.xrotation(PI).w); 2480 | assert(quat.axis_rotation(PI, vec3(1.0f, 1.0f, 1.0f)).quaternion == 2481 | quat.identity.rotate_axis(PI, vec3(1.0f, 1.0f, 1.0f)).quaternion); 2482 | 2483 | quat q1 = quat.euler_rotation(PI, PI, PI); 2484 | assert((q1.x > -1e-16) && (q1.x < 1e-16)); 2485 | assert((q1.y > -1e-16) && (q1.y < 1e-16)); 2486 | assert((q1.z > -1e-16) && (q1.z < 1e-16)); 2487 | //assert(q1.w == -1.0f); 2488 | assert(quat.euler_rotation(PI, PI, PI).quaternion == quat.identity.rotate_euler(PI, PI, PI).quaternion); 2489 | } 2490 | 2491 | Quaternion opBinary(string op : "*")(Quaternion inp) const { 2492 | Quaternion ret; 2493 | 2494 | ret.w = -x * inp.x - y * inp.y - z * inp.z + w * inp.w; 2495 | ret.x = x * inp.w + y * inp.z - z * inp.y + w * inp.x; 2496 | ret.y = -x * inp.z + y * inp.w + z * inp.x + w * inp.y; 2497 | ret.z = x * inp.y - y * inp.x + z * inp.w + w * inp.z; 2498 | 2499 | return ret; 2500 | } 2501 | 2502 | auto opBinaryRight(string op, T)(T inp) const if(!is_quaternion!T) { 2503 | return this.opBinary!(op)(inp); 2504 | } 2505 | 2506 | Quaternion opBinary(string op)(Quaternion inp) const if((op == "+") || (op == "-")) { 2507 | Quaternion ret; 2508 | 2509 | mixin("ret.w = w" ~ op ~ "inp.w;"); 2510 | mixin("ret.x = x" ~ op ~ "inp.x;"); 2511 | mixin("ret.y = y" ~ op ~ "inp.y;"); 2512 | mixin("ret.z = z" ~ op ~ "inp.z;"); 2513 | 2514 | return ret; 2515 | } 2516 | 2517 | Vector!(qt, 3) opBinary(string op : "*")(Vector!(qt, 3) inp) const { 2518 | Vector!(qt, 3) ret; 2519 | 2520 | qt ww = w^^2; 2521 | qt w2 = w * 2; 2522 | qt wx2 = w2 * x; 2523 | qt wy2 = w2 * y; 2524 | qt wz2 = w2 * z; 2525 | qt xx = x^^2; 2526 | qt x2 = x * 2; 2527 | qt xy2 = x2 * y; 2528 | qt xz2 = x2 * z; 2529 | qt yy = y^^2; 2530 | qt yz2 = 2 * y * z; 2531 | qt zz = z * z; 2532 | 2533 | ret.vector = [ww * inp.x + wy2 * inp.z - wz2 * inp.y + xx * inp.x + 2534 | xy2 * inp.y + xz2 * inp.z - zz * inp.x - yy * inp.x, 2535 | xy2 * inp.x + yy * inp.y + yz2 * inp.z + wz2 * inp.x - 2536 | zz * inp.y + ww * inp.y - wx2 * inp.z - xx * inp.y, 2537 | xz2 * inp.x + yz2 * inp.y + zz * inp.z - wy2 * inp.x - 2538 | yy * inp.z + wx2 * inp.y - xx * inp.z + ww * inp.z]; 2539 | 2540 | return ret; 2541 | } 2542 | 2543 | Quaternion opBinary(string op : "*")(qt inp) const { 2544 | return Quaternion(w*inp, x*inp, y*inp, z*inp); 2545 | } 2546 | 2547 | void opOpAssign(string op : "*")(Quaternion inp) { 2548 | qt w2 = -x * inp.x - y * inp.y - z * inp.z + w * inp.w; 2549 | qt x2 = x * inp.w + y * inp.z - z * inp.y + w * inp.x; 2550 | qt y2 = -x * inp.z + y * inp.w + z * inp.x + w * inp.y; 2551 | qt z2 = x * inp.y - y * inp.x + z * inp.w + w * inp.z; 2552 | w = w2; x = x2; y = y2; z = z2; 2553 | } 2554 | 2555 | void opOpAssign(string op)(Quaternion inp) if((op == "+") || (op == "-")) { 2556 | mixin("w = w" ~ op ~ "inp.w;"); 2557 | mixin("x = x" ~ op ~ "inp.x;"); 2558 | mixin("y = y" ~ op ~ "inp.y;"); 2559 | mixin("z = z" ~ op ~ "inp.z;"); 2560 | } 2561 | 2562 | void opOpAssign(string op : "*")(qt inp) { 2563 | quaternion[0] *= inp; 2564 | quaternion[1] *= inp; 2565 | quaternion[2] *= inp; 2566 | quaternion[3] *= inp; 2567 | } 2568 | 2569 | unittest { 2570 | quat q1 = quat.identity; 2571 | quat q2 = quat(3.0f, 0.0f, 1.0f, 2.0f); 2572 | quat q3 = quat(3.4f, 0.1f, 1.2f, 2.3f); 2573 | 2574 | assert((q1 * q1).quaternion == q1.quaternion); 2575 | assert((q1 * q2).quaternion == q2.quaternion); 2576 | assert((q2 * q1).quaternion == q2.quaternion); 2577 | quat q4 = q3 * q2; 2578 | assert((q2 * q3).quaternion != q4.quaternion); 2579 | q3 *= q2; 2580 | assert(q4.quaternion == q3.quaternion); 2581 | assert(almost_equal(q4.x, 0.4f)); 2582 | assert(almost_equal(q4.y, 6.8f)); 2583 | assert(almost_equal(q4.z, 13.8f)); 2584 | assert(almost_equal(q4.w, 4.4f)); 2585 | 2586 | quat q5 = quat(1.0f, 2.0f, 3.0f, 4.0f); 2587 | quat q6 = quat(3.0f, 1.0f, 6.0f, 2.0f); 2588 | 2589 | assert((q5 - q6).quaternion == [-2.0f, 1.0f, -3.0f, 2.0f]); 2590 | assert((q5 + q6).quaternion == [4.0f, 3.0f, 9.0f, 6.0f]); 2591 | assert((q6 - q5).quaternion == [2.0f, -1.0f, 3.0f, -2.0f]); 2592 | assert((q6 + q5).quaternion == [4.0f, 3.0f, 9.0f, 6.0f]); 2593 | q5 += q6; 2594 | assert(q5.quaternion == [4.0f, 3.0f, 9.0f, 6.0f]); 2595 | q6 -= q6; 2596 | assert(q6.quaternion == [0.0f, 0.0f, 0.0f, 0.0f]); 2597 | 2598 | quat q7 = quat(2.0f, 2.0f, 2.0f, 2.0f); 2599 | assert((q7 * 2).quaternion == [4.0f, 4.0f, 4.0f, 4.0f]); 2600 | assert((2 * q7).quaternion == (q7 * 2).quaternion); 2601 | q7 *= 2; 2602 | assert(q7.quaternion == [4.0f, 4.0f, 4.0f, 4.0f]); 2603 | 2604 | vec3 v1 = vec3(1.0f, 2.0f, 3.0f); 2605 | assert((q1 * v1).vector == v1.vector); 2606 | assert((v1 * q1).vector == (q1 * v1).vector); 2607 | assert((q2 * v1).vector == [-2.0f, 36.0f, 38.0f]); 2608 | } 2609 | 2610 | int opCmp(ref const Quaternion qua) const { 2611 | foreach(i, a; quaternion) { 2612 | if(a < qua.quaternion[i]) { 2613 | return -1; 2614 | } else if(a > qua.quaternion[i]) { 2615 | return 1; 2616 | } 2617 | } 2618 | 2619 | // Quaternions are the same 2620 | return 0; 2621 | } 2622 | 2623 | bool opEquals(const Quaternion qu) const { 2624 | return quaternion == qu.quaternion; 2625 | } 2626 | 2627 | bool opCast(T : bool)() const { 2628 | return isFinite; 2629 | } 2630 | 2631 | unittest { 2632 | assert(quat(1.0f, 2.0f, 3.0f, 4.0f) == quat(1.0f, 2.0f, 3.0f, 4.0f)); 2633 | assert(quat(1.0f, 2.0f, 3.0f, 4.0f) != quat(1.0f, 2.0f, 3.0f, 3.0f)); 2634 | 2635 | assert(!(quat(float.nan, float.nan, float.nan, float.nan))); 2636 | if(quat(1.0f, 1.0f, 1.0f, 1.0f)) { } 2637 | else { assert(false); } 2638 | } 2639 | 2640 | } 2641 | 2642 | /// Pre-defined quaternion of type float. 2643 | alias Quaternion!(float) quat; 2644 | -------------------------------------------------------------------------------- /gl3n/math.d: -------------------------------------------------------------------------------- 1 | /** 2 | gl3n.math 3 | 4 | Provides nearly all GLSL functions, according to spec 4.1, 5 | it also publically imports other useful functions (from std.math, core.stdc.math, std.alogrithm) 6 | so you only have to import this file to get all mathematical functions you need. 7 | 8 | Publically imports: PI, sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, 9 | asinh, acosh, atanh, pow, exp, log, exp2, log2, sqrt, abs, floor, trunc, round, ceil, modf, 10 | fmodf, min, max. 11 | 12 | Authors: David Herberth 13 | License: MIT 14 | */ 15 | 16 | module gl3n.math; 17 | 18 | public { 19 | import std.math : PI, sin, cos, tan, asin, acos, atan, atan2, 20 | sinh, cosh, tanh, asinh, acosh, atanh, 21 | pow, exp, log, exp2, log2, sqrt, 22 | floor, trunc, round, ceil, modf; 23 | alias round roundEven; 24 | alias floor fract; 25 | //import core.stdc.math : fmodf; 26 | import std.algorithm : min, max; 27 | } 28 | 29 | private { 30 | import std.conv : to; 31 | import std.algorithm : all; 32 | import std.range : zip; 33 | import std.traits : CommonType, Unqual; 34 | import std.range : ElementType; 35 | import smath = std.math; 36 | 37 | import gl3n.util : is_vector, is_quaternion, is_matrix; 38 | 39 | version(unittest) { 40 | import gl3n.linalg : vec2, vec2i, vec3, vec3i, quat; 41 | } 42 | } 43 | 44 | /// PI / 180 at compiletime, used for degrees/radians conversion. 45 | public enum real PI_180 = PI / 180; 46 | /// 180 / PI at compiletime, used for degrees/radians conversion. 47 | public enum real _180_PI = 180 / PI; 48 | 49 | /// Modulus. Returns x - y * floor(x/y). 50 | T mod(T)(T x, T y) { // std.math.floor is not pure 51 | return x - y * floor(x/y); 52 | } 53 | 54 | @safe pure nothrow: 55 | 56 | extern (C) { float fmodf(float x, float y); } 57 | 58 | /// Calculates the absolute value. 59 | T abs(T)(T t) if(!is_vector!T && !is_quaternion!T && !is_matrix!T) { 60 | return smath.abs(t); 61 | } 62 | 63 | /// Calculates the absolute value per component. 64 | T abs(T)(T vec) if(is_vector!T) { 65 | Unqual!T ret; 66 | 67 | foreach(i, element; vec.vector) { 68 | ret.vector[i] = abs(element); 69 | } 70 | 71 | return ret; 72 | } 73 | 74 | /// ditto 75 | T abs(T)(T quat) if(is_quaternion!T) { 76 | Unqual!T ret; 77 | 78 | ret.quaternion[0] = abs(quat.quaternion[0]); 79 | ret.quaternion[1] = abs(quat.quaternion[1]); 80 | ret.quaternion[2] = abs(quat.quaternion[2]); 81 | ret.quaternion[3] = abs(quat.quaternion[3]); 82 | 83 | return ret; 84 | } 85 | 86 | unittest { 87 | assert(abs(0) == 0); 88 | assert(abs(-1) == 1); 89 | assert(abs(1) == 1); 90 | assert(abs(0.0) == 0.0); 91 | assert(abs(-1.0) == 1.0); 92 | assert(abs(1.0) == 1.0); 93 | 94 | assert(abs(vec3i(-1, 0, -12)) == vec3(1, 0, 12)); 95 | assert(abs(vec3(-1, 0, -12)) == vec3(1, 0, 12)); 96 | assert(abs(vec3i(12, 12, 12)) == vec3(12, 12, 12)); 97 | 98 | assert(abs(quat(-1.0f, 0.0f, 1.0f, -12.0f)) == quat(1.0f, 0.0f, 1.0f, 12.0f)); 99 | } 100 | 101 | /// Returns 1/sqrt(x), results are undefined if x <= 0. 102 | real inversesqrt(real x) { 103 | return 1 / sqrt(x); 104 | } 105 | 106 | /// Returns 1.0 if x > 0, 0.0 if x = 0, or -1.0 if x < 0. 107 | float sign(T)(T x) { 108 | if(x > 0) { 109 | return 1.0f; 110 | } else if(x == 0) { 111 | return 0.0f; 112 | } else { // if x < 0 113 | return -1.0f; 114 | } 115 | } 116 | 117 | unittest { 118 | assert(almost_equal(inversesqrt(1.0f), 1.0f)); 119 | assert(almost_equal(inversesqrt(10.0f), (1/sqrt(10.0f)))); 120 | assert(almost_equal(inversesqrt(2342342.0f), (1/sqrt(2342342.0f)))); 121 | 122 | assert(sign(-1) == -1.0f); 123 | assert(sign(0) == 0.0f); 124 | assert(sign(1) == 1.0f); 125 | assert(sign(0.5) == 1.0f); 126 | assert(sign(-0.5) == -1.0f); 127 | 128 | assert(mod(12.0, 27.5) == 12.0); 129 | assert(mod(-12.0, 27.5) == 15.5); 130 | assert(mod(12.0, -27.5) == -15.5); 131 | } 132 | 133 | /// Compares to values and returns true if the difference is epsilon or smaller. 134 | bool almost_equal(T, S)(T a, S b, float epsilon = 0.000001f) if(!is_vector!T && !is_quaternion!T) { 135 | if(abs(a-b) <= epsilon) { 136 | return true; 137 | } 138 | return abs(a-b) <= epsilon * abs(b); 139 | } 140 | 141 | /// ditto 142 | bool almost_equal(T, S)(T a, S b, float epsilon = 0.000001f) if(is_vector!T && is_vector!S && T.dimension == S.dimension) { 143 | foreach(i; 0..T.dimension) { 144 | if(!almost_equal(a.vector[i], b.vector[i], epsilon)) { 145 | return false; 146 | } 147 | } 148 | return true; 149 | } 150 | 151 | bool almost_equal(T)(T a, T b, float epsilon = 0.000001f) if(is_quaternion!T) { 152 | foreach(i; 0..4) { 153 | if(!almost_equal(a.quaternion[i], b.quaternion[i], epsilon)) { 154 | return false; 155 | } 156 | } 157 | return true; 158 | } 159 | 160 | unittest { 161 | assert(almost_equal(0, 0)); 162 | assert(almost_equal(1, 1)); 163 | assert(almost_equal(-1, -1)); 164 | assert(almost_equal(0f, 0.000001f, 0.000001f)); 165 | assert(almost_equal(1f, 1.1f, 0.1f)); 166 | assert(!almost_equal(1f, 1.1f, 0.01f)); 167 | 168 | assert(almost_equal(vec2i(0, 0), vec2(0.0f, 0.0f))); 169 | assert(almost_equal(vec2(0.0f, 0.0f), vec2(0.000001f, 0.000001f))); 170 | assert(almost_equal(vec3(0.0f, 1.0f, 2.0f), vec3i(0, 1, 2))); 171 | 172 | assert(almost_equal(quat(0.0f, 0.0f, 0.0f, 0.0f), quat(0.0f, 0.0f, 0.0f, 0.0f))); 173 | assert(almost_equal(quat(0.0f, 0.0f, 0.0f, 0.0f), quat(0.000001f, 0.000001f, 0.000001f, 0.000001f))); 174 | } 175 | 176 | /// Converts degrees to radians. 177 | real radians(real degrees) { 178 | return PI_180 * degrees; 179 | } 180 | 181 | /// Compiletime version of $(I radians). 182 | real cradians(real degrees)() { 183 | return radians(degrees); 184 | } 185 | 186 | /// Converts radians to degrees. 187 | real degrees(real radians) { 188 | return _180_PI * radians; 189 | } 190 | 191 | /// Compiletime version of $(I degrees). 192 | real cdegrees(real radians)() { 193 | return degrees(radians); 194 | } 195 | 196 | unittest { 197 | assert(almost_equal(radians(to!(real)(0)), 0)); 198 | assert(almost_equal(radians(to!(real)(90)), PI/2)); 199 | assert(almost_equal(radians(to!(real)(180)), PI)); 200 | assert(almost_equal(radians(to!(real)(360)), 2*PI)); 201 | 202 | assert(almost_equal(degrees(to!(real)(0)), 0)); 203 | assert(almost_equal(degrees(to!(real)(PI/2)), 90)); 204 | assert(almost_equal(degrees(to!(real)(PI)), 180)); 205 | assert(almost_equal(degrees(to!(real)(2*PI)), 360)); 206 | 207 | assert(almost_equal(degrees(radians(to!(real)(12))), 12)); 208 | assert(almost_equal(degrees(radians(to!(real)(100))), 100)); 209 | assert(almost_equal(degrees(radians(to!(real)(213))), 213)); 210 | assert(almost_equal(degrees(radians(to!(real)(399))), 399)); 211 | 212 | /+static+/ assert(almost_equal(cdegrees!PI, 180)); 213 | /+static+/ assert(almost_equal(cradians!180, PI)); 214 | } 215 | 216 | /// Returns min(max(x, min_val), max_val), Results are undefined if min_val > max_val. 217 | CommonType!(T1, T2, T3) clamp(T1, T2, T3)(T1 x, T2 min_val, T3 max_val) { 218 | return min(max(x, min_val), max_val); 219 | } 220 | 221 | unittest { 222 | assert(clamp(-1, 0, 2) == 0); 223 | assert(clamp(0, 0, 2) == 0); 224 | assert(clamp(1, 0, 2) == 1); 225 | assert(clamp(2, 0, 2) == 2); 226 | assert(clamp(3, 0, 2) == 2); 227 | } 228 | 229 | /// Returns 0.0 if x < edge, otherwise it returns 1.0. 230 | float step(T1, T2)(T1 edge, T2 x) { 231 | return x < edge ? 0.0f:1.0f; 232 | } 233 | 234 | /// Returns 0.0 if x <= edge0 and 1.0 if x >= edge1 and performs smooth 235 | /// hermite interpolation between 0 and 1 when edge0 < x < edge1. 236 | /// This is useful in cases where you would want a threshold function with a smooth transition. 237 | CommonType!(T1, T2, T3) smoothstep(T1, T2, T3)(T1 edge0, T2 edge1, T3 x) { 238 | auto t = clamp((x - edge0) / (edge1 - edge0), 0, 1); 239 | return t * t * (3 - 2 * t); 240 | } 241 | 242 | unittest { 243 | assert(step(0, 1) == 1.0f); 244 | assert(step(0, 10) == 1.0f); 245 | assert(step(1, 0) == 0.0f); 246 | assert(step(10, 0) == 0.0f); 247 | assert(step(1, 1) == 1.0f); 248 | 249 | assert(smoothstep(1, 0, 2) == 0); 250 | assert(smoothstep(1.0, 0.0, 2.0) == 0); 251 | assert(smoothstep(1.0, 0.0, 0.5) == 0.5); 252 | assert(almost_equal(smoothstep(0.0, 2.0, 0.5), 0.15625, 0.00001)); 253 | } 254 | -------------------------------------------------------------------------------- /gl3n/plane.d: -------------------------------------------------------------------------------- 1 | module gl3n.plane; 2 | 3 | private { 4 | import gl3n.linalg : Vector, dot, vec3; 5 | import gl3n.math : almost_equal; 6 | 7 | import std.traits : isFloatingPoint; 8 | } 9 | 10 | 11 | /// Base template for all plane-types. 12 | /// Params: 13 | /// type = all values get stored as this type (must be floating point) 14 | struct PlaneT(type = float) if(isFloatingPoint!type) { 15 | alias type pt; /// Holds the internal type of the plane. 16 | alias Vector!(pt, 3) vec3; /// Convenience alias to the corresponding vector type. 17 | 18 | union { 19 | struct { 20 | pt a; /// normal.x 21 | pt b; /// normal.y 22 | pt c; /// normal.z 23 | } 24 | 25 | vec3 normal; /// Holds the planes normal. 26 | } 27 | 28 | pt d; /// Holds the planes "constant" (HNF). 29 | 30 | @safe pure nothrow: 31 | 32 | /// Constructs the plane, from either four scalars of type $(I pt) 33 | /// or from a 3-dimensional vector (= normal) and a scalar. 34 | this(pt a, pt b, pt c, pt d) { 35 | this.a = a; 36 | this.b = b; 37 | this.c = c; 38 | this.d = d; 39 | } 40 | 41 | /// ditto 42 | this(vec3 normal, pt d) { 43 | this.normal = normal; 44 | this.d = d; 45 | } 46 | 47 | unittest { 48 | Plane p = Plane(0.0f, 1.0f, 2.0f, 3.0f); 49 | assert(p.normal == vec3(0.0f, 1.0f, 2.0f)); 50 | assert(p.d == 3.0f); 51 | 52 | p.normal.x = 4.0f; 53 | assert(p.normal == vec3(4.0f, 1.0f, 2.0f)); 54 | assert(p.a == 4.0f); 55 | assert(p.b == 1.0f); 56 | assert(p.c == 2.0f); 57 | assert(p.d == 3.0f); 58 | } 59 | 60 | /// Normalizes the plane inplace. 61 | void normalize() { 62 | pt det = 1.0 / normal.length; 63 | normal *= det; 64 | d *= det; 65 | } 66 | 67 | /// Returns a normalized copy of the plane. 68 | @property PlaneT normalized() const { 69 | PlaneT ret = PlaneT(a, b, c, d); 70 | ret.normalize(); 71 | return ret; 72 | } 73 | 74 | unittest { 75 | Plane p = Plane(0.0f, 1.0f, 2.0f, 3.0f); 76 | Plane pn = p.normalized(); 77 | assert(pn.normal == vec3(0.0f, 1.0f, 2.0f).normalized); 78 | assert(almost_equal(pn.d, 3.0f/vec3(0.0f, 1.0f, 2.0f).length)); 79 | p.normalize(); 80 | assert(p == pn); 81 | } 82 | 83 | /// Returns the distance from a point to the plane. 84 | /// Note: the plane $(RED must) be normalized, the result can be negative. 85 | pt distance(vec3 point) const { 86 | return dot(point, normal) + d; 87 | } 88 | 89 | /// Returns the distance from a point to the plane. 90 | /// Note: the plane does not have to be normalized, the result can be negative. 91 | pt ndistance(vec3 point) const { 92 | return (dot(point, normal) + d) / normal.length; 93 | } 94 | 95 | unittest { 96 | Plane p = Plane(-1.0f, 4.0f, 19.0f, -10.0f); 97 | assert(almost_equal(p.ndistance(vec3(5.0f, -2.0f, 0.0f)), -1.182992)); 98 | assert(almost_equal(p.ndistance(vec3(5.0f, -2.0f, 0.0f)), 99 | p.normalized.distance(vec3(5.0f, -2.0f, 0.0f)))); 100 | } 101 | 102 | bool opEquals(PlaneT other) const { 103 | return other.normal == normal && other.d == d; 104 | } 105 | 106 | } 107 | 108 | alias PlaneT!(float) Plane; -------------------------------------------------------------------------------- /gl3n/util.d: -------------------------------------------------------------------------------- 1 | /** 2 | gl3n.util 3 | 4 | Authors: David Herberth 5 | License: MIT 6 | */ 7 | 8 | module gl3n.util; 9 | 10 | private { 11 | import gl3n.linalg : Vector, Matrix, Quaternion; 12 | import gl3n.plane : PlaneT; 13 | 14 | static import std.compiler; 15 | 16 | } 17 | 18 | static if (std.compiler.version_major > 2 || 19 | std.compiler.version_minor > 68) 20 | { 21 | private import std.meta : AliasSeq; 22 | public alias TypeTuple = AliasSeq; 23 | } else { 24 | public import std.typetuple : TypeTuple; 25 | } 26 | 27 | private void is_vector_impl(T, int d)(Vector!(T, d) vec) {} 28 | 29 | /// If T is a vector, this evaluates to true, otherwise false. 30 | template is_vector(T) { 31 | enum is_vector = is(typeof(is_vector_impl(T.init))); 32 | } 33 | 34 | private void is_matrix_impl(T, int r, int c)(Matrix!(T, r, c) mat) {} 35 | 36 | /// If T is a matrix, this evaluates to true, otherwise false. 37 | template is_matrix(T) { 38 | enum is_matrix = is(typeof(is_matrix_impl(T.init))); 39 | } 40 | 41 | private void is_quaternion_impl(T)(Quaternion!(T) qu) {} 42 | 43 | /// If T is a quaternion, this evaluates to true, otherwise false. 44 | template is_quaternion(T) { 45 | enum is_quaternion = is(typeof(is_quaternion_impl(T.init))); 46 | } 47 | 48 | private void is_plane_impl(T)(PlaneT!(T) p) {} 49 | 50 | /// If T is a plane, this evaluates to true, otherwise false. 51 | template is_plane(T) { 52 | enum is_plane = is(typeof(is_plane_impl(T.init))); 53 | } 54 | 55 | 56 | unittest { 57 | // I need to import it here like this, otherwise you'll get a compiler 58 | // or a linker error depending where gl3n.util gets imported 59 | import gl3n.linalg; 60 | import gl3n.plane; 61 | 62 | assert(is_vector!vec2); 63 | assert(is_vector!vec3); 64 | assert(is_vector!vec3d); 65 | assert(is_vector!vec4i); 66 | assert(!is_vector!int); 67 | assert(!is_vector!mat34); 68 | assert(!is_vector!quat); 69 | 70 | assert(is_matrix!mat2); 71 | assert(is_matrix!mat34); 72 | assert(is_matrix!mat4); 73 | assert(!is_matrix!float); 74 | assert(!is_matrix!vec3); 75 | assert(!is_matrix!quat); 76 | 77 | assert(is_quaternion!quat); 78 | assert(!is_quaternion!vec2); 79 | assert(!is_quaternion!vec4i); 80 | assert(!is_quaternion!mat2); 81 | assert(!is_quaternion!mat34); 82 | assert(!is_quaternion!float); 83 | 84 | assert(is_plane!Plane); 85 | assert(!is_plane!vec2); 86 | assert(!is_plane!quat); 87 | assert(!is_plane!mat4); 88 | assert(!is_plane!float); 89 | } 90 | 91 | template TupleRange(int from, int to) if (from <= to) { 92 | static if (from >= to) { 93 | alias TupleRange = TypeTuple!(); 94 | } else { 95 | alias TupleRange = TypeTuple!(from, TupleRange!(from + 1, to)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /index.d: -------------------------------------------------------------------------------- 1 | Ddoc 2 | 3 | $(LINK2 https://github.com/Dav1dde/gl3n, gl3n) provides all the math you need to work with OpenGL. 4 | Currently gl3n supports: 5 | $(UL 6 | $(LI linear algebra) 7 | $(UL 8 | $(LI vectors) 9 | $(LI matrices) 10 | $(LI quaternions) 11 | ) 12 | $(LI geometry) 13 | $(UL 14 | $(LI axis aligned bounding boxes) 15 | $(LI planes) 16 | $(LI frustum) 17 | ) 18 | $(LI interpolation) 19 | $(UL 20 | $(LI linear interpolation (lerp)) 21 | $(LI spherical linear interpolation (slerp)) 22 | $(LI hermite interpolation) 23 | $(LI catmull rom interpolation) 24 | ) 25 | $(LI nearly all GLSL defined functions (according to spec 4.1)) 26 | $(LI the power of D, e.g. dynamic swizzling, templated types (vectors, matrices, quaternions), impressive constructors and more!) 27 | ) 28 | $(BR) 29 | Furthermore $(LINK2 https://github.com/Dav1dde/gl3n, gl3n) is MIT licensed, 30 | which allows you to use it everywhere you want it. 31 | $(BR)$(BR) 32 | A little example of gl3n's power: 33 | --- 34 | vec4 v4 = vec4(1.0f, vec3(2.0f, 3.0f, 4.0f)); 35 | vec4 v4_2 = vec4(1.0f, vec4(1.0f, 2.0f, 3.0f, 4.0f).xyz); // "dynamic" swizzling with opDispatch 36 | vec4 v4_3 = v4_2.xxyz; // opDispatch returns a vector! 37 | 38 | vec3 v3 = my_3dvec.rgb; 39 | vec3 foo = v4.xyzzzwzyyxw.xyz // not useful but possible! 40 | 41 | mat4 m4fv = mat4.translation(-0.5f, -0.54f, 0.42f).rotatex(PI).rotatez(PI/2); 42 | glUniformMatrix4fv(location, 1, GL_TRUE, m4fv.value_ptr); // yes they are row major! 43 | 44 | alias Matrix!(double, 4, 4) mat4d; 45 | mat4d projection; 46 | glGetDoublev(GL_PROJECTION_MATRIX, projection.value_ptr); 47 | 48 | mat4 m4fv = mat4.translation(-0.5f, -0.54f, 0.42f).rotatex(PI).rotatez(PI/2); 49 | glUniformMatrix4fv(location, 1, GL_TRUE, m4fv.value_ptr); // yes they are row major! 50 | 51 | mat3 inv_view = view.rotation; 52 | mat3 inv_view = mat3(view); 53 | 54 | mat4 m4 = mat4(vec4(1.0f, 2.0f, 3.0f, 4.0f), 5.0f, 6.0f, 7.0f, 8.0f, vec4(...) ...); 55 | --- 56 | 57 | --- 58 | alias Vector!(real, 3) vec3r; 59 | 60 | struct Camera { 61 | vec3 position = vec3(0.0f, 0.0f, 0.0f); 62 | vec3r rot = vec3r(0.0f, 0.0f, 0.0f); 63 | 64 | Camera rotatex(real alpha) { 65 | rot.x = rot.x + alpha; 66 | return this; 67 | } 68 | 69 | Camera rotatey(real alpha) { 70 | // do degrees radians conversion at compiletime! 71 | rot.y = clamp(rot.y + alpha, cradians!(-70.0f), cradians!(70.0f)); 72 | return this; 73 | } 74 | 75 | Camera rotatez(real alpha) { 76 | rot.z = rot.z + alpha; 77 | return this; 78 | } 79 | 80 | Camera move(float x, float y, float z) { 81 | position += vec3(x, y, z); 82 | return this; 83 | } 84 | Camera move(vec3 s) { 85 | position += s; 86 | return this; 87 | } 88 | 89 | @property camera() { 90 | // gl3n allows chaining of matrix (also quaternion) operations 91 | return mat4.identity.translate(-position.x, -position.y, -position.z) 92 | .rotatex(rot.x) 93 | .rotatey(rot.y); 94 | } 95 | } 96 | 97 | // somwhere later in the code 98 | glUniformMatrix4fv(programs.main.view, 1, GL_TRUE, cam.camera.value_ptr); 99 | // or use a quaternion camera! 100 | 101 | 102 | --- -------------------------------------------------------------------------------- /modules.ddoc: -------------------------------------------------------------------------------- 1 | MODULES = 2 | $(MODULE index, Index) 3 | $(MODULE gl3n.linalg, linear algebra) 4 | $(MODULE gl3n.math, mathematical functions) 5 | $(MODULE gl3n.interpolate, interpolation) 6 | $(MODULE gl3n.aabb, axis aligned bounding box) 7 | $(MODULE gl3n.plane, plane) 8 | $(MODULE gl3n.frustum, frustum) 9 | $(MODULE gl3n.ext.hsv, color) 10 | $(MODULE gl3n.ext.matrixstack, matrixstack) 11 | $(MODULE gl3n.util, useful utilities) 12 | -------------------------------------------------------------------------------- /settings.ddoc: -------------------------------------------------------------------------------- 1 | PROJECTNAME = gl3n 2 | REFERENCETOP = gl3n.linalg.html 3 | REPOSRCTREE = https://github.com/Dav1dde/gl3n/tree/master/ 4 | TITLEBARTEXT = Github Page 5 | RESOURCEROOT = bootDoc/ 6 | PACKAGESEP = . 7 | FAVICON = $(RESOURCEROOT)assets/ico/favicon.ico 8 | LOGOSRC = $(RESOURCEROOT)assets/img/icon-github.png 9 | LOGOALT = $(PROJECTNAME) 10 | LOGOLINK = $(REFERENCETOP) 11 | COPYRIGHT = David Herberth 12 | COPYRIGHTFOOTER = © $(COPYRIGHT) 2012-$(YEAR) 13 | --------------------------------------------------------------------------------