From 17a86dcec5887320e0b4889e0ede099830d79a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laure=CE=B7t?= Date: Thu, 22 Jun 2023 20:30:57 +0200 Subject: [PATCH] init --- TP1-2/.gitignore | 1 + TP1-2/.vscode/settings.json | 73 + TP1-2/CMakeLists.txt | 43 + TP1-2/README.md | 183 + TP1-2/appveyor.yml | 30 + TP1-2/helloteapot.cpp | 136 + TP1-2/imgs/blue_teapot_gone.png | Bin 0 -> 5648 bytes TP1-2/imgs/sphere_morewire.png | Bin 0 -> 24988 bytes TP1-2/imgs/sphere_wire.png | Bin 0 -> 10781 bytes TP1-2/imgs/teapot_45.png | Bin 0 -> 12951 bytes TP1-2/imgs/teapot_90.png | Bin 0 -> 11817 bytes TP1-2/imgs/teapot_identity.png | Bin 0 -> 23255 bytes TP1-2/imgs/teapot_solid.png | Bin 0 -> 3227 bytes TP1-2/imgs/teapot_upsidedown.png | Bin 0 -> 8545 bytes TP1-2/imgs/teapot_wire.png | Bin 0 -> 6315 bytes TP1-2/moreteapots.cpp | 126 + TP1-2/navigator.cpp | 176 + TP1-2/readme.html | 124 + TP1-2/reponses.md | 137 + TP3/CMakeLists.txt | 33 + TP3/README.md | 170 + TP3/appveyor.yml | 31 + TP3/readme.html | 114 + TP3/robot.c | 355 + TP4/CMakeLists.txt | 33 + TP4/README.md | 170 + TP4/appveyor.yml | 31 + TP4/lumiere.c | 383 + TP5/.editorconfig | 12 + TP5/.gitignore | 2 + TP5/.vscode/settings.json | 7 + TP5/BUILD.md | 185 + TP5/CMakeLists.txt | 82 + TP5/LICENSE | 362 + TP5/Makefile | 19 + TP5/ObjModel.cpp | 563 + TP5/ObjModel.cxx | 413 + TP5/ObjModel.hpp | 260 + TP5/README.md | 40 + TP5/appveyor.yml | 31 + TP5/changelog.md | 27 + TP5/core.cpp | 443 + TP5/core.hpp | 568 + TP5/data/img/screeshot.png | Bin 0 -> 67166 bytes TP5/data/models/cow-nonnormalsmani.obj | 8723 + TP5/data/models/cow-nonormals.obj | 10388 + TP5/data/models/cube (copy).obj | 26 + TP5/data/models/cube.obj | 26 + TP5/data/models/icosahedron.obj | 37 + TP5/data/models/square.obj | 12 + TP5/data/models/sujet.obj | 19 + TP5/data/models/surprise.obj | 324549 ++++++++++++++++++++++ TP5/data/models/suzanne.obj | 1480 + TP5/data/models/teapot.obj | 9965 + TP5/data/models/teapotmani.obj | 9577 + TP5/data/models/teapotmani.ply | 9571 + TP5/data/models/teddy.obj | 4790 + TP5/data/models/triang.obj | 16 + TP5/main.cpp | 414 + TP5/message.txt | 563 + TP5/testEdge.cpp | 82 + TP5/testEdgeList.cpp | 94 + 62 files changed, 385695 insertions(+) create mode 100644 TP1-2/.gitignore create mode 100644 TP1-2/.vscode/settings.json create mode 100755 TP1-2/CMakeLists.txt create mode 100755 TP1-2/README.md create mode 100755 TP1-2/appveyor.yml create mode 100755 TP1-2/helloteapot.cpp create mode 100644 TP1-2/imgs/blue_teapot_gone.png create mode 100644 TP1-2/imgs/sphere_morewire.png create mode 100644 TP1-2/imgs/sphere_wire.png create mode 100644 TP1-2/imgs/teapot_45.png create mode 100644 TP1-2/imgs/teapot_90.png create mode 100644 TP1-2/imgs/teapot_identity.png create mode 100644 TP1-2/imgs/teapot_solid.png create mode 100644 TP1-2/imgs/teapot_upsidedown.png create mode 100644 TP1-2/imgs/teapot_wire.png create mode 100755 TP1-2/moreteapots.cpp create mode 100755 TP1-2/navigator.cpp create mode 100755 TP1-2/readme.html create mode 100644 TP1-2/reponses.md create mode 100644 TP3/CMakeLists.txt create mode 100644 TP3/README.md create mode 100644 TP3/appveyor.yml create mode 100644 TP3/readme.html create mode 100644 TP3/robot.c create mode 100644 TP4/CMakeLists.txt create mode 100644 TP4/README.md create mode 100644 TP4/appveyor.yml create mode 100644 TP4/lumiere.c create mode 100644 TP5/.editorconfig create mode 100644 TP5/.gitignore create mode 100644 TP5/.vscode/settings.json create mode 100755 TP5/BUILD.md create mode 100755 TP5/CMakeLists.txt create mode 100755 TP5/LICENSE create mode 100755 TP5/Makefile create mode 100755 TP5/ObjModel.cpp create mode 100755 TP5/ObjModel.cxx create mode 100755 TP5/ObjModel.hpp create mode 100755 TP5/README.md create mode 100755 TP5/appveyor.yml create mode 100755 TP5/changelog.md create mode 100755 TP5/core.cpp create mode 100755 TP5/core.hpp create mode 100755 TP5/data/img/screeshot.png create mode 100755 TP5/data/models/cow-nonnormalsmani.obj create mode 100755 TP5/data/models/cow-nonormals.obj create mode 100755 TP5/data/models/cube (copy).obj create mode 100755 TP5/data/models/cube.obj create mode 100755 TP5/data/models/icosahedron.obj create mode 100755 TP5/data/models/square.obj create mode 100755 TP5/data/models/sujet.obj create mode 100755 TP5/data/models/surprise.obj create mode 100755 TP5/data/models/suzanne.obj create mode 100755 TP5/data/models/teapot.obj create mode 100755 TP5/data/models/teapotmani.obj create mode 100755 TP5/data/models/teapotmani.ply create mode 100755 TP5/data/models/teddy.obj create mode 100755 TP5/data/models/triang.obj create mode 100755 TP5/main.cpp create mode 100644 TP5/message.txt create mode 100755 TP5/testEdge.cpp create mode 100755 TP5/testEdgeList.cpp diff --git a/TP1-2/.gitignore b/TP1-2/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/TP1-2/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/TP1-2/.vscode/settings.json b/TP1-2/.vscode/settings.json new file mode 100644 index 0000000..673ae8e --- /dev/null +++ b/TP1-2/.vscode/settings.json @@ -0,0 +1,73 @@ +{ + "files.associations": { + "*.html": "html", + "*.toml": "toml", + "cmath": "cpp", + "iostream": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "array": "cpp", + "atomic": "cpp", + "hash_map": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "cfenv": "cpp", + "chrono": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdint": "cpp", + "deque": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "semaphore": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp", + "valarray": "cpp", + "variant": "cpp" + } +} \ No newline at end of file diff --git a/TP1-2/CMakeLists.txt b/TP1-2/CMakeLists.txt new file mode 100755 index 0000000..686a033 --- /dev/null +++ b/TP1-2/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 3.10) +project(tp1 VERSION 2021.0.0 LANGUAGES C CXX) + +set(CMAKE_CXX_STANDARD 11) + +# set(OpenGL_GL_PREFERENCE "GLVND") +find_package(OpenGL REQUIRED) +message(STATUS "OPENGL_gl_LIBRARY: ${OPENGL_gl_LIBRARY}") + +if(MSVC) + set(GLUT_ROOT_PATH "${CMAKE_SOURCE_DIR}/freeglut") + message(STATUS "GLUT_ROOT_PATH: ${GLUT_ROOT_PATH}") +endif() +find_package(GLUT REQUIRED) +message(STATUS "GLUT_FOUND: ${GLUT_FOUND}") +message(STATUS "GLUT_INCLUDE_DIR: ${GLUT_INCLUDE_DIR}") +message(STATUS "GLUT_LIBRARIES: ${GLUT_LIBRARIES}") + +if(APPLE) + add_definitions(-Wno-deprecated-declarations) +endif() + +add_executable(helloteapot helloteapot.cpp) +target_include_directories(helloteapot PUBLIC ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIR}) +#target_link_libraries(helloteapot PUBLIC ${OPENGL_opengl_LIBRARY} ${OPENGL_glu_LIBRARY} ${GLUT_LIBRARIES}) +target_link_libraries(helloteapot PUBLIC OpenGL::GL OpenGL::GLU ${GLUT_LIBRARIES}) + +# Uncomment the following lines (remove the #) to add the helloteapot2.cpp to the build system +# add_executable(helloteapot2 helloteapot2.cpp) +# target_include_directories(helloteapot2 PUBLIC ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIR}) +# # target_link_libraries(helloteapot2 PUBLIC ${OPENGL_opengl_LIBRARY} ${OPENGL_glu_LIBRARY} ${GLUT_LIBRARIES}) +# target_link_libraries(helloteapot2 PUBLIC OpenGL::GL OpenGL::GLU ${GLUT_LIBRARIES}) + +add_executable(moreteapots moreteapots.cpp) +target_include_directories(moreteapots PUBLIC ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIR}) +#target_link_libraries(moreteapots PUBLIC ${OPENGL_opengl_LIBRARY} ${OPENGL_glu_LIBRARY} ${GLUT_LIBRARIES}) +target_link_libraries(moreteapots PUBLIC OpenGL::GL OpenGL::GLU ${GLUT_LIBRARIES}) + +# Uncomment the following lines (remove the #) to add the navigator to the build system (after having added the navigator.cpp) +add_executable(navigator navigator.cpp) +target_include_directories(navigator PUBLIC ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIR}) +# target_link_libraries(navigator PUBLIC ${OPENGL_opengl_LIBRARY} ${OPENGL_glu_LIBRARY} ${GLUT_LIBRARIES}) +target_link_libraries(navigator PUBLIC OpenGL::GL OpenGL::GLU ${GLUT_LIBRARIES}) diff --git a/TP1-2/README.md b/TP1-2/README.md new file mode 100755 index 0000000..6ef0ebe --- /dev/null +++ b/TP1-2/README.md @@ -0,0 +1,183 @@ +# Introduction to OpenGL + +- Building + * [Windows](#windows) + * [Linux](#linux) + * [MacOs](#macos) +- [Adding new files](#Adding-new-files-to-the-build-systems) + +--- + +## Windows + +### Prerequisites + +* download and install the latest version of CMake + + * download here: https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-win64-x64.msi + + * !!! When installing make sure that the checkbox "ne pas ajouter cmake au PATH" is NOT checked + + +* if you don't have it already, download and install MS Visual Studio Community Edition (free for students): https://visualstudio.microsoft.com/downloads/ + + * install instructions here: https://docs.microsoft.com/en-us/cpp/build/vscpp-step-0-installation?view=vs-2019 + + * !!! install the "Desktop development with C++" + + * If you have VS already installed, you can go in **Tools** --> **Get Tools and Features...** to install "Desktop development with C++" if it is missing. + + +### Create the Visual Studio Solution. + +This step enables you to create the project file to load inside VS: + +* unzip the code inside a folder. *Avoid to place the code in folders with spaces and accented characters*. + +* open a Terminal and go to the directory containing the code. + +* execute: + + * `md build` + + * `cd build` + + * `cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE:STRING=Release ..` + + * `dir` + + > if you had a different version of VS installed (not the latest) you may need to adapt the string `Visual Studio 16 2019` to your version: e.g. Visual Studio 15 2017, Visual Studio 14 2015, Visual Studio 12 2013 + +* if everything went well you should find a file named `tp1.sln` inside the directory. + + +### Compile, build, execute + +* open `tp1.sln` inside VS either by double clicking on it or opening from inside VS + +* build the solution (**Build Solution** from the **Build menu**) + +* from the tp directory copy `freeglut\bin\x64\freeglut.dll` in `build\Release` + +* execute the code: + + * Select the project you want to run (e.g. `helloteapot`), right click on it and select **Set as Startup Project** + + * On the menu bar, choose **Debug** --> **Start without debugging**.) + +(see https://docs.microsoft.com/en-us/cpp/build/vscpp-step-2-build?view=vs-2019 for how to build, execute, etc) + + +### Editing the code + +Edit the code according to the assignments that are given, rebuild the solution and execute. + +> !!! You need to run the cmake line only **once** + +> !!! You need to copy the dll file only **once**. + +--- + +## Linux + +### Prerequisites + +In order to develop with OpenGL some system packages are required (unless you are using the N7 machines): + +``` +sudo apt-get install libglu1-mesa-dev freeglut3-dev build-essential mesa-common-dev libxi-dev libxmu-dev automake +``` + +To build this code we use the CMake build system. You can install CMake from the system package manager but you need a recent version >= 3.10. Check the version that is provided by your linux distribution and if it is suitable usually you need to + + ``` + sudo apt-get install cmake + ``` + + otherwise you can install the binaries from here: https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Linux-x86_64.sh + + To install: + ``` + wget https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Linux-x86_64.sh + chmod +x cmake-3.17.1-Linux-x86_64.sh + sudo cmake-3.17.1-Linux-x86_64.sh --prefix=/usr/local/ --skip-license + ``` + +### Build + +To compile and build the code you do + + ``` + mkdir build && cd build + cmake .. + make + ``` + +Also, + +``` +make all +``` +builds everything, and + +``` +make clean +``` +cleans everything. + +Execute the code: + +``` +./helloteapot +``` + +### Editing the code + +Edit the code as required and then + +``` +make +``` + +> !!! the cmake line has to be run only **once** + +--- + +## macOS + +### Prerequisites + +In order to develop with OpenGL check if + +``` +ls /System/Library/Frameworks/ +``` +contains OpenGL and GLUT frameworks. +If not you need to install XCode from the `Mac App Store`, see here for more details https://developer.apple.com/support/xcode/ + +Install the latest version of CMake by downloading https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Darwin-x86_64.dmg + +### Build + + Same as Linux. + +### Editing the code + + Same as Linux. + +--- + +## Adding new files to the build systems + + * Create the new file (helloteapot2.cpp, navigator.cpp) + + * [Windows only] close VS + + * Edit `CMakeLists.txt` and uncomment (remove the `#` at the beginning) the lines relevant to the file + + * in the terminal, from the `build` directory run `cmake ..` + + * [Windows only] reload the solution file, now a new `helloteapot2` or `navigator` target should appear. Build/execute as usual + + * [other os] build and execute as usual, i.e. `make navigator`, `./navigator` ... + \ No newline at end of file diff --git a/TP1-2/appveyor.yml b/TP1-2/appveyor.yml new file mode 100755 index 0000000..446b0e1 --- /dev/null +++ b/TP1-2/appveyor.yml @@ -0,0 +1,30 @@ +version: '1.0.{build}' + +image: Visual Studio 2019 + +platform: + - x64 + +configuration: + - Release + +#install: +# - vcpkg upgrade --no-dry-run +# - vcpkg install +# opengl +# --triplet %PLATFORM%-windows + +before_build: + - md build + - cd build +# - cmake -G "Visual Studio 16 2019" -A x64 -T v140,host=x64 -DCMAKE_BUILD_TYPE=%configuration% -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake .. + - cmake -G "Visual Studio 16 2019" -A x64 -T v140,host=x64 -DCMAKE_BUILD_TYPE=%configuration% .. + - ls -l + +build: + verbosity: detailed + project: $(APPVEYOR_BUILD_FOLDER)\build\tp1.sln + parallel: true + +cache: +# - c:\tools\vcpkg\installed\ diff --git a/TP1-2/helloteapot.cpp b/TP1-2/helloteapot.cpp new file mode 100755 index 0000000..7601387 --- /dev/null +++ b/TP1-2/helloteapot.cpp @@ -0,0 +1,136 @@ +#include + +// for mac osx +#ifdef __APPLE__ +#include +#include +#include +#else +// only for windows +#ifdef _WIN32 +#include +#endif +// for windows and linux +#include +#include +#include +#include +#endif + +bool solidMode = false; +double angle = 0; + +// function called everytime the windows is refreshed +void display() +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // define the projection transformation + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(60, 1, 1, 10); + + // define the viewing transformation + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt(3 * cos(angle), 0, 3 * sin(angle), 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); + + // clear window + glClear(GL_COLOR_BUFFER_BIT); + + // draw scene + // glutWireTeapot(.5); + // glutSolidTeacup(0.5); + if (solidMode) + { + glutSolidTorus(0.25, 0.5, 100, 100); + } + else + { + glutWireTorus(0.25, 0.5, 100, 100); + } + + // flush drawing routines to the window + glFlush(); + angle += 0.001; + glutPostRedisplay(); +} + +// Function called everytime a key is pressed +void key(unsigned char key, int x, int y) +{ + switch (key) + { + case 'r': + angle += 0.1; + break; + case 'w': + solidMode = !solidMode; + break; + // the 'esc' key + case 27: + // the 'q' key + case 'q': + exit(EXIT_SUCCESS); + break; + default: + break; + } + glutPostRedisplay(); +} + +// Function called every time the main window is resized +void reshape(int width, int height) +{ + // define the viewport transformation; + glViewport(0, 0, width, height); +} + +// Main routine +int main(int argc, char *argv[]) +{ + + // initialize GLUT, using any commandline parameters passed to the + // program + glutInit(&argc, argv); + + // setup the size, position, and display mode for new windows + glutInitWindowSize(500, 500); + glutInitWindowPosition(0, 0); + glutInitDisplayMode(GLUT_RGB); + + // create and set up a window + glutCreateWindow("Hello, teapot!"); + + // Set up the callback functions: + // for display + glutDisplayFunc(display); + // for the keyboard + glutKeyboardFunc(key); + // for reshaping + glutReshapeFunc(reshape); + + // lights from tp3 + GLfloat light_ambient[] = {0.0, 0.0, 0.0, 1.0}; // the ambient component + GLfloat light_diffuse[] = {1.0, 1.0, 1.0, 1.0}; // the diffuse component + GLfloat light_specular[] = {1.0, 1.0, 1.0, 1.0}; // the specular component + GLfloat light_position[] = {1.0, 1.0, 1.0, 0.0}; // the light position + + // set the components to the first light + glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); + glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular); + glLightfv(GL_LIGHT0, GL_POSITION, light_position); + + // activate lighting effects + glEnable(GL_LIGHTING); + // turn on the first light + glEnable(GL_LIGHT0); + + glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH); + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + + // tell GLUT to wait for events + glutMainLoop(); +} diff --git a/TP1-2/imgs/blue_teapot_gone.png b/TP1-2/imgs/blue_teapot_gone.png new file mode 100644 index 0000000000000000000000000000000000000000..af15efa784d03d9799c05499d37ac09c3e15685e GIT binary patch literal 5648 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 Wnaz|PAIkc7gy93zzf0~vfBQcP=EuJP literal 0 HcmV?d00001 diff --git a/TP1-2/imgs/sphere_morewire.png b/TP1-2/imgs/sphere_morewire.png new file mode 100644 index 0000000000000000000000000000000000000000..432e38860703912ede2969cb621b94bec1b3c738 GIT binary patch literal 24988 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 ayCd8rPJvMMvJU?zn~H*_eC=C{@c#iYlH_v$ literal 0 HcmV?d00001 diff --git a/TP1-2/imgs/teapot_45.png b/TP1-2/imgs/teapot_45.png new file mode 100644 index 0000000000000000000000000000000000000000..c3a4a4fc50ed9dce03bfc619b99ede9fefc20cc3 GIT binary patch literal 12951 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%0D)A~{eertq<(QW3 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 P)pwp~8EMw5JH`GNoLJz; literal 0 HcmV?d00001 diff --git a/TP1-2/imgs/teapot_solid.png b/TP1-2/imgs/teapot_solid.png new file mode 100644 index 0000000000000000000000000000000000000000..4c2dabb6500e646510cf2b86ecc1a263d1f6915b GIT binary patch literal 3227 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 F{uc$y+nE3W literal 0 HcmV?d00001 diff --git a/TP1-2/imgs/teapot_upsidedown.png b/TP1-2/imgs/teapot_upsidedown.png new file mode 100644 index 0000000000000000000000000000000000000000..c114e3427a3c49517de4b54a2c199c5d7bbbf86c GIT binary patch literal 8545 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%0DKJ8Q7x@jGLb%{J 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 + +// for mac osx +#ifdef __APPLE__ +#include +#include +#include +#else +// only for windows +#ifdef _WIN32 +#include +#endif +// for windows and linux +#include +#include +#include +#endif + +#include +using namespace std; + +void display() +{ + glLoadIdentity(); + + /* clear window */ + glClear(GL_COLOR_BUFFER_BIT); + + // set current matrix as GL_MODELVIEW + glMatrixMode(GL_MODELVIEW); + + // draw scene + + // add a copy of the curr. matrix to the stack + glPushMatrix(); + + glPushMatrix(); + + // translate by -3 on the z + glTranslatef(0, 0, -3); + // set drawing color to red + glColor3f(1.0f, 0.0f, 0.0f); + // middle red teapot + glutWireTeapot(1); + // translate by 2 on the y + glTranslatef(0, 2, 0); + // set drawing color to green + glColor3f(0.0f, 1.0f, 0.0f); + // rotate 90 deg around x + glRotatef(90, 1.0f, 0.0f, 0.0f); + // top green teapot + glutWireTeapot(1); + // pop the current matrix + glPopMatrix(); + + // translate -2 on y and -1 on z + glTranslatef(0, -2, -1); + // set drawing color to blue + glColor3f(0.0f, 0.0f, 1.0f); + // bottom blue teapot + glutWireTeapot(1); + + // pop the current matrix + glPopMatrix(); + + /* flush drawing routines to the window */ + glFlush(); +} + +// Function called everytime a key is pressed +void key(unsigned char key, int x, int y) +{ + switch (key) + { + case 27: + case 'q': + exit(EXIT_SUCCESS); + break; + default: + break; + } + glutPostRedisplay(); +} + +void reshape(int width, int height) +{ + + /* define the viewport transformation */ + glViewport(0, 0, width, height); +} + +int main(int argc, char *argv[]) +{ + + /* initialize GLUT, using any commandline parameters passed to the + program */ + glutInit(&argc, argv); + + /* setup the size, position, and display mode for new windows */ + glutInitWindowSize(500, 500); + glutInitWindowPosition(0, 0); + glutInitDisplayMode(GLUT_RGB); + + /* create and set up a window */ + glutCreateWindow("hello, teapot!"); + // Set up the callback functions + // for display + glutDisplayFunc(display); + // for the keyboard + glutKeyboardFunc(key); + // for reshaping + glutReshapeFunc(reshape); + + /* define the projection transformation */ + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(60, 1, 1, 10); + + /* define the viewing transformation */ + glMatrixMode(GL_MODELVIEW); + + gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0); + + /* tell GLUT to wait for events */ + glutMainLoop(); +} diff --git a/TP1-2/navigator.cpp b/TP1-2/navigator.cpp new file mode 100755 index 0000000..67882e5 --- /dev/null +++ b/TP1-2/navigator.cpp @@ -0,0 +1,176 @@ +#include + +// for mac osx +#ifdef __APPLE__ +#include +#include +#include +#else +// only for windows +#ifdef _WIN32 +#include +#endif +// for windows and linux +#include +#include +#include +#include +#endif + +#include +using namespace std; + +#define SPEED 0.1 // OpenGL unit +#define ANG_SPEED 0.01 // degrees + +double x_cam = 0.0; +double y_cam = 0.0; +double z_cam = 5.0; + +double angleX_cam = 0.0; +double angleY_cam = -M_PI / 2; + +void display() +{ + /* define the projection transformation */ + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(60, 1, 1, 10); + + /* define the viewing transformation */ + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt( + x_cam, y_cam, z_cam, + x_cam + cos(angleY_cam), y_cam + sin(angleX_cam), z_cam + sin(angleY_cam) * cos(angleX_cam), + 0, cos(angleX_cam), 0); + + /* clear window */ + glClear(GL_COLOR_BUFFER_BIT); + + // set current matrix as GL_MODELVIEW + glMatrixMode(GL_MODELVIEW); + + // draw scene + + // add a copy of the curr. matrix to the stack + glPushMatrix(); + + glPushMatrix(); + // translate by -3 on the z + glTranslatef(0, 0, -3); + // set drawing color to red + glColor3f(1.0f, 0.0f, 0.0f); + // middle red teapot + glutWireTeapot(1); + // translate by 2 on the y + glTranslatef(0, 2, 0); + // set drawing color to blue + glColor3f(0.0f, 1.0f, 0.0f); + // rotate 90 deg around x + glRotatef(90, 1.0f, 0.0f, 0.0f); + // top green teapot + glutWireTeapot(1); + // pop the current matrix + glPopMatrix(); + + // translate -2 on y and -1 on z + glTranslatef(0, -2, -1); + // set drawing color to blue + glColor3f(0.0f, 0.0f, 1.0f); + // bottom blue teapot + glutWireTeapot(1); + + // pop the current matrix + glPopMatrix(); + + /* flush drawing routines to the window */ + glFlush(); +} + +// Function called everytime a key is pressed +void key(unsigned char key, int x, int y) +{ + switch (key) + { + case 'z': + z_cam -= SPEED; + break; + case 's': + z_cam += SPEED; + break; + case 'q': + x_cam -= SPEED; + break; + case 'd': + x_cam += SPEED; + break; + case 'a': + y_cam -= SPEED; + break; + case 'e': + y_cam += SPEED; + break; + case 27: + exit(EXIT_SUCCESS); + break; + default: + break; + } + glutPostRedisplay(); +} + +void specialkey(int key, int x, int y) +{ + switch (key) + { + case GLUT_KEY_LEFT: + angleY_cam -= ANG_SPEED; + break; + case GLUT_KEY_RIGHT: + angleY_cam += ANG_SPEED; + break; + case GLUT_KEY_DOWN: + angleX_cam -= ANG_SPEED; + break; + case GLUT_KEY_UP: + angleX_cam += ANG_SPEED; + break; + default: + break; + } + glutPostRedisplay(); +} + +void reshape(int width, int height) +{ + /* define the viewport transformation */ + glViewport(0, 0, width, height); +} + +int main(int argc, char *argv[]) +{ + /* initialize GLUT, using any commandline parameters passed to the + program */ + glutInit(&argc, argv); + + /* setup the size, position, and display mode for new windows */ + glutInitWindowSize(500, 500); + glutInitWindowPosition(0, 0); + glutInitDisplayMode(GLUT_RGB); + + /* create and set up a window */ + glutCreateWindow("hello, navigator!"); + // Set up the callback functions + // for display + glutDisplayFunc(display); + // for the keyboard + glutKeyboardFunc(key); + // for special keys + glutSpecialFunc(specialkey); + // for reshaping + glutReshapeFunc(reshape); + + /* tell GLUT to wait for events */ + glutMainLoop(); +} diff --git a/TP1-2/readme.html b/TP1-2/readme.html new file mode 100755 index 0000000..3a81489 --- /dev/null +++ b/TP1-2/readme.html @@ -0,0 +1,124 @@ + + + + + + + + + + +

