summaryrefslogtreecommitdiff
path: root/wave-sim
diff options
context:
space:
mode:
authorSebastian Park <SebPark03@gmail.com>2024-04-10 02:45:04 -0400
committerSebastian Park <SebPark03@gmail.com>2024-04-10 02:45:04 -0400
commit47cd8a592ecad52c1b01f27d23476c0a5afeb7f1 (patch)
tree36b9abaff4e92a4a6df0d5ecb0e43e05c3aefd48 /wave-sim
parentfd19124693bb32835ad97802ba1950cd5202dbd2 (diff)
initial
Diffstat (limited to 'wave-sim')
-rw-r--r--wave-sim/.gitignore189
-rw-r--r--wave-sim/.gitmodules3
-rw-r--r--wave-sim/CMakeLists.txt104
-rw-r--r--wave-sim/README.md199
-rw-r--r--wave-sim/example-meshes/cone.mesh812
-rw-r--r--wave-sim/example-meshes/cube.mesh13
-rw-r--r--wave-sim/example-meshes/ellipsoid.mesh356
-rw-r--r--wave-sim/example-meshes/single-tet.mesh5
-rw-r--r--wave-sim/example-meshes/sphere.mesh177
-rw-r--r--wave-sim/example-video/cube.gifbin0 -> 1087091 bytes
-rw-r--r--wave-sim/example-video/ellipsoid.gifbin0 -> 2009937 bytes
-rw-r--r--wave-sim/example-video/tet.gifbin0 -> 1516165 bytes
-rwxr-xr-xwave-sim/resources/shaders/shader.frag43
-rwxr-xr-xwave-sim/resources/shaders/shader.vert24
-rwxr-xr-xwave-sim/resources/shaders/shaders.qrc6
-rwxr-xr-xwave-sim/src/glwidget.cpp194
-rwxr-xr-xwave-sim/src/glwidget.h62
-rw-r--r--wave-sim/src/graphics/camera.cpp188
-rw-r--r--wave-sim/src/graphics/camera.h51
-rw-r--r--wave-sim/src/graphics/graphicsdebug.cpp126
-rw-r--r--wave-sim/src/graphics/graphicsdebug.h15
-rw-r--r--wave-sim/src/graphics/meshloader.cpp53
-rw-r--r--wave-sim/src/graphics/meshloader.h15
-rw-r--r--wave-sim/src/graphics/shader.cpp234
-rw-r--r--wave-sim/src/graphics/shader.h71
-rw-r--r--wave-sim/src/graphics/shape.cpp337
-rw-r--r--wave-sim/src/graphics/shape.h59
-rwxr-xr-xwave-sim/src/main.cpp38
-rwxr-xr-xwave-sim/src/mainwindow.cpp16
-rwxr-xr-xwave-sim/src/mainwindow.h17
-rwxr-xr-xwave-sim/src/mainwindow.ui46
-rw-r--r--wave-sim/src/simulation.cpp236
-rw-r--r--wave-sim/src/simulation.h42
-rw-r--r--wave-sim/src/system.cpp262
-rw-r--r--wave-sim/src/system.h99
-rw-r--r--wave-sim/submission-final.md52
-rw-r--r--wave-sim/util/tiny_obj_loader.h2242
-rw-r--r--wave-sim/util/unsupportedeigenthing/OpenGLSupport322
38 files changed, 6708 insertions, 0 deletions
diff --git a/wave-sim/.gitignore b/wave-sim/.gitignore
new file mode 100644
index 0000000..f9ddc77
--- /dev/null
+++ b/wave-sim/.gitignore
@@ -0,0 +1,189 @@
+# ------------------------------------
+# CS 2240 gitignore
+# ------------------------------------
+
+# Eigen and GLEW (so Gradescope submission is easier)
+Eigen/
+glew/
+
+# MacOS & clang files
+**/.DS_Store
+.qtc_clangd/
+
+# Windows & MinGW files
+*.Debug
+*.Release
+
+# Visual Studio Code files
+.vscode
+
+# Visual Studio files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.sdf
+*.opensdf
+*.vcxproj
+*vcxproj.*
+
+# Required for Qt Creator w/ CMake
+CMakeLists.txt.user*
+
+# ------------------------------------
+# Mysterious Qt Things (May Be Obsolete)
+# ------------------------------------
+
+object_script.*.Release
+object_script.*.Debug
+*_plugin_import.cpp
+/.qmake.cache
+/.qmake.stash
+*.pro.user
+*.pro.user.*
+*.qbs.user
+*.qbs.user.*
+*.moc
+moc_*.cpp
+moc_*.h
+qrc_*.cpp
+ui_*.h
+*.qmlc
+*.jsc
+Makefile*
+*build-*
+*.qm
+*.prl
+
+*.autosave
+
+*.qmlproject.user
+*.qmlproject.user.*
+
+compile_commands.json
+
+*creator.user*
+
+*_qmlcache.qrc
+
+# ------------------------------------
+# Taken from github/gitignore/c++
+# ------------------------------------
+
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+
+# ------------------------------------
+# Generated by Qt Creator (w/ cmake)
+# ------------------------------------
+
+*~
+*.autosave
+*.a
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.rej
+*.so
+*.so.*
+*_pch.h.cpp
+*_resource.rc
+*.qm
+.#*
+*.*#
+core
+!core/
+tags
+.DS_Store
+.directory
+*.debug
+Makefile*
+*.prl
+*.app
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+Thumbs.db
+*.res
+*.rc
+/.qmake.cache
+/.qmake.stash
+
+# qtcreator generated files
+*.pro.user*
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.sdf
+*.opensdf
+*.vcxproj
+*vcxproj.*
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Python byte code
+*.pyc
+
+# Binaries
+*.dll
+*.exe
+
+# ------------------------------------
+# Allow OBJ Files Just For Mesh
+# ------------------------------------
+
+!meshes/*.obj
+
diff --git a/wave-sim/.gitmodules b/wave-sim/.gitmodules
new file mode 100644
index 0000000..79a321b
--- /dev/null
+++ b/wave-sim/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "Eigen"]
+ path = Eigen
+ url = https://github.com/brown-cs-224/Eigen
diff --git a/wave-sim/CMakeLists.txt b/wave-sim/CMakeLists.txt
new file mode 100644
index 0000000..ce88f92
--- /dev/null
+++ b/wave-sim/CMakeLists.txt
@@ -0,0 +1,104 @@
+cmake_minimum_required(VERSION 3.16)
+
+# Sets project name
+project(wave-sim LANGUAGES CXX C)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOUIC ON)
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+
+# Sets C++ standard
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# Specifies required Qt components
+find_package(Qt6 REQUIRED COMPONENTS Core)
+find_package(Qt6 REQUIRED COMPONENTS Concurrent)
+find_package(Qt6 REQUIRED COMPONENTS Xml)
+find_package(Qt6 REQUIRED COMPONENTS Widgets)
+find_package(Qt6 REQUIRED COMPONENTS OpenGL)
+find_package(Qt6 REQUIRED COMPONENTS OpenGLWidgets)
+find_package(Qt6 REQUIRED COMPONENTS Gui)
+
+# Allows you to include files from within those directories, without prefixing their filepaths
+include_directories(src)
+include_directories(libs)
+
+# Specifies .cpp and .h files to be passed to the compiler
+add_executable(${PROJECT_NAME}
+ src/main.cpp
+ src/mainwindow.cpp
+ src/simulation.cpp
+ src/glwidget.cpp
+ src/graphics/camera.cpp
+ src/graphics/graphicsdebug.cpp
+ src/graphics/meshloader.cpp
+ src/graphics/shader.cpp
+ src/graphics/shape.cpp
+ src/system.cpp
+
+ src/mainwindow.h
+ src/simulation.h
+ src/glwidget.h
+ src/graphics/camera.h
+ src/graphics/graphicsdebug.h
+ src/graphics/meshloader.h
+ src/graphics/shader.h
+ src/graphics/shape.h
+ src/system.h
+
+ util/tiny_obj_loader.h
+ util/unsupportedeigenthing/OpenGLSupport
+)
+
+# GLEW: this creates its library and allows you to `#include "GL/glew.h"`
+add_library(StaticGLEW STATIC glew/src/glew.c)
+include_directories(${PROJECT_NAME} PRIVATE glew/include)
+
+# Specifies libraries to be linked (Qt components, glew, etc)
+target_link_libraries(${PROJECT_NAME} PRIVATE
+ Qt::Concurrent
+ Qt::Core
+ Qt::Gui
+ Qt::OpenGL
+ Qt::OpenGLWidgets
+ Qt::Widgets
+ Qt::Xml
+ StaticGLEW
+)
+
+# This allows you to `#include "Eigen/..."`
+target_include_directories(${PROJECT_NAME} PRIVATE
+ Eigen
+)
+
+# Specifies other files
+qt6_add_resources(${PROJECT_NAME} "Resources"
+ PREFIX
+ "/"
+ FILES
+ resources/shaders/shader.frag
+ resources/shaders/shader.vert
+ example-meshes/single-tet.mesh
+)
+
+# GLEW: this provides support for Windows (including 64-bit)
+if (WIN32)
+ add_compile_definitions(GLEW_STATIC)
+ target_link_libraries(${PROJECT_NAME} PRIVATE
+ opengl32
+ glu32
+ )
+endif()
+
+# Set this flag to silence warnings on Windows
+if (MSVC OR MSYS OR MINGW)
+ set(CMAKE_CXX_FLAGS "-Wno-volatile")
+endif()
+
+# Set this flag to silence warnings on MacOS
+if (APPLE)
+ set(CMAKE_CXX_FLAGS "-Wno-deprecated-volatile")
+endif()
diff --git a/wave-sim/README.md b/wave-sim/README.md
new file mode 100644
index 0000000..96d76d8
--- /dev/null
+++ b/wave-sim/README.md
@@ -0,0 +1,199 @@
+# Assignment 3: Finite Element Simulation (FEM)
+
+**Released:** 3/6/24
+
+**Due:** 3/20/24 @ 11:59pm EST
+
+In this assignment, you’ll animate deformable solid objects using the Finite Element Method (FEM). The name “Finite Element Method” comes from the fact that this approach divides a continuous chunk of material into a mesh made up of a finite number of discrete elements (in this case, you’ll use tetrahedra). FEM allows for the simulation of physically-based materials in a principled way (as opposed to ad-hoc methods such as spring-and-mass simulations). You will implement the core features needed for a basic deformable object simulation (e.g. force computation, time integration, simple collision resolution) plus one or more extra features. To show off what your code can do, you’ll submit one or more videos demonstrating your simulator in action.
+
+## Relevant Reading
+
+- The lecture slides!
+- [Baraff and Witkin’s course notes](https://www.cs.cmu.edu/~baraff/sigcourse/) on physically-based modeling are a good reference for the basics of dynamics and time integration.
+- [O’Brien and Hodgins](http://graphics.berkeley.edu/papers/Obrien-GMA-1999-08/Obrien-GMA-1999-08.pdf) provide a good introduction to FEM fundamentals for graphics (Sections 1 - 3).
+- [Adam Bargteil’s Finite Element Notes](https://cal.cs.umbc.edu/Courses/CS6967-F08/FE-notes.pdf) are a nice supplement.
+- [This handout](https://web.stanford.edu/class/cs205b/lectures/lecture7.pdf) on computing strain and stress may also be informative.
+
+## Requirements
+
+This assignment is out of **100 points**.
+
+Your simulator must implement at least the following features:
+
+* Extract the surface mesh from your tetrahedral mesh **(10 points)**
+* Compute and apply force due to gravity **(5 points)**
+* Compute and apply internal elastic forces **(30 points)**
+ * Compute Green’s strain for each element
+ * Compute the stress for each element
+ * Compute per-node forces
+ * You can assume a lumped-mass model for your mesh (i.e. constant density within an element, mass of element distributed evenly to its four vertices).
+* Compute and apply internal viscous damping forces **(10 points)**
+* Resolve collisions **(10 points)**
+ * You must implement collision between the mesh and a ground plane, as well as at least one other type of obstacle (e.g. spheres).
+ * The simple ‘penalty force’ method described in Section 3.3 of [O’Brien and Hodgins](http://graphics.berkeley.edu/papers/Obrien-GMA-1999-08/Obrien-GMA-1999-08.pdf) can work, but it's not terribly stable.
+ * A better option is to do the following for every tet vertex that inter-penetrates a collidier: (1) Project the vertex out of the collider, (2) Decompose the vertex's velocity into a a *normal component* (i.e. parallel to surface normal of the collider at the point of intersection and a *tangential component* (perpendicular to the collider normal), (3) Reflect the normal component of the velocity and scale by some friction constant between 0 and 1, (4) Scale the tangential component of the velocity by some friction constant between 0 and 1.
+* Integrate your simulation forward in time using the explicit [midpoint method](https://www.pixar.com/assets/pbm2001/pdf/notesb.pdf) **(10 points)** (regular Euler integration recommended to start with)
+* Video **(10 points)**
+ * You must submit at least one video demonstrating your simulator in action. The video(s) must demonstrate all of the features you have implemented (including any extra features). Particularly creative and/or nicely-rendered animations may receive extra credit. Think about interesting scenarios you could set up. Please use a standard format and codec for your video files (e.g. .mp4 with the H264 codec).
+ * There are a few different ways you might go about making such videos:
+ * Screen capture an OpenGL rendering of your simulation, e.g. using the interactive viewer code that we provide below (see “Resources”).
+ * Export frame-by-frame meshes from your simulator and use your path tracer from Assignment 1 to render them.
+ * Use some other modeling/animation/rendering software to render exported meshes (e.g. Maya, Blender).
+ * To turn a set of frame images into a video, you can use [FFMPEG](https://hamelot.io/visualization/using-ffmpeg-to-convert-a-set-of-images-into-a-video/).
+* README **(5 points)**
+ * Your README should explain your logic for
+ * extracting the surface mesh
+ * computing and applying internal forces
+ * collision resolution
+ * your explicit integration method
+ * and any extra features you choose to implement
+ * Explanations should be 3 sentences each maximum
+ * You should also embed your videos into the README file
+
+Successfully implementing all of the requirements results in a total of **90/100 points**.
+To score **100/100** (or more!), you’ll need to implement some extra features.
+
+### Extra Features
+Each of the following features that you implement will earn you extra points. The features are ordered roughly by difficulty of implementation.
+
+* Share a cool tet mesh on Slack **(2 points)**
+* Make the visualizer pretty **(5 points)**
+ * Miss programming shaders? Modify shader.frag to add some fancy effects. Skyboxes, shadows, FBO hacks, and more are all welcome.
+* A higher-order explicit integrator **(5 points)**
+ * This will allow you to take larger simulation timesteps.
+ * Runge-Kutta 4
+ * [Verlet integration](https://resources.saylor.org/wwwresources/archived/site/wp-content/uploads/2011/06/MA221-6.1.pdf)
+ * You will be asked to show your code implementation during grading.
+* Adaptive time stepping **(5 points)**
+ * Take the largest time step you can take while remaining within some error threshold.
+ * [Baraff and Witkin’s](https://www.cs.cmu.edu/~baraff/sigcourse/notesb.pdf) notes are helpful here.
+ * You will be asked to show your code implementation during grading.
+* Parallelize your code **(5 points)**
+ * Many simulator operations are ‘embarrassingly parallel’ (force computations, integrator steps, etc.)
+ * Even something as simple as [OpenMP’s parallel for loop](http://supercomputingblog.com/openmp/tutorial-parallel-for-loops-with-openmp/) can buy you significant speedups, if applied in the right places.
+ * You should record videos comparing execution of your code (with/without) parallelization on complicated meshes. You may find some good tetrahedral meshes [here](https://github.com/wildmeshing/fTetWild?tab=readme-ov-file) (please go to Dataset section).
+* Interactivity **(10 points)**
+ * Allow the user to poke, push, drag, etc. a deformable mesh.
+ * Please record a video of interating with the mesh.
+* Self collisions **(15 points)**
+ * Or, collisions between two deformable meshes.
+ * The [O’Brien and Hodgins paper](http://graphics.berkeley.edu/papers/Obrien-GMA-1999-08/Obrien-GMA-1999-08.pdf) has some suggestions for how to do this.
+ * Please record a video showing the collisions.
+* Something else!
+ * This list is not meant to be exhaustive--if you’ve got another advanced feature in mind, go for it! (though you may want to ask a TA or the instructor first if you’re concerned about whether the idea is feasible)
+
+### Advanced Extra Features
+These extra features are significantly more challenging to implement, and they involve reading other papers to implement. Some of these are probably big enough in scope to be closer to final project ideas, to be honest. I’ve listed them here for completeness and to potentially get some people interested in some of these ideas :)
+
+* Corotated linear elasticity **(20 points)**
+ * Take a look at [this paper](https://matthias-research.github.io/pages/publications/GI2004.pdf).
+ * Factors out the rotational part of a tetrahedron’s deformation when computing element stress. This allows for fast, stable simulations.
+* Invertible elements **(20 points)**
+ * Take a look at [this paper](http://physbam.stanford.edu/~fedkiw/papers/stanford2004-04.pdf).
+ * Allows for tetrahedra to invert, return to their original shape, and remain stable.
+* Plasticity **(20 points)**
+ * Make some part of the deformation that a mesh undergoes permanent, so that it does not fully return to its original rest shape.
+ * There are different ways to implement this depending on your elasticity model:
+ * [O’Brien et al.](http://graphics.berkeley.edu/papers/Obrien-GMA-2002-08/Obrien-GMA-2002-08.pdf) - Basic Green’s strain formulation
+ * [Müller and Gross](https://matthias-research.github.io/pages/publications/GI2004.pdf) - Formulation for corotated linear elasticity
+ * [Irving et al.](http://physbam.stanford.edu/~fedkiw/papers/stanford2004-04.pdf) - Formulation for invertible elements
+* Fracture **(20 points)**
+ * Split the mesh when stresses become sufficiently large.
+ * Try the method in the [Müller and Gross paper](https://matthias-research.github.io/pages/publications/GI2004.pdf). The method in the [O’Brien and Hodgins paper](http://graphics.berkeley.edu/papers/Obrien-GMA-1999-08/Obrien-GMA-1999-08.pdf), while more physically accurate, is much more complicated to implement.
+* Semi-implicit integration **(25 points)**
+ * Take very large time steps by ‘backwards’ simulation: find the step that, when run backwards from where you want the simulation to end up, takes the simulation to its current state.
+ * Once again, [Baraff and Witkin](https://www.cs.cmu.edu/~baraff/sigcourse/) have a good introduction.
+ * You’ll need to compute the derivative of node forces w.r.t. node positions. [Adam Bargteil’s notes](https://cal.cs.umbc.edu/Courses/CS6967-F08/FE-notes.pdf) have some derivations for this.
+ * You’ll also need to set up and solve a sparse linear system. [Eigen](http://eigen.tuxfamily.org/index.php?title=Main_Page) provides some good C++ libraries for this.
+* Learning deformable dynamics **(25 points)**
+ * Take a look at [this paper](https://arxiv.org/pdf/1803.09109.pdf).
+ * The DeepWarp model learns to adjust a simplified simulation (using linear elasticity) with a displacement fix.
+
+**Any extra features you implement must be mentioned in your README and demonstrated in your video (you can submit multiple videos to show off different features, if that’s easier).**
+
+### Resources
+
+Feel free to use this stencil code to get started.
+
+This includes a simple interactive 3D viewer for visualizing (and dynamically updating) tetrahedral meshes, as well as several example tetrahedral mesh files you can load.
+
+These files are in the .mesh format, a line-by-line file format that resembles the .obj file format. Lines come in one of two types:
+
+* `v x y z` -- a vertex at location (x, y, z).
+* `t i1 i2 i3 i4` -- a tetrahedron whose vertices are located at indices i1, i2, i3, and i4 in the list of vertices.
+
+If you want to create new tetrahedral meshes, you can do so using one of the following software packages:
+
+* [fTetWild](https://github.com/wildmeshing/fTetWild)
+* [Netgen](https://sourceforge.net/projects/netgen-mesher/)
+* [Tetgen](http://wias-berlin.de/software/index.jsp?id=TetGen&lang=1)
+* [Quartet](https://github.com/crawforddoran/quartet)
+
+These output their own various file formats (as there is, alas, less standardization in tet mesh file formats than for tri meshes), so you’d need to convert those to the .mesh format.
+
+## Starter Code
+The starter code in this repo provides a simple 3D viewer for tetrahedral mesh simulations. **After cloning the repo locally, you'll need to run `git submodule update --init --recursive` to update the Eigen submodule.**
+
+As given, the starter code will load up and visualize a single tetrahedron. Your job is to modify the code to load arbitrary meshes (e.g. the ones in `/example-meshes`), extract their surface meshes for visualization, and to compute simulation time step updates to the vertex positions of the mesh. Note: Your simulation does not have to support the cone mesh to receive full credit.
+
+You'll want to look at `src/simulation.cpp` to get started, as that's the only file you need to change (although you'll probably make several of your own new files, too).
+You also might want to look at `src/glwidget.cpp`, if you're interested in adding new interactivity/controls to the program.
+
+Speaking of controls: the controls offered by the starter code are:
+ * Move Camera: WASD
+ * Look around: Click and hold mouse and drag
+ * Toggle orbit mode: C (changes the camera from a first-person view to an orbiting camera a la what the Maya editor does)
+ * Toggle between displaying the surface mesh and a wireframe of the full tet mesh: T
+
+When the program first loads, you should see a ground plane and a single tet floating in space.
+If the tet does not display: check the console output. Most likely the .mesh file failed to load because the file couldn't be found. You'll need to set the working directory in Qt Creator to be the root directory of this repository. To do that, select "Projects" on the left-hand sidebar in Qt Creator, select "Run" under the "Build & Run options", and enter the path to the repo root in the "Working directory" field.
+
+### Implementation & Debugging Tips
+
+- Start by simulating a single tetrahedron and verifying that everything works in that case.
+- If your mesh has no forces applied, the deformation gradient for each element should be the identity matrix.
+- A sanity check: try initializing the position of one or more vertices to be different than the rest configuration; verify that the mesh moves back to its rest state when you run the simulation.
+- See the lecture slides for tips on what to do if your simulation explodes.
+- Simulation will involve many parameters; you may find it helpful to use a config.ini file to set them then use QSettings [https://doc.qt.io/qt-6/qsettings.html] to extract them
+- The lecture slides contain a few examples of material parameters. If you want more, you can check out the tables given [here](https://www.efunda.com/materials/common_matl/Common_Matl.cfm?MatlPhase=Solid&MatlProp=Mechanical) and [here](http://web.mit.edu/16.20/homepage/3_Constitutive/Constitutive_files/module_3_no_solutions.pdf). Be aware that most of these materials are very stiff and will likely be difficult to simulate in a stable manner. [This Wikipedia page](https://en.wikipedia.org/wiki/Lam%C3%A9_parameters) lists formulas for converting between different types of material parameters. What you want are “Lamé's first parameter” (that’s λ) and “Shear modulus” (that’s μ).
+- Here’s a set of parameters that have worked for students in previous years (with midpoint and rk4 integrators for the sphere and ellipsoid and a timestep of 0.001):
+ - const Vector3f \_gravity = Vector3f(0.f, -.1f, 0.f);
+ - const float \_lambda = 1e3f; //incompressibility for the whole material
+ - const float \_mu = 1e3f; //rigidity for the whole material
+ - const float \_phi = 1e1f; //coefficients of viscosity
+ - const float \_psi = 1e1f;
+ - const float \_rho = 1200.f; //density
+- Use `const` and `assert` wherever possible.
+- Check for uninitialized values.
+- Use Qt Creator's debugger.
+- **For numeric values that your simulation keeps track of (positions, velocities, forces, etc.), use** `double` **and** `Eigen::Vector3d` **instead of** `float` **and** `Eigen::Vector3f`**. The additional precision can have a big impact on your simulation's stability.**
+- **REMINDER: Your code will run much faster if you compile in Release mode ;)**
+
+### Submission Instructions
+
+Submit your branch of the Github classroom repository to the “Simulation” assignment.
+
+## Example Video
+
+
+- For the following example these parameters are used (Note: The video is compressed to gif and might not have the same frame rate with the real simulation):
+ - Eigen::Vector3d \_g = Eigen::Vector3d(0.0, -1.0, 0.0); // gravity
+ - double \_kFloor = 4e4; //penalty for node collision
+ - double \_lambda = 4e3; //incompressibility for the whole material
+ - double \_mu = 4e3; //rigidity for the whole material
+ - double \_phi = 100; //coefficients of viscosity
+ - double \_psi = 100;
+ - double \_density = 1200.0; //density
+ - timestep = 0.0003f;
+
+Single tetrahedron falling on the floor.
+
+![Alt Text](example-video/tet.gif)
+
+Cube falling on the floor.
+
+![Alt Text](example-video/cube.gif)
+
+Ellipsoid falling on the floor with fixed sphere.
+
+![Alt Text](example-video/ellipsoid.gif)
diff --git a/wave-sim/example-meshes/cone.mesh b/wave-sim/example-meshes/cone.mesh
new file mode 100644
index 0000000..0074fd2
--- /dev/null
+++ b/wave-sim/example-meshes/cone.mesh
@@ -0,0 +1,812 @@
+v 0 -1 0
+v 0 -0.864619 -0.502427
+v -0 -0.498241 -0.867038
+v -0 0.00272637 -0.999996
+v 0 0.501975 -0.864882
+v 0 0.867132 -0.498079
+v 0 0.999997 0.00253716
+v -0 0.86539 0.501098
+v 0 0.499881 0.866094
+v 0 0.000120005 1
+v 0 -0.500052 0.865996
+v 0 -0.865656 0.50064
+v 3 0 -0.1
+v 3 0.050017 -0.0865927
+v 3 0.0866384 -0.0499379
+v 3 0.0999999 0.000146805
+v 3 0.0865305 0.0501245
+v 3 0.0497623 0.0867393
+v 3 -0.000215957 0.0999998
+v 3 -0.0500491 0.0865742
+v 3 -0.0866379 0.0499387
+v 3 -0.0999999 -0.000134552
+v 3 -0.0865311 -0.0501235
+v 3 -0.0498637 -0.0866811
+v 0.40532 -0.853308 -0.208469
+v 0.427607 -0.236 -0.839164
+v 0.40766 0.606567 -0.63438
+v 0.420171 0.849106 0.206895
+v 0.423082 0.252 0.835917
+v 0.435143 -0.611609 0.617973
+v 0.416481 -0.845519 0.225433
+v 2.95901 0.03246 -0.107504
+v 2.95824 0.0802701 -0.0788605
+v 2.9595 0.109047 -0.0262
+v 2.95919 0.108316 0.0294281
+v 2.95944 0.0780175 0.0805919
+v 2.95903 0.0281238 0.108711
+v 2.95976 -0.0314001 0.107582
+v 2.95796 -0.0818144 0.0773825
+v 2.95924 -0.109457 0.0247851
+v 2.95987 -0.107934 -0.0300477
+v 2.9591 -0.0780107 -0.0807382
+v 2.9592 -0.0267097 -0.109016
+v 0.402746 -0.636434 -0.60655
+v 0.443342 0.215789 -0.839714
+v 0.391903 0.850185 -0.23636
+v 0.421071 0.633154 0.602023
+v 0.427694 -0.208458 0.846399
+v 0.785461 -0.0389787 -0.763367
+v 0.695936 0.682195 -0.400797
+v 0.788636 0.688213 0.330388
+v 0.795295 0.0437912 0.760151
+v 2.91532 0.00547341 -0.125283
+v 2.91506 0.125429 0.00367619
+v 2.9143 0.0674265 -0.106097
+v 2.91448 -0.125523 -0.00580264
+v 2.91443 0.110915 -0.0590865
+v 0.710718 -0.692948 -0.37263
+v 2.91295 0.0585218 0.111714
+v 0.843549 0.373508 -0.646841
+v 2.91244 -0.00190869 0.126253
+v 0.780939 -0.435023 -0.630142
+v 2.91332 0.108062 0.0648043
+v 2.91171 -0.0693073 0.105808
+v 2.91436 -0.107427 -0.0652495
+v 0.753943 0.77234 -0.0477945
+v 0.797304 0.426663 0.629912
+v 0.799438 -0.360725 0.669129
+v 2.91332 -0.113235 0.0552716
+v 2.91467 -0.0578337 -0.111492
+v 0.76877 -0.769058 -0.0218691
+v 2.86441 -0.138324 0.0256191
+v 1.09957 0.668616 0.0450258
+v 2.86408 0.133776 0.0438353
+v 2.86313 0.0951695 0.104121
+v 0.799202 -0.661182 0.375236
+v 2.86537 -0.133241 -0.0442229
+v 2.86578 -0.0936869 -0.10439
+v 1.12751 0.245117 0.614677
+v 1.04831 0.604602 -0.32307
+v 2.86553 0.0417692 -0.133982
+v 2.86543 -0.0286675 -0.137412
+v 2.86226 -0.0449 0.133998
+v 1.12581 -0.462015 0.474474
+v 1.12139 -0.12181 0.652306
+v 2.86332 0.0306413 0.137633
+v 1.12527 0.53452 0.391263
+v 2.86246 -0.107014 0.0922097
+v 2.86442 0.137287 -0.0306832
+v 1.09342 -0.592063 -0.317819
+v 1.10646 0.105549 -0.659672
+v 1.12477 -0.272894 -0.603761
+v 2.8652 0.10448 -0.0938487
+v 1.13669 -0.647605 0.121991
+v 2.80849 0.157198 0.00895636
+v 2.80831 0.13193 0.0860383
+v 2.80878 -0.142636 0.0664758
+v 1.41922 0.559089 0.131014
+v 2.80735 0.0713633 0.140735
+v 2.81016 0.0124507 -0.156457
+v 1.37905 0.534116 -0.241763
+v 2.81025 -0.156483 -0.0117585
+v 1.26324 0.343653 -0.51728
+v 2.80655 -0.0903755 0.129642
+v 2.80985 -0.130581 -0.087243
+v 2.8071 -0.0133551 0.157304
+v 1.40824 -0.0226556 -0.577084
+v 1.41902 0.0903651 0.56714
+v 1.44083 -0.494389 0.279145
+v 2.80854 0.140822 -0.0703987
+v 1.41914 0.385074 0.426014
+v 1.43967 -0.381173 -0.421242
+v 2.80825 0.0877708 -0.130805
+v 1.45146 -0.557295 -0.0902925
+v 2.8091 -0.0683312 -0.141649
+v 1.40892 -0.254634 0.518136
+v 1.6337 0.252025 -0.443249
+v 2.7421 -0.0623184 0.166061
+v 2.74178 0.0333582 0.174303
+v 1.68942 0.221694 0.440537
+v 2.74482 0.167241 0.0565864
+v 1.67841 -0.168613 -0.466967
+v 2.74452 -0.0304697 -0.173995
+v 1.67009 0.481933 -0.129282
+v 1.68611 0.453494 0.196327
+v 2.7439 -0.174701 0.0273579
+v 2.74315 0.116412 0.133403
+v 2.74333 -0.165757 -0.0620813
+v 2.7452 0.173204 -0.0336301
+v 2.74337 -0.137648 0.111259
+v 2.74537 0.134142 -0.114541
+v 1.63975 -0.325515 0.390101
+v 2.74487 0.0609178 -0.165696
+v 1.68002 -0.0658404 0.491604
+v 2.74391 -0.113452 -0.135633
+v 1.75957 -0.455968 0.122474
+v 2.67288 -0.159412 -0.117667
+v 2.67052 0.0832354 0.180584
+v 2.67519 -0.0805676 -0.180258
+v 1.69827 -0.429477 -0.236978
+v 2.67551 0.196792 0.0147762
+v 2.67093 -0.116913 0.160689
+v 2.67605 0.164378 0.108912
+v 2.67161 -0.183248 0.0763464
+v 2.67233 -0.196928 -0.0233007
+v 1.87063 -0.279742 -0.338081
+v 2.67574 0.178837 -0.0832815
+v 1.89544 0.0626391 0.426797
+v 1.86748 -0.217563 0.382167
+v 2.67077 -0.0206423 0.197696
+v 1.88133 0.317816 -0.297896
+v 2.67542 0.0194817 -0.196412
+v 1.89826 0.43029 -0.0141464
+v 1.90988 0.32987 0.271192
+v 1.91107 0.00300925 -0.42667
+v 2.67624 0.114955 -0.160141
+v 2.59115 -0.177817 0.134002
+v 2.58978 0.0369089 0.21999
+v 2.60166 0.216079 -0.0386093
+v 2.60352 0.0801107 -0.203761
+v 2.59403 -0.136201 -0.175046
+v 2.07524 0.335236 -0.173406
+v 2.59931 -0.0305202 -0.218081
+v 2.60063 0.173236 -0.1353
+v 1.95224 -0.402481 -0.0983742
+v 2.59735 0.208384 0.0729854
+v 2.59191 -0.0834216 0.206192
+v 2.01525 -0.337568 0.205935
+v 2.5861 0.144431 0.171441
+v 2.06321 -0.246908 -0.290217
+v 2.05434 -0.102488 0.369758
+v 2.08726 0.362888 0.0897545
+v 2.10307 0.183771 0.320073
+v 2.59517 -0.219679 0.0279323
+v 2.59747 -0.203392 -0.0858266
+v 2.49919 -0.0262648 0.24886
+v 2.24388 0.318626 -0.0727973
+v 2.49489 -0.158087 0.195648
+v 2.51173 0.153973 -0.192471
+v 2.51862 0.0362666 -0.241709
+v 2.49998 0.23077 -0.0961667
+v 2.48923 -0.238545 0.0849871
+v 2.04889 0.180222 -0.340591
+v 2.51763 0.243766 0.021505
+v 2.21704 0.180417 -0.282134
+v 2.13024 -0.0289046 -0.359768
+v 2.49953 -0.194432 -0.157375
+v 2.49223 0.10802 0.228039
+v 2.18643 -0.343949 -0.00917076
+v 2.50095 0.209404 0.136041
+v 2.19001 -0.2198 0.263314
+v 2.51306 -0.242768 -0.0402586
+v 2.50667 -0.0905917 -0.230859
+v 2.2139 -0.0125075 0.335597
+v 2.25624 0.286058 0.150275
+v 2.31221 -0.11974 0.281965
+v 2.24239 -0.207281 -0.253277
+v 2.41195 -0.0117776 -0.276164
+v 2.37994 0.0315921 0.284268
+v 2.38677 -0.146706 -0.243138
+v 2.35955 -0.234993 0.173556
+v 2.39546 0.279968 0.02797
+v 2.41591 -0.10801 0.253149
+v 2.38228 0.220741 -0.180771
+v 2.43809 0.108548 -0.245659
+v 2.37122 -0.280358 -0.0686194
+v 2.27089 0.147863 0.282361
+v 2.28492 -0.027692 -0.313303
+v 2.38728 0.196647 0.204651
+v 2.33969 0.10415 -0.279307
+v 0 -0.554186 -0.0153464
+v 0 -0.274624 -0.478469
+v 0 0.275819 -0.482357
+v 0 0.550462 -0.00137298
+v 0 0.278678 0.482663
+v 0 -0.262278 0.479208
+v 0 -0.0057102 -0.00636386
+v 3 0.0000335728 -0.0553485
+v 3 0.0486986 -0.028984
+v 3 0.0486274 0.0291126
+v 3 -0.0000969564 0.0553598
+v 3 -0.0487186 0.0289888
+v 3 -0.0486532 -0.0290939
+v 3 -0.0000134581 0.00000519592
+v 2.64134 -0.0154616 -0.0143692
+v 2.43712 0.0038496 0.00193871
+v 2.79202 -0.00121538 -0.000609376
+v 2.93579 0.00713071 0.0452753
+v 0.459851 -0.119675 0.365287
+v 0.566014 0.388385 -0.0498831
+v 2.20779 -0.00485698 0.0319731
+v 1.85993 0.0108995 0.166577
+v 2.89773 0.000417362 -0.0307343
+v 0.935533 -0.0556323 -0.033804
+v 1.56354 0.000594977 -0.0243156
+v 1.97794 0.0720504 -0.128042
+v 2.5714 0.116159 -0.0284068
+t 207 196 199 230
+t 225 207 199 230
+t 214 9 28 8
+t 80 99 81 226
+t 228 70 233 75
+t 80 81 232 226
+t 33 218 32 14
+t 232 92 80 226
+t 227 82 85 226
+t 229 49 45 65
+t 231 135 164 167
+t 80 232 81 52
+t 221 20 39 21
+t 40 22 41 222
+t 229 214 46 213
+t 194 208 206 230
+t 163 236 158 180
+t 234 115 107 233
+t 71 232 76 226
+t 156 225 166 224
+t 87 227 226 82
+t 96 71 101 226
+t 234 119 231 124
+t 212 5 26 4
+t 166 225 177 175
+t 225 157 224 168
+t 51 84 67 233
+t 95 73 226 94
+t 66 51 228 233
+t 227 82 63 60
+t 226 96 125 101
+t 76 232 55 64
+t 232 73 88 226
+t 55 64 232 40
+t 170 148 231 167
+t 31 218 232 217
+t 27 229 46 213
+t 122 99 226 114
+t 53 33 232 56
+t 236 159 179 178
+t 104 77 226 76
+t 208 198 206 230
+t 43 1 211 2
+t 227 62 35 34
+t 74 98 226 85
+t 95 98 226 74
+t 74 232 227 226
+t 143 129 125 224
+t 141 143 156 224
+t 227 60 58 85
+t 139 234 135 164
+t 225 203 178 204
+t 88 109 226 94
+t 76 104 101 226
+t 214 228 46 28
+t 118 126 226 224
+t 148 135 231 167
+t 137 126 118 224
+t 165 236 140 142
+t 226 122 134 224
+t 38 20 221 19
+t 210 1 211 43
+t 234 102 100 116
+t 216 229 212 213
+t 122 226 134 114
+t 136 127 134 224
+t 232 69 81 52
+t 226 104 134 114
+t 179 192 162 224
+t 224 174 160 136
+t 192 225 199 186
+t 34 227 219 35
+t 170 231 172 230
+t 93 233 113 89
+t 84 51 78 233
+t 170 172 231 147
+t 210 57 43 233
+t 229 216 228 214
+t 75 67 233 228
+t 3 44 211 25
+t 210 216 228 233
+t 29 11 215 30
+t 225 205 186 191
+t 190 188 230 200
+t 233 93 70 89
+t 148 133 147 231
+t 196 205 199 230
+t 170 148 147 231
+t 16 34 219 35
+t 218 31 32 13
+t 236 163 159 178
+t 174 144 224 173
+t 236 146 158 140
+t 203 225 178 180
+t 11 210 30 0
+t 127 144 125 224
+t 229 214 228 46
+t 211 229 216 233
+t 108 93 233 113
+t 236 163 158 146
+t 117 141 149 224
+t 227 74 58 62
+t 160 138 224 136
+t 66 229 228 46
+t 166 141 156 224
+t 43 25 2 211
+t 74 232 226 73
+t 5 45 6 213
+t 53 227 232 33
+t 53 227 33 34
+t 236 183 158 180
+t 97 233 72 100
+t 137 168 142 224
+t 210 57 233 70
+t 166 225 175 157
+t 11 210 215 30
+t 203 176 184 230
+t 183 225 201 180
+t 161 150 182 235
+t 139 121 234 145
+t 229 27 45 213
+t 3 44 4 212
+t 236 225 179 224
+t 96 87 226 103
+t 80 99 226 112
+t 225 187 168 189
+t 198 225 175 202
+t 53 227 62 232
+t 152 124 231 153
+t 210 57 70 24
+t 210 57 24 43
+t 227 218 219 223
+t 70 89 57 233
+t 210 43 211 233
+t 44 25 48 233
+t 87 227 82 63
+t 48 44 233 59
+t 29 11 10 215
+t 71 76 101 226
+t 44 229 59 26
+t 89 61 57 233
+t 230 198 193 195
+t 127 226 134 224
+t 42 31 232 217
+t 41 23 217 222
+t 172 230 194 206
+t 81 99 114 226
+t 198 230 193 206
+t 30 29 228 215
+t 7 27 46 213
+t 152 234 124 123
+t 74 232 73 62
+t 181 205 200 225
+t 97 234 100 123
+t 232 68 39 227
+t 225 205 200 230
+t 37 220 221 227
+t 230 172 170 193
+t 233 102 59 79
+t 223 227 222 221
+t 40 22 222 21
+t 150 152 123 234
+t 106 233 91 111
+t 159 179 162 224
+t 51 66 228 28
+t 233 234 113 111
+t 234 106 111 233
+t 121 106 111 234
+t 171 235 230 231
+t 96 129 103 226
+t 27 229 65 50
+t 230 185 169 235
+t 44 229 233 59
+t 226 127 125 224
+t 42 23 217 41
+t 232 42 217 41
+t 210 1 43 24
+t 210 30 228 215
+t 227 218 223 232
+t 216 210 211 233
+t 212 5 213 45
+t 21 40 39 222
+t 79 229 59 233
+t 48 59 233 90
+t 231 119 147 153
+t 108 115 233 83
+t 172 231 147 153
+t 216 214 215 228
+t 76 232 64 77
+t 205 225 199 230
+t 195 230 202 200
+t 108 234 131 135
+t 18 220 36 17
+t 135 148 231 131
+t 233 91 48 90
+t 234 115 131 133
+t 36 227 58 35
+t 173 225 156 224
+t 218 33 219 15
+t 154 150 235 182
+t 88 232 56 53
+t 157 137 149 224
+t 141 166 149 224
+t 87 71 96 226
+t 201 225 208 230
+t 34 16 219 15
+t 33 34 219 15
+t 33 218 14 15
+t 132 130 155 224
+t 120 128 94 226
+t 44 212 26 4
+t 225 157 168 187
+t 163 236 159 155
+t 5 212 26 45
+t 87 227 63 68
+t 174 186 191 224
+t 232 87 226 71
+t 93 108 233 83
+t 234 154 235 145
+t 233 110 78 107
+t 233 106 90 102
+t 181 205 225 191
+t 229 44 212 26
+t 37 220 227 36
+t 132 155 151 224
+t 186 225 191 224
+t 117 129 224 226
+t 214 7 46 213
+t 221 21 39 222
+t 227 221 39 222
+t 143 141 129 224
+t 172 231 171 230
+t 230 188 164 169
+t 196 205 230 188
+t 212 229 26 45
+t 229 49 26 45
+t 130 128 146 224
+t 227 38 37 63
+t 130 109 226 112
+t 95 126 226 98
+t 165 236 158 140
+t 106 233 90 91
+t 236 163 146 155
+t 42 69 41 232
+t 45 27 6 213
+t 225 205 199 186
+t 229 216 214 213
+t 75 93 70 233
+t 211 229 212 216
+t 37 221 220 19
+t 44 3 211 212
+t 207 185 230 184
+t 185 154 235 182
+t 234 110 233 107
+t 38 221 37 19
+t 227 62 58 35
+t 69 64 41 232
+t 227 220 219 17
+t 119 110 234 107
+t 84 115 233 107
+t 203 209 204 225
+t 214 8 28 46
+t 233 91 111 89
+t 234 97 124 123
+t 235 176 230 184
+t 37 220 36 18
+t 22 23 41 222
+t 38 20 39 221
+t 220 227 36 17
+t 38 227 221 39
+t 9 214 28 47
+t 47 51 228 28
+t 230 231 164 167
+t 214 47 228 28
+t 229 27 46 50
+t 38 227 37 221
+t 44 25 233 211
+t 230 170 231 167
+t 228 66 46 28
+t 232 40 41 222
+t 126 98 118 226
+t 233 84 67 83
+t 185 230 169 196
+t 73 88 226 94
+t 214 7 8 46
+t 232 87 71 68
+t 152 234 235 231
+t 227 38 68 39
+t 71 232 68 55
+t 225 203 201 180
+t 88 109 92 226
+t 144 127 136 224
+t 232 39 68 55
+t 173 225 224 191
+t 84 233 78 107
+t 53 227 34 62
+t 218 223 232 217
+t 139 234 164 145
+t 234 100 123 116
+t 225 192 197 179
+t 71 232 55 76
+t 87 82 226 103
+t 229 72 233 79
+t 227 74 226 85
+t 86 110 233 97
+t 225 201 208 189
+t 224 174 173 191
+t 232 73 62 53
+t 118 117 226 105
+t 150 234 123 116
+t 88 232 226 92
+t 60 227 36 37
+t 113 233 111 89
+t 25 61 48 233
+t 198 230 202 195
+t 29 75 228 67
+t 228 70 75 30
+t 170 230 190 167
+t 198 225 202 230
+t 232 87 68 227
+t 55 40 232 39
+t 109 92 226 112
+t 173 225 191 181
+t 12 42 23 217
+t 97 110 234 124
+t 127 104 226 101
+t 236 165 158 183
+t 227 38 63 68
+t 228 70 30 210
+t 232 227 39 222
+t 86 229 233 66
+t 233 102 90 59
+t 225 207 230 209
+t 152 234 231 124
+t 194 176 201 230
+t 115 234 107 133
+t 229 49 59 26
+t 218 33 32 232
+t 31 218 32 232
+t 67 75 233 83
+t 51 66 78 233
+t 232 92 56 54
+t 110 119 234 124
+t 227 223 222 232
+t 233 102 79 100
+t 209 203 184 230
+t 67 51 233 228
+t 152 150 161 235
+t 231 230 164 235
+t 231 234 235 164
+t 196 185 230 207
+t 228 70 210 233
+t 91 61 233 48
+t 91 233 61 89
+t 47 29 228 67
+t 230 172 194 171
+t 229 72 79 65
+t 82 105 226 103
+t 135 234 231 164
+t 31 232 32 54
+t 229 212 213 45
+t 127 104 134 226
+t 229 216 233 228
+t 66 229 46 50
+t 236 183 180 225
+t 115 84 233 83
+t 86 229 66 50
+t 108 115 234 233
+t 93 75 83 233
+t 146 236 155 224
+t 234 119 133 231
+t 132 99 226 122
+t 122 138 134 224
+t 61 43 57 233
+t 159 236 224 155
+t 74 232 62 227
+t 117 141 224 129
+t 128 109 94 226
+t 121 139 234 111
+t 14 218 32 13
+t 235 164 145 169
+t 230 225 202 200
+t 143 173 156 224
+t 128 146 224 140
+t 157 137 224 168
+t 127 226 125 101
+t 173 225 181 156
+t 126 95 226 120
+t 156 225 181 177
+t 98 85 105 226
+t 227 36 17 35
+t 144 143 125 224
+t 80 232 52 54
+t 86 229 50 72
+t 234 139 113 111
+t 233 100 79 72
+t 106 234 116 102
+t 152 231 171 153
+t 79 229 65 49
+t 110 86 233 78
+t 229 72 65 50
+t 124 119 231 153
+t 86 229 72 233
+t 232 92 54 80
+t 29 47 215 10
+t 150 154 234 116
+t 27 7 6 213
+t 99 132 226 112
+t 232 73 53 88
+t 232 87 227 226
+t 121 106 234 116
+t 60 227 37 63
+t 154 121 234 116
+t 229 66 228 233
+t 232 31 52 54
+t 97 86 72 233
+t 168 236 142 224
+t 144 174 224 136
+t 76 232 77 226
+t 192 225 186 224
+t 98 118 226 105
+t 95 120 94 226
+t 227 220 221 223
+t 211 229 233 44
+t 223 232 217 222
+t 66 86 78 233
+t 108 234 115 131
+t 40 64 232 41
+t 133 234 231 131
+t 234 135 231 131
+t 129 96 125 226
+t 166 225 157 224
+t 132 122 226 224
+t 219 16 35 17
+t 165 236 142 168
+t 188 205 230 200
+t 31 42 232 52
+t 129 226 125 224
+t 172 171 231 153
+t 25 43 61 233
+t 137 126 224 142
+t 163 236 180 178
+t 225 179 204 178
+t 227 82 60 85
+t 33 218 227 232
+t 133 119 147 231
+t 88 232 92 56
+t 130 146 155 224
+t 30 210 24 0
+t 97 110 233 234
+t 164 234 235 145
+t 211 229 44 212
+t 171 235 231 152
+t 232 56 32 54
+t 192 225 197 199
+t 95 73 74 226
+t 104 77 114 226
+t 121 154 234 145
+t 234 108 233 113
+t 225 207 209 197
+t 154 235 145 169
+t 230 196 188 169
+t 77 81 114 226
+t 148 133 231 131
+t 187 198 225 175
+t 117 118 226 224
+t 122 151 138 224
+t 225 177 202 200
+t 225 192 179 224
+t 60 227 58 36
+t 227 219 35 17
+t 138 136 134 224
+t 126 120 226 224
+t 120 126 142 224
+t 159 236 179 224
+t 130 132 226 224
+t 156 225 177 166
+t 225 236 179 178
+t 146 236 224 140
+t 188 230 164 167
+t 42 69 232 52
+t 159 224 162 151
+t 132 130 226 112
+t 186 174 160 224
+t 192 186 160 224
+t 198 225 230 208
+t 218 31 13 217
+t 234 139 135 113
+t 108 234 135 113
+t 162 224 160 138
+t 130 128 226 109
+t 225 183 201 189
+t 190 230 195 200
+t 128 130 226 224
+t 118 137 224 149
+t 224 192 162 160
+t 225 157 187 175
+t 144 143 224 173
+t 225 236 178 180
+t 183 236 189 225
+t 122 132 151 224
+t 225 207 197 199
+t 165 236 168 189
+t 172 230 206 193
+t 225 177 175 202
+t 198 187 225 208
+t 129 117 103 226
+t 187 225 208 189
+t 171 235 152 161
+t 47 29 215 228
+t 30 210 70 24
+t 225 181 177 200
+t 235 176 184 161
+t 232 77 226 81
+t 33 227 219 34
+t 33 218 219 227
+t 194 201 208 230
+t 209 203 230 225
+t 176 230 194 171
+t 235 176 161 171
+t 203 225 201 230
+t 85 82 105 226
+t 165 236 189 183
+t 234 233 100 102
+t 176 203 201 230
+t 164 230 169 235
+t 234 97 100 233
+t 185 154 169 235
+t 190 230 188 167
+t 234 119 107 133
+t 142 236 140 224
+t 120 142 140 224
+t 166 157 149 224
+t 120 128 226 224
+t 128 120 140 224
+t 232 77 81 69
+t 31 12 13 217
+t 232 33 32 56
+t 209 207 230 184
+t 152 150 235 234
+t 234 106 233 102
+t 184 235 185 230
+t 235 176 171 230
+t 232 41 217 222
+t 184 235 161 182
+t 31 42 12 217
+t 40 232 39 222
+t 117 105 103 226
+t 184 235 182 185
+t 92 80 226 112
+t 154 150 234 235
+t 170 230 193 190
+t 190 230 193 195
+t 210 1 24 0
+t 51 47 228 67
+t 43 25 211 233
+t 79 229 49 59
+t 27 229 45 65
+t 232 77 69 64
+t 220 227 219 223
+t 37 220 18 19
+t 225 179 197 204
+t 209 225 197 204
+t 3 25 211 2
+t 75 29 228 30
+t 118 117 149 224
+t 47 9 215 10
+t 162 151 224 138
+t 214 47 215 228
+t 214 9 215 47
+t 159 155 224 151
+t 227 74 85 58
+t 168 225 236 224
+t 216 210 228 215
+t 168 236 225 189
diff --git a/wave-sim/example-meshes/cube.mesh b/wave-sim/example-meshes/cube.mesh
new file mode 100644
index 0000000..c502cca
--- /dev/null
+++ b/wave-sim/example-meshes/cube.mesh
@@ -0,0 +1,13 @@
+v 0.41 0.41 0.41
+v 0.41 0.41 -0.41
+v 0.41 -0.41 0.41
+v 0.41 -0.41 -0.41
+v -0.41 0.41 0.41
+v -0.41 0.41 -0.41
+v -0.41 -0.41 0.41
+v -0.41 -0.41 -0.41
+t 0 1 2 4
+t 5 1 4 7
+t 1 2 4 7
+t 3 1 7 2
+t 6 4 2 7 \ No newline at end of file
diff --git a/wave-sim/example-meshes/ellipsoid.mesh b/wave-sim/example-meshes/ellipsoid.mesh
new file mode 100644
index 0000000..ccaeb64
--- /dev/null
+++ b/wave-sim/example-meshes/ellipsoid.mesh
@@ -0,0 +1,356 @@
+v 2 0 0
+v 1.8622 0.364763 0
+v 1.90476 0.109692 0.284502
+v 1.89798 0.13851 -0.283252
+v 1.70733 0.405905 -0.326339
+v 1.56806 0.620706 -0.00458453
+v 1.86423 -0.286855 0.221077
+v 1.70844 0.399649 0.332553
+v 1.85864 -0.259729 -0.262497
+v 1.6422 0.0389652 -0.569451
+v 1.67515 -0.545212 -0.034815
+v 1.65767 -0.0227703 0.559026
+v 1.51839 -0.453777 -0.466596
+v 1.53257 -0.503798 0.398743
+v 1.3488 0.509363 -0.534542
+v 1.33664 0.499263 0.551437
+v 1.32076 -0.750223 -0.0326346
+v 1.25029 -0.0758834 -0.77681
+v 1.1123 -0.682512 0.474209
+v 1.18175 0.805857 -0.0382601
+v 1.28982 -0.198385 0.738063
+v 0.934783 0.323178 -0.822862
+v 0.9148 0.752901 -0.473208
+v 0.716338 0.933601 -0.0101755
+v 0.958231 0.794623 0.372857
+v 1.02058 0.153374 0.846214
+v 0.893169 0.555302 0.701571
+v 0.592525 0.27368 0.915056
+v 0.801135 -0.332255 0.853904
+v 0.782809 -0.188742 -0.900655
+v 0.887386 -0.896138 -0.00849572
+v 1.03515 -0.641124 -0.566635
+v 0.457538 0.879735 -0.41681
+v 0.497142 0.789243 0.561523
+v 0.580771 -0.592188 -0.751658
+v 0.441603 0.20614 -0.953285
+v 0.60359 0.595006 -0.744908
+v 0.654788 -0.823042 0.464129
+v 0.622546 -0.85669 -0.411328
+v 0.25648 0.989514 0.0664565
+v 0.195413 0.61505 -0.782411
+v 0.387942 -0.589907 0.783827
+v 0.00377508 0.849318 0.527878
+v 0.146849 -0.80047 -0.594859
+v 0.263491 -0.291947 -0.947317
+v 0.143624 0.455977 0.88709
+v 0.163574 -0.890302 0.447965
+v 0.307473 -0.132835 0.979142
+v 0.387532 -0.98092 -0.0158159
+v -0.00388249 0.916253 -0.400595
+v -0.142943 0.174759 -0.982014
+v -0.229009 0.9904 0.0774367
+v -0.142136 -0.997328 -0.0169042
+v -0.211676 -0.429832 -0.896684
+v -0.35023 0.544347 0.820379
+v -0.23461 0.0247359 0.992788
+v -0.0633408 -0.530841 0.84688
+v -0.226895 0.675713 -0.728383
+v -0.432276 0.85775 0.466422
+v -0.490768 0.885776 -0.393937
+v -0.475184 -0.790551 -0.564428
+v -0.363352 -0.854004 0.487514
+v -0.723994 0.925315 0.112915
+v -0.562778 -0.432922 0.856387
+v -0.750392 0.160185 0.913
+v -0.82493 0.669331 0.617956
+v -0.620529 0.513974 -0.799729
+v -0.962062 0.805721 -0.345577
+v -1.09212 -0.320866 0.773862
+v -0.828768 -0.73993 0.529896
+v -1.08393 0.422659 -0.726382
+v -0.705619 -0.934894 0.038714
+v -0.991258 -0.75926 -0.421755
+v -1.45272 0.607768 -0.320961
+v -1.19677 0.776252 0.198417
+v -1.24768 0.30989 0.71749
+v -1.27736 -0.733504 0.232514
+v -0.801309 -0.169375 -0.900438
+v -1.59976 -0.164428 0.577194
+v -1.41995 0.106379 -0.696148
+v -1.27931 -0.391644 -0.661406
+v -1.44699 -0.628098 -0.286446
+v -1.68081 0.464589 0.279057
+v -1.86068 0.220298 -0.293154
+v -1.6651 -0.287101 -0.473744
+v -1.78848 -0.445517 0.0429575
+v -1.92628 0.0409837 0.265851
+v 1.22204 0.0647182 0.0397057
+v 0.356939 0.00279809 -0.011249
+v -1.33979 -0.0527357 0.0294218
+v -0.675485 0.268836 -0.418091
+v -1.04526 0.017402 -0.421514
+v -0.885885 -0.0602097 0.501551
+v -0.871399 -0.341398 -0.146011
+v -0.831352 0.27626 0.0874527
+v -0.274087 0.399075 0.313192
+v -0.325662 -0.271529 -0.203298
+v -0.481544 -0.0796356 0.455227
+v -1.12071 0.371211 -0.276968
+v -0.259498 0.42249 -0.294953
+t 87 11 20 13
+t 14 21 17 87
+t 31 87 16 12
+t 87 9 8 12
+t 6 87 2 0
+t 50 90 96 99
+t 89 93 92 69
+t 87 22 23 19
+t 38 88 48 30
+t 68 76 78 89
+t 91 79 98 70
+t 96 93 90 77
+t 91 79 89 98
+t 32 88 39 49
+t 87 6 8 0
+t 89 79 84 83
+t 98 79 73 70
+t 33 87 23 24
+t 33 88 23 87
+t 94 93 90 96
+t 11 7 87 15
+t 14 87 4 5
+t 99 40 88 50
+t 99 40 50 57
+t 87 14 4 9
+t 50 90 99 66
+t 66 90 67 70
+t 54 97 65 95
+t 96 95 88 99
+t 55 47 88 45
+t 96 95 99 94
+t 50 90 66 77
+t 76 69 71 93
+t 88 22 32 23
+t 98 90 70 67
+t 46 88 48 52
+t 38 43 48 88
+t 54 97 55 64
+t 87 5 1 4
+t 61 96 97 88
+t 87 25 20 15
+t 99 94 90 96
+t 87 18 16 13
+t 54 97 95 55
+t 90 59 99 66
+t 87 33 27 26
+t 87 7 5 15
+t 88 44 50 35
+t 87 2 0 1
+t 11 87 20 15
+t 10 87 16 13
+t 94 91 90 93
+t 88 22 23 87
+t 92 93 89 94
+t 88 40 32 36
+t 88 36 21 35
+t 75 94 65 92
+t 65 62 94 74
+t 69 96 71 93
+t 99 95 88 51
+t 88 51 39 49
+t 94 91 93 89
+t 65 97 92 94
+t 55 97 95 88
+t 99 50 88 96
+t 33 88 45 42
+t 18 87 20 13
+t 87 25 28 20
+t 77 80 91 79
+t 18 87 28 20
+t 96 95 94 97
+t 31 29 88 87
+t 93 60 96 71
+t 44 43 34 88
+t 54 58 95 65
+t 88 29 21 87
+t 68 89 78 75
+t 10 87 8 12
+t 92 65 75 64
+t 18 87 16 30
+t 90 59 66 67
+t 5 87 15 19
+t 87 10 16 12
+t 14 22 21 87
+t 43 38 34 88
+t 55 97 63 64
+t 89 85 76 78
+t 55 95 45 88
+t 55 47 56 88
+t 93 60 71 72
+t 96 44 50 88
+t 27 47 45 88
+t 96 95 97 88
+t 87 24 15 19
+t 21 29 17 87
+t 56 47 41 88
+t 60 96 53 77
+t 61 97 96 69
+t 33 87 27 88
+t 93 81 76 89
+t 92 93 97 69
+t 22 88 21 87
+t 99 95 51 62
+t 62 58 65 95
+t 53 96 50 77
+t 89 73 98 79
+t 33 88 42 39
+t 89 83 86 82
+t 90 59 67 62
+t 90 98 94 67
+t 93 81 89 80
+t 92 63 97 64
+t 96 44 88 43
+t 82 89 73 74
+t 94 91 89 98
+t 50 90 77 96
+t 71 96 52 60
+t 54 97 64 65
+t 94 98 74 67
+t 77 91 70 79
+t 91 89 79 80
+t 46 61 56 88
+t 61 96 88 52
+t 66 99 57 59
+t 87 17 9 12
+t 90 62 67 94
+t 52 96 43 60
+t 99 50 66 57
+t 93 80 89 91
+t 95 51 62 58
+t 3 87 8 0
+t 24 87 23 19
+t 88 22 21 36
+t 3 87 4 9
+t 57 99 49 59
+t 97 93 92 94
+t 80 89 84 81
+t 98 90 91 70
+t 22 88 32 36
+t 88 33 23 39
+t 17 87 31 12
+t 99 40 57 49
+t 28 88 37 41
+t 33 27 45 88
+t 49 99 51 59
+t 94 62 67 74
+t 43 96 52 88
+t 51 99 49 88
+t 3 87 9 8
+t 87 10 8 6
+t 33 87 24 26
+t 51 99 62 59
+t 65 97 64 92
+t 31 38 88 34
+t 93 81 80 72
+t 25 87 28 27
+t 74 98 94 89
+t 24 87 15 26
+t 31 88 29 34
+t 99 95 62 94
+t 14 87 5 19
+t 47 28 88 27
+t 90 59 62 99
+t 14 22 87 19
+t 28 87 88 27
+t 87 25 15 26
+t 38 31 88 30
+t 31 87 88 30
+t 25 87 27 26
+t 44 88 34 29
+t 10 87 13 6
+t 73 98 74 89
+t 87 11 13 6
+t 31 87 30 16
+t 87 14 9 17
+t 96 44 43 53
+t 99 40 49 88
+t 40 88 32 49
+t 43 96 53 60
+t 46 56 41 88
+t 75 94 92 89
+t 89 69 68 76
+t 18 88 87 30
+t 47 28 41 88
+t 18 88 30 37
+t 32 88 23 39
+t 80 89 79 84
+t 96 44 53 50
+t 55 97 56 63
+t 82 89 78 86
+t 88 51 42 39
+t 95 51 58 42
+t 18 88 37 28
+t 88 44 35 29
+t 40 88 35 36
+t 40 88 50 35
+t 93 60 72 77
+t 18 88 28 87
+t 29 88 21 35
+t 61 96 52 71
+t 29 87 31 17
+t 61 46 52 88
+t 92 63 64 68
+t 97 95 94 65
+t 56 97 55 88
+t 56 97 61 63
+t 30 88 48 37
+t 92 63 68 69
+t 46 88 41 37
+t 89 82 78 75
+t 97 93 96 69
+t 88 46 48 37
+t 75 94 89 74
+t 87 7 1 5
+t 3 87 1 4
+t 86 89 78 85
+t 67 98 73 70
+t 87 7 2 1
+t 3 87 0 1
+t 92 89 68 75
+t 11 87 2 6
+t 11 7 2 87
+t 74 98 73 67
+t 88 43 48 52
+t 61 97 56 88
+t 89 83 85 86
+t 89 69 92 68
+t 89 85 81 76
+t 69 89 93 76
+t 93 80 91 77
+t 61 97 69 63
+t 82 89 74 75
+t 93 60 77 96
+t 66 77 90 70
+t 93 80 77 72
+t 94 95 62 65
+t 99 94 62 90
+t 91 77 70 90
+t 58 95 42 54
+t 95 51 42 88
+t 84 85 89 83
+t 92 63 69 97
+t 89 83 82 73
+t 76 93 71 72
+t 94 91 98 90
+t 89 83 73 79
+t 84 89 85 81
+t 61 96 71 69
+t 42 95 45 54
+t 93 81 72 76
+t 45 95 42 88
+t 45 95 55 54
+t 93 91 90 77
+t 75 94 74 65
+t 68 92 75 64
+t 96 93 97 94
diff --git a/wave-sim/example-meshes/single-tet.mesh b/wave-sim/example-meshes/single-tet.mesh
new file mode 100644
index 0000000..5f62dbe
--- /dev/null
+++ b/wave-sim/example-meshes/single-tet.mesh
@@ -0,0 +1,5 @@
+v 0 0.82 0
+v -0.5 0 -0.3
+v 0.5 0 -0.3
+v 0 0 0.6
+t 0 1 2 3
diff --git a/wave-sim/example-meshes/sphere.mesh b/wave-sim/example-meshes/sphere.mesh
new file mode 100644
index 0000000..cbd62f2
--- /dev/null
+++ b/wave-sim/example-meshes/sphere.mesh
@@ -0,0 +1,177 @@
+v 0 0 0
+v 1 0 0
+v 0.894427 0.447214 0
+v 0.924658 0.194311 0.327491
+v 0.888925 0.218673 -0.402486
+v 0.736112 0.599077 -0.315032
+v 0.61923 0.784348 0.0367659
+v 0.894236 -0.246334 0.373714
+v 0.707486 0.551874 0.441473
+v 0.880107 -0.233582 -0.413341
+v 0.916168 -0.400795 -0.000350195
+v 0.665458 0.00685229 -0.746404
+v 0.573332 0.457656 -0.67959
+v 0.409525 0.821214 -0.397361
+v 0.712366 0.096514 0.69514
+v 0.240784 0.970252 -0.0251814
+v 0.579009 -0.45162 -0.678813
+v 0.605982 -0.414577 0.678905
+v 0.302433 0.857675 0.415846
+v 0.691098 -0.679646 0.245896
+v 0.349309 0.496986 0.794347
+v 0.696233 -0.674001 -0.246945
+v -0.035932 0.933391 -0.357058
+v 0.338418 -0.215782 -0.915921
+v 0.153241 0.660177 -0.735312
+v 0.277186 0.211488 -0.937252
+v 0.322982 -0.0552575 0.944791
+v 0.21729 -0.511964 0.83107
+v 0.346876 -0.80694 -0.478043
+v -0.158833 0.977716 0.13727
+v 0.291174 -0.826802 0.481264
+v 0.124357 -0.591609 -0.796576
+v -0.258388 0.724289 -0.639251
+v 0.341866 -0.939287 -0.0294584
+v -0.0766425 0.289742 0.954031
+v -0.487165 0.834224 -0.258342
+v -0.133259 0.761769 0.633996
+v -0.0891821 -0.170641 -0.981289
+v -0.11031 -0.257341 0.960004
+v -0.0664263 -0.916707 -0.394001
+v -0.602127 0.760145 0.244175
+v -0.152591 -0.726537 0.66997
+v -0.207822 0.340429 -0.917016
+v -0.451122 0.0276906 0.892033
+v -0.843022 0.507538 -0.1781
+v -0.565787 0.456045 0.686955
+v -0.379144 -0.577531 -0.722985
+v -0.122236 -0.977225 0.173462
+v -0.610673 0.482569 -0.627859
+v -0.553685 -0.0419533 -0.831669
+v -0.514331 -0.410633 0.752891
+v -0.89534 0.352752 0.271905
+v -0.555226 -0.801687 -0.22141
+v -0.562323 -0.758795 0.328668
+v -0.776413 -0.000904782 0.630224
+v -0.811658 -0.354539 -0.464234
+v -0.85066 0.112984 -0.513432
+v -0.805824 -0.412069 0.42526
+v -0.990134 0.00316965 -0.140088
+v -0.873805 -0.486261 0.00385952
+v -0.960943 -0.111836 0.253144
+t 21 10 9 0
+t 47 33 39 0
+t 21 33 19 0
+t 2 8 6 0
+t 48 32 35 0
+t 5 2 6 0
+t 30 47 41 0
+t 28 31 39 0
+t 42 37 25 0
+t 36 20 34 0
+t 25 37 23 0
+t 49 48 56 0
+t 55 59 52 0
+t 14 7 17 0
+t 10 21 19 0
+t 35 22 29 0
+t 11 16 9 0
+t 4 11 9 0
+t 10 7 1 0
+t 15 22 13 0
+t 44 48 35 0
+t 14 3 7 0
+t 31 46 39 0
+t 14 20 8 0
+t 29 36 40 0
+t 44 35 40 0
+t 27 26 17 0
+t 43 38 50 0
+t 38 27 41 0
+t 16 31 28 0
+t 51 40 45 0
+t 4 9 1 0
+t 3 2 1 0
+t 10 19 7 0
+t 30 27 17 0
+t 54 51 45 0
+t 41 53 50 0
+t 32 22 35 0
+t 31 37 46 0
+t 54 60 51 0
+t 32 48 42 0
+t 3 8 2 0
+t 22 32 24 0
+t 36 34 45 0
+t 37 31 23 0
+t 7 3 1 0
+t 33 21 28 0
+t 16 21 9 0
+t 19 30 17 0
+t 18 36 29 0
+t 49 37 42 0
+t 34 43 45 0
+t 26 20 14 0
+t 52 47 39 0
+t 20 26 34 0
+t 33 28 39 0
+t 11 4 12 0
+t 53 57 50 0
+t 35 29 40 0
+t 38 43 34 0
+t 13 24 12 0
+t 24 25 12 0
+t 37 49 46 0
+t 44 40 51 0
+t 20 36 18 0
+t 25 11 12 0
+t 49 55 46 0
+t 55 49 56 0
+t 26 14 17 0
+t 3 14 8 0
+t 48 44 56 0
+t 8 18 6 0
+t 5 4 2 0
+t 15 18 29 0
+t 2 4 1 0
+t 53 59 57 0
+t 38 41 50 0
+t 32 42 24 0
+t 44 58 56 0
+t 5 13 12 0
+t 33 47 30 0
+t 58 44 51 0
+t 7 19 17 0
+t 40 36 45 0
+t 13 22 24 0
+t 43 54 45 0
+t 42 25 24 0
+t 59 53 52 0
+t 47 53 41 0
+t 60 54 57 0
+t 31 16 23 0
+t 58 55 56 0
+t 53 47 52 0
+t 26 38 34 0
+t 11 25 23 0
+t 48 49 42 0
+t 16 11 23 0
+t 54 43 50 0
+t 22 15 29 0
+t 59 60 57 0
+t 13 5 6 0
+t 27 38 26 0
+t 8 20 18 0
+t 15 13 6 0
+t 46 52 39 0
+t 57 54 50 0
+t 9 10 1 0
+t 55 52 46 0
+t 19 33 30 0
+t 27 30 41 0
+t 59 55 58 0
+t 4 5 12 0
+t 60 58 51 0
+t 60 59 58 0
+t 21 16 28 0
+t 18 15 6 0
diff --git a/wave-sim/example-video/cube.gif b/wave-sim/example-video/cube.gif
new file mode 100644
index 0000000..e0dacca
--- /dev/null
+++ b/wave-sim/example-video/cube.gif
Binary files differ
diff --git a/wave-sim/example-video/ellipsoid.gif b/wave-sim/example-video/ellipsoid.gif
new file mode 100644
index 0000000..2563e1d
--- /dev/null
+++ b/wave-sim/example-video/ellipsoid.gif
Binary files differ
diff --git a/wave-sim/example-video/tet.gif b/wave-sim/example-video/tet.gif
new file mode 100644
index 0000000..1fb8f1a
--- /dev/null
+++ b/wave-sim/example-video/tet.gif
Binary files differ
diff --git a/wave-sim/resources/shaders/shader.frag b/wave-sim/resources/shaders/shader.frag
new file mode 100755
index 0000000..e4cf718
--- /dev/null
+++ b/wave-sim/resources/shaders/shader.frag
@@ -0,0 +1,43 @@
+#version 330 core
+out vec4 fragColor;
+
+// Additional information for lighting
+in vec4 normal_worldSpace;
+in vec4 position_worldSpace;
+in vec4 force_worldSpace;
+
+uniform int wire = 0;
+uniform float red = 1.0;
+uniform float green = 1.0;
+uniform float blue = 1.0;
+uniform float alpha = 1.0;
+uniform int displayForce = 0;
+
+void main() {
+ if (wire == 1) {
+ fragColor = vec4(0.0, 0.0, 0.0, 1);
+ return;
+ }
+ vec4 lightPos = vec4(-2.0, 2.0, -3.0 , 1.0);
+ vec3 lightColor = vec3(1.0f, alpha, 0.0f);
+ vec4 lightDir = normalize(-lightPos + position_worldSpace);
+ float c = clamp(dot(-normal_worldSpace, lightDir), 0, 1);
+
+ if(displayForce == 1) {
+ fragColor = force_worldSpace;
+ return;
+ }
+
+ float r = red;
+ float g = green;
+ float b = blue;
+
+// if (displayForce == 1 && wire == 0) {
+// r = force_worldSpace[0];
+// g = force_worldSpace[1];
+// b = force_worldSpace[2];
+// }
+
+ fragColor = vec4(r * c * lightColor[0], g * c * lightColor[0], b * c * lightColor[0], 1);
+
+}
diff --git a/wave-sim/resources/shaders/shader.vert b/wave-sim/resources/shaders/shader.vert
new file mode 100755
index 0000000..6f15a59
--- /dev/null
+++ b/wave-sim/resources/shaders/shader.vert
@@ -0,0 +1,24 @@
+#version 330 core
+
+layout(location = 0) in vec3 position; // Position of the vertex
+layout(location = 1) in vec3 normal; // Normal of the vertex
+layout(location = 2) in vec3 force;
+
+uniform mat4 proj;
+uniform mat4 view;
+uniform mat4 model;
+
+uniform mat3 inverseTransposeModel;
+
+out vec4 normal_worldSpace;
+out vec4 position_worldSpace;
+out vec4 force_worldSpace;
+
+void main() {
+ normal_worldSpace = vec4(normalize(inverseTransposeModel * normal), 0);
+ position_worldSpace = vec4(position, 1.0);
+ force_worldSpace = vec4(force, 1.0);
+// force_worldSpace = vec4(1.0, 0.0, 0.0, 1.0);
+
+ gl_Position = proj * view * model * vec4(position, 1.0);
+}
diff --git a/wave-sim/resources/shaders/shaders.qrc b/wave-sim/resources/shaders/shaders.qrc
new file mode 100755
index 0000000..45dc189
--- /dev/null
+++ b/wave-sim/resources/shaders/shaders.qrc
@@ -0,0 +1,6 @@
+<RCC>
+ <qresource prefix="/shaders">
+ <file>shader.frag</file>
+ <file>shader.vert</file>
+ </qresource>
+</RCC>
diff --git a/wave-sim/src/glwidget.cpp b/wave-sim/src/glwidget.cpp
new file mode 100755
index 0000000..ce8af46
--- /dev/null
+++ b/wave-sim/src/glwidget.cpp
@@ -0,0 +1,194 @@
+#include "glwidget.h"
+
+#include <QApplication>
+#include <QKeyEvent>
+#include <iostream>
+
+#define SPEED 1.5
+#define ROTATE_SPEED 0.0025
+
+using namespace std;
+
+GLWidget::GLWidget(QWidget *parent) :
+ QOpenGLWidget(parent),
+ m_deltaTimeProvider(),
+ m_intervalTimer(),
+ m_sim(),
+ m_camera(),
+ m_shader(),
+ m_forward(),
+ m_sideways(),
+ m_vertical(),
+ m_lastX(),
+ m_lastY(),
+ m_capture(false)
+{
+ // GLWidget needs all mouse move events, not just mouse drag events
+ setMouseTracking(true);
+
+ // Hide the cursor since this is a fullscreen app
+ QApplication::setOverrideCursor(Qt::ArrowCursor);
+
+ // GLWidget needs keyboard focus
+ setFocusPolicy(Qt::StrongFocus);
+
+ // Function tick() will be called once per interva
+ connect(&m_intervalTimer, SIGNAL(timeout()), this, SLOT(tick()));
+}
+
+GLWidget::~GLWidget()
+{
+ if (m_shader != nullptr) delete m_shader;
+}
+
+// ================== Basic OpenGL Overrides
+
+void GLWidget::initializeGL()
+{
+ // Initialize GL extension wrangler
+ glewExperimental = GL_TRUE;
+ GLenum err = glewInit();
+ if (err != GLEW_OK) fprintf(stderr, "Error while initializing GLEW: %s\n", glewGetErrorString(err));
+ fprintf(stdout, "Successfully initialized GLEW %s\n", glewGetString(GLEW_VERSION));
+
+ // Set clear color to white
+ glClearColor(1, 1, 1, 1);
+
+ // Enable depth-testing and backface culling
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_CULL_FACE);
+ glCullFace(GL_BACK);
+
+ // Initialize the shader and simulation
+ m_shader = new Shader(":/resources/shaders/shader.vert", ":/resources/shaders/shader.frag");
+ m_sim.init();
+
+ // Initialize camera with a reasonable transform
+ Eigen::Vector3f eye = {0, 2, -5};
+ Eigen::Vector3f target = {0, 1, 0};
+ m_camera.lookAt(eye, target);
+ m_camera.setOrbitPoint(target);
+ m_camera.setPerspective(120, width() / static_cast<float>(height()), 0.1, 50);
+
+ m_deltaTimeProvider.start();
+// m_intervalTimer.start(1);
+// m_intervalTimer.start(1000 / 60);
+ m_intervalTimer.start(1000 / 120);
+}
+
+void GLWidget::paintGL()
+{
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ m_shader->bind();
+ m_shader->setUniform("proj", m_camera.getProjection());
+ m_shader->setUniform("view", m_camera.getView());
+ m_sim.draw(m_shader);
+ m_shader->unbind();
+}
+
+void GLWidget::resizeGL(int w, int h)
+{
+ glViewport(0, 0, w, h);
+ m_camera.setAspect(static_cast<float>(w) / h);
+}
+
+// ================== Event Listeners
+
+void GLWidget::mousePressEvent(QMouseEvent *event)
+{
+ m_capture = true;
+ m_lastX = event->position().x();
+ m_lastY = event->position().y();
+}
+
+void GLWidget::mouseMoveEvent(QMouseEvent *event)
+{
+ if (!m_capture) return;
+
+ int currX = event->position().x();
+ int currY = event->position().y();
+
+ int deltaX = currX - m_lastX;
+ int deltaY = currY - m_lastY;
+
+ if (deltaX == 0 && deltaY == 0) return;
+
+ m_camera.rotate(deltaY * ROTATE_SPEED,
+ -deltaX * ROTATE_SPEED);
+
+ m_lastX = currX;
+ m_lastY = currY;
+}
+
+void GLWidget::mouseReleaseEvent(QMouseEvent *event)
+{
+ m_capture = false;
+}
+
+void GLWidget::wheelEvent(QWheelEvent *event)
+{
+ float zoom = 1 - event->pixelDelta().y() * 0.1f / 120.f;
+ m_camera.zoom(zoom);
+}
+
+void GLWidget::keyPressEvent(QKeyEvent *event)
+{
+ if (event->isAutoRepeat()) return;
+
+ switch (event->key())
+ {
+ case Qt::Key_W: m_forward += SPEED; break;
+ case Qt::Key_S: m_forward -= SPEED; break;
+ case Qt::Key_A: m_sideways -= SPEED; break;
+ case Qt::Key_D: m_sideways += SPEED; break;
+ case Qt::Key_F: m_vertical -= SPEED; break;
+ case Qt::Key_R: m_vertical += SPEED; break;
+ case Qt::Key_C: m_camera.toggleIsOrbiting(); break;
+ case Qt::Key_T: m_sim.toggleWire(); break;
+ case Qt::Key_O: m_sim.toggleForceRender(); break;
+ case Qt::Key_Escape: QApplication::quit();
+ }
+}
+
+void GLWidget::keyReleaseEvent(QKeyEvent *event)
+{
+ if (event->isAutoRepeat()) return;
+
+ switch (event->key())
+ {
+ case Qt::Key_W: m_forward -= SPEED; break;
+ case Qt::Key_S: m_forward += SPEED; break;
+ case Qt::Key_A: m_sideways += SPEED; break;
+ case Qt::Key_D: m_sideways -= SPEED; break;
+ case Qt::Key_F: m_vertical += SPEED; break;
+ case Qt::Key_R: m_vertical -= SPEED; break;
+ }
+}
+
+// ================== Physics Tick
+
+
+void GLWidget::tick()
+{
+ float deltaSeconds = m_deltaTimeProvider.restart() / 1000.f;
+// deltaSeconds = 0.5f;
+// std::cout << deltaSeconds << std::endl;
+ // .02 is optimal, .021 disappears after 4 for sphere
+ // .015 okay for ellipsoid
+ // .001 okay for cone
+ deltaSeconds = .01;
+ deltaSeconds = .014;
+ m_sim.update(deltaSeconds);
+
+ // Move camera
+ auto look = m_camera.getLook();
+ look.y() = 0;
+ look.normalize();
+ Eigen::Vector3f perp(-look.z(), 0, look.x());
+ Eigen::Vector3f moveVec = m_forward * look.normalized() + m_sideways * perp.normalized() + m_vertical * Eigen::Vector3f::UnitY();
+ moveVec *= deltaSeconds;
+ m_camera.move(moveVec);
+
+ // Flag this view for repainting (Qt will call paintGL() soon after)
+ update();
+}
diff --git a/wave-sim/src/glwidget.h b/wave-sim/src/glwidget.h
new file mode 100755
index 0000000..d108ccc
--- /dev/null
+++ b/wave-sim/src/glwidget.h
@@ -0,0 +1,62 @@
+#pragma once
+
+#ifdef __APPLE__
+#define GL_SILENCE_DEPRECATION
+#endif
+
+#include "simulation.h"
+#include "graphics/camera.h"
+#include "graphics/shader.h"
+
+#include <QOpenGLWidget>
+#include <QElapsedTimer>
+#include <QTimer>
+#include <memory>
+
+class GLWidget : public QOpenGLWidget
+{
+ Q_OBJECT
+
+public:
+ GLWidget(QWidget *parent = nullptr);
+ ~GLWidget();
+
+private:
+ static const int FRAMES_TO_AVERAGE = 30;
+
+private:
+ // Basic OpenGL Overrides
+ void initializeGL() override;
+ void paintGL() override;
+ void resizeGL(int w, int h) override;
+
+ // Event Listeners
+ void mousePressEvent (QMouseEvent *event) override;
+ void mouseMoveEvent (QMouseEvent *event) override;
+ void mouseReleaseEvent(QMouseEvent *event) override;
+ void wheelEvent (QWheelEvent *event) override;
+ void keyPressEvent (QKeyEvent *event) override;
+ void keyReleaseEvent (QKeyEvent *event) override;
+
+private:
+ QElapsedTimer m_deltaTimeProvider; // For measuring elapsed time
+ QTimer m_intervalTimer; // For triggering timed events
+
+ Simulation m_sim;
+ Camera m_camera;
+ Shader *m_shader;
+
+ int m_forward;
+ int m_sideways;
+ int m_vertical;
+
+ int m_lastX;
+ int m_lastY;
+
+ bool m_capture;
+
+private slots:
+
+ // Physics Tick
+ void tick();
+};
diff --git a/wave-sim/src/graphics/camera.cpp b/wave-sim/src/graphics/camera.cpp
new file mode 100644
index 0000000..85fc7d9
--- /dev/null
+++ b/wave-sim/src/graphics/camera.cpp
@@ -0,0 +1,188 @@
+#include "graphics/camera.h"
+
+#include <iostream>
+
+Camera::Camera()
+ : m_position(0,0,0),
+ m_pitch(0), m_yaw(0),
+ m_look(0, 0, 1),
+ m_orbitPoint(0, 0, 0),
+ m_isOrbiting(false),
+ m_view(Eigen::Matrix4f::Identity()),
+ m_proj(Eigen::Matrix4f::Identity()),
+ m_viewDirty(true),
+ m_projDirty(true),
+ m_fovY(90), m_aspect(1), m_near(0.1f), m_far(50.f),
+ m_zoom(1)
+{}
+
+// ================== Position
+
+void Camera::setPosition(const Eigen::Vector3f &position)
+{
+ m_position = position;
+ m_viewDirty = true;
+}
+
+void Camera::move(const Eigen::Vector3f &deltaPosition)
+{
+ if (deltaPosition.squaredNorm() == 0) return;
+
+ m_position += deltaPosition;
+
+ if (m_isOrbiting) {
+ m_orbitPoint += deltaPosition;
+ }
+
+ m_viewDirty = true;
+
+}
+
+// ================== Rotation
+
+void Camera::setRotation(float pitch, float yaw)
+{
+ m_pitch = pitch;
+ m_yaw = yaw;
+ m_viewDirty = true;
+ updateLook();
+}
+
+void Camera::rotate(float deltaPitch, float deltaYaw)
+{
+ m_pitch += deltaPitch;
+ m_yaw += deltaYaw;
+ m_pitch = std::clamp(m_pitch, (float) -M_PI_2 + 0.01f, (float) M_PI_2 - 0.01f);
+ m_viewDirty = true;
+ updateLook();
+
+ if (m_isOrbiting) {
+ m_position = m_orbitPoint - m_look * m_zoom;
+ }
+}
+
+// ================== Position and Rotation
+
+void Camera::lookAt(const Eigen::Vector3f &eye, const Eigen::Vector3f &target)
+{
+ m_position = eye;
+ m_look = (target - eye).normalized();
+ m_viewDirty = true;
+ updatePitchAndYaw();
+}
+
+// ================== Orbiting
+
+void Camera::setOrbitPoint(const Eigen::Vector3f &orbitPoint)
+{
+ m_orbitPoint = orbitPoint;
+ m_viewDirty = true;
+}
+
+bool Camera::getIsOrbiting()
+{
+ return m_isOrbiting;
+}
+
+void Camera::setIsOrbiting(bool isOrbiting)
+{
+ m_isOrbiting = isOrbiting;
+ m_viewDirty = true;
+}
+
+void Camera::toggleIsOrbiting()
+{
+ m_isOrbiting = !m_isOrbiting;
+ m_viewDirty = true;
+
+ if (m_isOrbiting) {
+ m_zoom = (m_orbitPoint - m_position).norm();
+ m_look = (m_orbitPoint - m_position).normalized();
+ updatePitchAndYaw();
+ }
+}
+
+void Camera::zoom(float zoomMultiplier)
+{
+ if (!m_isOrbiting) return;
+
+ m_zoom *= zoomMultiplier;
+ m_position = m_orbitPoint - m_look * m_zoom;
+ m_viewDirty = true;
+}
+
+// ================== Intrinsics
+
+void Camera::setPerspective(float fovY, float aspect, float near, float far)
+{
+ m_fovY = fovY;
+ m_aspect = aspect;
+ m_near = near;
+ m_far = far;
+ m_projDirty = true;
+}
+
+void Camera::setAspect(float aspect)
+{
+ m_aspect = aspect;
+ m_projDirty = true;
+}
+
+// ================== Important Getters
+
+const Eigen::Matrix4f &Camera::getView()
+{
+ if (m_viewDirty) {
+ Eigen::Matrix3f R;
+ Eigen::Vector3f f = m_look.normalized();
+ Eigen::Vector3f u = Eigen::Vector3f::UnitY();
+ Eigen::Vector3f s = f.cross(u).normalized();
+ u = s.cross(f);
+ R.col(0) = s;
+ R.col(1) = u;
+ R.col(2) = -f;
+ m_view.topLeftCorner<3, 3>() = R.transpose();
+ m_view.topRightCorner<3, 1>() = -R.transpose() * m_position;
+ m_view(3, 3) = 1.f;
+ m_viewDirty = false;
+ }
+ return m_view;
+}
+
+const Eigen::Matrix4f &Camera::getProjection()
+{
+ if(m_projDirty) {
+ float theta = m_fovY * 0.5f;
+ float invRange = 1.f / (m_far - m_near);
+ float invtan = 1.f / tanf(theta);
+ m_proj(0, 0) = invtan / m_aspect;
+ m_proj(1, 1) = invtan;
+ m_proj(2, 2) = -(m_near + m_far) * invRange;
+ m_proj(3, 2) = -1;
+ m_proj(2, 3) = -2 * m_near * m_far * invRange;
+ m_proj(3, 3) = 0;
+ m_projDirty = false;
+ }
+ return m_proj;
+}
+
+const Eigen::Vector3f &Camera::getLook()
+{
+ return m_look;
+}
+
+// ================== Private Helpers
+
+void Camera::updateLook()
+{
+ m_look = Eigen::Vector3f(0, 0, 1);
+ m_look = Eigen::AngleAxis<float>(m_pitch, Eigen::Vector3f::UnitX()) * m_look;
+ m_look = Eigen::AngleAxis<float>(m_yaw, Eigen::Vector3f::UnitY()) * m_look;
+ m_look = m_look.normalized();
+}
+
+void Camera::updatePitchAndYaw()
+{
+ m_pitch = asinf(-m_look.y());
+ m_yaw = atan2f(m_look.x(), m_look.z());
+}
diff --git a/wave-sim/src/graphics/camera.h b/wave-sim/src/graphics/camera.h
new file mode 100644
index 0000000..04586af
--- /dev/null
+++ b/wave-sim/src/graphics/camera.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "Eigen/Dense"
+
+class Camera
+{
+public:
+ EIGEN_MAKE_ALIGNED_OPERATOR_NEW
+ Camera();
+
+ void setPosition(const Eigen::Vector3f &position);
+ void move (const Eigen::Vector3f &deltaPosition);
+
+ void setRotation(float pitch, float yaw);
+ void rotate (float deltaPitch, float deltaYaw);
+
+ void lookAt(const Eigen::Vector3f &eye, const Eigen::Vector3f &target);
+
+ void setOrbitPoint(const Eigen::Vector3f &target);
+ bool getIsOrbiting();
+ void setIsOrbiting(bool orbit);
+ void toggleIsOrbiting();
+ void zoom(float zoomMultiplier);
+
+ const Eigen::Matrix4f &getView();
+ const Eigen::Matrix4f &getProjection();
+ const Eigen::Vector3f &getLook();
+
+ void setPerspective(float fovY, float aspect, float near, float far);
+ void setAspect(float aspect);
+
+private:
+ void updateLook();
+ void updatePitchAndYaw();
+
+ // Do not mess with the order of these variables. Some Eigen voodoo will cause an inexplicable crash.
+
+ Eigen::Vector3f m_position;
+
+ float m_pitch, m_yaw;
+ Eigen::Vector3f m_look;
+
+ Eigen::Vector3f m_orbitPoint;
+ bool m_isOrbiting;
+
+ Eigen::Matrix4f m_view, m_proj;
+ bool m_viewDirty, m_projDirty;
+
+ float m_fovY, m_aspect, m_near, m_far;
+ float m_zoom;
+};
diff --git a/wave-sim/src/graphics/graphicsdebug.cpp b/wave-sim/src/graphics/graphicsdebug.cpp
new file mode 100644
index 0000000..b9d831c
--- /dev/null
+++ b/wave-sim/src/graphics/graphicsdebug.cpp
@@ -0,0 +1,126 @@
+#include <GL/glew.h>
+#include "graphics/graphicsdebug.h"
+
+#include <iostream>
+#include <vector>
+
+
+void checkError(std::string prefix) {
+ GLenum err = glGetError();
+ if (err != GL_NO_ERROR) {
+ std::cerr << prefix << (prefix == std::string("") ? "" : ": ") << "GL is in an error state before painting." << std::endl;
+ printGLErrorCodeInEnglish(err);
+ }
+}
+
+void printGLErrorCodeInEnglish(GLenum err) {
+ std::cerr << "GL error code " << err << ":" << std::endl;
+ switch(err) {
+ case GL_INVALID_ENUM:
+ std::cerr << "GL_INVALID_ENUM" << std::endl;
+ std::cerr << "An unacceptable value is specified for an enumerated argument. The offending command is ignored and has no other side effect than to set the error flag." << std::endl;
+ break;
+ case GL_INVALID_VALUE:
+ std::cerr << "GL_INVALID_VALUE" << std::endl;
+ std::cerr << "A numeric argument is out of range. The offending command is ignored and has no other side effect than to set the error flag." << std::endl;
+ break;
+ case GL_INVALID_OPERATION:
+ std::cerr << "GL_INVALID_OPERATION" << std::endl;
+ std::cerr << "The specified operation is not allowed in the current state. The offending command is ignored and has no other side effect than to set the error flag." << std::endl;
+ break;
+ case GL_INVALID_FRAMEBUFFER_OPERATION:
+ std::cerr << "GL_INVALID_FRAMEBUFFER_OPERATION" << std::endl;
+ std::cerr << "The framebuffer object is not complete. The offending command is ignored and has no other side effect than to set the error flag." << std::endl;
+ break;
+ case GL_OUT_OF_MEMORY:
+ std::cerr << "GL_OUT_OF_MEMORY" << std::endl;
+ std::cerr << "There is not enough memory left to execute the command. The state of the GL is undefined, except for the state of the error flags, after this error is recorded." << std::endl;
+ break;
+ case GL_STACK_UNDERFLOW:
+ std::cerr << "GL_STACK_UNDERFLOW" << std::endl;
+ std::cerr << "An attempt has been made to perform an operation that would cause an internal stack to underflow." << std::endl;
+ break;
+ case GL_STACK_OVERFLOW:
+ std::cerr << "GL_STACK_OVERFLOW" << std::endl;
+ std::cerr << "An attempt has been made to perform an operation that would cause an internal stack to overflow." << std::endl;
+ break;
+ default:
+ std::cerr << "Unknown GL error code" << std::endl;
+ }
+}
+
+void checkFramebufferStatus() {
+ GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (status != GL_FRAMEBUFFER_COMPLETE) {
+ std::cerr << "Framebuffer is incomplete." << std::endl;
+ printFramebufferErrorCodeInEnglish(status);
+ }
+}
+
+void printFramebufferErrorCodeInEnglish(GLenum err) {
+ switch(err) {
+ case GL_FRAMEBUFFER_UNDEFINED:
+ std:: cerr << "GL_FRAMEBUFFER_UNDEFINED is returned if the specified framebuffer is the default read or draw framebuffer, but the default framebuffer does not exist." << std::endl;
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
+ std::cerr << "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT is returned if any of the framebuffer attachment points are framebuffer incomplete." << std::endl;
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
+ std::cerr << "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT is returned if the framebuffer does not have at least one image attached to it." << std::endl;
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
+ std::cerr << "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER is returned if the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for any color attachment point(s) named by GL_DRAW_BUFFERi." << std::endl;
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
+ std::cerr << "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER is returned if GL_READ_BUFFER is not GL_NONE and the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for the color attachment point named by GL_READ_BUFFER." << std::endl;
+ break;
+ case GL_FRAMEBUFFER_UNSUPPORTED:
+ std::cerr << "GL_FRAMEBUFFER_UNSUPPORTED is returned if the combination of internal formats of the attached images violates an implementation-dependent set of restrictions." << std::endl;
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
+ std::cerr << "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE is returned if the value of GL_RENDERBUFFER_SAMPLES is not the same for all attached renderbuffers; if the value of GL_TEXTURE_SAMPLES is the not same for all attached textures; or, if the attached images are a mix of renderbuffers and textures, the value of GL_RENDERBUFFER_SAMPLES does not match the value of GL_TEXTURE_SAMPLES." << std::endl;
+ std::cerr << "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE is also returned if the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not the same for all attached textures; or, if the attached images are a mix of renderbuffers and textures, the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not GL_TRUE for all attached textures." << std::endl;
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
+ std::cerr << "GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS is returned if any framebuffer attachment is layered, and any populated attachment is not layered, or if all populated color attachments are not from textures of the same target." << std::endl;
+ break;
+ }
+}
+
+void checkShaderCompilationStatus(GLuint shaderID) {
+ GLint status;
+ glGetShaderiv(shaderID, GL_COMPILE_STATUS, &status);
+ if (status == GL_FALSE) {
+ std::cerr << "Error: Could not compile shader." << std::endl;
+
+ GLint maxLength = 0;
+ glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &maxLength);
+
+ // The maxLength includes the null character
+ std::vector<GLchar> errorLog(maxLength);
+ glGetShaderInfoLog(shaderID, maxLength, &maxLength, &errorLog[0]);
+
+ std::cerr << &errorLog[0] << std::endl;
+ } else {
+ std::cerr << "Shader compiled." << std::endl;
+ }
+}
+
+void checkShaderLinkStatus(GLuint shaderProgramID) {
+ GLint linked;
+ glGetProgramiv(shaderProgramID, GL_LINK_STATUS, &linked);
+ if (linked == GL_FALSE) {
+ std::cerr << "Shader failed to link" << std::endl;
+
+ GLint maxLength = 0;
+ glGetProgramiv(shaderProgramID, GL_INFO_LOG_LENGTH, &maxLength);
+
+ // The maxLength includes the null character
+ std::vector<GLchar> errorLog(maxLength);
+ glGetProgramInfoLog(shaderProgramID, maxLength, &maxLength, &errorLog[0]);
+
+ std::cerr << &errorLog[0] << std::endl;
+ } else {
+ std::cerr << "Shader linked successfully." << std::endl;
+ }
+}
diff --git a/wave-sim/src/graphics/graphicsdebug.h b/wave-sim/src/graphics/graphicsdebug.h
new file mode 100644
index 0000000..9be33b4
--- /dev/null
+++ b/wave-sim/src/graphics/graphicsdebug.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <GL/glew.h>
+#include <string>
+
+#define GRAPHICS_DEBUG_LEVEL 0
+
+void checkError(std::string prefix = "");
+void printGLErrorCodeInEnglish(GLenum err);
+
+void checkFramebufferStatus();
+void printFramebufferErrorCodeInEnglish(GLenum err);
+
+void checkShaderCompilationStatus(GLuint shaderID);
+void checkShaderLinkStatus(GLuint shaderProgramID);
diff --git a/wave-sim/src/graphics/meshloader.cpp b/wave-sim/src/graphics/meshloader.cpp
new file mode 100644
index 0000000..84e78c7
--- /dev/null
+++ b/wave-sim/src/graphics/meshloader.cpp
@@ -0,0 +1,53 @@
+#include "graphics/meshloader.h"
+
+#define TINYOBJLOADER_IMPLEMENTATION
+#include "util/tiny_obj_loader.h"
+
+#include <iostream>
+
+#include <QString>
+#include <QFile>
+#include <QTextStream>
+#include <QRegularExpression>
+
+using namespace Eigen;
+
+bool MeshLoader::loadTetMesh(const std::string &filepath, std::vector<Eigen::Vector3d> &vertices, std::vector<Eigen::Vector4i> &tets)
+{
+ QString qpath = QString::fromStdString(filepath);
+ QFile file(qpath);
+
+ if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ std::cout << "Error opening file: " << filepath << std::endl;
+ return false;
+ }
+ QTextStream in(&file);
+
+ QRegularExpression vrxp("v (-?\\d*\\.?\\d+) +(-?\\d*\\.?\\d+) +(-?\\d*\\.?\\d+)");
+ QRegularExpression trxp("t (\\d+) +(\\d+) +(\\d+) +(\\d+)");
+
+ while(!in.atEnd()) {
+ QString line = in.readLine();
+ auto match = vrxp.match(line);
+ if(match.hasMatch()) {
+ vertices.emplace_back(match.captured(1).toDouble(),
+ match.captured(2).toDouble(),
+ match.captured(3).toDouble());
+ continue;
+ }
+ match = trxp.match(line);
+ if(match.hasMatch()) {
+ tets.emplace_back(match.captured(1).toInt(),
+ match.captured(2).toInt(),
+ match.captured(3).toInt(),
+ match.captured(4).toInt());
+ }
+ }
+ file.close();
+ return true;
+}
+
+MeshLoader::MeshLoader()
+{
+
+}
diff --git a/wave-sim/src/graphics/meshloader.h b/wave-sim/src/graphics/meshloader.h
new file mode 100644
index 0000000..e6b87fd
--- /dev/null
+++ b/wave-sim/src/graphics/meshloader.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <vector>
+#include "Eigen/Dense"
+#include "Eigen/StdVector"
+
+EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(Eigen::Matrix4i)
+
+class MeshLoader
+{
+public:
+ static bool loadTetMesh(const std::string &filepath, std::vector<Eigen::Vector3d> &vertices, std::vector<Eigen::Vector4i> &tets);
+private:
+ MeshLoader();
+};
diff --git a/wave-sim/src/graphics/shader.cpp b/wave-sim/src/graphics/shader.cpp
new file mode 100644
index 0000000..3789be0
--- /dev/null
+++ b/wave-sim/src/graphics/shader.cpp
@@ -0,0 +1,234 @@
+#include "graphics/shader.h"
+#include "graphics/graphicsdebug.h"
+
+#include <QFile>
+#include <QString>
+#include <QTextStream>
+#include <iostream>
+
+Shader::Shader(const std::string &vertexPath,
+ const std::string &fragmentPath)
+{
+ createProgramID();
+
+ std::vector<GLuint> shaders = {
+ createShaderFromString(getFileContents(vertexPath), GL_VERTEX_SHADER),
+ createShaderFromString(getFileContents(fragmentPath), GL_FRAGMENT_SHADER)
+ };
+
+ buildShaderProgramFromShaders(shaders);
+ discoverShaderData();
+}
+
+Shader::~Shader()
+{
+ glDeleteProgram(m_programID);
+}
+
+Shader::Shader(Shader &&that)
+ : m_programID(that.m_programID),
+ m_attributes(std::move(that.m_attributes)),
+ m_uniforms(std::move(that.m_uniforms))
+{
+ that.m_programID = 0;
+}
+
+Shader& Shader::operator=(Shader &&that)
+{
+ this->~Shader();
+
+ m_programID = that.m_programID;
+ m_attributes = std::move(that.m_attributes);
+ m_uniforms = std::move(that.m_uniforms);
+
+ that.m_programID = 0;
+
+ return *this;
+}
+
+// ================== Regular Use
+
+void Shader::bind() const { glUseProgram(m_programID); }
+
+void Shader::unbind() const { glUseProgram(0); }
+
+GLuint Shader::getUniformLocation(std::string name)
+{
+ return glGetUniformLocation(m_programID, name.c_str());
+}
+
+GLuint Shader::getEnumeratedUniformLocation(std::string name, int index)
+{
+ std::string n = name + "[" + std::to_string(index) + "]";
+ return glGetUniformLocation(m_programID, n.c_str());
+}
+
+// ================== Setting Uniforms
+
+// Note: the overload to set matrix uniforms is in the .h file
+
+void Shader::setUniform(const std::string &name, float f)
+{
+ glUniform1f(m_uniforms[name], f);
+}
+
+void Shader::setUniform(const std::string &name, int i)
+{
+ glUniform1i(m_uniforms[name], i);
+}
+
+void Shader::setUniform(const std::string &name, bool b)
+{
+ glUniform1i(m_uniforms[name], static_cast<GLint>(b));
+}
+
+// ================== Creating the Program
+
+void Shader::createProgramID()
+{
+ m_programID = glCreateProgram();
+}
+
+void Shader::buildShaderProgramFromShaders(const std::vector<GLuint> &shaderHandles)
+{
+ // Attach shaders
+ for (const GLuint &shaderHandle : shaderHandles) {
+ glAttachShader(m_programID, shaderHandle);
+ }
+
+ // Link program
+ glLinkProgram(m_programID);
+ checkShaderLinkStatus(m_programID);
+
+ // Detach and delete shaders
+ for (const GLuint &shaderHandle : shaderHandles) {
+ glDetachShader(m_programID, shaderHandle);
+ glDeleteShader(shaderHandle);
+ }
+}
+
+// ================== Creating Shaders From Filepaths
+
+std::string Shader::getFileContents(std::string filepath)
+{
+ QString filepathStr = QString::fromStdString(filepath);
+ QFile file(filepathStr);
+
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ throw std::runtime_error(std::string("Failed to open shader: ") + filepath);
+ }
+
+ QTextStream stream(&file);
+ QString contents = stream.readAll();
+ file.close();
+
+ return contents.toStdString();
+}
+
+GLuint Shader::createShaderFromString(const std::string &str, GLenum shaderType)
+{
+ GLuint shaderHandle = glCreateShader(shaderType);
+
+ // Compile shader code
+ const char *codePtr = str.c_str();
+ glShaderSource(shaderHandle, 1, &codePtr, nullptr); // Assumes code is null terminated
+ glCompileShader(shaderHandle);
+
+ checkShaderCompilationStatus(shaderHandle);
+
+ return shaderHandle;
+}
+
+// ================== Discovering Attributes/Uniforms/Textures
+
+void Shader::discoverShaderData() {
+ discoverAttributes();
+ discoverUniforms();
+}
+
+void Shader::discoverAttributes() {
+ bind();
+ GLint attribCount;
+ glGetProgramiv(m_programID, GL_ACTIVE_ATTRIBUTES, &attribCount);
+ for (int i = 0; i < attribCount; i++) {
+ const GLsizei bufSize = 256;
+ GLsizei nameLength = 0;
+ GLint arraySize = 0;
+ GLenum type;
+ GLchar name[bufSize];
+ glGetActiveAttrib(m_programID, i, bufSize, &nameLength, &arraySize, &type, name);
+ name[std::min(nameLength, bufSize - 1)] = 0;
+ m_attributes[std::string(name)] = glGetAttribLocation(m_programID, name);
+ }
+ unbind();
+}
+
+void Shader::discoverUniforms() {
+ bind();
+ GLint uniformCount;
+ glGetProgramiv(m_programID, GL_ACTIVE_UNIFORMS, &uniformCount);
+ for (int i = 0; i < uniformCount; i++) {
+ const GLsizei bufSize = 256;
+ GLsizei nameLength = 0;
+ GLint arraySize = 0;
+ GLenum type;
+ GLchar name[bufSize];
+ glGetActiveUniform(m_programID, i, bufSize, &nameLength, &arraySize, &type, name);
+ name[std::min(nameLength, bufSize - 1)] = 0;
+
+ std::string strname(name);
+ if (isUniformArray(name, nameLength)) {
+ addUniformArray(strname, arraySize);
+ } else if (isTexture(type)) {
+ addTexture(strname);
+ } else {
+ addUniform(strname);
+ }
+ }
+ unbind();
+}
+
+bool Shader::isUniformArray(const GLchar *name, GLsizei nameLength) {
+ // Check if the last 3 characters are '[0]'
+ return (name[nameLength - 3] == '[') &&
+ (name[nameLength - 2] == '0') &&
+ (name[nameLength - 1] == ']');
+}
+
+bool Shader::isTexture(GLenum type) {
+ return (type == GL_SAMPLER_2D) ||
+ (type == GL_SAMPLER_CUBE);
+}
+
+void Shader::addUniformArray(const std::string &name, size_t size) {
+ std::string cleanName = name.substr(0, name.length() - 3);
+ for (auto i = static_cast<size_t>(0); i < size; i++) {
+ std::string enumeratedName = name;
+ enumeratedName[enumeratedName.length() - 2] = static_cast<char>('0' + i);
+ std::tuple< std::string, size_t > nameIndexTuple = std::make_tuple(cleanName, i);
+ m_uniformArrays[nameIndexTuple] = glGetUniformLocation(m_programID, enumeratedName.c_str());
+ }
+
+#if GRAPHICS_DEBUG_LEVEL > 0
+ m_trackedUniformArrays[name] = false;
+#endif
+}
+
+void Shader::addTexture(const std::string &name) {
+ GLint location = glGetUniformLocation(m_programID, name.c_str());
+ m_textureLocations[name] = location;
+ GLint slot = m_textureSlots.size();
+ m_textureSlots[location] = slot; // Assign slots in increasing order.
+
+#if GRAPHICS_DEBUG_LEVEL > 0
+ m_trackedTextures[name] = false;
+#endif
+}
+
+void Shader::addUniform(const std::string &name) {
+ m_uniforms[name] = glGetUniformLocation(m_programID, name.c_str());
+
+#if GRAPHICS_DEBUG_LEVEL > 0
+ m_trackedUniforms[name] = false;
+#endif
+}
diff --git a/wave-sim/src/graphics/shader.h b/wave-sim/src/graphics/shader.h
new file mode 100644
index 0000000..08f6654
--- /dev/null
+++ b/wave-sim/src/graphics/shader.h
@@ -0,0 +1,71 @@
+#pragma once
+
+#include <map>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <GL/glew.h>
+#include "Eigen/Dense"
+#include "util/unsupportedeigenthing/OpenGLSupport"
+
+
+class Shader {
+public:
+ Shader(const std::string &vertexPath,
+ const std::string &fragmentPath);
+
+ virtual ~Shader();
+
+ Shader(Shader &that) = delete;
+ Shader& operator=(Shader &that) = delete;
+ Shader(Shader &&that);
+ Shader& operator=(Shader &&that);
+
+ // Basic Usage
+ GLuint getProgramID() const { return m_programID; }
+ void bind() const;
+ void unbind() const;
+ GLuint getUniformLocation(std::string name);
+ GLuint getEnumeratedUniformLocation(std::string name, int index);
+
+ // Setting Uniforms
+ void setUniform(const std::string &name, float f);
+ void setUniform(const std::string &name, int i);
+ void setUniform(const std::string &name, bool b);
+ template<typename type, int n, int m>
+ void setUniform(const std::string &name, const Eigen::Matrix<type, n, m> &mat)
+ {
+ glUniform(m_uniforms[name], mat);
+ }
+
+
+private:
+ // Creating the Program
+ void createProgramID();
+ void buildShaderProgramFromShaders(const std::vector<GLuint> &shaders);
+
+ // Creating Shaders From Filepaths
+ std::string getFileContents(std::string path);
+ GLuint createShaderFromString(const std::string &str, GLenum shaderType);
+
+ // Discovering attributes/uniforms/textures
+ void discoverShaderData();
+ void discoverAttributes();
+ void discoverUniforms();
+ bool isUniformArray(const GLchar *name , GLsizei nameLength);
+ bool isTexture(GLenum type);
+ void addUniform(const std::string &name);
+ void addUniformArray(const std::string &name, size_t size);
+ void addTexture(const std::string &name);
+
+ // Identifies the shader program associated with this shader
+ GLuint m_programID;
+
+ // Collections of known attributes/uniforms/textures
+ std::map<std::string, GLuint> m_attributes;
+ std::map<std::string, GLuint> m_uniforms;
+ std::map<std::tuple<std::string, size_t>, GLuint> m_uniformArrays;
+ std::map<std::string, GLuint> m_textureLocations; // name to uniform location
+ std::map<GLuint, GLuint> m_textureSlots; // uniform location to texture slot
+};
diff --git a/wave-sim/src/graphics/shape.cpp b/wave-sim/src/graphics/shape.cpp
new file mode 100644
index 0000000..e59e009
--- /dev/null
+++ b/wave-sim/src/graphics/shape.cpp
@@ -0,0 +1,337 @@
+#include "shape.h"
+
+#include <iostream>
+
+#include "graphics/shader.h"
+
+using namespace Eigen;
+
+Shape::Shape()
+ : m_tetVao(-1),
+ m_numSurfaceVertices(),
+ m_verticesSize(),
+ m_modelMatrix(Eigen::Matrix4f::Identity()),
+ m_wireframe(false)
+{
+}
+
+//void Shape::init(const std::vector<Eigen::Vector3d> &vertices, const std::vector<Eigen::Vector3d> &normals, const std::vector<Eigen::Vector3i> &triangles)
+//{
+// if(vertices.size() != normals.size()) {
+// std::cerr << "Vertices and normals are not the same size" << std::endl;
+// return;
+// }
+// glGenBuffers(1, &m_surfaceVbo);
+// glGenBuffers(1, &m_surfaceIbo);
+// glGenVertexArrays(1, &m_surfaceVao);
+
+// glBindBuffer(GL_ARRAY_BUFFER, m_surfaceVbo);
+// glBufferData(GL_ARRAY_BUFFER, sizeof(double) * vertices.size() * 3 * 2, nullptr, GL_DYNAMIC_DRAW);
+// glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(double) * vertices.size() * 3, static_cast<const void *>(vertices.data()));
+// glBufferSubData(GL_ARRAY_BUFFER, sizeof(double) * vertices.size() * 3, sizeof(double) * vertices.size() * 3, static_cast<const void *>(normals.data()));
+// glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_surfaceIbo);
+// glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * 3 * triangles.size(), static_cast<const void *>(triangles.data()), GL_STATIC_DRAW);
+// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+// glBindVertexArray(m_surfaceVao);
+// glBindBuffer(GL_ARRAY_BUFFER, m_surfaceVbo);
+// glEnableVertexAttribArray(0);
+// glVertexAttribPointer(0, 3, GL_DOUBLE, GL_FALSE, 0, static_cast<GLvoid *>(0));
+// glEnableVertexAttribArray(1);
+// glVertexAttribPointer(1, 3, GL_DOUBLE, GL_FALSE, 0, reinterpret_cast<GLvoid *>(sizeof(double) * vertices.size() * 3));
+// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_surfaceIbo);
+// glBindVertexArray(0);
+// glBindBuffer(GL_ARRAY_BUFFER, 0);
+// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+// m_numSurfaceVertices = triangles.size() * 3;
+// m_verticesSize = vertices.size();
+// m_faces = triangles;
+//}
+
+void Shape::init(const std::vector<Eigen::Vector3d> &vertices, const std::vector<Eigen::Vector3i> &triangles)
+{
+ std::vector<Eigen::Vector3d> verts;
+ std::vector<Eigen::Vector3d> normals;
+ std::vector<Eigen::Vector3i> faces;
+ std::vector<Eigen::Vector3d> forces;
+ verts.reserve(triangles.size() * 3);
+ normals.reserve(triangles.size() * 3);
+ for(auto& f : triangles) {
+ auto& v1 = vertices[f[0]];
+ auto& v2 = vertices[f[1]];
+ auto& v3 = vertices[f[2]];
+ auto& e1 = v2 - v1;
+ auto& e2 = v3 - v1;
+ auto n = e1.cross(e2);
+ int s = verts.size();
+ faces.push_back(Eigen::Vector3i(s, s + 1, s + 2));
+ normals.push_back(n);
+ normals.push_back(n);
+ normals.push_back(n);
+ verts.push_back(v1);
+ verts.push_back(v2);
+ verts.push_back(v3);
+ forces.push_back(Eigen::Vector3d(1.0, 1.0, 0.0));
+ forces.push_back(Eigen::Vector3d(1.0, 1.0, 0.0));
+ forces.push_back(Eigen::Vector3d(1.0, 1.0, 0.0));
+ }
+ glGenBuffers(1, &m_surfaceVbo);
+ glGenBuffers(1, &m_surfaceIbo);
+ glGenVertexArrays(1, &m_surfaceVao);
+
+ glBindBuffer(GL_ARRAY_BUFFER, m_surfaceVbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(double) * verts.size() * 3 * 3, nullptr, GL_DYNAMIC_DRAW);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(double) * verts.size() * 3, static_cast<const void *>(verts.data()));
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(double) * verts.size() * 3, sizeof(double) * verts.size() * 3, static_cast<const void *>(normals.data()));
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(double) * verts.size() * 3 * 2, sizeof(double) * verts.size() * 3, static_cast<const void *>(forces.data()));
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_surfaceIbo);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * 3 * faces.size(), static_cast<const void *>(faces.data()), GL_STATIC_DRAW);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+ glBindVertexArray(m_surfaceVao);
+ glBindBuffer(GL_ARRAY_BUFFER, m_surfaceVbo);
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 3, GL_DOUBLE, GL_FALSE, 0, static_cast<GLvoid *>(0));
+ glEnableVertexAttribArray(1);
+ glVertexAttribPointer(1, 3, GL_DOUBLE, GL_FALSE, 0, reinterpret_cast<GLvoid *>(sizeof(double) * verts.size() * 3));
+ glEnableVertexAttribArray(2);
+ glVertexAttribPointer(2, 3, GL_DOUBLE, GL_FALSE, 0, reinterpret_cast<GLvoid *>(sizeof(double) * verts.size() * 3 * 2));
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_surfaceIbo);
+ glBindVertexArray(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+ m_numSurfaceVertices = faces.size() * 3;
+ m_verticesSize = vertices.size();
+ m_faces = triangles;
+
+ if (vertices.size() > 4) { //shape
+ m_red = 0.93;
+ m_green = 0.8;
+ m_blue = 1.f;
+ m_alpha = 1.f;
+ } else { //ground
+ m_red = 1;
+ m_green = 1;
+ m_blue = 1;
+ m_alpha = 1.f;
+ }
+ m_force = 0;
+// m_red = static_cast <float> (rand()) / static_cast <float> (RAND_MAX);
+// m_blue = static_cast <float> (rand()) / static_cast <float> (RAND_MAX);
+// m_green = static_cast <float> (rand()) / static_cast <float> (RAND_MAX);
+// m_alpha = static_cast <float> (rand()) / static_cast <float> (RAND_MAX);
+}
+
+void Shape::setColor(float r, float g, float b) {
+ m_red = r;
+ m_green = g;
+ m_blue = b;
+}
+
+void Shape::init(const std::vector<Eigen::Vector3d> &vertices, const std::vector<Eigen::Vector3i> &triangles, const std::vector<Eigen::Vector4i> &tetIndices)
+{
+ init(vertices, triangles);
+
+ std::vector<Eigen::Vector2i> lines;
+ for(Vector4i tet : tetIndices) {
+ lines.emplace_back(tet[0], tet[1]);
+ lines.emplace_back(tet[0], tet[2]);
+ lines.emplace_back(tet[0], tet[3]);
+ lines.emplace_back(tet[1], tet[2]);
+ lines.emplace_back(tet[1], tet[3]);
+ lines.emplace_back(tet[2], tet[3]);
+ }
+ glGenBuffers(1, &m_tetVbo);
+ glGenBuffers(1, &m_tetIbo);
+ glGenVertexArrays(1, &m_tetVao);
+
+ glBindBuffer(GL_ARRAY_BUFFER, m_tetVbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(double) * vertices.size() * 3, vertices.data(), GL_DYNAMIC_DRAW);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_tetIbo);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * 2 * lines.size(), static_cast<const void *>(lines.data()), GL_STATIC_DRAW);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+ glBindVertexArray(m_tetVao);
+ glBindBuffer(GL_ARRAY_BUFFER, m_tetVbo);
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 3, GL_DOUBLE, GL_FALSE, 0, static_cast<GLvoid *>(0));
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_tetIbo);
+ glBindVertexArray(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+ m_numTetVertices = lines.size() * 2;
+}
+
+void Shape::setVertices(const std::vector<Eigen::Vector3d> &vertices)
+{
+ if(vertices.size() != m_verticesSize) {
+ std::cerr << "You can't set vertices to a vector that is a different length that what shape was inited with" << std::endl;
+ return;
+ }
+ std::vector<Eigen::Vector3d> verts;
+ std::vector<Eigen::Vector3d> normals;
+ std::vector<Eigen::Vector3d> forces;
+ verts.reserve(m_faces.size() * 3);
+ normals.reserve(m_faces.size() * 3);
+ for(auto& f : m_faces) {
+ auto& v1 = vertices[f[0]];
+ auto& v2 = vertices[f[1]];
+ auto& v3 = vertices[f[2]];
+ auto& e1 = v2 - v1;
+ auto& e2 = v3 - v1;
+ auto n = e1.cross(e2);
+ normals.push_back(n);
+ normals.push_back(n);
+ normals.push_back(n);
+ verts.push_back(v1);
+ verts.push_back(v2);
+ verts.push_back(v3);
+ forces.push_back(Eigen::Vector3d(1.0, 1.0, 0.0));
+ forces.push_back(Eigen::Vector3d(1.0, 1.0, 0.0));
+ forces.push_back(Eigen::Vector3d(1.0, 1.0, 0.0));
+ }
+ glBindBuffer(GL_ARRAY_BUFFER, m_surfaceVbo);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(double) * verts.size() * 3, static_cast<const void *>(verts.data()));
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(double) * verts.size() * 3, sizeof(double) * verts.size() * 3, static_cast<const void *>(normals.data()));
+ if(m_tetVao != static_cast<GLuint>(-1)) {
+ glBindBuffer(GL_ARRAY_BUFFER, m_tetVbo);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(double) * vertices.size() * 3, static_cast<const void *>(vertices.data()));
+ }
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(double) * vertices.size() * 3 * 2, sizeof(double) * vertices.size() * 3 * 2, static_cast<const void *>(forces.data()));
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+void Shape::setVerticesF(const std::vector<Eigen::Vector3d> &vertices, const std::vector<Eigen::Vector3d> &forces) {
+ if(vertices.size() != m_verticesSize) {
+ std::cerr << "You can't set vertices to a vector that is a different length that what shape was inited with" << std::endl;
+ return;
+ }
+ if(vertices.size() != forces.size()) {
+ std::cerr << "Vertices and forces are not the same size" << std::endl;
+ return;
+ }
+ std::vector<Eigen::Vector3d> verts;
+ std::vector<Eigen::Vector3d> normals;
+ std::vector<Eigen::Vector3d> glForces;
+ verts.reserve(m_faces.size() * 3);
+ normals.reserve(m_faces.size() * 3);
+ glForces.reserve(m_faces.size() * 3);
+
+ double maxForceNorm = 500;
+ for(auto& f : m_faces) {
+ auto& v1 = vertices[f[0]];
+ auto& v2 = vertices[f[1]];
+ auto& v3 = vertices[f[2]];
+// auto& f1 = forces[f[0]].normalized();
+// auto& f2 = forces[f[1]].normalized();
+// auto& f3 = forces[f[2]].normalized();
+ auto& f1 = forces[f[0]];
+ auto& f2 = forces[f[1]];
+ auto& f3 = forces[f[2]];
+ maxForceNorm = std::max(f1.norm(), maxForceNorm);
+ maxForceNorm = std::max(f2.norm(), maxForceNorm);
+ maxForceNorm = std::max(f3.norm(), maxForceNorm);
+ auto& e1 = v2 - v1;
+ auto& e2 = v3 - v1;
+ auto n = e1.cross(e2);
+ normals.push_back(n);
+ normals.push_back(n);
+ normals.push_back(n);
+ verts.push_back(v1);
+ verts.push_back(v2);
+ verts.push_back(v3);
+ glForces.push_back(f1);
+ glForces.push_back(f2);
+ glForces.push_back(f3); // Cool effect if it's v1, v2, v3 instead
+ }
+// std::cout << maxForceNorm << std::endl;
+ for(Eigen::Vector3d &f : glForces) {
+ f /= maxForceNorm;
+ f = f.cwiseAbs();
+ }
+ glBindBuffer(GL_ARRAY_BUFFER, m_surfaceVbo);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(double) * verts.size() * 3, static_cast<const void *>(verts.data()));
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(double) * verts.size() * 3, sizeof(double) * verts.size() * 3, static_cast<const void *>(normals.data()));
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(double) * verts.size() * 3 * 2, sizeof(double) * verts.size() * 3, static_cast<const void *>(glForces.data()));
+ if(m_tetVao != static_cast<GLuint>(-1)) {
+ glBindBuffer(GL_ARRAY_BUFFER, m_tetVbo);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(double) * vertices.size() * 3, static_cast<const void *>(vertices.data()));
+ }
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+void Shape::setModelMatrix(const Eigen::Affine3f &model)
+{
+ m_modelMatrix = model.matrix();
+}
+
+void Shape::toggleWireframe()
+{
+ m_wireframe = !m_wireframe;
+}
+
+void Shape::toggleForce() {
+ m_force = std::abs(m_force - 1);
+}
+
+void Shape::setVertices(const std::vector<Eigen::Vector3d> &vertices, const std::vector<Eigen::Vector3d> &normals)
+{
+ std::vector<Eigen::Vector3d> forces;
+ if(vertices.size() != normals.size()) {
+ std::cerr << "Vertices and normals are not the same size" << std::endl;
+ return;
+ }
+ if(vertices.size() != m_verticesSize) {
+ std::cerr << "You can't set vertices to a vector that is a different length that what shape was inited with" << std::endl;
+ return;
+ }
+ for(Eigen::Vector3d v : vertices) {
+ forces.push_back(Eigen::Vector3d(1.0, 1.0, 0.0));
+ }
+ glBindBuffer(GL_ARRAY_BUFFER, m_surfaceVbo);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(double) * vertices.size() * 3, static_cast<const void *>(vertices.data()));
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(double) * vertices.size() * 3, sizeof(double) * vertices.size() * 3, static_cast<const void *>(normals.data()));
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(double) * vertices.size() * 3 * 2, sizeof(double) * vertices.size() * 3, static_cast<const void *>(forces.data()));
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+void Shape::draw(Shader *shader)
+{
+ Eigen::Matrix3f m3 = m_modelMatrix.topLeftCorner(3, 3);
+ Eigen::Matrix3f inverseTransposeModel = m3.inverse().transpose();
+
+ if(m_wireframe && m_tetVao != static_cast<GLuint>(-1)) {
+ shader->setUniform("wire", 1);
+ shader->setUniform("model", m_modelMatrix);
+ shader->setUniform("inverseTransposeModel", inverseTransposeModel);
+ shader->setUniform("red", 1);
+ shader->setUniform("green", 1);
+ shader->setUniform("blue", 1);
+ shader->setUniform("alpha", 1);
+ shader->setUniform("displayForce", m_force);
+ glBindVertexArray(m_tetVao);
+ glDrawElements(GL_LINES, m_numTetVertices, GL_UNSIGNED_INT, reinterpret_cast<GLvoid *>(0));
+ glBindVertexArray(0);
+ } else {
+ shader->setUniform("wire", 0);
+ shader->setUniform("model", m_modelMatrix);
+ shader->setUniform("inverseTransposeModel", inverseTransposeModel);
+ shader->setUniform("red", m_red);
+ shader->setUniform("green", m_green);
+ shader->setUniform("blue", m_blue);
+ shader->setUniform("alpha", m_alpha);
+ shader->setUniform("displayForce", m_force);
+ glBindVertexArray(m_surfaceVao);
+ glDrawElements(GL_TRIANGLES, m_numSurfaceVertices, GL_UNSIGNED_INT, reinterpret_cast<GLvoid *>(0));
+ glBindVertexArray(0);
+ }
+}
diff --git a/wave-sim/src/graphics/shape.h b/wave-sim/src/graphics/shape.h
new file mode 100644
index 0000000..6540ef6
--- /dev/null
+++ b/wave-sim/src/graphics/shape.h
@@ -0,0 +1,59 @@
+#ifndef SHAPE_H
+#define SHAPE_H
+
+#include <GL/glew.h>
+#include <vector>
+
+#include <Eigen/Dense>
+
+class Shader;
+
+class Shape
+{
+public:
+ Shape();
+
+// void init(const std::vector<Eigen::Vector3d> &vertices, const std::vector<Eigen::Vector3d> &normals, const std::vector<Eigen::Vector3i> &triangles);
+ void init(const std::vector<Eigen::Vector3d> &vertices, const std::vector<Eigen::Vector3i> &triangles);
+ void init(const std::vector<Eigen::Vector3d> &vertices, const std::vector<Eigen::Vector3i> &triangles, const std::vector<Eigen::Vector4i> &tetIndices);
+
+ void setVertices(const std::vector<Eigen::Vector3d> &vertices, const std::vector<Eigen::Vector3d> &normals);
+ void setVertices(const std::vector<Eigen::Vector3d> &vertices);
+ void setVerticesF(const std::vector<Eigen::Vector3d> &vertices, const std::vector<Eigen::Vector3d> &forces);
+
+ void setModelMatrix(const Eigen::Affine3f &model);
+
+ void toggleWireframe();
+
+ void draw(Shader *shader);
+
+ void setColor(float r, float g, float b);
+
+ void toggleForce();
+
+private:
+ GLuint m_surfaceVao;
+ GLuint m_tetVao;
+ GLuint m_surfaceVbo;
+ GLuint m_tetVbo;
+ GLuint m_surfaceIbo;
+ GLuint m_tetIbo;
+
+ unsigned int m_numSurfaceVertices;
+ unsigned int m_numTetVertices;
+ unsigned int m_verticesSize;
+ float m_red;
+ float m_blue;
+ float m_green;
+ float m_alpha;
+
+ std::vector<Eigen::Vector3i> m_faces;
+
+ Eigen::Matrix4f m_modelMatrix;
+
+ bool m_wireframe;
+
+ int m_force;
+};
+
+#endif // SHAPE_H
diff --git a/wave-sim/src/main.cpp b/wave-sim/src/main.cpp
new file mode 100755
index 0000000..112c77b
--- /dev/null
+++ b/wave-sim/src/main.cpp
@@ -0,0 +1,38 @@
+#include "mainwindow.h"
+#include <cstdlib>
+#include <ctime>
+
+#include <QApplication>
+#include <QSurfaceFormat>
+#include <QScreen>
+
+int main(int argc, char *argv[])
+{
+ srand(static_cast<unsigned>(time(0)));
+
+ // Create a Qt application
+ QApplication a(argc, argv);
+ QCoreApplication::setApplicationName("Simulation");
+ QCoreApplication::setOrganizationName("CS 2240");
+ QCoreApplication::setApplicationVersion(QT_VERSION_STR);
+
+ // Set OpenGL version to 4.1 and context to Core
+ QSurfaceFormat fmt;
+ fmt.setVersion(4, 1);
+ fmt.setProfile(QSurfaceFormat::CoreProfile);
+ QSurfaceFormat::setDefaultFormat(fmt);
+
+ // Create a GUI window
+ MainWindow w;
+ w.resize(600, 500);
+ int desktopArea = QGuiApplication::primaryScreen()->size().width() *
+ QGuiApplication::primaryScreen()->size().height();
+ int widgetArea = w.width() * w.height();
+ if (((float)widgetArea / (float)desktopArea) < 0.75f)
+ w.show();
+ else
+ w.showMaximized();
+
+
+ return a.exec();
+}
diff --git a/wave-sim/src/mainwindow.cpp b/wave-sim/src/mainwindow.cpp
new file mode 100755
index 0000000..72560dd
--- /dev/null
+++ b/wave-sim/src/mainwindow.cpp
@@ -0,0 +1,16 @@
+#include "mainwindow.h"
+#include <QHBoxLayout>
+
+MainWindow::MainWindow()
+{
+ glWidget = new GLWidget();
+
+ QHBoxLayout *container = new QHBoxLayout;
+ container->addWidget(glWidget);
+ this->setLayout(container);
+}
+
+MainWindow::~MainWindow()
+{
+ delete glWidget;
+}
diff --git a/wave-sim/src/mainwindow.h b/wave-sim/src/mainwindow.h
new file mode 100755
index 0000000..721c8f8
--- /dev/null
+++ b/wave-sim/src/mainwindow.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <QMainWindow>
+#include "glwidget.h"
+
+class MainWindow : public QWidget
+{
+ Q_OBJECT
+
+public:
+ MainWindow();
+ ~MainWindow();
+
+private:
+
+ GLWidget *glWidget;
+};
diff --git a/wave-sim/src/mainwindow.ui b/wave-sim/src/mainwindow.ui
new file mode 100755
index 0000000..45c61e5
--- /dev/null
+++ b/wave-sim/src/mainwindow.ui
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>simulation</string>
+ </property>
+ <widget class="QWidget" name="centralWidget">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="View" name="view" native="true"/>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <customwidgets>
+ <customwidget>
+ <class>View</class>
+ <extends>QWidget</extends>
+ <header>view.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/wave-sim/src/simulation.cpp b/wave-sim/src/simulation.cpp
new file mode 100644
index 0000000..409fa90
--- /dev/null
+++ b/wave-sim/src/simulation.cpp
@@ -0,0 +1,236 @@
+#include "simulation.h"
+#include "graphics/meshloader.h"
+
+#include <unordered_map>
+#include <unordered_set>
+#include <iostream>
+
+using namespace Eigen;
+
+Simulation::Simulation() {}
+
+int createFaceHash(int a, int b, int c, int n_vertices) {
+ int &low = a;
+ int &middle = b;
+ int &high = c;
+ if (low > middle)
+ {
+ std::swap(low, middle);
+ }
+ if (middle > high)
+ {
+ std::swap(middle, high);
+ }
+ if (low > middle)
+ {
+ std::swap(low, middle);
+ }
+
+ return (n_vertices * n_vertices * low) + (n_vertices * middle) + high;
+}
+
+Eigen::Vector3d Simulation::calculateFaceNormal(Eigen::Vector3d a, Eigen::Vector3d b, Eigen::Vector3d c) {
+ return (b - a).cross(c - a).normalized();
+}
+
+bool Simulation::calculatePointBehindNormal(Eigen::Vector3d a, Eigen::Vector3d b, Eigen::Vector3d c, Eigen::Vector3d p) {
+ Eigen::Vector3d a_to_p = p - a; // Calculates a direction from point on plane to point in question
+ return a_to_p.dot(calculateFaceNormal(a, b, c)) < 0;
+}
+
+void Simulation::init()
+{
+ // STUDENTS: This code loads up the tetrahedral mesh in 'example-meshes/single-tet.mesh'
+ // (note: your working directory must be set to the root directory of the starter code
+ // repo for this file to load correctly). You'll probably want to instead have this code
+ // load up a tet mesh based on e.g. a file path specified with a command line argument.
+ std::vector<Vector3d> vertices;
+ std::vector<Vector4i> tets;
+ this->mainSystem = System();
+ if (MeshLoader::loadTetMesh("./example-meshes/sphere.mesh", vertices, tets)) {
+ // STUDENTS: This code computes the surface mesh of the loaded tet mesh, i.e. the faces
+ // of tetrahedra which are on the exterior surface of the object. Right now, this is
+ // hard-coded for the single-tet mesh. You'll need to implement surface mesh extraction
+ // for arbitrary tet meshes. Think about how you can identify which tetrahedron faces
+ // are surface faces...
+ std::vector<Vector3i> faces;
+// std::unordered_map<int, std::pair<Eigen::Vector3i*, int>> includedFaces;
+// std::unordered_map<int, int> includedFaces;
+ std::unordered_map<int, Eigen::Vector3i> includedFaces;
+ // includes the currently parsed faces and their index
+ std::vector<Vector4i> faceOrders;
+
+ faceOrders.emplace_back(1, 0, 2, 3); // first three are the included points, last is the excluded point
+ faceOrders.emplace_back(2, 0, 3, 1);
+ faceOrders.emplace_back(3, 1, 2, 0);
+ faceOrders.emplace_back(3, 0, 1, 2);
+
+ for(Vector4i t : tets) {
+ for(Vector4i fo : faceOrders) {
+ int hash = createFaceHash(t[fo[0]], t[fo[1]], t[fo[2]], vertices.size()); // Finding which vertex indexes the face has and ordering them from least to smallest
+ if(includedFaces.contains(hash)) {
+ includedFaces.erase(hash);
+ } else {
+ Eigen::Vector3i orderedFace;
+ Vector3d a = vertices.at(t[fo[0]]);
+ Vector3d b = vertices.at(t[fo[1]]);
+ Vector3d c = vertices.at(t[fo[2]]);
+ Vector3d d = vertices.at(t[fo[3]]);
+ if(calculatePointBehindNormal(a, b, c, d)) {
+ orderedFace = Eigen::Vector3i(t[fo[0]], t[fo[1]], t[fo[2]]); // Wind it backwards if the excluded point is behind the face
+ } else {
+ orderedFace = Eigen::Vector3i(t[fo[2]], t[fo[1]], t[fo[0]]); // Wind it backwards if the excluded point is in front of the face
+ }
+
+ includedFaces.emplace(hash, orderedFace);
+ }
+ }
+ }
+ for (const auto & [ key, value ] : includedFaces) {
+ faces.push_back(value);
+ }
+ m_shape.init(vertices, faces, tets);
+ mainSystem.initFromVecs(vertices, tets);
+ }
+ m_shape.setModelMatrix(Affine3f(Eigen::Translation3f(0, 2, 0)));
+
+ initGround();
+ initExtra();
+}
+
+void Simulation::update(double seconds)
+{
+ // STUDENTS: This method should contain all the time-stepping logic for your simulation.
+ // Specifically, the code you write here should compute new, updated vertex positions for your
+ // simulation mesh, and it should then call m_shape.setVertices to update the display with those
+ // newly-updated vertices.
+
+ // STUDENTS: As currently written, the program will just continually compute simulation timesteps as long
+ // as the program is running (see View::tick in view.cpp) . You might want to e.g. add a hotkey for pausing
+ // the simulation, and perhaps start the simulation out in a paused state.
+
+ // Note that the "seconds" parameter represents the amount of time that has passed since
+ // the last update
+ if(this->estimationMode == EULER) {
+ mainSystem.updateCalculations();
+ mainSystem.updatePositions(seconds);
+ mainSystem.resolveCollisions();
+ }
+ else if (this->estimationMode == MIDPOINT) {
+ mainSystem.updateCalculations();
+ std::vector<Eigen::Vector3d> originalPositions = mainSystem.getPositions();
+ std::vector<Eigen::Vector3d> originalVelocities = mainSystem.getVelocities();
+ mainSystem.updatePositions(seconds / 2);
+ mainSystem.updateCalculations();
+ mainSystem.setVelocities(originalVelocities);
+ mainSystem.setPositions(originalPositions);
+ mainSystem.updatePositions(seconds);
+ mainSystem.resolveCollisions();
+ // m_shape.setVertices(mainSystem.getNodePos());
+ m_shape.setVerticesF(mainSystem.getNodePos(), mainSystem.getNodeForces());
+ } else if (this->estimationMode == ADAPTIVE) {
+ double errorTolerance = 1e-4;
+
+ std::vector<Eigen::Vector3d> originalPositions = mainSystem.getPositions();
+ std::vector<Eigen::Vector3d> originalVelocities = mainSystem.getVelocities();
+
+ mainSystem.updateCalculations();
+ mainSystem.updatePositions(seconds);
+ Eigen::VectorXd state1 = mainSystem.getState();
+
+ mainSystem.setVelocities(originalVelocities);
+ mainSystem.setPositions(originalPositions);
+ mainSystem.updatePositions(seconds / 2);
+ mainSystem.updateCalculations();
+ mainSystem.updatePositions(seconds / 2);
+ Eigen::VectorXd state2 = mainSystem.getState();
+
+ double newStepsize = seconds * std::sqrt(errorTolerance / (state1 - state2).norm());
+
+ mainSystem.setVelocities(originalVelocities);
+ mainSystem.setPositions(originalPositions);
+ mainSystem.updateCalculations();
+ mainSystem.updatePositions(newStepsize);
+ mainSystem.resolveCollisions();
+ m_shape.setVerticesF(mainSystem.getNodePos(), mainSystem.getNodeForces());
+ }
+}
+
+void Simulation::draw(Shader *shader)
+{
+ m_shape.draw(shader);
+ m_ground.draw(shader);
+ m_extra.draw(shader);
+}
+
+void Simulation::toggleWire()
+{
+ m_shape.toggleWireframe();
+}
+
+void Simulation::toggleForceRender() {
+ m_shape.toggleForce();
+}
+
+void Simulation::initGround()
+{
+ std::vector<Vector3d> groundVerts;
+ std::vector<Vector3i> groundFaces;
+ groundVerts.emplace_back(-5, 0, -5);
+ groundVerts.emplace_back(-5, 0, 5);
+ groundVerts.emplace_back(5, 0, 5);
+ groundVerts.emplace_back(5, 0, -5);
+ groundFaces.emplace_back(0, 1, 2);
+ groundFaces.emplace_back(0, 2, 3);
+ m_ground.init(groundVerts, groundFaces);
+}
+
+void Simulation::initExtra()
+{
+ std::vector<Vector3d> extraVerts;
+ std::vector<Vector4i> extraTets;
+ Eigen::Vector3d pos = Eigen::Vector3d(0, 0, 0);
+ if (MeshLoader::loadTetMesh("./example-meshes/sphere.mesh", extraVerts, extraTets)) {
+
+ std::vector<Vector3i> faces;
+ std::unordered_map<int, Eigen::Vector3i> includedFaces;
+ std::vector<Vector4i> faceOrders;
+
+ faceOrders.emplace_back(1, 0, 2, 3); // first three are the included points, last is the excluded point
+ faceOrders.emplace_back(2, 0, 3, 1);
+ faceOrders.emplace_back(3, 1, 2, 0);
+ faceOrders.emplace_back(3, 0, 1, 2);
+
+ for(Eigen::Vector3d& ev : extraVerts) {
+ ev += pos;
+ }
+
+ for(Vector4i t : extraTets) {
+ for(Vector4i fo : faceOrders) {
+ int hash = createFaceHash(t[fo[0]], t[fo[1]], t[fo[2]], extraVerts.size()); // Finding which vertex indexes the face has and ordering them from least to smallest
+ if(includedFaces.contains(hash)) {
+ includedFaces.erase(hash);
+ } else {
+ Eigen::Vector3i orderedFace;
+ Vector3d a = extraVerts.at(t[fo[0]]);
+ Vector3d b = extraVerts.at(t[fo[1]]);
+ Vector3d c = extraVerts.at(t[fo[2]]);
+ Vector3d d = extraVerts.at(t[fo[3]]);
+ if(calculatePointBehindNormal(a, b, c, d)) {
+ orderedFace = Eigen::Vector3i(t[fo[0]], t[fo[1]], t[fo[2]]); // Wind it backwards if the excluded point is behind the face
+ } else {
+ orderedFace = Eigen::Vector3i(t[fo[2]], t[fo[1]], t[fo[0]]); // Wind it backwards if the excluded point is in front of the face
+ }
+
+ includedFaces.emplace(hash, orderedFace);
+ }
+ }
+ }
+ for (const auto & [ key, value ] : includedFaces) {
+// std::cout << key << ": " << value << std::endl;
+ faces.push_back(value);
+ }
+ m_extra.init(extraVerts, faces);
+ m_extra.setColor(0.9, 0.8, 0.1);
+ }
+}
diff --git a/wave-sim/src/simulation.h b/wave-sim/src/simulation.h
new file mode 100644
index 0000000..68f2178
--- /dev/null
+++ b/wave-sim/src/simulation.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "graphics/shape.h"
+#include "system.h"
+
+class Shader;
+
+class Simulation
+{
+public:
+ Simulation();
+
+ void init();
+
+ void update(double seconds);
+
+ void draw(Shader *shader);
+
+ void toggleWire();
+
+ void toggleForceRender();
+
+ Eigen::Vector3d calculateFaceNormal(Eigen::Vector3d a, Eigen::Vector3d b, Eigen::Vector3d c);
+
+ bool calculatePointBehindNormal(Eigen::Vector3d a, Eigen::Vector3d b, Eigen::Vector3d c, Eigen::Vector3d p);
+
+ System mainSystem;
+
+ enum EstimationMode { EULER, MIDPOINT, ADAPTIVE };
+
+private:
+ Shape m_shape;
+
+ Shape m_ground;
+
+ Shape m_extra;
+
+ EstimationMode estimationMode = MIDPOINT;
+
+ void initGround();
+ void initExtra();
+};
diff --git a/wave-sim/src/system.cpp b/wave-sim/src/system.cpp
new file mode 100644
index 0000000..f0a4894
--- /dev/null
+++ b/wave-sim/src/system.cpp
@@ -0,0 +1,262 @@
+#include "system.h"
+#include <iostream>
+
+System::System()
+{
+ nodes = std::vector<Node*>();
+ tets = std::vector<Tet*>();
+}
+
+void System::initFromVecs(std::vector<Eigen::Vector3d> v, std::vector<Eigen::Vector4i> t) {
+ nodes = std::vector<Node*>();
+ tets = std::vector<Tet*>();
+
+ for(Eigen::Vector3d vertPos : v) {
+ nodes.push_back( new Node { vertPos } );
+ }
+ for(Eigen::Vector4i tetVerts : t) {
+ std::vector<Node*> definingNodes;
+ definingNodes.push_back(nodes.at(tetVerts[0]));
+ definingNodes.push_back(nodes.at(tetVerts[1]));
+ definingNodes.push_back(nodes.at(tetVerts[2]));
+ definingNodes.push_back(nodes.at(tetVerts[3]));
+ tets.push_back( new Tet(definingNodes) );
+ }
+ this->setParams( Params { } );
+}
+
+std::vector<Eigen::Vector3d> System::getPositions() {
+ std::vector<Eigen::Vector3d> res;
+ for(int i = 0 ; i < nodes.size(); ++i) {
+ res.push_back(nodes.at(i)->pos);
+ }
+
+ return res;
+}
+
+void System::setPositions(std::vector<Eigen::Vector3d> positions) {
+ assert(positions.size() == nodes.size());
+ for(int i = 0 ; i < positions.size(); ++i) {
+ nodes.at(i)->pos = positions.at(i);
+ }
+}
+
+std::vector<Eigen::Vector3d> System::getVelocities() {
+ std::vector<Eigen::Vector3d> res;
+ for(int i = 0 ; i < nodes.size(); ++i) {
+ res.push_back(nodes.at(i)->vel);
+ }
+
+ return res;
+}
+
+void System::setVelocities(std::vector<Eigen::Vector3d> velocities) {
+ assert(velocities.size() == nodes.size());
+ for(int i = 0 ; i < velocities.size(); ++i) {
+ nodes.at(i)->vel = velocities.at(i);
+ }
+}
+
+System::Face::Face(Node* a, Node* b, Node* c, Node* p){
+ area = (b->pos - a->pos).cross(c->pos - a->pos).norm() / 2;
+ normal = (b->pos - a->pos).cross(c->pos - a->pos).normalized();
+ if(normal.dot(p->pos - a->pos) >= 0) {
+ // If the normal faces towards the back point, flip it and wind nodes other way.
+ normal = -normal;
+ nodes.push_back(c);
+ nodes.push_back(b);
+ nodes.push_back(a);
+ }
+ else {
+ nodes.push_back(a);
+ nodes.push_back(b);
+ nodes.push_back(c);
+ }
+ opp = p;
+}
+
+void System::Face::calculateAreaNorms() {
+ Node* a = nodes.at(0);
+ Node* b = nodes.at(1);
+ Node* c = nodes.at(2);
+ this->area = (b->pos - a->pos).cross(c->pos - a->pos).norm() / 2;
+ this->normal = (b->pos - a->pos).cross(c->pos - a->pos).normalized();
+}
+
+System::Tet::Tet(std::vector<Node*> in_nodes) {
+ nodes = in_nodes;
+
+ Eigen::Vector3d p0 = in_nodes.at(0)->pos;
+ Eigen::Vector3d p1 = in_nodes.at(1)->pos;
+ Eigen::Vector3d p2 = in_nodes.at(2)->pos;
+ Eigen::Vector3d p3 = in_nodes.at(3)->pos;
+
+ Node* a = nodes.at(0);
+ Node* b = nodes.at(1);
+ Node* c = nodes.at(2);
+ Node* d = nodes.at(3); // Origin at d
+
+ // Face 0 defined by 1, 0, 2
+ // Face 1 defined by 2, 0, 3
+ // Face 2 defined by 3, 1, 2
+ // Face 3 defined by 3, 0, 1
+ faces.push_back(new Face(b, a, c, d));
+ faces.push_back(new Face(c, a, d, b));
+ faces.push_back(new Face(d, b, c, a));
+ faces.push_back(new Face(d, a, b, c));
+
+ betaMat << (a->pos - d->pos), (b->pos - d->pos), (c->pos - d->pos);
+
+ posMat = betaMat;
+ betaMat = betaMat.inverse().eval();
+ this->velMat << (a->vel - d->vel), (b->vel - d->vel), (c->vel - d->vel);
+
+ Eigen::Matrix4d massDeterminer;
+
+ massDeterminer << a->pos[0], a->pos[1], a->pos[2], 1,
+ b->pos[0], b->pos[1], b->pos[2], 1,
+ c->pos[0], c->pos[1], c->pos[2], 1,
+ d->pos[0], d->pos[1], d->pos[2], 1;
+
+ mass = this->_rho * (massDeterminer.determinant()) / 6.;
+
+ for(Node* n : this->nodes) {
+ n->mass += mass / 4;
+ }
+
+ for(Face* f : this->faces) {
+ f->calculateAreaNorms();
+// totalFaceArea += f->area;
+ }
+}
+
+void System::updateCalculations() {
+ for(Node* n : nodes) {
+ n->force = Eigen::Vector3d::Zero();
+ n->force = this->_gravity * n->mass;
+ }
+
+ for(Tet* t : tets) {
+ t->update();
+ }
+}
+
+void System::resolveCollisions() {
+ double groundLevel = -2;
+ double radius = 1;
+
+ for(Node* n : nodes) {
+ if(n->pos[1] < groundLevel && std::abs(n->pos[0]) < 5 && std::abs(n->pos[2]) < 5) {
+// n->vel[1] *= -1/(4) * (n->pos[1] + 2);
+ n->pos[1] = groundLevel + (groundLevel - n->pos[1]);
+ n->vel[0] /= this->groundTraction;
+ n->vel[1] = std::abs(n->vel[1]) / this->groundAbsorbance;
+ }
+ if(n->pos[0] * n->pos[0] + n->pos[2] * n->pos[2] + std::pow(n->pos[1] + 2, 2) < radius * radius) {
+ Eigen::Vector3d normal = (n->pos - Eigen::Vector3d(0, -2, 0)).normalized();
+// n->pos = 2 * normal * radius + Eigen::Vector3d(0, -2, 0) - (n->pos - Eigen::Vector3d(0, -2, 0));
+ n->pos = normal * radius + Eigen::Vector3d(0, -2, 0);
+ Eigen::Vector3d normalComponent = n->vel.dot(normal) * normal;
+ Eigen::Vector3d tangentComponent = n->vel - normalComponent;
+ n->vel = -(normalComponent / this->groundAbsorbance) - tangentComponent / this->groundTraction;
+ }
+ }
+}
+
+void System::setParams(Params params) {
+ for(Tet* t : tets) {
+ t->_lambda = params._lambda;
+ t->_mu = params._mu;
+ t->_rho = params._rho;
+ t->_phi = params._phi;
+ t->_psi = params._psi;
+ }
+ this->_gravity = params.gravity;
+ this->groundAbsorbance = params._absorbance;
+ this->groundTraction = params._traction;
+}
+
+void System::updatePositions(double deltaTime) {
+// double totalFaceArea = 0;
+ for(Node* n : nodes) {
+ n->acc = n->force * (1 / n->mass);
+ n->vel += n->acc * deltaTime;
+ n->pos += n->vel * deltaTime;
+ }
+// this->resolveCollisions();
+// std::cout << totalFaceArea << std::endl;
+}
+
+void System::Tet::update() {
+// for(Face* f : this->faces) {
+// f->calculateAreaNorms();
+//// totalFaceArea += f->area;
+// }
+
+ Node* a = nodes.at(0);
+ Node* b = nodes.at(1);
+ Node* c = nodes.at(2);
+ Node* d = nodes.at(3); // Origin at d
+
+ this->posMat << (a->pos - d->pos), (b->pos - d->pos), (c->pos - d->pos);
+ this->velMat << (a->vel - d->vel), (b->vel - d->vel), (c->vel - d->vel);
+
+ this->FMat = posMat * betaMat;
+
+// Eigen::Matrix3d test;
+// test << Eigen::Vector3d(1, 2, 3), Eigen::Vector3d(4, 5, 6), Eigen::Vector3d(7, 8, 9);
+
+ if(this->FMat.isApprox(Eigen::Matrix3d::Identity())) {
+ this->FMat = Eigen::Matrix3d::Identity();
+ }
+
+ this->strain = (FMat.transpose() * FMat) - Eigen::Matrix3d::Identity();
+ this->strainRate = ((posMat * betaMat).transpose() * (velMat * betaMat)) + ((velMat * betaMat).transpose() * (posMat * betaMat));
+
+ Eigen::Matrix3d stressFromStrain = (this->_lambda * Eigen::Matrix3d::Identity() * strain.trace()) + (2 * this->_mu * strain);
+ Eigen::Matrix3d stressFromStrainRate = (this->_phi * Eigen::Matrix3d::Identity() * strainRate.trace()) + (2 * this->_psi * strainRate);
+ this->stress = stressFromStrain + stressFromStrainRate;
+
+// for(Node* n : this->nodes) {
+//// n->force = Eigen::Vector3d(0., -.1, 0.) * n->mass;
+//// n->force = Eigen::Vector3d(0, 0, 0);
+//// n->acc = Ei
+// }
+
+ for(Face* f : this->faces) {
+ Eigen::Vector3d faceForce = this->FMat * this->stress * f->area * f->normal;
+ for(Node* n : f->nodes) {
+ n->force += faceForce / 3;
+ }
+// f->opp->force -= faceForce;
+ }
+}
+
+std::vector<Eigen::Vector3d> System::getNodePos() {
+ std::vector<Eigen::Vector3d> res;
+ for(Node* n : nodes) {
+ res.push_back(n->pos);
+ }
+ return res;
+}
+
+std::vector<Eigen::Vector3d> System::getNodeForces() {
+ std::vector<Eigen::Vector3d> res;
+ for(Node* n : nodes) {
+ res.push_back(n->force);
+ }
+ return res;
+}
+
+Eigen::VectorXd System::getState() {
+ std::vector<double> state;
+ for(Node* n : this->nodes) {
+ state.push_back(n->pos[0]);
+ state.push_back(n->pos[1]);
+ state.push_back(n->pos[2]);
+ state.push_back(n->vel[0]);
+ state.push_back(n->vel[1]);
+ state.push_back(n->vel[2]);
+ }
+ return Eigen::Map<Eigen::VectorXd>(state.data(), state.size());
+}
diff --git a/wave-sim/src/system.h b/wave-sim/src/system.h
new file mode 100644
index 0000000..289aca3
--- /dev/null
+++ b/wave-sim/src/system.h
@@ -0,0 +1,99 @@
+#pragma once
+
+#include <Eigen/Dense>
+#include <vector>
+
+class System
+{
+public:
+ System();
+
+ struct Node;
+ struct Tet;
+
+ Eigen::Vector3d _gravity = Eigen::Vector3d(0., -1.0, 0.);
+ double groundAbsorbance = 4e4;
+ double groundTraction = 4e4;
+
+ std::vector<Node*> nodes;
+ std::vector<Tet*> tets;
+
+ std::vector<Eigen::Vector3d> getPositions();
+ void setPositions(std::vector<Eigen::Vector3d> positions);
+
+ std::vector<Eigen::Vector3d> getVelocities();
+ void setVelocities(std::vector<Eigen::Vector3d> velocities);
+
+ void initFromVecs(std::vector<Eigen::Vector3d> v, std::vector<Eigen::Vector4i> t);
+
+ struct Params {
+ const Eigen::Vector3d gravity = Eigen::Vector3d(0, -1, 0);
+ const double _lambda = 1e4; //incompressibility for the whole material
+ const double _mu = 1e4; //rigidity for the whole material
+ const double _rho = 1200.; //density
+ const double _phi = 1e1; //coefficients of viscosity
+ const double _psi = 1e1;
+ const double _traction = 4e4;
+ const double _absorbance = 1e1;
+ };
+
+ void setParams(Params params);
+
+ struct Node {
+ Eigen::Vector3d pos;
+ double mass = 0;
+
+ Eigen::Vector3d vel = Eigen::Vector3d(0., 0., 0.);
+ Eigen::Vector3d force = Eigen::Vector3d(0., 0., 0.);
+ Eigen::Vector3d acc = Eigen::Vector3d(0., 0., 0.);
+ };
+
+ struct Face {
+ double area;
+ Eigen::Vector3d normal;
+
+ std::vector<Node*> nodes;
+ Node* opp;
+
+ // P is unused point we use to see if normal is facing right way.
+ Face(Node* a, Node* b, Node* c, Node* p);
+
+ void calculateAreaNorms();
+ };
+
+ struct Tet {
+ double _lambda = 1e4; //incompressibility for the whole material
+ double _mu = 1e4; //rigidity for the whole material
+ double _rho = 1200.; //density
+ double _phi = 1e1; //coefficients of viscosity
+ double _psi = 1e1;
+
+ std::vector<Node*> nodes;
+// Eigen::Vector4d faceAreas;
+// std::vector<Eigen::Vector3d> normals;
+ std::vector<Face*> faces;
+ Eigen::Matrix3d betaMat;
+ Eigen::Matrix3d posMat;
+ Eigen::Matrix3d velMat;
+
+ Eigen::Matrix3d FMat;
+ Eigen::Matrix3d strain;
+ Eigen::Matrix3d strainRate;
+ Eigen::Matrix3d stress;
+
+ double mass;
+
+ Tet(std::vector<Node*> in_nodes);
+
+ void update();
+ };
+
+ void updateCalculations();
+ void resolveCollisions();
+ void updatePositions(double deltaTime);
+
+ std::vector<Eigen::Vector3d> getNodePos();
+ std::vector<Eigen::Vector3d> getNodeForces();
+
+ Eigen::VectorXd getState();
+};
diff --git a/wave-sim/submission-final.md b/wave-sim/submission-final.md
new file mode 100644
index 0000000..5da5c6e
--- /dev/null
+++ b/wave-sim/submission-final.md
@@ -0,0 +1,52 @@
+## FEM (final submission)
+
+Please fill this out and submit your work to Gradescope by the deadline.
+
+### Output Video
+![](final2.gif)
+
+### Design Choices
+- Extracting the surface mesh.
+
+ I extracted the surface mesh by first creating a hashing function for faces that orders the face indices from least to greatest and turns them into a unique integer where the first number is multiplied by the total number of vertices squared, the second number is multiplied by the total number of vertices, and the largest number by 1 and then they're all added together. Then I created a hashmap from integers to faces. When I went to add a face in the mesh, I hashed the face, and checked if the hashmap already had the face; if it didn't I added the { hash, face } pair to the hashmap or if it did I deleted the face from the hashmap. Then when I had checked all the faces I iterated through all of the values of the hashmap and added them to an std::vector which I used to initialize the mesh. (Sorry I know that was more than 3 sentences, but I couldn't condense it).
+
+- computing and applying internal forces
+ I had a `Node` class that kept track of velocity, force, position, and acceleration; I had a `Face` class that kept track of its original area, normal, the nodes that define it and the node opposite it on the tetrahedron; finally, I had a `System` class that kept track of all the nodes and the tetrahedrons in the system. The tetrahedrons kept track of all of their faces, and the faces, again, pointed to the corresponding nodes, so that I could perform all the necessary calculations to calculate stress and strain in `updateCalculations` and actually apply them to the nodes in `updatePositions`.
+
+- collision resolution
+ I had a method in the `System` class called `resolveCollisions` which used implicit equations to check if the body was inside the collider.
+
+- your explicit integration method
+ I used the midpoint method by first saving the current positions and velocities, updating calculations, stepping forward by 1 half of the time step, updating calculations, resetting the positions and velocities, and stepping forward with `updatePositions` by one time step.
+ I also did adaptive time step detailed below.
+
+### Extra Features
+Briefly explain your implementation of any extra features, provide output images, and describe what each image demonstrates.
+
+#### Make the visualizer pretty (5 points)
+
+I implemented a shader that would show how forces were applied throughout the body at different tetrahedrons.
+
+![](finalForce.gif)
+
+#### Adaptive time stepping (5 points)
+
+I implemented adaptive time stepping by saving the initial positions and velocities, stepping a whole step, storing all positions and velocities in a single vector, reverting, stepping forward two half steps, getting the state from all positions and velocities, and performing calculations accordingly according to the paper.
+
+### Collaboration/References
+
+I collaborated with classmates in collaborative hours including:
+
+<ul>
+
+<li> Autumn Tilley (atilley) </li>
+<li> Jean Yoo </li>
+<li> Jamie Chen </li>
+<li> Luke Riley </li>
+<li> Dylan Lee </li>
+
+</ul>
+
+### Known Bugs
+
+None \ No newline at end of file
diff --git a/wave-sim/util/tiny_obj_loader.h b/wave-sim/util/tiny_obj_loader.h
new file mode 100644
index 0000000..474e088
--- /dev/null
+++ b/wave-sim/util/tiny_obj_loader.h
@@ -0,0 +1,2242 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2012-2017 Syoyo Fujita and many contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+//
+// version 1.1.0 : Support parsing vertex color(#144)
+// version 1.0.8 : Fix parsing `g` tag just after `usemtl`(#138)
+// version 1.0.7 : Support multiple tex options(#126)
+// version 1.0.6 : Add TINYOBJLOADER_USE_DOUBLE option(#124)
+// version 1.0.5 : Ignore `Tr` when `d` exists in MTL(#43)
+// version 1.0.4 : Support multiple filenames for 'mtllib'(#112)
+// version 1.0.3 : Support parsing texture options(#85)
+// version 1.0.2 : Improve parsing speed by about a factor of 2 for large
+// files(#105)
+// version 1.0.1 : Fixes a shape is lost if obj ends with a 'usemtl'(#104)
+// version 1.0.0 : Change data structure. Change license from BSD to MIT.
+//
+
+//
+// Use this in *one* .cc
+// #define TINYOBJLOADER_IMPLEMENTATION
+// #include "tiny_obj_loader.h"
+//
+
+#ifndef TINY_OBJ_LOADER_H_
+#define TINY_OBJ_LOADER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace tinyobj {
+
+// https://en.wikipedia.org/wiki/Wavefront_.obj_file says ...
+//
+// -blendu on | off # set horizontal texture blending
+// (default on)
+// -blendv on | off # set vertical texture blending
+// (default on)
+// -boost real_value # boost mip-map sharpness
+// -mm base_value gain_value # modify texture map values (default
+// 0 1)
+// # base_value = brightness,
+// gain_value = contrast
+// -o u [v [w]] # Origin offset (default
+// 0 0 0)
+// -s u [v [w]] # Scale (default
+// 1 1 1)
+// -t u [v [w]] # Turbulence (default
+// 0 0 0)
+// -texres resolution # texture resolution to create
+// -clamp on | off # only render texels in the clamped
+// 0-1 range (default off)
+// # When unclamped, textures are
+// repeated across a surface,
+// # when clamped, only texels which
+// fall within the 0-1
+// # range are rendered.
+// -bm mult_value # bump multiplier (for bump maps
+// only)
+//
+// -imfchan r | g | b | m | l | z # specifies which channel of the file
+// is used to
+// # create a scalar or bump texture.
+// r:red, g:green,
+// # b:blue, m:matte, l:luminance,
+// z:z-depth..
+// # (the default for bump is 'l' and
+// for decal is 'm')
+// bump -imfchan r bumpmap.tga # says to use the red channel of
+// bumpmap.tga as the bumpmap
+//
+// For reflection maps...
+//
+// -type sphere # specifies a sphere for a "refl"
+// reflection map
+// -type cube_top | cube_bottom | # when using a cube map, the texture
+// file for each
+// cube_front | cube_back | # side of the cube is specified
+// separately
+// cube_left | cube_right
+
+#ifdef TINYOBJLOADER_USE_DOUBLE
+//#pragma message "using double"
+typedef double real_t;
+#else
+//#pragma message "using float"
+typedef float real_t;
+#endif
+
+typedef enum {
+ TEXTURE_TYPE_NONE, // default
+ TEXTURE_TYPE_SPHERE,
+ TEXTURE_TYPE_CUBE_TOP,
+ TEXTURE_TYPE_CUBE_BOTTOM,
+ TEXTURE_TYPE_CUBE_FRONT,
+ TEXTURE_TYPE_CUBE_BACK,
+ TEXTURE_TYPE_CUBE_LEFT,
+ TEXTURE_TYPE_CUBE_RIGHT
+} texture_type_t;
+
+typedef struct {
+ texture_type_t type; // -type (default TEXTURE_TYPE_NONE)
+ real_t sharpness; // -boost (default 1.0?)
+ real_t brightness; // base_value in -mm option (default 0)
+ real_t contrast; // gain_value in -mm option (default 1)
+ real_t origin_offset[3]; // -o u [v [w]] (default 0 0 0)
+ real_t scale[3]; // -s u [v [w]] (default 1 1 1)
+ real_t turbulence[3]; // -t u [v [w]] (default 0 0 0)
+ // int texture_resolution; // -texres resolution (default = ?) TODO
+ bool clamp; // -clamp (default false)
+ char imfchan; // -imfchan (the default for bump is 'l' and for decal is 'm')
+ bool blendu; // -blendu (default on)
+ bool blendv; // -blendv (default on)
+ real_t bump_multiplier; // -bm (for bump maps only, default 1.0)
+} texture_option_t;
+
+typedef struct {
+ std::string name;
+
+ real_t ambient[3];
+ real_t diffuse[3];
+ real_t specular[3];
+ real_t transmittance[3];
+ real_t emission[3];
+ real_t shininess;
+ real_t ior; // index of refraction
+ real_t dissolve; // 1 == opaque; 0 == fully transparent
+ // illumination model (see http://www.fileformat.info/format/material/)
+ int illum;
+
+ int dummy; // Suppress padding warning.
+
+ std::string ambient_texname; // map_Ka
+ std::string diffuse_texname; // map_Kd
+ std::string specular_texname; // map_Ks
+ std::string specular_highlight_texname; // map_Ns
+ std::string bump_texname; // map_bump, map_Bump, bump
+ std::string displacement_texname; // disp
+ std::string alpha_texname; // map_d
+ std::string reflection_texname; // refl
+
+ texture_option_t ambient_texopt;
+ texture_option_t diffuse_texopt;
+ texture_option_t specular_texopt;
+ texture_option_t specular_highlight_texopt;
+ texture_option_t bump_texopt;
+ texture_option_t displacement_texopt;
+ texture_option_t alpha_texopt;
+ texture_option_t reflection_texopt;
+
+ // PBR extension
+ // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr
+ real_t roughness; // [0, 1] default 0
+ real_t metallic; // [0, 1] default 0
+ real_t sheen; // [0, 1] default 0
+ real_t clearcoat_thickness; // [0, 1] default 0
+ real_t clearcoat_roughness; // [0, 1] default 0
+ real_t anisotropy; // aniso. [0, 1] default 0
+ real_t anisotropy_rotation; // anisor. [0, 1] default 0
+ real_t pad0;
+ std::string roughness_texname; // map_Pr
+ std::string metallic_texname; // map_Pm
+ std::string sheen_texname; // map_Ps
+ std::string emissive_texname; // map_Ke
+ std::string normal_texname; // norm. For normal mapping.
+
+ texture_option_t roughness_texopt;
+ texture_option_t metallic_texopt;
+ texture_option_t sheen_texopt;
+ texture_option_t emissive_texopt;
+ texture_option_t normal_texopt;
+
+ int pad2;
+
+ std::map<std::string, std::string> unknown_parameter;
+} material_t;
+
+typedef struct {
+ std::string name;
+
+ std::vector<int> intValues;
+ std::vector<real_t> floatValues;
+ std::vector<std::string> stringValues;
+} tag_t;
+
+// Index struct to support different indices for vtx/normal/texcoord.
+// -1 means not used.
+typedef struct {
+ int vertex_index;
+ int normal_index;
+ int texcoord_index;
+} index_t;
+
+typedef struct {
+ std::vector<index_t> indices;
+ std::vector<unsigned char> num_face_vertices; // The number of vertices per
+ // face. 3 = polygon, 4 = quad,
+ // ... Up to 255.
+ std::vector<int> material_ids; // per-face material ID
+ std::vector<tag_t> tags; // SubD tag
+} mesh_t;
+
+typedef struct {
+ std::string name;
+ mesh_t mesh;
+} shape_t;
+
+// Vertex attributes
+typedef struct {
+ std::vector<real_t> vertices; // 'v'
+ std::vector<real_t> normals; // 'vn'
+ std::vector<real_t> texcoords; // 'vt'
+ std::vector<real_t> colors; // extension: vertex colors
+} attrib_t;
+
+typedef struct callback_t_ {
+ // W is optional and set to 1 if there is no `w` item in `v` line
+ void (*vertex_cb)(void *user_data, real_t x, real_t y, real_t z, real_t w);
+ void (*normal_cb)(void *user_data, real_t x, real_t y, real_t z);
+
+ // y and z are optional and set to 0 if there is no `y` and/or `z` item(s) in
+ // `vt` line.
+ void (*texcoord_cb)(void *user_data, real_t x, real_t y, real_t z);
+
+ // called per 'f' line. num_indices is the number of face indices(e.g. 3 for
+ // triangle, 4 for quad)
+ // 0 will be passed for undefined index in index_t members.
+ void (*index_cb)(void *user_data, index_t *indices, int num_indices);
+ // `name` material name, `material_id` = the array index of material_t[]. -1
+ // if
+ // a material not found in .mtl
+ void (*usemtl_cb)(void *user_data, const char *name, int material_id);
+ // `materials` = parsed material data.
+ void (*mtllib_cb)(void *user_data, const material_t *materials,
+ int num_materials);
+ // There may be multiple group names
+ void (*group_cb)(void *user_data, const char **names, int num_names);
+ void (*object_cb)(void *user_data, const char *name);
+
+ callback_t_()
+ : vertex_cb(NULL),
+ normal_cb(NULL),
+ texcoord_cb(NULL),
+ index_cb(NULL),
+ usemtl_cb(NULL),
+ mtllib_cb(NULL),
+ group_cb(NULL),
+ object_cb(NULL) {}
+} callback_t;
+
+class MaterialReader {
+ public:
+ MaterialReader() {}
+ virtual ~MaterialReader();
+
+ virtual bool operator()(const std::string &matId,
+ std::vector<material_t> *materials,
+ std::map<std::string, int> *matMap,
+ std::string *err) = 0;
+};
+
+class MaterialFileReader : public MaterialReader {
+ public:
+ explicit MaterialFileReader(const std::string &mtl_basedir)
+ : m_mtlBaseDir(mtl_basedir) {}
+ virtual ~MaterialFileReader() {}
+ virtual bool operator()(const std::string &matId,
+ std::vector<material_t> *materials,
+ std::map<std::string, int> *matMap, std::string *err);
+
+ private:
+ std::string m_mtlBaseDir;
+};
+
+class MaterialStreamReader : public MaterialReader {
+ public:
+ explicit MaterialStreamReader(std::istream &inStream)
+ : m_inStream(inStream) {}
+ virtual ~MaterialStreamReader() {}
+ virtual bool operator()(const std::string &matId,
+ std::vector<material_t> *materials,
+ std::map<std::string, int> *matMap, std::string *err);
+
+ private:
+ std::istream &m_inStream;
+};
+
+/// Loads .obj from a file.
+/// 'attrib', 'shapes' and 'materials' will be filled with parsed shape data
+/// 'shapes' will be filled with parsed shape data
+/// Returns true when loading .obj become success.
+/// Returns warning and error message into `err`
+/// 'mtl_basedir' is optional, and used for base directory for .mtl file.
+/// In default(`NULL'), .mtl file is searched from an application's working
+/// directory.
+/// 'triangulate' is optional, and used whether triangulate polygon face in .obj
+/// or not.
+bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
+ std::vector<material_t> *materials, std::string *err,
+ const char *filename, const char *mtl_basedir = NULL,
+ bool triangulate = true);
+
+/// Loads .obj from a file with custom user callback.
+/// .mtl is loaded as usual and parsed material_t data will be passed to
+/// `callback.mtllib_cb`.
+/// Returns true when loading .obj/.mtl become success.
+/// Returns warning and error message into `err`
+/// See `examples/callback_api/` for how to use this function.
+bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback,
+ void *user_data = NULL,
+ MaterialReader *readMatFn = NULL,
+ std::string *err = NULL);
+
+/// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve
+/// std::istream for materials.
+/// Returns true when loading .obj become success.
+/// Returns warning and error message into `err`
+bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
+ std::vector<material_t> *materials, std::string *err,
+ std::istream *inStream, MaterialReader *readMatFn = NULL,
+ bool triangulate = true);
+
+/// Loads materials into std::map
+void LoadMtl(std::map<std::string, int> *material_map,
+ std::vector<material_t> *materials, std::istream *inStream,
+ std::string *warning);
+
+} // namespace tinyobj
+
+#endif // TINY_OBJ_LOADER_H_
+
+#ifdef TINYOBJLOADER_IMPLEMENTATION
+#include <cassert>
+#include <cctype>
+#include <cmath>
+#include <cstddef>
+#include <cstdlib>
+#include <cstring>
+#include <utility>
+
+#include <fstream>
+#include <sstream>
+
+namespace tinyobj {
+
+MaterialReader::~MaterialReader() {}
+
+struct vertex_index {
+ int v_idx, vt_idx, vn_idx;
+ vertex_index() : v_idx(-1), vt_idx(-1), vn_idx(-1) {}
+ explicit vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {}
+ vertex_index(int vidx, int vtidx, int vnidx)
+ : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {}
+};
+
+struct tag_sizes {
+ tag_sizes() : num_ints(0), num_reals(0), num_strings(0) {}
+ int num_ints;
+ int num_reals;
+ int num_strings;
+};
+
+struct obj_shape {
+ std::vector<real_t> v;
+ std::vector<real_t> vn;
+ std::vector<real_t> vt;
+};
+
+// See
+// http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf
+static std::istream &safeGetline(std::istream &is, std::string &t) {
+ t.clear();
+
+ // The characters in the stream are read one-by-one using a std::streambuf.
+ // That is faster than reading them one-by-one using the std::istream.
+ // Code that uses streambuf this way must be guarded by a sentry object.
+ // The sentry object performs various tasks,
+ // such as thread synchronization and updating the stream state.
+
+ std::istream::sentry se(is, true);
+ std::streambuf *sb = is.rdbuf();
+
+ if (se) {
+ for (;;) {
+ int c = sb->sbumpc();
+ switch (c) {
+ case '\n':
+ return is;
+ case '\r':
+ if (sb->sgetc() == '\n') sb->sbumpc();
+ return is;
+ case EOF:
+ // Also handle the case when the last line has no line ending
+ if (t.empty()) is.setstate(std::ios::eofbit);
+ return is;
+ default:
+ t += static_cast<char>(c);
+ }
+ }
+ }
+
+ return is;
+}
+
+#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t'))
+#define IS_DIGIT(x) \
+ (static_cast<unsigned int>((x) - '0') < static_cast<unsigned int>(10))
+#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0'))
+
+// Make index zero-base, and also support relative index.
+static inline bool fixIndex(int idx, int n, int *ret) {
+ if (!ret) {
+ return false;
+ }
+
+ if (idx > 0) {
+ (*ret) = idx - 1;
+ return true;
+ }
+
+ if (idx == 0) {
+ // zero is not allowed according to the spec.
+ return false;
+ }
+
+ if (idx < 0) {
+ (*ret) = n + idx; // negative value = relative
+ return true;
+ }
+
+ return false; // never reach here.
+}
+
+static inline std::string parseString(const char **token) {
+ std::string s;
+ (*token) += strspn((*token), " \t");
+ size_t e = strcspn((*token), " \t\r");
+ s = std::string((*token), &(*token)[e]);
+ (*token) += e;
+ return s;
+}
+
+static inline int parseInt(const char **token) {
+ (*token) += strspn((*token), " \t");
+ int i = atoi((*token));
+ (*token) += strcspn((*token), " \t\r");
+ return i;
+}
+
+// Tries to parse a floating point number located at s.
+//
+// s_end should be a location in the string where reading should absolutely
+// stop. For example at the end of the string, to prevent buffer overflows.
+//
+// Parses the following EBNF grammar:
+// sign = "+" | "-" ;
+// END = ? anything not in digit ?
+// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
+// integer = [sign] , digit , {digit} ;
+// decimal = integer , ["." , integer] ;
+// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ;
+//
+// Valid strings are for example:
+// -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2
+//
+// If the parsing is a success, result is set to the parsed value and true
+// is returned.
+//
+// The function is greedy and will parse until any of the following happens:
+// - a non-conforming character is encountered.
+// - s_end is reached.
+//
+// The following situations triggers a failure:
+// - s >= s_end.
+// - parse failure.
+//
+static bool tryParseDouble(const char *s, const char *s_end, double *result) {
+ if (s >= s_end) {
+ return false;
+ }
+
+ double mantissa = 0.0;
+ // This exponent is base 2 rather than 10.
+ // However the exponent we parse is supposed to be one of ten,
+ // thus we must take care to convert the exponent/and or the
+ // mantissa to a * 2^E, where a is the mantissa and E is the
+ // exponent.
+ // To get the final double we will use ldexp, it requires the
+ // exponent to be in base 2.
+ int exponent = 0;
+
+ // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED
+ // TO JUMP OVER DEFINITIONS.
+ char sign = '+';
+ char exp_sign = '+';
+ char const *curr = s;
+
+ // How many characters were read in a loop.
+ int read = 0;
+ // Tells whether a loop terminated due to reaching s_end.
+ bool end_not_reached = false;
+
+ /*
+ BEGIN PARSING.
+ */
+
+ // Find out what sign we've got.
+ if (*curr == '+' || *curr == '-') {
+ sign = *curr;
+ curr++;
+ } else if (IS_DIGIT(*curr)) { /* Pass through. */
+ } else {
+ goto fail;
+ }
+
+ // Read the integer part.
+ end_not_reached = (curr != s_end);
+ while (end_not_reached && IS_DIGIT(*curr)) {
+ mantissa *= 10;
+ mantissa += static_cast<int>(*curr - 0x30);
+ curr++;
+ read++;
+ end_not_reached = (curr != s_end);
+ }
+
+ // We must make sure we actually got something.
+ if (read == 0) goto fail;
+ // We allow numbers of form "#", "###" etc.
+ if (!end_not_reached) goto assemble;
+
+ // Read the decimal part.
+ if (*curr == '.') {
+ curr++;
+ read = 1;
+ end_not_reached = (curr != s_end);
+ while (end_not_reached && IS_DIGIT(*curr)) {
+ static const double pow_lut[] = {
+ 1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001,
+ };
+ const int lut_entries = sizeof pow_lut / sizeof pow_lut[0];
+
+ // NOTE: Don't use powf here, it will absolutely murder precision.
+ mantissa += static_cast<int>(*curr - 0x30) *
+ (read < lut_entries ? pow_lut[read] : std::pow(10.0, -read));
+ read++;
+ curr++;
+ end_not_reached = (curr != s_end);
+ }
+ } else if (*curr == 'e' || *curr == 'E') {
+ } else {
+ goto assemble;
+ }
+
+ if (!end_not_reached) goto assemble;
+
+ // Read the exponent part.
+ if (*curr == 'e' || *curr == 'E') {
+ curr++;
+ // Figure out if a sign is present and if it is.
+ end_not_reached = (curr != s_end);
+ if (end_not_reached && (*curr == '+' || *curr == '-')) {
+ exp_sign = *curr;
+ curr++;
+ } else if (IS_DIGIT(*curr)) { /* Pass through. */
+ } else {
+ // Empty E is not allowed.
+ goto fail;
+ }
+
+ read = 0;
+ end_not_reached = (curr != s_end);
+ while (end_not_reached && IS_DIGIT(*curr)) {
+ exponent *= 10;
+ exponent += static_cast<int>(*curr - 0x30);
+ curr++;
+ read++;
+ end_not_reached = (curr != s_end);
+ }
+ exponent *= (exp_sign == '+' ? 1 : -1);
+ if (read == 0) goto fail;
+ }
+
+assemble:
+ *result = (sign == '+' ? 1 : -1) *
+ (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent)
+ : mantissa);
+ return true;
+fail:
+ return false;
+}
+
+static inline real_t parseReal(const char **token, double default_value = 0.0) {
+ (*token) += strspn((*token), " \t");
+ const char *end = (*token) + strcspn((*token), " \t\r");
+ double val = default_value;
+ tryParseDouble((*token), end, &val);
+ real_t f = static_cast<real_t>(val);
+ (*token) = end;
+ return f;
+}
+
+static inline bool parseReal(const char **token, real_t *out) {
+ (*token) += strspn((*token), " \t");
+ const char *end = (*token) + strcspn((*token), " \t\r");
+ double val;
+ bool ret = tryParseDouble((*token), end, &val);
+ if (ret) {
+ real_t f = static_cast<real_t>(val);
+ (*out) = f;
+ }
+ (*token) = end;
+ return ret;
+}
+
+static inline void parseReal2(real_t *x, real_t *y, const char **token,
+ const double default_x = 0.0,
+ const double default_y = 0.0) {
+ (*x) = parseReal(token, default_x);
+ (*y) = parseReal(token, default_y);
+}
+
+static inline void parseReal3(real_t *x, real_t *y, real_t *z,
+ const char **token, const double default_x = 0.0,
+ const double default_y = 0.0,
+ const double default_z = 0.0) {
+ (*x) = parseReal(token, default_x);
+ (*y) = parseReal(token, default_y);
+ (*z) = parseReal(token, default_z);
+}
+
+static inline void parseV(real_t *x, real_t *y, real_t *z, real_t *w,
+ const char **token, const double default_x = 0.0,
+ const double default_y = 0.0,
+ const double default_z = 0.0,
+ const double default_w = 1.0) {
+ (*x) = parseReal(token, default_x);
+ (*y) = parseReal(token, default_y);
+ (*z) = parseReal(token, default_z);
+ (*w) = parseReal(token, default_w);
+}
+
+// Extension: parse vertex with colors(6 items)
+static inline bool parseVertexWithColor(real_t *x, real_t *y, real_t *z, real_t *r,
+ real_t *g, real_t *b,
+ const char **token, const double default_x = 0.0,
+ const double default_y = 0.0,
+ const double default_z = 0.0) {
+ (*x) = parseReal(token, default_x);
+ (*y) = parseReal(token, default_y);
+ (*z) = parseReal(token, default_z);
+
+ (*r) = parseReal(token, 1.0);
+ (*g) = parseReal(token, 1.0);
+ (*b) = parseReal(token, 1.0);
+
+ return true;
+}
+
+static inline bool parseOnOff(const char **token, bool default_value = true) {
+ (*token) += strspn((*token), " \t");
+ const char *end = (*token) + strcspn((*token), " \t\r");
+
+ bool ret = default_value;
+ if ((0 == strncmp((*token), "on", 2))) {
+ ret = true;
+ } else if ((0 == strncmp((*token), "off", 3))) {
+ ret = false;
+ }
+
+ (*token) = end;
+ return ret;
+}
+
+static inline texture_type_t parseTextureType(
+ const char **token, texture_type_t default_value = TEXTURE_TYPE_NONE) {
+ (*token) += strspn((*token), " \t");
+ const char *end = (*token) + strcspn((*token), " \t\r");
+ texture_type_t ty = default_value;
+
+ if ((0 == strncmp((*token), "cube_top", strlen("cube_top")))) {
+ ty = TEXTURE_TYPE_CUBE_TOP;
+ } else if ((0 == strncmp((*token), "cube_bottom", strlen("cube_bottom")))) {
+ ty = TEXTURE_TYPE_CUBE_BOTTOM;
+ } else if ((0 == strncmp((*token), "cube_left", strlen("cube_left")))) {
+ ty = TEXTURE_TYPE_CUBE_LEFT;
+ } else if ((0 == strncmp((*token), "cube_right", strlen("cube_right")))) {
+ ty = TEXTURE_TYPE_CUBE_RIGHT;
+ } else if ((0 == strncmp((*token), "cube_front", strlen("cube_front")))) {
+ ty = TEXTURE_TYPE_CUBE_FRONT;
+ } else if ((0 == strncmp((*token), "cube_back", strlen("cube_back")))) {
+ ty = TEXTURE_TYPE_CUBE_BACK;
+ } else if ((0 == strncmp((*token), "sphere", strlen("sphere")))) {
+ ty = TEXTURE_TYPE_SPHERE;
+ }
+
+ (*token) = end;
+ return ty;
+}
+
+static tag_sizes parseTagTriple(const char **token) {
+ tag_sizes ts;
+
+ (*token) += strspn((*token), " \t");
+ ts.num_ints = atoi((*token));
+ (*token) += strcspn((*token), "/ \t\r");
+ if ((*token)[0] != '/') {
+ return ts;
+ }
+
+ (*token)++; // Skip '/'
+
+ (*token) += strspn((*token), " \t");
+ ts.num_reals = atoi((*token));
+ (*token) += strcspn((*token), "/ \t\r");
+ if ((*token)[0] != '/') {
+ return ts;
+ }
+ (*token)++; // Skip '/'
+
+ ts.num_strings = parseInt(token);
+
+ return ts;
+}
+
+// Parse triples with index offsets: i, i/j/k, i//k, i/j
+static bool parseTriple(const char **token, int vsize, int vnsize, int vtsize,
+ vertex_index *ret) {
+ if (!ret) {
+ return false;
+ }
+
+ vertex_index vi(-1);
+
+ if (!fixIndex(atoi((*token)), vsize, &(vi.v_idx))) {
+ return false;
+ }
+
+ (*token) += strcspn((*token), "/ \t\r");
+ if ((*token)[0] != '/') {
+ (*ret) = vi;
+ return true;
+ }
+ (*token)++;
+
+ // i//k
+ if ((*token)[0] == '/') {
+ (*token)++;
+ if (!fixIndex(atoi((*token)), vnsize, &(vi.vn_idx))) {
+ return false;
+ }
+ (*token) += strcspn((*token), "/ \t\r");
+ (*ret) = vi;
+ return true;
+ }
+
+ // i/j/k or i/j
+ if (!fixIndex(atoi((*token)), vtsize, &(vi.vt_idx))) {
+ return false;
+ }
+
+ (*token) += strcspn((*token), "/ \t\r");
+ if ((*token)[0] != '/') {
+ (*ret) = vi;
+ return true;
+ }
+
+ // i/j/k
+ (*token)++; // skip '/'
+ if (!fixIndex(atoi((*token)), vnsize, &(vi.vn_idx))) {
+ return false;
+ }
+ (*token) += strcspn((*token), "/ \t\r");
+
+ (*ret) = vi;
+
+ return true;
+}
+
+// Parse raw triples: i, i/j/k, i//k, i/j
+static vertex_index parseRawTriple(const char **token) {
+ vertex_index vi(static_cast<int>(0)); // 0 is an invalid index in OBJ
+
+ vi.v_idx = atoi((*token));
+ (*token) += strcspn((*token), "/ \t\r");
+ if ((*token)[0] != '/') {
+ return vi;
+ }
+ (*token)++;
+
+ // i//k
+ if ((*token)[0] == '/') {
+ (*token)++;
+ vi.vn_idx = atoi((*token));
+ (*token) += strcspn((*token), "/ \t\r");
+ return vi;
+ }
+
+ // i/j/k or i/j
+ vi.vt_idx = atoi((*token));
+ (*token) += strcspn((*token), "/ \t\r");
+ if ((*token)[0] != '/') {
+ return vi;
+ }
+
+ // i/j/k
+ (*token)++; // skip '/'
+ vi.vn_idx = atoi((*token));
+ (*token) += strcspn((*token), "/ \t\r");
+ return vi;
+}
+
+static bool ParseTextureNameAndOption(std::string *texname,
+ texture_option_t *texopt,
+ const char *linebuf, const bool is_bump) {
+ // @todo { write more robust lexer and parser. }
+ bool found_texname = false;
+ std::string texture_name;
+
+ // Fill with default value for texopt.
+ if (is_bump) {
+ texopt->imfchan = 'l';
+ } else {
+ texopt->imfchan = 'm';
+ }
+ texopt->bump_multiplier = 1.0f;
+ texopt->clamp = false;
+ texopt->blendu = true;
+ texopt->blendv = true;
+ texopt->sharpness = 1.0f;
+ texopt->brightness = 0.0f;
+ texopt->contrast = 1.0f;
+ texopt->origin_offset[0] = 0.0f;
+ texopt->origin_offset[1] = 0.0f;
+ texopt->origin_offset[2] = 0.0f;
+ texopt->scale[0] = 1.0f;
+ texopt->scale[1] = 1.0f;
+ texopt->scale[2] = 1.0f;
+ texopt->turbulence[0] = 0.0f;
+ texopt->turbulence[1] = 0.0f;
+ texopt->turbulence[2] = 0.0f;
+ texopt->type = TEXTURE_TYPE_NONE;
+
+ const char *token = linebuf; // Assume line ends with NULL
+
+ while (!IS_NEW_LINE((*token))) {
+ token += strspn(token, " \t"); // skip space
+ if ((0 == strncmp(token, "-blendu", 7)) && IS_SPACE((token[7]))) {
+ token += 8;
+ texopt->blendu = parseOnOff(&token, /* default */ true);
+ } else if ((0 == strncmp(token, "-blendv", 7)) && IS_SPACE((token[7]))) {
+ token += 8;
+ texopt->blendv = parseOnOff(&token, /* default */ true);
+ } else if ((0 == strncmp(token, "-clamp", 6)) && IS_SPACE((token[6]))) {
+ token += 7;
+ texopt->clamp = parseOnOff(&token, /* default */ true);
+ } else if ((0 == strncmp(token, "-boost", 6)) && IS_SPACE((token[6]))) {
+ token += 7;
+ texopt->sharpness = parseReal(&token, 1.0);
+ } else if ((0 == strncmp(token, "-bm", 3)) && IS_SPACE((token[3]))) {
+ token += 4;
+ texopt->bump_multiplier = parseReal(&token, 1.0);
+ } else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) {
+ token += 3;
+ parseReal3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]),
+ &(texopt->origin_offset[2]), &token);
+ } else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) {
+ token += 3;
+ parseReal3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]),
+ &token, 1.0, 1.0, 1.0);
+ } else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) {
+ token += 3;
+ parseReal3(&(texopt->turbulence[0]), &(texopt->turbulence[1]),
+ &(texopt->turbulence[2]), &token);
+ } else if ((0 == strncmp(token, "-type", 5)) && IS_SPACE((token[5]))) {
+ token += 5;
+ texopt->type = parseTextureType((&token), TEXTURE_TYPE_NONE);
+ } else if ((0 == strncmp(token, "-imfchan", 8)) && IS_SPACE((token[8]))) {
+ token += 9;
+ token += strspn(token, " \t");
+ const char *end = token + strcspn(token, " \t\r");
+ if ((end - token) == 1) { // Assume one char for -imfchan
+ texopt->imfchan = (*token);
+ }
+ token = end;
+ } else if ((0 == strncmp(token, "-mm", 3)) && IS_SPACE((token[3]))) {
+ token += 4;
+ parseReal2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0);
+ } else {
+ // Assume texture filename
+#if 0
+ size_t len = strcspn(token, " \t\r"); // untile next space
+ texture_name = std::string(token, token + len);
+ token += len;
+
+ token += strspn(token, " \t"); // skip space
+#else
+ // Read filename until line end to parse filename containing whitespace
+ // TODO(syoyo): Support parsing texture option flag after the filename.
+ texture_name = std::string(token);
+ token += texture_name.length();
+#endif
+
+ found_texname = true;
+ }
+ }
+
+ if (found_texname) {
+ (*texname) = texture_name;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static void InitMaterial(material_t *material) {
+ material->name = "";
+ material->ambient_texname = "";
+ material->diffuse_texname = "";
+ material->specular_texname = "";
+ material->specular_highlight_texname = "";
+ material->bump_texname = "";
+ material->displacement_texname = "";
+ material->reflection_texname = "";
+ material->alpha_texname = "";
+ for (int i = 0; i < 3; i++) {
+ material->ambient[i] = 0.f;
+ material->diffuse[i] = 0.f;
+ material->specular[i] = 0.f;
+ material->transmittance[i] = 0.f;
+ material->emission[i] = 0.f;
+ }
+ material->illum = 0;
+ material->dissolve = 1.f;
+ material->shininess = 1.f;
+ material->ior = 1.f;
+
+ material->roughness = 0.f;
+ material->metallic = 0.f;
+ material->sheen = 0.f;
+ material->clearcoat_thickness = 0.f;
+ material->clearcoat_roughness = 0.f;
+ material->anisotropy_rotation = 0.f;
+ material->anisotropy = 0.f;
+ material->roughness_texname = "";
+ material->metallic_texname = "";
+ material->sheen_texname = "";
+ material->emissive_texname = "";
+ material->normal_texname = "";
+
+ material->unknown_parameter.clear();
+}
+
+// code from https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html
+static int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy)
+{
+ int i, j, c = 0;
+ for (i = 0, j = nvert-1; i < nvert; j = i++) {
+ if ( ((verty[i]>testy) != (verty[j]>testy)) &&
+ (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
+ c = !c;
+ }
+ return c;
+}
+
+static bool exportFaceGroupToShape(
+ shape_t *shape, const std::vector<std::vector<vertex_index> > &faceGroup,
+ const std::vector<tag_t> &tags, const int material_id,
+ const std::string &name, bool triangulate, const std::vector<real_t> &v) {
+ if (faceGroup.empty()) {
+ return false;
+ }
+
+ // Flatten vertices and indices
+ for (size_t i = 0; i < faceGroup.size(); i++) {
+ const std::vector<vertex_index> &face = faceGroup[i];
+
+ vertex_index i0 = face[0];
+ vertex_index i1(-1);
+ vertex_index i2 = face[1];
+
+ size_t npolys = face.size();
+
+ if (triangulate) {
+ // find the two axes to work in
+ size_t axes[2] = { 1, 2 };
+ for(size_t k = 0; k < npolys; ++k) {
+ i0 = face[(k+0)%npolys];
+ i1 = face[(k+1)%npolys];
+ i2 = face[(k+2)%npolys];
+ size_t vi0 = size_t(i0.v_idx);
+ size_t vi1 = size_t(i1.v_idx);
+ size_t vi2 = size_t(i2.v_idx);
+ real_t v0x = v[vi0*3+0];
+ real_t v0y = v[vi0*3+1];
+ real_t v0z = v[vi0*3+2];
+ real_t v1x = v[vi1*3+0];
+ real_t v1y = v[vi1*3+1];
+ real_t v1z = v[vi1*3+2];
+ real_t v2x = v[vi2*3+0];
+ real_t v2y = v[vi2*3+1];
+ real_t v2z = v[vi2*3+2];
+ real_t e0x = v1x - v0x;
+ real_t e0y = v1y - v0y;
+ real_t e0z = v1z - v0z;
+ real_t e1x = v2x - v1x;
+ real_t e1y = v2y - v1y;
+ real_t e1z = v2z - v1z;
+ float cx = fabs(e0y*e1z - e0z*e1y);
+ float cy = fabs(e0z*e1x - e0x*e1z);
+ float cz = fabs(e0x*e1y - e0y*e1x);
+ const float epsilon = 0.0001f;
+ if( cx > epsilon || cy > epsilon || cz > epsilon ) {
+ // found a corner
+ if( cx > cy && cx > cz ) {
+ } else {
+ axes[0] = 0;
+ if( cz > cx && cz > cy )
+ axes[1] = 1;
+ }
+ break;
+ }
+ }
+
+ real_t area = 0;
+ for(size_t k = 0; k < npolys; ++k) {
+ i0 = face[(k+0)%npolys];
+ i1 = face[(k+1)%npolys];
+ size_t vi0 = size_t(i0.v_idx);
+ size_t vi1 = size_t(i1.v_idx);
+ real_t v0x = v[vi0*3+axes[0]];
+ real_t v0y = v[vi0*3+axes[1]];
+ real_t v1x = v[vi1*3+axes[0]];
+ real_t v1y = v[vi1*3+axes[1]];
+ area += (v0x*v1y - v0y*v1x)*0.5f;
+ }
+
+ int maxRounds = 10; // arbitrary max loop count to protect against unexpected errors
+
+ std::vector<vertex_index> remainingFace = face;
+ size_t guess_vert = 0;
+ vertex_index ind[3];
+ real_t vx[3];
+ real_t vy[3];
+ while( remainingFace.size() > 3 && maxRounds > 0 ) {
+ npolys = remainingFace.size();
+ if( guess_vert >= npolys ) {
+ maxRounds -= 1;
+ guess_vert -= npolys;
+ }
+ for( size_t k = 0; k < 3; k++ ) {
+ ind[k] = remainingFace[(guess_vert+k)%npolys];
+ size_t vi = size_t(ind[k].v_idx);
+ vx[k] = v[vi*3+axes[0]];
+ vy[k] = v[vi*3+axes[1]];
+ }
+ real_t e0x = vx[1] - vx[0];
+ real_t e0y = vy[1] - vy[0];
+ real_t e1x = vx[2] - vx[1];
+ real_t e1y = vy[2] - vy[1];
+ real_t cross = e0x*e1y - e0y*e1x;
+ // if an internal angle
+ if( cross * area < 0.0f ) { guess_vert += 1; continue; }
+
+ // check all other verts in case they are inside this triangle
+ bool overlap = false;
+ for( size_t otherVert = 3; otherVert < npolys; ++otherVert ) {
+ size_t ovi = size_t(remainingFace[(guess_vert+otherVert)%npolys].v_idx);
+ real_t tx = v[ovi*3+axes[0]];
+ real_t ty = v[ovi*3+axes[1]];
+ if( pnpoly( 3, vx, vy, tx, ty ) ) {
+ overlap = true;
+ break;
+ }
+ }
+
+ if( overlap ) { guess_vert += 1; continue; }
+
+ // this triangle is an ear
+ {
+ index_t idx0, idx1, idx2;
+ idx0.vertex_index = ind[0].v_idx;
+ idx0.normal_index = ind[0].vn_idx;
+ idx0.texcoord_index = ind[0].vt_idx;
+ idx1.vertex_index = ind[1].v_idx;
+ idx1.normal_index = ind[1].vn_idx;
+ idx1.texcoord_index = ind[1].vt_idx;
+ idx2.vertex_index = ind[2].v_idx;
+ idx2.normal_index = ind[2].vn_idx;
+ idx2.texcoord_index = ind[2].vt_idx;
+
+ shape->mesh.indices.push_back(idx0);
+ shape->mesh.indices.push_back(idx1);
+ shape->mesh.indices.push_back(idx2);
+
+ shape->mesh.num_face_vertices.push_back(3);
+ shape->mesh.material_ids.push_back(material_id);
+ }
+
+ // remove v1 from the list
+ size_t removed_vert_index = (guess_vert+1)%npolys;
+ while( removed_vert_index + 1 < npolys ) {
+ remainingFace[removed_vert_index] = remainingFace[removed_vert_index+1];
+ removed_vert_index += 1;
+ }
+ remainingFace.pop_back();
+ }
+
+ if( remainingFace.size() == 3 ) {
+ i0 = remainingFace[0];
+ i1 = remainingFace[1];
+ i2 = remainingFace[2];
+ {
+ index_t idx0, idx1, idx2;
+ idx0.vertex_index = i0.v_idx;
+ idx0.normal_index = i0.vn_idx;
+ idx0.texcoord_index = i0.vt_idx;
+ idx1.vertex_index = i1.v_idx;
+ idx1.normal_index = i1.vn_idx;
+ idx1.texcoord_index = i1.vt_idx;
+ idx2.vertex_index = i2.v_idx;
+ idx2.normal_index = i2.vn_idx;
+ idx2.texcoord_index = i2.vt_idx;
+
+ shape->mesh.indices.push_back(idx0);
+ shape->mesh.indices.push_back(idx1);
+ shape->mesh.indices.push_back(idx2);
+
+ shape->mesh.num_face_vertices.push_back(3);
+ shape->mesh.material_ids.push_back(material_id);
+ }
+ }
+ } else {
+ for (size_t k = 0; k < npolys; k++) {
+ index_t idx;
+ idx.vertex_index = face[k].v_idx;
+ idx.normal_index = face[k].vn_idx;
+ idx.texcoord_index = face[k].vt_idx;
+ shape->mesh.indices.push_back(idx);
+ }
+
+ shape->mesh.num_face_vertices.push_back(
+ static_cast<unsigned char>(npolys));
+ shape->mesh.material_ids.push_back(material_id); // per face
+ }
+ }
+
+ shape->name = name;
+ shape->mesh.tags = tags;
+
+ return true;
+}
+
+// Split a string with specified delimiter character.
+// http://stackoverflow.com/questions/236129/split-a-string-in-c
+static void SplitString(const std::string &s, char delim,
+ std::vector<std::string> &elems) {
+ std::stringstream ss;
+ ss.str(s);
+ std::string item;
+ while (std::getline(ss, item, delim)) {
+ elems.push_back(item);
+ }
+}
+
+void LoadMtl(std::map<std::string, int> *material_map,
+ std::vector<material_t> *materials, std::istream *inStream,
+ std::string *warning) {
+ // Create a default material anyway.
+ material_t material;
+ InitMaterial(&material);
+
+ // Issue 43. `d` wins against `Tr` since `Tr` is not in the MTL specification.
+ bool has_d = false;
+ bool has_tr = false;
+
+ std::stringstream ss;
+
+ std::string linebuf;
+ while (inStream->peek() != -1) {
+ safeGetline(*inStream, linebuf);
+
+ // Trim trailing whitespace.
+ if (linebuf.size() > 0) {
+ linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1);
+ }
+
+ // Trim newline '\r\n' or '\n'
+ if (linebuf.size() > 0) {
+ if (linebuf[linebuf.size() - 1] == '\n')
+ linebuf.erase(linebuf.size() - 1);
+ }
+ if (linebuf.size() > 0) {
+ if (linebuf[linebuf.size() - 1] == '\r')
+ linebuf.erase(linebuf.size() - 1);
+ }
+
+ // Skip if empty line.
+ if (linebuf.empty()) {
+ continue;
+ }
+
+ // Skip leading space.
+ const char *token = linebuf.c_str();
+ token += strspn(token, " \t");
+
+ assert(token);
+ if (token[0] == '\0') continue; // empty line
+
+ if (token[0] == '#') continue; // comment line
+
+ // new mtl
+ if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) {
+ // flush previous material.
+ if (!material.name.empty()) {
+ material_map->insert(std::pair<std::string, int>(
+ material.name, static_cast<int>(materials->size())));
+ materials->push_back(material);
+ }
+
+ // initial temporary material
+ InitMaterial(&material);
+
+ has_d = false;
+ has_tr = false;
+
+ // set new mtl name
+ token += 7;
+ {
+ std::stringstream sstr;
+ sstr << token;
+ material.name = sstr.str();
+ }
+ continue;
+ }
+
+ // ambient
+ if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) {
+ token += 2;
+ real_t r, g, b;
+ parseReal3(&r, &g, &b, &token);
+ material.ambient[0] = r;
+ material.ambient[1] = g;
+ material.ambient[2] = b;
+ continue;
+ }
+
+ // diffuse
+ if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) {
+ token += 2;
+ real_t r, g, b;
+ parseReal3(&r, &g, &b, &token);
+ material.diffuse[0] = r;
+ material.diffuse[1] = g;
+ material.diffuse[2] = b;
+ continue;
+ }
+
+ // specular
+ if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) {
+ token += 2;
+ real_t r, g, b;
+ parseReal3(&r, &g, &b, &token);
+ material.specular[0] = r;
+ material.specular[1] = g;
+ material.specular[2] = b;
+ continue;
+ }
+
+ // transmittance
+ if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) ||
+ (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) {
+ token += 2;
+ real_t r, g, b;
+ parseReal3(&r, &g, &b, &token);
+ material.transmittance[0] = r;
+ material.transmittance[1] = g;
+ material.transmittance[2] = b;
+ continue;
+ }
+
+ // ior(index of refraction)
+ if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) {
+ token += 2;
+ material.ior = parseReal(&token);
+ continue;
+ }
+
+ // emission
+ if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) {
+ token += 2;
+ real_t r, g, b;
+ parseReal3(&r, &g, &b, &token);
+ material.emission[0] = r;
+ material.emission[1] = g;
+ material.emission[2] = b;
+ continue;
+ }
+
+ // shininess
+ if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) {
+ token += 2;
+ material.shininess = parseReal(&token);
+ continue;
+ }
+
+ // illum model
+ if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) {
+ token += 6;
+ material.illum = parseInt(&token);
+ continue;
+ }
+
+ // dissolve
+ if ((token[0] == 'd' && IS_SPACE(token[1]))) {
+ token += 1;
+ material.dissolve = parseReal(&token);
+
+ if (has_tr) {
+ ss << "WARN: Both `d` and `Tr` parameters defined for \""
+ << material.name << "\". Use the value of `d` for dissolve."
+ << std::endl;
+ }
+ has_d = true;
+ continue;
+ }
+ if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) {
+ token += 2;
+ if (has_d) {
+ // `d` wins. Ignore `Tr` value.
+ ss << "WARN: Both `d` and `Tr` parameters defined for \""
+ << material.name << "\". Use the value of `d` for dissolve."
+ << std::endl;
+ } else {
+ // We invert value of Tr(assume Tr is in range [0, 1])
+ // NOTE: Interpretation of Tr is application(exporter) dependent. For
+ // some application(e.g. 3ds max obj exporter), Tr = d(Issue 43)
+ material.dissolve = 1.0f - parseReal(&token);
+ }
+ has_tr = true;
+ continue;
+ }
+
+ // PBR: roughness
+ if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) {
+ token += 2;
+ material.roughness = parseReal(&token);
+ continue;
+ }
+
+ // PBR: metallic
+ if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) {
+ token += 2;
+ material.metallic = parseReal(&token);
+ continue;
+ }
+
+ // PBR: sheen
+ if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) {
+ token += 2;
+ material.sheen = parseReal(&token);
+ continue;
+ }
+
+ // PBR: clearcoat thickness
+ if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) {
+ token += 2;
+ material.clearcoat_thickness = parseReal(&token);
+ continue;
+ }
+
+ // PBR: clearcoat roughness
+ if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) {
+ token += 4;
+ material.clearcoat_roughness = parseReal(&token);
+ continue;
+ }
+
+ // PBR: anisotropy
+ if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) {
+ token += 6;
+ material.anisotropy = parseReal(&token);
+ continue;
+ }
+
+ // PBR: anisotropy rotation
+ if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.anisotropy_rotation = parseReal(&token);
+ continue;
+ }
+
+ // ambient texture
+ if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ ParseTextureNameAndOption(&(material.ambient_texname),
+ &(material.ambient_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // diffuse texture
+ if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ ParseTextureNameAndOption(&(material.diffuse_texname),
+ &(material.diffuse_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // specular texture
+ if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ ParseTextureNameAndOption(&(material.specular_texname),
+ &(material.specular_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // specular highlight texture
+ if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ ParseTextureNameAndOption(&(material.specular_highlight_texname),
+ &(material.specular_highlight_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // bump texture
+ if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) {
+ token += 9;
+ ParseTextureNameAndOption(&(material.bump_texname),
+ &(material.bump_texopt), token,
+ /* is_bump */ true);
+ continue;
+ }
+
+ // bump texture
+ if ((0 == strncmp(token, "map_Bump", 8)) && IS_SPACE(token[8])) {
+ token += 9;
+ ParseTextureNameAndOption(&(material.bump_texname),
+ &(material.bump_texopt), token,
+ /* is_bump */ true);
+ continue;
+ }
+
+ // bump texture
+ if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) {
+ token += 5;
+ ParseTextureNameAndOption(&(material.bump_texname),
+ &(material.bump_texopt), token,
+ /* is_bump */ true);
+ continue;
+ }
+
+ // alpha texture
+ if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) {
+ token += 6;
+ material.alpha_texname = token;
+ ParseTextureNameAndOption(&(material.alpha_texname),
+ &(material.alpha_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // displacement texture
+ if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) {
+ token += 5;
+ ParseTextureNameAndOption(&(material.displacement_texname),
+ &(material.displacement_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // reflection map
+ if ((0 == strncmp(token, "refl", 4)) && IS_SPACE(token[4])) {
+ token += 5;
+ ParseTextureNameAndOption(&(material.reflection_texname),
+ &(material.reflection_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // PBR: roughness texture
+ if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ ParseTextureNameAndOption(&(material.roughness_texname),
+ &(material.roughness_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // PBR: metallic texture
+ if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ ParseTextureNameAndOption(&(material.metallic_texname),
+ &(material.metallic_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // PBR: sheen texture
+ if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ ParseTextureNameAndOption(&(material.sheen_texname),
+ &(material.sheen_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // PBR: emissive texture
+ if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ ParseTextureNameAndOption(&(material.emissive_texname),
+ &(material.emissive_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // PBR: normal map texture
+ if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) {
+ token += 5;
+ ParseTextureNameAndOption(
+ &(material.normal_texname), &(material.normal_texopt), token,
+ /* is_bump */ false); // @fixme { is_bump will be true? }
+ continue;
+ }
+
+ // unknown parameter
+ const char *_space = strchr(token, ' ');
+ if (!_space) {
+ _space = strchr(token, '\t');
+ }
+ if (_space) {
+ std::ptrdiff_t len = _space - token;
+ std::string key(token, static_cast<size_t>(len));
+ std::string value = _space + 1;
+ material.unknown_parameter.insert(
+ std::pair<std::string, std::string>(key, value));
+ }
+ }
+ // flush last material.
+ material_map->insert(std::pair<std::string, int>(
+ material.name, static_cast<int>(materials->size())));
+ materials->push_back(material);
+
+ if (warning) {
+ (*warning) = ss.str();
+ }
+}
+
+bool MaterialFileReader::operator()(const std::string &matId,
+ std::vector<material_t> *materials,
+ std::map<std::string, int> *matMap,
+ std::string *err) {
+ std::string filepath;
+
+ if (!m_mtlBaseDir.empty()) {
+ filepath = std::string(m_mtlBaseDir) + matId;
+ } else {
+ filepath = matId;
+ }
+
+ std::ifstream matIStream(filepath.c_str());
+ if (!matIStream) {
+ std::stringstream ss;
+ ss << "WARN: Material file [ " << filepath << " ] not found." << std::endl;
+ if (err) {
+ (*err) += ss.str();
+ }
+ return false;
+ }
+
+ std::string warning;
+ LoadMtl(matMap, materials, &matIStream, &warning);
+
+ if (!warning.empty()) {
+ if (err) {
+ (*err) += warning;
+ }
+ }
+
+ return true;
+}
+
+bool MaterialStreamReader::operator()(const std::string &matId,
+ std::vector<material_t> *materials,
+ std::map<std::string, int> *matMap,
+ std::string *err) {
+ (void)matId;
+ if (!m_inStream) {
+ std::stringstream ss;
+ ss << "WARN: Material stream in error state. " << std::endl;
+ if (err) {
+ (*err) += ss.str();
+ }
+ return false;
+ }
+
+ std::string warning;
+ LoadMtl(matMap, materials, &m_inStream, &warning);
+
+ if (!warning.empty()) {
+ if (err) {
+ (*err) += warning;
+ }
+ }
+
+ return true;
+}
+
+bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
+ std::vector<material_t> *materials, std::string *err,
+ const char *filename, const char *mtl_basedir, bool trianglulate) {
+ attrib->vertices.clear();
+ attrib->normals.clear();
+ attrib->texcoords.clear();
+ attrib->colors.clear();
+ shapes->clear();
+
+ std::stringstream errss;
+
+ std::ifstream ifs(filename);
+ if (!ifs) {
+ errss << "Cannot open file [" << filename << "]" << std::endl;
+ if (err) {
+ (*err) = errss.str();
+ }
+ return false;
+ }
+
+ std::string baseDir;
+ if (mtl_basedir) {
+ baseDir = mtl_basedir;
+ }
+ MaterialFileReader matFileReader(baseDir);
+
+ return LoadObj(attrib, shapes, materials, err, &ifs, &matFileReader,
+ trianglulate);
+}
+
+bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
+ std::vector<material_t> *materials, std::string *err,
+ std::istream *inStream, MaterialReader *readMatFn /*= NULL*/,
+ bool triangulate) {
+ std::stringstream errss;
+
+ std::vector<real_t> v;
+ std::vector<real_t> vn;
+ std::vector<real_t> vt;
+ std::vector<real_t> vc;
+ std::vector<tag_t> tags;
+ std::vector<std::vector<vertex_index> > faceGroup;
+ std::string name;
+
+ // material
+ std::map<std::string, int> material_map;
+ int material = -1;
+
+ shape_t shape;
+
+ std::string linebuf;
+ while (inStream->peek() != -1) {
+ safeGetline(*inStream, linebuf);
+
+ // Trim newline '\r\n' or '\n'
+ if (linebuf.size() > 0) {
+ if (linebuf[linebuf.size() - 1] == '\n')
+ linebuf.erase(linebuf.size() - 1);
+ }
+ if (linebuf.size() > 0) {
+ if (linebuf[linebuf.size() - 1] == '\r')
+ linebuf.erase(linebuf.size() - 1);
+ }
+
+ // Skip if empty line.
+ if (linebuf.empty()) {
+ continue;
+ }
+
+ // Skip leading space.
+ const char *token = linebuf.c_str();
+ token += strspn(token, " \t");
+
+ assert(token);
+ if (token[0] == '\0') continue; // empty line
+
+ if (token[0] == '#') continue; // comment line
+
+ // vertex
+ if (token[0] == 'v' && IS_SPACE((token[1]))) {
+ token += 2;
+ real_t x, y, z;
+ real_t r, g, b;
+ parseVertexWithColor(&x, &y, &z, &r, &g, &b, &token);
+ v.push_back(x);
+ v.push_back(y);
+ v.push_back(z);
+
+ vc.push_back(r);
+ vc.push_back(g);
+ vc.push_back(b);
+ continue;
+ }
+
+ // normal
+ if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) {
+ token += 3;
+ real_t x, y, z;
+ parseReal3(&x, &y, &z, &token);
+ vn.push_back(x);
+ vn.push_back(y);
+ vn.push_back(z);
+ continue;
+ }
+
+ // texcoord
+ if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) {
+ token += 3;
+ real_t x, y;
+ parseReal2(&x, &y, &token);
+ vt.push_back(x);
+ vt.push_back(y);
+ continue;
+ }
+
+ // face
+ if (token[0] == 'f' && IS_SPACE((token[1]))) {
+ token += 2;
+ token += strspn(token, " \t");
+
+ std::vector<vertex_index> face;
+ face.reserve(3);
+
+ while (!IS_NEW_LINE(token[0])) {
+ vertex_index vi;
+ if (!parseTriple(&token, static_cast<int>(v.size() / 3),
+ static_cast<int>(vn.size() / 3),
+ static_cast<int>(vt.size() / 2), &vi)) {
+ if (err) {
+ (*err) = "Failed parse `f' line(e.g. zero value for face index).\n";
+ }
+ return false;
+ }
+
+ face.push_back(vi);
+ size_t n = strspn(token, " \t\r");
+ token += n;
+ }
+
+ // replace with emplace_back + std::move on C++11
+ faceGroup.push_back(std::vector<vertex_index>());
+ faceGroup[faceGroup.size() - 1].swap(face);
+
+ continue;
+ }
+
+ // use mtl
+ if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) {
+ token += 7;
+ std::stringstream ss;
+ ss << token;
+ std::string namebuf = ss.str();
+
+ int newMaterialId = -1;
+ if (material_map.find(namebuf) != material_map.end()) {
+ newMaterialId = material_map[namebuf];
+ } else {
+ // { error!! material not found }
+ }
+
+ if (newMaterialId != material) {
+ // Create per-face material. Thus we don't add `shape` to `shapes` at
+ // this time.
+ // just clear `faceGroup` after `exportFaceGroupToShape()` call.
+ exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
+ triangulate, v);
+ faceGroup.clear();
+ material = newMaterialId;
+ }
+
+ continue;
+ }
+
+ // load mtl
+ if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
+ if (readMatFn) {
+ token += 7;
+
+ std::vector<std::string> filenames;
+ SplitString(std::string(token), ' ', filenames);
+
+ if (filenames.empty()) {
+ if (err) {
+ (*err) +=
+ "WARN: Looks like empty filename for mtllib. Use default "
+ "material. \n";
+ }
+ } else {
+ bool found = false;
+ for (size_t s = 0; s < filenames.size(); s++) {
+ std::string err_mtl;
+ bool ok = (*readMatFn)(filenames[s].c_str(), materials,
+ &material_map, &err_mtl);
+ if (err && (!err_mtl.empty())) {
+ (*err) += err_mtl; // This should be warn message.
+ }
+
+ if (ok) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ if (err) {
+ (*err) +=
+ "WARN: Failed to load material file(s). Use default "
+ "material.\n";
+ }
+ }
+ }
+ }
+
+ continue;
+ }
+
+ // group name
+ if (token[0] == 'g' && IS_SPACE((token[1]))) {
+ // flush previous face group.
+ bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
+ triangulate, v);
+ (void)ret; // return value not used.
+
+ if (shape.mesh.indices.size() > 0) {
+ shapes->push_back(shape);
+ }
+
+ shape = shape_t();
+
+ // material = -1;
+ faceGroup.clear();
+
+ std::vector<std::string> names;
+ names.reserve(2);
+
+ while (!IS_NEW_LINE(token[0])) {
+ std::string str = parseString(&token);
+ names.push_back(str);
+ token += strspn(token, " \t\r"); // skip tag
+ }
+
+ assert(names.size() > 0);
+
+ // names[0] must be 'g', so skip the 0th element.
+ if (names.size() > 1) {
+ name = names[1];
+ } else {
+ name = "";
+ }
+
+ continue;
+ }
+
+ // object name
+ if (token[0] == 'o' && IS_SPACE((token[1]))) {
+ // flush previous face group.
+ bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
+ triangulate, v);
+ if (ret) {
+ shapes->push_back(shape);
+ }
+
+ // material = -1;
+ faceGroup.clear();
+ shape = shape_t();
+
+ // @todo { multiple object name? }
+ token += 2;
+ std::stringstream ss;
+ ss << token;
+ name = ss.str();
+
+ continue;
+ }
+
+ if (token[0] == 't' && IS_SPACE(token[1])) {
+ tag_t tag;
+
+ token += 2;
+
+ tag.name = parseString(&token);
+
+ tag_sizes ts = parseTagTriple(&token);
+
+ tag.intValues.resize(static_cast<size_t>(ts.num_ints));
+
+ for (size_t i = 0; i < static_cast<size_t>(ts.num_ints); ++i) {
+ tag.intValues[i] = parseInt(&token);
+ }
+
+ tag.floatValues.resize(static_cast<size_t>(ts.num_reals));
+ for (size_t i = 0; i < static_cast<size_t>(ts.num_reals); ++i) {
+ tag.floatValues[i] = parseReal(&token);
+ }
+
+ tag.stringValues.resize(static_cast<size_t>(ts.num_strings));
+ for (size_t i = 0; i < static_cast<size_t>(ts.num_strings); ++i) {
+ tag.stringValues[i] = parseString(&token);
+ }
+
+ tags.push_back(tag);
+ }
+
+ // Ignore unknown command.
+ }
+
+ bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
+ triangulate, v);
+ // exportFaceGroupToShape return false when `usemtl` is called in the last
+ // line.
+ // we also add `shape` to `shapes` when `shape.mesh` has already some
+ // faces(indices)
+ if (ret || shape.mesh.indices.size()) {
+ shapes->push_back(shape);
+ }
+ faceGroup.clear(); // for safety
+
+ if (err) {
+ (*err) += errss.str();
+ }
+
+ attrib->vertices.swap(v);
+ attrib->normals.swap(vn);
+ attrib->texcoords.swap(vt);
+ attrib->colors.swap(vc);
+
+ return true;
+}
+
+bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback,
+ void *user_data /*= NULL*/,
+ MaterialReader *readMatFn /*= NULL*/,
+ std::string *err /*= NULL*/) {
+ std::stringstream errss;
+
+ // material
+ std::map<std::string, int> material_map;
+ int material_id = -1; // -1 = invalid
+
+ std::vector<index_t> indices;
+ std::vector<material_t> materials;
+ std::vector<std::string> names;
+ names.reserve(2);
+ std::string name;
+ std::vector<const char *> names_out;
+
+ std::string linebuf;
+ while (inStream.peek() != -1) {
+ safeGetline(inStream, linebuf);
+
+ // Trim newline '\r\n' or '\n'
+ if (linebuf.size() > 0) {
+ if (linebuf[linebuf.size() - 1] == '\n')
+ linebuf.erase(linebuf.size() - 1);
+ }
+ if (linebuf.size() > 0) {
+ if (linebuf[linebuf.size() - 1] == '\r')
+ linebuf.erase(linebuf.size() - 1);
+ }
+
+ // Skip if empty line.
+ if (linebuf.empty()) {
+ continue;
+ }
+
+ // Skip leading space.
+ const char *token = linebuf.c_str();
+ token += strspn(token, " \t");
+
+ assert(token);
+ if (token[0] == '\0') continue; // empty line
+
+ if (token[0] == '#') continue; // comment line
+
+ // vertex
+ if (token[0] == 'v' && IS_SPACE((token[1]))) {
+ token += 2;
+ // TODO(syoyo): Support parsing vertex color extension.
+ real_t x, y, z, w; // w is optional. default = 1.0
+ parseV(&x, &y, &z, &w, &token);
+ if (callback.vertex_cb) {
+ callback.vertex_cb(user_data, x, y, z, w);
+ }
+ continue;
+ }
+
+ // normal
+ if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) {
+ token += 3;
+ real_t x, y, z;
+ parseReal3(&x, &y, &z, &token);
+ if (callback.normal_cb) {
+ callback.normal_cb(user_data, x, y, z);
+ }
+ continue;
+ }
+
+ // texcoord
+ if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) {
+ token += 3;
+ real_t x, y, z; // y and z are optional. default = 0.0
+ parseReal3(&x, &y, &z, &token);
+ if (callback.texcoord_cb) {
+ callback.texcoord_cb(user_data, x, y, z);
+ }
+ continue;
+ }
+
+ // face
+ if (token[0] == 'f' && IS_SPACE((token[1]))) {
+ token += 2;
+ token += strspn(token, " \t");
+
+ indices.clear();
+ while (!IS_NEW_LINE(token[0])) {
+ vertex_index vi = parseRawTriple(&token);
+
+ index_t idx;
+ idx.vertex_index = vi.v_idx;
+ idx.normal_index = vi.vn_idx;
+ idx.texcoord_index = vi.vt_idx;
+
+ indices.push_back(idx);
+ size_t n = strspn(token, " \t\r");
+ token += n;
+ }
+
+ if (callback.index_cb && indices.size() > 0) {
+ callback.index_cb(user_data, &indices.at(0),
+ static_cast<int>(indices.size()));
+ }
+
+ continue;
+ }
+
+ // use mtl
+ if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) {
+ token += 7;
+ std::stringstream ss;
+ ss << token;
+ std::string namebuf = ss.str();
+
+ int newMaterialId = -1;
+ if (material_map.find(namebuf) != material_map.end()) {
+ newMaterialId = material_map[namebuf];
+ } else {
+ // { error!! material not found }
+ }
+
+ if (newMaterialId != material_id) {
+ material_id = newMaterialId;
+ }
+
+ if (callback.usemtl_cb) {
+ callback.usemtl_cb(user_data, namebuf.c_str(), material_id);
+ }
+
+ continue;
+ }
+
+ // load mtl
+ if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
+ if (readMatFn) {
+ token += 7;
+
+ std::vector<std::string> filenames;
+ SplitString(std::string(token), ' ', filenames);
+
+ if (filenames.empty()) {
+ if (err) {
+ (*err) +=
+ "WARN: Looks like empty filename for mtllib. Use default "
+ "material. \n";
+ }
+ } else {
+ bool found = false;
+ for (size_t s = 0; s < filenames.size(); s++) {
+ std::string err_mtl;
+ bool ok = (*readMatFn)(filenames[s].c_str(), &materials,
+ &material_map, &err_mtl);
+ if (err && (!err_mtl.empty())) {
+ (*err) += err_mtl; // This should be warn message.
+ }
+
+ if (ok) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ if (err) {
+ (*err) +=
+ "WARN: Failed to load material file(s). Use default "
+ "material.\n";
+ }
+ } else {
+ if (callback.mtllib_cb) {
+ callback.mtllib_cb(user_data, &materials.at(0),
+ static_cast<int>(materials.size()));
+ }
+ }
+ }
+ }
+
+ continue;
+ }
+
+ // group name
+ if (token[0] == 'g' && IS_SPACE((token[1]))) {
+ names.clear();
+
+ while (!IS_NEW_LINE(token[0])) {
+ std::string str = parseString(&token);
+ names.push_back(str);
+ token += strspn(token, " \t\r"); // skip tag
+ }
+
+ assert(names.size() > 0);
+
+ // names[0] must be 'g', so skip the 0th element.
+ if (names.size() > 1) {
+ name = names[1];
+ } else {
+ name.clear();
+ }
+
+ if (callback.group_cb) {
+ if (names.size() > 1) {
+ // create const char* array.
+ names_out.resize(names.size() - 1);
+ for (size_t j = 0; j < names_out.size(); j++) {
+ names_out[j] = names[j + 1].c_str();
+ }
+ callback.group_cb(user_data, &names_out.at(0),
+ static_cast<int>(names_out.size()));
+
+ } else {
+ callback.group_cb(user_data, NULL, 0);
+ }
+ }
+
+ continue;
+ }
+
+ // object name
+ if (token[0] == 'o' && IS_SPACE((token[1]))) {
+ // @todo { multiple object name? }
+ token += 2;
+
+ std::stringstream ss;
+ ss << token;
+ std::string object_name = ss.str();
+
+ if (callback.object_cb) {
+ callback.object_cb(user_data, object_name.c_str());
+ }
+
+ continue;
+ }
+
+#if 0 // @todo
+ if (token[0] == 't' && IS_SPACE(token[1])) {
+ tag_t tag;
+
+ token += 2;
+ std::stringstream ss;
+ ss << token;
+ tag.name = ss.str();
+
+ token += tag.name.size() + 1;
+
+ tag_sizes ts = parseTagTriple(&token);
+
+ tag.intValues.resize(static_cast<size_t>(ts.num_ints));
+
+ for (size_t i = 0; i < static_cast<size_t>(ts.num_ints); ++i) {
+ tag.intValues[i] = atoi(token);
+ token += strcspn(token, "/ \t\r") + 1;
+ }
+
+ tag.floatValues.resize(static_cast<size_t>(ts.num_reals));
+ for (size_t i = 0; i < static_cast<size_t>(ts.num_reals); ++i) {
+ tag.floatValues[i] = parseReal(&token);
+ token += strcspn(token, "/ \t\r") + 1;
+ }
+
+ tag.stringValues.resize(static_cast<size_t>(ts.num_strings));
+ for (size_t i = 0; i < static_cast<size_t>(ts.num_strings); ++i) {
+ std::stringstream ss;
+ ss << token;
+ tag.stringValues[i] = ss.str();
+ token += tag.stringValues[i].size() + 1;
+ }
+
+ tags.push_back(tag);
+ }
+#endif
+
+ // Ignore unknown command.
+ }
+
+ if (err) {
+ (*err) += errss.str();
+ }
+
+ return true;
+}
+} // namespace tinyobj
+
+#endif
diff --git a/wave-sim/util/unsupportedeigenthing/OpenGLSupport b/wave-sim/util/unsupportedeigenthing/OpenGLSupport
new file mode 100644
index 0000000..f8c2130
--- /dev/null
+++ b/wave-sim/util/unsupportedeigenthing/OpenGLSupport
@@ -0,0 +1,322 @@
+// This file is part of Eigen, a lightweight C++ template library
+// for linear algebra.
+//
+// Copyright (C) 2010 Gael Guennebaud <gael.guennebaud@inria.fr>
+//
+// This Source Code Form is subject to the terms of the Mozilla
+// Public License v. 2.0. If a copy of the MPL was not distributed
+// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef EIGEN_OPENGL_MODULE
+#define EIGEN_OPENGL_MODULE
+
+#include "../../Eigen/Geometry"
+
+#if defined(__APPLE_CC__)
+ #include <OpenGL/gl.h>
+#else
+ #include <GL/gl.h>
+#endif
+
+namespace Eigen {
+
+/**
+ * \defgroup OpenGLSUpport_Module OpenGL Support module
+ *
+ * This module provides wrapper functions for a couple of OpenGL functions
+ * which simplify the way to pass Eigen's object to openGL.
+ * Here is an example:
+ *
+ * \code
+ * // You need to add path_to_eigen/unsupported to your include path.
+ * #include <Eigen/OpenGLSupport>
+ * // ...
+ * Vector3f x, y;
+ * Matrix3f rot;
+ *
+ * glVertex(y + x * rot);
+ *
+ * Quaternion q;
+ * glRotate(q);
+ *
+ * // ...
+ * \endcode
+ *
+ */
+//@{
+
+#define EIGEN_GL_FUNC_DECLARATION(FUNC) \
+namespace internal { \
+ template< typename XprType, \
+ typename Scalar = typename XprType::Scalar, \
+ int Rows = XprType::RowsAtCompileTime, \
+ int Cols = XprType::ColsAtCompileTime, \
+ bool IsGLCompatible = bool(internal::evaluator<XprType>::Flags&LinearAccessBit) \
+ && bool(XprType::Flags&DirectAccessBit) \
+ && (XprType::IsVectorAtCompileTime || (XprType::Flags&RowMajorBit)==0)> \
+ struct EIGEN_CAT(EIGEN_CAT(gl_,FUNC),_impl); \
+ \
+ template<typename XprType, typename Scalar, int Rows, int Cols> \
+ struct EIGEN_CAT(EIGEN_CAT(gl_,FUNC),_impl)<XprType,Scalar,Rows,Cols,false> { \
+ inline static void run(const XprType& p) { \
+ EIGEN_CAT(EIGEN_CAT(gl_,FUNC),_impl)<typename plain_matrix_type_column_major<XprType>::type>::run(p); } \
+ }; \
+} \
+ \
+template<typename Derived> inline void FUNC(const Eigen::DenseBase<Derived>& p) { \
+ EIGEN_CAT(EIGEN_CAT(internal::gl_,FUNC),_impl)<Derived>::run(p.derived()); \
+}
+
+
+#define EIGEN_GL_FUNC_SPECIALIZATION_MAT(FUNC,SCALAR,ROWS,COLS,SUFFIX) \
+namespace internal { \
+ template< typename XprType> struct EIGEN_CAT(EIGEN_CAT(gl_,FUNC),_impl)<XprType, SCALAR, ROWS, COLS, true> { \
+ inline static void run(const XprType& p) { FUNC##SUFFIX(p.data()); } \
+ }; \
+}
+
+
+#define EIGEN_GL_FUNC_SPECIALIZATION_VEC(FUNC,SCALAR,SIZE,SUFFIX) \
+namespace internal { \
+ template< typename XprType> struct EIGEN_CAT(EIGEN_CAT(gl_,FUNC),_impl)<XprType, SCALAR, SIZE, 1, true> { \
+ inline static void run(const XprType& p) { FUNC##SUFFIX(p.data()); } \
+ }; \
+ template< typename XprType> struct EIGEN_CAT(EIGEN_CAT(gl_,FUNC),_impl)<XprType, SCALAR, 1, SIZE, true> { \
+ inline static void run(const XprType& p) { FUNC##SUFFIX(p.data()); } \
+ }; \
+}
+
+
+EIGEN_GL_FUNC_DECLARATION (glVertex)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glVertex,int, 2,2iv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glVertex,short, 2,2sv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glVertex,float, 2,2fv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glVertex,double, 2,2dv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glVertex,int, 3,3iv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glVertex,short, 3,3sv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glVertex,float, 3,3fv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glVertex,double, 3,3dv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glVertex,int, 4,4iv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glVertex,short, 4,4sv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glVertex,float, 4,4fv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glVertex,double, 4,4dv)
+
+EIGEN_GL_FUNC_DECLARATION (glTexCoord)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glTexCoord,int, 2,2iv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glTexCoord,short, 2,2sv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glTexCoord,float, 2,2fv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glTexCoord,double, 2,2dv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glTexCoord,int, 3,3iv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glTexCoord,short, 3,3sv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glTexCoord,float, 3,3fv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glTexCoord,double, 3,3dv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glTexCoord,int, 4,4iv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glTexCoord,short, 4,4sv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glTexCoord,float, 4,4fv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glTexCoord,double, 4,4dv)
+
+EIGEN_GL_FUNC_DECLARATION (glColor)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glColor,int, 2,2iv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glColor,short, 2,2sv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glColor,float, 2,2fv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glColor,double, 2,2dv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glColor,int, 3,3iv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glColor,short, 3,3sv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glColor,float, 3,3fv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glColor,double, 3,3dv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glColor,int, 4,4iv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glColor,short, 4,4sv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glColor,float, 4,4fv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glColor,double, 4,4dv)
+
+EIGEN_GL_FUNC_DECLARATION (glNormal)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glNormal,int, 3,3iv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glNormal,short, 3,3sv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glNormal,float, 3,3fv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glNormal,double, 3,3dv)
+
+inline void glScale2fv(const float* v) { glScalef(v[0], v[1], 1.f); }
+inline void glScale2dv(const double* v) { glScaled(v[0], v[1], 1.0); }
+inline void glScale3fv(const float* v) { glScalef(v[0], v[1], v[2]); }
+inline void glScale3dv(const double* v) { glScaled(v[0], v[1], v[2]); }
+
+EIGEN_GL_FUNC_DECLARATION (glScale)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glScale,float, 2,2fv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glScale,double, 2,2dv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glScale,float, 3,3fv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glScale,double, 3,3dv)
+
+template<typename Scalar> void glScale(const UniformScaling<Scalar>& s) { glScale(Matrix<Scalar,3,1>::Constant(s.factor())); }
+
+inline void glTranslate2fv(const float* v) { glTranslatef(v[0], v[1], 0.f); }
+inline void glTranslate2dv(const double* v) { glTranslated(v[0], v[1], 0.0); }
+inline void glTranslate3fv(const float* v) { glTranslatef(v[0], v[1], v[2]); }
+inline void glTranslate3dv(const double* v) { glTranslated(v[0], v[1], v[2]); }
+
+EIGEN_GL_FUNC_DECLARATION (glTranslate)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glTranslate,float, 2,2fv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glTranslate,double, 2,2dv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glTranslate,float, 3,3fv)
+EIGEN_GL_FUNC_SPECIALIZATION_VEC(glTranslate,double, 3,3dv)
+
+template<typename Scalar> void glTranslate(const Translation<Scalar,2>& t) { glTranslate(t.vector()); }
+template<typename Scalar> void glTranslate(const Translation<Scalar,3>& t) { glTranslate(t.vector()); }
+
+EIGEN_GL_FUNC_DECLARATION (glMultMatrix)
+EIGEN_GL_FUNC_SPECIALIZATION_MAT(glMultMatrix,float, 4,4,f)
+EIGEN_GL_FUNC_SPECIALIZATION_MAT(glMultMatrix,double, 4,4,d)
+
+template<typename Scalar> void glMultMatrix(const Transform<Scalar,3,Affine>& t) { glMultMatrix(t.matrix()); }
+template<typename Scalar> void glMultMatrix(const Transform<Scalar,3,Projective>& t) { glMultMatrix(t.matrix()); }
+template<typename Scalar> void glMultMatrix(const Transform<Scalar,3,AffineCompact>& t) { glMultMatrix(Transform<Scalar,3,Affine>(t).matrix()); }
+
+EIGEN_GL_FUNC_DECLARATION (glLoadMatrix)
+EIGEN_GL_FUNC_SPECIALIZATION_MAT(glLoadMatrix,float, 4,4,f)
+EIGEN_GL_FUNC_SPECIALIZATION_MAT(glLoadMatrix,double, 4,4,d)
+
+template<typename Scalar> void glLoadMatrix(const Transform<Scalar,3,Affine>& t) { glLoadMatrix(t.matrix()); }
+template<typename Scalar> void glLoadMatrix(const Transform<Scalar,3,Projective>& t) { glLoadMatrix(t.matrix()); }
+template<typename Scalar> void glLoadMatrix(const Transform<Scalar,3,AffineCompact>& t) { glLoadMatrix(Transform<Scalar,3,Affine>(t).matrix()); }
+
+inline void glRotate(const Rotation2D<float>& rot)
+{
+ glRotatef(rot.angle()*180.f/float(EIGEN_PI), 0.f, 0.f, 1.f);
+}
+inline void glRotate(const Rotation2D<double>& rot)
+{
+ glRotated(rot.angle()*180.0/double(EIGEN_PI), 0.0, 0.0, 1.0);
+}
+
+template<typename Derived> void glRotate(const RotationBase<Derived,3>& rot)
+{
+ Transform<typename Derived::Scalar,3,Projective> tr(rot);
+ glMultMatrix(tr.matrix());
+}
+
+#define EIGEN_GL_MAKE_CONST_const const
+#define EIGEN_GL_MAKE_CONST__
+#define EIGEN_GL_EVAL(X) X
+
+#define EIGEN_GL_FUNC1_DECLARATION(FUNC,ARG1,CONST) \
+namespace internal { \
+ template< typename XprType, \
+ typename Scalar = typename XprType::Scalar, \
+ int Rows = XprType::RowsAtCompileTime, \
+ int Cols = XprType::ColsAtCompileTime, \
+ bool IsGLCompatible = bool(internal::evaluator<XprType>::Flags&LinearAccessBit) \
+ && bool(XprType::Flags&DirectAccessBit) \
+ && (XprType::IsVectorAtCompileTime || (XprType::Flags&RowMajorBit)==0)> \
+ struct EIGEN_CAT(EIGEN_CAT(gl_,FUNC),_impl); \
+ \
+ template<typename XprType, typename Scalar, int Rows, int Cols> \
+ struct EIGEN_CAT(EIGEN_CAT(gl_,FUNC),_impl)<XprType,Scalar,Rows,Cols,false> { \
+ inline static void run(ARG1 a,EIGEN_GL_EVAL(EIGEN_GL_MAKE_CONST_##CONST) XprType& p) { \
+ EIGEN_CAT(EIGEN_CAT(gl_,FUNC),_impl)<typename plain_matrix_type_column_major<XprType>::type>::run(a,p); } \
+ }; \
+} \
+ \
+template<typename Derived> inline void FUNC(ARG1 a,EIGEN_GL_EVAL(EIGEN_GL_MAKE_CONST_##CONST) Eigen::DenseBase<Derived>& p) { \
+ EIGEN_CAT(EIGEN_CAT(internal::gl_,FUNC),_impl)<Derived>::run(a,p.derived()); \
+}
+
+
+#define EIGEN_GL_FUNC1_SPECIALIZATION_MAT(FUNC,ARG1,CONST,SCALAR,ROWS,COLS,SUFFIX) \
+namespace internal { \
+ template< typename XprType> struct EIGEN_CAT(EIGEN_CAT(gl_,FUNC),_impl)<XprType, SCALAR, ROWS, COLS, true> { \
+ inline static void run(ARG1 a, EIGEN_GL_EVAL(EIGEN_GL_MAKE_CONST_##CONST) XprType& p) { FUNC##SUFFIX(a,p.data()); } \
+ }; \
+}
+
+
+#define EIGEN_GL_FUNC1_SPECIALIZATION_VEC(FUNC,ARG1,CONST,SCALAR,SIZE,SUFFIX) \
+namespace internal { \
+ template< typename XprType> struct EIGEN_CAT(EIGEN_CAT(gl_,FUNC),_impl)<XprType, SCALAR, SIZE, 1, true> { \
+ inline static void run(ARG1 a, EIGEN_GL_EVAL(EIGEN_GL_MAKE_CONST_##CONST) XprType& p) { FUNC##SUFFIX(a,p.data()); } \
+ }; \
+ template< typename XprType> struct EIGEN_CAT(EIGEN_CAT(gl_,FUNC),_impl)<XprType, SCALAR, 1, SIZE, true> { \
+ inline static void run(ARG1 a, EIGEN_GL_EVAL(EIGEN_GL_MAKE_CONST_##CONST) XprType& p) { FUNC##SUFFIX(a,p.data()); } \
+ }; \
+}
+
+EIGEN_GL_FUNC1_DECLARATION (glGet,GLenum,_)
+EIGEN_GL_FUNC1_SPECIALIZATION_MAT(glGet,GLenum,_,float, 4,4,Floatv)
+EIGEN_GL_FUNC1_SPECIALIZATION_MAT(glGet,GLenum,_,double, 4,4,Doublev)
+
+// glUniform API
+
+#ifdef GL_VERSION_2_0
+
+inline void glUniform2fv_ei (GLint loc, const float* v) { glUniform2fv(loc,1,v); }
+inline void glUniform2iv_ei (GLint loc, const int* v) { glUniform2iv(loc,1,v); }
+
+inline void glUniform3fv_ei (GLint loc, const float* v) { glUniform3fv(loc,1,v); }
+inline void glUniform3iv_ei (GLint loc, const int* v) { glUniform3iv(loc,1,v); }
+
+inline void glUniform4fv_ei (GLint loc, const float* v) { glUniform4fv(loc,1,v); }
+inline void glUniform4iv_ei (GLint loc, const int* v) { glUniform4iv(loc,1,v); }
+
+inline void glUniformMatrix2fv_ei (GLint loc, const float* v) { glUniformMatrix2fv(loc,1,false,v); }
+inline void glUniformMatrix3fv_ei (GLint loc, const float* v) { glUniformMatrix3fv(loc,1,false,v); }
+inline void glUniformMatrix4fv_ei (GLint loc, const float* v) { glUniformMatrix4fv(loc,1,false,v); }
+
+
+EIGEN_GL_FUNC1_DECLARATION (glUniform,GLint,const)
+EIGEN_GL_FUNC1_SPECIALIZATION_VEC(glUniform,GLint,const,float, 2,2fv_ei)
+EIGEN_GL_FUNC1_SPECIALIZATION_VEC(glUniform,GLint,const,int, 2,2iv_ei)
+EIGEN_GL_FUNC1_SPECIALIZATION_VEC(glUniform,GLint,const,float, 3,3fv_ei)
+EIGEN_GL_FUNC1_SPECIALIZATION_VEC(glUniform,GLint,const,int, 3,3iv_ei)
+EIGEN_GL_FUNC1_SPECIALIZATION_VEC(glUniform,GLint,const,float, 4,4fv_ei)
+EIGEN_GL_FUNC1_SPECIALIZATION_VEC(glUniform,GLint,const,int, 4,4iv_ei)
+
+EIGEN_GL_FUNC1_SPECIALIZATION_MAT(glUniform,GLint,const,float, 2,2,Matrix2fv_ei)
+EIGEN_GL_FUNC1_SPECIALIZATION_MAT(glUniform,GLint,const,float, 3,3,Matrix3fv_ei)
+EIGEN_GL_FUNC1_SPECIALIZATION_MAT(glUniform,GLint,const,float, 4,4,Matrix4fv_ei)
+
+#endif
+
+#ifdef GL_VERSION_2_1
+
+inline void glUniformMatrix2x3fv_ei(GLint loc, const float* v) { glUniformMatrix2x3fv(loc,1,false,v); }
+inline void glUniformMatrix3x2fv_ei(GLint loc, const float* v) { glUniformMatrix3x2fv(loc,1,false,v); }
+inline void glUniformMatrix2x4fv_ei(GLint loc, const float* v) { glUniformMatrix2x4fv(loc,1,false,v); }
+inline void glUniformMatrix4x2fv_ei(GLint loc, const float* v) { glUniformMatrix4x2fv(loc,1,false,v); }
+inline void glUniformMatrix3x4fv_ei(GLint loc, const float* v) { glUniformMatrix3x4fv(loc,1,false,v); }
+inline void glUniformMatrix4x3fv_ei(GLint loc, const float* v) { glUniformMatrix4x3fv(loc,1,false,v); }
+
+EIGEN_GL_FUNC1_SPECIALIZATION_MAT(glUniform,GLint,const,float, 2,3,Matrix2x3fv_ei)
+EIGEN_GL_FUNC1_SPECIALIZATION_MAT(glUniform,GLint,const,float, 3,2,Matrix3x2fv_ei)
+EIGEN_GL_FUNC1_SPECIALIZATION_MAT(glUniform,GLint,const,float, 2,4,Matrix2x4fv_ei)
+EIGEN_GL_FUNC1_SPECIALIZATION_MAT(glUniform,GLint,const,float, 4,2,Matrix4x2fv_ei)
+EIGEN_GL_FUNC1_SPECIALIZATION_MAT(glUniform,GLint,const,float, 3,4,Matrix3x4fv_ei)
+EIGEN_GL_FUNC1_SPECIALIZATION_MAT(glUniform,GLint,const,float, 4,3,Matrix4x3fv_ei)
+
+#endif
+
+#ifdef GL_VERSION_3_0
+
+inline void glUniform2uiv_ei (GLint loc, const unsigned int* v) { glUniform2uiv(loc,1,v); }
+inline void glUniform3uiv_ei (GLint loc, const unsigned int* v) { glUniform3uiv(loc,1,v); }
+inline void glUniform4uiv_ei (GLint loc, const unsigned int* v) { glUniform4uiv(loc,1,v); }
+
+EIGEN_GL_FUNC1_SPECIALIZATION_VEC(glUniform,GLint,const,unsigned int, 2,2uiv_ei)
+EIGEN_GL_FUNC1_SPECIALIZATION_VEC(glUniform,GLint,const,unsigned int, 3,3uiv_ei)
+EIGEN_GL_FUNC1_SPECIALIZATION_VEC(glUniform,GLint,const,unsigned int, 4,4uiv_ei)
+
+#endif
+
+#ifdef GL_ARB_gpu_shader_fp64
+inline void glUniform2dv_ei (GLint loc, const double* v) { glUniform2dv(loc,1,v); }
+inline void glUniform3dv_ei (GLint loc, const double* v) { glUniform3dv(loc,1,v); }
+inline void glUniform4dv_ei (GLint loc, const double* v) { glUniform4dv(loc,1,v); }
+
+EIGEN_GL_FUNC1_SPECIALIZATION_VEC(glUniform,GLint,const,double, 2,2dv_ei)
+EIGEN_GL_FUNC1_SPECIALIZATION_VEC(glUniform,GLint,const,double, 3,3dv_ei)
+EIGEN_GL_FUNC1_SPECIALIZATION_VEC(glUniform,GLint,const,double, 4,4dv_ei)
+#endif
+
+
+//@}
+
+}
+
+#endif // EIGEN_OPENGL_MODULE