├── .gitmodules ├── main.cpp ├── CMakeLists.txt ├── .gitignore └── README.md /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Eigen"] 2 | path = Eigen 3 | url = git@github.com:brown-cs-224/Eigen.git 4 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "Eigen/Core" 2 | #include "iostream" 3 | 4 | using namespace std; 5 | using namespace Eigen; 6 | 7 | int main() { 8 | cout << "Eigen version: " << EIGEN_MAJOR_VERSION << "." << EIGEN_MINOR_VERSION << endl; 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | # Sets project name 4 | project(eigen_tutorial LANGUAGES CXX) 5 | 6 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 7 | 8 | # Sets C++ standard 9 | set(CMAKE_CXX_STANDARD 20) 10 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 11 | 12 | 13 | # Specifies .cpp and .h files to be passed to the compiler 14 | add_executable(${PROJECT_NAME} 15 | main.cpp 16 | ) 17 | 18 | file(GLOB EIGEN_DIR_CONTENTS ${CMAKE_CURRENT_LIST_DIR}/Eigen/*) 19 | list(LENGTH EIGEN_DIR_CONTENTS EIGEN_DIR_SIZE) 20 | if(EIGEN_DIR_SIZE EQUAL 0) 21 | message(FATAL_ERROR "Eigen dependency not pulled, please run `git submodule update --init --recursive`") 22 | endif() 23 | 24 | # This allows you to `#include ` 25 | target_include_directories(${PROJECT_NAME} PRIVATE 26 | Eigen 27 | ) 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ------------------------------------ 2 | # CS 2240 gitignore 3 | # ------------------------------------ 4 | 5 | # Eigen (so Gradescope submission is easier) 6 | Eigen/ 7 | 8 | # MacOS & clang files 9 | **/.DS_Store 10 | .qtc_clangd/ 11 | 12 | # Windows & MinGW files 13 | *.Debug 14 | *.Release 15 | 16 | # Visual Studio Code files 17 | .vscode 18 | 19 | # Visual Studio files 20 | *.ib_pdb_index 21 | *.idb 22 | *.ilk 23 | *.pdb 24 | *.sln 25 | *.suo 26 | *.vcproj 27 | *vcproj.*.*.user 28 | *.ncb 29 | *.sdf 30 | *.opensdf 31 | *.vcxproj 32 | *vcxproj.* 33 | 34 | # Required for Qt Creator w/ CMake 35 | CMakeLists.txt.user* 36 | 37 | # ------------------------------------ 38 | # Mysterious Qt Things (May Be Obsolete) 39 | # ------------------------------------ 40 | 41 | object_script.*.Release 42 | object_script.*.Debug 43 | *_plugin_import.cpp 44 | /.qmake.cache 45 | /.qmake.stash 46 | *.pro.user 47 | *.pro.user.* 48 | *.qbs.user 49 | *.qbs.user.* 50 | *.moc 51 | moc_*.cpp 52 | moc_*.h 53 | qrc_*.cpp 54 | ui_*.h 55 | *.qmlc 56 | *.jsc 57 | Makefile* 58 | *build-* 59 | *.qm 60 | *.prl 61 | 62 | *.autosave 63 | 64 | *.qmlproject.user 65 | *.qmlproject.user.* 66 | 67 | compile_commands.json 68 | 69 | *creator.user* 70 | 71 | *_qmlcache.qrc 72 | 73 | # ------------------------------------ 74 | # Taken from github/gitignore/c++ 75 | # ------------------------------------ 76 | 77 | # Prerequisites 78 | *.d 79 | 80 | # Compiled Object files 81 | *.slo 82 | *.lo 83 | *.o 84 | *.obj 85 | 86 | # Precompiled Headers 87 | *.gch 88 | *.pch 89 | 90 | # Compiled Dynamic libraries 91 | *.so 92 | *.dylib 93 | *.dll 94 | 95 | # Fortran module files 96 | *.mod 97 | *.smod 98 | 99 | # Compiled Static libraries 100 | *.lai 101 | *.la 102 | *.a 103 | *.lib 104 | 105 | # Executables 106 | *.exe 107 | *.out 108 | *.app 109 | 110 | # ------------------------------------ 111 | # Generated by Qt Creator (w/ cmake) 112 | # ------------------------------------ 113 | 114 | *~ 115 | *.autosave 116 | *.a 117 | *.core 118 | *.moc 119 | *.o 120 | *.obj 121 | *.orig 122 | *.rej 123 | *.so 124 | *.so.* 125 | *_pch.h.cpp 126 | *_resource.rc 127 | *.qm 128 | .#* 129 | *.*# 130 | core 131 | !core/ 132 | tags 133 | .DS_Store 134 | .directory 135 | *.debug 136 | Makefile* 137 | *.prl 138 | *.app 139 | moc_*.cpp 140 | ui_*.h 141 | qrc_*.cpp 142 | Thumbs.db 143 | *.res 144 | *.rc 145 | /.qmake.cache 146 | /.qmake.stash 147 | 148 | # qtcreator generated files 149 | *.pro.user* 150 | 151 | # xemacs temporary files 152 | *.flc 153 | 154 | # Vim temporary files 155 | .*.swp 156 | 157 | # Visual Studio generated files 158 | *.ib_pdb_index 159 | *.idb 160 | *.ilk 161 | *.pdb 162 | *.sln 163 | *.suo 164 | *.vcproj 165 | *vcproj.*.*.user 166 | *.ncb 167 | *.sdf 168 | *.opensdf 169 | *.vcxproj 170 | *vcxproj.* 171 | 172 | # MinGW generated files 173 | *.Debug 174 | *.Release 175 | 176 | # Python byte code 177 | *.pyc 178 | 179 | # Binaries 180 | *.dll 181 | *.exe 182 | 183 | # ------------------------------------ 184 | # Allow OBJ Files Just For Mesh 185 | # ------------------------------------ 186 | 187 | !meshes/*.obj 188 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Eigen Tutorial 2 | 3 | ## Introduction 4 | 5 | Eigen is an open-source linear algebra library implemented in C++. It’s fast and well-suited for a wide range of tasks, from heavy numerical computation, to simple vector arithmetic. The goal of this tutorial is to introduce the features of Eigen required for implementing graphics applications to readers possessing basic knowledge of C++, linear algebra, and computer graphics. 6 | 7 | ### Goals 8 | 9 | After reading this tutorial, the reader should be able to: 10 | 11 | 1. Create and initialize matrices and vectors of any size with Eigen in C++; 12 | 2. Use Eigen for basic algebraic operations on matrices and vectors, specifically addition, matrix multiplication, scalar multiplication, inversion, and transposition; and 13 | 3. Use Eigen’s built-in functions to create 4x4 transformation matrices. 14 | 15 | ## Installing Eigen 16 | 17 | In this course, we include Eigen separately in each project to make sure there are no versioning conflicts. While you could install Eigen globally on your machine, this could present challenges if you have multiple projects that rely on different versions of Eigen. 18 | 19 | We've added Eigen as a git submodule to all project repositories, so you can download it for each of your project repository folders by running the following command in those folders: 20 | 21 | ``` 22 | git submodule update --init --recursive 23 | ``` 24 | 25 | Try it now on this repository and verify that the `Eigen` directory is not empty. 26 | 27 | ### Good Day, Universe! 28 | 29 | Let’s test our installation by writing a simple program. If you’ve followed the steps above, you should be able to compile the following piece of code (in `main.cpp`) without any additional configuration. 30 | 31 | ```cpp 32 | #include "Eigen/Core" 33 | #include 34 | 35 | using namespace std; 36 | using namespace Eigen; 37 | 38 | int main() { 39 | cout << "Eigen version: " << EIGEN_MAJOR_VERSION << "." << EIGEN_MINOR_VERSION << endl; 40 | return 0; 41 | } 42 | ``` 43 | 44 | You can use `main.cpp` to run any code you'd like for the rest of this tutorial. 45 | 46 | ## Matrices 47 | 48 | Creating matrices with Eigen is simple: 49 | 50 | ```cpp 51 | Matrix3f A; 52 | Matrix4d B; 53 | ``` 54 | 55 | Eigen uses a naming convention for its datatypes that is quite similar to OpenGL. The actual datatype name is followed by suffixes that describe its size and the type of its member elements respectively. In the example above, the variable `A` is of type `Matrix3f`—i.e. `A` is a 3x3 matrix 56 | of `float`s. `B`, on the other hand, is a 4x4 matrix of `double`s. 57 | 58 | However, not all such combinations of size and type are valid. For example, `Matrix2i` is a valid type, but `Matrix5s` is not. This is not to say that you can’t create 5x5 matrices of type `short`—doing so merely requires a slightly different syntax: 59 | 60 | ```cpp 61 | // 5x5 matrix of type short 62 | Matrix M1; 63 | 64 | // 20x75 matrix of type float 65 | Matrix M2; 66 | ``` 67 | 68 | This more verbose form also allows you to create non-square matrices. 69 | 70 | As a matter of fact, all matrix types in Eigen are defined like this under the hood. Short-form type names like `Matrix3f` are only provided as a convenience for commonly used sizes and types. 71 | 72 | > Interestingly, Eigen even allows us to create matrices whose size is not known at compile time by using an `X` in place of the size suffix: e.g. `MatrixXf` and `MatrixXd`. However, as the majority of graphics operations only require us to work with 3x3 and 4x4 single- or double-precision matrices, we will restrict our attention in this tutorial to the datatypes `Matrix3f`, `Matrix4f`, `Matrix3d`, and `Matrix4d`. 73 | 74 | ### Initialization 75 | 76 | Now that we’ve created our matrix variables, let’s assign some values to them: 77 | 78 | #### Comma-Initialization 79 | 80 | ```cpp 81 | // Initialize A using the comma-initializer syntax 82 | A << 1.0f, 0.0f, 0.0f, 83 | 0.0f, 1.0f, 0.0f, 84 | 0.0f, 0.0f, 1.0f; 85 | ``` 86 | 87 | The first method uses the comma-initializer syntax: the programmer specifies the coefficients **row by row**. Note that all coefficients need to be provided—if the number of coefficients does not match the size of the matrix, the compiler will throw an error. You can imagine this might be rather cumbersome for large matrices. 88 | 89 | #### Accessing Individual Matrix Elements 90 | 91 | ```cpp 92 | // Initialize B by assigning values to its individual elements 93 | // Note that the matrix is zero-indexed 94 | for (int col = 0; col < 4; ++col) { 95 | for (int row = 0; row < 4; ++row) { 96 | B(row, col) = 0.0; 97 | } 98 | } 99 | ``` 100 | 101 | The second method accesses the individual coefficients of the matrix `B` using the **overloaded** parentheses **operators**. Indexing starts at zero, with the first index specifying the row number, and the second the column number. Note that this is unlike C lists, C++ vectors, and Python arrays: Eigen uses parentheses rather than square brackets to access matrix elements. 102 | 103 | You can use this same syntax to retrieve a matrix's values. Can you guess whether the following code prints a zero or a one? 104 | 105 | ```cpp 106 | cout << A(1, 2) << endl; 107 | ``` 108 | 109 | Note that in this example, we structured our loops such that the outer loop iterates over columns, and the inner loop iterates over rows. Doing it the other way around would have worked too, but it would have been less efficient. This is because Eigen stores matrices in **column-major order** by default. Recall, however, that coefficients are specified in **row-major order** in the comma-initializer syntax. Presumably, that was an intentional design decision to make comma-initialization less counterintuitive. 110 | 111 | #### Using Utility Functions 112 | 113 | In addition to the above two methods, Eigen provides utility functions to initialize matrices to predefined values: 114 | 115 | ```cpp 116 | // Set each coefficient to a uniform random value in the range [-1, 1] 117 | A = Matrix3f::Random(); 118 | 119 | // Set B to the identity matrix 120 | B = Matrix4d::Identity(); 121 | 122 | // Set all elements to zero 123 | A = Matrix3f::Zero(); 124 | 125 | // Set all elements to ones 126 | A = Matrix3f::Ones(); 127 | 128 | // Set all elements to a constant value 129 | B = Matrix4d::Constant(4.5); 130 | ``` 131 | 132 | ### Matrix Operations 133 | 134 | #### Basic Arithmetic 135 | 136 | Common arithmetic operators are overloaded to work with matrices: 137 | 138 | ```cpp 139 | Matrix4f M1 = Matrix4f::Random(); 140 | Matrix4f M2 = Matrix4f::Constant(2.2); 141 | 142 | // Addition 143 | // The size and the coefficient-types of the matrices must match 144 | cout << M1 + M2 << endl; 145 | 146 | // Matrix multiplication 147 | // The inner dimensions and the coefficient-types must match 148 | cout << M1 * M2 << endl; 149 | 150 | // Scalar multiplication and subtraction 151 | // What do you expect the output to be? 152 | cout << M2 - Matrix4f::Ones() * 2.2 << endl; 153 | ``` 154 | 155 | #### Comparison 156 | 157 | Equality (`==`) and inequality (`!=`) are the only relational operators that work with matrices. Two matrices are considered equal if all their corresponding coefficients are equal. 158 | 159 | ```cpp 160 | cout << (M2 - Matrix4f::Ones() * 2.2 == Matrix4f::Zero()) << endl; 161 | ``` 162 | 163 | #### Other Matrix Operations 164 | 165 | Common matrix operations are also provided as methods of the matrix class: 166 | 167 | ```cpp 168 | // Transposition 169 | cout << M1.transpose() << endl; 170 | 171 | // Inversion (you must `#include "Eigen/Dense"`) 172 | // Generates NaNs if the matrix is not invertible 173 | cout << M1.inverse() << endl; 174 | ``` 175 | 176 | #### Element-wise Operations 177 | 178 | Sometimes, we may prefer to apply an operation to a matrix element-wise. This can be done by asking Eigen to treat the matrix as a general array by invoking the `array()` method: 179 | 180 | ```cpp 181 | // Square each element of the matrix 182 | cout << M1.array().square() << endl; 183 | 184 | // Multiply two matrices element -wise 185 | cout << M1.array() * Matrix4f::Identity().array() << endl; 186 | 187 | // All relational operators can be applied element -wise 188 | cout << (M1.array() <= M2.array()) << endl << endl; 189 | cout << (M1.array() > M2.array()) << endl; 190 | ``` 191 | 192 | **Warning**: these operations do not work in-place. That is, calling `M1.array().sqrt()` returns a new matrix, with `M1` retaining its original value. 193 | 194 | ## Vectors 195 | 196 | A vector in Eigen is nothing more than a matrix with a single **column**: 197 | 198 | ```cpp 199 | typedef Matrix Vector3f; 200 | typedef Matrix Vector4d; 201 | ``` 202 | 203 | Consequently, many of the operators and functions we discussed above for matrices also work with vectors. The naming convention is also similar (in this case, the size suffix defines the one-dimensional length, rather than the square dimensions). 204 | 205 | ```cpp 206 | // Comma initialization 207 | v << 1.0f, 2.0f, 3.0f; 208 | 209 | // Coefficient access 210 | cout << v(2) << endl; 211 | 212 | // Vectors of length up to four can be initialized in the constructor 213 | Vector3f w(1.0f, 2.0f, 3.0f); 214 | 215 | // Utility functions 216 | Vector3f v1 = Vector3f::Ones(); 217 | Vector3f v2 = Vector3f::Zero(); 218 | Vector4d v3 = Vector4d::Random(); 219 | Vector4d v4 = Vector4d::Constant(1.8); 220 | 221 | // Arithmetic operations 222 | cout << v1 + v2 << endl << endl; 223 | cout << v4 - v3 << endl; 224 | 225 | // Scalar multiplication 226 | cout << v4 * 2 << endl; 227 | 228 | // Equality 229 | // Again, equality and inequality are the only relational 230 | // operators that work with vectors 231 | cout << (Vector2f::Ones() * 3 == Vector2f::Constant(3)) << endl; 232 | ``` 233 | 234 | Since vectors are just one-dimensional matrices, matrix-vector multiplication works as long as the inner dimensions and coefficient-types of the operands agree: 235 | 236 | ```cpp 237 | Vector4f v5 = Vector4f(1.0f, 2.0f, 3.0f, 4.0f); 238 | 239 | // 4x4 * 4x1 --> this works! 240 | cout << Matrix4f::Random() * v5 << endl; 241 | 242 | // 4x1 * 4x4 --> this causes a compiler error. 243 | cout << v5 * Matrix4f::Random() << endl; 244 | ``` 245 | 246 | As you would expect, matrix multiplication doesn't work with two vectors as the inner dimensions of two `n`x`1` matrices don't match. However, transposing one of the vectors fixes this: 247 | 248 | ```cpp 249 | // Transposition converts the column vector to a row vector 250 | // This makes the inner dimensions match, allowing matrix multiplication 251 | v1 = Vector3f::Random(); 252 | v2 = Vector3f::Random(); 253 | cout << v1 * v2.transpose() << endl; 254 | ``` 255 | 256 | The linear algebra-savvy among us probably recognize the last operation as nothing more than a dot product. In fact, vectors have built-in functions for the dot product, and other common vector operations: 257 | 258 | ```cpp 259 | cout << v1.dot(v2) << endl << endl; 260 | cout << v1.normalized() << endl << endl; 261 | cout << v1.cross(v2) << endl; 262 | 263 | // Convert a vector to and from homogenous coordinates 264 | Vector3f s = Vector3f::Random(); 265 | Vector4f q = s.homogeneous(); 266 | cout << (s == q.normalized()) << endl; 267 | ``` 268 | 269 | And, finally, element-wise operations can be performed by asking Eigen to treat the vector as a general array: 270 | 271 | ```cpp 272 | cout << v1.array() * v2.array() << endl << endl; 273 | cout << v1.array().sin() << endl; 274 | ``` 275 | 276 | ## Worked Example: Vertex Transformation 277 | 278 | Now that we have a basic understanding of Eigen, let's use it to perform a very common operation in graphics: vertex transformation. 279 | 280 | ```cpp 281 | #include "Eigen/Core" 282 | #include "Eigen/Geometry" 283 | 284 | #include 285 | 286 | using namespace std; 287 | using namespace Eigen; 288 | 289 | int main() { 290 | float arrVertices [] = { 291 | -1.0, -1.0, -1.0, 292 | 1.0, -1.0, -1.0, 293 | 1.0, 1.0, -1.0, 294 | -1.0, 1.0, -1.0, 295 | -1.0, -1.0, 1.0, 296 | 1.0, -1.0, 1.0, 297 | 1.0, 1.0, 1.0, 298 | -1.0, 1.0, 1.0 299 | }; 300 | 301 | MatrixXf mVertices = Map>(arrVertices); 302 | 303 | Transform t = Transform::Identity(); 304 | 305 | t.scale(0.8f); 306 | t.rotate(AngleAxisf(0.25f * M_PI, Vector3f::UnitX())); 307 | t.translate(Vector3f(1.5, 10.2, -5.1)); 308 | 309 | cout << t * mVertices.colwise().homogeneous() << endl; 310 | } 311 | ``` 312 | 313 | This example introduces several new Eigen constructs. 314 | 315 | First, we are using a C++ array to initialize a `MatrixXf` object. The array holds the x, y, and z coordinates of the vertices of a cube. Eigen's `Map` class allows us to perform this initialization from an array. 316 | 317 | Next, we are creating a `Transform`. You may recall from linear algebra that a linear transformation can be represented by a matrix. Eigen's `Transform` class allows us to abstract away this underlying matrix representation in favor of helper functions like `scale()`. If you need to, you can access the underlying matrix by calling `t.matrix()`. 318 | 319 | `Transform t` creates a 3-dimensional affine transformation with single-precision `float` coefficients. The next three lines apply a uniform scaling, an angle-axis rotation, and a translation to the created transform object. In matrix form, this may be written as 320 | 321 | ```cpp 322 | U = T * R * S * I; // I is the identity matrix 323 | ``` 324 | 325 | The rotation is specified as a combination of angle and rotation-axis by using the `AngleAxisf` class. The axis should be normalized, and in most cases we can simply use the convenience functions `Vector3f::UnitX()`, `Vector3f::UnitY`, and `Vector3f::UnitZ()` which represent the unit vectors in the x, y, and z directions, respectively. 326 | 327 | Finally, we apply the transformation to the vertices of our cube. See how storing the vertices as columns of a matrix allows us to transform all vertices with a single matrix multiplication. To do this, however, the inner dimensions of the transformation matrix, and the vertex matrix have to match. `t` uses homogeneous coordinates, and so a 3-dimensional transformation is represented as a 4x4 matrix. To match these dimensions, we homogenize each column of our vertex matrix by using the `colwise()` method. 328 | 329 | Each column of the output represents a transformed vertex. This can be seen in the printout you might get from the above code snippet: 330 | 331 | ``` 332 | 0.4 2 2 0.4 0.4 2 2 0.4 333 | 8.65499 8.65499 9.78636 9.78636 7.52362 7.52362 8.65499 8.65499 334 | 1.75362 1.75362 2.885 2.885 2.885 2.885 4.01637 4.01637 335 | ``` 336 | 337 | ## Further Reading 338 | 339 | The Eigen Quick Reference Guide provides a handy reference to most matrix and vector operations: https://eigen.tuxfamily.org/dox/group__QuickRefPage.html 340 | 341 | You will almost certainly have to refer to the Eigen documentation, especially for later projects which use features of Eigen which we did not cover in this tutorial. For this reason, we suggest visiting the site at least once or twice before then. 342 | 343 | All the best! 344 | 345 | ## Common Pitfalls 346 | 347 | You may also want to check out the common pitfalls page in the Eigen guide here: https://eigen.tuxfamily.org/dox/TopicPitfalls.html 348 | 349 | In particular, using `auto` with Matrix types can lead to very weird, unexpected behavior. The documentation recommends you do not use the auto keywords with Eigen's expressions. This is because Eigen uses expression templates to enable lazy evaluation as an optimization. 350 | 351 | In the following code as an example, C++ does not infer `C` as a `MatrixDx` but instead as an abstract matrix product expression referencing `A` and `B`. With lazy evaluation, the multiplication is first evaluated at `MatrixXd R1 = C` such that `R1 = A * B`. When `A` is changed afterwards with `A = D`, the multiplication evaluated at `MatrixXd R2 = C` is equivalent to `R2 = A * B = D * B` so R1 ≠ R2 which may be unexpected. 352 | 353 | ```c++ 354 | MatrixXd A = ..., B = ...; 355 | auto C = A*B; 356 | MatrixXd R1 = C; 357 | 358 | MatrixXd D = ...; 359 | A = D; 360 | MatrixXd R2 = C; 361 | // Note that R1 != R2 362 | ``` --------------------------------------------------------------------------------