Introduction to OpenGL

+
+
+

Windows

+

Prerequisites

+
    +
  • download and install the latest version of CMake

  • +
  • download here: https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-win64-x64.msi

  • +
  • !!! When installing make sure that the checkbox "ne pas ajouter cmake au PATH" is NOT checked

  • +
  • if you don't have it already, download and install MS Visual Studio Community Edition (free for students): https://visualstudio.microsoft.com/downloads/

    +
      +
    • install instructions here: https://docs.microsoft.com/en-us/cpp/build/vscpp-step-0-installation?view=vs-2019

    • +
    • !!! install the "Desktop development with C++"

    • +
    • If you have VS already installed, you can go in Tools --> Get Tools and Features... to install "Desktop development with C++" if it is missing.

    • +
  • +
+

Create the Visual Studio Solution.

+

This step enables you to create the project file to load inside VS:

+
    +
  • unzip the code inside a folder. Avoid to place the code in folders with spaces and accented characters.

  • +
  • open a Terminal and go to the directory containing the code.

  • +
  • execute:

  • +
  • md build

  • +
  • cd build

  • +
  • cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE:STRING=Release ..

  • +
  • dir

  • +
+
+

if you had a different version of VS installed (not the latest) you may need to adapt the string Visual Studio 16 2019 to your version: e.g. Visual Studio 15 2017, Visual Studio 14 2015, Visual Studio 12 2013

+
+
    +
  • if everything went well you should find a file named tp1.sln inside the directory.
  • +
+

Compile, build, execute

+
    +
  • open tp1.sln inside VS either by double clicking on it or opening from inside VS

  • +
  • build the solution (Build Solution from the Build menu)

  • +
  • from the tp directory copy freeglut\bin\x64\freeglut.dll in build\Release

  • +
  • execute the code:

  • +
  • Select the project you want to run (e.g. helloteapot), right click on it and select Set as Startup Project

  • +
  • On the menu bar, choose Debug --> Start without debugging.)

  • +
+

(see https://docs.microsoft.com/en-us/cpp/build/vscpp-step-2-build?view=vs-2019 for how to build, execute, etc)

+

Editing the code

+

Edit the code according to the assignments that are given, rebuild the solution and execute.

+
+

!!! You need to run the cmake line only once

+
+
+

!!! You need to copy the dll file only once.

+
+
+

Linux

+

Prerequisites

+

In order to develop with OpenGL some system packages are required (unless you are using the N7 machines):

+
sudo apt-get install libglu1-mesa-dev freeglut3-dev build-essential mesa-common-dev libxi-dev libxmu-dev automake
+

To build this code we use the CMake build system. You can install CMake from the system package manager but you need a recent version >= 3.10. Check the version that is provided by your linux distribution and if it is suitable usually you need to

+
```
+sudo apt-get install cmake
+```
+
+otherwise you can install the binaries from here: https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Linux-x86_64.sh
+
+To install:
+```
+wget https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Linux-x86_64.sh
+chmod +x cmake-3.17.1-Linux-x86_64.sh
+sudo cmake-3.17.1-Linux-x86_64.sh --prefix=/usr/local/ --skip-license
+```
+

Build

+

To compile and build the code you do

+

mkdir build && cd build cmake .. make <name_file_without_cpp>

+

Also,

+
make all
+

builds everything, and

+
make clean
+

cleans everything.

+

Execute the code:

+
./helloteapot
+

Editing the code

+

Edit the code as required and then

+
make  <name_file_without_cpp>
+
+

!!! the cmake line has to be run only once

+
+
+

macOS

+

Prerequisites

+

In order to develop with OpenGL check if

+
ls  /System/Library/Frameworks/
+

contains OpenGL and GLUT frameworks. If not you need to install Xcode from the Mac App Store, see here for more details https://developer.apple.com/support/xcode/

+

If you want to use CMake, follow the instructions for linux to install the latest version

+

Build

+

Same as Linux.

+

Editing the code

+

Same as Linux.

+
+

Adding new files to the build systems

+
    +
  • Create the new file (helloteapot2.cpp, navigator.cpp)

  • +
  • [Windows only] close VS

  • +
  • Edit CMakeLists.txt and uncomment (remove the # at the beginning) the lines relevant to the file

  • +
  • in the terminal, from the build directory run cmake ..

  • +
  • [Windows only] reload the solution file, now a new helloteapot2 or navigator target should appear. Build/execute as usual

  • +
  • [other os] build and execute as usual, i.e. make navigator, ./navigator ...

  • +
+ + diff --git a/TP1-2/reponses.md b/TP1-2/reponses.md new file mode 100644 index 0000000..d81b0e5 --- /dev/null +++ b/TP1-2/reponses.md @@ -0,0 +1,137 @@ +# Replace glutWireTeapot with glutSolidTeapot. +What do you see ? +What do you think it is still missing in order to see a more realistic rendering of the teapot? + +> On voit un teapot "plein"/solid, il manque simplement un peu de lumière/shading, pour que le rendu soit plus réaliste. + + + + + + +
+
glutWireTeapot(.5)
+ +
+
glutSolidTeapot(.5)
+ +
+ +# Replace the teapot with a sphere using glutWireSphere. +The parameter slices is the number of ‘meridians’ around the z-axis to draw, and stacks the ‘parallels’ along the z-axis. +Try with different values for each of them and compare the results + + + + + + +
+
glutWireSphere(0.7, 20, 20)
+ +
+
glutWireSphere(0.7, 100, 100)
+ +
+ +# Try to manually resize the window. What happens? +How can we preserve the aspect ratio of our scene? +Change the call to glViewport so that every time the windows is resized, the viewport is always a square centred both vertically and horizontally inside the window, and its size is the maximum that can fit the window (i.e. its size is the minimum between the width and the height of the window). + +> L'aspect ratio de notre fenêtre change, mais l'aspect ratio de notre scène n'étant pas fixe, on observe que le rendu devient difforme. + +> En utilisant le callback glviewport, on peut ainsi fixer/conserver l'aspect ratio en toute circonstance. + +# Add a new case for the letter ‘w’, so that every time it is pressed we can switch from a wireframe teapot to a solid teapot and viceversa. +(Hint: you can define and use a global bool variable...) + +> cf helloteapot.cpp + +# Try to increase and decrease the value of the parameter fovy of gluPerspective. +What happens and why? + +> fovy correspond à la focale de la caméra selon l'axe y de l'image. + +> On observe alors que les objets sont comprimés ou étalées, si l'on diminue ou augmente fovy. + +# Try to increase the value of the parameter zNear of gluPerspective. +What happens for values, e.g., greater than 2.1? + +> zNear permet de définir le début du volume de clipping. + +> Ainsi si l'on défini zNear supérieur à 2.1 dans la scène de l'exercice, le volume de clipping se situe derrière le teapot et donc on ne le voit plus. + +# What happens if we invert the up vector, i.e. we set it to (0,-1,0)? + +> L'image est à l'envers. + + + + + +
+
gluLookAt(0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0)
+ +
+ +# Set the camera so that we can see the teapot as in Figure 9 (a-b)? +How do you change the “up-vector”? + +> On défini le up vector : (1, 1, -1) + + + + + + +
+
gluLookAt(0.0, 0.0, -2.0, 0.0, 0.0, 0.0, 1.0, 1.0, -1.0)
+ +
+
gluLookAt(0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0);
+ +
+ +# What happens if the second glPopMatrix in display isn’t there? +Try resizing the window or partially covering it, to force a repaint. +Why does this occur? (Consider what happens to the modelview stack.) + +> On push deux matrices au début de la fonction. On doit alors pop deux matrices avant de finir la fonction. Si on enlève le deuxième glPopMatrix, alors les transformations suivantes seront conservées lors de la prochaine itération: + +```cpp +// translate -2 on y and -1 on z +glTranslatef(0, -2, -1); +// set drawing color to blue +glColor3f(0.0f, 0.0f, 1.0f); +// bottom blue teapot +glutWireTeapot(1); +``` + +Ainsi lorsque l'on redimensionne la fenêtre, on doit redessiner les teapots, et par accumulation des glTranslatef, ceux-ci disparaissent. + +# What happens if the gluLookAt(0.0,0.0,5.0,0.0,0.0,0.0,0.0,1.0,0.0) line in main is moved after the second glPushMatrix in display? +Why does this occur? (Consider what happens to the modelview stack.) + +> On observe que le teapot bleu n'est plus affiché. On suppose que cela se produit car le gluLookAt n'est appliqué que sur les deux premiers teapots (le rouge et le vert). Après le premier pop, l'information du gluLookAt, n'est pas transmise au tracé du teapot bleu. + + + + + +
+ +
+ + +# What happens instead if the glLoadIdentity line in main is moved to just before the “draw scene” comment in display, and why? +(Consider what happens to the modelview stack.) + +> En utilisant glLoadIdentity en début d'itéation, on "reset" la camera, cela inclut gluLookAt et gluPerspective. On obtient une image très zoommée. + + + + + +
+ +
\ No newline at end of file diff --git a/TP3/CMakeLists.txt b/TP3/CMakeLists.txt new file mode 100644 index 0000000..8cce6b5 --- /dev/null +++ b/TP3/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.10) +project(tp2 VERSION 2021.0.0 LANGUAGES C CXX) + +set(CMAKE_CXX_STANDARD 11) + +#set(OpenGL_GL_PREFERENCE "GLVND") +find_package(OpenGL REQUIRED) +message(STATUS "OPENGL_gl_LIBRARY: ${OPENGL_gl_LIBRARY}") + +if(MSVC) + set(GLUT_ROOT_PATH "${CMAKE_SOURCE_DIR}/freeglut") + message(STATUS "GLUT_ROOT_PATH: ${GLUT_ROOT_PATH}") +endif() +find_package(GLUT REQUIRED) +message(STATUS "GLUT_FOUND: ${GLUT_FOUND}") +message(STATUS "GLUT_INCLUDE_DIR: ${GLUT_INCLUDE_DIR}") +message(STATUS "GLUT_LIBRARIES: ${GLUT_LIBRARIES}") + +if(APPLE) + add_definitions(-Wno-deprecated-declarations) +endif() + + +add_executable(robot robot.c) +#target_include_directories(robot PUBLIC ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIR}) +#target_link_libraries(robot PUBLIC ${OPENGL_opengl_LIBRARY} ${OPENGL_glu_LIBRARY} ${GLUT_LIBRARIES}) +target_link_libraries(robot OpenGL::GL OpenGL::GLU GLUT::GLUT) +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + target_compile_options(robot PRIVATE -Wall -Wextra -pedantic -Wno-comment) +endif() +if(MSVC) + target_compile_definitions(robot PUBLIC -DNOMINMAX) +endif() diff --git a/TP3/README.md b/TP3/README.md new file mode 100644 index 0000000..8fcd300 --- /dev/null +++ b/TP3/README.md @@ -0,0 +1,170 @@ +# Introduction to OpenGL + +- Building + * [Windows](#windows) + * [Linux](#linux) + * [MacOs](#macos) + +--- + +## Windows + +### Prerequisites + +Not necessary if you already did the previous tp, but to recall: +* download and install the latest version of CMake + + * download here: https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-win64-x64.msi + + * !!! When installing make sure that the checkbox "ne pas ajouter cmake au PATH" is NOT checked + + +* if you don't have it already, download and install MS Visual Studio Community Edition (free for students): https://visualstudio.microsoft.com/downloads/ + + * install instructions here: https://docs.microsoft.com/en-us/cpp/build/vscpp-step-0-installation?view=vs-2019 + + * !!! install the "Desktop development with C++" + + * If you have VS already installed, you can go in **Tools** --> **Get Tools and Features...** to install "Desktop development with C++" if it is missing. + + +### Create the Visual Studio Solution. + +This step enables you to create the project file to load inside VS: + +* unzip the code inside a folder. *Avoid to place the code in folders with spaces and accented characters*. + +* open a Terminal and go to the directory containing the code. + +* execute: + + * `md build` + + * `cd build` + + * `cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE:STRING=Release ..` + + * `dir` + + > if you had a different version of VS installed (not the latest) you may need to adapt the string `Visual Studio 16 2019` to your version: e.g. Visual Studio 15 2017, Visual Studio 14 2015, Visual Studio 12 2013 + +* if everything went well you should find a file named `tp2.sln` inside the directory. + + +### Compile, build, execute + +* open `tp1.sln` inside VS either by double clicking on it or opening from inside VS + +* build the solution (**Build Solution** from the **Build menu**) + +* from the tp directory copy `freeglut\bin\x64\freeglut.dll` in `build\Release` + +* execute the code: + + * Select the project you want to run (e.g. `robot`), right click on it and select **Set as Startup Project** + + * On the menu bar, choose **Debug** --> **Start without debugging**.) + +(see https://docs.microsoft.com/en-us/cpp/build/vscpp-step-2-build?view=vs-2019 for how to build, execute, etc) + + +### Editing the code + +Edit the code according to the assignments that are given, rebuild the solution and execute. + +> !!! You need to run the cmake line only **once** + +> !!! You need to copy the dll file only **once**. + +--- + +## Linux + +### Prerequisites + +**Not necessary if you already did the previous tp.** + +In order to develop with OpenGL some system packages are required (unless you are using the N7 machines): + +``` +sudo apt-get install libglu1-mesa-dev freeglut3-dev build-essential mesa-common-dev libxi-dev libxmu-dev automake +``` + +To build this code we use the CMake build system. You can install CMake from the system package manager but you need a recent version >= 3.10. Check the version that is provided by your linux distribution and if it is suitable usually you need to + + ``` + sudo apt-get install cmake + ``` + + otherwise you can install the binaries from here: https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Linux-x86_64.sh + + To install: + ``` + wget https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Linux-x86_64.sh + chmod +x cmake-3.17.1-Linux-x86_64.sh + sudo cmake-3.17.1-Linux-x86_64.sh --prefix=/usr/local/ --skip-license + ``` + +### Build + +To compile and build the code you do + + ``` + mkdir build && cd build + cmake .. + make robot + ``` + +Also, + +``` +make all +``` +builds everything, and + +``` +make clean +``` +cleans everything. + +Execute the code: + +``` +./robot +``` + +### Editing the code + +Edit the code as required and then + +``` +make robot +``` + +> !!! the cmake line has to be run only **once** + +--- + +## macOS + +### Prerequisites + +In order to develop with OpenGL check if + +``` +ls /System/Library/Frameworks/ +``` +contains OpenGL and GLUT frameworks. +If not you need to install XCode from the `Mac App Store`, see here for more details https://developer.apple.com/support/xcode/ + +Install the latest version of CMake by downloading https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Darwin-x86_64.dmg + +### Build + +Same as Linux. + +### Editing the code + +Same as Linux. + + diff --git a/TP3/appveyor.yml b/TP3/appveyor.yml new file mode 100644 index 0000000..49a0e92 --- /dev/null +++ b/TP3/appveyor.yml @@ -0,0 +1,31 @@ +version: '2021.0.0.{build}' + +image: Visual Studio 2019 + +platform: + - x64 + +configuration: + - Release + - Debug + +#install: +# - vcpkg upgrade --no-dry-run +# - vcpkg install +# opengl +# --triplet %PLATFORM%-windows + +before_build: + - md build + - cd build +# - cmake -G "Visual Studio 16 2019" -A x64 -T v140,host=x64 -DCMAKE_BUILD_TYPE=%configuration% -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake .. + - cmake -G "Visual Studio 16 2019" -A x64 -T v140,host=x64 -DCMAKE_BUILD_TYPE=%configuration% .. + - ls -l + +build: + verbosity: detailed + project: $(APPVEYOR_BUILD_FOLDER)\build\tp2.sln + parallel: true + +cache: +# - c:\tools\vcpkg\installed\ diff --git a/TP3/readme.html b/TP3/readme.html new file mode 100644 index 0000000..155e709 --- /dev/null +++ b/TP3/readme.html @@ -0,0 +1,114 @@ + + + + + + + + + + +

Introduction to OpenGL

+ +
+

Windows

+

Prerequisites

+

Not necessary if you already did the previous tp, but to recall: * download and install the latest version of CMake

+
    +
  • download here: https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-win64-x64.msi

  • +
  • !!! When installing make sure that the checkbox "ne pas ajouter cmake au PATH" is NOT checked

  • +
  • if you don't have it already, download and install MS Visual Studio Community Edition (free for students): https://visualstudio.microsoft.com/downloads/

    +
      +
    • install instructions here: https://docs.microsoft.com/en-us/cpp/build/vscpp-step-0-installation?view=vs-2019

    • +
    • !!! install the "Desktop development with C++"

    • +
    • If you have VS already installed, you can go in Tools --> Get Tools and Features... to install "Desktop development with C++" if it is missing.

    • +
  • +
+

Create the Visual Studio Solution.

+

This step enables you to create the project file to load inside VS:

+
    +
  • unzip the code inside a folder. Avoid to place the code in folders with spaces and accented characters.

  • +
  • open a Terminal and go to the directory containing the code.

  • +
  • execute:

  • +
  • md build

  • +
  • cd build

  • +
  • cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE:STRING=Release ..

  • +
  • dir

  • +
+
+

if you had a different version of VS installed (not the latest) you may need to adapt the string Visual Studio 16 2019 to your version: e.g. Visual Studio 15 2017, Visual Studio 14 2015, Visual Studio 12 2013

+
+
    +
  • if everything went well you should find a file named tp2.sln inside the directory.
  • +
+

Compile, build, execute

+
    +
  • open tp1.sln inside VS either by double clicking on it or opening from inside VS

  • +
  • build the solution (Build Solution from the Build menu)

  • +
  • from the tp directory copy freeglut\bin\x64\freeglut.dll in build\Release

  • +
  • execute the code:

  • +
  • Select the project you want to run (e.g. robot), right click on it and select Set as Startup Project

  • +
  • On the menu bar, choose Debug --> Start without debugging.)

  • +
