diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/documentation/images/example/a2.png b/hw04/documentation/images/example/a2.png new file mode 100644 index 0000000..070f413 --- /dev/null +++ b/hw04/documentation/images/example/a2.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/documentation/images/example/a2.png b/hw04/documentation/images/example/a2.png new file mode 100644 index 0000000..070f413 --- /dev/null +++ b/hw04/documentation/images/example/a2.png Binary files differ diff --git a/hw04/documentation/images/example/a3.png b/hw04/documentation/images/example/a3.png new file mode 100644 index 0000000..5984c6c --- /dev/null +++ b/hw04/documentation/images/example/a3.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/documentation/images/example/a2.png b/hw04/documentation/images/example/a2.png new file mode 100644 index 0000000..070f413 --- /dev/null +++ b/hw04/documentation/images/example/a2.png Binary files differ diff --git a/hw04/documentation/images/example/a3.png b/hw04/documentation/images/example/a3.png new file mode 100644 index 0000000..5984c6c --- /dev/null +++ b/hw04/documentation/images/example/a3.png Binary files differ diff --git a/hw04/documentation/images/example/a4.png b/hw04/documentation/images/example/a4.png new file mode 100644 index 0000000..9cea993 --- /dev/null +++ b/hw04/documentation/images/example/a4.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/documentation/images/example/a2.png b/hw04/documentation/images/example/a2.png new file mode 100644 index 0000000..070f413 --- /dev/null +++ b/hw04/documentation/images/example/a2.png Binary files differ diff --git a/hw04/documentation/images/example/a3.png b/hw04/documentation/images/example/a3.png new file mode 100644 index 0000000..5984c6c --- /dev/null +++ b/hw04/documentation/images/example/a3.png Binary files differ diff --git a/hw04/documentation/images/example/a4.png b/hw04/documentation/images/example/a4.png new file mode 100644 index 0000000..9cea993 --- /dev/null +++ b/hw04/documentation/images/example/a4.png Binary files differ diff --git a/hw04/documentation/images/example/b0.png b/hw04/documentation/images/example/b0.png new file mode 100644 index 0000000..7d484d2 --- /dev/null +++ b/hw04/documentation/images/example/b0.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/documentation/images/example/a2.png b/hw04/documentation/images/example/a2.png new file mode 100644 index 0000000..070f413 --- /dev/null +++ b/hw04/documentation/images/example/a2.png Binary files differ diff --git a/hw04/documentation/images/example/a3.png b/hw04/documentation/images/example/a3.png new file mode 100644 index 0000000..5984c6c --- /dev/null +++ b/hw04/documentation/images/example/a3.png Binary files differ diff --git a/hw04/documentation/images/example/a4.png b/hw04/documentation/images/example/a4.png new file mode 100644 index 0000000..9cea993 --- /dev/null +++ b/hw04/documentation/images/example/a4.png Binary files differ diff --git a/hw04/documentation/images/example/b0.png b/hw04/documentation/images/example/b0.png new file mode 100644 index 0000000..7d484d2 --- /dev/null +++ b/hw04/documentation/images/example/b0.png Binary files differ diff --git a/hw04/documentation/images/example/b1.png b/hw04/documentation/images/example/b1.png new file mode 100644 index 0000000..3c9d514 --- /dev/null +++ b/hw04/documentation/images/example/b1.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/documentation/images/example/a2.png b/hw04/documentation/images/example/a2.png new file mode 100644 index 0000000..070f413 --- /dev/null +++ b/hw04/documentation/images/example/a2.png Binary files differ diff --git a/hw04/documentation/images/example/a3.png b/hw04/documentation/images/example/a3.png new file mode 100644 index 0000000..5984c6c --- /dev/null +++ b/hw04/documentation/images/example/a3.png Binary files differ diff --git a/hw04/documentation/images/example/a4.png b/hw04/documentation/images/example/a4.png new file mode 100644 index 0000000..9cea993 --- /dev/null +++ b/hw04/documentation/images/example/a4.png Binary files differ diff --git a/hw04/documentation/images/example/b0.png b/hw04/documentation/images/example/b0.png new file mode 100644 index 0000000..7d484d2 --- /dev/null +++ b/hw04/documentation/images/example/b0.png Binary files differ diff --git a/hw04/documentation/images/example/b1.png b/hw04/documentation/images/example/b1.png new file mode 100644 index 0000000..3c9d514 --- /dev/null +++ b/hw04/documentation/images/example/b1.png Binary files differ diff --git a/hw04/documentation/images/example/b2.png b/hw04/documentation/images/example/b2.png new file mode 100644 index 0000000..35beda4 --- /dev/null +++ b/hw04/documentation/images/example/b2.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/documentation/images/example/a2.png b/hw04/documentation/images/example/a2.png new file mode 100644 index 0000000..070f413 --- /dev/null +++ b/hw04/documentation/images/example/a2.png Binary files differ diff --git a/hw04/documentation/images/example/a3.png b/hw04/documentation/images/example/a3.png new file mode 100644 index 0000000..5984c6c --- /dev/null +++ b/hw04/documentation/images/example/a3.png Binary files differ diff --git a/hw04/documentation/images/example/a4.png b/hw04/documentation/images/example/a4.png new file mode 100644 index 0000000..9cea993 --- /dev/null +++ b/hw04/documentation/images/example/a4.png Binary files differ diff --git a/hw04/documentation/images/example/b0.png b/hw04/documentation/images/example/b0.png new file mode 100644 index 0000000..7d484d2 --- /dev/null +++ b/hw04/documentation/images/example/b0.png Binary files differ diff --git a/hw04/documentation/images/example/b1.png b/hw04/documentation/images/example/b1.png new file mode 100644 index 0000000..3c9d514 --- /dev/null +++ b/hw04/documentation/images/example/b1.png Binary files differ diff --git a/hw04/documentation/images/example/b2.png b/hw04/documentation/images/example/b2.png new file mode 100644 index 0000000..35beda4 --- /dev/null +++ b/hw04/documentation/images/example/b2.png Binary files differ diff --git a/hw04/documentation/images/example/b3.png b/hw04/documentation/images/example/b3.png new file mode 100644 index 0000000..fe89454 --- /dev/null +++ b/hw04/documentation/images/example/b3.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/documentation/images/example/a2.png b/hw04/documentation/images/example/a2.png new file mode 100644 index 0000000..070f413 --- /dev/null +++ b/hw04/documentation/images/example/a2.png Binary files differ diff --git a/hw04/documentation/images/example/a3.png b/hw04/documentation/images/example/a3.png new file mode 100644 index 0000000..5984c6c --- /dev/null +++ b/hw04/documentation/images/example/a3.png Binary files differ diff --git a/hw04/documentation/images/example/a4.png b/hw04/documentation/images/example/a4.png new file mode 100644 index 0000000..9cea993 --- /dev/null +++ b/hw04/documentation/images/example/a4.png Binary files differ diff --git a/hw04/documentation/images/example/b0.png b/hw04/documentation/images/example/b0.png new file mode 100644 index 0000000..7d484d2 --- /dev/null +++ b/hw04/documentation/images/example/b0.png Binary files differ diff --git a/hw04/documentation/images/example/b1.png b/hw04/documentation/images/example/b1.png new file mode 100644 index 0000000..3c9d514 --- /dev/null +++ b/hw04/documentation/images/example/b1.png Binary files differ diff --git a/hw04/documentation/images/example/b2.png b/hw04/documentation/images/example/b2.png new file mode 100644 index 0000000..35beda4 --- /dev/null +++ b/hw04/documentation/images/example/b2.png Binary files differ diff --git a/hw04/documentation/images/example/b3.png b/hw04/documentation/images/example/b3.png new file mode 100644 index 0000000..fe89454 --- /dev/null +++ b/hw04/documentation/images/example/b3.png Binary files differ diff --git a/hw04/documentation/images/example/b4.png b/hw04/documentation/images/example/b4.png new file mode 100644 index 0000000..aec3742 --- /dev/null +++ b/hw04/documentation/images/example/b4.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/documentation/images/example/a2.png b/hw04/documentation/images/example/a2.png new file mode 100644 index 0000000..070f413 --- /dev/null +++ b/hw04/documentation/images/example/a2.png Binary files differ diff --git a/hw04/documentation/images/example/a3.png b/hw04/documentation/images/example/a3.png new file mode 100644 index 0000000..5984c6c --- /dev/null +++ b/hw04/documentation/images/example/a3.png Binary files differ diff --git a/hw04/documentation/images/example/a4.png b/hw04/documentation/images/example/a4.png new file mode 100644 index 0000000..9cea993 --- /dev/null +++ b/hw04/documentation/images/example/a4.png Binary files differ diff --git a/hw04/documentation/images/example/b0.png b/hw04/documentation/images/example/b0.png new file mode 100644 index 0000000..7d484d2 --- /dev/null +++ b/hw04/documentation/images/example/b0.png Binary files differ diff --git a/hw04/documentation/images/example/b1.png b/hw04/documentation/images/example/b1.png new file mode 100644 index 0000000..3c9d514 --- /dev/null +++ b/hw04/documentation/images/example/b1.png Binary files differ diff --git a/hw04/documentation/images/example/b2.png b/hw04/documentation/images/example/b2.png new file mode 100644 index 0000000..35beda4 --- /dev/null +++ b/hw04/documentation/images/example/b2.png Binary files differ diff --git a/hw04/documentation/images/example/b3.png b/hw04/documentation/images/example/b3.png new file mode 100644 index 0000000..fe89454 --- /dev/null +++ b/hw04/documentation/images/example/b3.png Binary files differ diff --git a/hw04/documentation/images/example/b4.png b/hw04/documentation/images/example/b4.png new file mode 100644 index 0000000..aec3742 --- /dev/null +++ b/hw04/documentation/images/example/b4.png Binary files differ diff --git a/hw04/documentation/images/example/c0.png b/hw04/documentation/images/example/c0.png new file mode 100644 index 0000000..3b26682 --- /dev/null +++ b/hw04/documentation/images/example/c0.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/documentation/images/example/a2.png b/hw04/documentation/images/example/a2.png new file mode 100644 index 0000000..070f413 --- /dev/null +++ b/hw04/documentation/images/example/a2.png Binary files differ diff --git a/hw04/documentation/images/example/a3.png b/hw04/documentation/images/example/a3.png new file mode 100644 index 0000000..5984c6c --- /dev/null +++ b/hw04/documentation/images/example/a3.png Binary files differ diff --git a/hw04/documentation/images/example/a4.png b/hw04/documentation/images/example/a4.png new file mode 100644 index 0000000..9cea993 --- /dev/null +++ b/hw04/documentation/images/example/a4.png Binary files differ diff --git a/hw04/documentation/images/example/b0.png b/hw04/documentation/images/example/b0.png new file mode 100644 index 0000000..7d484d2 --- /dev/null +++ b/hw04/documentation/images/example/b0.png Binary files differ diff --git a/hw04/documentation/images/example/b1.png b/hw04/documentation/images/example/b1.png new file mode 100644 index 0000000..3c9d514 --- /dev/null +++ b/hw04/documentation/images/example/b1.png Binary files differ diff --git a/hw04/documentation/images/example/b2.png b/hw04/documentation/images/example/b2.png new file mode 100644 index 0000000..35beda4 --- /dev/null +++ b/hw04/documentation/images/example/b2.png Binary files differ diff --git a/hw04/documentation/images/example/b3.png b/hw04/documentation/images/example/b3.png new file mode 100644 index 0000000..fe89454 --- /dev/null +++ b/hw04/documentation/images/example/b3.png Binary files differ diff --git a/hw04/documentation/images/example/b4.png b/hw04/documentation/images/example/b4.png new file mode 100644 index 0000000..aec3742 --- /dev/null +++ b/hw04/documentation/images/example/b4.png Binary files differ diff --git a/hw04/documentation/images/example/c0.png b/hw04/documentation/images/example/c0.png new file mode 100644 index 0000000..3b26682 --- /dev/null +++ b/hw04/documentation/images/example/c0.png Binary files differ diff --git a/hw04/documentation/images/example/c1.png b/hw04/documentation/images/example/c1.png new file mode 100644 index 0000000..88565b1 --- /dev/null +++ b/hw04/documentation/images/example/c1.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/documentation/images/example/a2.png b/hw04/documentation/images/example/a2.png new file mode 100644 index 0000000..070f413 --- /dev/null +++ b/hw04/documentation/images/example/a2.png Binary files differ diff --git a/hw04/documentation/images/example/a3.png b/hw04/documentation/images/example/a3.png new file mode 100644 index 0000000..5984c6c --- /dev/null +++ b/hw04/documentation/images/example/a3.png Binary files differ diff --git a/hw04/documentation/images/example/a4.png b/hw04/documentation/images/example/a4.png new file mode 100644 index 0000000..9cea993 --- /dev/null +++ b/hw04/documentation/images/example/a4.png Binary files differ diff --git a/hw04/documentation/images/example/b0.png b/hw04/documentation/images/example/b0.png new file mode 100644 index 0000000..7d484d2 --- /dev/null +++ b/hw04/documentation/images/example/b0.png Binary files differ diff --git a/hw04/documentation/images/example/b1.png b/hw04/documentation/images/example/b1.png new file mode 100644 index 0000000..3c9d514 --- /dev/null +++ b/hw04/documentation/images/example/b1.png Binary files differ diff --git a/hw04/documentation/images/example/b2.png b/hw04/documentation/images/example/b2.png new file mode 100644 index 0000000..35beda4 --- /dev/null +++ b/hw04/documentation/images/example/b2.png Binary files differ diff --git a/hw04/documentation/images/example/b3.png b/hw04/documentation/images/example/b3.png new file mode 100644 index 0000000..fe89454 --- /dev/null +++ b/hw04/documentation/images/example/b3.png Binary files differ diff --git a/hw04/documentation/images/example/b4.png b/hw04/documentation/images/example/b4.png new file mode 100644 index 0000000..aec3742 --- /dev/null +++ b/hw04/documentation/images/example/b4.png Binary files differ diff --git a/hw04/documentation/images/example/c0.png b/hw04/documentation/images/example/c0.png new file mode 100644 index 0000000..3b26682 --- /dev/null +++ b/hw04/documentation/images/example/c0.png Binary files differ diff --git a/hw04/documentation/images/example/c1.png b/hw04/documentation/images/example/c1.png new file mode 100644 index 0000000..88565b1 --- /dev/null +++ b/hw04/documentation/images/example/c1.png Binary files differ diff --git a/hw04/documentation/images/example/c2.png b/hw04/documentation/images/example/c2.png new file mode 100644 index 0000000..1948a7a --- /dev/null +++ b/hw04/documentation/images/example/c2.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/documentation/images/example/a2.png b/hw04/documentation/images/example/a2.png new file mode 100644 index 0000000..070f413 --- /dev/null +++ b/hw04/documentation/images/example/a2.png Binary files differ diff --git a/hw04/documentation/images/example/a3.png b/hw04/documentation/images/example/a3.png new file mode 100644 index 0000000..5984c6c --- /dev/null +++ b/hw04/documentation/images/example/a3.png Binary files differ diff --git a/hw04/documentation/images/example/a4.png b/hw04/documentation/images/example/a4.png new file mode 100644 index 0000000..9cea993 --- /dev/null +++ b/hw04/documentation/images/example/a4.png Binary files differ diff --git a/hw04/documentation/images/example/b0.png b/hw04/documentation/images/example/b0.png new file mode 100644 index 0000000..7d484d2 --- /dev/null +++ b/hw04/documentation/images/example/b0.png Binary files differ diff --git a/hw04/documentation/images/example/b1.png b/hw04/documentation/images/example/b1.png new file mode 100644 index 0000000..3c9d514 --- /dev/null +++ b/hw04/documentation/images/example/b1.png Binary files differ diff --git a/hw04/documentation/images/example/b2.png b/hw04/documentation/images/example/b2.png new file mode 100644 index 0000000..35beda4 --- /dev/null +++ b/hw04/documentation/images/example/b2.png Binary files differ diff --git a/hw04/documentation/images/example/b3.png b/hw04/documentation/images/example/b3.png new file mode 100644 index 0000000..fe89454 --- /dev/null +++ b/hw04/documentation/images/example/b3.png Binary files differ diff --git a/hw04/documentation/images/example/b4.png b/hw04/documentation/images/example/b4.png new file mode 100644 index 0000000..aec3742 --- /dev/null +++ b/hw04/documentation/images/example/b4.png Binary files differ diff --git a/hw04/documentation/images/example/c0.png b/hw04/documentation/images/example/c0.png new file mode 100644 index 0000000..3b26682 --- /dev/null +++ b/hw04/documentation/images/example/c0.png Binary files differ diff --git a/hw04/documentation/images/example/c1.png b/hw04/documentation/images/example/c1.png new file mode 100644 index 0000000..88565b1 --- /dev/null +++ b/hw04/documentation/images/example/c1.png Binary files differ diff --git a/hw04/documentation/images/example/c2.png b/hw04/documentation/images/example/c2.png new file mode 100644 index 0000000..1948a7a --- /dev/null +++ b/hw04/documentation/images/example/c2.png Binary files differ diff --git a/hw04/documentation/images/example/c3.png b/hw04/documentation/images/example/c3.png new file mode 100644 index 0000000..3423428 --- /dev/null +++ b/hw04/documentation/images/example/c3.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/documentation/images/example/a2.png b/hw04/documentation/images/example/a2.png new file mode 100644 index 0000000..070f413 --- /dev/null +++ b/hw04/documentation/images/example/a2.png Binary files differ diff --git a/hw04/documentation/images/example/a3.png b/hw04/documentation/images/example/a3.png new file mode 100644 index 0000000..5984c6c --- /dev/null +++ b/hw04/documentation/images/example/a3.png Binary files differ diff --git a/hw04/documentation/images/example/a4.png b/hw04/documentation/images/example/a4.png new file mode 100644 index 0000000..9cea993 --- /dev/null +++ b/hw04/documentation/images/example/a4.png Binary files differ diff --git a/hw04/documentation/images/example/b0.png b/hw04/documentation/images/example/b0.png new file mode 100644 index 0000000..7d484d2 --- /dev/null +++ b/hw04/documentation/images/example/b0.png Binary files differ diff --git a/hw04/documentation/images/example/b1.png b/hw04/documentation/images/example/b1.png new file mode 100644 index 0000000..3c9d514 --- /dev/null +++ b/hw04/documentation/images/example/b1.png Binary files differ diff --git a/hw04/documentation/images/example/b2.png b/hw04/documentation/images/example/b2.png new file mode 100644 index 0000000..35beda4 --- /dev/null +++ b/hw04/documentation/images/example/b2.png Binary files differ diff --git a/hw04/documentation/images/example/b3.png b/hw04/documentation/images/example/b3.png new file mode 100644 index 0000000..fe89454 --- /dev/null +++ b/hw04/documentation/images/example/b3.png Binary files differ diff --git a/hw04/documentation/images/example/b4.png b/hw04/documentation/images/example/b4.png new file mode 100644 index 0000000..aec3742 --- /dev/null +++ b/hw04/documentation/images/example/b4.png Binary files differ diff --git a/hw04/documentation/images/example/c0.png b/hw04/documentation/images/example/c0.png new file mode 100644 index 0000000..3b26682 --- /dev/null +++ b/hw04/documentation/images/example/c0.png Binary files differ diff --git a/hw04/documentation/images/example/c1.png b/hw04/documentation/images/example/c1.png new file mode 100644 index 0000000..88565b1 --- /dev/null +++ b/hw04/documentation/images/example/c1.png Binary files differ diff --git a/hw04/documentation/images/example/c2.png b/hw04/documentation/images/example/c2.png new file mode 100644 index 0000000..1948a7a --- /dev/null +++ b/hw04/documentation/images/example/c2.png Binary files differ diff --git a/hw04/documentation/images/example/c3.png b/hw04/documentation/images/example/c3.png new file mode 100644 index 0000000..3423428 --- /dev/null +++ b/hw04/documentation/images/example/c3.png Binary files differ diff --git a/hw04/documentation/images/example/c4.png b/hw04/documentation/images/example/c4.png new file mode 100644 index 0000000..89cb07f --- /dev/null +++ b/hw04/documentation/images/example/c4.png Binary files differ diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/documentation/images/example/a2.png b/hw04/documentation/images/example/a2.png new file mode 100644 index 0000000..070f413 --- /dev/null +++ b/hw04/documentation/images/example/a2.png Binary files differ diff --git a/hw04/documentation/images/example/a3.png b/hw04/documentation/images/example/a3.png new file mode 100644 index 0000000..5984c6c --- /dev/null +++ b/hw04/documentation/images/example/a3.png Binary files differ diff --git a/hw04/documentation/images/example/a4.png b/hw04/documentation/images/example/a4.png new file mode 100644 index 0000000..9cea993 --- /dev/null +++ b/hw04/documentation/images/example/a4.png Binary files differ diff --git a/hw04/documentation/images/example/b0.png b/hw04/documentation/images/example/b0.png new file mode 100644 index 0000000..7d484d2 --- /dev/null +++ b/hw04/documentation/images/example/b0.png Binary files differ diff --git a/hw04/documentation/images/example/b1.png b/hw04/documentation/images/example/b1.png new file mode 100644 index 0000000..3c9d514 --- /dev/null +++ b/hw04/documentation/images/example/b1.png Binary files differ diff --git a/hw04/documentation/images/example/b2.png b/hw04/documentation/images/example/b2.png new file mode 100644 index 0000000..35beda4 --- /dev/null +++ b/hw04/documentation/images/example/b2.png Binary files differ diff --git a/hw04/documentation/images/example/b3.png b/hw04/documentation/images/example/b3.png new file mode 100644 index 0000000..fe89454 --- /dev/null +++ b/hw04/documentation/images/example/b3.png Binary files differ diff --git a/hw04/documentation/images/example/b4.png b/hw04/documentation/images/example/b4.png new file mode 100644 index 0000000..aec3742 --- /dev/null +++ b/hw04/documentation/images/example/b4.png Binary files differ diff --git a/hw04/documentation/images/example/c0.png b/hw04/documentation/images/example/c0.png new file mode 100644 index 0000000..3b26682 --- /dev/null +++ b/hw04/documentation/images/example/c0.png Binary files differ diff --git a/hw04/documentation/images/example/c1.png b/hw04/documentation/images/example/c1.png new file mode 100644 index 0000000..88565b1 --- /dev/null +++ b/hw04/documentation/images/example/c1.png Binary files differ diff --git a/hw04/documentation/images/example/c2.png b/hw04/documentation/images/example/c2.png new file mode 100644 index 0000000..1948a7a --- /dev/null +++ b/hw04/documentation/images/example/c2.png Binary files differ diff --git a/hw04/documentation/images/example/c3.png b/hw04/documentation/images/example/c3.png new file mode 100644 index 0000000..3423428 --- /dev/null +++ b/hw04/documentation/images/example/c3.png Binary files differ diff --git a/hw04/documentation/images/example/c4.png b/hw04/documentation/images/example/c4.png new file mode 100644 index 0000000..89cb07f --- /dev/null +++ b/hw04/documentation/images/example/c4.png Binary files differ diff --git a/hw04/documentation/main.tex b/hw04/documentation/main.tex new file mode 100644 index 0000000..fdd09d6 --- /dev/null +++ b/hw04/documentation/main.tex @@ -0,0 +1,136 @@ +\documentclass{article} + +\usepackage{svg} +\usepackage{amssymb} +\usepackage[british]{babel} +\usepackage[utf8]{inputenc} +\usepackage{amsmath,amssymb} +\usepackage{parskip} +\usepackage{graphicx} +\usepackage{listings} +\usepackage{caption} +\DeclareCaptionType{code}[Codeblock][List of Codeblocks] +\usepackage[a]{} +\usepackage{float} +\usepackage{hyperref} +\usepackage{tcolorbox} +\usepackage{datatool}% http://ctan.org/pkg/datatool +\newcommand{\sortitem}[1]{% + \DTLnewrow{list}% Create a new entry + \DTLnewdbentry{list}{description}{#1}% Add entry as description +} +\newenvironment{sortedlist}{% + \DTLifdbexists{list}{\DTLcleardb{list}}{\DTLnewdb{list}}% Create new/discard old list +}{% + \DTLsort{description}{list}% Sort list + \begin{itemize}% + \DTLforeach*{list}{\theDesc=description}{% + \item \theDesc}% Print each item + \end{itemize}% +} +\usepackage{tikz} +\newcommand*\circled[1]{\tikz[baseline=(char.base)]{ + \node[shape=circle,fill=red, text=white, draw=red,inner sep=2pt,font=\bfseries] (char) {#1};}} + +\usepackage[a4paper, textwidth=15cm]{geometry} + +\usepackage{minted} +\usemintedstyle{monokai} +\lstset{language=C++} +% load package with ``framed'' and ``numbered'' option. +\usepackage[framed,numbered,autolinebreaks,useliterate]{mcode} +% something NOT relevant to the usage of the package. +\usepackage{url} + +\newcommand{\inCode}[1]{\small\colorbox{black!70}{\mintinline{Cirru}{#1}}} +\newcommand{\paragraphnl}[1]{\paragraph{#1}\mbox{}\\} + +\usepackage{tocloft} + +\usepackage[ +backend=biber, +style=alphabetic, +citestyle=authoryear +]{biblatex} +\addbibresource{source.bib} + + +%%%%%%%%%%%%%%%%% +% Title % +%%%%%%%%%%%%%%%%% +\title{% \vfill + %\vspace{-2.0cm} + Computer Graphics \\ Project 1 \\ Subdividing Surfaces} + + +\author{ \\\\ Antonio Martinez Casadesus \\ 5103414 \\ \href{mailto:acasadesus@stud.hs-bremen.de}{acasadesus @ stud.hs-bremen.de} +\\ + \and \\\\ Pascal Syma \\ 5097056 \\ \href{mailto:psyma@stud.hs-bremen.de}{psyma @ stud.hs-bremen.de}\\ } +\date{ \today} +\parindent 0pt +\parskip 1ex + +\pagenumbering{roman} +\begin{document} + +\graphicspath{ {./images/} } +\maketitle +\begin{center} + +\begin{figure}[H] +\centering +\includegraphics[scale=0.7]{images/example/a4.png} +\end{figure} + +Computer Graphics \\ Prof. Dr. Martin Hering-Bertram \\ SoSe 2021 \\ Gruppe 7 \\~\\ Source code: \\ \href{https://git.syma.dev/Pascal/cg/tree/master/hw04}{https://git.syma.dev / Pascal/cg} +\end{center} + +\lstset{literate=% + {Ö}{{\"O}}1 + {Ä}{{\"A}}1 + {Ü}{{\"U}}1 + {ß}{{\ss}}2 + {ü}{{\"u}}1 + {ä}{{\"a}}1 + {ö}{{\"o}}1 +} + + +%%%%%%%%%%%%%%%%%%%%%%%%%% +% Inhaltsverzeichnis % +%%%%%%%%%%%%%%%%%%%%%%%%% +\newpage +\tableofcontents + +\clearpage +\listoffigures +\listoftables +\listofcodes + + +\clearpage + +\section{Abstract} +\pagenumbering{arabic} +\input{_Abstract} + +\section{Fundamentals} +\input{_Fundamentals} + +\section{Algorithms} +\input{_Algorithms} + +\section{Implementation} +\input{_Implementation} + +\clearpage +\section{Examples} +\input{_Examples} + +\clearpage +\section{Conclusions} +\input{_Conclusions} + +\printbibliography[title={References},heading=bibnumbered] + +\end{document} \ No newline at end of file diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/documentation/images/example/a2.png b/hw04/documentation/images/example/a2.png new file mode 100644 index 0000000..070f413 --- /dev/null +++ b/hw04/documentation/images/example/a2.png Binary files differ diff --git a/hw04/documentation/images/example/a3.png b/hw04/documentation/images/example/a3.png new file mode 100644 index 0000000..5984c6c --- /dev/null +++ b/hw04/documentation/images/example/a3.png Binary files differ diff --git a/hw04/documentation/images/example/a4.png b/hw04/documentation/images/example/a4.png new file mode 100644 index 0000000..9cea993 --- /dev/null +++ b/hw04/documentation/images/example/a4.png Binary files differ diff --git a/hw04/documentation/images/example/b0.png b/hw04/documentation/images/example/b0.png new file mode 100644 index 0000000..7d484d2 --- /dev/null +++ b/hw04/documentation/images/example/b0.png Binary files differ diff --git a/hw04/documentation/images/example/b1.png b/hw04/documentation/images/example/b1.png new file mode 100644 index 0000000..3c9d514 --- /dev/null +++ b/hw04/documentation/images/example/b1.png Binary files differ diff --git a/hw04/documentation/images/example/b2.png b/hw04/documentation/images/example/b2.png new file mode 100644 index 0000000..35beda4 --- /dev/null +++ b/hw04/documentation/images/example/b2.png Binary files differ diff --git a/hw04/documentation/images/example/b3.png b/hw04/documentation/images/example/b3.png new file mode 100644 index 0000000..fe89454 --- /dev/null +++ b/hw04/documentation/images/example/b3.png Binary files differ diff --git a/hw04/documentation/images/example/b4.png b/hw04/documentation/images/example/b4.png new file mode 100644 index 0000000..aec3742 --- /dev/null +++ b/hw04/documentation/images/example/b4.png Binary files differ diff --git a/hw04/documentation/images/example/c0.png b/hw04/documentation/images/example/c0.png new file mode 100644 index 0000000..3b26682 --- /dev/null +++ b/hw04/documentation/images/example/c0.png Binary files differ diff --git a/hw04/documentation/images/example/c1.png b/hw04/documentation/images/example/c1.png new file mode 100644 index 0000000..88565b1 --- /dev/null +++ b/hw04/documentation/images/example/c1.png Binary files differ diff --git a/hw04/documentation/images/example/c2.png b/hw04/documentation/images/example/c2.png new file mode 100644 index 0000000..1948a7a --- /dev/null +++ b/hw04/documentation/images/example/c2.png Binary files differ diff --git a/hw04/documentation/images/example/c3.png b/hw04/documentation/images/example/c3.png new file mode 100644 index 0000000..3423428 --- /dev/null +++ b/hw04/documentation/images/example/c3.png Binary files differ diff --git a/hw04/documentation/images/example/c4.png b/hw04/documentation/images/example/c4.png new file mode 100644 index 0000000..89cb07f --- /dev/null +++ b/hw04/documentation/images/example/c4.png Binary files differ diff --git a/hw04/documentation/main.tex b/hw04/documentation/main.tex new file mode 100644 index 0000000..fdd09d6 --- /dev/null +++ b/hw04/documentation/main.tex @@ -0,0 +1,136 @@ +\documentclass{article} + +\usepackage{svg} +\usepackage{amssymb} +\usepackage[british]{babel} +\usepackage[utf8]{inputenc} +\usepackage{amsmath,amssymb} +\usepackage{parskip} +\usepackage{graphicx} +\usepackage{listings} +\usepackage{caption} +\DeclareCaptionType{code}[Codeblock][List of Codeblocks] +\usepackage[a]{} +\usepackage{float} +\usepackage{hyperref} +\usepackage{tcolorbox} +\usepackage{datatool}% http://ctan.org/pkg/datatool +\newcommand{\sortitem}[1]{% + \DTLnewrow{list}% Create a new entry + \DTLnewdbentry{list}{description}{#1}% Add entry as description +} +\newenvironment{sortedlist}{% + \DTLifdbexists{list}{\DTLcleardb{list}}{\DTLnewdb{list}}% Create new/discard old list +}{% + \DTLsort{description}{list}% Sort list + \begin{itemize}% + \DTLforeach*{list}{\theDesc=description}{% + \item \theDesc}% Print each item + \end{itemize}% +} +\usepackage{tikz} +\newcommand*\circled[1]{\tikz[baseline=(char.base)]{ + \node[shape=circle,fill=red, text=white, draw=red,inner sep=2pt,font=\bfseries] (char) {#1};}} + +\usepackage[a4paper, textwidth=15cm]{geometry} + +\usepackage{minted} +\usemintedstyle{monokai} +\lstset{language=C++} +% load package with ``framed'' and ``numbered'' option. +\usepackage[framed,numbered,autolinebreaks,useliterate]{mcode} +% something NOT relevant to the usage of the package. +\usepackage{url} + +\newcommand{\inCode}[1]{\small\colorbox{black!70}{\mintinline{Cirru}{#1}}} +\newcommand{\paragraphnl}[1]{\paragraph{#1}\mbox{}\\} + +\usepackage{tocloft} + +\usepackage[ +backend=biber, +style=alphabetic, +citestyle=authoryear +]{biblatex} +\addbibresource{source.bib} + + +%%%%%%%%%%%%%%%%% +% Title % +%%%%%%%%%%%%%%%%% +\title{% \vfill + %\vspace{-2.0cm} + Computer Graphics \\ Project 1 \\ Subdividing Surfaces} + + +\author{ \\\\ Antonio Martinez Casadesus \\ 5103414 \\ \href{mailto:acasadesus@stud.hs-bremen.de}{acasadesus @ stud.hs-bremen.de} +\\ + \and \\\\ Pascal Syma \\ 5097056 \\ \href{mailto:psyma@stud.hs-bremen.de}{psyma @ stud.hs-bremen.de}\\ } +\date{ \today} +\parindent 0pt +\parskip 1ex + +\pagenumbering{roman} +\begin{document} + +\graphicspath{ {./images/} } +\maketitle +\begin{center} + +\begin{figure}[H] +\centering +\includegraphics[scale=0.7]{images/example/a4.png} +\end{figure} + +Computer Graphics \\ Prof. Dr. Martin Hering-Bertram \\ SoSe 2021 \\ Gruppe 7 \\~\\ Source code: \\ \href{https://git.syma.dev/Pascal/cg/tree/master/hw04}{https://git.syma.dev / Pascal/cg} +\end{center} + +\lstset{literate=% + {Ö}{{\"O}}1 + {Ä}{{\"A}}1 + {Ü}{{\"U}}1 + {ß}{{\ss}}2 + {ü}{{\"u}}1 + {ä}{{\"a}}1 + {ö}{{\"o}}1 +} + + +%%%%%%%%%%%%%%%%%%%%%%%%%% +% Inhaltsverzeichnis % +%%%%%%%%%%%%%%%%%%%%%%%%% +\newpage +\tableofcontents + +\clearpage +\listoffigures +\listoftables +\listofcodes + + +\clearpage + +\section{Abstract} +\pagenumbering{arabic} +\input{_Abstract} + +\section{Fundamentals} +\input{_Fundamentals} + +\section{Algorithms} +\input{_Algorithms} + +\section{Implementation} +\input{_Implementation} + +\clearpage +\section{Examples} +\input{_Examples} + +\clearpage +\section{Conclusions} +\input{_Conclusions} + +\printbibliography[title={References},heading=bibnumbered] + +\end{document} \ No newline at end of file diff --git a/hw04/documentation/mcode.sty b/hw04/documentation/mcode.sty new file mode 100644 index 0000000..46cc001 --- /dev/null +++ b/hw04/documentation/mcode.sty @@ -0,0 +1,291 @@ +%% +%% This is file `mcode.sty' +%% +%% It is supposed to help you easily include MATLAB source code +%% into LaTeX document, but have it nicely highlighted, using +%% the great listings package. +%% +%% PLEASE NOTE that this package does nothing but save you from +%% figuring out some configurations in setting up the LISTINGS +%% package. ALL the work is done by that package! Thus, please +%% refer your questions to the listings package documentation. +%% +%% Usage: You have three ways of including your MATLAB code. As +%% environment, as inline object and directly from an external +%% file. +%% +%% 1) Environment: +%% +%% \begin{lstlisting} +%% YOUR CODE HERE +%% \end{lstlisting} +%% +%% +%% 2) Inline object*: +%% +%% Bla bla \mcode{CODEFRAGMENT} bla bla. +%% +%% +%% 3) Include external file (in environment form) +%% +%% \lstinputlisting{YOUR-FILE.m} +%% +%% +%% For your convenience this package has the following options: +%% +%% - bw if you intend to print the document (highlighting done +%% via text formatting (bold, italic) and shades of gray) +%% +%% - numbered if you want line numbers +%% +%% - autolinebreaks if you want the package to automatically +%% wrap your code. This is buggy as it may well break +%% break syntax and it doesn't work well with comments. +%% You REALLY should wrap your code manually. +%% +%% - useliterate if you want some characters / relations in +%% your code to be replace with something more readable. +%% Example: ~= becomes $\neq$, >= becomes $\geq$, delta +%% becomes $\delta$ and so on. +%% +%% - framed if you want a frame around the source code blocks +%% +%% - final if you have ``gloablly'' set the draft option, the +%% listings package will not output the code at all. to +%% force it to do so anyway, load this package with the +%% final option (passes the ``final'' on to listings). +%% +%% For example, you may use \usepackage[numbered,framed]{mcode} +%% in your document preamble. +%% +%% * If you want to place some inline code in a footnote, use +%% \mcodefn{} instead (this will reduce the font size a bit). +%% +%% Note: Inside code blocks you can escape to LaTeX text mode +%% using §...§. For ex. §text and some math: $x^2$§, which is +%% especially useful in comments for putting nicely typeset +%% equations etc. To get the same colour/style as in the rest +%% of the comment use \mcommentfont, i.e. §\mcommentfont $x^2$§ +%% +%% To change the font used, edit the first line in the "custo- +%% mise below" section. And feel free to edit other things as +%% well. Refer to the documentation of the listings package to +%% see what else you could do. If an extra small font is re- +%% quired, use {\fontfamily{pcr}\fontsize{3}{4.6}\selectfont} +%% in the definition of \lstbasicfont. +%% +%% Author: +%% Florian Knorn | florian@knorn.org | www.florian-knorn.com +%% +%% Version history: +%% 2.5 -- Renamed internal variables (thx S. Kranenbarg!) +%% 2.4 -- Added \mcodefn{} command (thx Tony Almeida!) +%% 2.3 -- More keywords (thx Dominik Wild!) +%% 2.2 -- Bugfix (thx Willi Gerbig!) +%% 2.1 -- Finally automatic detection between end and end +%% 2.0 -- New options for line breaking and literate prog. +%% 1.8 -- Fixed typo in documentation regarding §...§ +%% 1.7 -- Added MATLAB block comment syntax %{ ...... %} +%% 1.6 -- Added some infos, dealing with keyword ``end'' +%% 1.5 -- Tweaked check to see wether textcomp is loaded +%% 1.4 -- Fixed misconfig (mathescape now set to false) +%% 1.3 -- Purely cosmetic (tabs replaced by spaces) +%% 1.2 -- Added \lstset{showstringspaces=false} +%% 1.1 -- Added \mcode command and [final] option +%% 1.0 -- Release + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% D O N ' T T O U C H T H I S % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\def\fileversion{2.5} +\def\filedate{2014/03/06} + +\typeout{-- Package: `mcode' \fileversion\space <\filedate> --} +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{mcode}[\filedate\space\fileversion] + +% for bw-option +\newif\ifmcode@bw +\DeclareOption{bw}{\mcode@bwtrue} + +% numbered option +\newif\ifmcode@numbered +\DeclareOption{numbered}{\mcode@numberedtrue} + +% final option +\newif\ifmcode@final +\DeclareOption{final}{\mcode@finaltrue} + +% autolinebreaks option +\newif\ifmcode@autolinebreaks +\DeclareOption{autolinebreaks}{\mcode@autolinebreakstrue} + +% literate programming (replace certain characters/relations +\newif\ifmcode@useliterate +\DeclareOption{useliterate}{\mcode@useliteratetrue} + +% framed option +\newif\ifmcode@framed +\DeclareOption{framed}{\mcode@framedtrue} + +\DeclareOption*{% default + \PackageWarning{mcode}{Unknown option `\CurrentOption' !}% +} +\ProcessOptions + +\ifmcode@bw\typeout{ - settings optimized for printing (bw formating)} +\else\typeout{ - settings optimized for display (colour formating)}\fi +\ifmcode@numbered\typeout{ - line numbering enabled}\else\fi +\ifmcode@useliterate\typeout{ - literate programming (character replacements) enabled}\else\fi +\ifmcode@autolinebreaks\typeout{ - automatic line breaking enabled (careful, buggy!)}\else\fi +\ifmcode@framed\typeout{ - framed listings}\else\fi + +% This command allows you to typeset syntax highlighted Matlab +% code ``inline''. The font size \small seems to look best... +\newcommand{\mcode}[1]{\lstinline[basicstyle=\lstbasicfont\small]|#1|} + +% Same, but for footnotes +\newcommand{\mcodefn}[1]{\lstinline[basicstyle=\lstbasicfont\footnotesize]|#1|} + +% check if color command exists +\ifx\color\undefined% + \RequirePackage{xcolor}% +\fi + +% check if listings has been loaded +\ifx\lstset\undefined% + \ifmcode@final + \RequirePackage[final]{listings} + \else + \RequirePackage{listings} + \fi +\fi + +% Check if textcomp has been loaded (this package is needed for +% upright quotes '' (instead of typographic ones `´)... +\ifx\textquotesingle\undefined% + \RequirePackage{textcomp}% +\fi + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% C U S T O M I S E B E L O W % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% --------------------------------------------------------------------------------- +% default font +\def\lstbasicfont{\fontfamily{pcr}\selectfont\footnotesize} + +% --------------------------------------------------------------------------------- +% matlat languate definition +\lstdefinelanguage{matlabfloz}{% + alsoletter={...},% + morekeywords={% % keywords + break,case,catch,continue,elseif,else,end,% + for,function,global,if,otherwise,persistent,% + return,switch,try,while,methods,properties,% + events,classdef,...},% + comment=[l]\%, % comments + morecomment=[l]..., % comments + morecomment=[s]{\%\{}{\%\}}, % block comments + morestring=[m]' % strings +}[keywords,comments,strings]% + +% --------------------------------------------------------------------------------- +% general definitions +\lstset{% + basicstyle={\lstbasicfont}, % set font + showstringspaces=false, % do not emphasize spaces in strings + tabsize=4, % number of spaces of a TAB + mathescape=false,escapechar=§, % escape to latex with §...§ + upquote=true, % upright quotes + aboveskip={1.5\baselineskip}, % a bit of space above listings + columns=fixed % nice spacing +} + +% --------------------------------------------------------------------------------- +% define colours and styles +\ifmcode@bw % use font formating and gray 'colors' + \def\mcommentfont{\color[gray]{.75}\itshape} %comments light gray and italic + \lstset{language=matlabfloz, % use our version of highlighting + keywordstyle=\bfseries, % keywords in bold + commentstyle=\mcommentfont, % comments + stringstyle=\color[gray]{0.5} % strings darker gray + } +\else% notbw => use colors : ) + \def\mcommentfont{\color[rgb]{.133,.545,.133}} %comments in green + \lstset{language=matlabfloz, % use our version of highlighting + keywordstyle=\color[rgb]{0,0,1}, % keywords in blue + commentstyle=\mcommentfont, % comments + stringstyle=\color[rgb]{.627,.126,.941} % strings in purple + } +\fi%bw + +% --------------------------------------------------------------------------------- +% automatic line breaking --- warning, this is buggy and +% doesn't break comments correctly! +\ifmcode@autolinebreaks + \newsavebox{\lbreakdots}\sbox{\lbreakdots}{\lstbasicfont\mcommentfont...} + \lstset{breaklines=true,breakatwhitespace=true,prebreak=\usebox{\lbreakdots}} +\fi + +% --------------------------------------------------------------------------------- +% literate replacements +% the following is for replacing some matlab relations like >= or ~= +% by the corresponding LaTeX symbols, which are much easier to read ... +\ifmcode@useliterate + \lstset{% + literate=% + {~}{{$\neg$}}1 % \neg + {<=}{{\tiny$\leq$}}1 % \leq + {>=}{{\tiny$\geq$}}1 % \geq + {~=}{{\tiny$\neq$}}1 % \neq + {delta}{{\tiny$\Delta$}}1 % \Delta + {(end)}{\lstbasicfont (end)}{5} % black ``end'' when indexing last vector element + {({ }end)}{\lstbasicfont ({ }end)}{6} + {(end{ })}{\lstbasicfont (end{ })}{6} + {({ }end{ })}{\lstbasicfont ({ }end{ })}{7} + {:end}{\lstbasicfont :end}{4} + {:{ }end}{\lstbasicfont :{ }end}{5} + {end:}{\lstbasicfont end:}{4} + {end{ }:}{\lstbasicfont end{ }:}{5} + {,end}{\lstbasicfont ,end}{4} + {,{ }end}{\lstbasicfont ,{ }end}{5} + } +\else + \lstset{% + literate=% + {(end)}{\lstbasicfont (end)}{5} % black ``end'' when indexing last vector element + {({ }end)}{\lstbasicfont ({ }end)}{6} + {(end{ })}{\lstbasicfont (end{ })}{6} + {({ }end{ })}{\lstbasicfont ({ }end{ })}{7} + {:end}{\lstbasicfont :end}{4} + {:{ }end}{\lstbasicfont :{ }end}{5} + {end:}{\lstbasicfont end:}{4} + {end{ }:}{\lstbasicfont end{ }:}{5} + {,end}{\lstbasicfont ,end}{4} + {,{ }end}{\lstbasicfont ,{ }end}{5} + } +\fi%literates + +% --------------------------------------------------------------------------------- +% line numbering +\ifmcode@numbered% numbered option + \lstset{% + numbersep=3mm, numbers=left, numberstyle=\tiny, % number style + } +\fi + +\ifmcode@framed% framed option + \lstset{% + frame=single, % frame + } + \ifmcode@numbered% + \lstset{% + framexleftmargin=6mm, xleftmargin=6mm % tweak margins + } + \fi +\fi + +\endinput +%% End of file `mcode.sty'. diff --git a/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf new file mode 100644 index 0000000..c2cb959 --- /dev/null +++ b/hw04/CG_SS21_Project_1_acasadesus_psyma.pdf Binary files differ diff --git a/hw04/README.md b/hw04/README.md new file mode 100644 index 0000000..66304b5 --- /dev/null +++ b/hw04/README.md @@ -0,0 +1,2 @@ +Git-Repo: +https://git.syma.dev/Pascal/cg/tree/master/hw04 \ No newline at end of file diff --git a/hw04/documentation/Bowl.obj b/hw04/documentation/Bowl.obj new file mode 100644 index 0000000..1064228 --- /dev/null +++ b/hw04/documentation/Bowl.obj @@ -0,0 +1,44 @@ +v -0.801131 1.000000 0.801131 +v -0.551457 0.190130 -0.551457 +v -0.801131 1.000000 -0.801131 +v 1.000000 1.000000 1.000000 +v -0.688348 -0.321413 0.688348 +v 0.688348 -0.321413 0.688348 +v -1.000000 1.000000 1.000000 +v -0.688348 -0.321413 -0.688348 +v 0.688348 -0.321413 -0.688348 +v 1.000000 1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 0.801131 1.000000 0.801131 +v 0.801131 1.000000 -0.801131 +v 0.551457 0.190130 0.551457 +v 0.551457 0.190130 -0.551457 +v -0.551457 0.190130 0.551457 +f 1 2 3 +f 4 5 6 +f 7 8 5 +f 9 5 8 +f 10 6 9 +f 11 9 8 +f 1 4 12 +f 12 10 13 +f 3 7 1 +f 13 11 3 +f 2 14 15 +f 12 16 1 +f 3 15 13 +f 13 14 12 +f 1 16 2 +f 4 7 5 +f 7 11 8 +f 9 6 5 +f 10 4 6 +f 11 10 9 +f 1 7 4 +f 12 4 10 +f 3 11 7 +f 13 10 11 +f 2 16 14 +f 12 14 16 +f 3 2 15 +f 13 15 14 diff --git a/hw04/documentation/Vase.obj b/hw04/documentation/Vase.obj new file mode 100644 index 0000000..0e6f099 --- /dev/null +++ b/hw04/documentation/Vase.obj @@ -0,0 +1,104 @@ +v -1.000000 0.431230 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 0.431230 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 0.431230 1.000000 +v 0.710240 1.000000 -0.710240 +v 0.940032 2.084522 0.940032 +v 0.710240 1.000000 0.710240 +v -0.710240 1.000000 -0.710240 +v -0.710240 1.000000 0.710240 +v -0.476062 2.889306 0.476062 +v -0.940032 2.084522 0.940032 +v -0.940032 2.084522 -0.940032 +v 0.940032 2.084522 -0.940032 +v 0.220728 2.889306 -0.220728 +v 0.583217 1.562964 0.583217 +v 0.220728 2.889306 0.220728 +v 0.476062 2.889306 -0.476062 +v 0.476062 2.889306 0.476062 +v -0.476062 2.889306 -0.476062 +v -0.220728 2.889306 -0.220728 +v -0.220728 2.889306 0.220728 +v 0.654179 0.554739 0.654179 +v -0.583217 -0.453487 0.583217 +v -0.654179 0.554739 0.654179 +v -0.583217 1.562964 -0.583217 +v -0.583217 1.562964 0.583217 +v 0.583217 1.562964 -0.583217 +v -0.583217 -0.453487 -0.583217 +v 0.583217 -0.453487 0.583217 +v 0.583217 -0.453487 -0.583217 +v -0.654179 0.554739 -0.654179 +v 0.654179 0.554739 -0.654179 +f 1 2 3 +f 4 5 2 +f 6 7 5 +f 8 3 7 +f 5 3 2 +f 9 10 11 +f 12 1 13 +f 9 4 12 +f 11 6 9 +f 13 8 11 +f 10 14 15 +f 13 16 12 +f 11 15 13 +f 12 17 9 +f 18 19 20 +f 16 21 17 +f 17 22 10 +f 15 23 16 +f 24 14 25 +f 18 23 24 +f 20 21 18 +f 25 22 20 +f 26 27 28 +f 25 29 24 +f 20 30 25 +f 24 31 18 +f 32 33 34 +f 35 34 36 +f 36 33 26 +f 28 32 35 +f 30 35 29 +f 31 26 19 +f 29 36 31 +f 19 28 30 +f 1 4 2 +f 4 6 5 +f 6 8 7 +f 8 1 3 +f 5 7 3 +f 9 17 10 +f 12 4 1 +f 9 6 4 +f 11 8 6 +f 13 1 8 +f 10 22 14 +f 13 15 16 +f 11 10 15 +f 12 16 17 +f 18 31 19 +f 16 23 21 +f 17 21 22 +f 15 14 23 +f 24 23 14 +f 18 21 23 +f 20 22 21 +f 25 14 22 +f 26 33 27 +f 25 30 29 +f 20 19 30 +f 24 29 31 +f 32 27 33 +f 35 32 34 +f 36 34 33 +f 28 27 32 +f 30 28 35 +f 31 36 26 +f 29 35 36 +f 19 26 28 diff --git a/hw04/documentation/_Abstract.tex b/hw04/documentation/_Abstract.tex new file mode 100644 index 0000000..fc3eb59 --- /dev/null +++ b/hw04/documentation/_Abstract.tex @@ -0,0 +1 @@ +This paper shows the Chaikin's, linear and loop subdivision algorithm. The Chaikin's algorithm is briefly discussed in the beginning using the vector template as well as the cubic scheme to subdivide a curve and making it smoother in the process. Linear Subdivision is used to add more triangles to the mesh, raising the resolution, without changing the form of the given mesh. The last algorithm that is discussed in this paper is the loop subdivision algorithm. Using the loop subdivision algorithm not only the resolution of the given mesh becomes higher, it also smooths out edges and corners leading to realistic and smooth surfaces. \ No newline at end of file diff --git a/hw04/documentation/_Algorithms.tex b/hw04/documentation/_Algorithms.tex new file mode 100644 index 0000000..81d5b33 --- /dev/null +++ b/hw04/documentation/_Algorithms.tex @@ -0,0 +1,80 @@ +\subsection{Chaikin’s Algorithm} +To smooth out a curve one possibility is the Chaikin's algorithm (\cite{DBLP:journals/cvgip/Chaikin74}). Using a set of points the Chaikin's algorithm can be used to generate the mask M which then is multiplied with the set of points to calculate the new set. This process can be repeated indefinitely to further smooth out the curve by constantly adding new points along the edges in perspective to the adjacent points. This algorithm was based on tests and not on mathematical proofs. + +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + & 3/4 & 1/4 \\ + & 1/4 & 3/4 \\ + & & 3/4 & 1/4 \\ + & & 1/4 & 3/4 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Interpolating Cubic Subdivision} +While Chaikin's Algorithm is fairly accurate the Interpolating Cubic Subdivision scheme is more detailed and mathematical proven. To use this method the mask needs to be changed to the following. +\begin{equation} +M = +\begin{bmatrix} + 1 \\ + 1/2 & 1/2 \\ + 1/8 & 3/4 & 1/8 \\ + & 1/2 & 1/2 \\ + & 1/8 & 3/4 & 1/8 \\ + & & 1/2 & 1/2 \\ + & & & & \ddots \\ + & & & & & 1\\ + +\end{bmatrix} +\end{equation} + +\subsection{Linear Loop subdivision - Edge midpoints} +The Linear Loop subdivision (\cite{loop1987smooth}) adds a point at the middle of every edge and then reconnects every corner vertex with its two new adjacent mid points and then all the mid new calculated mid points as another face. This effectively creates three new faces therefore increasing the Resolution of the mesh. This can also be done indefinitely, always increasing the amount of triangles in a mesh. + +\begin{figure}[H] + \centering + \includesvg[]{images/LinearLoop.svg} + \caption{Linear Loop subdivision - Edge midpoints} +\end{figure} +\subsection{Loop subdivision - Vertex mask} +Loop subdivision does not only add more triangles to the mesh but also smooths out the shape. To do so we need to know the exact neighbours of each face. A face is considered to be a neighbour of another face if they share exactly two vertices. In order to calculate the new vertices e0 needs to be calculated using the four different points of the main triangle and the one new point from the neighbouring triangle. + + +\begin{figure}[H] + \centering + \includesvg[]{images/E0.svg} + \caption{Loop subdivision - Edge mask} + \label{fig:edge-mask} +\end{figure} + +\begin{equation} + e0 = \frac{3}{8}(V0 + V1) + \frac{1}{8}(V2 + V3) +\end{equation} + +Now this is done for every side with the according neighbour of the triangle face. For the next step it is also needed $\beta (n)$, where n is the valence of the vertex. + +\begin{equation} \label{eq:alpha-beta} +\begin{split} + &\alpha(n) = \frac{3}{8} + (\frac{3}{8} + \frac{1}{4} \cos{\frac{2\pi}{n}})^2 \\ + &\beta(n) = \frac{8}{5} \alpha(n) - \frac{3}{5} +\end{split} +\end{equation} + +Using $\beta (n)$ of every original vertex is used to calculate the new coordinates using the following formula. + +\begin{equation} + v' = \beta (n)v + \frac{1 - \beta (n)}{n} \sum_{i=0}^n e'_i +\end{equation} + +\begin{figure}[H] + \centering + \includesvg[]{E012.svg} + \caption{Loop subdivision - Vertex mask} + \label{fig:edge-mask-all} +\end{figure} + diff --git a/hw04/documentation/_Conclusions.tex b/hw04/documentation/_Conclusions.tex new file mode 100644 index 0000000..4544ee9 --- /dev/null +++ b/hw04/documentation/_Conclusions.tex @@ -0,0 +1 @@ +Loop subdivision is an easy and fast way to smooth out a mesh or to higher its resolution. Seemingly round objects can be modeled as bricks using a lot less computational power whilst modeling and using this method actually become closer to being round then it could be modeled in thousands of hours. And while the loop subdivision is a key piece to model seemingly round surfaces the linear loop subdivision also has its place as enabling actual mesh displacement to display photo realistic materials in actual geometry. \ No newline at end of file diff --git a/hw04/documentation/_Examples.tex b/hw04/documentation/_Examples.tex new file mode 100644 index 0000000..161642f --- /dev/null +++ b/hw04/documentation/_Examples.tex @@ -0,0 +1,40 @@ +In order to test the implementation two low-poly 3D models are subdivided multiple times. The figures become smoother with every iteration as seen in figure \ref{fig:ex-subdiv-gra}. + +\begin{figure}[H] +\begin{tabular}{c|cccc} + \multicolumn{5}{c}{Subdivision count} \\ + 0 (original) & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + \includegraphics[width=26mm]{images/example/a0.png} & \includegraphics[width=26mm]{images/example/a1.png} &\includegraphics[width=26mm]{images/example/a2.png} &\includegraphics[width=26mm]{images/example/a3.png} &\includegraphics[width=26mm]{images/example/a4.png} \\ + \multicolumn{5}{c}{(a) Vase} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/b0.png} & \includegraphics[width=26mm]{images/example/b1.png} &\includegraphics[width=26mm]{images/example/b2.png} &\includegraphics[width=26mm]{images/example/b3.png} &\includegraphics[width=26mm]{images/example/b4.png} \\ + \multicolumn{5}{c}{(b) Vase with wireframe} \\ \hline \\ + + \includegraphics[width=26mm]{images/example/c0.png} & \includegraphics[width=26mm]{images/example/c1.png} &\includegraphics[width=26mm]{images/example/c2.png} &\includegraphics[width=26mm]{images/example/c3.png} &\includegraphics[width=26mm]{images/example/c4.png} \\ + \multicolumn{5}{c}{(c) Bowl} \\ +\end{tabular} +\caption{Increasing subdivisions (graphical)} +\label{fig:ex-subdiv-gra} +\end{figure} + +\begin{table}[H] +\centering +\begin{tabular}{r|ccccc} + &\multicolumn{5}{c}{Subdivision count} \\ + &0 & 1 & 2 & 3 & 4 \\[6pt] \hline \\ + + &\multicolumn{5}{c}{(a) Vase} \\[6pt] + Triangles & 68 & 272 & 1088 & 4352 & 17408 \\ + Vertices & 36 & 138 & 546 & 2178 & 8706 \\ + \hline \\ + + &\multicolumn{5}{c}{(c) Bowl} \\[6pt] + Triangles & 28 & 112 & 448 & 1792 & 7168 \\ + Vertices & 16 & 58 & 226 & 898 & 3586 \\ + +\end{tabular} +\caption{Increasing subdivisions (numerical)} +\label{fig:ex-subdiv-num} +\end{table} + +The amount of triangles are constantly quadrupled with each step, this is due to the way of dividing existing triangles into four smaller ones. This is not the case with the vertices, since multiple triangles can share the same vertex. \ No newline at end of file diff --git a/hw04/documentation/_Fundamentals.tex b/hw04/documentation/_Fundamentals.tex new file mode 100644 index 0000000..fc5f91a --- /dev/null +++ b/hw04/documentation/_Fundamentals.tex @@ -0,0 +1,6 @@ +\paragraphnl{Meshes} +Meshes are saved in the Wavefront OBJ format. +\paragraphnl{Vertex} +For each vertex the file contains the key \inCode{v} followed by three floating numbers for the coordinates. +\paragraphnl{Triangle} +And for each triangle face the key \inCode{f} followed by three integers, representing the index of its vertices. diff --git a/hw04/documentation/_Implementation.tex b/hw04/documentation/_Implementation.tex new file mode 100644 index 0000000..f148267 --- /dev/null +++ b/hw04/documentation/_Implementation.tex @@ -0,0 +1,102 @@ +All the implementations are done in C++ using QT5 for the UI and OpenGL for rendering the meshes. + +\subsection{3D-Mesh} +The following three classes are sufficient as data structure: + +\paragraph{Vertex} is represented as a three-dimensional point and a valence. + +\paragraph{Triangle} consists of three vertices whose index is taken from the list of vertices of the mesh. For the subdivision, the three neighbors and the edge vertex are also stored, each also as index. + +\paragraph{Mesh} stores all triangles and vertices in lists that can be accessed. + +To be able to work universally with different models, the models can be loaded as Wavefront OBJ, with the restriction to support only triangles, other commands (like normals or textures) are ignored. + +\subsubsection{Connectivity algorithm} +To find the neighboring triangles, it is required to iterate over all other triangles for each triangle. Between the triangles of the inner and outer loop, the three vertices are now checked for matching. If two vertices match, the inner triangle is stored as the neighbor of the outer one. To simplify further use, the edge at which this neighbor is located is stored. For this the index of the last, not matching, vertex can be used. If the neighbor is located at the edge between the second and third vertex, it is stored as the first neighbor. + +And the valence of the vertices is increased at each occurrence. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={72-109}]{code/Mesh.cpp} +\caption{Mesh.cpp - Connectivity algorithm} +\end{code} + +\subsubsection{Loop Subdivision using the edge midpoint} +For the Edge Midpoint Subdivision, each existing triangle is iterated over and the midpoints between the vertices are calculated. This midpoint between two vertices A and B can be calculated as follows: +\begin{equation} + \begin{bmatrix} + A.x + B.x \\ + A.y + B.y \\ + A.z + B.z + \end{bmatrix} + \mathbin{/} + 2 + \end{equation} +If a vertex with the same coordinates already exists, the index of the existing one is used in the following, otherwise the new one is added to the vertex list. Based on the new vertices, new triangles can now be created and the original one can be adjusted. +\begin{code}[H] +\lstinputlisting[language=C++, linerange={208-256}]{code/Mesh.cpp} +\caption{Mesh.cpp - Subdivide edge midpoint} +\label{apx:edge-subdiv} +\end{code} + + +\subsubsection{Loop Subdivision} +\paragraphnl{Edge mask} +Based on the neighboring triangles it is now possible to calculate the edge masks (see figure \ref{fig:edge-mask}). +Similar to the previous method, it is again possible to check if an edge mask has already been calculated by the neighboring triangle. Since the loop is incremental, it is sufficient to query whether the neighbor is before or after the current triangle in the mesh list. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={117-152}]{code/Mesh.cpp} +\caption{Mesh.cpp - Edge mask} +\end{code} + +\paragraphnl{Vertex mask} +With the functions $\alpha(n)$ and $\beta(n)$ (see equation \ref{eq:alpha-beta}) based on the valence $n$ of the vertices, the vertex mask can be calculated and the vertices can be moved. +Before relocating, each vertex must be multiplied by $\beta(n)$. Then the following equations can be applied (based on figure \ref{fig:edge-mask-all}): +\begin{equation} \label{eq:vertex-mask} +\begin{split} + &V0 \mathrel{+}= \frac{1 - \beta(V0.valence)}{V0.valence} * \frac{e1 + e2}{2} \\ + &V1 \mathrel{+}= \frac{1 - \beta(V1.valence)}{V1.valence} * \frac{e0 + e1}{2} \\ + &V2 \mathrel{+}= \frac{1 - \beta(V2.valence)}{V2.valence} * \frac{e2 + e0}{2} +\end{split} +\end{equation} + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={154-178,185-193}]{code/Mesh.cpp} +\caption{Mesh.cpp - Vertex mask} +\end{code} + +\paragraphnl{Replacing triangles} +After moving the original vertices, new triangles can be formed. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={196-204}]{code/Mesh.cpp} +\caption{Mesh.cpp - Replacing triangles} +\end{code} + +\subsection{GUI} +In the GUI, the user is supposed to import a 3D model \circled{2} via the file path \circled{1}, and if the import is successful, the other settings are unlocked. To clarify the subdivision it is possible to display the imported model in two different subdivision levels \circled{3}, which can be entered individually in \circled{4} and \circled{5}. It is also possible to display the inner model as a wireframe \circled{6}. The scaling of the models can be adjusted via a slider \circled{7}. + +\begin{figure}[H] + \centering + \includegraphics[scale=0.7]{images/GUI.png} + \caption{GUI - Overview} + \label{fig:gui} +\end{figure} + +The UI change events are subscribed to by the main process, which then applies the change in the next frame. + +\clearpage +\subsection{Rendering} +Since OpenGL does most of the work, rendering meshes is fairly straightforward. For each triangle, its vertices are passed along with the surface normal, which is calculated using the cross product. + +\begin{equation} + normal = (b - a) \times (c - a) +\end{equation} + +Depending on whether the edges or the faces are to be drawn, the primitives \inCode{GL_LINE_STRIP} or \inCode{GL_TRIANGLES} are used. + +\begin{code}[H] +\lstinputlisting[language=C++, linerange={37-51}]{code/oglwidget.cpp} +\caption{oglwidget.cpp - Draw Triangles} +\end{code} \ No newline at end of file diff --git a/hw04/documentation/code/Mesh.cpp b/hw04/documentation/code/Mesh.cpp new file mode 100644 index 0000000..e53f093 --- /dev/null +++ b/hw04/documentation/code/Mesh.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +// +// Created by Pascal on 17.05.2021. +// + +#include +#include +#include +#include +#include "Mesh.h" +#include "Util.h" + +using namespace std; + +Mesh::Mesh() { + this->tris = {}; + this->pts = {}; +} + + +void Mesh::saveData(const string& fileName) { + ofstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return; + } + + for (auto vert : pts) { + file << "v\t" << vert.p[0] << "\t" << vert.p[1] << "\t" << vert.p[2] << endl; + } + + for (auto triag : tris) { + file << "f\t" << triag.iv[0]+1 << "\t" << triag.iv[1]+1 << "\t" << triag.iv[2]+1 << endl; + } + + file.close(); +} + +bool Mesh::loadData(const string& fileName) { + ifstream file( fileName); + if (!file){ + cout << "error opening file" << endl; + return false; + } + string key; + while( file){ + //getline( file, line); + file >> key; + if (key == "v") { + // vertex + float x, y, z; + file >> x >> y >> z; + Vertex pVertex = *new Vertex(this, x, y, z); + pts.push_back(pVertex); + } else if (key == "f") { + // face (only triangles supported) + int a, b, c; + file >> a >> b >> c; + Tri pTriangle = *new Tri(this, a-1, b-1, c-1); + tris.push_back(pTriangle); + } + } + file.close(); + + tris.pop_back(); + + return true; +} + +void Mesh::connectivityAlgo() { + for (auto & pt : pts) { + pt.valence = 0; + } + + // Connectivity Algorithm + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + // Search for neighbour + for (int ti = 0; ti < tris.size(); ++ti) { + if (i == ti) continue; + Tri t = tris[ti]; + int count = 0; + int side = 0; + for (int x = 0; x < 3; ++x) { + bool hit = false; + for (int y : t.iv) { + if (triag.iv[x] == y) + hit = true; + } + if (hit) + count++; + else + side = x; + } + // if two points are the same, they are neighbours + if (count == 2) + triag.it[side] = ti; + } + + // increase valence for each vertex of triangle + ++pts[triag.iv[0]].valence; + ++pts[triag.iv[1]].valence; + ++pts[triag.iv[2]].valence; + + tris[i] = triag; + } +} + +void Mesh::subDivLoop(int count) { + for (int i = 0; i < count; ++i) { + this->subDivLoop(); + } +} + +void Mesh::subDivLoop() { + this->connectivityAlgo(); + + for (int i = 0; i < tris.size(); ++i) { + Tri triag = tris[i]; + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + for (int tI = 0; tI < 3; ++tI) { + Tri t = tris[triag.it[tI]]; + + // figure out, which neighbour I am to my neighbour + int otherEI = 0; + for (int j = 0; j < 3; ++j) { + if (t.it[j] == i) { + otherEI = j; + break; + } + } + if (i < triag.it[tI]) { + Vertex d = pts[t.iv[otherEI]]; + Vertex e = (1.0f / 8.0f) * + ((((tI == 1 || tI == 2) ? 3.0f : 1.0f) * a) + (((tI == 0 || tI == 2) ? 3.0f : 1.0f) * b) + + (((tI == 1 || tI == 0) ? 3.0f : 1.0f) * c) + d); + triag.ie[tI] = pts.size(); + pts.push_back(e); + } else { + // the calculated edge-mask is already present in the neighbour + + triag.ie[tI] = t.ie[otherEI]; + } + } + + tris[i] = triag; + } + + for (auto &pt : pts) { // multiply every vertex with beta + int n = pt.valence; // n = valence of v_i + if (n < 3) continue; + float beta = Util::beta_n(n); + pt *= beta; // v_i *= beta(n) + } + + // remember the original length, because it will grow + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + int ai = triag.iv[0]; + Vertex a = pts[ai]; + int bi = triag.iv[1]; + Vertex b = pts[bi]; + int ci = triag.iv[2]; + Vertex c = pts[ci]; + + + Vertex e[3] = {}; + + for (int tI = 0; tI < 3; ++tI) { + e[tI] = pts[triag.ie[tI]]; + } + +// cout << triag.ie[0] << " " << triag.ie[1] << " " << triag.ie[2] << endl; +// cout << e[0].p[0] << " " << e[0].p[1] << " " << e[0].p[2] << endl; +// cout << e[1].p[0] << " " << e[1].p[1] << " " << e[1].p[2] << endl; +// cout << e[2].p[0] << " " << e[2].p[1] << " " << e[2].p[2] << endl << endl; + + + a += (0.5f * ((1-Util::beta_n(a.valence)) / float(a.valence)) * (e[1] + e[2])); + b += (0.5f * ((1-Util::beta_n(b.valence)) / float(b.valence)) * (e[0] + e[2])); + c += (0.5f * ((1-Util::beta_n(c.valence)) / float(c.valence)) * (e[1] + e[0])); + + + pts[ai] = a; + pts[bi] = b; + pts[ci] = c; + + + triag.iv[0] = triag.ie[1]; + triag.iv[1] = triag.ie[0]; + triag.iv[2] = ci; + + tris[i] = triag; + + tris.push_back(*new Tri(this, triag.ie[1], triag.ie[2], triag.ie[0])); + tris.push_back(*new Tri(this, ai, triag.ie[2], triag.ie[1])); + tris.push_back(*new Tri(this, triag.ie[2], bi, triag.ie[0])); + } +} + +void Mesh::subDivEdgeMidpoint() { + + const unsigned long long int amountToSubDiv = tris.size(); + for (int i = 0; i < amountToSubDiv; ++i) { + Tri triag = tris[i]; + + Vertex a = pts[triag.iv[0]]; + Vertex b = pts[triag.iv[1]]; + Vertex c = pts[triag.iv[2]]; + + // calculate the midpoints of all edges + int ei0 = -1; + int ei1 = -1; + int ei2 = -1; + Vertex e0 = 0.5f * (a + b); + Vertex e1 = 0.5f * (c + b); + Vertex e2 = 0.5f * (a + c); + + // check if any of the midpoints if already a known vertex + for (int j = 0; j < pts.size(); ++j) { + if (pts[j] == e0) + ei0 = j; + if (pts[j] == e1) + ei1 = j; + if (pts[j] == e2) + ei2 = j; + } + if (ei0 == -1) { + ei0 = pts.size(); + pts.push_back(e0); + } + if (ei1 == -1) { + ei1 = pts.size(); + pts.push_back(e1); + } + if (ei2 == -1) { + ei2 = pts.size(); + pts.push_back(e2); + } + + tris.push_back(*new Tri(this, triag.iv[2], ei2, ei1)); + tris.push_back(*new Tri(this, triag.iv[1], ei0, ei1)); + tris.push_back(*new Tri(this, ei0, ei1, ei2)); + + + triag.iv[1] = ei0; + triag.iv[2] = ei2; + tris[i] = triag; + } +} + +Mesh* Mesh::copy() { + Mesh *copy = new Mesh(); + copy->drawWireframe = this->drawWireframe; + copy->drawOutline = this->drawOutline; + + for (auto vert : this->pts) { + copy->pts.push_back(*vert.copy(copy)); + } + for (auto tri : this->tris) { + copy->tris.push_back(*tri.copy(copy)); + } + + + return copy; +} diff --git a/hw04/documentation/code/oglwidget.cpp b/hw04/documentation/code/oglwidget.cpp new file mode 100644 index 0000000..9026d6f --- /dev/null +++ b/hw04/documentation/code/oglwidget.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021. Pascal Syma and Antonio Martinez Casadesus . +// All rights reserved. + +#include "oglwidget.h" +#include "src/Mesh.h" +#include +#include + +#define PI 3.14159265358979323846 +using namespace std; + +static double alpha = 45.0; // rotation angle + +/// Read-only mesh +Mesh *originalMesh; +/// Outer mesh +Mesh *parentMesh; +/// Inner mesh +Mesh *childMesh; + +bool init = false; +bool childWireframe = false; +bool drawParent = false; +int parentSubdivCount = 0; +int childSubdivCount = 1; +float scale = 2.0f; + +/// Draw a mesh. +/// \param mesh Mesh to draw +void drawMesh(Mesh mesh) { + glPolygonMode(GL_FRONT_AND_BACK, mesh.drawWireframe ? GL_LINE : GL_FILL); + if (mesh.drawWireframe) + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); + + glBegin(GL_TRIANGLES); + for (auto tri : mesh.tris) { + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + Vertex nvec = ((b - a) % (c - a)); + + glNormal3fv(nvec.p); + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + } + glEnd(); + if (!mesh.drawOutline) return; + + // draw the edges on top of the faces + for (auto tri : mesh.tris) { + glLineWidth(3); + glBegin( GL_LINE_STRIP); + Vertex a = mesh.pts[tri.iv[0]]; + Vertex b = mesh.pts[tri.iv[1]]; + Vertex c = mesh.pts[tri.iv[2]]; + + glVertex3fv(a.p); + glVertex3fv(b.p); + glVertex3fv(c.p); + + glEnd(); + } +} + +/// Draw the two meshes +void DrawTriag() { + if (!init) return; + + drawMesh(*childMesh); + if (drawParent) drawMesh(*parentMesh); + +} + +/// initialize Open GL lighting and projection matrix +void InitLightingAndProjection() // to be executed once before drawing +{ + // light positions and colors + GLfloat LightPosition1[4] = { 10, 5, 10, 0}; + GLfloat LightPosition2[4] = { -5, 5, -10, 0}; + GLfloat ColorRedish[4] = { 1.0, .8, .8, 1}; // white with a little bit of red + GLfloat ColorBlueish[4] = { .8, .8, 1.0, 1};// white with a little bit of blue + + glEnable( GL_DEPTH_TEST); // switch on z-buffer + glDepthFunc( GL_LESS); + + glShadeModel( GL_SMOOTH); // Gouraud shading + //glShadeModel( GL_FLAT); + + glEnable( GL_LIGHTING); // use lighting + glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1); // draw both sides + + // define and switch on light 0 + glLightfv( GL_LIGHT0, GL_POSITION, LightPosition1); + glLightfv( GL_LIGHT0, GL_DIFFUSE, ColorRedish); + glLightfv( GL_LIGHT0, GL_SPECULAR, ColorRedish); + glEnable( GL_LIGHT0); + + // define and switch on light 1 + glLightfv( GL_LIGHT1, GL_POSITION, LightPosition2); + glLightfv( GL_LIGHT1, GL_DIFFUSE, ColorBlueish); + glLightfv( GL_LIGHT1, GL_SPECULAR, ColorBlueish); + glEnable( GL_LIGHT1); + + glMatrixMode( GL_PROJECTION); // define camera projection + glLoadIdentity(); // reset matrix to identity (otherwise existing matrix will be multiplied with) + glOrtho( -15, 15, -10, 10, -50, 50); // orthogonal projection (xmin xmax ymin ymax zmin zmax) + //glFrustum( -10, 10, -8, 8, 2, 20); // perspective projektion +} + +/// define material color properties for front and back side +void SetMaterialColor( int side, float r, float g, float b){ + float amb[4], dif[4], spe[4]; + int i, mat; + + dif[0] = r; // diffuse color as defined by r,g, and b + dif[1] = g; + dif[2] = b; + for( i=0; i<3; i++){ + amb[i] = .1 * dif[i]; // ambient color is 10 percent of diffuse + spe[i] = .5; // specular color is just white / gray + } + amb[3] = dif[3] = spe[3] = 1.0; // alpha component is always 1 + switch( side){ + case 1: mat = GL_FRONT; break; + case 2: mat = GL_BACK; break; + default: mat = GL_FRONT_AND_BACK; break; + } + glMaterialfv( mat, GL_AMBIENT, amb); // define ambient, diffuse and specular components + glMaterialfv( mat, GL_DIFFUSE, dif); + glMaterialfv( mat, GL_SPECULAR, spe); + glMaterialf( mat, GL_SHININESS, 50.0); // Phong constant for the size of highlights +} + + +OGLWidget::OGLWidget(QWidget *parent) // constructor + : QOpenGLWidget(parent) +{ + // Setup the animation timer to fire every x msec + animtimer = new QTimer(this); + animtimer->start( 50 ); + + // Everytime the timer fires, the animation is going one step forward + connect(animtimer, SIGNAL(timeout()), this, SLOT(stepAnimation())); + + animstep = 0; +} + +OGLWidget::~OGLWidget() // destructor +{ +} + +void OGLWidget::stepAnimation() +{ + animstep++; // Increase animation steps + update(); // Trigger redraw of scene with paintGL +} + +void OGLWidget::initializeGL() // initializations to be called once +{ + initializeOpenGLFunctions(); + + InitLightingAndProjection(); // define light sources and projection + +} + +void OGLWidget::paintGL() // draw everything, to be called repeatedly +{ + glEnable(GL_NORMALIZE); // this is necessary when using glScale (keep normals to unit length) + + // set background color + glClearColor(0.8, 0.8, 1.0, 1.0); // bright blue + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // draw the scene + glMatrixMode( GL_MODELVIEW); + glLoadIdentity(); // Reset The Current Modelview Matrix + glTranslated( 0 ,-5 ,-10.0); // Move 10 units backwards in z, since camera is at origin + glScaled( scale, scale, scale); // scale objects + glRotated( alpha, 0, 3, 1); // continuous rotation + alpha += 2; + + // define color: 1=front, 2=back, 3=both, followed by r, g, and b + SetMaterialColor( 1, 1.0, .2, .2); // front color is red + SetMaterialColor( 2, 0.2, 0.2, 1.0); // back color is blue + + // draw a cylinder with default resolution + DrawTriag(); + + // make it appear (before this, it's hidden in the rear buffer) + glFlush(); +} + +void OGLWidget::resizeGL(int w, int h) // called when window size is changed +{ + // adjust viewport transform + glViewport(0,0,w,h); +} + +/// Clone and subdivide the outer mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::parentSubdiv(int i) { + parentSubdivCount = i; + + parentMesh = originalMesh->copy(); + parentMesh->drawWireframe = true; + parentMesh->subDivLoop(parentSubdivCount); +} + +/// Clone and subdivide the inner mesh. +/// Callback from UI. +/// \param i Subdivision steps +void OGLWidget::childSubdiv(int i) { + childSubdivCount = i; + + childMesh = originalMesh->copy(); + childMesh->drawWireframe = childWireframe; + childMesh->subDivLoop(childSubdivCount); +} + +/// Whether or not to draw the outer mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawOutline(int i) { + drawParent = i == 2; +} + +/// Whether or not to draw the inner mesh. +/// Callback from UI. +/// \param i Selection state of the checkbox (0=Unchecked; 1=PartiallyChecked; 2=Checked) +void OGLWidget::drawWireframe(int i) { + childWireframe = i == 2; + childMesh->drawWireframe = childWireframe; +} + +/// Set global scale. +/// Callback from UI. +/// \param i Slider value +void OGLWidget::setScale(int i) { + + scale = float(i) / 10.0f; +} + +/// Load a mesh from filepath. +/// Callback from UI. +/// \see bool Mesh::loadData(const string& fileName) +/// \param filename Path to mesh file +/// \return True, if loading was successful +bool OGLWidget::loadFile(string filename) { + + originalMesh = new Mesh(); + bool success = originalMesh->loadData(filename); + + init = success; + if (!success) return false; + + + OGLWidget::childSubdiv(childSubdivCount); + +// childMesh->saveData( R"(C:\CLionProjects\cg\hw04\test2.obj)"); + + OGLWidget::parentSubdiv(parentSubdivCount); + + return true; +} + diff --git a/hw04/documentation/images/E0-8.png b/hw04/documentation/images/E0-8.png new file mode 100644 index 0000000..adb8401 --- /dev/null +++ b/hw04/documentation/images/E0-8.png Binary files differ diff --git a/hw04/documentation/images/E0.svg b/hw04/documentation/images/E0.svg new file mode 100644 index 0000000..bf5ae5a --- /dev/null +++ b/hw04/documentation/images/E0.svg @@ -0,0 +1 @@ +V3V0V1V2e0 \ No newline at end of file diff --git a/hw04/documentation/images/E012.svg b/hw04/documentation/images/E012.svg new file mode 100644 index 0000000..c60dfcf --- /dev/null +++ b/hw04/documentation/images/E012.svg @@ -0,0 +1 @@ +V0V2V1V3V3V3e0e1e2 \ No newline at end of file diff --git a/hw04/documentation/images/GUI.png b/hw04/documentation/images/GUI.png new file mode 100644 index 0000000..89d92a5 --- /dev/null +++ b/hw04/documentation/images/GUI.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop-8.png b/hw04/documentation/images/LinearLoop-8.png new file mode 100644 index 0000000..90c7f4f --- /dev/null +++ b/hw04/documentation/images/LinearLoop-8.png Binary files differ diff --git a/hw04/documentation/images/LinearLoop.svg b/hw04/documentation/images/LinearLoop.svg new file mode 100644 index 0000000..b88b5ba --- /dev/null +++ b/hw04/documentation/images/LinearLoop.svg @@ -0,0 +1 @@ +V0V1N2N1N0V2 \ No newline at end of file diff --git a/hw04/documentation/images/example/a0.png b/hw04/documentation/images/example/a0.png new file mode 100644 index 0000000..11a30aa --- /dev/null +++ b/hw04/documentation/images/example/a0.png Binary files differ diff --git a/hw04/documentation/images/example/a1.png b/hw04/documentation/images/example/a1.png new file mode 100644 index 0000000..b5e2b92 --- /dev/null +++ b/hw04/documentation/images/example/a1.png Binary files differ diff --git a/hw04/documentation/images/example/a2.png b/hw04/documentation/images/example/a2.png new file mode 100644 index 0000000..070f413 --- /dev/null +++ b/hw04/documentation/images/example/a2.png Binary files differ diff --git a/hw04/documentation/images/example/a3.png b/hw04/documentation/images/example/a3.png new file mode 100644 index 0000000..5984c6c --- /dev/null +++ b/hw04/documentation/images/example/a3.png Binary files differ diff --git a/hw04/documentation/images/example/a4.png b/hw04/documentation/images/example/a4.png new file mode 100644 index 0000000..9cea993 --- /dev/null +++ b/hw04/documentation/images/example/a4.png Binary files differ diff --git a/hw04/documentation/images/example/b0.png b/hw04/documentation/images/example/b0.png new file mode 100644 index 0000000..7d484d2 --- /dev/null +++ b/hw04/documentation/images/example/b0.png Binary files differ diff --git a/hw04/documentation/images/example/b1.png b/hw04/documentation/images/example/b1.png new file mode 100644 index 0000000..3c9d514 --- /dev/null +++ b/hw04/documentation/images/example/b1.png Binary files differ diff --git a/hw04/documentation/images/example/b2.png b/hw04/documentation/images/example/b2.png new file mode 100644 index 0000000..35beda4 --- /dev/null +++ b/hw04/documentation/images/example/b2.png Binary files differ diff --git a/hw04/documentation/images/example/b3.png b/hw04/documentation/images/example/b3.png new file mode 100644 index 0000000..fe89454 --- /dev/null +++ b/hw04/documentation/images/example/b3.png Binary files differ diff --git a/hw04/documentation/images/example/b4.png b/hw04/documentation/images/example/b4.png new file mode 100644 index 0000000..aec3742 --- /dev/null +++ b/hw04/documentation/images/example/b4.png Binary files differ diff --git a/hw04/documentation/images/example/c0.png b/hw04/documentation/images/example/c0.png new file mode 100644 index 0000000..3b26682 --- /dev/null +++ b/hw04/documentation/images/example/c0.png Binary files differ diff --git a/hw04/documentation/images/example/c1.png b/hw04/documentation/images/example/c1.png new file mode 100644 index 0000000..88565b1 --- /dev/null +++ b/hw04/documentation/images/example/c1.png Binary files differ diff --git a/hw04/documentation/images/example/c2.png b/hw04/documentation/images/example/c2.png new file mode 100644 index 0000000..1948a7a --- /dev/null +++ b/hw04/documentation/images/example/c2.png Binary files differ diff --git a/hw04/documentation/images/example/c3.png b/hw04/documentation/images/example/c3.png new file mode 100644 index 0000000..3423428 --- /dev/null +++ b/hw04/documentation/images/example/c3.png Binary files differ diff --git a/hw04/documentation/images/example/c4.png b/hw04/documentation/images/example/c4.png new file mode 100644 index 0000000..89cb07f --- /dev/null +++ b/hw04/documentation/images/example/c4.png Binary files differ diff --git a/hw04/documentation/main.tex b/hw04/documentation/main.tex new file mode 100644 index 0000000..fdd09d6 --- /dev/null +++ b/hw04/documentation/main.tex @@ -0,0 +1,136 @@ +\documentclass{article} + +\usepackage{svg} +\usepackage{amssymb} +\usepackage[british]{babel} +\usepackage[utf8]{inputenc} +\usepackage{amsmath,amssymb} +\usepackage{parskip} +\usepackage{graphicx} +\usepackage{listings} +\usepackage{caption} +\DeclareCaptionType{code}[Codeblock][List of Codeblocks] +\usepackage[a]{} +\usepackage{float} +\usepackage{hyperref} +\usepackage{tcolorbox} +\usepackage{datatool}% http://ctan.org/pkg/datatool +\newcommand{\sortitem}[1]{% + \DTLnewrow{list}% Create a new entry + \DTLnewdbentry{list}{description}{#1}% Add entry as description +} +\newenvironment{sortedlist}{% + \DTLifdbexists{list}{\DTLcleardb{list}}{\DTLnewdb{list}}% Create new/discard old list +}{% + \DTLsort{description}{list}% Sort list + \begin{itemize}% + \DTLforeach*{list}{\theDesc=description}{% + \item \theDesc}% Print each item + \end{itemize}% +} +\usepackage{tikz} +\newcommand*\circled[1]{\tikz[baseline=(char.base)]{ + \node[shape=circle,fill=red, text=white, draw=red,inner sep=2pt,font=\bfseries] (char) {#1};}} + +\usepackage[a4paper, textwidth=15cm]{geometry} + +\usepackage{minted} +\usemintedstyle{monokai} +\lstset{language=C++} +% load package with ``framed'' and ``numbered'' option. +\usepackage[framed,numbered,autolinebreaks,useliterate]{mcode} +% something NOT relevant to the usage of the package. +\usepackage{url} + +\newcommand{\inCode}[1]{\small\colorbox{black!70}{\mintinline{Cirru}{#1}}} +\newcommand{\paragraphnl}[1]{\paragraph{#1}\mbox{}\\} + +\usepackage{tocloft} + +\usepackage[ +backend=biber, +style=alphabetic, +citestyle=authoryear +]{biblatex} +\addbibresource{source.bib} + + +%%%%%%%%%%%%%%%%% +% Title % +%%%%%%%%%%%%%%%%% +\title{% \vfill + %\vspace{-2.0cm} + Computer Graphics \\ Project 1 \\ Subdividing Surfaces} + + +\author{ \\\\ Antonio Martinez Casadesus \\ 5103414 \\ \href{mailto:acasadesus@stud.hs-bremen.de}{acasadesus @ stud.hs-bremen.de} +\\ + \and \\\\ Pascal Syma \\ 5097056 \\ \href{mailto:psyma@stud.hs-bremen.de}{psyma @ stud.hs-bremen.de}\\ } +\date{ \today} +\parindent 0pt +\parskip 1ex + +\pagenumbering{roman} +\begin{document} + +\graphicspath{ {./images/} } +\maketitle +\begin{center} + +\begin{figure}[H] +\centering +\includegraphics[scale=0.7]{images/example/a4.png} +\end{figure} + +Computer Graphics \\ Prof. Dr. Martin Hering-Bertram \\ SoSe 2021 \\ Gruppe 7 \\~\\ Source code: \\ \href{https://git.syma.dev/Pascal/cg/tree/master/hw04}{https://git.syma.dev / Pascal/cg} +\end{center} + +\lstset{literate=% + {Ö}{{\"O}}1 + {Ä}{{\"A}}1 + {Ü}{{\"U}}1 + {ß}{{\ss}}2 + {ü}{{\"u}}1 + {ä}{{\"a}}1 + {ö}{{\"o}}1 +} + + +%%%%%%%%%%%%%%%%%%%%%%%%%% +% Inhaltsverzeichnis % +%%%%%%%%%%%%%%%%%%%%%%%%% +\newpage +\tableofcontents + +\clearpage +\listoffigures +\listoftables +\listofcodes + + +\clearpage + +\section{Abstract} +\pagenumbering{arabic} +\input{_Abstract} + +\section{Fundamentals} +\input{_Fundamentals} + +\section{Algorithms} +\input{_Algorithms} + +\section{Implementation} +\input{_Implementation} + +\clearpage +\section{Examples} +\input{_Examples} + +\clearpage +\section{Conclusions} +\input{_Conclusions} + +\printbibliography[title={References},heading=bibnumbered] + +\end{document} \ No newline at end of file diff --git a/hw04/documentation/mcode.sty b/hw04/documentation/mcode.sty new file mode 100644 index 0000000..46cc001 --- /dev/null +++ b/hw04/documentation/mcode.sty @@ -0,0 +1,291 @@ +%% +%% This is file `mcode.sty' +%% +%% It is supposed to help you easily include MATLAB source code +%% into LaTeX document, but have it nicely highlighted, using +%% the great listings package. +%% +%% PLEASE NOTE that this package does nothing but save you from +%% figuring out some configurations in setting up the LISTINGS +%% package. ALL the work is done by that package! Thus, please +%% refer your questions to the listings package documentation. +%% +%% Usage: You have three ways of including your MATLAB code. As +%% environment, as inline object and directly from an external +%% file. +%% +%% 1) Environment: +%% +%% \begin{lstlisting} +%% YOUR CODE HERE +%% \end{lstlisting} +%% +%% +%% 2) Inline object*: +%% +%% Bla bla \mcode{CODEFRAGMENT} bla bla. +%% +%% +%% 3) Include external file (in environment form) +%% +%% \lstinputlisting{YOUR-FILE.m} +%% +%% +%% For your convenience this package has the following options: +%% +%% - bw if you intend to print the document (highlighting done +%% via text formatting (bold, italic) and shades of gray) +%% +%% - numbered if you want line numbers +%% +%% - autolinebreaks if you want the package to automatically +%% wrap your code. This is buggy as it may well break +%% break syntax and it doesn't work well with comments. +%% You REALLY should wrap your code manually. +%% +%% - useliterate if you want some characters / relations in +%% your code to be replace with something more readable. +%% Example: ~= becomes $\neq$, >= becomes $\geq$, delta +%% becomes $\delta$ and so on. +%% +%% - framed if you want a frame around the source code blocks +%% +%% - final if you have ``gloablly'' set the draft option, the +%% listings package will not output the code at all. to +%% force it to do so anyway, load this package with the +%% final option (passes the ``final'' on to listings). +%% +%% For example, you may use \usepackage[numbered,framed]{mcode} +%% in your document preamble. +%% +%% * If you want to place some inline code in a footnote, use +%% \mcodefn{} instead (this will reduce the font size a bit). +%% +%% Note: Inside code blocks you can escape to LaTeX text mode +%% using §...§. For ex. §text and some math: $x^2$§, which is +%% especially useful in comments for putting nicely typeset +%% equations etc. To get the same colour/style as in the rest +%% of the comment use \mcommentfont, i.e. §\mcommentfont $x^2$§ +%% +%% To change the font used, edit the first line in the "custo- +%% mise below" section. And feel free to edit other things as +%% well. Refer to the documentation of the listings package to +%% see what else you could do. If an extra small font is re- +%% quired, use {\fontfamily{pcr}\fontsize{3}{4.6}\selectfont} +%% in the definition of \lstbasicfont. +%% +%% Author: +%% Florian Knorn | florian@knorn.org | www.florian-knorn.com +%% +%% Version history: +%% 2.5 -- Renamed internal variables (thx S. Kranenbarg!) +%% 2.4 -- Added \mcodefn{} command (thx Tony Almeida!) +%% 2.3 -- More keywords (thx Dominik Wild!) +%% 2.2 -- Bugfix (thx Willi Gerbig!) +%% 2.1 -- Finally automatic detection between end and end +%% 2.0 -- New options for line breaking and literate prog. +%% 1.8 -- Fixed typo in documentation regarding §...§ +%% 1.7 -- Added MATLAB block comment syntax %{ ...... %} +%% 1.6 -- Added some infos, dealing with keyword ``end'' +%% 1.5 -- Tweaked check to see wether textcomp is loaded +%% 1.4 -- Fixed misconfig (mathescape now set to false) +%% 1.3 -- Purely cosmetic (tabs replaced by spaces) +%% 1.2 -- Added \lstset{showstringspaces=false} +%% 1.1 -- Added \mcode command and [final] option +%% 1.0 -- Release + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% D O N ' T T O U C H T H I S % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\def\fileversion{2.5} +\def\filedate{2014/03/06} + +\typeout{-- Package: `mcode' \fileversion\space <\filedate> --} +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{mcode}[\filedate\space\fileversion] + +% for bw-option +\newif\ifmcode@bw +\DeclareOption{bw}{\mcode@bwtrue} + +% numbered option +\newif\ifmcode@numbered +\DeclareOption{numbered}{\mcode@numberedtrue} + +% final option +\newif\ifmcode@final +\DeclareOption{final}{\mcode@finaltrue} + +% autolinebreaks option +\newif\ifmcode@autolinebreaks +\DeclareOption{autolinebreaks}{\mcode@autolinebreakstrue} + +% literate programming (replace certain characters/relations +\newif\ifmcode@useliterate +\DeclareOption{useliterate}{\mcode@useliteratetrue} + +% framed option +\newif\ifmcode@framed +\DeclareOption{framed}{\mcode@framedtrue} + +\DeclareOption*{% default + \PackageWarning{mcode}{Unknown option `\CurrentOption' !}% +} +\ProcessOptions + +\ifmcode@bw\typeout{ - settings optimized for printing (bw formating)} +\else\typeout{ - settings optimized for display (colour formating)}\fi +\ifmcode@numbered\typeout{ - line numbering enabled}\else\fi +\ifmcode@useliterate\typeout{ - literate programming (character replacements) enabled}\else\fi +\ifmcode@autolinebreaks\typeout{ - automatic line breaking enabled (careful, buggy!)}\else\fi +\ifmcode@framed\typeout{ - framed listings}\else\fi + +% This command allows you to typeset syntax highlighted Matlab +% code ``inline''. The font size \small seems to look best... +\newcommand{\mcode}[1]{\lstinline[basicstyle=\lstbasicfont\small]|#1|} + +% Same, but for footnotes +\newcommand{\mcodefn}[1]{\lstinline[basicstyle=\lstbasicfont\footnotesize]|#1|} + +% check if color command exists +\ifx\color\undefined% + \RequirePackage{xcolor}% +\fi + +% check if listings has been loaded +\ifx\lstset\undefined% + \ifmcode@final + \RequirePackage[final]{listings} + \else + \RequirePackage{listings} + \fi +\fi + +% Check if textcomp has been loaded (this package is needed for +% upright quotes '' (instead of typographic ones `´)... +\ifx\textquotesingle\undefined% + \RequirePackage{textcomp}% +\fi + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% C U S T O M I S E B E L O W % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% --------------------------------------------------------------------------------- +% default font +\def\lstbasicfont{\fontfamily{pcr}\selectfont\footnotesize} + +% --------------------------------------------------------------------------------- +% matlat languate definition +\lstdefinelanguage{matlabfloz}{% + alsoletter={...},% + morekeywords={% % keywords + break,case,catch,continue,elseif,else,end,% + for,function,global,if,otherwise,persistent,% + return,switch,try,while,methods,properties,% + events,classdef,...},% + comment=[l]\%, % comments + morecomment=[l]..., % comments + morecomment=[s]{\%\{}{\%\}}, % block comments + morestring=[m]' % strings +}[keywords,comments,strings]% + +% --------------------------------------------------------------------------------- +% general definitions +\lstset{% + basicstyle={\lstbasicfont}, % set font + showstringspaces=false, % do not emphasize spaces in strings + tabsize=4, % number of spaces of a TAB + mathescape=false,escapechar=§, % escape to latex with §...§ + upquote=true, % upright quotes + aboveskip={1.5\baselineskip}, % a bit of space above listings + columns=fixed % nice spacing +} + +% --------------------------------------------------------------------------------- +% define colours and styles +\ifmcode@bw % use font formating and gray 'colors' + \def\mcommentfont{\color[gray]{.75}\itshape} %comments light gray and italic + \lstset{language=matlabfloz, % use our version of highlighting + keywordstyle=\bfseries, % keywords in bold + commentstyle=\mcommentfont, % comments + stringstyle=\color[gray]{0.5} % strings darker gray + } +\else% notbw => use colors : ) + \def\mcommentfont{\color[rgb]{.133,.545,.133}} %comments in green + \lstset{language=matlabfloz, % use our version of highlighting + keywordstyle=\color[rgb]{0,0,1}, % keywords in blue + commentstyle=\mcommentfont, % comments + stringstyle=\color[rgb]{.627,.126,.941} % strings in purple + } +\fi%bw + +% --------------------------------------------------------------------------------- +% automatic line breaking --- warning, this is buggy and +% doesn't break comments correctly! +\ifmcode@autolinebreaks + \newsavebox{\lbreakdots}\sbox{\lbreakdots}{\lstbasicfont\mcommentfont...} + \lstset{breaklines=true,breakatwhitespace=true,prebreak=\usebox{\lbreakdots}} +\fi + +% --------------------------------------------------------------------------------- +% literate replacements +% the following is for replacing some matlab relations like >= or ~= +% by the corresponding LaTeX symbols, which are much easier to read ... +\ifmcode@useliterate + \lstset{% + literate=% + {~}{{$\neg$}}1 % \neg + {<=}{{\tiny$\leq$}}1 % \leq + {>=}{{\tiny$\geq$}}1 % \geq + {~=}{{\tiny$\neq$}}1 % \neq + {delta}{{\tiny$\Delta$}}1 % \Delta + {(end)}{\lstbasicfont (end)}{5} % black ``end'' when indexing last vector element + {({ }end)}{\lstbasicfont ({ }end)}{6} + {(end{ })}{\lstbasicfont (end{ })}{6} + {({ }end{ })}{\lstbasicfont ({ }end{ })}{7} + {:end}{\lstbasicfont :end}{4} + {:{ }end}{\lstbasicfont :{ }end}{5} + {end:}{\lstbasicfont end:}{4} + {end{ }:}{\lstbasicfont end{ }:}{5} + {,end}{\lstbasicfont ,end}{4} + {,{ }end}{\lstbasicfont ,{ }end}{5} + } +\else + \lstset{% + literate=% + {(end)}{\lstbasicfont (end)}{5} % black ``end'' when indexing last vector element + {({ }end)}{\lstbasicfont ({ }end)}{6} + {(end{ })}{\lstbasicfont (end{ })}{6} + {({ }end{ })}{\lstbasicfont ({ }end{ })}{7} + {:end}{\lstbasicfont :end}{4} + {:{ }end}{\lstbasicfont :{ }end}{5} + {end:}{\lstbasicfont end:}{4} + {end{ }:}{\lstbasicfont end{ }:}{5} + {,end}{\lstbasicfont ,end}{4} + {,{ }end}{\lstbasicfont ,{ }end}{5} + } +\fi%literates + +% --------------------------------------------------------------------------------- +% line numbering +\ifmcode@numbered% numbered option + \lstset{% + numbersep=3mm, numbers=left, numberstyle=\tiny, % number style + } +\fi + +\ifmcode@framed% framed option + \lstset{% + frame=single, % frame + } + \ifmcode@numbered% + \lstset{% + framexleftmargin=6mm, xleftmargin=6mm % tweak margins + } + \fi +\fi + +\endinput +%% End of file `mcode.sty'. diff --git a/hw04/documentation/source.bib b/hw04/documentation/source.bib new file mode 100644 index 0000000..9928428 --- /dev/null +++ b/hw04/documentation/source.bib @@ -0,0 +1,24 @@ +@article{loop1987smooth, + author = {Loop, Charles}, + title = {Smooth Subdivision Surfaces Based on Triangles}, + year = {1987}, + journal = {Department of Mathematics, The University of Utah, Masters Thesis}, + month = 1, + abstract = {Department of Mathematics, The University of Utah, Masters Thesis}, + url = {https://www.microsoft.com/en-us/research/publication/smooth-subdivision-surfaces-based-on-triangles/}, +} + + @article{DBLP:journals/cvgip/Chaikin74, + author = {George Merrill Chaikin}, + title = {An algorithm for high-speed curve generation}, + journal = {Comput. Graph. Image Process.}, + volume = {3}, + number = {4}, + pages = {346--349}, + year = {1974}, + url = {https://doi.org/10.1016/0146-664X(74)90028-8}, + doi = {10.1016/0146-664X(74)90028-8}, + timestamp = {Wed, 20 May 2020 16:37:23 +0200}, + biburl = {https://dblp.org/rec/journals/cvgip/Chaikin74.bib}, + bibsource = {dblp computer science bibliography, https://dblp.org} +} \ No newline at end of file