{"id":1252,"date":"2022-01-15T03:21:26","date_gmt":"2022-01-15T03:21:26","guid":{"rendered":"https:\/\/writingagame.com\/?p=1252"},"modified":"2022-01-30T18:27:25","modified_gmt":"2022-01-30T18:27:25","slug":"chapter-37-mesh-optimization","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2022\/01\/15\/chapter-37-mesh-optimization\/","title":{"rendered":"Chapter 37. Mesh optimization"},"content":{"rendered":"\n<p>Though the model is completely ready now, still we have 1 more issue to resolve:<\/p>\n\n\n\n<p>Currently our model consists of 316 vertices and 144 triangles (432 indices). Actually, not too much comparing to 3D editors. But still, how come so many??<\/p>\n\n\n\n<p>Well, root box for instance: <\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>6 sides, 4 vertices each = 24 vertices<\/li><li>12 ribs, 4 vertices each = 48 vertices<\/li><li>8 corners, 3 vertices each = 24 vertices <\/li><\/ul>\n\n\n\n<p>96  <span style=\"font-size: revert;\">vertices<\/span> total.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Plus another 96 for clear-film box<\/li><li>Plus gilded and embossed projections, 4 verts each<\/li><li>Plus sealing ribbon line, 12 verts<\/li><li>Plus (most &#8220;expensive&#8221;) 6 <em>a2mesh <\/em>cases<\/li><\/ul>\n\n\n\n<p>So, 316  vertices  is quite reasonable.<\/p>\n\n\n\n<p>However, many of them are actually the same. For example, box corners, where each of 3 points is duplicated in:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Corner triangle itself<\/li><li>Plus in 2 attached ribs<\/li><li>Plus in 1 box side<\/li><\/ul>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c37\/01.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>So, it&#8217;s not just  duplicated, it is <em>quadrupled<\/em> !<\/p>\n\n\n\n<p>Our next task is to detect and to eliminate such vertex redundancies.<\/p>\n\n\n\n<p>We&#8217;ll do it in <strong>ModelBuilder1base <\/strong>class, in <em>buildSingleDrawJob(..)<\/em> function. <\/p>\n\n\n\n<p>1. Start VS, open&nbsp;<em>C:\\CPP\\a997modeler\\p_windows\\p_windows.sln<\/em>.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>2.  Open&nbsp;<em>ModelBuilder1base.h <\/em>and replace code by: <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [47]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &lt;string&gt;\n#include &lt;vector&gt;\n#include &quot;Vertex01.h&quot;\n#include &quot;Triangle01.h&quot;\n#include &quot;VirtualShape.h&quot;\n#include &quot;Group01.h&quot;\n#include &quot;Material.h&quot;\n#include &quot;GameSubj.h&quot;\n#include &lt;map&gt;\n\nclass ModelBuilder1base\n{\npublic:\n\tstd::vector&lt;Vertex01*&gt; vertices;\n\tstd::vector&lt;Triangle01*&gt; triangles;\n\tstd::vector&lt;int&gt; subjNumbersList;\n\tint usingSubjN = -1;\n\n\tstd::vector&lt;Group01*&gt; groupsStack;\n\tGroup01* pCurrentGroup = NULL;\n\tGroup01* pLastClosedGroup = NULL;\n\t\n\tstd::vector&lt;VirtualShape*&gt; vShapesStack;\n\tVirtualShape* pCurrentVShape = NULL;\n\n\tstd::vector&lt;Material*&gt; materialsList;\n\tint usingMaterialN = -1;\n\tstd::vector&lt;int&gt; materialsStack;\n\n\tstd::map&lt;std::string, int&gt; texturesHashMap;\npublic:\n\tvirtual ~ModelBuilder1base();\n\tstatic int useSubjN(ModelBuilder1base* pMB, int subjN);\n\tstatic int getMaterialN(ModelBuilder1base* pMB, Material* pMT);\n\tstatic void lockGroup(ModelBuilder1base* pMB);\n\tstatic void releaseGroup(ModelBuilder1base* pMB);\n\tstatic int addVertex(ModelBuilder1base* pMB, float kx, float ky, float kz, float nx, float ny, float nz);\n\tstatic int add2triangles(ModelBuilder1base* pMB, int nNW, int nNE, int nSW, int nSE, int n);\n\tstatic int addTriangle(ModelBuilder1base* pMB, int n0, int n1, int n2);\n\tstatic int buildDrawJobs(ModelBuilder1base* pMB, std::vector&lt;GameSubj*&gt;* pGameSubjs);\n\tstatic int rearrangeArraysForDrawJob(std::vector&lt;Vertex01*&gt;* pAllVertices, std::vector&lt;Vertex01*&gt;* pUseVertices, std::vector&lt;Triangle01*&gt;* pUseTriangles);\n\tstatic int buildSingleDrawJob(Material* pMT, std::vector&lt;Vertex01*&gt;* pVertices, std::vector&lt;Triangle01*&gt;* pTriangles);\n\tstatic int moveGroupDg(ModelBuilder1base* pMB, float aX, float aY, float aZ, float kX, float kY, float kZ);\n\tstatic int calculateTangentSpace(std::vector&lt;Vertex01*&gt;* pUseVertices, std::vector&lt;Triangle01*&gt;* pUseTriangles);\n\tstatic int finalizeLine(std::vector&lt;Vertex01*&gt;* pVerts, int lineStartsAt = 0, int lineEndsAt = 0);\n\tstatic int optimizeMesh(std::vector&lt;Vertex01*&gt;* pVertices, std::vector&lt;Triangle01*&gt;* pTriangles);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>New function here is <em>optimizeMesh(..)<\/em><\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>3.  Open&nbsp;<em>ModelBuilder1base.cpp <\/em>and replace code by: <br><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [226,492]; title: ; notranslate\" title=\"\">\n#include &quot;ModelBuilder1base.h&quot;\n#include &quot;platform.h&quot;\n#include &quot;utils.h&quot;\n#include &quot;DrawJob.h&quot;\n#include &quot;Shader.h&quot;\n\nextern float degrees2radians;\n\nModelBuilder1base::~ModelBuilder1base() {\n\treleaseGroup(this);\n\n\t\/\/clear all vectors\n\tint itemsN = vertices.size();\n\tfor (int i = 0; i &lt; itemsN; i++)\n\t\tdelete vertices.at(i);\n\tvertices.clear();\n\n\titemsN = triangles.size();\n\tfor (int i = 0; i &lt; itemsN; i++)\n\t\tdelete triangles.at(i);\n\ttriangles.clear();\n\n\titemsN = vShapesStack.size();\n\tfor (int i = 0; i &lt; itemsN; i++)\n\t\tdelete vShapesStack.at(i);\n\tvShapesStack.clear();\n\n\titemsN = groupsStack.size();\n\tfor (int i = 0; i &lt; itemsN; i++)\n\t\tdelete groupsStack.at(i);\n\tgroupsStack.clear();\n\tif (pCurrentGroup != NULL)\n\t\tdelete pCurrentGroup;\n\tif (pLastClosedGroup != NULL)\n\t\tdelete pLastClosedGroup;\n\n\titemsN = materialsList.size();\n\tfor (int i = 0; i &lt; itemsN; i++)\n\t\tdelete materialsList.at(i);\n\tmaterialsList.clear();\n\n\tsubjNumbersList.clear();\n}\nint ModelBuilder1base::useSubjN(ModelBuilder1base* pMB, int subjN) {\n\tpMB-&gt;usingSubjN = subjN;\n\tint itemsN = pMB-&gt;subjNumbersList.size();\n\tbool newN = true;\n\tif (itemsN &gt; 0)\n\t\tfor (int i = 0; i &lt; itemsN; i++)\n\t\t\tif (pMB-&gt;subjNumbersList.at(i) == subjN) {\n\t\t\t\tnewN = false;\n\t\t\t\tbreak;\n\t\t\t}\n\tif (newN)\n\t\tpMB-&gt;subjNumbersList.push_back(subjN);\n\treturn subjN;\n}\nint ModelBuilder1base::getMaterialN(ModelBuilder1base* pMB, Material* pMT) {\n\tint itemsN = pMB-&gt;materialsList.size();\n\tif (itemsN &gt; 0)\n\t\tfor (int i = 0; i &lt; itemsN; i++)\n\t\t\tif (memcmp(pMB-&gt;materialsList.at(i), pMT, sizeof(Material)) == 0) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\/\/if here - add new material to the list\n\tMaterial* pMTnew = new Material(*pMT);\n\tpMB-&gt;materialsList.push_back(pMTnew);\n\treturn itemsN;\n}\nint ModelBuilder1base::add2triangles(ModelBuilder1base* pMB, int nNW, int nNE, int nSW, int nSE, int n) {\n\t\/\/indexes: NorthWest, NorthEast, SouthWest,SouthEast\n\tif (n % 2 == 0) { \/\/even number\n\t\taddTriangle(pMB, nNW, nSW, nNE);\n\t\taddTriangle(pMB, nNE, nSW, nSE);\n\t}\n\telse { \/\/odd number\n\t\taddTriangle(pMB, nNW, nSE, nNE);\n\t\taddTriangle(pMB, nNW, nSW, nSE);\n\t}\n\treturn pMB-&gt;triangles.size() - 1;\n}\nint ModelBuilder1base::addTriangle(ModelBuilder1base* pMB, int i0, int i1, int i2) {\n\tTriangle01* pTR = new Triangle01();\n\tpMB-&gt;triangles.push_back(pTR);\n\tpTR-&gt;idx&#91;0] = i0;\n\tpTR-&gt;idx&#91;1] = i1;\n\tpTR-&gt;idx&#91;2] = i2;\n\tpTR-&gt;subjN = pMB-&gt;usingSubjN;\n\tpTR-&gt;materialN = pMB-&gt;usingMaterialN;\n\t\/\/mark\n\tif (pMB-&gt;pCurrentGroup != NULL)\n\t\tif (strcmp(pMB-&gt;pCurrentGroup-&gt;marks, &quot;&quot;) != 0)\n\t\t\tmyStrcpy_s(pTR-&gt;marks, 124, pMB-&gt;pCurrentGroup-&gt;marks);\n\treturn pMB-&gt;triangles.size() - 1;\n}\nint ModelBuilder1base::addVertex(ModelBuilder1base* pMB, float kx, float ky, float kz, float nx, float ny, float nz) {\n\tVertex01* pVX = new Vertex01();\n\tpMB-&gt;vertices.push_back(pVX);\n\tpVX-&gt;aPos&#91;0] = kx;\n\tpVX-&gt;aPos&#91;1] = ky;\n\tpVX-&gt;aPos&#91;2] = kz;\n\t\/\/normal\n\tpVX-&gt;aNormal&#91;0] = nx;\n\tpVX-&gt;aNormal&#91;1] = ny;\n\tpVX-&gt;aNormal&#91;2] = nz;\n\tpVX-&gt;subjN = pMB-&gt;usingSubjN;\n\tpVX-&gt;materialN = pMB-&gt;usingMaterialN;\n\t\/\/mark\n\tif (pMB-&gt;pCurrentGroup != NULL)\n\t\tif (strcmp(pMB-&gt;pCurrentGroup-&gt;marks, &quot;&quot;) != 0)\n\t\t\tmyStrcpy_s(pVX-&gt;marks, 124, pMB-&gt;pCurrentGroup-&gt;marks);\n\treturn pMB-&gt;vertices.size() - 1;\n}\nint ModelBuilder1base::buildDrawJobs(ModelBuilder1base* pMB, std::vector&lt;GameSubj*&gt;* pGameSubjs) {\n\tint totalSubjsN = pMB-&gt;subjNumbersList.size();\n\tif (totalSubjsN &lt; 1) {\n\t\tpMB-&gt;subjNumbersList.push_back(-1);\n\t\ttotalSubjsN = 1;\n\t}\n\tint totalMaterialsN = pMB-&gt;materialsList.size();\n\tif (totalSubjsN &lt; 2 &amp;&amp; totalMaterialsN &lt; 2) {\n\t\t\/\/simple single DrawJob\n\t\tMaterial* pMT = pMB-&gt;materialsList.at(0);\n\t\tGameSubj* pGS = NULL;\n\t\tint gsN = pMB-&gt;subjNumbersList.at(0);\n\t\tif (gsN &gt;= 0)\n\t\t\tpGS = pGameSubjs-&gt;at(gsN);\n\t\tif (pGS != NULL)\n\t\t\tpGS-&gt;djStartN = DrawJob::drawJobs.size();\n\t\tbuildSingleDrawJob(pMT, &amp;pMB-&gt;vertices, &amp;pMB-&gt;triangles);\n\t\tif (pGS != NULL)\n\t\t\tpGS-&gt;djTotalN = DrawJob::drawJobs.size() - pGS-&gt;djStartN;\n\t\treturn 1;\n\t}\n\tint totalVertsN = pMB-&gt;vertices.size();\n\tint totalTrianglesN = pMB-&gt;triangles.size();\n\t\/\/clear flags\n\tfor (int vN = 0; vN &lt; totalVertsN; vN++) {\n\t\tVertex01* pVX = pMB-&gt;vertices.at(vN);\n\t\tpVX-&gt;flag = 0;\n\t}\n\tfor (int tN = 0; tN &lt; totalTrianglesN; tN++) {\n\t\tTriangle01* pTR = pMB-&gt;triangles.at(tN);\n\t\tpTR-&gt;flag = 0;\n\t}\n\tint addedDJs = 0;\n\tfor (int sN = 0; sN &lt; totalSubjsN; sN++) {\n\t\tGameSubj* pGS = NULL;\n\t\tint gsN = pMB-&gt;subjNumbersList.at(sN);\n\t\tif (gsN &gt;= 0)\n\t\t\tpGS = pGameSubjs-&gt;at(gsN);\n\t\tif (pGS != NULL)\n\t\t\tpGS-&gt;djStartN = DrawJob::drawJobs.size();\n\t\tfor (int mtN = 0; mtN &lt; totalMaterialsN; mtN++) {\n\t\t\tMaterial* pMT = pMB-&gt;materialsList.at(mtN);\n\t\t\tstd::vector&lt;Vertex01*&gt; useVertices;\n\t\t\tstd::vector&lt;Triangle01*&gt; useTriangles;\n\t\t\tfor (int vN = 0; vN &lt; totalVertsN; vN++) {\n\t\t\t\tVertex01* pVX = pMB-&gt;vertices.at(vN);\n\t\t\t\tif (pVX-&gt;flag != 0)\n\t\t\t\t\tcontinue;\n\t\t\t\tif (pVX-&gt;subjN != gsN)\n\t\t\t\t\tcontinue;\n\t\t\t\tif (pVX-&gt;materialN != mtN)\n\t\t\t\t\tcontinue;\n\t\t\t\t\/\/if here - make a copy\n\t\t\t\tpVX-&gt;altN = useVertices.size();\n\t\t\t\tVertex01* pVX2 = new Vertex01(*pVX);\n\t\t\t\tuseVertices.push_back(pVX2);\n\t\t\t\tpVX-&gt;flag = 1;\n\t\t\t\tif (pVX-&gt;endOfSequence &gt; 0) {\n\t\t\t\t\t\/\/rearrangeArraysForDrawJob(pMB, pMB-&gt;vertices, useVertices, useTriangles);\n\t\t\t\t\tbuildSingleDrawJob(pMT, &amp;useVertices, &amp;useTriangles);\n\t\t\t\t\taddedDJs++;\n\t\t\t\t\t\/\/clear and proceed to next sequence\n\t\t\t\t\tint useVerticesN = useVertices.size();\n\t\t\t\t\tfor (int i = 0; i &lt; useVerticesN; i++)\n\t\t\t\t\t\tdelete useVertices.at(i);\n\t\t\t\t\tuseVertices.clear();\n\t\t\t\t}\n\t\t\t}\n\t\t\tint useVerticesN = useVertices.size();\n\t\t\tif (useVerticesN &lt; 1)\n\t\t\t\tcontinue; \/\/to next material\n\t\t\t\/\/pick triangles\n\t\t\tfor (int tN = 0; tN &lt; totalTrianglesN; tN++) {\n\t\t\t\tTriangle01* pTR = pMB-&gt;triangles.at(tN);\n\t\t\t\tif (pTR-&gt;flag != 0)\n\t\t\t\t\tcontinue;\n\t\t\t\tif (pTR-&gt;subjN != gsN)\n\t\t\t\t\tcontinue;\n\t\t\t\tif (pTR-&gt;materialN != mtN)\n\t\t\t\t\tcontinue;\n\t\t\t\t\/\/if here - make a copy\n\t\t\t\tTriangle01* pTR2 = new Triangle01(*pTR);\n\t\t\t\tuseTriangles.push_back(pTR2);\n\t\t\t\tpTR-&gt;flag = 1;\n\t\t\t}\n\t\t\trearrangeArraysForDrawJob(&amp;pMB-&gt;vertices, &amp;useVertices, &amp;useTriangles);\n\t\t\tbuildSingleDrawJob(pMT, &amp;useVertices, &amp;useTriangles);\n\t\t\tuseVerticesN = useVertices.size();\n\t\t\taddedDJs++;\n\t\t\t\/\/clear all for next material\n\t\t\tfor (int i = 0; i &lt; useVerticesN; i++)\n\t\t\t\tdelete useVertices.at(i);\n\t\t\tuseVertices.clear();\n\t\t\tint useTrianglesN = useTriangles.size();\n\t\t\tfor (int i = 0; i &lt; useTrianglesN; i++)\n\t\t\t\tdelete useTriangles.at(i);\n\t\t\tuseTriangles.clear();\n\t\t}\n\t\tif (pGS != NULL)\n\t\t\tpGS-&gt;djTotalN = DrawJob::drawJobs.size() - pGS-&gt;djStartN;\n\t}\n\treturn addedDJs;\n}\nint ModelBuilder1base::buildSingleDrawJob(Material* pMT, std::vector&lt;Vertex01*&gt;* pVertices, std::vector&lt;Triangle01*&gt;* pTriangles) {\n\tint totalVertsN = pVertices-&gt;size();\n\tif (totalVertsN &lt; 1)\n\t\treturn 0;\n\tif (DrawJob::lineWidthIsImportant(pMT-&gt;primitiveType)) {\n\t\tif (strcmp(pMT-&gt;shaderType, &quot;wire&quot;) == 0)\n\t\t\tfinalizeLine(pVertices);\n\t}\n\telse {\n\t\toptimizeMesh(pVertices, pTriangles);\n\t\ttotalVertsN = pVertices-&gt;size();\n\t}\n\tif (pMT-&gt;uTex2nm &gt;= 0)\n\t\tcalculateTangentSpace(pVertices, pTriangles);\n\tpMT-&gt;pickShaderNumber();\n\tDrawJob* pDJ = new DrawJob();\n\t\/\/copy material to DJ\n\tmemcpy(&amp;pDJ-&gt;mt, pMT, sizeof(Material));\n\t\/\/calculate VBO element size (stride) and variables offsets in VBO\n\tint VBOid = DrawJob::newBufferId();\n\tint stride = 0;\n\tpDJ-&gt;setDesirableOffsets(&amp;stride, pDJ-&gt;mt.shaderN, VBOid);\n\t\/\/create an array for VBO\n\tint bufferSize = totalVertsN * stride;\n\tfloat* vertsBuffer = new float&#91;bufferSize];\n\t\/\/fill vertsBuffer\n\tShader* pSh = Shader::shaders.at(pDJ-&gt;mt.shaderN);\n\tint floatSize = sizeof(float);\n\tfor (int vN = 0; vN &lt; totalVertsN; vN++) {\n\t\tVertex01* pVX = pVertices-&gt;at(vN);\n\t\tint idx = vN * stride \/ floatSize;\n\t\t\/\/pick data from vertex and move to the buffer\n\t\tmemcpy(&amp;vertsBuffer&#91;idx + pDJ-&gt;aPos.offset \/ floatSize], pVX-&gt;aPos, 3 * floatSize);\n\t\tif (pSh-&gt;l_aNormal &gt;= 0) \/\/normal\n\t\t\tmemcpy(&amp;vertsBuffer&#91;idx + pDJ-&gt;aNormal.offset \/ floatSize], pVX-&gt;aNormal, 3 * floatSize);\n\t\tif (pSh-&gt;l_aTuv &gt;= 0) \/\/attribute TUV (texture coordinates)\n\t\t\tmemcpy(&amp;vertsBuffer&#91;idx + pDJ-&gt;aTuv.offset \/ floatSize], pVX-&gt;aTuv, 2 * floatSize);\n\t\tif (pSh-&gt;l_aTuv2 &gt;= 0) \/\/attribute TUV2 (normal maps)\n\t\t\tmemcpy(&amp;vertsBuffer&#91;idx + pDJ-&gt;aTuv2.offset \/ floatSize], pVX-&gt;aTuv2, 2 * floatSize);\n\t\tif (pSh-&gt;l_aTangent &gt;= 0)\n\t\t\tmemcpy(&amp;vertsBuffer&#91;idx + pDJ-&gt;aTangent.offset \/ floatSize], pVX-&gt;aTangent, 3 * floatSize);\n\t\tif (pSh-&gt;l_aBinormal &gt;= 0)\n\t\t\tmemcpy(&amp;vertsBuffer&#91;idx + pDJ-&gt;aBinormal.offset \/ floatSize], pVX-&gt;aBinormal, 3 * floatSize);\n\t}\n\t\/\/buffer is ready, create VBO\n\tglBindBuffer(GL_ARRAY_BUFFER, VBOid);\n\tglBufferData(GL_ARRAY_BUFFER, bufferSize * floatSize, vertsBuffer, GL_STATIC_DRAW);\n\tdelete&#91;] vertsBuffer;\n\tpDJ-&gt;pointsN = totalVertsN;\n\n\tint totalTrianglesN = pTriangles-&gt;size();\n\tif (totalTrianglesN &gt; 0) {\n\t\t\/\/create EBO\n\t\tint totalIndexesN = totalTrianglesN * 3;\n\t\t\/\/create buffer\n\t\tGLushort* indexBuffer = new GLushort&#91;totalIndexesN];\n\t\tfor (int tN = 0; tN &lt; totalTrianglesN; tN++) {\n\t\t\tTriangle01* pTR = pTriangles-&gt;at(tN);\n\t\t\tint idx = tN * 3;\n\t\t\tindexBuffer&#91;idx] = (GLushort)pTR-&gt;idx&#91;0];\n\t\t\tindexBuffer&#91;idx + 1] = (GLushort)pTR-&gt;idx&#91;1];\n\t\t\tindexBuffer&#91;idx + 2] = (GLushort)pTR-&gt;idx&#91;2];\n\t\t}\n\t\t\/\/buffer is ready, create IBO\n\t\tpDJ-&gt;glEBOid = DrawJob::newBufferId();\n\t\tglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, pDJ-&gt;glEBOid);\n\t\tglBufferData(GL_ELEMENT_ARRAY_BUFFER, totalIndexesN * sizeof(GLushort), indexBuffer, GL_STATIC_DRAW);\n\t\tdelete&#91;] indexBuffer;\n\t\tpDJ-&gt;pointsN = totalIndexesN;\n\t}\n\t\/\/create and fill vertex attributes array (VAO)\n\tpDJ-&gt;buildVAO();\n\treturn 1;\n}\nint ModelBuilder1base::rearrangeArraysForDrawJob(std::vector&lt;Vertex01*&gt;* pAllVertices, std::vector&lt;Vertex01*&gt;* pUseVertices, std::vector&lt;Triangle01*&gt;* pUseTriangles) {\n\tint totalTrianglesN = pUseTriangles-&gt;size();\n\tif (totalTrianglesN &lt; 1)\n\t\treturn 0;\n\t\/\/replace triangle original indices by new numbers saved in original vertices altN\n\tfor (int tN = 0; tN &lt; totalTrianglesN; tN++) {\n\t\tTriangle01* pTR = pUseTriangles-&gt;at(tN);\n\t\tfor (int i = 0; i &lt; 3; i++) {\n\t\t\tVertex01* pVX0 = pAllVertices-&gt;at(pTR-&gt;idx&#91;i]);\n\t\t\tpTR-&gt;idx&#91;i] = pVX0-&gt;altN;\n\t\t}\n\t}\n\treturn 1;\n}\n\nint ModelBuilder1base::moveGroupDg(ModelBuilder1base* pMB, float aX, float aY, float aZ, float kX, float kY, float kZ) {\n\t\/\/moves and rotates vertex group\n\t\/\/rotation angles are set in degrees\n\tmat4x4 transformMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };\n\tmat4x4_translate(transformMatrix, kX, kY, kZ);\n\t\/\/rotation order: Z-X-Y\n\tif (aY != 0) mat4x4_rotate_Y(transformMatrix, transformMatrix, degrees2radians * aY);\n\tif (aX != 0) mat4x4_rotate_X(transformMatrix, transformMatrix, degrees2radians * aX);\n\tif (aZ != 0) mat4x4_rotate_Z(transformMatrix, transformMatrix, degrees2radians * aZ);\n\n\tint vertsN = pMB-&gt;vertices.size();\n\tfor (int i = pMB-&gt;pCurrentGroup-&gt;fromVertexN; i &lt; vertsN; i++) {\n\t\tVertex01* pVX = pMB-&gt;vertices.at(i);\n\t\tmat4x4_mul_vec4plus(pVX-&gt;aPos, transformMatrix, pVX-&gt;aPos, 1);\n\t\tmat4x4_mul_vec4plus(pVX-&gt;aNormal, transformMatrix, pVX-&gt;aNormal, 0);\n\t}\n\treturn 1;\n}\n\nint ModelBuilder1base::calculateTangentSpace(std::vector&lt;Vertex01*&gt;* pUseVertices, std::vector&lt;Triangle01*&gt;* pUseTriangles) {\n\tint totalVertsN = pUseVertices-&gt;size();\n\tif (totalVertsN &lt; 1)\n\t\treturn 0;\n\tint totalTrianglesN = pUseTriangles-&gt;size();\n\t\/\/assuming that GL_TRIANGLES\n\t\/\/clear flags\n\tfor (int vN = 0; vN &lt; totalVertsN; vN++) {\n\t\tVertex01* pV = pUseVertices-&gt;at(vN);\n\t\tpV-&gt;flag = 0;\n\t}\n\tfor (int vN = 0; vN &lt; totalVertsN; vN++) {\n\t\tVertex01* pVX = pUseVertices-&gt;at(vN);\n\t\tif (pVX-&gt;flag != 0)\n\t\t\tcontinue;\n\t\tTriangle01* pT = NULL;\n\t\tfor (int tN = 0; tN &lt; totalTrianglesN; tN++) {\n\t\t\tpT = pUseTriangles-&gt;at(tN);\n\t\t\tbool haveTriangle = false;\n\t\t\tfor (int i = 0; i &lt; 3; i++)\n\t\t\t\tif (pT-&gt;idx&#91;i] == vN) {\n\t\t\t\t\thaveTriangle = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tif (haveTriangle)\n\t\t\t\tbreak;\n\t\t}\n\t\tVertex01* pV&#91;3];\n\t\tfor (int i = 0; i &lt; 3; i++)\n\t\t\tpV&#91;i] = pUseVertices-&gt;at(pT-&gt;idx&#91;i]);\n\n\t\tfloat dPos1&#91;3];\n\t\tfloat dPos2&#91;3];\n\t\tfloat dUV1&#91;2];\n\t\tfloat dUV2&#91;2];\n\t\tfor (int i = 0; i &lt; 3; i++) {\n\t\t\tdPos1&#91;i] = pV&#91;1]-&gt;aPos&#91;i] - pV&#91;0]-&gt;aPos&#91;i];\n\t\t\tdPos2&#91;i] = pV&#91;2]-&gt;aPos&#91;i] - pV&#91;0]-&gt;aPos&#91;i];\n\t\t}\n\t\tfor (int i = 0; i &lt; 2; i++) {\n\t\t\tdUV1&#91;i] = pV&#91;1]-&gt;aTuv2&#91;i] - pV&#91;0]-&gt;aTuv2&#91;i];\n\t\t\tdUV2&#91;i] = pV&#91;2]-&gt;aTuv2&#91;i] - pV&#91;0]-&gt;aTuv2&#91;i];\n\t\t}\n\n\t\tfloat tangent&#91;3];\n\t\tfloat binormal&#91;3];\n\t\tfloat divider = dUV1&#91;0] * dUV2&#91;1] - dUV1&#91;1] * dUV2&#91;0];\n\t\tif (divider == 0) {\n\t\t\tv3set(tangent, 1, 0, 0);\n\t\t\tv3set(binormal, 0, -1, 0);\n\t\t}\n\t\telse {\n\t\t\tfloat r = 1.0f \/ divider;\n\t\t\tfor (int i = 0; i &lt; 3; i++) {\n\t\t\t\ttangent&#91;i] = (dPos1&#91;i] * dUV2&#91;1] - dPos2&#91;i] * dUV1&#91;1]) * r;\n\t\t\t\tbinormal&#91;i] = -(dPos2&#91;i] * dUV1&#91;0] - dPos1&#91;i] * dUV2&#91;0]) * r;\n\t\t\t}\n\t\t\tvec3_norm(tangent, tangent);\n\t\t\tvec3_norm(binormal, binormal);\n\t\t}\n\t\t\/\/add to all 3 vertices\n\t\tfor (int n = 0; n &lt; 3; n++) {\n\t\t\tif (pV&#91;n]-&gt;flag &gt; 0)\n\t\t\t\tcontinue;\n\t\t\tv3copy(pV&#91;n]-&gt;aTangent, tangent);\n\t\t\tv3copy(pV&#91;n]-&gt;aBinormal, binormal);\n\t\t\tpV&#91;n]-&gt;flag = 1;\n\t\t}\n\t}\n\t\/\/normalize tangent and binormal around normal\n\tfor (int vN = 0; vN &lt; totalVertsN; vN++) {\n\t\tVertex01* pV = pUseVertices-&gt;at(vN);\n\t\tfloat v3out&#91;3];\n\t\t\/\/tangent\n\t\tvec3_mul_cross(v3out, pV-&gt;aNormal, pV-&gt;aBinormal);\n\t\tif (v3dotProduct(pV-&gt;aTangent, v3out) &lt; 0)\n\t\t\tv3inverse(v3out);\n\t\tv3copy(pV-&gt;aTangent, v3out);\n\t\t\/\/binormal\n\t\tvec3_mul_cross(v3out, pV-&gt;aNormal, pV-&gt;aTangent);\n\t\tif (v3dotProduct(pV-&gt;aBinormal, v3out) &lt; 0)\n\t\t\tv3inverse(v3out);\n\t\tv3copy(pV-&gt;aBinormal, v3out);\n\t}\n\treturn 1;\n}\nvoid ModelBuilder1base::lockGroup(ModelBuilder1base* pMB) {\n\tGroup01* pPrevGroup = pMB-&gt;pCurrentGroup;\n\tif (pMB-&gt;pCurrentGroup != NULL)\n\t\tpMB-&gt;groupsStack.push_back(pMB-&gt;pCurrentGroup);\n\tpMB-&gt;pCurrentGroup = new Group01();\n\tpMB-&gt;pCurrentGroup-&gt;fromVertexN = pMB-&gt;vertices.size();\n\tpMB-&gt;pCurrentGroup-&gt;fromTriangleN = pMB-&gt;triangles.size();\n\t\/\/marks\n\tif(pPrevGroup != NULL)\n\t\tif (strcmp(pPrevGroup-&gt;marks, &quot;&quot;) != 0)\n\t\t\tmyStrcpy_s(pMB-&gt;pCurrentGroup-&gt;marks, 124, pPrevGroup-&gt;marks);\n}\nvoid ModelBuilder1base::releaseGroup(ModelBuilder1base* pMB) {\n\tif (pMB-&gt;pLastClosedGroup != NULL)\n\t\tdelete pMB-&gt;pLastClosedGroup;\n\tpMB-&gt;pLastClosedGroup = pMB-&gt;pCurrentGroup;\n\n\tif (pMB-&gt;groupsStack.size() &gt; 0) {\n\t\tpMB-&gt;pCurrentGroup = pMB-&gt;groupsStack.back();\n\t\tpMB-&gt;groupsStack.pop_back();\n\t}\n\telse\n\t\tpMB-&gt;pCurrentGroup = NULL;\n}\nint ModelBuilder1base::finalizeLine(std::vector&lt;Vertex01*&gt;* pVerts, int lineStartsAt, int lineEndsAt) {\n\tif (lineEndsAt &lt;= 0)\n\t\tlineEndsAt = pVerts-&gt;size() - 1;\n\tVertex01* pV0 = pVerts-&gt;at(lineStartsAt);\n\tVertex01* pV2 = pVerts-&gt;at(lineEndsAt);\n\tbool closedLine = false;\n\tif (v3match(pV0-&gt;aPos, pV2-&gt;aPos))\n\t\tclosedLine = true;\n\tfor (int vN = lineStartsAt; vN &lt;= lineEndsAt; vN++) {\n\t\tVertex01* pV = pVerts-&gt;at(vN);\n\t\t\/\/prev point\n\t\tif (vN == lineStartsAt) {\n\t\t\t\/\/first point\n\t\t\tif (closedLine)\n\t\t\t\tpV0 = pVerts-&gt;at(lineEndsAt);\n\t\t\telse\n\t\t\t\tpV0 = NULL;\n\t\t}\n\t\telse\n\t\t\tpV0 = pVerts-&gt;at(vN - 1);\n\t\t\/\/next point\n\t\tif (vN == lineEndsAt) {\n\t\t\t\/\/last point\n\t\t\tif (closedLine)\n\t\t\t\tpV2 = pVerts-&gt;at(lineStartsAt);\n\t\t\telse\n\t\t\t\tpV2 = NULL;\n\t\t}\n\t\telse\n\t\t\tpV2 = pVerts-&gt;at(vN + 1);\n\t\t\/\/distances to neighbor points\n\t\tfloat distFromPrev = 0;\n\t\tfloat dirFromPrev&#91;3] = { 0,0,0 };\n\t\tif (pV0 != NULL) {\n\t\t\tdistFromPrev = v3lengthFromTo(pV0-&gt;aPos, pV-&gt;aPos);\n\t\t\tv3dirFromTo(dirFromPrev, pV0-&gt;aPos, pV-&gt;aPos);\n\t\t}\n\t\tfloat distToNext = 0;\n\t\tfloat dirToNext&#91;3] = { 0,0,0 };\n\t\tif (pV2 != NULL) {\n\t\t\tdistToNext = v3lengthFromTo(pV-&gt;aPos, pV2-&gt;aPos);\n\t\t\tv3dirFromTo(dirToNext, pV-&gt;aPos, pV2-&gt;aPos);\n\t\t}\n\t\tfloat distTotal = distFromPrev + distToNext;\n\t\tfloat kPrev = distFromPrev \/ distTotal;\n\t\tfloat kNext = distToNext \/ distTotal;\n\t\tif (kPrev &gt; kNext * 3)\n\t\t\tv3copy(pV-&gt;aNormal, dirFromPrev);\n\t\telse if (kNext &gt; kPrev * 3)\n\t\t\tv3copy(pV-&gt;aNormal, dirToNext);\n\t\telse\n\t\t\tfor (int i = 0; i &lt; 3; i++)\n\t\t\t\tpV-&gt;aNormal&#91;i] = kPrev * dirFromPrev&#91;i] + kNext * dirToNext&#91;i];\n\t\tvec3_norm(pV-&gt;aNormal, pV-&gt;aNormal);\n\t}\n\treturn 1;\n}\nint ModelBuilder1base::optimizeMesh(std::vector&lt;Vertex01*&gt;* pVertices, std::vector&lt;Triangle01*&gt;* pTriangles) {\n\tint trianglesN0 = pTriangles-&gt;size();\n\tif (trianglesN0 == 0)\n\t\treturn 0;\n\tint vertsN0 = pVertices-&gt;size();\n\t\/\/clear verts first for comparison\n\tfor (int vN = 0; vN &lt; vertsN0; vN++) {\n\t\tVertex01* pV = pVertices-&gt;at(vN);\n\t\tmyStrcpy_s(pV-&gt;marks, 124, &quot;&quot;);\n\t\tpV-&gt;altN = -1;\n\t\tpV-&gt;flag = 0;\n\t\tpV-&gt;aTangent&#91;0] = 0;\n\t\t\/\/round up\n\t\tfor (int i = 0; i &lt; 3; i++) {\n\t\t\tpV-&gt;aPos&#91;i] = round(pV-&gt;aPos&#91;i] * 1000.0) \/ 1000.0;\n\t\t\tpV-&gt;aNormal&#91;i] = round(pV-&gt;aNormal&#91;i] * 1000.0) \/ 1000.0;\n\t\t}\n \t}\n\t\/\/find the same verts\n\tint matchesN = 0;\n\tfor (int vN = 0; vN &lt; vertsN0-1; vN++) {\n\t\tVertex01* pV = pVertices-&gt;at(vN);\n\t\tif (pV-&gt;flag &lt; 0)\n\t\t\tcontinue;\n\t\tfor (int vN2 = vN+1; vN2 &lt; vertsN0; vN2++) {\n\t\t\tVertex01* pV2 = pVertices-&gt;at(vN2);\n\t\t\tif (pV2-&gt;flag &lt; 0)\n\t\t\t\tcontinue;\n\t\t\tif (memcmp(pV, pV2, sizeof(Vertex01)) != 0)\n\t\t\t\tcontinue;\n\t\t\t\/\/if here - verts are equal\n\t\t\tpV2-&gt;flag = -1;\n\t\t\tmatchesN++;\n\t\t\t\/\/change refs in useTriangles from vN2 to vN\n\t\t\tfor (int tN = 0; tN &lt; trianglesN0; tN++) {\n\t\t\t\tTriangle01* pT = pTriangles-&gt;at(tN);\n\t\t\t\tfor (int i = 0; i &lt; 3; i++)\n\t\t\t\t\tif (pT-&gt;idx&#91;i] == vN2)\n\t\t\t\t\t\tpT-&gt;idx&#91;i] = vN;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (matchesN == 0)\n\t\treturn 0;\n\t\/\/unflag all verts\n\tfor (int vN = 0; vN &lt; vertsN0 - 1; vN++) {\n\t\tVertex01* pV = pVertices-&gt;at(vN);\n\t\tpV-&gt;flag = -1;\n\t}\n\t\/\/flag verts in use\n\tfor (int tN = 0; tN &lt; trianglesN0; tN++) {\n\t\tTriangle01* pT = pTriangles-&gt;at(tN);\n\t\tfor (int i = 0; i &lt; 3; i++) {\n\t\t\tint vN = pT-&gt;idx&#91;i];\n\t\t\tVertex01* pV = pVertices-&gt;at(vN);\n\t\t\tpV-&gt;flag = 0;\n\t\t}\n\t}\n\t\/\/save original useVertices copy\n\tstd::vector&lt;Vertex01*&gt; oldVertices;\n\tfor (int vN = 0; vN &lt; vertsN0; vN++) {\n\t\tVertex01* pV = pVertices-&gt;at(vN);\n\t\toldVertices.push_back(pV);\n\t}\n\tpVertices-&gt;clear();\n\t\/\/copy back only verts in use\n\tfor (int vN = 0; vN &lt; vertsN0; vN++) {\n\t\tVertex01* pV = oldVertices.at(vN);\n\t\tif (pV-&gt;flag &lt; 0)\n\t\t\tcontinue;\n\t\tpV-&gt;altN = pVertices-&gt;size();\n\t\tpVertices-&gt;push_back(pV);\n\t}\n\t\/\/re-factor triangles\n\trearrangeArraysForDrawJob(&amp;oldVertices, pVertices, pTriangles);\n\toldVertices.clear();\n\treturn pVertices-&gt;size();\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Made a surprising (to me) discovery: when passing <em>vector <\/em>as a parameter, receiving function <strong>can <\/strong>access and modify vector&#8217;s elements as usual, which gives an illusion that you are working with original vector, but you are not, it&#8217;s a COPY. So, when you are modifying vector itself (adding or removing elements), it won&#8217;t affect an original vector. Before we just didn&#8217;t need to modify vectors themselves (inside of such functions). Therefore, instead of passing vectors I switched to passing vectors&#8217; addresses (pointers). So, <em>optimizeMesh(..)<\/em> is not the only change here.<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>4. Build and run. <\/p>\n\n\n\n<p>The image is the same, but now instead of  316 vertices we have only 219, <strong>31% less<\/strong>, even better than I expected.<\/p>\n\n\n\n<p>Details:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Root box + excise mark (it&#8217;s 1 DrawJob): initially 108 verts, 42 redundancies<\/li><li>Gilded prints, 5 projections: 20 verts, no redundancies<\/li><li>Blazon, 2 projections: 8 verts, no redundancies<\/li><li>Marlboro sign, 2 projections: 8 verts, no redundancies<\/li><li>Slit, 5 projections: initially 64 verts, 9 redundancies<\/li><li>Sealing ribbon (as a line): 12 verts, no redundancies<\/li><li>Clear-film, front, back, left and right sides:  initially 32 verts, 14 redundancies<\/li><li>Clear-film top\/bottom (with normal maps): initially 64 verts, 32 redundancies<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Off-topic:<\/p>\n\n\n\n<p>While working on this chapter, tried to run clear-film box only (for debugging). Actually, deserves a separate video:<\/p>\n\n\n\n<iframe loading=\"lazy\" width=\"100%\" height=\"400\" src=\"https:\/\/www.youtube.com\/embed\/3skwlNtXQG4?controls=0&amp;autoplay=1&amp;loop=1&amp;playlist=3skwlNtXQG4\" title=\"Clear film\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen=\"\"><\/iframe>\n\n\n\n<p><\/p>\n\n\n\n<p>Impressive, isn&#8217;t it?<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p class=\"mb-2\">Though the model is completely ready now, still we have 1 more issue to resolve: Currently our model consists of 316 vertices and 144 triangles (432 indices). Actually, not too much comparing to 3D editors. But still, how come so many?? Well, root box for instance: 6 sides, 4 vertices each = 24 vertices 12 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-1252","post","type-post","status-publish","format-standard","hentry","category-cross-platform-3d"],"_links":{"self":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/1252","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/comments?post=1252"}],"version-history":[{"count":33,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/1252\/revisions"}],"predecessor-version":[{"id":1377,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/1252\/revisions\/1377"}],"wp:attachment":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media?parent=1252"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=1252"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=1252"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}