+

(see https://docs.microsoft.com/en-us/cpp/build/vscpp-step-2-build?view=vs-2019 for how to build, execute, etc)

+

Editing the code

+

Edit the code according to the assignments that are given, rebuild the solution and execute.

+
+

!!! You need to run the cmake line only once

+
+
+

!!! You need to copy the dll file only once.

+
+
+

Linux

+

Prerequisites

+

Not necessary if you already did the previous tp.

+

In order to develop with OpenGL some system packages are required (unless you are using the N7 machines):

+
sudo apt-get install libglu1-mesa-dev freeglut3-dev build-essential mesa-common-dev libxi-dev libxmu-dev automake
+

To build this code we use the CMake build system. You can install CMake from the system package manager but you need a recent version >= 3.10. Check the version that is provided by your linux distribution and if it is suitable usually you need to

+
```
+sudo apt-get install cmake
+```
+
+otherwise you can install the binaries from here: https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Linux-x86_64.sh
+
+To install:
+```
+wget https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Linux-x86_64.sh
+chmod +x cmake-3.17.1-Linux-x86_64.sh
+sudo cmake-3.17.1-Linux-x86_64.sh --prefix=/usr/local/ --skip-license
+```
+

Build

+

To compile and build the code you do

+

mkdir build && cd build cmake .. make robot

+

Also,

+
make all
+

builds everything, and

+
make clean
+

cleans everything.

+

Execute the code:

+
./robot
+

Editing the code

+

Edit the code as required and then

+
make robot
+
+

!!! the cmake line has to be run only once

+
+
+

macOS

+

Prerequisites

+

In order to develop with OpenGL check if

+
ls  /System/Library/Frameworks/
+

contains OpenGL and GLUT frameworks. If not you need to install XCode from the Mac App Store, see here for more details https://developer.apple.com/support/xcode/

+

Install the latest version of CMake by downloading https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Darwin-x86_64.dmg

+

Build

+

Same as Linux.

+

Editing the code

+

Same as Linux.

+ + diff --git a/TP3/robot.c b/TP3/robot.c new file mode 100644 index 0000000..27b50f7 --- /dev/null +++ b/TP3/robot.c @@ -0,0 +1,355 @@ + +#include +#include + +// for mac osx +#ifdef __APPLE__ +#include +#include +#include +#else +// only for windows +#ifdef _WIN32 +#include +#endif +// for windows and linux +#include +#include +#include +#endif + +#include + +// Global variables to animate the robotic arm +int Angle1 = 45; +int Angle2 = 45; + +// Global variables to rotate the arm as a whole +int RobotAngleX = 0; +int RobotAngleY = -20; + +float pincer = 0.5; + +//************************************************************************* +// Function that draws a reference system +//************************************************************************* +void DrawReferenceSystem() +{ + //********************************** + // set the line width to 3.0 + //********************************** + + glLineWidth(3); + + //********************************** + // Draw three lines along the x, y, z axis to represent the reference system + // Use red for the x-axis, green for the y-axis and blue for the z-axis + //********************************** + + glBegin(GL_LINES); + + glColor3f(1, 0, 0); + glVertex3f(0, 0, 0); + glVertex3f(1, 0, 0); + + glColor3f(0, 1, 0); + glVertex3f(0, 0, 0); + glVertex3f(0, 1, 0); + + glColor3f(0, 0, 1); + glVertex3f(0, 0, 0); + glVertex3f(0, 0, 1); + + glEnd(); + + //********************************** + // reset the drawing color to white + //********************************** + + glColor3f(1, 1, 1); + + //********************************** + // reset the line width to 1.0 + //********************************** + + glLineWidth(1); +} + +//************************************************************************* +// Function that draws a single joint of the robotic arm +//************************************************************************* +void DrawJoint() +{ + // first draw the reference system + DrawReferenceSystem(); + + // Draw the joint as a parallelepiped (a cube scaled on the y-axis) + // the bottom face of the cube must be on the xz plane + + //********************************** + // draw the parallelepiped + //********************************** + glPushMatrix(); + glTranslatef(0, 1, 0); + glScalef(1, 2, 1); + glutWireCube(1); + glPopMatrix(); +} + +// Function that draws the robot as three parallelepipeds +void DrawRobot() +{ + //********************************** + // It's better to work on a local reference system... + //********************************** + glPushMatrix(); + + // draw the first joint + DrawJoint(); + + // Draw the other joints + // every joint must be placed on top of the previous one + // and rotated according to the relevant Angle + //********************************** + // the second joint + //********************************** + glTranslatef(0, 2, 0); + glRotatef(Angle1, 0, 0, 1); + DrawJoint(); + + //********************************** + // the third joint + //********************************** + glTranslatef(0, 2, 0); + glRotatef(Angle2, 0, 0, 1); + DrawJoint(); + + //********************************** + // the pincer's base + //********************************** + glTranslatef(0, 2, 0); + DrawReferenceSystem(); + + glPushMatrix(); + glTranslatef(0, 0.125, 0); + glScalef(1, 0.25, 0.25); + glutWireCube(1); + glPopMatrix(); + + glTranslatef(0, 0.25, 0); + DrawReferenceSystem(); + + // pincer left + glPushMatrix(); + glTranslatef(pincer, 0.5, 0); + glScalef(0.25, 1, 0.25); + glutWireCube(1); + glPopMatrix(); + + // picer right + glPushMatrix(); + glTranslatef(-pincer, 0.5, 0); + glScalef(0.25, 1, 0.25); + glutWireCube(1); + glPopMatrix(); + + //********************************** + // "Release" the copy of the current MODELVIEW matrix + //********************************** + glPopMatrix(); +} + +//************************************************************************* +// display callback +//************************************************************************* +void display(void) +{ + // clear the window + glClear(GL_COLOR_BUFFER_BIT); + + // working with the GL_MODELVIEW Matrix + glMatrixMode(GL_MODELVIEW); + + //********************************** + // we work on a copy of the current MODELVIEW matrix, hence we need to... + //********************************** + glPushMatrix(); + + //********************************** + // Rotate the robot around the x-axis and y-axis according to the relevant angles + //********************************** + glRotatef(RobotAngleX, 1, 0, 0); + glRotatef(RobotAngleY, 0, 1, 0); + + // draw the robot + DrawRobot(); + + //********************************** + // not anymore on a local reference system + //********************************** + glPopMatrix(); + + // flush drawing routines to the window + glutSwapBuffers(); +} + +//************************************************************************* +// Special keys callback +//************************************************************************* +void arrows(int key, int x, int y) +{ + //********************************** + // Manage the update of RobotAngleX and RobotAngleY with the arrow keys + //********************************** + switch (key) + { + case GLUT_KEY_LEFT: + RobotAngleY += 5; + break; + case GLUT_KEY_RIGHT: + RobotAngleY -= 5; + break; + case GLUT_KEY_UP: + RobotAngleX += 5; + break; + case GLUT_KEY_DOWN: + RobotAngleX -= 5; + break; + default: + break; + } + glutPostRedisplay(); +} + +//************************************************************************* +// Keyboard callback +//************************************************************************* +void keyboard(unsigned char key, int x, int y) +{ + switch (key) + { + case 'q': + case 27: + exit(0); + break; + + //********************************** + // Manage the update of Angle1 with the key 'a' and 'z' + //********************************** + case 'a': + Angle1 += 5; + break; + case 'z': + Angle1 -= 5; + break; + + //********************************** + // Manage the update of Angle2 with the key 'e' and 'r' + //********************************** + case 'e': + Angle2 += 5; + break; + case 'r': + Angle2 -= 5; + break; + + //********************************** + // Manage the pincer + //********************************** + case 'o': + pincer += 0.01; + break; + case 'l': + pincer -= 0.01; + break; + + default: + break; + } + + if (pincer >= 0.5) + { + pincer = 0.5; + } + else if (pincer <= 0.125) + { + pincer = 0.125; + } + + glutPostRedisplay(); +} + +void init(void) +{ + glClearColor(0.0, 0.0, 0.0, 0.0); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(65.0, 1.0, 1.0, 100.0); + + glShadeModel(GL_FLAT); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + // Place the camera + gluLookAt(-6, 5, -6, 0, 0, 2, 0, 1, 0); +} + +//************************************************************************* +// Function called every time the main window is resized +//************************************************************************* +void reshape(int width, int height) +{ + + // define the viewport transformation; + glViewport(0, 0, width, height); + if (width < height) + glViewport(0, (height - width) / 2, width, width); + else + glViewport((width - height) / 2, 0, height, height); +} + +//************************************************************************* +// Prints out how to use the keyboard +//************************************************************************* +void usage() +{ + printf("\n*******\n"); + printf("Arrows key: rotate the whole robot\n"); + printf("[a][z] : move the second joint of the arm\n"); + printf("[e][r] : move the third joint of the arm\n"); + printf("[esc] : terminate\n"); + printf("*******\n"); +} + +////////////////////////////////////////////////////////////// +int main(int argc, char **argv) +{ + glutInit(&argc, argv); + glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); + glutInitWindowSize(500, 500); + glutInitWindowPosition(100, 100); + glutCreateWindow(argv[0]); + init(); + glutDisplayFunc(display); + + glutReshapeFunc(reshape); + + //********************************** + // Register the keyboard function + //********************************** + glutKeyboardFunc(keyboard); + + //********************************** + // Register the special key function + //********************************** + glutSpecialFunc(arrows); + + // just print the help + usage(); + + glutMainLoop(); + + return EXIT_SUCCESS; +} + +////////////////////////////////////////////////////////////// diff --git a/TP4/CMakeLists.txt b/TP4/CMakeLists.txt new file mode 100644 index 0000000..a77912a --- /dev/null +++ b/TP4/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.10) +project(tp3 VERSION 2021.0.0 LANGUAGES C CXX) + +set(CMAKE_CXX_STANDARD 11) + +set(OpenGL_GL_PREFERENCE "GLVND") +find_package(OpenGL REQUIRED) +message(STATUS "OPENGL_gl_LIBRARY: ${OPENGL_gl_LIBRARY}") + +if(MSVC) + set(GLUT_ROOT_PATH "${CMAKE_SOURCE_DIR}/freeglut") + message(STATUS "GLUT_ROOT_PATH: ${GLUT_ROOT_PATH}") +endif() +find_package(GLUT REQUIRED) +message(STATUS "GLUT_FOUND: ${GLUT_FOUND}") +message(STATUS "GLUT_INCLUDE_DIR: ${GLUT_INCLUDE_DIR}") +message(STATUS "GLUT_LIBRARIES: ${GLUT_LIBRARIES}") + +if(APPLE) + add_definitions(-Wno-deprecated-declarations) +endif() + +add_executable(lumiere lumiere.c) +#target_include_directories(lumiere PUBLIC ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIR}) +#target_link_libraries(lumiere PUBLIC ${OPENGL_opengl_LIBRARY} ${OPENGL_glu_LIBRARY} ${GLUT_LIBRARIES}) +target_link_libraries(lumiere OpenGL::GL OpenGL::GLU GLUT::GLUT) + +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + target_compile_options(lumiere PRIVATE -Wall -Wextra -pedantic -Wno-comment) +endif() +if(MSVC) + target_compile_definitions(lumiere PUBLIC -DNOMINMAX) +endif() diff --git a/TP4/README.md b/TP4/README.md new file mode 100644 index 0000000..1e0c4a9 --- /dev/null +++ b/TP4/README.md @@ -0,0 +1,170 @@ +# Introduction to OpenGL + +- Building + * [Windows](#windows) + * [Linux](#linux) + * [MacOs](#macos) + +--- + +## Windows + +### Prerequisites + +Not necessary if you already did the previous tp, but to recall: +* download and install the latest version of CMake + + * download here: https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-win64-x64.msi + + * !!! When installing make sure that the checkbox "ne pas ajouter cmake au PATH" is NOT checked + + +* if you don't have it already, download and install MS Visual Studio Community Edition (free for students): https://visualstudio.microsoft.com/downloads/ + + * install instructions here: https://docs.microsoft.com/en-us/cpp/build/vscpp-step-0-installation?view=vs-2019 + + * !!! install the "Desktop development with C++" + + * If you have VS already installed, you can go in **Tools** --> **Get Tools and Features...** to install "Desktop development with C++" if it is missing. + + +### Create the Visual Studio Solution. + +This step enables you to create the project file to load inside VS: + +* unzip the code inside a folder. *Avoid to place the code in folders with spaces and accented characters*. + +* open a Terminal and go to the directory containing the code. + +* execute: + + * `md build` + + * `cd build` + + * `cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE:STRING=Release ..` + + * `dir` + + > if you had a different version of VS installed (not the latest) you may need to adapt the string `Visual Studio 16 2019` to your version: e.g. Visual Studio 15 2017, Visual Studio 14 2015, Visual Studio 12 2013 + +* if everything went well you should find a file named `tp3.sln` inside the directory. + + +### Compile, build, execute + +* open `tp3.sln` inside VS either by double clicking on it or opening from inside VS + +* build the solution (**Build Solution** from the **Build menu**) + +* from the tp directory copy `freeglut\bin\x64\freeglut.dll` in `build\Release` + +* execute the code: + + * Select the project you want to run (e.g. `lumiere`), right click on it and select **Set as Startup Project** + + * On the menu bar, choose **Debug** --> **Start without debugging**.) + +(see https://docs.microsoft.com/en-us/cpp/build/vscpp-step-2-build?view=vs-2019 for how to build, execute, etc) + + +### Editing the code + +Edit the code according to the assignments that are given, rebuild the solution and execute. + +> !!! You need to run the cmake line only **once** + +> !!! You need to copy the dll file only **once**. + +--- + +## Linux + +### Prerequisites + +**Not necessary if you already did the previous tp.** + +In order to develop with OpenGL some system packages are required (unless you are using the N7 machines): + +``` +sudo apt-get install libglu1-mesa-dev freeglut3-dev build-essential mesa-common-dev libxi-dev libxmu-dev automake +``` + +To build this code we use the CMake build system. You can install CMake from the system package manager but you need a recent version >= 3.10. Check the version that is provided by your linux distribution and if it is suitable usually you need to + + ``` + sudo apt-get install cmake + ``` + + otherwise you can install the binaries from here: https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Linux-x86_64.sh + + To install: + ``` + wget https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Linux-x86_64.sh + chmod +x cmake-3.17.1-Linux-x86_64.sh + sudo cmake-3.17.1-Linux-x86_64.sh --prefix=/usr/local/ --skip-license + ``` + +### Build + +To compile and build the code you do + + ``` + mkdir build && cd build + cmake .. + make lumiere + ``` + +Also, + +``` +make all +``` +builds everything, and + +``` +make clean +``` +cleans everything. + +Execute the code: + +``` +./lumiere +``` + +### Editing the code + +Edit the code as required and then + +``` +make lumiere +``` + +> !!! the cmake line has to be run only **once** + +--- + +## macOS + +### Prerequisites + +In order to develop with OpenGL check if + +``` +ls /System/Library/Frameworks/ +``` +contains OpenGL and GLUT frameworks. +If not you need to install XCode from the `Mac App Store`, see here for more details https://developer.apple.com/support/xcode/ + +Install the latest version of CMake by downloading https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Darwin-x86_64.dmg + +### Build + +Same as Linux. + +### Editing the code + +Same as Linux. + + diff --git a/TP4/appveyor.yml b/TP4/appveyor.yml new file mode 100644 index 0000000..83a1951 --- /dev/null +++ b/TP4/appveyor.yml @@ -0,0 +1,31 @@ +version: '1.0.{build}' + +image: Visual Studio 2019 + +platform: + - x64 + +configuration: + - Release + - Debug + +#install: +# - vcpkg upgrade --no-dry-run +# - vcpkg install +# opengl +# --triplet %PLATFORM%-windows + +before_build: + - md build + - cd build +# - cmake -G "Visual Studio 16 2019" -A x64 -T v140,host=x64 -DCMAKE_BUILD_TYPE=%configuration% -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake .. + - cmake -G "Visual Studio 16 2019" -A x64 -T v140,host=x64 -DCMAKE_BUILD_TYPE=%configuration% .. + - ls -l + +build: + verbosity: detailed + project: $(APPVEYOR_BUILD_FOLDER)\build\tp3.sln + parallel: true + +cache: +# - c:\tools\vcpkg\installed\ diff --git a/TP4/lumiere.c b/TP4/lumiere.c new file mode 100644 index 0000000..21560b9 --- /dev/null +++ b/TP4/lumiere.c @@ -0,0 +1,383 @@ +/* + * lumiere.c + * + * OpenGL light + */ + +#include +#include + +// for mac osx +#ifdef __APPLE__ +#include +#include +#include +#else +// only for windows +#ifdef _WIN32 +#include +#endif +// for windows and linux +#include +#include +#include +#endif + +int angle_x = 45, angle_y = -45; +float distance = 8.f; +float shininess = 25.f; + +int directional = 0; +int infinite_view = 0; + +// the room: it's a cube with inverted normals, ie pointing inwards +void glRoom(GLfloat size) +{ + GLfloat v = size / 2; + + glBegin(GL_QUADS); + glNormal3f(0, 0, 1); + glVertex3f(-v, -v, -v); + glVertex3f(v, -v, -v); + glVertex3f(v, v, -v); + glVertex3f(-v, v, -v); + + glNormal3f(0, 0, -1); + glVertex3f(v, -v, v); + glVertex3f(-v, -v, v); + glVertex3f(-v, v, v); + glVertex3f(v, v, v); + + glNormal3f(-1, 0, 0); + glVertex3f(v, -v, -v); + glVertex3f(v, -v, v); + glVertex3f(v, v, v); + glVertex3f(v, v, -v); + + glNormal3f(1, 0, 0); + glVertex3f(-v, -v, v); + glVertex3f(-v, -v, -v); + glVertex3f(-v, v, -v); + glVertex3f(-v, v, v); + + glNormal3f(0, -1, 0); + glVertex3f(-v, v, -v); + glVertex3f(v, v, -v); + glVertex3f(v, v, v); + glVertex3f(-v, v, v); + + glNormal3f(0, 1, 0); + glVertex3f(-v, -v, -v); + glVertex3f(-v, -v, v); + glVertex3f(v, -v, v); + glVertex3f(v, -v, -v); + glEnd(); +} + +// place the camera, make the scene turn around the scene origin +void place_camera() +{ + glTranslatef(0.f, 0.f, -distance); + glRotatef(angle_x, 1.f, 0.f, 0.f); + glRotatef(angle_y, 0.f, 1.f, 0.f); +} + +// place the light in x,y,z +void place_light(GLfloat x, GLfloat y, GLfloat z) +{ + //********************************** + // set the light components: ambient (0.2 grey), + // diffuse and specular (both white) + //********************************** + GLfloat light_ambient[] = {0.2, 0.2, 0.2, 1.0}; // the ambient component + // GLfloat light_diffuse[] = {1.0, 1.0, 1.0, 1.0}; // the diffuse component + // GLfloat light_specular[] = {1.0, 1.0, 1.0, 1.0}; // the specular component + + glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); + // glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); + // glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular); + + //********************************** + // set the light position (directional or positional) + //********************************** + GLfloat light_position[] = {x, y, z, directional}; // the light position + glLightfv(GL_LIGHT0, GL_POSITION, light_position); + + //********************************** + // draw a yellow point to visually represent the light + //********************************** + glDisable(GL_LIGHT0); + glDisable(GL_LIGHTING); + + glPushMatrix(); + glColor3f(1, 1, 0); + glTranslatef(x, y, z); + glutSolidSphere(0.1, 100, 100); // a yellow sphere + glPopMatrix(); + + glEnable(GL_LIGHT0); + glEnable(GL_LIGHTING); + + //********************************** + // turn the light and the lighting on + //********************************** + glEnable(GL_LIGHT0); // turn light on + glEnable(GL_LIGHTING); // turn lighting on +} + +// define a material in terms of its components +void define_material(GLfloat ar, GLfloat ag, GLfloat ab, // ambient + GLfloat dr, GLfloat dg, GLfloat db, // diffuse + GLfloat sr, GLfloat sg, GLfloat sb, // specular + GLfloat sh // shininess +) +{ + GLfloat mat_ambient[4]; + GLfloat mat_diffuse[4]; + GLfloat mat_specular[4]; + + //********************************** + // set the ambient property + //********************************** + mat_ambient[0] = ar; + mat_ambient[1] = ag; + mat_ambient[2] = ab; + mat_ambient[3] = 1.f; + glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient); + + //********************************** + // set the diffuse property + //********************************** + mat_diffuse[0] = dr; + mat_diffuse[1] = dg; + mat_diffuse[2] = db; + mat_diffuse[3] = 1.f; + glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse); + + //********************************** + // set the specular property + //********************************** + mat_specular[0] = sr; + mat_specular[1] = sg; + mat_specular[2] = sb; + mat_specular[3] = 1.f; + glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); + + //********************************** + // set the shininess property + //********************************** + glMaterialf(GL_FRONT, GL_SHININESS, sh); +} + +// draw the room +void place_background() +{ + glPushMatrix(); + glScalef(15.f, 15.f, 15.f); + glRoom(1.f); + glPopMatrix(); +} + +/* + * OpenGL Initialization + */ +void init() +{ + glClearColor(0.f, 0.f, 0.f, 0.f); + //********************************** + // activate the Gouraud shading instead of the flat one + //********************************** + glShadeModel(GL_FLAT); + + //********************************** + // enable face culling + //********************************** + glEnable(GL_CULL_FACE); + + //********************************** + // enable the depth test + //********************************** + glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH); + glEnable(GL_DEPTH_TEST); + + //********************************** + // set the ambient light with glLightModelfv to a 50% grey + //********************************** + GLfloat light_ambient[] = {0.5, 0.5, 0.5, 1.0}; + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, light_ambient); + + glEnable(GL_NORMALIZE); +} + +void display() +{ + glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, infinite_view); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + // place the camera + place_camera(); + + //********************************** + // place the light in the scene using place_light + //********************************** + place_light(7.4, 4, -7.4); + + //********************************** + // define the material for the room (instead of color) + //********************************** + // glColor3f(1.f, 1.f, 1.f); + define_material(1, 1, 1, + 0.5, 0.5, 0.5, + 0, 0, 0, + 0); + place_background(); + + // the 2 objects + // red shining sphere + glPushMatrix(); + glTranslatef(-2.f, 0.f, 0.f); + + //********************************** + // define the material for the sphere (instead of color) + //********************************** + // glColor3f(1.f, 0.f, 0.f); + define_material(0.2, 0, 0, + 0.8, 0, 0, + 0.8, 0.8, 0.8, + shininess); + glutSolidSphere(1.0, 240, 120); + + glPopMatrix(); + + // green cube + glPushMatrix(); + glTranslatef(2.f, 0.f, 0.f); + + //********************************** + // define the material for the cube (instead of color) + //********************************** + // glColor3f(0.f, 1.f, 0.f); + define_material(0, 0.8, 0, + 0, 0.5, 0, + 0, 0, 0, + 0); + glutSolidCube(2.0); + + glPopMatrix(); + + glutSwapBuffers(); +} + +/* + * @brief Callback for window size change + * @param[in] w new width of the window + * @param[in] h new height of the window + */ +void reshape(int w, int h) +{ + glViewport(0, 0, (GLsizei)w, (GLsizei)h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(60, (GLfloat)w / (GLfloat)h, 0.1, 50); +} + +/* + * Callback for special keys + */ +#define DELTA_ANGLE_X 5 +#define DELTA_ANGLE_Y 5 +#define DELTA_DISTANCE 0.3f +#define DISTANCE_MIN 0.0 +void special(int key, int x, int y) +{ + switch (key) + { + case GLUT_KEY_UP: + angle_x = (angle_x + DELTA_ANGLE_X) % 360; + break; + case GLUT_KEY_DOWN: + angle_x = (angle_x - DELTA_ANGLE_X) % 360; + break; + case GLUT_KEY_LEFT: + angle_y = (angle_y + DELTA_ANGLE_Y) % 360; + break; + case GLUT_KEY_RIGHT: + angle_y = (angle_y - DELTA_ANGLE_Y) % 360; + break; + case GLUT_KEY_PAGE_DOWN: + distance += DELTA_DISTANCE; + break; + case GLUT_KEY_PAGE_UP: + distance -= (distance > DISTANCE_MIN) ? DELTA_DISTANCE : 0.f; + break; + default: + break; + } + glutPostRedisplay(); +} + +void keyboard(unsigned char key, int x, int y) +{ + switch (key) + { + //********************************** + // directional light, use global variable directional + //********************************** + case 'd': + directional = (directional + 1) % 2; + break; + + //********************************** + // infinite_view + //********************************** + case 'f': + infinite_view = (infinite_view + 1) % 2; + break; + //********************************** + // Shininess: 's' to decrement, 'S' to increment + //********************************** + case 'S': + shininess += 1; + break; + + case 's': + shininess -= 1; + break; + + case 'q': + case 27: // [ESC] + exit(0); + break; + default: + break; + } + glutPostRedisplay(); +} + +int main(int argc, char **argv) +{ + + glutInit(&argc, argv); + // enable the double buffer and the depth buffer + glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); + + // main window + glutInitWindowSize(640, 480); + glutInitWindowPosition(50, 50); + glutCreateWindow("Testing OpenGL light"); + + // callbacks + glutDisplayFunc(display); + glutReshapeFunc(reshape); + + glutKeyboardFunc(keyboard); + glutSpecialFunc(special); + + init(); + + glutMainLoop(); + exit(0); +} diff --git a/TP5/.editorconfig b/TP5/.editorconfig new file mode 100644 index 0000000..c1322dc --- /dev/null +++ b/TP5/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = false \ No newline at end of file diff --git a/TP5/.gitignore b/TP5/.gitignore new file mode 100644 index 0000000..7771f79 --- /dev/null +++ b/TP5/.gitignore @@ -0,0 +1,2 @@ +build +*DS_Store diff --git a/TP5/.vscode/settings.json b/TP5/.vscode/settings.json new file mode 100644 index 0000000..ed868d6 --- /dev/null +++ b/TP5/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "files.associations": { + "*.html": "html", + "*.toml": "toml", + "iosfwd": "cpp" + } +} \ No newline at end of file diff --git a/TP5/BUILD.md b/TP5/BUILD.md new file mode 100755 index 0000000..85af13d --- /dev/null +++ b/TP5/BUILD.md @@ -0,0 +1,185 @@ +# OpenGL OBJ Visualizer + +## Building instructions + +Required tools: +* C/C++ 11 compiler (gcc or visual studio or clang) +* cmake + + +### Dependencies + +The project depends on: + +- OpenGL +- freeglut (included for ease in windows) + + +## Windows + +### Prerequisites + +* download and install the latest version of CMake + + * download here: https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-win64-x64.msi + + * !!! When installing make sure that the checkbox "ne pas ajouter cmake au PATH" is NOT checked + + +* if you don't have it already, download and install MS Visual Studio Community Edition (free for students): https://visualstudio.microsoft.com/downloads/ + + * install instructions here: https://docs.microsoft.com/en-us/cpp/build/vscpp-step-0-installation?view=vs-2019 + + * !!! install the "Desktop development with C++" + + * If you have VS already installed, you can go in **Tools** --> **Get Tools and Features...** to install "Desktop development with C++" if it is missing. + + +### Create the Visual Studio Solution. + +This step enables you to create the project file to load inside VS: + +* unzip the code inside a folder. *Avoid to place the code in folders with spaces and accented characters*. + +* open a Terminal and go to the directory containing the code. + +* execute: + + * `md build` + + * `cd build` + + * `cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE:STRING=Release ..` + + * `dir` + + > if you had a different version of VS installed (not the latest) you may need to adapt the string `Visual Studio 16 2019` to your version: e.g. Visual Studio 15 2017, Visual Studio 14 2015, Visual Studio 12 2013 + +* if everything went well you should find a file named `tp4.sln` inside the directory. + + +### Compile, build, execute + +* open `tp4.sln` inside VS either by double clicking on it or opening from inside VS + +* build the solution (**Build Solution** from the **Build menu**) + +* from the tp directory copy `freeglut\bin\x64\freeglut.dll` in `build\Release` + +* execute the code: + + * From VS: + * Select the project you want to run (e.g. `visualizer`), right click on it and select **Set as Startup Project** + + * From **Debug > visualizer Properties... > Configuration Porpoerties > Command Arguments**, insert the full path of the obj file that you want to test + + (In French: **Déboguer > Propriétés de visualizer > Débogage > Arguments de la commande**) + + * On the menu bar, choose **Debug** --> **Start without debugging**.) + + (see https://docs.microsoft.com/en-us/cpp/build/vscpp-step-2-build?view=vs-2019 for how to build, execute, etc) + + * From the command line: + + * Open a Terminal and go to `build/Release` + + * launch `visualizer ../../data/models/cube.obj` (the argument being the model to load) + + +### Editing the code + +Edit the code according to the assignments that are given, rebuild the solution and execute. + +> !!! You need to run the cmake line only **once** + +> !!! You need to copy the dll file only **once**. + +--- + +## Linux + +### Prerequisites + +In order to develop with OpenGL some system packages are required (unless you are using the N7 machines): + +``` +sudo apt-get install libglu1-mesa-dev freeglut3-dev build-essential mesa-common-dev libxi-dev libxmu-dev automake +``` + +To build this code we use the CMake build system. You can install CMake from the system package manager but you need a recent version >= 3.10. Check the version that is provided by your linux distribution and if it is suitable usually you need to + + ``` + sudo apt-get install cmake + ``` + + otherwise you can install the binaries from here: https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Linux-x86_64.sh + + To install: + ``` + wget https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Linux-x86_64.sh + chmod +x cmake-3.17.1-Linux-x86_64.sh + sudo cmake-3.17.1-Linux-x86_64.sh --prefix=/usr/local/ --skip-license + ``` + +### Build + +To compile and build the code you do + + ``` + mkdir build && cd build + cmake .. + make visualizer + ``` + +Also, + +``` +make all +``` +builds everything, and + +``` +make clean +``` +cleans everything. + +Execute the code: + +``` +./visualizer ../data/models/cube.obj +``` + +### Editing the code + +Edit the code as required and then + +``` +make +``` + +> !!! the cmake line has to be run only **once** + +--- + +## macOS + +### Prerequisites + +In order to develop with OpenGL check if + +``` +ls /System/Library/Frameworks/ +``` +contains OpenGL and GLUT frameworks. +If not you need to install XCode from the `Mac App Store`, see here for more details https://developer.apple.com/support/xcode/ + +Install the latest version of CMake by downloading https://github.com/Kitware/CMake/releases/download/v3.17.1/cmake-3.17.1-Darwin-x86_64.dmg + +### Build + + Same as Linux. + +### Editing the code + + Same as Linux. + diff --git a/TP5/CMakeLists.txt b/TP5/CMakeLists.txt new file mode 100755 index 0000000..0406bfc --- /dev/null +++ b/TP5/CMakeLists.txt @@ -0,0 +1,82 @@ +cmake_minimum_required(VERSION 3.1) + +project( tp4 LANGUAGES CXX VERSION 2021.0.0) + +option(BUILD_TESTS "Build tests" OFF) + +######################################################### +# +# EXTERNAL LIBRARIES +# +######################################################### + +######################################################### +# FIND OPENGL +######################################################### +set(OpenGL_GL_PREFERENCE "GLVND") +find_package(OpenGL REQUIRED) +message(STATUS "OPENGL_gl_LIBRARY: ${OPENGL_gl_LIBRARY}") + +######################################################### +# FIND GLUT +######################################################### +if(MSVC) + set(GLUT_ROOT_PATH "${CMAKE_SOURCE_DIR}/freeglut") + message(STATUS "GLUT_ROOT_PATH: ${GLUT_ROOT_PATH}") +endif() +find_package(GLUT REQUIRED) +message(STATUS "GLUT_FOUND: ${GLUT_FOUND}") +message(STATUS "GLUT_INCLUDE_DIR: ${GLUT_INCLUDE_DIR}") +message(STATUS "GLUT_LIBRARIES: ${GLUT_LIBRARIES}") + +if(CMAKE_SYSTEM_NAME STREQUAL Linux) + ######################################################### + # FIND Threads, not used but necessary for linkning on linux + # funny story, even if it is not used it is needed by other + # dependencies which do not propagate their dependencies (?) + ######################################################### + find_package(Threads REQUIRED) + # this force all libs to be include even if not directly used + set(CMAKE_EXE_LINKER_FLAGS "-Wl,--no-as-needed") +endif() + + +######################################################### +# SET COMPILATION FLAGS FOR C++11 +######################################################### +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable( visualizer main.cpp ObjModel.cpp core.cpp) +#target_include_directories(visualizer PUBLIC ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIR}) +#target_link_libraries( visualizer ${OPENGL_opengl_LIBRARY} ${OPENGL_glu_LIBRARY} ${GLUT_LIBRARIES} ) +target_link_libraries( visualizer OpenGL::GL OpenGL::GLU GLUT::GLUT ) +if(CMAKE_SYSTEM_NAME STREQUAL Linux) + target_link_libraries( visualizer ${CMAKE_THREAD_LIBS_INIT} ) +endif() + +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + target_compile_options(visualizer PRIVATE -Wno-deprecated-declarations) +endif() +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + target_compile_options(visualizer PRIVATE -Wall -Wextra -pedantic -Wno-comment) +endif() +if(MSVC) + target_compile_definitions(visualizer PUBLIC -DNOMINMAX) +endif() + +if(BUILD_TESTS) + add_executable( testEdge testEdge.cpp core.cpp) + target_include_directories(testEdge PUBLIC ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIR}) + target_link_libraries( testEdge ${OPENGL_opengl_LIBRARY} ${OPENGL_glu_LIBRARY} ${GLUT_LIBRARIES} ) + + add_executable( testEdgeList testEdgeList.cpp core.cpp) + target_include_directories(testEdgeList PUBLIC ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIR}) + target_link_libraries( testEdgeList ${OPENGL_opengl_LIBRARY} ${OPENGL_glu_LIBRARY} ${GLUT_LIBRARIES} ) + if(CMAKE_SYSTEM_NAME STREQUAL Linux) + target_link_libraries( testEdge ${CMAKE_THREAD_LIBS_INIT} ) + target_link_libraries( testEdgeList ${CMAKE_THREAD_LIBS_INIT} ) + endif() + enable_testing() + add_test( testEdgeList bin/testEdgeList 1000 ) +endif() diff --git a/TP5/LICENSE b/TP5/LICENSE new file mode 100755 index 0000000..be2cc4d --- /dev/null +++ b/TP5/LICENSE @@ -0,0 +1,362 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. "Contributor" + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. "Contributor Version" + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. + +1.6. "Executable Form" + + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. + +1.8. "License" + + means this document. + +1.9. "Licensable" + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. + +1.10. "Modifications" + + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" + + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. + +Exhibit A - Source Code Form License Notice + + 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/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. diff --git a/TP5/Makefile b/TP5/Makefile new file mode 100755 index 0000000..4aa98c1 --- /dev/null +++ b/TP5/Makefile @@ -0,0 +1,19 @@ +CC = g++ +CXXFLAGS = -Wall -O3 -std=c++11 -Wno-comment +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Darwin) +LIBS := -framework OpenGL -lglut -lm +CXXFLAGS += -Wno-deprecated +else +LIBS = -Wl,--no-as-needed -lpthread -lGL -lglut -lm -lGLU -pthread +endif + + +all: visualizer + +# solutions +visualizer: main.o ObjModel.o core.o + $(CC) $(CXXFLAGS) -o $@ $^ $(LIBS) + +clean: + rm -f *.o visualizer diff --git a/TP5/ObjModel.cpp b/TP5/ObjModel.cpp new file mode 100755 index 0000000..1389b23 --- /dev/null +++ b/TP5/ObjModel.cpp @@ -0,0 +1,563 @@ +/** + * @file ObjModel.cpp + * @author Simone Gasparini + * @version 1.0 + * + * @section LICENSE + * + * 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/. + * + * @section DESCRIPTION + * + * Simple Class to load and draw 3D objects from OBJ files + * Using triangles and normals as static object. No texture mapping. + * OBJ files must be triangulated!!! + * + */ + +#include "ObjModel.hpp" + +#include +#include +#include +#include +#include +//#include + +#include +#include +#include +#include + +using namespace std; + +/** + * Load the OBJ data from file + * @param filename The name of the OBJ file + * @return 0 if everything went well + */ +int ObjModel::load(char *filename) +{ + + string line; + ifstream objFile(filename); + // ifstream objFile("../data/models/cube.obj"); + // ifstream objFile("../data/models/cow-nonormals.obj"); + // ifstream objFile("../data/models/triang.obj"); + + // If obj file is open, continue + if (objFile.is_open()) + { + // Start reading file data + while (!objFile.eof()) + { + // Get a line from file + getline(objFile, line); + + // If the first character is a simple 'v'... + // PRINTVAR(line); + if ((line.c_str()[0] == 'v') && (line.c_str()[1] == ' ')) // to drop all the vn and vn lines + { + // PRINTVAR(line); + // Read 3 floats from the line: X Y Z and store them in the corresponding place in _vertices + point3d p; + sscanf(line.c_str(), "v %f %f %f ", &p.x, &p.y, &p.z); + + //************************************************** + // add the new point to the list of the vertices + // and its normal to the list of normals: for the time + // being it is a [0, 0 ,0] normal. + //************************************************** + _vertices.push_back(p); + _normals.push_back(vec3d(0, 0, 0)); + + // update the bounding box, if it is the first vertex simply + // set the bb to it + if (_vertices.size() == 1) + { + _bb.set(p); + } + else + { + // otherwise add the point + _bb.add(p); + } + } + // If the first character is a 'f'... + if (line.c_str()[0] == 'f') + { + + face t; + const auto ok = parseFaceString(line, t); + if (!ok) + throw std::runtime_error("Error while reading line: " + line); + + //************************************************** + // correct the indices: OBJ starts counting from 1, in C the arrays starts at 0... + //************************************************** + t.v1--; + t.v2--; + t.v3--; + + //************************************************** + // add it to the mesh + //************************************************** + _mesh.push_back(t); + + //********************************************************************* + // Compute the normal of the face + //********************************************************************* + vec3d normal; + computeNormal(_vertices[t.v1], _vertices[t.v2], _vertices[t.v3], normal); + + //********************************************************************* + // Sum the normal of the face to each vertex normal + //********************************************************************* + _normals[t.v1] += normal * angleAtVertex(_vertices[t.v1], _vertices[t.v2], _vertices[t.v3]); + _normals[t.v2] += normal * angleAtVertex(_vertices[t.v2], _vertices[t.v1], _vertices[t.v3]); + _normals[t.v3] += normal * angleAtVertex(_vertices[t.v3], _vertices[t.v1], _vertices[t.v2]); + } + } + + cerr << "Found :\n\tNumber of triangles (_indices) " << _mesh.size() << "\n\tNumber of Vertices: " << _vertices.size() << "\n\tNumber of Normals: " << _normals.size() << endl; + // PRINTVAR(_mesh); + // PRINTVAR(_vertices); + // PRINTVAR(_normals); + + //********************************************************************* + // normalize the normals of each vertex + //********************************************************************* + for (vec3d n : _normals) + { + n.normalize(); + } + + // PRINTVAR(_normals); + + // Close OBJ file + objFile.close(); + } + else + { + cout << "Unable to open file"; + } + + cout << "Object loaded with " << _vertices.size() << " vertices and " << _mesh.size() << " faces" << endl; + cout << "Bounding box : pmax=" << _bb.pmax << " pmin=" << _bb.pmin << endl; + return 0; +} + +/** + * Draw the wireframe of the model + * + * @param vertices The list of vertices + * @param mesh The mesh as a list of faces, each face is a tripleIndex of vertex indices + * @param params The rendering parameters + */ +void ObjModel::drawWireframe(const std::vector &vertices, const std::vector &mesh, const RenderingParameters ¶ms) const +{ + //************************************************** + // we first need to disable the lighting in order to + // draw colored segments + //************************************************** + glDisable(GL_LIGHTING); + + // if we are displaying the object with colored faces + if (params.solid) + { + // use black ticker lines + glColor3f(0, 0, 0); + glLineWidth(2); + } + else + { + // otherwise use white thinner lines for wireframe only + glColor3f(.8, .8, .8); + glLineWidth(.21); + } + + //************************************************** + // for each face of the mesh... + //************************************************** + for (auto &face : mesh) + { + //************************************************** + // draw the contour of the face as a GL_LINE_LOOP + //************************************************** + glBegin(GL_LINE_LOOP); + glVertex3fv((float *)&vertices[face.v1]); + glVertex3fv((float *)&vertices[face.v2]); + glVertex3fv((float *)&vertices[face.v3]); + glEnd(); + } + + //************************************************** + // re-enable the lighting + //************************************************** + glEnable(GL_LIGHTING); +} + +/** + * Calculate the normal of a triangular face defined by three points + * + * @param[in] v1 the first vertex + * @param[in] v2 the second vertex + * @param[in] v3 the third vertex + * @param[out] norm the normal + */ +void ObjModel::computeNormal(const point3d &v1, const point3d &v2, const point3d &v3, vec3d &norm) const +{ + //************************************************** + // compute the cross product between two edges of the triangular face + //************************************************** + norm = (v2 - v1).cross(v3 - v1); + + //************************************************** + // remember to normalize the result + //************************************************** + norm.normalize(); +} + +/** + * Draw the faces using the computed normal of each face + * + * @param vertices The list of vertices + * @param mesh The list of face, each face containing the indices of the vertices + * @param params The rendering parameters + */ +void ObjModel::drawFlatFaces(const std::vector &vertices, const std::vector &mesh, const RenderingParameters ¶ms) const +{ + // shading model to use + if (params.smooth) + { + glShadeModel(GL_SMOOTH); + } + else + { + glShadeModel(GL_FLAT); + } + + //************************************************** + // for each face + //************************************************** + for (auto &face : mesh) + { + //************************************************** + // Compute the normal to the face and then draw the + // faces as GL_TRIANGLES assigning the proper normal + //************************************************** + vec3d normal; + computeNormal(vertices[face.v1], vertices[face.v2], vertices[face.v3], normal); + glNormal3fv((float *)&normal); + + glBegin(GL_TRIANGLES); + glVertex3fv((float *)&vertices[face.v1]); + glVertex3fv((float *)&vertices[face.v2]); + glVertex3fv((float *)&vertices[face.v3]); + glEnd(); + } +} + +/** + * Draw the model using the vertex indices + * + * @param vertices The vertices + * @param indices The list of the faces, each face containing the 3 indices of the vertices + * @param vertexNormals The list of normals associated to each vertex + * @param params The rendering parameters + */ +void ObjModel::drawSmoothFaces(const std::vector &vertices, + const std::vector &mesh, + std::vector &vertexNormals, + const RenderingParameters ¶ms) const +{ + if (params.smooth) + { + glShadeModel(GL_SMOOTH); + } + else + { + glShadeModel(GL_FLAT); + } + //**************************************** + // Enable vertex arrays + //**************************************** + glEnableClientState(GL_VERTEX_ARRAY); + + //**************************************** + // Enable normal arrays + //**************************************** + glEnableClientState(GL_NORMAL_ARRAY); + + //**************************************** + // Normal pointer to normal array + //**************************************** + glNormalPointer(GL_FLOAT, 0, &vertexNormals[0]); + + //**************************************** + // Vertex pointer to Vertex array + //**************************************** + glVertexPointer(3, GL_FLOAT, 0, &vertices[0]); + + //**************************************** + // Draw the faces + //**************************************** + glDrawElements(GL_TRIANGLES, 3 * mesh.size(), GL_UNSIGNED_INT, &mesh[0]); + + //**************************************** + // Disable vertex arrays + //**************************************** + glDisableClientState(GL_VERTEX_ARRAY); + + //**************************************** + // Disable normal arrays + //**************************************** + glDisableClientState(GL_NORMAL_ARRAY); +} + +/** + * Compute the subdivision of the input mesh by applying one step of the Loop algorithm + * + * @param[in] origVert The list of the input vertices + * @param[in] origMesh The input mesh (the vertex indices for each face/triangle) + * @param[out] destVert The list of the new vertices for the subdivided mesh + * @param[out] destMesh The new subdivided mesh (the vertex indices for each face/triangle) + * @param[out] destNorm The new list of normals for each new vertex of the subdivided mesh + */ +void ObjModel::loopSubdivision(const std::vector &origVert, //!< the original vertices + const std::vector &origMesh, //!< the original mesh + std::vector &destVert, //!< the new vertices + std::vector &destMesh, //!< the new mesh + std::vector &destNorm) const //!< the new normals +{ + // copy the original vertices in destVert + destVert = origVert; + + // start fresh with the new mesh + destMesh.clear(); + + // PRINTVAR(destVert); + // PRINTVAR(origVert); + + // create a list of the new vertices creates with the reference to the edge + EdgeList newVertices; + + //********************************************************************* + // for each face + //********************************************************************* + for (auto &f : origMesh) + { + //********************************************************************* + // get the indices of the triangle vertices + //********************************************************************* + uint v1 = f.v1; + uint v2 = f.v2; + uint v3 = f.v3; + + //********************************************************************* + // for each edge get the index of the vertex of the midpoint using getNewVertex + //********************************************************************* + const uint a = getNewVertex(edge{v1, v2}, destVert, origMesh, newVertices); + const uint b = getNewVertex(edge{v2, v3}, destVert, origMesh, newVertices); + const uint c = getNewVertex(edge{v3, v1}, destVert, origMesh, newVertices); + + //********************************************************************* + // create the four new triangles + // BE CAREFUL WITH THE VERTEX ORDER!! + // v2 + // /\ + // / \ + // / \ + // a ---- b + // / \ /\ + // / \ / \ + // / \ / \ + // v1 ---- c ---- v3 + // + // the original triangle was v1-v2-v3, use the same clock-wise order for the other + // hence v1-a-c, a-b-c and so on + //********************************************************************* + destMesh.push_back(face{v1, a, c}); + destMesh.push_back(face{a, b, c}); + destMesh.push_back(face{c, b, v3}); + destMesh.push_back(face{a, v2, b}); + } + + //********************************************************************* + // Update each "old" vertex using the Loop coefficients. A smart way to do + // so is to think in terms of faces than the single vertex: for each face + // we update each of the 3 vertices using the Loop formula wrt the other 2 and + // sum it to a temporary copy tmp of the vertices (which is initialized to + // [0 0 0] at the beginning). We also keep a record of the occurrence of each vertex. + // At then end, to get the final vertices we just need to divide each vertex + // in tmp by its occurrence + //********************************************************************* + + // A list containing the occurrence of each vertex + vector occurrences(origVert.size(), 0.0f); + + // A list of the same size as origVert with all the elements initialized to [0 0 0] + vector tmp(origVert.size()); + + //********************************************************************* + // for each face + //********************************************************************* + for (auto &face : origMesh) + { + //********************************************************************* + // consider each of the 3 vertices: + // 1) increment its occurrence + // 2) apply Loop update with the other 2 vertices of the face + // BE CAREFUL WITH THE COEFFICIENT OF THE OTHER 2 VERTICES!... consider + // how many times each vertex is summed in the general case... + //********************************************************************* + + // 1) increment occurrences + occurrences[face.v1] += 2; + occurrences[face.v2] += 2; + occurrences[face.v3] += 2; + + // 2) apply Loop update + tmp[face.v1] += (destVert[face.v2] + destVert[face.v3]); + tmp[face.v2] += (destVert[face.v1] + destVert[face.v3]); + tmp[face.v3] += (destVert[face.v1] + destVert[face.v2]); + } + + //********************************************************************* + // To obtain the new vertices, divide each vertex by its occurrence value + //********************************************************************* + for (int i = 0; i < tmp.size(); i++) + { + assert(occurrences[i] != 0); + destVert[i] = 5.0f / 8.0f * destVert[i] + 3.0f / 8.0f / occurrences[i] * tmp[i]; + } + // PRINTVAR(destVert); + + // redo the normals, reset and create a list of normals of the same size as + // the vertices, each normal set to [0 0 0] + destNorm.clear(); + destNorm = vector(destVert.size()); + + //********************************************************************* + // Recompute the normals for each face + //********************************************************************* + for (auto &f : destMesh) + { + //********************************************************************* + // Calculate the normal of the triangles, it will be the same for each vertex + //********************************************************************* + vec3d n; + computeNormal(destVert[f.v1], destVert[f.v2], destVert[f.v3], n); + + //********************************************************************* + // Sum the normal of the face to each vertex normal using the angleAtVertex as weight + //********************************************************************* + destNorm[f.v1] += n * angleAtVertex(destVert[f.v1], destVert[f.v2], destVert[f.v3]); + destNorm[f.v2] += n * angleAtVertex(destVert[f.v2], destVert[f.v1], destVert[f.v3]); + destNorm[f.v3] += n * angleAtVertex(destVert[f.v3], destVert[f.v2], destVert[f.v1]); + } + //********************************************************************* + // normalize the normals of each vertex + //********************************************************************* + for (vec3d n : destNorm) + { + n.normalize(); + } +} + +/** + * For a given edge it returns the index of the new vertex created on its middle point. + * If such vertex already exists it just returns the its index; if it does not exist + * it creates it in vertList along it's normal and return the index + * + * @param[in] e the edge + * @param[in,out] vertList the list of vertices + * @param[in] mesh the list of triangles + * @param[in,out] newVertList The list of the new vertices added so far + * @return the index of the new vertex or the one that has been already created for that edge + * @see EdgeList + */ +idxtype ObjModel::getNewVertex(const edge &e, + std::vector &vertList, + const std::vector &mesh, + EdgeList &newVertList) const +{ + // PRINTVAR(e); + // PRINTVAR(newVertList); + + //********************************************************************* + // if the egde is NOT contained in the new vertex list (see EdgeList.contains() method) + //********************************************************************* + if (!newVertList.contains(e)) + { + //********************************************************************* + // generate new index (vertex.size) + //********************************************************************* + int newIndex = vertList.size(); + + //********************************************************************* + // add the edge and index to the newVertList + //********************************************************************* + newVertList.add(e, newIndex); + + // generate new vertex + point3d nvert; //!< this will contain the new vertex + idxtype oppV1; //!< the index of the first "opposite" vertex + idxtype oppV2; //!< the index of the second "opposite" vertex (if it exists) + + //********************************************************************* + // check if it is a boundary edge, ie check if there is another triangle + // sharing this edge and if so get the index of its "opposite" vertex + //********************************************************************* + if (!isBoundaryEdge(e, mesh, oppV1, oppV2)) + { + // if it is not a boundary edge create the new vertex + + //********************************************************************* + // the new vertex is the linear combination of the two extrema of + // the edge V1 and V2 and the two opposite vertices oppV1 and oppV2 + // Using the loop coefficient the new vertex is + // nvert = 3/8 (V1+V2) + 1/8(oppV1 + oppV2) + // + // REMEMBER THAT IN THE CODE OPPV1 AND OPPV2 ARE INDICES, NOT VERTICES!!! + //********************************************************************* + nvert = 3.0f / 8.0f * (vertList[e.first] + vertList[e.second]) + 1.0f / 8.0f * (vertList[oppV1] + vertList[oppV2]); + } + else + { + //********************************************************************* + // otherwise it is a boundary edge then the vertex is the linear combination of the + // two extrema + //********************************************************************* + nvert = 0.5f * vertList[e.first] + 0.5f * vertList[e.second]; + } + //********************************************************************* + // append the new vertex to the list of vertices + //********************************************************************* + vertList.push_back(nvert); + + //********************************************************************* + // return the index of the new vertex + //********************************************************************* + return newIndex; + } + else + // else we don't need to do anything, just return the associated index of the + // already existing vertex + { + //********************************************************************* + // get and return the index of the vertex + //********************************************************************* + return newVertList.getIndex(e); + } + + // this is just to avoid compilation errors at the beginning + // execution should normally never reach here + // the return instructions go inside each branch of the if - else above + std::cerr << "WARNING: the subdivision may not be implemented correctly" << std::endl; + return 0; +} + +#include "ObjModel.cxx" diff --git a/TP5/ObjModel.cxx b/TP5/ObjModel.cxx new file mode 100755 index 0000000..8a184d5 --- /dev/null +++ b/TP5/ObjModel.cxx @@ -0,0 +1,413 @@ +/** + * @file ObjModel.cxx + * @author Simone Gasparini + * @version 1.0 + * + * @section LICENSE + * + * 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/. + * + * @section DESCRIPTION + * + * Simple Class to load and draw 3D objects from OBJ files + * Using triangles and normals as static object. No texture mapping. + * OBJ files must be triangulated!!! + * + */ + +#include + +/** + * Computes the angle at vertex baseV formed by the edges connecting it with the + * vertices v1 and v2 respectively, ie the baseV-v1 and baseV-v2 edges + * + * @brief Computes the angle at vertex + * @param baseV the vertex at which to compute the angle + * @param v1 the other vertex of the first edge baseV-v1 + * @param v2 the other vertex of the second edge baseV-v2 + * @return the angle in radiants + */ +float ObjModel::angleAtVertex( const point3d& baseV, const point3d& v1, const point3d& v2 ) const +{ + vec3d e1 = baseV - v1; + vec3d e2 = baseV - v2; + //safe acos... + if ( fabs( (e1).dot( e2 ) / (e1.norm( ) * e2.norm( )) ) >= 1.0f ) + { + cerr << "warning: using safe acos" << endl; + return (acos( 1.0f )); + } + else + { + return ( acos( (e1).dot( e2 ) / (e1.norm( ) * e2.norm( )) )); + } +} + + +/** +* Render the model according to the provided parameters +* @param params The rendering parameters +*/ +void ObjModel::render( const RenderingParameters ¶ms ) +{ + // if we need to draw the original model + if ( !params.subdivision ) + { + // draw it + draw( _vertices, _mesh, _normals, params ); + // draw the normals + if ( params.normals ) + { + drawNormals( _vertices, _normals ); + } + } + else + { + PRINTVAR(params.subdivLevel); + PRINTVAR(_currentSubdivLevel); + // before drawing check the current level of subdivision and the required one + if ( ( _currentSubdivLevel == 0 ) || ( _currentSubdivLevel != params.subdivLevel ) ) + { + // if they are different apply the missing steps: either restart from the beginning + // if the required level is less than the current one or apply the missing + // steps starting from the current one + vector tmpVert; //!< a temporary list of vertices used in the iterations + vector tmpMesh; //!< a temporary mesh used in the iterations + + if(( _currentSubdivLevel == 0 ) || ( _currentSubdivLevel > params.subdivLevel ) ) + { + // start from the beginning + _currentSubdivLevel = 0; + tmpVert = _vertices; + tmpMesh = _mesh; + } + else + { + // start from the current level + tmpVert = _subVert; + tmpMesh = _subMesh; + } + + // apply the proper subdivision iterations + for( ; _currentSubdivLevel < params.subdivLevel; ++_currentSubdivLevel) + { + cerr << "[Loop subdivision] iteration " << _currentSubdivLevel << endl; + loopSubdivision( tmpVert, tmpMesh, _subVert, _subMesh, _subNorm ); + // swap unless it's the last iteration + if( _currentSubdivLevel < ( params.subdivLevel - 1) ) + { + tmpVert = _subVert; + tmpMesh = _subMesh; + } + } + } + + draw( _subVert, _subMesh, _subNorm, params ); + if ( params.normals ) + { + drawNormals( _subVert, _subNorm ); + } + } +} + +/** + * Draw the model + * + * @param vertices list of vertices + * @param indices list of faces + * @param vertexNormals list of normals + * @param params Rendering parameters + */ +void ObjModel::draw( const std::vector &vertices, const std::vector &indices, std::vector &vertexNormals, const RenderingParameters ¶ms ) const +{ + if ( params.solid ) + { + drawSolid( vertices, indices, vertexNormals, params ); + } + if ( params.wireframe ) + { + drawWireframe( vertices, indices, params ); + } + +} + +void ObjModel::drawSolid( const vector &vertices, const vector &indices, vector &vertexNormals, const RenderingParameters ¶ms ) const +{ + if ( params.useIndexRendering ) + { + drawSmoothFaces( vertices, indices, vertexNormals, params ); + } + else + { + drawFlatFaces( vertices, indices, params ); + } +} + +/** + * Draw the normals at each vertex + * @param vertices The list of vertices + * @param vertexNormals The list of associated normals + */ +void ObjModel::drawNormals( const std::vector &vertices, std::vector &vertexNormals ) const +{ + glDisable( GL_LIGHTING ); + + glColor3f( 0.8, 0, 0 ); + glLineWidth( 2 ); + + for(const auto &v : vertices) + { + glBegin( GL_LINES ); + + vec3d newP = v + 0.1 * v; + glVertex3fv( (float*) &v ); + + glVertex3f( newP.x, newP.y, newP.z ); + + glEnd( ); + } + glEnable( GL_LIGHTING ); +} + +/** + * It scales the model to unitary size by translating it to the origin and + * scaling it to fit in a unit cube around the origin. + * + * @return the scale factor used to transform the model + */ +float ObjModel::unitizeModel( ) +{ + if ( !_vertices.empty( ) && !_mesh.empty( ) ) + { + //**************************************** + // calculate model width, height, and + // depth using the bounding box + //**************************************** + const float w = fabs( _bb.pmax.x - _bb.pmin.x ); + const float h = fabs( _bb.pmax.y - _bb.pmin.y ); + const float d = fabs( _bb.pmax.z - _bb.pmin.z ); + + cout << "size: w: " << w << " h " << h << " d " << d << endl; + //**************************************** + // calculate center of the bounding box of the model + //**************************************** + const point3d c = (_bb.pmax + _bb.pmin) * 0.5; + + //**************************************** + // calculate the unitizing scale factor as the + // maximum of the 3 dimensions + //**************************************** + const float scale = 2.0 / std::max( std::max( w, h ), d ); + + cout << "scale: " << scale << " cx " << c.x << " cy " << c.y << " cz " << c.z << endl; + + // translate each vertex wrt to the center and then apply the scaling to the coordinate + for(auto& v : _vertices) + { + //**************************************** + // translate the vertex + //**************************************** + v.translate( -c.x, -c.y, -c.z ); + + //**************************************** + // apply the scaling + //**************************************** + v.scale( scale ); + + } + + + //**************************************** + // update the bounding box, ie translate and scale the 6 coordinates + //**************************************** + _bb.pmax = (_bb.pmax - c) * scale; + _bb.pmin = (_bb.pmin - c) * scale; + + + cout << "New bounding box : pmax=" << _bb.pmax << " pmin=" << _bb.pmin << endl; + + return scale; + + } + + return 0; +} + +void ObjModel::release( ) { } + +/** + * It parses a line of the OBJ file containing a face and it return the result. + * NB: it only recover the indices, it discard normal and texture indices + * + * @param toParse the string to parse in the OBJ format for a face (f v/vt/vn v/vt/vn v/vt/vn) and its variants + * @param out the 3 indices for the face + * @return true if the parse was successful + */ +bool ObjModel::parseFaceString( const string &toParse, face &out ) const +{ +// PRINTVAR( toParse ); + if ( toParse.c_str( )[0] == 'f' ) + { + idxtype a; + // now check the different formats: %d, %d//%d, %d/%d, %d/%d/%d + if ( strstr( toParse.c_str( ), "//" ) ) + { + // v//n + return ( sscanf( toParse.c_str( ), "f %u//%u %u//%u %u//%u", &(out.v1), &a, &(out.v2), &a, &(out.v3), &a ) == 6); + } + else if ( sscanf( toParse.c_str( ), "f %u/%u/%u", &a, &a, &a ) == 3 ) + { + // v/t/n + return ( sscanf( toParse.c_str( ), "f %u/%u/%u %u/%u/%u %u/%u/%u", &(out.v1), &a, &a, &(out.v2), &a, &a, &(out.v3), &a, &a ) == 9); + } + else if ( sscanf( toParse.c_str( ), "f %u/%u", &a, &a ) == 2 ) + { + // v/t . + return ( sscanf( toParse.c_str( ), "f %u/%u %u/%u %u/%u", &(out.v1), &a, &(out.v2), &a, &(out.v3), &a ) == 6); + } + else + { + // v +// sscanf( toParse.c_str( ), "f %u %u %u", &(out.v1), &(out.v2), &(out.v3) ); + +// PRINTVAR( out ); + return ( sscanf( toParse.c_str( ), "f %u %u %u", &(out.v1), &(out.v2), &(out.v3) ) == 3); + } + } + else + { + return false; + } +} + + +//***************************************************************************** +//* DEPRECATED FUNCTIONS +//***************************************************************************** + +// to be deprecated + +void ObjModel::flatDraw( ) const +{ + glShadeModel( GL_SMOOTH ); + + // for each triangle draw the vertices and the normals + for(const auto &face : _mesh) + { + glBegin( GL_TRIANGLES ); + //compute the normal of the triangle + vec3d n; + computeNormal( _vertices[face.v1], _vertices[(int) face.v2], _vertices[(int) face.v3], n ); + glNormal3fv( (float*) &n ); + + glVertex3fv( (float*) &_vertices[face.v1] ); + + glVertex3fv( (float*) &_vertices[face.v2] ); + + glVertex3fv( (float*) &_vertices[face.v3] ); + + glEnd( ); + } + +} + +// to be deprecated + +void ObjModel::drawWireframe( ) const +{ + + drawWireframe( _vertices, _mesh, RenderingParameters( ) ); + +} + +// to be deprecated + +void ObjModel::indexDraw( ) const +{ + glShadeModel( GL_SMOOTH ); + //**************************************** + // Enable vertex arrays + //**************************************** + glEnableClientState( GL_VERTEX_ARRAY ); + + //**************************************** + // Enable normal arrays + //**************************************** + glEnableClientState( GL_NORMAL_ARRAY ); + + //**************************************** + // Vertex Pointer to triangle array + //**************************************** + glEnableClientState( GL_VERTEX_ARRAY ); + + //**************************************** + // Normal pointer to normal array + //**************************************** + glNormalPointer( GL_FLOAT, 0, (float*) &_normals[0] ); + + //**************************************** + // Index pointer to normal array + //**************************************** + glVertexPointer( COORD_PER_VERTEX, GL_FLOAT, 0, (float*) &_vertices[0] ); + + //**************************************** + // Draw the triangles + //**************************************** + glDrawElements( GL_TRIANGLES, _mesh.size( ) * VERTICES_PER_TRIANGLE, GL_UNSIGNED_INT, (idxtype*) & _mesh[0] ); + + //**************************************** + // Disable vertex arrays + //**************************************** + glDisableClientState( GL_VERTEX_ARRAY ); // disable vertex arrays + + //**************************************** + // Disable normal arrays + //**************************************** + glDisableClientState( GL_NORMAL_ARRAY ); +} + +// to be deprecated + +void ObjModel::drawSubdivision( ) +{ + if ( _subMesh.empty( ) || _subNorm.empty( ) || _subVert.empty( ) ) + { + loopSubdivision( _vertices, _mesh, _subVert, _subMesh, _subNorm ); + } + + glShadeModel( GL_SMOOTH ); + + glEnableClientState( GL_NORMAL_ARRAY ); + glEnableClientState( GL_VERTEX_ARRAY ); + + glNormalPointer( GL_FLOAT, 0, (float*) &_subNorm[0] ); + glVertexPointer( COORD_PER_VERTEX, GL_FLOAT, 0, (float*) &_subVert[0] ); + + glDrawElements( GL_TRIANGLES, _subMesh.size( ) * VERTICES_PER_TRIANGLE, GL_UNSIGNED_SHORT, (idxtype*) & _subMesh[0] ); + + + glDisableClientState( GL_VERTEX_ARRAY ); // disable vertex arrays + glDisableClientState( GL_NORMAL_ARRAY ); + + drawWireframe( _subVert, _subMesh, RenderingParameters( ) ); + +} + +// to be removed +void ObjModel::applyLoop( const face &t, const std::vector &origVert, std::vector &valence, std::vector &destVert ) const +{ + // 5/8 V + 3/8 sum(V_i)) + // in this case since we are summing each face the other vertices are counted + // twice, so we use 3/16 instead of 3/8 + valence[t.v1]++; + destVert[t.v1] += (0.625f * origVert[t.v1] + 0.1875f * origVert[t.v2] + 0.1875f * origVert[t.v3]); + // PRINTVAR(valence[t.v1]); + + valence[t.v2]++; + destVert[t.v2] += (0.625f * origVert[t.v2] + 0.1875f * origVert[t.v1] + 0.1875f * origVert[t.v3]); + + valence[t.v3]++; + destVert[t.v3] += (0.625f * origVert[t.v3] + 0.1875f * origVert[t.v2] + 0.1875f * origVert[t.v1]); +} diff --git a/TP5/ObjModel.hpp b/TP5/ObjModel.hpp new file mode 100755 index 0000000..c322adc --- /dev/null +++ b/TP5/ObjModel.hpp @@ -0,0 +1,260 @@ +/** + * @file ObjModel.hpp + * @author Simone Gasparini + * @version 1.0 + * + * @section LICENSE + * + * 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/. + * + * @section DESCRIPTION + * + * Simple Class to load and draw 3D objects from OBJ files + * Using triangles and normals as static object. No texture mapping. + * OBJ files must be triangulated!!! + * + */ + + +#pragma once + +#include "core.hpp" + +#include +#include +#include + + +#define VERTICES_PER_TRIANGLE 3 +#define COORD_PER_VERTEX 3 +#define TOTAL_FLOATS_IN_TRIANGLE (VERTICES_PER_TRIANGLE*COORD_PER_VERTEX) + + + +/** + * A structure that model the bounding box + */ +typedef struct BoundingBox +{ + point3d pmax; //! _mesh; //!< Stores the vertex indices for the triangles + std::vector _vertices; //!< Stores the vertices + std::vector _normals; //!< Stores the normals for the triangles + + // Subdivision + std::vector _subMesh; //!< Stores the vertex indices for the triangles + std::vector _subVert; //!< Stores the vertices + std::vector _subNorm; //!< Stores the normals for the triangles + + BoundingBox _bb; //!< the current bounding box of the model + + unsigned short _currentSubdivLevel{}; //!< the current subdivision level + +public: + ObjModel() = default; + + /** + * Calculate the normal of a triangular face defined by three points + * + * @param[in] v1 the first vertex + * @param[in] v2 the second vertex + * @param[in] cv3 the third vertex + * @param[out] norm the normal + */ + void computeNormal(const point3d& v1, const point3d& v2, const point3d& v3, vec3d &norm) const; + + /** + * Computes the angle at vertex baseV formed by the edges connecting it with the + * vertices v1 and v2 respectively, ie the baseV-v1 and baseV-v2 edges + * + * @brief Computes the angle at vertex + * @param[in] baseV the vertex at which to compute the angle + * @param[in] v1 the other vertex of the first edge baseV-v1 + * @param[in] v2 the other vertex of the second edge baseV-v2 + * @return the angle in radiants + */ + float angleAtVertex(const point3d& v1, const point3d& v2, const point3d& v3) const; + + /** + * Load the OBJ data from file + * @param[in] filename The name of the OBJ file + * @return 0 if everything went well + */ + int load(char *filename); + + /** + * Render the model according to the provided parameters + * @param params The rendering parameters + */ + void render(const RenderingParameters ¶ms = RenderingParameters()); + + /** + * Release the model + */ + void release(); + + /** + * It scales the model to unitary size by translating it to the origin and + * scaling it to fit in a unit cube around the origin. + * + * @return the scale factor used to transform the model + */ + float unitizeModel(); + + +private: + /** + * Draw the model + * + * @param[in] vertices list of vertices + * @param[in] indices list of faces + * @param[in] vertexNormals list of normals + * @param[in] params Rendering parameters + */ + void draw(const std::vector &vertices, const std::vector &indices, std::vector &vertexNormals, const RenderingParameters ¶ms) const; + + void drawSolid(const std::vector &vertices, const std::vector &indices, std::vector &vertexNormals, const RenderingParameters ¶ms) const; + + /** + * Draw the wireframe of the model + * + * @param vertices The list of vertices + * @param mesh The mesh as a list of faces, each face is a tripleIndex of vertex indices + * @param params The rendering parameters + */ + void drawWireframe(const std::vector &vertices, const std::vector &indices, const RenderingParameters ¶ms) const; + + /** + * Draw the model using the vertex indices and using a single normal for each vertex + * + * @param vertices The vertices + * @param indices The list of the faces, each face containing the 3 indices of the vertices + * @param vertexNormals The list of normals associated to each vertex + * @param params The rendering parameters + */ + void drawSmoothFaces(const std::vector &vertices, const std::vector &indices, std::vector &vertexNormals, const RenderingParameters ¶ms) const; + + /** + * Draw the faces using the computed normal of each face + * + * @param vertices The list of vertices + * @param mesh The list of face, each face containing the indices of the vertices + * @param params The rendering parameters + */ + void drawFlatFaces(const std::vector &vertices, const std::vector &indices, const RenderingParameters ¶ms) const; + /** + * Draw the normals at each vertex + * @param vertices The list of vertices + * @param vertexNormals The list of associated normals + */ + void drawNormals(const std::vector &vertices, std::vector &vertexNormals) const; + + + ///////////////////////////// + // DEPRECATED METHODS + DEPRECATED(void drawSubdivision()); + DEPRECATED(void indexDraw() const); + DEPRECATED(void flatDraw() const); + DEPRECATED(void drawWireframe() const); + + + +private: + + /** + * Compute the subdivision of the input mesh by applying one step of the Loop algorithm + * + * @param[in] origVert The list of the input vertices + * @param[in] origMesh The input mesh (the vertex indices for each face/triangle) + * @param[out] destVert The list of the new vertices for the subdivided mesh + * @param[out] destMesh The new subdivided mesh (the vertex indices for each face/triangle) + * @param[out] destNorm The new list of normals for each new vertex of the subdivided mesh + */ + void loopSubdivision(const std::vector &origVert, const std::vector &origMesh, std::vector &destVert, std::vector &destMesh, std::vector &destNorm) const; + + + /** + * For a given edge it returns the index of the new vertex created on its middle point. If such vertex already exists it just returns the + * its index; if it does not exist it creates it in vertList along it's normal and return the index + * @brief ObjModel::getNewVertex + * @param e the edge + * @param currFace the current triangle containing the edge e + * @param vertList the list of vertices + * @param indices the list of triangles + * @param normList the list of normals associated to the vertices + * @param newVertList The list of the new vertices added so far + * @return the index of the new vertex + * @see EdgeList + */ + idxtype getNewVertex(const edge &e, std::vector &vertList, const std::vector &mesh, EdgeList &newVertList) const; + + /** + * It parses a line of the OBJ file containing a face and it return the result. + * NB: it only recover the indices, it discard normal and texture indices + * + * @param toParse the string to parse in the OBJ format for a face (f v/vt/vn v/vt/vn v/vt/vn) and its variants + * @param out the 3 indices for the face + * @return true if the parse was successful + */ + bool parseFaceString(const std::string &toParse, face &out) const; + + DEPRECATED(void applyLoop(const face &t, const std::vector &orig, std::vector &valence, std::vector &dest) const); + +}; diff --git a/TP5/README.md b/TP5/README.md new file mode 100755 index 0000000..ff6d956 --- /dev/null +++ b/TP5/README.md @@ -0,0 +1,40 @@ +# OpenGL OBJ Visualizer + +## Introduction + +The fifth TP is about creating a visualizer for models loaded from [Wavefront OBJ format](https://en.wikipedia.org/wiki/Wavefront_.obj_file) files. +It also lets you implement a simple subdivision model of the mesh using Loop's algorithm. + +![alt text](data/img/screeshot.png "Application visualizer") + +The application lets you navigate the scene and change the different rendering algorithms. +The keys are: + +* `s` - use index rendering +* `w` - draw wireframe +* `s` - enable/disable subdivision +* `1`-`4` - with subdivision enabled, level of subdivision +* `d` - enable/disable solid rendering +* `a` - enable/disable smooth rendering +* `arrow keys` - rotate around the object +* `pg down/up` - zoom out/in + + +The folder [data/models](data/models) contains some 3D models to play with. + +## Building + +See [BUILD](BUILD.md) text file + +## License + +See [LICENSE](LICENSE) text file + +## Authors + +Simone Gasparini + + +## Contact + +Simone Gasparini simone.gasparini@irit.fr diff --git a/TP5/appveyor.yml b/TP5/appveyor.yml new file mode 100755 index 0000000..9ce6f37 --- /dev/null +++ b/TP5/appveyor.yml @@ -0,0 +1,31 @@ +version: '2021.0.0.{build}' + +image: Visual Studio 2019 + +platform: + - x64 + +configuration: + - Release + - Debug + +#install: +# - vcpkg upgrade --no-dry-run +# - vcpkg install +# opengl +# --triplet %PLATFORM%-windows + +before_build: + - md build + - cd build +# - cmake -G "Visual Studio 16 2019" -A x64 -T v140,host=x64 -DCMAKE_BUILD_TYPE=%configuration% -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake .. + - cmake -G "Visual Studio 16 2019" -A x64 -T v140,host=x64 -DCMAKE_BUILD_TYPE=%configuration% .. + - ls -l + +build: + verbosity: detailed + project: $(APPVEYOR_BUILD_FOLDER)\build\tp4.sln + parallel: true + +cache: +# - c:\tools\vcpkg\installed\ diff --git a/TP5/changelog.md b/TP5/changelog.md new file mode 100755 index 0000000..954f2ef --- /dev/null +++ b/TP5/changelog.md @@ -0,0 +1,27 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +### Added +- support for windows +- embed freeglut for windows +- ci on appveyor + +### Changed +- flat structure of the code +- modernize the code, rely only on c++11 +- raised cmake version + + +## [0.6.0] - 2017-10-05 +First stable version with subdivision +### Added +- Subdivision + + +## [0.5.0] - 2014-10-19 +First version of the visualizer + diff --git a/TP5/core.cpp b/TP5/core.cpp new file mode 100755 index 0000000..ca129be --- /dev/null +++ b/TP5/core.cpp @@ -0,0 +1,443 @@ +/** + * @file core.cpp + * @author Simone Gasparini + * @version 1.0 + * + * @section LICENSE + * + * 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/. + * + * @section DESCRIPTION + * + * The core module providing some helper classes for manipulating mesh data + * + */ + +#include "core.hpp" + +#include +#include +#include +#include + +void v3f::normalize() +{ + const float n = norm(); + if (n > (100.f * std::numeric_limits::epsilon()) ) + { + x /= n; + y /= n; + z /= n; + } +} + +float v3f::dot(const v3f &v) const +{ + return (x*v.x + y*v.y + z*v.z); +} + +float v3f::norm() const +{ + return std::sqrt(x*x + y*y + z*z); +} + +/** + * Translate the vector + * @param x the delta x of the translation + * @param y the delta y of the translation + * @param z the delta z of the translation + */ +void v3f::translate( const float &x, const float &y, const float &z ) +{ + this->x += x; + this->y += y; + this->z += z; +} + +void v3f::translate( const v3f &t ) +{ + x += t.x; + y += t.y; + z += t.z; +} + +void v3f::scale( const v3f &t ) +{ + x *= t.x; + y *= t.y; + z *= t.z; +} + +void v3f::scale( const float &x, const float &y, const float &z ) +{ + scale(v3f(x,y,z)); +} + +void v3f::scale( const float &a ) +{ + scale(v3f(a,a,a)); +} + +void v3f::min( const v3f& a ) +{ + x = std::min( x, a.x ); + y = std::min( y, a.y ); + z = std::min( z, a.z ); +} + +void v3f::max( const v3f& a ) +{ + x = std::max( x, a.x ); + y = std::max( y, a.y ); + z = std::max( z, a.z ); +} + +/** + * Return the minimum value among the 3 elements + * @return the minimum value + */ +float v3f::min() const +{ + return std::min( std::min( x, y ), z ); +} + +/** + * Return the maximum value among the 3 elements + * @return the maximum value + */ +float v3f::max() const +{ + return std::max( std::max( x, y ), z ); +} + +v3f v3f::cross(const v3f& v) const +{ + return {y*v.z - z*v.y, + z*v.x - x*v.z, + x*v.y - y*v.x}; +} + +v3f v3f::cross(const float v[3]) const +{ + return {y*v[2] - z*v[1], + z*v[0] - x*v[2], + x*v[1] - y*v[0]}; +} + +// OPERATOR OVERLOADINGS + +v3f v3f::operator +(const v3f& a) const +{ + return {x + a.x, y + a.y, z + a.z}; +} + +v3f& v3f::operator +=(const v3f& a) +{ + x += a.x; + y += a.y; + z += a.z; + return *this; +} + +v3f v3f::operator +(const float a[3]) const +{ + return {x + a[0], y + a[1], z + a[2]}; +} + +v3f& v3f::operator +=(const float a[3]) +{ + x += a[0]; + y += a[1]; + z += a[2]; + return *this; +} + +v3f v3f::operator +(const float &a) const +{ + return {x + a, y + a, z + a}; +} + +v3f& v3f::operator +=(const float &a) +{ + x += a; + y += a; + z += a; + return *this; +} + +// subtraction + +v3f v3f::operator -(const v3f& a) const +{ + return {x - a.x, y - a.y, z - a.z}; +} + +v3f& v3f::operator -=(const v3f& a) +{ + x -= a.x; + y -= a.y; + z -= a.z; + return *this; +} + +v3f v3f::operator -(const float a[3]) const +{ + return {x - a[0], y - a[1], z - a[2]}; +} + +v3f& v3f::operator -=(const float a[3]) +{ + x -= a[0]; + y -= a[1]; + z -= a[2]; + return *this; +} + +v3f v3f::operator -(const float &a) const +{ + return {x - a, y - a, z - a}; +} + +v3f& v3f::operator -=(const float& a) +{ + x -= a; + y -= a; + z -= a; + return *this; +} + +// element-wise product + +v3f v3f::operator *(const v3f& a) const +{ + return {x * a.x, y * a.y, z * a.z}; +} + +v3f& v3f::operator *=(const v3f& a) +{ + x *= a.x; + y *= a.y; + z *= a.z; + return *this; +} + +v3f v3f::operator *(const float a[3]) const +{ + return {x * a[0], y * a[1], z * a[2]}; +} + +v3f& v3f::operator *=(const float a[3]) +{ + x *= a[0]; + y *= a[1]; + z *= a[2]; + return *this; +} + +v3f v3f::operator *(const float &a) const +{ + return {x * a, y * a, z * a}; +} + +v3f& v3f::operator *=(const float &a) +{ + x *= a; + y *= a; + z *= a; + return *this; +} + +// element-wise ratio + +v3f v3f::operator /(const v3f& a) const +{ + return {x / a.x, y / a.y, z / a.z}; +} +v3f& v3f::operator /=(const v3f& a) +{ + x /= a.x; + y /= a.y; + z /= a.z; + return *this; +} + +v3f v3f::operator /(const float a[3]) const +{ + return {x / a[0], y / a[1], z / a[2]}; +} +v3f& v3f::operator /=(const float a[3]) +{ + x /= a[0]; + y /= a[1]; + z /= a[2]; + return *this; +} + +v3f v3f::operator /(const float &a) const +{ + return {x / a, y / a, z / a}; +} +v3f& v3f::operator /=(const float &a) +{ + x /= a; + y /= a; + z /= a; + return *this; +} + +//**************************** tindex ***********************// + + +/** + * Return true if the edge e is contained in the triplet of indices. If it is + * contained it also return the opposite vertex (ie the index of the vertex not + * belonging to the edge) + * + * @param[in] e the edge to check + * @param[out] oppositeVertex If the triplet contain the edge, this will be the (index of the) opposite + * vertex wrt the edge in the triangle + * @return true if the edge is contained (the order of the indices does not matter) + */ +bool face::containsEdge(const edge &e, idxtype &oppositeVertex) const +{ + if( edge(v1, v2) == e ) + { + oppositeVertex = v3; + return true; + } + else if ( edge(v2, v3) == e ) + { + oppositeVertex = v1; + return true; + } + else if ( edge(v3, v1) == e ) + { + oppositeVertex = v2; + return true; + } + else + { + return false; + } +} + +// ********** SUM +face face::operator +(const face& a) const +{ + return {v1 + a.v1, v2 + a.v2, v3 + a.v3}; + +} +face& face::operator +=(const face& a) +{ + v1 += a.v1; + v2 += a.v2; + v3 += a.v3; + return *this; +} + +face face::operator +(const idxtype &a) const +{ + return {v1 + a, v2 + a, v3 + a}; +} +face& face::operator +=(const idxtype &a) +{ + v1 += a; + v2 += a; + v3 += a; + return *this; +} + +// ********** DIFFERENCE +face face::operator -(const face& a) const +{ + return {v1 - a.v1, v2 - a.v2, v3 - a.v3}; +} +face& face::operator -=(const face& a) +{ + v1 -= a.v1; + v2 -= a.v2; + v3 -= a.v3; + return *this; +} + +face face::operator -(const idxtype &a) const +{ + return {v1 - a, v2 - a, v3 - a}; +} +face& face::operator -=(const idxtype &a) +{ + v1 -= a; + v2 -= a; + v3 -= a; + return *this; +} + +// ********** MULTIPLICATION +face face::operator *(const face& a) const +{ + return {v1 * a.v1, v2 * a.v2, v3 * a.v3}; +} +face& face::operator *=(const face& a) +{ + v1 *= a.v1; + v2 *= a.v2; + v3 *= a.v3; + return *this; +} + +face face::operator *(const idxtype &a) const +{ + return {v1 * a, v2 * a, v3 * a}; +} +face& face::operator *=(const idxtype &a) +{ + v1 *= a; + v2 *= a; + v3 *= a; + return *this; +} + +bool face::operator==( const face& r) const +{ + return ( (v1 == r.v1) && (v2 == r.v2) && (v3 == r.v3)); +} +/** + * Two index triplets are different if... they are not equal + */ +bool face::operator!=( const face& r ) const +{ + return ( !(*this == r)); +} + +/** + * It checks if the edge e is a boundary edge in the list of triangle. It also + * return the indices of the two opposite vertices of the edge or only one of + * them if it is a boundary edge + * + * @param[in] e the edge to check + * @param[in] mesh the list of triangles + * @param[out] oppVert1 the index of the first opposite vertices (the only one if the edge is a boundary edge) + * @param[out] oppVert2 the index of the second opposite vertices (only if the edge is not a boundary edge) + * @return true if the edge is a boundary edge + */ +bool isBoundaryEdge(const edge &e, const std::vector &mesh, idxtype &oppVert1, idxtype &oppVert2 ) +{ + bool foundFirst = false; + bool foundSecond = false; + for(size_t i = 0; (i < mesh.size()) && ( !(foundFirst && foundSecond)); ++i ) + { + if(!foundFirst) + { + foundFirst = mesh[i].containsEdge(e, oppVert1); + } + else + { + foundSecond = mesh[i].containsEdge(e, oppVert2); + } + } + // the edge should be in the list, so at least one should be found + assert(foundFirst); + return(foundFirst && (!foundSecond)); +} diff --git a/TP5/core.hpp b/TP5/core.hpp new file mode 100755 index 0000000..2c5f694 --- /dev/null +++ b/TP5/core.hpp @@ -0,0 +1,568 @@ +/** + * @file core.hpp + * @author Simone Gasparini + * @version 1.0 + * + * @section LICENSE + * + * 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/. + * + * @section DESCRIPTION + * + * The core module providing some helper classes for manipulating mesh data + * + */ + +#pragma once + +// for mac osx +#ifdef __APPLE__ +#include +#else +// only for windows +#ifdef _WIN32 +#include +#endif +// for windows and linux +#include +#endif + +#ifdef __GNUC__ +#define DEPRECATED(func) func __attribute__ ((deprecated)) +#elif defined(_MSC_VER) +#define DEPRECATED(func) __declspec(deprecated) func +#else +#pragma message("WARNING: You need to implement DEPRECATED for this compiler") +#define DEPRECATED(func) func +#endif //__attribute__ ((deprecated)) + +#include +#include +#include + +#include +#include + + +#define DEBUGGING 1 + +#if DEBUGGING +#define PRINTVAR( a ) std::cout << #a << " = " << (a) << std::endl << std::endl; +#else +#define PRINTVAR( a ) +#endif + +/** + * Renaming, the type of an index is a unsigned int + */ +using idxtype = GLuint; + +/** + * An edge is defined as a pair of indices of the vertices + */ +using edge = std::pair; + +/** + * Function to print the values of an edge + * + * @param os the string to fill + * @param p the edge to print + * @return the string with the printed value of the edge + */ +inline std::ostream& operator<<( std::ostream& os, const edge& e ) +{ + return os << "[" << e.first << "," << e.second << "]"; +} + +/** + * It checks whether two edges are equals: two edges are equal if their indices + * are the same, no matter the order + * + * @param[in] a The first edge + * @param[in] b The second edge + * @return true if the edges are equal + */ +inline bool operator==( const edge& a, const edge& b ) +{ + return ( ( ( a.first == b.first ) && ( a.second == b.second ) ) || + ( ( a.first == b.second ) && ( a.second == b.first ) ) ); +} + +/** + * Return the sum of vertex indices of an edge + * @param[in] e the edge + * @return the sum of the indices + */ +inline idxtype sum( const edge &e ) +{ + return (e.first + e.second ); +} + +/** + * return the min index of the two vertices + * @param[in] e the edge + * @return the min index + */ +inline idxtype min( const edge &e ) +{ + return (( e.first > e.second ) ? ( e.second ) : ( e.first ) ); +} + +/** + * Structure used to compare two edges + */ +struct edgeEquivalent +{ + + bool operator( ) ( const edge &a, const edge &b ) const + { + // return !( ( (a.first == b.first) && (a.second == b.second) ) || + // ( (a.first == b.second) && (a.second == b.first) ) ); + + // two edges are equal either their corresponding elements are equal or + // they are inverted + return ( ( ( a.first == b.first ) && ( a.second == b.second ) ) || + ( ( a.first == b.second ) && ( a.second == b.first ) ) ); + } +}; + + + +// to be used with unordered + +struct edgeHash +{ + + size_t operator( ) ( const edge &a ) const + { + std::hash fun; + return (fun( ( a.first > a.second ) ? ( "v" + std::to_string( a.second ) + "-" + std::to_string( a.first ) ) : + ( "v" + std::to_string( a.first ) + "-" + std::to_string( a.second ) ) ) ); + } +}; + + +/** + * An edge list is a map of edges (the keys) and a index of the vertex + */ +using edge2vertex = std::unordered_map< edge, idxtype, edgeHash, edgeEquivalent >; + +inline std::ostream& operator<<( std::ostream& os, const edge2vertex & l ) +{ + os << std::endl; + for (const auto &it : l) + os << "\t" << it.first << "\t" << it.second << std::endl; + return os; +} + +/** + * A helper class containing the indices of the new vertices added with the subdivision + * coupled with the edge that has generated them. More specifically, it is a list in which + * each entry has a key (ie an identifier) and a value: the key is the edge that + * generate the vertex, the value is the index of the new vertex. + * The key (ie the edge) is unique, ie an edge cannot generate more than one vertex. The + * edge is a pair of vertex indices, two edges are the same if they contain the + * same pair of vertices, no matter their order, ie + * edge(v1, v2) == edge(v2, v1) + * + * @see edge + */ +class EdgeList +{ +public: + + EdgeList( ) = default; + + /** + * Add the edge and the index of the new vertex generated on it + * @param[in] e the edge + * @param[in] idx the index of the new vertex generated on the edge + */ + void add( const edge &e, const idxtype &idx ) + { + list[e] = idx; + } + + /** + * Return true if the edge is in the map + * @param[in] e the edge to search for + * @return true if the edge is in the map + */ + bool contains( const edge &e ) const + { + return (list.find( e ) != list.end( ) ); + } + + /** + * Get the vertex index associated to the edge + * @param e the edge + * @return the index + */ + idxtype getIndex( const edge &e ) + { + return (list[e] ); + } + + friend std::ostream& operator<<( std::ostream& os, const EdgeList& l ); + + +private: + edge2vertex list; + +}; + +inline std::ostream& operator<<( std::ostream& os, const EdgeList& l ) +{ + return (os << l.list ); +} + +/**************************************************************************/ + + + +/** + * A generic vector of three elements + */ +struct v3f +{ + float x; //!< the first component + float y; //!< the second component + float z; //!< the third component + + /** + * Generic constructor + * @param x the first element + * @param y the second element + * @param z the third element + */ + v3f( float x, float y, float z ) : x( x ), y( y ), z( z ) { } + + /** + * Default constructor, everything is initialized to 0 + */ + v3f( ) : x( 0 ), y( 0 ), z( 0 ) { } + + /** + * Constructor from an array of three elements + * @param[in] a the array from which to copy the elements + */ + explicit v3f( const float a[3] ) : x( a[0] ), y( a[1] ), z( a[2] ) { } + + /** + * Normalize the vector (ie divide by the norm) + */ + void normalize( ); + + /** + * Return the dot product + * @param[in] v the other vector + * @return the dot product + */ + float dot( const v3f &v ) const; + + /** + * Return the norm of the vector + * @return the norm + */ + float norm( ) const; + + /** + * Translate the vector + * @param[in] x the delta x of the translation + * @param[in] y the delta y of the translation + * @param[in] z the delta z of the translation + */ + void translate( const float &x, const float &y, const float &z ); + + /** + * Translate the vector + * @param[in] t the translation + */ + void translate( const v3f &t ); + + /** + * Scale each element of the vector by the corresponding value + * @param[in] t a vector containing a factor scale to apply to each element + */ + void scale( const v3f &t ); + + /** + * Scale each element of the vector by the corresponding value + * @param x the scale value on x + * @param y the scale value on y + * @param z the scale value on z + */ + void scale( const float &x, const float& y, const float &z ); + + /** + * Scale each element of the vector by the same value + * @param a The scalar value to apply to each element + */ + void scale( const float &a ); + + /** + * Set each element of the current vector to the minimum value wrt another vector + * @param a the other vector + */ + void min( const v3f& a ); + + /** + * Return the minimum value among the 3 elements + * @return the minimum value + */ + float min( ) const; + + /** + * Set each element of the current vector to the maximum value wrt another vector + * @param a the other vector + */ + void max( const v3f& a ); + + /** + * Return the maximum value among the 3 elements + * @return the maximum value + */ + float max( ) const; + + /** + * Return the cross product of two vectors + * @param v the other vector + * @return the cross product + */ + v3f cross( const v3f& v ) const; + + /** + * Return the cross product of two vectors + * @param v the other array + * @return the cross product + */ + v3f cross( const float v[3] ) const; + + // element-wise addition + + v3f operator +( const v3f& a ) const; + v3f& operator +=( const v3f& a ); + + v3f operator +( const float a[3] ) const; + v3f& operator +=( const float a[3] ); + + v3f operator +( const float &a ) const; + v3f& operator +=( const float &a ); + + // element-wise subtraction + + v3f operator -( const v3f& a ) const; + v3f& operator -=( const v3f& a ); + + v3f operator -( const float a[3] ) const; + v3f& operator -=( const float a[3] ); + + v3f operator -( const float &a ) const; + v3f& operator -=( const float &a ); + + // element-wise product + + v3f operator *( const v3f& a ) const; + v3f& operator *=( const v3f& a ); + + v3f operator *( const float a[3] ) const; + v3f& operator *=( const float a[3] ); + + v3f operator *( const float &a ) const; + v3f& operator *=( const float &a ); + + // element-wise ratio + + v3f operator /( const v3f& a ) const; + v3f& operator /=( const v3f& a ); + + v3f operator /( const float a[3] ) const; + v3f& operator /=( const float a[3] ); + + v3f operator /( const float &a ) const; + v3f& operator /=( const float &a ); + +}; + +/** + * Some definitions + */ +using point3d = struct v3f ; +using vec3d = struct v3f; + +/** + * Print the elements of a vector + * @param os the string to fill with the vector values + * @param p the vector + * @return the string with the values + */ +inline std::ostream& operator<<( std::ostream& os, const v3f& p ) +{ + return os << "[" << p.x << "," << p.y << "," << p.z << "]"; +} + +/** + * Print the elements of a vector of v3f elements + * @param os the string to fill with the vector elements + * @param p the vector + * @return the string with the vector elements + */ +inline std::ostream& operator<<( std::ostream& os, const std::vector& p ) +{ + os << std::endl; + for (const auto& v : p) + os << "\t" << v << std::endl; + return os; +} + + + +// REFLEXIVE OPERATORS FOR V3F + +inline v3f operator +( const float &a, const v3f& p ) +{ + return (p + a ); +} + +inline v3f operator +( const float a[3], const v3f& p ) +{ + return (p + a ); +} + +inline v3f operator -( const float &a, const v3f& p ) +{ + return (p - a ); +} + +inline v3f operator -( const float a[3], const v3f& p ) +{ + return (p - a ); +} + +inline v3f operator *( const float a[3], const v3f& p ) +{ + return (p * a ); +} + +inline v3f operator *( const float &a, const v3f& p ) +{ + return (p * a ); +} + +inline v3f operator /( const float a[3], const v3f& p ) +{ + return (p / a ); +} + +inline v3f operator /( const float &a, const v3f& p ) +{ + return (p / a ); +} + + +/**************************************************************************/ + + + +/** + * The face as a triplet of indices + */ +struct face +{ + idxtype v1; //!< the first index + idxtype v2; //!< the second index + idxtype v3; //!< the third index + + /** + * Default constructor, everything set to 0 + */ + face( ) : v1( 0 ), v2( 0 ), v3( 0 ) { } + + /** + * Constructor from indices + * @param v1 the first index + * @param v2 the second index + * @param v3 the third index + */ + face( idxtype v1, idxtype v2, idxtype v3 ) : v1( v1 ), v2( v2 ), v3( v3 ) { } + + /** + * Return true if the edge e is contained in the triplet of indices. If it is + * contained it also return the opposite vertex (ie the index of the vertex not + * belonging to the edge) + * + * @param[in] e the edge to check + * @param[out] oppositeVertex If the triplet contain the edge, this will be the (index of the) opposite + * vertex wrt the edge in the triangle + * @return true if the edge is contained (the order of the indices does not matter) + */ + bool containsEdge( const edge &e, idxtype &oppositeVertex ) const; + + face operator +( const face& a ) const; + face& operator +=( const face& a ); + + face operator +( const idxtype &a ) const; + face& operator +=( const idxtype &a ); + + face operator -( const face& a ) const; + face& operator -=( const face& a ); + + face operator -( const idxtype &a ) const; + face& operator -=( const idxtype &a ); + + face operator *( const face& a ) const; + face& operator *=( const face& a ); + + face operator *( const idxtype &a ) const; + face& operator *=( const idxtype &a ); + + /** + * Two index triplets are equal if their corresponding elements are equal + */ + bool operator==( const face& rhs ) const; + /** + * Two index triplets are different if... they are not equal + */ + bool operator!=( const face& rhs ) const; +}; + +/** + * Print the face values on a string + * @param os the string to fill with the values + * @param p the face to print + * @return the string filled with the values + */ +inline std::ostream& operator<<( std::ostream& os, const face& p ) +{ + return os << "[" << p.v1 << "," << p.v2 << "," << p.v3 << "]"; +} + +/** + * Print a vector of faces on a string + * @param[in,out] os the string to fill with the faces + * @param[in] p the vector of faces to print + * @return the string filled with the faces + */ +inline std::ostream& operator<<( std::ostream& os, const std::vector& p ) +{ + os << std::endl; + for (auto v : p) + os << "\t" << v << std::endl; + return os; +} + +/** + * It checks if the edge e is a boundary edge in the list of triangle. It also + * return the indices of the two opposite vertices of the edge or only one of + * them if it is a boundary edge + * + * @param[in] e the edge to check + * @param[in] triangleList the list of triangles + * @param[out] oppVert1 the index of the first opposite vertices (the only one if the edge is a boundary edge) + * @param[out] oppVert2 the index of the second opposite vertices (only if the edge is not a boundary edge) + * @return true if the edge is not a boundary edge + */ +bool isBoundaryEdge( const edge &e, const std::vector &triangleList, idxtype &oppVert1, idxtype &oppVert2 ); + diff --git a/TP5/data/img/screeshot.png b/TP5/data/img/screeshot.png new file mode 100755 index 0000000000000000000000000000000000000000..c047f02408c1f5f43603536de2bc7ee74ccb2408 GIT binary patch literal 67166 zcmeGEi$7HB`#+A4Q;8(yP)Rx{mC)G^8IjGTA|;_|DoGMkIfcxk!rrw}Q*E7)5;;>c z7%Zu%tLins_yUhSklI9+V?#p zqh;rT4}0S^k8EG?*XO7EZz|lq`Io%>wa-r#HTP&d)LeK-9VGjMyt4YyN0Huw1Sc0 zwZ5qYf_2kL;&ept?;Ln)e|`j6Di5E)Uym%5$jLr=x#b#B_K~pT&+)mkk56lrVfpY+ z`;xc%vX4>A)(913pE!FdBC?N!^Z)dz%RXAiYy96g`M-Daf9K@??#};}g8#oMkK*h3 zG&w?ee(*>KiGH>zh4rC8kw6G~L8VPSrl;#tD-O|%FZS2OiZ^+dxoC zjmZ`}!ujV&t?(#0ZBE2WzTX6*b0-~sh_6IAQ9hE-T*gH{txdW|qgaR9jn8%d+kQpZ z-k~Rf2S?2XbMrLSvHbMaQLLHIMg8cH-gris zaf5Dh|H{uh_RI38ROQl|c~cgcwn(jC|J|wXUK_&%fh8)x}aHxVzN!%#YCdKkqvIDbU?@`ON6fr;X6m%kS@wn1QFBzhogZ zMV{cS0gtWn<*}Jl&e1%TfKnY5oQLAH(J`w?kpq2ZtUL2WWw!|Jw1(61mT6r`9lT&`z$;w^! zAe+XSJ>$F+ZvUd0xAn1jeaXD(mqzf&E%8p%xpvRVH{ zO&Go!-N0<+gXud3A>@CqD0Hu!tZ`0#z&kmgL13@6BX#L&{;Mc zS}7XNQR_^;^5gID(W1K3oyox+?n?_SH)#&vT{u%jTm|3T9}YWRQ;)HR)MPoef7pv& zgS0gBE@u+v!&Az^J6MoJp@qE*_Up{Q-(Iw*_v4*4YrW?pW_(>U3DYKqkQHc#Q*cq| zvdt&0ZH4MeNMMJ%k8l0I#*Zw+E{_+dt=>afJ?e!|&z0f2QT_bN84qJ}`S$Q`8?Sm*q$my@;zcU`I5a%|3 z

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

;gaG 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 +#include +#include +#else +// only for windows +#ifdef _WIN32 +#include +#endif +// for windows and linux +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#define KEY_ESCAPE 27 + +#define DELTA_ANGLE_X 5 +#define DELTA_ANGLE_Y 5 +#define DELTA_DISTANCE 0.3 +#define DISTANCE_MIN 0.0 + +using namespace std; + +// Window + +struct glutWindow +{ + int width{1024}; + int height{760}; + string title; + + float field_of_view_angle{45}; + float z_near{0.25f}; + float z_far{500.0f}; + + glutWindow() = default; + + explicit glutWindow(string &winName) : title(winName) {} +}; + +//************************************ +// global variable containing the OBJ model +//************************************ +ObjModel obj; + +int angle_y = 0; +int angle_x = 0; +float camDistance = 5; +RenderingParameters params; + +glutWindow win; + +// Function called every time the main window is resized +void reshape(int width, int height); +void DrawAxis(float scale); +// initialize the opengl +void initialize(); + +// define a material in terms of its components + +void define_material(GLfloat ar, GLfloat ag, GLfloat ab, // ambient + GLfloat dr, GLfloat dg, GLfloat db, // diffuse + GLfloat sr, GLfloat sg, GLfloat sb, // specular + GLfloat sh // shininess +) +{ + GLfloat mat_ambient[4]; + GLfloat mat_diffuse[4]; + GLfloat mat_specular[4]; + + mat_ambient[0] = ar; + mat_ambient[1] = ag; + mat_ambient[2] = ab; + mat_ambient[3] = 1.0; + glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient); + + mat_diffuse[0] = dr; + mat_diffuse[1] = dg; + mat_diffuse[2] = db; + mat_diffuse[3] = 1.0; + glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse); + + mat_specular[0] = sr; + mat_specular[1] = sg; + mat_specular[2] = sb; + mat_specular[3] = 1.0; + glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); + + glMaterialf(GL_FRONT, GL_SHININESS, sh); +} + +// place the light in x,y,z + +void place_light(GLfloat x, GLfloat y, GLfloat z) +{ + + GLfloat light_position[4]; + GLfloat light_ambient[] = {0.2, 0.2, 0.2, 1.0}; + GLfloat light_diffuse[] = {1.0, 1.0, 1.0, 1.0}; + GLfloat light_specular[] = {1.0, 1.0, 1.0, 1.0}; + + light_position[0] = x; + light_position[1] = y; + light_position[2] = z; + light_position[3] = 1.0; + + glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); + glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular); + glLightfv(GL_LIGHT0, GL_POSITION, light_position); + + glEnable(GL_LIGHT0); + glEnable(GL_LIGHTING); +} + +void display() +{ + glClearColor(0.5, .5, .75, 1.); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glPushMatrix(); + place_light(0, 0, 140); + glTranslatef(0, 0, -camDistance); + glRotatef(angle_x, 1, 0, 0); + glRotatef(angle_y, 0, 1, 0); + + // angle_y += 1; + + DrawAxis(1.0f); + + define_material( + 0.2, 0.2, 0.2, + 0.8, 0.8, 0.8, + 1.0, 0.8, 0.8, + 100); + //*********************************************** + // draw the model + //*********************************************** + obj.render(params); + + glPopMatrix(); + + glutSwapBuffers(); +} + +void printKeyboardHelp() +{ + std::cout << "keys:" + << "\t s - use index rendering\n" + << "\t w - draw wireframe\n" + << "\t h - enable/disable subdivision\n" + << "\t 1-4 - with subdivision enabled, level of subdivision\n" + << "\t d - enable/disable solid rendering\n" + << "\t a - enable/disable smooth rendering\n" + << "\t n - enable/disable normals rendering\n" + << "\t arrow keys - rotate around the object\n" + << "\t pg down/up - zoom out/in\n" + << std::endl; +} + +void keyboard(unsigned char key, int, int) +{ + switch (key) + { + case KEY_ESCAPE: + exit(0); + case 's': + params.useIndexRendering = !params.useIndexRendering; + PRINTVAR(params.useIndexRendering); + break; + case 'w': + params.wireframe = !params.wireframe; + PRINTVAR(params.wireframe); + break; + case 'h': + params.subdivision = !params.subdivision; + PRINTVAR(params.subdivision); + break; + case 'd': + params.solid = !params.solid; + PRINTVAR(params.solid); + break; + case 'a': + params.smooth = !params.smooth; + PRINTVAR(params.smooth); + break; + case 'n': + params.normals = !params.normals; + PRINTVAR(params.normals); + break; + case '1': + case '2': + case '3': + case '4': + params.subdivLevel = (key - '0'); + PRINTVAR(params.subdivLevel); + break; + default: + break; + } + glutPostRedisplay(); + printKeyboardHelp(); +} + +void arrows(int key, int, int) +{ + switch (key) + { + case GLUT_KEY_UP: + angle_x = (angle_x + DELTA_ANGLE_X) % 360; + break; + case GLUT_KEY_DOWN: + angle_x = (angle_x - DELTA_ANGLE_X) % 360; + break; + case GLUT_KEY_LEFT: + angle_y = (angle_y + DELTA_ANGLE_Y) % 360; + break; + case GLUT_KEY_RIGHT: + angle_y = (angle_y - DELTA_ANGLE_Y) % 360; + break; + case GLUT_KEY_PAGE_DOWN: + camDistance += DELTA_DISTANCE; + break; + case GLUT_KEY_PAGE_UP: + camDistance -= (camDistance > DISTANCE_MIN) ? DELTA_DISTANCE : 0.0; + break; + default: + break; + } + glutPostRedisplay(); +} + +int main(int argc, char **argv) +{ + if (argc == 1) + { + std::cout << "No obj file to load, displaying an emply scene with the reference system" << std::endl; + std::cout << "Usage:\n\t" + std::string(argv[0]) + " " << std::endl; + } + + // set window values + win.width = 1024; + win.height = 760; + win.title = string("OpenGL/GLUT OBJ Loader."); + win.field_of_view_angle = 45; + win.z_near = 0.25f; + win.z_far = 500.0f; + + // initialize and run program + glutInit(&argc, argv); + glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); + glutInitWindowSize(win.width, win.height); + glutCreateWindow(win.title.c_str()); + glutDisplayFunc(display); + glutReshapeFunc(reshape); + // glutIdleFunc( display ); // register Idle Function + glutKeyboardFunc(keyboard); + glutSpecialFunc(arrows); + initialize(); + + //*********************************************** + // Load the obj model from file + //*********************************************** + obj.load(argv[1]); + + //*********************************************** + // Make it unitary + //*********************************************** + obj.unitizeModel(); + + printKeyboardHelp(); + + glutMainLoop(); + + return EXIT_SUCCESS; +} + +// Function called every time the main window is resized + +void reshape(int width, int height) +{ + // define the viewport transformation; + glViewport(0, 0, width, height); + + GLfloat aspect = (GLfloat)win.width / win.height; + + if (aspect > 1.0f) // w>h + { + // if (width < height) + // { + // glViewport(0,(height-width/aspect)/2, width, width/aspect); + // cout << "a" << endl; + // } + // else if ( width/height > aspect ) + // { + // glViewport((width-height*aspect)/2,0,height*aspect,height); + // cout << "b" << endl; + // } + // else + if (width / height < aspect) + { + glViewport(0, (height - width / aspect) / 2, width, width / aspect); + // cout << "c " << (height-width/aspect)/2 << endl; + } + else + { + glViewport((width - height * aspect) / 2, 0, height * aspect, height); + // cout << "d " << (width-height*aspect)/2 << endl; + } + } + else // h>h + { + // if (height < width) + // { + // glViewport((width-height*aspect)/2,0, height*aspect, height); + // cout << "d "<< endl; + // } + // else if ( width/height > aspect ) + // { + // glViewport((width-height*aspect)/2,0,height*aspect,height); + // cout << "e" << endl; + // } + // else + { + glViewport((width - height * aspect) / 2, 0, height * aspect, height); + cout << "d " << (width - height * aspect) / 2 << endl; + } + } +} + +void DrawAxis(float scale) +{ + glPushMatrix(); + glDisable(GL_LIGHTING); + + glScalef(scale, scale, scale); + + glBegin(GL_LINES); + + glColor3f(1.0, 0.0, 0.0); + /* X axis */ + glVertex3f(0.0, 0.0, 0.0); + glVertex3f(1.0, 0.0, 0.0); + /* Letter X */ + glVertex3f(.8f, 0.05f, 0.0); // "backslash"" + glVertex3f(1.0, 0.25f, 0.0); + glVertex3f(0.8f, .25f, 0.0); // "slash" + glVertex3f(1.0, 0.05f, 0.0); + + /* Y axis */ + glColor3f(0.0, 1.0, 0.0); + glVertex3f(0.0, 0.0, 0.0); + glVertex3f(0.0, 1.0, 0.0); + + // z-axis + glColor3f(0.0, 0.0, 1.0); + glVertex3f(0.0, 0.0, 0.0); + glVertex3f(0.0, 0.0, 1.0); + /* Letter Z */ + glVertex3f(0.0, 0.05f, 0.8); // bottom horizontal leg + glVertex3f(0.0, 0.05f, 1.0); + glVertex3f(0.0, 0.05f, 1.0); // slash + glVertex3f(0.0, 0.25f, 0.8); + glVertex3f(0.0, 0.25f, 0.8); // upper horizontal leg + glVertex3f(0.0, 0.25f, 1.0); + + glEnd(); + + glEnable(GL_LIGHTING); + + glColor3f(1.0, 1.0, 1.0); + glPopMatrix(); +} + +void initialize() +{ + glMatrixMode(GL_MODELVIEW); + glShadeModel(GL_SMOOTH); + + glEnable(GL_CULL_FACE); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + + glEnable(GL_NORMALIZE); + + glMatrixMode(GL_PROJECTION); + glViewport(0, 0, win.width, win.height); + GLfloat aspect = (GLfloat)win.width / win.height; + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(win.field_of_view_angle, aspect, win.z_near, win.z_far); + glMatrixMode(GL_MODELVIEW); +} diff --git a/TP5/message.txt b/TP5/message.txt new file mode 100644 index 0000000..dc787c1 --- /dev/null +++ b/TP5/message.txt @@ -0,0 +1,563 @@ +/** + * @file ObjModel.cpp + * @author Simone Gasparini + * @version 1.0 + * + * @section LICENSE + * + * 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/. + * + * @section DESCRIPTION + * + * Simple Class to load and draw 3D objects from OBJ files + * Using triangles and normals as static object. No texture mapping. + * OBJ files must be triangulated!!! + * + */ + +#include "ObjModel.hpp" + +#include +#include +#include +#include +#include +//#include + +#include +#include +#include +#include + +using namespace std; + +/** + * Load the OBJ data from file + * @param filename The name of the OBJ file + * @return 0 if everything went well + */ +int ObjModel::load(char *filename) +{ + + string line; + ifstream objFile(filename); + + // If obj file is open, continue + if (objFile.is_open()) + { + // Start reading file data + while (!objFile.eof()) + { + // Get a line from file + getline(objFile, line); + + // If the first character is a simple 'v'... + // PRINTVAR( line ); + if ((line.c_str()[0] == 'v') && (line.c_str()[1] == ' ')) // to drop all the vn and vn lines + { + // PRINTVAR( line ); + // Read 3 floats from the line: X Y Z and store them in the corresponding place in _vertices + point3d p; + sscanf(line.c_str(), "v %f %f %f ", &p.x, &p.y, &p.z); + + //************************************************** + // add the new point to the list of the vertices + // and its normal to the list of normals: for the time + // being it is a [0, 0 ,0] normal. + //************************************************** + _vertices.push_back(p); + _normals.push_back((vec3d){0.0f, 0.0f, 0.0f}); + + // update the bounding box, if it is the first vertex simply + // set the bb to it + if (_vertices.size() == 1) + { + _bb.set(p); + } + else + { + // otherwise add the point + _bb.add(p); + } + } + // If the first character is a 'f'... + if (line.c_str()[0] == 'f') + { + + face t; + const auto ok = parseFaceString(line, t); + if (!ok) + throw std::runtime_error("Error while reading line: " + line); + + //************************************************** + // correct the indices: OBJ starts counting from 1, in C the arrays starts at 0... + //************************************************** + sscanf(line.c_str(), "v %d %d %d", &t.v1, &t.v2, &t.v3); + t.v1--; + t.v2--; + t.v3--; + + //************************************************** + // add it to the mesh + //************************************************** + _mesh.push_back(t); + + //********************************************************************* + // Compute the normal of the face + //********************************************************************* + vec3d n; + computeNormal(_vertices[t.v1], _vertices[t.v2], _vertices[t.v3], n); + + //********************************************************************* + // Sum the normal of the face to each vertex normal + //********************************************************************* + _normals[t.v1] += n * angleAtVertex(_vertices[t.v1], _vertices[t.v2], _vertices[t.v3]); + _normals[t.v2] += n * angleAtVertex(_vertices[t.v2], _vertices[t.v1], _vertices[t.v3]); + _normals[t.v3] += n * angleAtVertex(_vertices[t.v3], _vertices[t.v2], _vertices[t.v1]); + } + } + + cerr << "Found :\n\tNumber of triangles (_indices) " << _mesh.size() << "\n\tNumber of Vertices: " << _vertices.size() << "\n\tNumber of Normals: " << _normals.size() << endl; + // PRINTVAR( _mesh ); + // PRINTVAR( _vertices ); + // PRINTVAR( _normals ); + + //********************************************************************* + // normalize the normals of each vertex + //********************************************************************* + for (vec3d n : _normals) + { + n.normalize(); + } + + // PRINTVAR( _normals ); + + // Close OBJ file + objFile.close(); + } + else + { + cout << "Unable to open file"; + } + + cout << "Object loaded with " << _vertices.size() << " vertices and " << _mesh.size() << " faces" << endl; + cout << "Bounding box : pmax=" << _bb.pmax << " pmin=" << _bb.pmin << endl; + return 0; +} + +/** + * Draw the wireframe of the model + * + * @param vertices The list of vertices + * @param mesh The mesh as a list of faces, each face is a tripleIndex of vertex indices + * @param params The rendering parameters + */ +void ObjModel::drawWireframe(const std::vector &vertices, const std::vector &mesh, const RenderingParameters ¶ms) const +{ + //************************************************** + // we first need to disable the lighting in order to + // draw colored segments + //************************************************** + glDisable(GL_LIGHTING); + + // if we are displaying the object with colored faces + if (params.solid) + { + // use black ticker lines + glColor3f(0, 0, 0); + glLineWidth(2); + } + else + { + // otherwise use white thinner lines for wireframe only + glColor3f(.8, .8, .8); + glLineWidth(.21); + } + + //************************************************** + // for each face of the mesh... + //************************************************** + for (auto &f : mesh) + { + //************************************************** + // draw the contour of the face as a GL_LINE_LOOP + //************************************************** + glBegin(GL_LINE_LOOP); + glVertex3f(vertices[f.v1].x, vertices[f.v1].y, vertices[f.v1].z); + glVertex3f(vertices[f.v2].x, vertices[f.v2].y, vertices[f.v2].z); + glVertex3f(vertices[f.v3].x, vertices[f.v3].y, vertices[f.v3].z); + glEnd(); + } + + //************************************************** + // re-enable the lighting + //************************************************** + glEnable(GL_LIGHTING); +} + +/** + * Calculate the normal of a triangular face defined by three points + * + * @param[in] v1 the first vertex + * @param[in] v2 the second vertex + * @param[in] v3 the third vertex + * @param[out] norm the normal + */ +void ObjModel::computeNormal(const point3d &v1, const point3d &v2, const point3d &v3, vec3d &norm) const +{ + //************************************************** + // compute the cross product between two edges of the triangular face + //************************************************** + norm = (v2 - v1).cross(v3 - v1); + + //************************************************** + // remember to normalize the result + //************************************************** + norm.normalize(); +} + +/** + * Draw the faces using the computed normal of each face + * + * @param vertices The list of vertices + * @param mesh The list of face, each face containing the indices of the vertices + * @param params The rendering parameters + */ +void ObjModel::drawFlatFaces(const std::vector &vertices, const std::vector &mesh, const RenderingParameters ¶ms) const +{ + // shading model to use + if (params.smooth) + { + glShadeModel(GL_SMOOTH); + } + else + { + glShadeModel(GL_FLAT); + } + + //************************************************** + // for each face + //************************************************** + + std::vector subList(&mesh[params.slice], &mesh[params.slice + 1]); + for (auto &f : mesh) + { + //************************************************** + // Compute the normal to the face and then draw the + // faces as GL_TRIANGLES assigning the proper normal + //************************************************** + vec3d normal; + computeNormal(vertices[f.v1], vertices[f.v2], vertices[f.v3], normal); + glNormal3f(normal.x, normal.y, normal.z); + + glBegin(GL_TRIANGLES); + glVertex3f(vertices[f.v1].x, vertices[f.v1].y, vertices[f.v1].z); + glVertex3f(vertices[f.v2].x, vertices[f.v2].y, vertices[f.v2].z); + glVertex3f(vertices[f.v3].x, vertices[f.v3].y, vertices[f.v3].z); + glEnd(); + } +} + +/** + * Draw the model using the vertex indices + * + * @param vertices The vertices + * @param indices The list of the faces, each face containing the 3 indices of the vertices + * @param vertexNormals The list of normals associated to each vertex + * @param params The rendering parameters + */ +void ObjModel::drawSmoothFaces(const std::vector &vertices, + const std::vector &mesh, + std::vector &vertexNormals, + const RenderingParameters ¶ms) const +{ + if (params.smooth) + { + glShadeModel(GL_SMOOTH); + } + else + { + glShadeModel(GL_FLAT); + } + //**************************************** + // Enable vertex arrays + //**************************************** + glEnable(GL_VERTEX_ARRAY); + + //**************************************** + // Enable normal arrays + //**************************************** + glEnable(GL_NORMAL_ARRAY); + + //**************************************** + // Normal pointer to normal array + //**************************************** + glNormalPointer(GL_FLOAT, 0, &vertexNormals[0]); + + //**************************************** + // Vertex pointer to Vertex array + //**************************************** + glVertexPointer(3, GL_FLOAT, 0, &vertices[0]); + + //**************************************** + // Draw the faces + //**************************************** + glDrawElements(GL_TRIANGLES, 3 * mesh.size(), GL_UNSIGNED_INT, &mesh[0]); + + //**************************************** + // Disable vertex arrays + //**************************************** + glDisable(GL_VERTEX_ARRAY); + + //**************************************** + // Disable normal arrays + //**************************************** + glDisable(GL_NORMAL_ARRAY); +} + +/** + * Compute the subdivision of the input mesh by applying one step of the Loop algorithm + * + * @param[in] origVert The list of the input vertices + * @param[in] origMesh The input mesh (the vertex indices for each face/triangle) + * @param[out] destVert The list of the new vertices for the subdivided mesh + * @param[out] destMesh The new subdivided mesh (the vertex indices for each face/triangle) + * @param[out] destNorm The new list of normals for each new vertex of the subdivided mesh + */ +void ObjModel::loopSubdivision(const std::vector &origVert, //!< the original vertices + const std::vector &origMesh, //!< the original mesh + std::vector &destVert, //!< the new vertices + std::vector &destMesh, //!< the new mesh + std::vector &destNorm) const //!< the new normals +{ + // copy the original vertices in destVert + destVert = origVert; + + // start fresh with the new mesh + destMesh.clear(); + + // PRINTVAR(destVert); + // PRINTVAR(origVert); + + // create a list of the new vertices creates with the reference to the edge + EdgeList newVertices; + + //********************************************************************* + // for each face + //********************************************************************* + for (auto &f : origMesh) + { + //********************************************************************* + // get the indices of the triangle vertices + //********************************************************************* + int v1 = f.v1; + int v2 = f.v2; + int v3 = f.v3; + + //********************************************************************* + // for each edge get the index of the vertex of the midpoint using getNewVertex + //********************************************************************* + const int a = getNewVertex(edge{v1, v2}, destVert, origMesh, newVertices); + const int b = getNewVertex(edge{v2, v3}, destVert, origMesh, newVertices); + const int c = getNewVertex(edge{v3, v1}, destVert, origMesh, newVertices); + + //********************************************************************* + // create the four new triangles + // BE CAREFUL WITH THE VERTEX ORDER!! + // v2 + // /\ + // / \ + // / \ + // a ---- b + // / \ /\ + // / \ / \ + // / \ / \ + // v1 ---- c ---- v3 + // + // the original triangle was v1-v2-v3, use the same clock-wise order for the other + // hence v1-a-c, a-b-c and so on + //********************************************************************* + destMesh.push_back(face{v1, a, c}); + destMesh.push_back(face{a, v2, b}); + destMesh.push_back(face{c, b, v3}); + destMesh.push_back(face{a, b, c}); + } + + //********************************************************************* + // Update each "old" vertex using the Loop coefficients. A smart way to do + // so is to think in terms of faces than the single vertex: for each face + // we update each of the 3 vertices using the Loop formula wrt the other 2 and + // sum it to a temporary copy tmp of the vertices (which is initialized to + // [0 0 0] at the beginning). We also keep a record of the occurrence of each vertex. + // At then end, to get the final vertices we just need to divide each vertex + // in tmp by its occurrence + //******************************************************************** + + // A list containing the occurrence of each vertex + vector occurrences(origVert.size(), 0); + + // A list of the same size as origVert with all the elements initialized to [0 0 0] + vector tmp(origVert.size()); + + //********************************************************************* + // for each face + //********************************************************************* + for (auto &f : origMesh) + { + //********************************************************************* + // consider each of the 3 vertices: + // 1) increment its occurrence + // 2) apply Loop update with the other 2 vertices of the face + // BE CAREFUL WITH THE COEFFICIENT OF THE OTHER 2 VERTICES!... consider + // how many times each vertex is summed in the general case... + //********************************************************************* + + // 1) increment occurrences + occurrences[f.v1] += 2; + occurrences[f.v2] += 2; + occurrences[f.v3] += 2; + + // 2) apply Loop update + tmp[f.v1] += (destVert[f.v2] + destVert[f.v3]); + tmp[f.v2] += (destVert[f.v1] + destVert[f.v3]); + tmp[f.v3] += (destVert[f.v1] + destVert[f.v2]); + } + + //********************************************************************* + // To obtain the new vertices, divide each vertex by its occurrence value + //********************************************************************* + for (int i = 0; i < tmp.size(); i++) + { + assert(occurrences[i] != 0); + destVert[i] = 5.0f / 8.0f * destVert[i] + 3.0f / 8.0f / occurrences[i] * tmp[i]; + } + // PRINTVAR(destVert); + + // redo the normals, reset and create a list of normals of the same size as + // the vertices, each normal set to [0 0 0] + destNorm.clear(); + destNorm = vector(destVert.size()); + + //********************************************************************* + // Recompute the normals for each face + //********************************************************************* + for (auto &f : destMesh) + { + //********************************************************************* + // Calculate the normal of the triangles, it will be the same for each vertex + //********************************************************************* + vec3d n; + computeNormal(destVert[f.v1], destVert[f.v2], destVert[f.v3], n); + + //********************************************************************* + // Sum the normal of the face to each vertex normal using the angleAtVertex as weight + //********************************************************************* + destNorm[f.v1] += n * angleAtVertex(destVert[f.v1], destVert[f.v2], destVert[f.v3]); + destNorm[f.v2] += n * angleAtVertex(destVert[f.v2], destVert[f.v1], destVert[f.v3]); + destNorm[f.v3] += n * angleAtVertex(destVert[f.v3], destVert[f.v2], destVert[f.v1]); + } + //********************************************************************* + // normalize the normals of each vertex + //********************************************************************* + for (vec3d n : destNorm) + { + n.normalize(); + } +} + +/** + * For a given edge it returns the index of the new vertex created on its middle point. + * If such vertex already exists it just returns the its index; if it does not exist + * it creates it in vertList along it's normal and return the index + * + * @param[in] e the edge + * @param[in,out] vertList the list of vertices + * @param[in] mesh the list of triangles + * @param[in,out] newVertList The list of the new vertices added so far + * @return the index of the new vertex or the one that has been already created for that edge + * @see EdgeList + */ +idxtype ObjModel::getNewVertex(const edge &e, + std::vector &vertList, + const std::vector &mesh, + EdgeList &newVertList) const +{ + // PRINTVAR(e); + // PRINTVAR(newVertList); + + //********************************************************************* + // if the egde is NOT contained in the new vertex list (see EdgeList.contains() method) + //********************************************************************* + if (!newVertList.contains(e)) + { + //********************************************************************* + // generate new index (vertex.size) + //********************************************************************* + int newIndex = vertList.size(); + + //********************************************************************* + // add the edge and index to the newVertList + //********************************************************************* + newVertList.add(e, newIndex); + + // generate new vertex + point3d nvert; //!< this will contain the new vertex + idxtype oppV1; //!< the index of the first "opposite" vertex + idxtype oppV2; //!< the index of the second "opposite" vertex (if it exists) + + //********************************************************************* + // check if it is a boundary edge, ie check if there is another triangle + // sharing this edge and if so get the index of its "opposite" vertex + //********************************************************************* + if (!isBoundaryEdge(e, mesh, oppV1, oppV2)) + { + // if it is not a boundary edge create the new vertex + + //********************************************************************* + // the new vertex is the linear combination of the two extrema of + // the edge V1 and V2 and the two opposite vertices oppV1 and oppV2 + // Using the loop coefficient the new vertex is + // nvert = 3/8 (V1+V2) + 1/8(oppV1 + oppV2) + // + // REMEMBER THAT IN THE CODE OPPV1 AND OPPV2 ARE INDICES, NOT VERTICES!!! + //********************************************************************* + nvert = 3.0f / 8.0f * (vertList[e.first] + vertList[e.second]) + 1.0f / 8.0f * (vertList[oppV1] + vertList[oppV2]); + } + else + { + //********************************************************************* + // otherwise it is a boundary edge then the vertex is the linear combination of the + // two extrema + //********************************************************************* + nvert = 0.5f * vertList[e.first] + 0.5f * vertList[e.second]; + } + //********************************************************************* + // append the new vertex to the list of vertices + //********************************************************************* + vertList.push_back(nvert); + + //********************************************************************* + // return the index of the new vertex + //********************************************************************* + return newIndex; + } + else + // else we don't need to do anything, just return the associated index of the + // already existing vertex + { + //********************************************************************* + // get and return the index of the vertex + //********************************************************************* + return newVertList.getIndex(e); + } + + // this is just to avoid compilation errors at the beginning + // execution should normally never reach here + // the return instructions go inside each branch of the if - else above + std::cerr << "WARNING: the subdivision may not be implemented correctly" << std::endl; + return 0; +} + +#include "ObjModel.cxx" diff --git a/TP5/testEdge.cpp b/TP5/testEdge.cpp new file mode 100755 index 0000000..68b9bea --- /dev/null +++ b/TP5/testEdge.cpp @@ -0,0 +1,82 @@ +/* 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/. */ + +#include "core.hpp" +#include "ObjModel.hpp" +#include +#include + +#include + +std::unordered_map< int, int > map; + + +int main(int argc, char **argv) +{ + edge2vertex mylist; + + + mylist.insert(std::pair(edge(6,5),6)); + mylist.insert(std::pair(edge(1,2),1)); + mylist.insert(std::pair(edge(2,1),4)); + mylist.insert(std::pair(edge(1,4),5)); + mylist.insert(std::pair(edge(3,5),6)); +// mylist[edge(3,2)] = 10; + + PRINTVAR(mylist); + PRINTVAR(mylist.size()); + + bool res = (mylist.find(edge(5,3))) != mylist.end(); + PRINTVAR(res); + + PRINTVAR(((mylist.find(edge(4,1))) != mylist.end())); + PRINTVAR(((mylist.find(edge(3,2))) != mylist.end())); + PRINTVAR(((mylist.find(edge(1,2))) != mylist.end())); + PRINTVAR(((mylist.find(edge(2,1))) != mylist.end())); + + PRINTVAR(mylist[edge(4,1)]); + PRINTVAR(mylist[edge(2,1)]); + PRINTVAR(mylist[edge(3,5)]); + PRINTVAR(((mylist.find(edge(5,3))) != mylist.end())); + PRINTVAR(mylist[edge(6,5)]); + PRINTVAR(((mylist.find(edge(5,6))) != mylist.end())); + mylist[edge(0,1)]=100; + + PRINTVAR(mylist); + + std::string line("f 34//23 11//65 123//98"); + idxtype a; + face out; + std::string b; +// std::stringstream parser (line); +// parser >> b +// >> out.v1 >> b >> a +// >> out.v2 >> b >> a +// >> out.v3 >> b >> a; +// PRINTVAR(parser.fail()); +// std::cout << out << a << b << std::endl; + PRINTVAR(line); + PRINTVAR( (sscanf(line.c_str(), "f %u//%u %u//%u %u//%u", &(out.v1), &a, &(out.v2), &a, &(out.v3), &a) == 6) ); + std::cout << out << a << b << std::endl; + + PRINTVAR(v3f(1,2,4)*10.f); + PRINTVAR(10.f*v3f(1,2,4)); + + PRINTVAR((edge(3,5)==edge(3,5))); + PRINTVAR((edge(3,5)==edge(5,3))); + PRINTVAR((edge(1,2)==edge(3,5))); + + idxtype idx; + + PRINTVAR((face(1,2,3).containsEdge( edge(3,5), idx ))); + PRINTVAR( idx ); + PRINTVAR((face(1,2,3).containsEdge( edge(2,3), idx ))); + PRINTVAR( idx ); + PRINTVAR((face(1,2,3).containsEdge( edge(3,2), idx ))); + PRINTVAR( idx ); + PRINTVAR((face(1,2,3).containsEdge( edge(0,2), idx ))); + PRINTVAR( idx ); + PRINTVAR((face(1,2,3).containsEdge( edge(3,1), idx ))); + PRINTVAR( idx ); +} diff --git a/TP5/testEdgeList.cpp b/TP5/testEdgeList.cpp new file mode 100755 index 0000000..16b33b7 --- /dev/null +++ b/TP5/testEdgeList.cpp @@ -0,0 +1,94 @@ +/* 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/. */ +/* + * File: testEdgeList.cpp + * Author: Simone Gasparini + * + * Created on October 25, 2014, 1:26 PM + */ + + +#include "core.hpp" +#include "ObjModel.hpp" +#include +#include +#include +#include + +using namespace std; + +/* + * + */ +int main( int argc, char** argv ) +{ + + EdgeList list; + int numTrial; + + if(argc == 2) + { + numTrial = atoi(argv[1]); + } + else + { + numTrial = 1000; + } + + idxtype maxIdx = numTrial/5; + + vector listEdges; + vector listIdx; + + // fill up the list with random edges + cout << "Filling up data..." << endl; + for(size_t i =0; i < numTrial; ++i) + { + edge e(rand() % maxIdx, rand() % maxIdx); + listEdges.push_back(e); + listIdx.push_back(i); + + list.add(e, i); + } + + // test + cout << "Testing..." << endl; + for(size_t i =0; i < numTrial; ++i) + { + edge e = listEdges[i]; + // test it contains the edges we inserted + assert(list.contains( e )); + + // reflexive test (inverted indices)) + assert(list.contains( edge(e.second, e.first))); + + idxtype res = list.getIndex( e ); + + // if the index is different + if( res != listIdx[i] ) + { + // then there must be another edge with the same indices in the reversed order + bool found = false; + for( size_t j= 0; (j < numTrial) && (!found); ++j ) + { + // avoid to check for the current edge + if(j != i ) + { + found = (listEdges[j] == e); + if(found) + { + PRINTVAR(e); + PRINTVAR(listEdges[j]); + PRINTVAR(i); + PRINTVAR(j) + } + } + } + assert(found); + } + } + + cout << "Test passed!" << endl; +} +