{"id":1003,"date":"2021-12-21T00:34:48","date_gmt":"2021-12-21T00:34:48","guid":{"rendered":"https:\/\/writingagame.com\/?p=1003"},"modified":"2021-12-28T21:27:09","modified_gmt":"2021-12-28T21:27:09","slug":"chapter-32-marked-vertices-groups","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2021\/12\/21\/chapter-32-marked-vertices-groups\/","title":{"rendered":"Chapter 32. Marked vertices groups"},"content":{"rendered":"\n<p>Next I want to &#8220;draw&#8221; the joint (slit) between the pack and the lid, like a small <em>normal map<\/em> applied to existing textured mesh&#8217;s <strong>fragment<\/strong>, not to entire mesh and not to <em>VirtualShape <\/em>as we did before. This will require a few new concepts.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Windows<\/h2>\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>The first new concept &#8211; <strong>marked vertices\/triangles groups<\/strong>, so later we can address certain mesh by the name (mark).<\/p>\n\n\n\n<p>2. Open <em>Group01.h<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [6]; title: ; notranslate\" title=\"\">\n#pragma once\n\nclass Group01\n{\npublic:\n\tchar marks&#91;124] = &quot;&quot;;\n\tint fromVertexN = 0;\n\tint fromTriangleN = 0;\n};\n\n<\/pre><\/div>\n\n\n<p> <\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>3. Open <em>Vertex01.h<\/em> and replace code by: <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [6]; title: ; notranslate\" title=\"\">\n#pragma once\n\nclass Vertex01\n{\npublic:\n\tchar marks&#91;124] = &quot;&quot;;\n\tint subjN = -1; \/\/game subject number\n\tint materialN = -1; \/\/material number\n\tint flag = 0;\n\tint endOfSequence = 0; \/\/for sequentional (unindexed) primitives (like GL_LINE_STRIP for example)\n\tint altN = -1; \/\/vertex&#039; position in alternative array\n\t\/\/atributes\n\tfloat aPos&#91;4] = { 0,0,0,0 }; \/\/position x,y,z + 4-th float for matrix operations\n\tfloat aNormal&#91;4] = { 0,0,0,0 }; \/\/normal (surface reflection vector) x,y,z + 4-th float for matrix operations\n\tfloat aTuv&#91;2] = { 0,0 }; \/\/2D texture coordinates\n\tfloat aTuv2&#91;2] = { 0,0 }; \/\/for normal maps\n\t\/\/tangent space (for normal maps)\n\tfloat aTangent&#91;3] = { 0,0,0 };\n\tfloat aBinormal&#91;3] = { 0,0,0 };\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>4. Open <em>Triangle01.h<\/em> and replace code by: <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [6]; title: ; notranslate\" title=\"\">\n#pragma once\n\nclass Triangle01\n{\npublic:\n\tchar marks&#91;124] = &quot;&quot;;\n\tint subjN = -1; \/\/game subject number\n\tint materialN = -1; \/\/material number\n\tint flag = 0;\n\tint idx&#91;3] = { 0,0,0 }; \/\/3 vertex indices\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p><strong>GroupTransform<\/strong> class will hold all info about what we are selecting and what we want to do with this selection.<\/p>\n\n\n\n<p>5. Open <em>GroupTransform.h<\/em> and replace code by: <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#pragma once\n#include &lt;stdlib.h&gt;\n#include &quot;ModelBuilder1base.h&quot;\n\nclass GroupTransform\n{\npublic:\n\tfloat shift&#91;4] = { 0,0,0,0 };\n\tfloat spin&#91;3] = { 0,0,0 }; \/\/in degrees\n\tfloat scale&#91;3] = { 1,1,1 };\n\tstd::string allign = &quot;&quot;;\n\tstd::string headZto = &quot;&quot;;\n\tstd::string onThe = &quot;&quot;; \/\/left\/right\/etc.\n\t\/\/limit to\n\tGroup01* pGroup = NULL; \/\/NULL-all, can also be pCurrentGroup or pLastClosedGroup\n\tchar mark&#91;32] = &quot;&quot;; \/\/must be in &lt;&gt;\n\tfloat pMin&#91;3]{ -1000000 ,-1000000 ,-1000000 };\n\tfloat pMax&#91;3]{ 1000000 , 1000000 , 1000000 };\npublic:\n\tint executeGroupTransform(ModelBuilder1base* pMB) { return executeGroupTransform(pMB, this); };\n\tstatic int executeGroupTransform(ModelBuilder1base* pMB, GroupTransform* pGT);\n\t\/\/set limits\n\tstatic void limit2mark(GroupTransform* pGT, std::string mark0);\n\tstatic int flagSelection(GroupTransform* pGT, std::vector&lt;Vertex01*&gt;* pVertices, std::vector&lt;Triangle01*&gt;* pTriangles);\n\tstatic int cloneFlagged(ModelBuilder1base* pMB,\n\t\tstd::vector&lt;Vertex01*&gt;* pVxDst, std::vector&lt;Triangle01*&gt;* pTrDst,\n\t\tstd::vector&lt;Vertex01*&gt;* pVxSrc, std::vector&lt;Triangle01*&gt;* pTrSrc);\n\tstatic int refactorTriangles(std::vector&lt;Triangle01*&gt;* pTrDst, int trianglesN0dst, std::vector&lt;Vertex01*&gt;* pVxSrc);\n\tstatic int transformFlagged(GroupTransform* pGT, std::vector&lt;Vertex01*&gt;* pVxDst);\n\tstatic int transformFlaggedMx(std::vector&lt;Vertex01*&gt;* pVx, mat4x4* pTransformMatrix);\n\n\tstatic int buildTransformMatrix(GroupTransform* pGT, mat4x4* pTransformMatrix);\n\tstatic void flagAll(std::vector&lt;Vertex01*&gt;* pVx, std::vector&lt;Triangle01*&gt;* pTr);\n\tstatic int buildBoundingBoxFlagged(float* bbMin, float* bbMax, std::vector&lt;Vertex01*&gt;* pVx);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>6. Open <em>GroupTransform.cpp<\/em> and replace code by:  <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#include &quot;GroupTransform.h&quot;\n#include &quot;utils.h&quot;\n\nextern float degrees2radians;\n\nint GroupTransform::executeGroupTransform(ModelBuilder1base* pMB, GroupTransform* pGT) {\n\t\/\/pMB-&gt;moveGroupDg(pMB, pGT-&gt;spin&#91;0], pGT-&gt;spin&#91;1], pGT-&gt;spin&#91;2], pGT-&gt;shift&#91;0], pGT-&gt;shift&#91;1], pGT-&gt;shift&#91;2]);\n\tflagSelection(pGT, &amp;pMB-&gt;vertices, &amp;pMB-&gt;triangles);\n\ttransformFlagged(pGT, &amp;pMB-&gt;vertices);\n\treturn 1;\n}\nvoid GroupTransform::limit2mark(GroupTransform* pGT, std::string mark0) {\n\tstd::string outStr;\n\toutStr.append(&quot;&lt;&quot; + mark0 + &quot;&gt;&quot;);\n\tmyStrcpy_s(pGT-&gt;mark, 32, outStr.c_str());\n}\nint GroupTransform::flagSelection(GroupTransform* pGT, std::vector&lt;Vertex01*&gt;* pVertices, std::vector&lt;Triangle01*&gt;* pTriangles) {\n\tif (pVertices != NULL) {\n\t\tbool checkSize = false;\n\t\tfor (int i = 0; i &lt; 3; i++) {\n\t\t\tif (pGT-&gt;pMin&#91;i] &gt; -1000000) {\n\t\t\t\tcheckSize = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (pGT-&gt;pMax&#91;i] &lt; 1000000) {\n\t\t\t\tcheckSize = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tint totalN = pVertices-&gt;size();\n\t\tfor (int vN = 0; vN &lt; totalN; vN++) {\n\t\t\tVertex01* pV = pVertices-&gt;at(vN);\n\t\t\tif (pGT-&gt;pGroup != NULL) {\n\t\t\t\tif (vN &lt; pGT-&gt;pGroup-&gt;fromVertexN) {\n\t\t\t\t\tpV-&gt;flag = -1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (strcmp(pGT-&gt;mark, &quot;&quot;) != 0) {\n\t\t\t\tif (strstr(pV-&gt;marks, pGT-&gt;mark) == nullptr) {\n\t\t\t\t\tpV-&gt;flag = -1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (checkSize) {\n\t\t\t\tbool fits = true;\n\t\t\t\tfor (int i = 0; i &lt; 3; i++) {\n\t\t\t\t\tif (pV-&gt;aPos&#91;i] &lt; pGT-&gt;pMin&#91;i]) {\n\t\t\t\t\t\tfits = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tif (pV-&gt;aPos&#91;i] &gt; pGT-&gt;pMax&#91;i]) {\n\t\t\t\t\t\tfits = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!fits) {\n\t\t\t\t\tpV-&gt;flag = -1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tpV-&gt;flag = 0;\n\t\t}\n\t}\n\tif (pTriangles != NULL) {\n\t\tint totalN = pTriangles-&gt;size();\n\t\tfor (int tN = 0; tN &lt; totalN; tN++) {\n\t\t\tTriangle01* pT = pTriangles-&gt;at(tN);\n\t\t\tif (pGT-&gt;pGroup != NULL) {\n\t\t\t\tif (tN &lt; pGT-&gt;pGroup-&gt;fromTriangleN) {\n\t\t\t\t\tpT-&gt;flag = -1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (strcmp(pGT-&gt;mark, &quot;&quot;) != 0) {\n\t\t\t\tif (strstr(pT-&gt;marks, pGT-&gt;mark) == nullptr) {\n\t\t\t\t\tpT-&gt;flag = -1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tpT-&gt;flag = 0;\n\t\t}\n\t}\n\treturn 1;\n}\nint GroupTransform::cloneFlagged(ModelBuilder1base* pMB,\n\tstd::vector&lt;Vertex01*&gt;* pVxDst, std::vector&lt;Triangle01*&gt;* pTrDst,\n\tstd::vector&lt;Vertex01*&gt;* pVxSrc, std::vector&lt;Triangle01*&gt;* pTrSrc) {\n\tint vertsNsrc = pVxSrc-&gt;size();\n\tint trianglesNsrc = pTrSrc-&gt;size();\n\tint vertsN0dst = pVxDst-&gt;size();\n\tint trianglesN0dst = pTrDst-&gt;size();\n\tfor (int i = 0; i &lt; vertsNsrc; i++) {\n\t\tVertex01* pV0 = pVxSrc-&gt;at(i);\n\t\tif (pV0-&gt;flag &lt; 0)\n\t\t\tcontinue;\n\t\tpV0-&gt;altN = pVxDst-&gt;size();\n\t\tVertex01* pV = new Vertex01(*pV0);\n\t\tpVxDst-&gt;push_back(pV);\n\t\t\/\/overwrite marks\n\t\tif (pMB != NULL) {\n\t\t\tmyStrcpy_s(pV-&gt;marks, 124, pMB-&gt;pCurrentGroup-&gt;marks);\n\t\t\tpV-&gt;subjN = pMB-&gt;usingSubjN;\n\t\t}\n\t\tpV-&gt;flag = -1;\n\t}\n\tfor (int i = 0; i &lt; trianglesNsrc; i++) {\n\t\tTriangle01* pT0 = pTrSrc-&gt;at(i);\n\t\tif (pT0-&gt;flag &lt; 0)\n\t\t\tcontinue;\n\t\tTriangle01* pT = new Triangle01(*pT0);\n\t\tpTrDst-&gt;push_back(pT);\n\t\t\/\/overwrite marks\n\t\tif (pMB != NULL) {\n\t\t\tmyStrcpy_s(pT-&gt;marks, 124, pMB-&gt;pCurrentGroup-&gt;marks);\n\t\t\tpT-&gt;subjN = pMB-&gt;usingSubjN;\n\t\t}\n\t\tpT-&gt;flag = -1;\n\t}\n\trefactorTriangles(pTrDst, trianglesN0dst, pVxSrc);\n\treturn 1;\n}\nint GroupTransform::refactorTriangles(std::vector&lt;Triangle01*&gt;* pTrDst, int trianglesN0dst, std::vector&lt;Vertex01*&gt;* pVxSrc){\n\t\/\/re-factor triangles idx, adjusting triangles verts #s\n\tint trianglesNdst = pTrDst-&gt;size();\n\tfor (int tN = trianglesN0dst; tN &lt; trianglesNdst; tN++) {\n\t\tTriangle01* pT = pTrDst-&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 = pVxSrc-&gt;at(vN);\n\t\t\tpT-&gt;idx&#91;i] = pV-&gt;altN;\n\t\t}\n\t}\n\treturn 1;\n}\nint GroupTransform::buildTransformMatrix(GroupTransform* pGT, mat4x4* pTransformMatrix) {\n\tmat4x4_identity(*pTransformMatrix);\n\tif (!pGT-&gt;onThe.empty()) {\n\t\tif (pGT-&gt;onThe.compare(&quot;back&quot;) == 0)\n\t\t\tmat4x4_rotate_Y(*pTransformMatrix, *pTransformMatrix, degrees2radians * 180);\n\t\telse if (pGT-&gt;onThe.compare(&quot;left&quot;) == 0)\n\t\t\tmat4x4_rotate_Y(*pTransformMatrix, *pTransformMatrix, -degrees2radians * 90);\n\t\telse if (pGT-&gt;onThe.compare(&quot;right&quot;) == 0)\n\t\t\tmat4x4_rotate_Y(*pTransformMatrix, *pTransformMatrix, degrees2radians * 90);\n\t\telse if (pGT-&gt;onThe.compare(&quot;top&quot;) == 0) {\n\t\t\tmat4x4_rotate_Y(*pTransformMatrix, *pTransformMatrix, degrees2radians * 180);\n\t\t\tmat4x4_rotate_X(*pTransformMatrix, *pTransformMatrix, -degrees2radians * 90);\n\t\t}\n\t\telse if (pGT-&gt;onThe.compare(&quot;bottom&quot;) == 0)\n\t\t\tmat4x4_rotate_X(*pTransformMatrix, *pTransformMatrix, degrees2radians * 90);\n\t}\n\tif(!v3equals(pGT-&gt;shift, 0))\n\t\tmat4x4_translate_in_place(*pTransformMatrix, pGT-&gt;shift&#91;0], pGT-&gt;shift&#91;1], pGT-&gt;shift&#91;2]);\n\n\t\/\/rotation order: Z-X-Y\n\tif (pGT-&gt;spin&#91;1] != 0)\n\t\tmat4x4_rotate_Y(*pTransformMatrix, *pTransformMatrix, degrees2radians * pGT-&gt;spin&#91;1]);\n\tif (pGT-&gt;spin&#91;0] != 0)\n\t\tmat4x4_rotate_X(*pTransformMatrix, *pTransformMatrix, degrees2radians * pGT-&gt;spin&#91;0]);\n\tif (pGT-&gt;spin&#91;2] != 0)\n\t\tmat4x4_rotate_Z(*pTransformMatrix, *pTransformMatrix, degrees2radians * pGT-&gt;spin&#91;2]);\n\n\tif (!v3equals(pGT-&gt;scale, 1))\n\t\tmat4x4_scale_aniso(*pTransformMatrix, *pTransformMatrix, pGT-&gt;scale&#91;0], pGT-&gt;scale&#91;1], pGT-&gt;scale&#91;2]);\n\treturn 1;\n}\nint GroupTransform::transformFlagged(GroupTransform* pGT, std::vector&lt;Vertex01*&gt;* pVx) {\n\t\/\/moves and rotates vertex group\n\t\/\/rotation angles are set in degrees\n\tmat4x4 transformMatrix;\n\tbuildTransformMatrix(pGT, &amp;transformMatrix);\n\ttransformFlaggedMx(pVx, &amp;transformMatrix);\n\treturn 1;\n}\nint GroupTransform::transformFlaggedMx(std::vector&lt;Vertex01*&gt;* pVx, mat4x4* pTransformMatrix) {\n\t\/\/moves and rotates vertex group\n\tint vertsN = pVx-&gt;size();\n\tfor (int i = 0; i &lt; vertsN; i++) {\n\t\tVertex01* pV = pVx-&gt;at(i);\n\t\tif (pV-&gt;flag &lt; 0)\n\t\t\tcontinue;\n\t\tmat4x4_mul_vec4plus(pV-&gt;aPos, *pTransformMatrix, pV-&gt;aPos, 1);\n\t\t\/\/normal\n\t\tmat4x4_mul_vec4plus(pV-&gt;aNormal, *pTransformMatrix, pV-&gt;aNormal, 0);\n\t\tvec3_norm(pV-&gt;aNormal, pV-&gt;aNormal);\n\t}\n\treturn 1;\n}\nvoid GroupTransform::flagAll(std::vector&lt;Vertex01*&gt;* pVx, std::vector&lt;Triangle01*&gt;* pTr) {\n\t\/\/set flags\n\tfor (int i = pVx-&gt;size() - 1; i &gt;= 0; i--)\n\t\tpVx-&gt;at(i)-&gt;flag = 0;\n\tfor (int i = pTr-&gt;size() - 1; i &gt;= 0; i--)\n\t\tpTr-&gt;at(i)-&gt;flag = 0;\n}\nint GroupTransform::buildBoundingBoxFlagged(float* bbMin, float* bbMax, std::vector&lt;Vertex01*&gt;* pVx) {\n\tv3copy(bbMin, pVx-&gt;at(0)-&gt;aPos);\n\tv3copy(bbMax, pVx-&gt;at(0)-&gt;aPos);\n\t\/\/scan all flagged except #0\n\tfor (int vN = pVx-&gt;size() - 1; vN &gt; 0; vN--) {\n\t\tVertex01* pV = pVx-&gt;at(vN);\n\t\tif (pV-&gt;flag &lt; 0)\n\t\t\tcontinue;\n\t\tfor(int i=0;i&lt;3;i++){\n\t\t\tif (bbMin&#91;i] &gt; pV-&gt;aPos&#91;i])\n\t\t\t\tbbMin&#91;i] = pV-&gt;aPos&#91;i];\n\t\t\tif (bbMax&#91;i] &lt; pV-&gt;aPos&#91;i])\n\t\t\t\tbbMax&#91;i] = pV-&gt;aPos&#91;i];\n\t\t}\n\t}\n\treturn 1;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>To test it we will add to <strong>root01.txt<\/strong> couple commands.<\/p>\n\n\n\n<p>7. Copy following code to a <strong>Text Editor<\/strong> and save it (overwrite) to\/as<\/p>\n\n\n\n<p><em>C:\\CPP\\a997modeler\\dt\\models\\misc\\marlboro01\\root01.txt<\/em><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; highlight: [6,32]; title: ; notranslate\" title=\"\">\n&lt;texture_as=&quot;tx0&quot; src=&quot;marlboro03small.png&quot; ckey=&quot;#00ff00&quot;\/&gt;\n&lt;mt_type=&quot;phong&quot; uTex0_use=&quot;tx0&quot; \/&gt;\n&lt;vs=&quot;box_tank&quot; whl=&quot;53,83,21&quot; ext=1 sectR=1 \/&gt;\n&lt;a=&quot;front v&quot; xywh=&quot;2,1,323,495&quot;\/&gt;\n&lt;a=&quot;back v&quot;  xywh=&quot;2,1,323,495&quot;\/&gt;\n&lt;a=&quot;right all&quot; xywh=&quot;327,1,128,495&quot; mark=&quot;box_right&quot;\/&gt;\n&lt;a=&quot;left all&quot; xywh=&quot;457,1,128,495&quot;\/&gt;\n&lt;a=&quot;top&quot; xywh=&quot;588,1,323,133&quot;\/&gt;\n&lt;a=&quot;bottom&quot; xywh=&quot;587,136,324,134&quot;\/&gt;\n\/\/golden prints\n&lt;vs=&quot;box&quot; whl=&quot;55.1,85.1,23.1&quot; \/&gt;\n&lt;texture_as=&quot;whitenoise&quot; src=&quot;\/dt\/common\/img\/whitenoise\/wn64_blur3.bmp&quot;\/&gt;\n&lt;texture_as=&quot;gold&quot; src=&quot;\/dt\/common\/img\/materials\/gold02roman.bmp&quot; \/&gt;\n&lt;mt_type=&quot;mirror&quot; uAlphaBlending uTex1mask_use=&quot;tx0&quot; uTex1alphaChannelN=1 uTex0_use=&quot;whitenoise&quot; uTex0translateChannelN=0 uTex3_use=&quot;gold&quot; \/&gt;\n\/\/side golden prints\n&lt;a=&quot;right&quot; xywh=&quot;342,12,101,10&quot; whl=&quot;x,1.8,18.1&quot; pxyz=&quot;x,39.8, -0.3&quot; \/&gt; \/\/Please do not litter\n&lt;a=&quot;right&quot; xywh=&quot;339,144,105,89&quot; whl=&quot;x,15.35,18.9&quot; pxyz=&quot;x,10.3,-0.12&quot; \/&gt; \/\/For special offers...\n&lt;a=&quot;left&quot; xywh=&quot;475,15,95,48&quot; whl=&quot;x,8.4,17&quot; pxyz=&quot;x,36, 0.3&quot; \/&gt; \/\/Underage sale...\n\/\/front prints\n&lt;group&gt;\n\t\/\/bottom golden print &quot;20 class a...&quot;\n\t&lt;a=&quot;front&quot; xywh=&quot;20,498,289,13&quot; whl=&quot;47.5,2,x&quot; pxyz=&quot;1,-36,x&quot; \/&gt;\n\t\/\/blazon\/emblem\n\t&lt;mt_type=&quot;mirror&quot; uAlphaBlending uTex2nm_use=&quot;tx0&quot; uTex0_use=&quot;whitenoise&quot; uTex0translateChannelN=0 uTex3_use=&quot;gold&quot; \/&gt;\n\t&lt;a=&quot;front&quot; xywh2nm=&quot;589,415,128,94&quot; whl=&quot;20.7,16,x&quot; pxyz=&quot;0.3,6.1,x&quot; \/&gt; \/\/emblem\n\t\/\/&quot;Marlboro\n\t&lt;mt_type=&quot;phong&quot; uAlphaBlending uTex2nm_use=&quot;tx0&quot; uColor=&quot;#1E211E&quot; \/&gt;\n\t&lt;a=&quot;front&quot; xywh2nm=&quot;590,275,301,136&quot; whl=&quot;49.2,23.3,x&quot; pxyz=&quot;0.21,-18,x&quot; \/&gt; \/\/marlboro\n&lt;\/group&gt; \n&lt;clone ay=180 \/&gt;\n\n&lt;do px=5 all markedAs=&quot;box_right&quot; \/&gt;\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>In line 6 we are marking right side as &#8220;box_right&#8221;.<\/li><li>All vertices and triangles for this side will have this mark.<\/li><li>In line 32 we are saying to move everything marked as &#8220;box_right&#8221; to the right  (x coordinate) by 5 units.<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>In order to accept new parameters, <strong>XMLParser,<\/strong> <strong>ModelBuilder1base <\/strong>and <strong>ModelLoader <\/strong>are modified a bit.<\/p>\n\n\n\n<p>8. Open <em>XMLParser.cpp<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [180,181,182,183,184,185,201,202,203,204,205,206]; title: ; notranslate\" title=\"\">\n#include &quot;XMLparser.h&quot;\n#include &quot;platform.h&quot;\n#include &quot;utils.h&quot;\n#include &quot;MyColor.h&quot;\n#include &lt;vector&gt;\n\nextern std::string filesRoot;\n\nint XMLparser::removeComments(XMLparser* pXP) {\n\t\/\/find all occurances of &quot;\/*&quot;\n\tstd::vector&lt;char*&gt; commentsStarts;\n\t\/\/ \/* comments *\/\n\tchar* scanFrom = pXP-&gt;pData;\n\twhile (1) {\n\t\tchar* commentStarts = strstr(scanFrom, &quot;\/*&quot;);\n\t\tif (commentStarts == NULL)\n\t\t\tbreak;\n\t\tcommentsStarts.push_back(commentStarts);\n\t\tscanFrom = commentStarts + 2;\n\t}\n\t\/\/here we have a list of \/* comments *\/\n\twhile(commentsStarts.size() &gt; 0){\n\t\t\/\/get last comment\n\t\tchar* commentStarts = commentsStarts.back();\n\t\tcommentsStarts.pop_back();\n\t\tchar* commentEnds = strstr(commentStarts, &quot;*\/&quot;);\n\t\tint commentLength = (int)(commentEnds - commentStarts) + 2;\n\t\t\/\/shift text left\n\t\tmyStrcpy_s(commentStarts, pXP-&gt;dataSize - (int)(commentStarts - pXP-&gt;pData), commentStarts + commentLength);\n\t\tpXP-&gt;dataSize -= commentLength;\n\/\/mylog(&quot;======\\n%s----------\\n&quot;, pXP-&gt;pData);\n\t}\n\t\/\/ \/\/line comments\n\tscanFrom = pXP-&gt;pData;\n\twhile (1) {\n\t\tchar* commentStarts = strstr(scanFrom, &quot;\/\/&quot;);\n\t\tif (commentStarts == NULL)\n\t\t\tbreak;\n\t\tchar* commentEnds = strstr(commentStarts, &quot;\\n&quot;);\n\t\tint commentLength = (int)(commentEnds - commentStarts) + 1;\n\t\t\/\/shift text left\n\t\tmyStrcpy_s(commentStarts, pXP-&gt;dataSize - (int)(commentStarts - pXP-&gt;pData), commentStarts + commentLength);\n\t\tpXP-&gt;dataSize -= commentLength;\n\t\tscanFrom = commentStarts;\n\/\/mylog(&quot;======\\n%s----------\\n&quot;, pXP-&gt;pData);\n\t}\n\t\/\/ &lt;!-- comments --&gt;\n\tscanFrom = pXP-&gt;pData;\n\twhile (1) {\n\t\tchar* commentStarts = strstr(scanFrom, &quot;&lt;!--&quot;);\n\t\tif (commentStarts == NULL)\n\t\t\tbreak;\n\t\tcommentsStarts.push_back(commentStarts);\n\t\tscanFrom = commentStarts + 4;\n\t}\n\t\/\/here we have a list of &lt;!-- comments --&gt;\n\twhile (commentsStarts.size() &gt; 0) {\n\t\t\/\/get last comment\n\t\tchar* commentStarts = commentsStarts.back();\n\t\tcommentsStarts.pop_back();\n\t\tchar* commentEnds = strstr(commentStarts, &quot;--&gt;&quot;);\n\t\tint commentLength = (int)(commentEnds - commentStarts) + 3;\n\t\t\/\/shift text left\n\t\tmyStrcpy_s(commentStarts, pXP-&gt;dataSize - (int)(commentStarts - pXP-&gt;pData), commentStarts + commentLength);\n\t\tpXP-&gt;dataSize -= commentLength;\n\/\/mylog(&quot;======\\n%s----------\\n&quot;, pXP-&gt;pData);\n\t}\n\treturn 1;\n}\nbool XMLparser::nextTag(XMLparser* pXP) {\n\t\/\/returns 0 if no more tags, 1 - tag extracted\n\tchar* tagStarts = strstr(pXP-&gt;readFrom, &quot;&lt;&quot;);\n\tif (tagStarts == NULL)\n\t\treturn false;\n\tpXP-&gt;readFrom++;\n\tchar* tagEnds = strstr(pXP-&gt;readFrom, &quot;&gt;&quot;);\n\tif (tagEnds == NULL)\n\t\treturn false;\n\tpXP-&gt;readFrom = tagEnds + 1;\n\tpXP-&gt;tagLength = (int)(tagEnds - tagStarts) + 1;\n\tpXP-&gt;currentTag.assign(tagStarts, pXP-&gt;tagLength);\n\treturn true;\n} \nint XMLparser::nameEndsAt(std::string varName, std::string tag) {\n\tint scanFrom = 0;\n\tint nameLength = varName.length();\n\tstd::string optsBefore = &quot;&lt; &quot;;\n\tstd::string optsAfter = &quot; =\/&gt;\\n&quot;;\n\twhile (1) {\n\t\tint varStartsAt = tag.find(varName, scanFrom);\n\t\tif (varStartsAt == std::string::npos)\n\t\t\treturn -1;\n\t\tscanFrom = varStartsAt + nameLength;\n\t\tchar charBefore = tag.at(varStartsAt - 1);\n\t\tif (optsBefore.find(charBefore) == std::string::npos)\n\t\t\tcontinue;\n\t\tchar charAfter = tag.at(scanFrom);\n\t\tif (optsAfter.find(charAfter) == std::string::npos)\n\t\t\tcontinue;\n\t\treturn scanFrom;\n\t}\n}\nint XMLparser::processSource(XMLparser* pXP) {\n\twhile (pXP-&gt;nextTag()) {\n\t\t\/\/extract tagName\n\t\tint nameStart = pXP-&gt;currentTag.find_first_not_of(&quot; &lt;&quot;);\n\t\tint nameEnd = pXP-&gt;currentTag.find_first_of(&quot; =\/&gt;\\n&quot;, nameStart+1);\n\t\tpXP-&gt;tagName = pXP-&gt;currentTag.substr(nameStart, (nameEnd - nameStart));\n\t\t\/\/open\/closed tag\n\t\tchar lastChar = pXP-&gt;currentTag.at(pXP-&gt;tagLength - 2);\n\t\tpXP-&gt;closedTag = (lastChar == &#039;\/&#039;);\n\/\/mylog(&quot;&#91;%s] &#91;%s] closed=%d nameStart=%d nameEnd=%d\\n&quot;, pXP-&gt;currentTag.c_str(), pXP-&gt;tagName.c_str(), pXP-&gt;closedTag, nameStart, nameEnd);\n\t\tpXP-&gt;processTag();\n\t}\n\treturn 1;\n}\nstd::string XMLparser::buildFullPath(XMLparser* pXP, std::string filePath) {\n\tif (filePath.at(0) != &#039;\/&#039;)\n\t\tfilePath = pXP-&gt;inAppFolder + filePath;\n\treturn (filesRoot + filePath);\n}\n\nstd::string XMLparser::getStringValue(std::string varName, std::string tag) {\n\t\/\/returns std::string\n\tint valueStartsAt = nameEndsAt(varName, tag);\n\tif (valueStartsAt &lt; 0)\n\t\treturn &quot;&quot;; \/\/var not found\n\tvalueStartsAt = tag.find_first_not_of(&quot; &quot;, valueStartsAt);\n\tchar c = tag.at(valueStartsAt);\n\tif (c != &#039;=&#039;)\n\t\treturn &quot;&quot;; \/\/var exists, but value not set\n\tvalueStartsAt = tag.find_first_not_of(&quot; &quot;, valueStartsAt + 1);\n\tc = tag.at(valueStartsAt);\n\tstd::string optsQuote = &quot;\\&quot;&#039;&quot;;\n\tint valueEndsAt = 0;\n\tif (optsQuote.find(c) != std::string::npos) {\n\t\t\/\/the value is in quotes\n\t\tvalueStartsAt++;\n\t\tvalueEndsAt = tag.find(c, valueStartsAt);\n\t}\n\telse { \/\/value not quoted\n\t\tvalueEndsAt = tag.find_first_of(&quot; \/&gt;\\n&quot;, valueStartsAt);\n\t}\n\treturn tag.substr(valueStartsAt, valueEndsAt - valueStartsAt);\n}\n\nbool XMLparser::varExists(std::string varName, std::string tag) {\n\tint valueStartsAt = nameEndsAt(varName, tag);\n\tif (valueStartsAt &lt; 0)\n\t\treturn false; \/\/var not found\n\treturn true;\n}\nint XMLparser::setCharsValue(char* pChars, int charsLength, std::string varName, std::string tag) {\n\tif (!varExists(varName, tag))\n\t\treturn 0; \/\/var not found\n\tstd::string val = getStringValue(varName, tag);\n\tmyStrcpy_s(pChars, charsLength, (char*)val.c_str());\n\treturn 1;\n}\nint XMLparser::setIntValue(int* pInt, std::string varName, std::string tag) {\n\tif (!varExists(varName, tag))\n\t\treturn 0; \/\/var not found\n\tstd::string val = getStringValue(varName, tag);\n\t*pInt = stoi(val);\n\treturn 1;\n}\nint XMLparser::setFloatValue(float* pFloat, std::string varName, std::string tag) {\n\tif (!varExists(varName, tag))\n\t\treturn 0; \/\/var not found\n\tstd::string val = getStringValue(varName, tag);\n\t*pFloat = stof(val);\n\treturn 1;\n}\nint XMLparser::setFloatArray(float* pFloats, int arrayLength, std::string varName, std::string tag) {\n\tif (!varExists(varName, tag))\n\t\treturn 0; \/\/var not found\n\tstd::string valuesString = getStringValue(varName, tag);\n\tstd::vector&lt;std::string&gt; valuesVector = splitString(valuesString, &quot;,&quot;);\n\tint valsN = valuesVector.size();\n\tif (valsN == 1) {\n\t\tfloat val = stof(valuesVector.at(0));\n\t\tfor (int i = 0; i &lt; arrayLength; i++)\n\t\t\tpFloats&#91;i] = val;\n\t\treturn 1;\n\t}\n\tif (valsN != arrayLength) {\n\t\tmylog(&quot;ERROR in XMLparser::getFloatArray, %s, %s\\n&quot;, varName.c_str(), tag.c_str());\n\t\treturn -1;\n\t}\n\tfor (int i = 0; i &lt; valsN; i++) {\n\t\tif (valuesVector.at(i).compare(&quot;x&quot;) != 0)\n\t\t\tpFloats&#91;i] = stof(valuesVector.at(i));\n\t}\n\treturn 1;\n}int XMLparser::setIntArray(int* pInts, int arrayLength, std::string varName, std::string tag) {\n\tif (!varExists(varName, tag))\n\t\treturn 0; \/\/var not found\n\tstd::string valuesString = getStringValue(varName, tag);\n\tstd::vector&lt;std::string&gt; valuesVector = splitString(valuesString, &quot;,&quot;);\n\tint valsN = valuesVector.size();\n\tif (valsN == 1) {\n\t\tint val = stoi(valuesVector.at(0));\n\t\tfor (int i = 0; i &lt; arrayLength; i++)\n\t\t\tpInts&#91;i] = val;\n\t\treturn 1;\n\t}\n\tif (valsN != arrayLength) {\n\t\tmylog(&quot;ERROR in XMLparser::getIntArray, %s, %s\\n&quot;, varName.c_str(), tag.c_str());\n\t\treturn -1;\n\t}\n\tfor (int i = 0; i &lt; valsN; i++) {\n\t\tif(valuesVector.at(i).compare(&quot;x&quot;) != 0)\n\t\t\tpInts&#91;i] = stoi(valuesVector.at(i));\n\t}\n\treturn 1;\n}\nint XMLparser::setUintColorValue(unsigned int* pInt, std::string varName, std::string tag) {\n\tif (!varExists(varName, tag))\n\t\treturn 0; \/\/var not found\n\tMyColor clr;\n\tstd::string val = getStringValue(varName, tag);\n\tif (val.at(0) == &#039;#&#039;) {\n\t\t\/\/the value is in HTML HEX format (like #ff0000)\n\t\tint r = std::stoi(val.substr(1, 2), nullptr, 16);\n\t\tint g = std::stoi(val.substr(3, 2), nullptr, 16);\n\t\tint b = std::stoi(val.substr(5, 2), nullptr, 16);\n\t\tint a = 255;\n\t\tif (val.size() &gt; 7)\n\t\t\ta = std::stoi(val.substr(7, 2), nullptr, 16);\n\t\tclr.setRGBA(r, g, b, a);\n\t}\n\telse if (val.find(&quot;,&quot;) &gt; 0) {\n\t\t\/\/the value is an array of ints (?)\n\t\tstd::vector&lt;std::string&gt; valuesVector = splitString(val, &quot;,&quot;);\n\t\tint r = std::stoi(valuesVector&#91;0]);\n\t\tint g = std::stoi(valuesVector&#91;1]);\n\t\tint b = std::stoi(valuesVector&#91;2]);\n\t\tint a = 255;\n\t\tif (valuesVector.size() &gt; 3)\n\t\t\ta = std::stoi(valuesVector&#91;3]);\n\t\tclr.setRGBA(r, g, b, a);\n\t}\n\telse {\n\t\tmylog(&quot;ERROR in XMLparser::setUintColorValue: unhandled Color format %s\\n&quot;,tag.c_str());\n\t\treturn -1;\n\t}\n\t*pInt = clr.getUint32();\n\treturn 1;\n}\nint XMLparser::setIntBoolValue(int* pInt, std::string varName, std::string tag) {\n\tif (!varExists(varName, tag))\n\t\treturn 0; \/\/var not found\n\tstd::string val = getStringValue(varName, tag);\n\tif (val.compare(&quot;&quot;) == 0) *pInt = 1;\n\telse if (val.compare(&quot;1&quot;) == 0) *pInt = 1;\n\telse if (val.compare(&quot;0&quot;) == 0) *pInt = 0;\n\telse if (val.compare(&quot;yes&quot;) == 0) *pInt = 1;\n\telse if (val.compare(&quot;no&quot;) == 0) *pInt = 0;\n\telse if (val.compare(&quot;true&quot;) == 0) *pInt = 1;\n\telse if (val.compare(&quot;false&quot;) == 0) *pInt = 0;\n\telse {\n\t\tmylog(&quot;ERROR in XMLparser::setIntBoolValue, %s=%s in %s\\n&quot;,varName.c_str(),val.c_str(),tag.c_str());\n\t\treturn -1;\n\t}\n\treturn 1;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>Modified functions:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>setFloatArray(..)<\/li><li>setIntArray(..)<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>9. Open <em>ModelBuilder1base.cpp<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [93,94,95,96,111,112,113,114,423,424,425,426]; 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::useMaterial(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\tpMB-&gt;usingMaterialN = i;\n\t\t\t\treturn i;\n\t\t\t}\n\t\/\/if here - add new material to the list\n\tpMB-&gt;usingMaterialN = itemsN;\n\t\/\/create a copy of new Material and add 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\n\treturn pMB-&gt;vertices.size() - 1;\n}\nint ModelBuilder1base::buildDrawJobs(ModelBuilder1base* pMB, std::vector&lt;GameSubj*&gt; gameSubjs) {\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 = gameSubjs.at(gsN);\n\t\tif (pGS != NULL)\n\t\t\tpGS-&gt;djStartN = DrawJob::drawJobs.size();\n\t\tbuildSingleDrawJob(pMT, pMB-&gt;vertices, 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 = gameSubjs.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\tVertex01* pVX2 = new Vertex01(*pVX);\n\t\t\t\tuseVertices.push_back(pVX2);\n\t\t\t\tpVX2-&gt;altN = vN;\n\t\t\t\tpVX-&gt;flag = 1;\n\t\t\t\tif (pVX-&gt;endOfSequence &gt; 0) {\n\t\t\t\t\trearrangeArraysForDrawJob(pMB, pMB-&gt;vertices, useVertices, useTriangles);\n\t\t\t\t\tbuildSingleDrawJob(pMT, useVertices, 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(pMB, pMB-&gt;vertices, useVertices, useTriangles);\n\t\t\tbuildSingleDrawJob(pMT, useVertices, useTriangles);\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}\n\nint ModelBuilder1base::buildSingleDrawJob(Material* pMT, std::vector&lt;Vertex01*&gt; useVertices, std::vector&lt;Triangle01*&gt; useTriangles) {\n\tint totalVertsN = useVertices.size();\n\tif (totalVertsN &lt; 1)\n\t\treturn 0;\n\tif (pMT-&gt;uTex2nm &gt;= 0)\n\t\tcalculateTangentSpace(useVertices, useTriangles);\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 = useVertices.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 = useTriangles.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 = useTriangles&#91;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}\n\nint ModelBuilder1base::rearrangeArraysForDrawJob(ModelBuilder1base* pMB, std::vector&lt;Vertex01*&gt; allVertices, std::vector&lt;Vertex01*&gt; useVertices, std::vector&lt;Triangle01*&gt; useTriangles) {\n\tint totalTrianglesN = useTriangles.size();\n\tif (totalTrianglesN &lt; 1)\n\t\treturn 0;\n\tint totalVerticesN = useVertices.size();\n\t\/\/save new vertices order in original vertices array\n\t\/\/since triangles indices refer to original vertices order\n\tfor (int i = 0; i &lt; totalVerticesN; i++) {\n\t\tVertex01* pVX1 = useVertices.at(i);\n\t\tVertex01* pVX0 = allVertices.at(pVX1-&gt;altN);\n\t\tpVX0-&gt;altN = i;\n\t}\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 = useTriangles.at(tN);\n\t\tfor (int i = 0; i &lt; 3; i++) {\n\t\t\tVertex01* pVX0 = allVertices.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; useVertices, std::vector&lt;Triangle01*&gt; useTriangles) {\n\tint totalVertsN = useVertices.size();\n\tif (totalVertsN &lt; 1)\n\t\treturn 0;\n\tint totalTrianglesN = useTriangles.size();\n\t\/\/assuming that GL_TRIANGLES\n\t\/\/clear flags\n\tfor (int vN = 0; vN &lt; totalVertsN; vN++) {\n\t\tVertex01* pV = useVertices.at(vN);\n\t\tpV-&gt;flag = 0;\n\t}\n\tfor (int vN = 0; vN &lt; totalVertsN; vN++) {\n\t\tVertex01* pVX = useVertices.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 = useTriangles.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] = useVertices.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 = useVertices.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}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>Modified functions::<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>lockGroup(..) <\/li><li>addVertex(..)<\/li><li>addTriangle(..)<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>10. Open <em>ModelLoader.h<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [43,44]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &quot;XMLparser.h&quot;\n#include &quot;ModelBuilder.h&quot;\n#include &quot;GroupTransform.h&quot;\n\nclass ModelLoader : public XMLparser\n{\npublic:\n\tModelBuilder* pModelBuilder = NULL;\n\tbool ownModelBuilder = false;\n\tstd::vector&lt;GameSubj*&gt;* pSubjsVector = NULL;\npublic:\n\tModelLoader(std::vector&lt;GameSubj*&gt;* pSubjsVector0, int subjN, ModelBuilder* pMB, std::string filePath) : XMLparser(filePath) {\n\t\tpSubjsVector = pSubjsVector0;\n\t\tif (pMB != NULL) {\n\t\t\townModelBuilder = false;\n\t\t\tpModelBuilder = pMB;\n\t\t}\n\t\telse {\n\t\t\townModelBuilder = true;\n\t\t\tpModelBuilder = new ModelBuilder();\n\t\t\tpModelBuilder-&gt;lockGroup(pModelBuilder);\n\t\t}\n\t\tpModelBuilder-&gt;useSubjN(subjN);\n\t};\n\tvirtual ~ModelLoader() {\n\t\tif (!ownModelBuilder)\n\t\t\treturn;\n\t\tpModelBuilder-&gt;buildDrawJobs(*pSubjsVector);\n\t\tdelete pModelBuilder;\n\t};\n\tstatic int processTag_a(ModelLoader* pML); \/\/apply\n\tstatic int setValueFromIntHashMap(int* pInt, std::map&lt;std::string, int&gt; intHashMap, std::string varName, std::string tagStr);\n\tstatic int setTexture(ModelLoader* pML, int* pInt, std::string txName);\n\tstatic int setMaterialTextures(ModelLoader* pML, Material* pMT);\n\tstatic int fillProps_vs(VirtualShape* pVS, std::string tagStr); \/\/virtual shape\n\tstatic int fillProps_mt(Material* pMT, std::string tagStr, ModelLoader* pML); \/\/Material\n\tstatic int fillProps_gt(GroupTransform* pGS, ModelBuilder* pMB, std::string tagStr);\n\tint processTag() { return processTag(this); };\n\tstatic int processTag(ModelLoader* pML);\n\tstatic int loadModel(std::vector&lt;GameSubj*&gt;* pSubjsVector0, std::string sourceFile, std::string subjClass);\n\tstatic int processTag_clone(ModelLoader* pML);\n\tstatic int addMark(char* marks, std::string newMark);\n\tstatic int processTag_do(ModelLoader* pML);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>New functions:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>addMark(..)<\/li><li>processTag_do(..)<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>11. Open <em>ModelLoader.cpp<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [265,286,296,325]; title: ; notranslate\" title=\"\">\n#include &quot;ModelLoader.h&quot;\n#include &quot;platform.h&quot;\n#include &quot;TheGame.h&quot;\n#include &quot;DrawJob.h&quot;\n#include &quot;Texture.h&quot;\n#include &quot;utils.h&quot;\n\nextern TheGame theGame;\n\nint ModelLoader::loadModel(std::vector&lt;GameSubj*&gt;* pSubjsVector0, std::string sourceFile, std::string subjClass) {\n\t\/\/returns element&#039;s (Subj) number or -1\n\tint subjN = pSubjsVector0-&gt;size();\n\tGameSubj* pGS = theGame.newGameSubj(subjClass);\n\tpSubjsVector0-&gt;push_back(pGS);\n\t\/\/pGS-&gt;djStartN = DrawJob::drawJobs.size();\n\tModelLoader* pML = new ModelLoader(pSubjsVector0, subjN, NULL, sourceFile);\n\tprocessSource(pML);\n\tdelete pML;\n\t\/\/pGS-&gt;djTotalN = DrawJob::drawJobs.size() - pGS-&gt;djStartN;\n\treturn subjN;\n}\nint ModelLoader::setValueFromIntHashMap(int* pInt, std::map&lt;std::string, int&gt; intHashMap, std::string varName, std::string tagStr) {\n\tif (!varExists(varName, tagStr))\n\t\treturn 0;\n\tstd::string str0 = getStringValue(varName, tagStr);\n\tif (intHashMap.find(str0) == intHashMap.end()) {\n\t\tmylog(&quot;ERROR in ModelLoader::setValueFromIntMap, %s not found, %s\\n&quot;, varName.c_str(), tagStr.c_str());\n\t\treturn -1;\n\t}\n\t*pInt = intHashMap&#91;getStringValue(varName, tagStr)];\n\treturn 1;\n}\nint ModelLoader::setTexture(ModelLoader* pML, int* pInt, std::string txName) {\n\tModelBuilder* pMB = pML-&gt;pModelBuilder;\n\tstd::string varName = txName + &quot;_use&quot;;\n\tif (setValueFromIntHashMap(pInt, pMB-&gt;texturesHashMap, varName, pML-&gt;currentTag) == 0) {\n\t\t\/\/the texture is not in hash table\n\t\tvarName = txName + &quot;_src&quot;;\n\t\tif (varExists(varName, pML-&gt;currentTag)) {\n\t\t\tstd::string txFile = getStringValue(varName, pML-&gt;currentTag);\n\t\t\tvarName = txName + &quot;_ckey&quot;;\n\t\t\tunsigned int intCkey = 0;\n\t\t\tsetUintColorValue(&amp;intCkey, varName, pML-&gt;currentTag);\n\t\t\t*pInt = Texture::loadTexture(buildFullPath(pML, txFile), intCkey);\n\t\t}\n\t}\n\treturn 1;\n}\nint ModelLoader::setMaterialTextures(ModelLoader* pML, Material* pMT) {\n\tsetTexture(pML, &amp;pMT-&gt;uTex0, &quot;uTex0&quot;);\n\tsetTexture(pML, &amp;pMT-&gt;uTex1mask, &quot;uTex1mask&quot;);\n\tsetTexture(pML, &amp;pMT-&gt;uTex2nm, &quot;uTex2nm&quot;);\n\tsetTexture(pML, &amp;pMT-&gt;uTex3, &quot;uTex3&quot;);\n\treturn 1;\n}\nint ModelLoader::fillProps_mt(Material* pMT, std::string tagStr, ModelLoader* pML) {\n\tsetCharsValue(pMT-&gt;shaderType, 20, &quot;mt_type&quot;, tagStr);\n\tsetMaterialTextures(pML, pMT);\n\t\/\/color\n\tif (varExists(&quot;uColor&quot;, tagStr)) {\n\t\tunsigned int uintColor = 0;\n\t\tsetUintColorValue(&amp;uintColor, &quot;uColor&quot;, tagStr);\n\t\tpMT-&gt;uColor.setUint32(uintColor);\n\t}\n\t\/\/mylog(&quot;mt.uTex0=%d, mt.uTex1mask=%d\\n&quot;, mt.uTex0, mt.uTex1mask);\n\tif (varExists(&quot;primitiveType&quot;, tagStr)) {\n\t\tstd::string str0 = getStringValue(&quot;primitiveType&quot;, tagStr);\n\t\tif (str0.compare(&quot;GL_POINTS&quot;) == 0) pMT-&gt;primitiveType = GL_POINTS;\n\t\telse if (str0.compare(&quot;GL_LINES&quot;) == 0) pMT-&gt;primitiveType = GL_LINES;\n\t\telse if (str0.compare(&quot;GL_LINE_STRIP&quot;) == 0) pMT-&gt;primitiveType = GL_LINE_STRIP;\n\t\telse if (str0.compare(&quot;GL_LINE_LOOP&quot;) == 0) pMT-&gt;primitiveType = GL_LINE_LOOP;\n\t\telse if (str0.compare(&quot;GL_TRIANGLE_STRIP&quot;) == 0) pMT-&gt;primitiveType = GL_TRIANGLE_STRIP;\n\t\telse if (str0.compare(&quot;GL_TRIANGLE_FAN&quot;) == 0) pMT-&gt;primitiveType = GL_TRIANGLE_FAN;\n\t\telse pMT-&gt;primitiveType = GL_TRIANGLES;\n\t}\n\tsetIntValue(&amp;pMT-&gt;uTex1alphaChannelN, &quot;uTex1alphaChannelN&quot;, tagStr);\n\tsetIntValue(&amp;pMT-&gt;uTex0translateChannelN, &quot;uTex0translateChannelN&quot;, tagStr);\n\tsetIntBoolValue(&amp;pMT-&gt;uAlphaBlending, &quot;uAlphaBlending&quot;, tagStr);\n\tsetFloatValue(&amp;pMT-&gt;uAlphaFactor, &quot;uAlphaFactor&quot;, tagStr);\n\tsetFloatValue(&amp;pMT-&gt;uAmbient, &quot;uAmbient&quot;, tagStr);\n\tsetFloatValue(&amp;pMT-&gt;uSpecularIntencity, &quot;uSpecularIntencity&quot;, tagStr);\n\tsetFloatValue(&amp;pMT-&gt;uSpecularMinDot, &quot;uSpecularMinDot&quot;, tagStr);\n\tsetFloatValue(&amp;pMT-&gt;uSpecularPowerOf, &quot;uSpecularPowerOf&quot;, tagStr);\n\n\tpML-&gt;pModelBuilder-&gt;useMaterial(pMT);\n\treturn 1;\n}\nint ModelLoader::processTag(ModelLoader* pML) {\n\tModelBuilder* pMB = pML-&gt;pModelBuilder;\n\tif (pML-&gt;tagName.compare(&quot;texture_as&quot;) == 0) {\n\t\t\/\/saves texture N in texturesMap under given name\n\t\tstd::string keyName = getStringValue(&quot;texture_as&quot;, pML-&gt;currentTag);\n\t\tif (pMB-&gt;texturesHashMap.find(keyName) != pMB-&gt;texturesHashMap.end())\n\t\t\treturn pMB-&gt;texturesHashMap&#91;keyName];\n\t\telse { \/\/add new\n\t\t\tstd::string txFile = getStringValue(&quot;src&quot;, pML-&gt;currentTag);\n\t\t\tunsigned int intCkey = 0;\n\t\t\tsetUintColorValue(&amp;intCkey, &quot;ckey&quot;, pML-&gt;currentTag);\n\t\t\tint txN = Texture::loadTexture(buildFullPath(pML, txFile), intCkey);\n\t\t\tpMB-&gt;texturesHashMap&#91;keyName] = txN;\n\t\t\t\/\/mylog(&quot;%s=%d\\n&quot;, keyName.c_str(), pMB-&gt;texturesMap&#91;keyName]);\n\t\t\treturn txN;\n\t\t}\n\t}\n\tif (pML-&gt;tagName.find(&quot;mt_&quot;) == 0) {\n\t\t\/\/sets current material\n\t\tModelBuilder* pMB = pML-&gt;pModelBuilder;\n\t\tif (!pML-&gt;closedTag) {\n\t\t\t\/\/save previous material in stack\n\t\t\tif (pMB-&gt;usingMaterialN &gt;= 0)\n\t\t\t\tpMB-&gt;materialsStack.push_back(pMB-&gt;usingMaterialN);\n\t\t}\n\t\tMaterial mt;\n\t\treturn fillProps_mt(&amp;mt, pML-&gt;currentTag, pML);\n\t}\n\tif (pML-&gt;tagName.find(&quot;\/mt_&quot;) == 0) {\n\t\t\/\/restore previous material\n\t\tif (pMB-&gt;materialsStack.size() &gt; 0) {\n\t\t\tpMB-&gt;usingMaterialN = pMB-&gt;materialsStack.back();\n\t\t\tpMB-&gt;materialsStack.pop_back();\n\t\t}\n\t\treturn 1;\n\t}\n\tif (pML-&gt;tagName.compare(&quot;vs&quot;) == 0) {\n\t\t\/\/sets virtual shape\n\t\tModelBuilder* pMB = pML-&gt;pModelBuilder;\n\t\tif (pML-&gt;closedTag) {\n\t\t\tif (pMB-&gt;pCurrentVShape != NULL)\n\t\t\t\tdelete pMB-&gt;pCurrentVShape;\n\t\t}\n\t\telse { \/\/open tag\n\t\t\t\/\/save previous vshape in stack\n\t\t\tif (pMB-&gt;pCurrentVShape != NULL)\n\t\t\t\tpMB-&gt;vShapesStack.push_back(pMB-&gt;pCurrentVShape);\n\t\t}\n\t\tpMB-&gt;pCurrentVShape = new VirtualShape();\n\t\tfillProps_vs(pMB-&gt;pCurrentVShape, pML-&gt;currentTag);\n\t\treturn 1;\n\t}\n\tif (pML-&gt;tagName.compare(&quot;\/vs&quot;) == 0) {\n\t\t\/\/restore previous virtual shape\n\t\tif (pMB-&gt;vShapesStack.size() &gt; 0) {\n\t\t\tif (pMB-&gt;pCurrentVShape != NULL)\n\t\t\t\tdelete(pMB-&gt;pCurrentVShape);\n\t\t\tpMB-&gt;pCurrentVShape = pMB-&gt;vShapesStack.back();\n\t\t\tpMB-&gt;vShapesStack.pop_back();\n\t\t}\n\t\treturn 1;\n\t}\n\tif (pML-&gt;tagName.compare(&quot;group&quot;) == 0) {\n\t\tstd::string notAllowed&#91;] = { &quot;pxyz&quot;,&quot;axyz&quot;,&quot;align&quot;,&quot;headTo&quot; };\n\t\tint notAllowedLn = sizeof(notAllowed) \/ sizeof(notAllowed&#91;0]);\n\t\tfor (int i = 0; i &lt; notAllowedLn; i++)\n\t\t\tif (varExists(notAllowed&#91;i], pML-&gt;currentTag)) {\n\t\t\t\tmylog(&quot;ERROR in ModelLoader::processTag: use %s in &lt;\/group&gt;: %s\\n&quot;, notAllowed&#91;i].c_str(), pML-&gt;currentTag.c_str());\n\t\t\t\treturn -1;\n\t\t\t}\n\t\tpMB-&gt;lockGroup(pMB);\n\t\t\/\/mark\n\t\tif (varExists(&quot;mark&quot;, pML-&gt;currentTag))\n\t\t\taddMark(pMB-&gt;pCurrentGroup-&gt;marks, getStringValue(&quot;mark&quot;, pML-&gt;currentTag));\n\t\treturn 1;\n\t}\n\tif (pML-&gt;tagName.compare(&quot;\/group&quot;) == 0) {\n\t\tGroupTransform gt;\n\t\tfillProps_gt(&amp;gt, pMB, pML-&gt;currentTag);\n\t\tgt.executeGroupTransform(pMB);\n\n\t\tpMB-&gt;releaseGroup(pMB);\n\t\treturn 1;\n\t}\n\tif (pML-&gt;tagName.compare(&quot;a&quot;) == 0)\n\t\treturn processTag_a(pML); \/\/apply \n\tif (pML-&gt;tagName.compare(&quot;clone&quot;) == 0)\n\t\treturn processTag_clone(pML);\n\tif (pML-&gt;tagName.compare(&quot;\/clone&quot;) == 0)\n\t\treturn processTag_clone(pML);\n\tif (pML-&gt;tagName.compare(&quot;do&quot;) == 0)\n\t\treturn processTag_do(pML);\n\n\t\/\/mylog(&quot;%s, %s \/group?=%d\\n&quot;,pML-&gt;currentTag.c_str(), pML-&gt;tagName.c_str(), (pML-&gt;tagName.compare(&quot;\/group&quot;) == 0));\n\tmylog(&quot;ERROR in ModelLoader::processTag, unhandled tag %s, file %s\\n&quot;, pML-&gt;currentTag.c_str(), pML-&gt;fullPath.c_str());\n\treturn -1;\n}\nint ModelLoader::fillProps_vs(VirtualShape* pVS, std::string tagStr) {\n\t\/\/sets virtual shape\n\tsetCharsValue(pVS-&gt;shapeType, 20, &quot;vs&quot;, tagStr);\n\tsetFloatArray(pVS-&gt;whl, 3, &quot;whl&quot;, tagStr);\n\t\/\/extensions\n\tfloat ext;\n\tif (varExists(&quot;ext&quot;, tagStr)) {\n\t\tsetFloatValue(&amp;ext, &quot;ext&quot;, tagStr);\n\t\tpVS-&gt;setExt(ext);\n\t}\n\tif (varExists(&quot;extX&quot;, tagStr)) {\n\t\tsetFloatValue(&amp;ext, &quot;extX&quot;, tagStr);\n\t\tpVS-&gt;setExtX(ext);\n\t}\n\tif (varExists(&quot;extY&quot;, tagStr)) {\n\t\tsetFloatValue(&amp;ext, &quot;extY&quot;, tagStr);\n\t\tpVS-&gt;setExtY(ext);\n\t}\n\tif (varExists(&quot;extZ&quot;, tagStr)) {\n\t\tsetFloatValue(&amp;ext, &quot;extZ&quot;, tagStr);\n\t\tpVS-&gt;setExtZ(ext);\n\t}\n\tsetFloatValue(&amp;pVS-&gt;extU, &quot;extU&quot;, tagStr);\n\tsetFloatValue(&amp;pVS-&gt;extD, &quot;extD&quot;, tagStr);\n\tsetFloatValue(&amp;pVS-&gt;extL, &quot;extL&quot;, tagStr);\n\tsetFloatValue(&amp;pVS-&gt;extR, &quot;extR&quot;, tagStr);\n\tsetFloatValue(&amp;pVS-&gt;extF, &quot;extF&quot;, tagStr);\n\tsetFloatValue(&amp;pVS-&gt;extB, &quot;extB&quot;, tagStr);\n\t\/\/sections\n\tsetIntValue(&amp;pVS-&gt;sectionsR, &quot;sectR&quot;, tagStr);\n\tsetIntValue(&amp;pVS-&gt;sections&#91;0], &quot;sectX&quot;, tagStr);\n\tsetIntValue(&amp;pVS-&gt;sections&#91;1], &quot;sectY&quot;, tagStr);\n\tsetIntValue(&amp;pVS-&gt;sections&#91;2], &quot;sectZ&quot;, tagStr);\n\n\t\/\/mylog(&quot;pVS-&gt;shapeType=%s whl=%fx%fx%f\\n&quot;, pVS-&gt;shapeType, pVS-&gt;whl&#91;0], pVS-&gt;whl&#91;1], pVS-&gt;whl&#91;2]);\n\treturn 1;\n}\nint ModelLoader::processTag_a(ModelLoader* pML) {\n\t\/\/apply\n\tModelBuilder* pMB = pML-&gt;pModelBuilder;\n\tstd::string tagStr = pML-&gt;currentTag;\n\tpMB-&gt;lockGroup(pMB);\n\t\/\/mark\n\tif (varExists(&quot;mark&quot;, tagStr))\n\t\taddMark(pMB-&gt;pCurrentGroup-&gt;marks, getStringValue(&quot;mark&quot;, tagStr));\n\n\tstd::vector&lt;std::string&gt; applyTosVector = splitString(pML-&gt;getStringValue(&quot;a&quot;, tagStr), &quot;,&quot;);\n\tMaterial* pMT = pMB-&gt;materialsList.at(pMB-&gt;usingMaterialN);\n\tint texN = pMT-&gt;uTex1mask;\n\tif (texN &lt; 0)\n\t\ttexN = pMT-&gt;uTex0;\n\tfloat xywh&#91;4] = { 0,0,1,1 };\n\tsetFloatArray(xywh, 4, &quot;xywh&quot;, tagStr);\n\tstd::string flipStr = getStringValue(&quot;flip&quot;, tagStr);\n\tTexCoords tc;\n\ttc.set(texN, xywh&#91;0], xywh&#91;1], xywh&#91;2], xywh&#91;3], flipStr);\n\n\tsetFloatArray(xywh, 4, &quot;xywh2nm&quot;, tagStr);\n\tflipStr = getStringValue(&quot;flip2nm&quot;, tagStr);\n\tTexCoords tc2nm;\n\ttc2nm.set(pMT-&gt;uTex2nm, xywh&#91;0], xywh&#91;1], xywh&#91;2], xywh&#91;3], flipStr);\n\n\t\/\/adjusted VirtualShape\n\tVirtualShape* pVS_a = new VirtualShape(*pMB-&gt;pCurrentVShape);\n\tfillProps_vs(pVS_a, tagStr);\n\n\tfor (int aN = 0; aN &lt; (int)applyTosVector.size(); aN++) {\n\t\t\/\/pMB-&gt;buildFace(pMB, applyTosVector.at(aN), pMB-&gt;pCurrentVShape, &amp;tc, &amp;tc2nm);\n\t\tpMB-&gt;buildFace(pMB, applyTosVector.at(aN), pVS_a, &amp;tc, &amp;tc2nm);\n\t}\n\tdelete pVS_a;\n\t\/\/mylog(&quot;vertsN=%d\\n&quot;,pMB-&gt;vertices.size());\n\n\tGroupTransform GT_a;\n\tfillProps_gt(&amp;GT_a, pMB, tagStr);\n\tGT_a.executeGroupTransform(pMB);\n\n\tpMB-&gt;releaseGroup(pMB);\n\treturn 1;\n}\nint ModelLoader::processTag_clone(ModelLoader* pML) {\n\tModelBuilder* pMB = pML-&gt;pModelBuilder;\n\tif (pML-&gt;tagName.compare(&quot;clone&quot;) == 0) {\n\t\t\/\/mark what to clone\n\t\tGroupTransform gt;\n\t\tgt.pGroup = pMB-&gt;pLastClosedGroup;\n\t\tgt.flagSelection(&amp;gt, &amp;pMB-&gt;vertices, &amp;pMB-&gt;triangles);\n\n\t\t\/\/cloning\n\t\tpMB-&gt;lockGroup(pMB);\n\t\tgt.cloneFlagged(pMB, &amp;pMB-&gt;vertices, &amp;pMB-&gt;triangles, &amp;pMB-&gt;vertices, &amp;pMB-&gt;triangles);\n\t}\n\tGroupTransform gt;\n\tfillProps_gt(&amp;gt, pMB, pML-&gt;currentTag);\n\tgt.executeGroupTransform(pMB);\n\n\tif (pML-&gt;tagName.compare(&quot;\/clone&quot;) == 0 || pML-&gt;closedTag) {\n\t\tpMB-&gt;releaseGroup(pMB);\n\t}\n\treturn 1;\n}\nint ModelLoader::addMark(char* marks, std::string newMark) {\n\tif (newMark.empty())\n\t\treturn 0;\n\tstd::string allMarks;\n\tallMarks.assign(marks);\n\tallMarks.append(&quot;&lt;&quot;+ newMark+&quot;&gt;&quot;);\n\tmyStrcpy_s(marks,124, allMarks.c_str());\n\treturn 1;\n}\n\nint ModelLoader::fillProps_gt(GroupTransform* pGT, ModelBuilder* pMB, std::string tagStr) {\n\tpGT-&gt;pGroup = pMB-&gt;pCurrentGroup;\n\t\/\/position\n\tsetFloatArray(pGT-&gt;shift, 3, &quot;pxyz&quot;, tagStr);\n\tsetFloatValue(&amp;pGT-&gt;shift&#91;0], &quot;px&quot;, tagStr);\n\tsetFloatValue(&amp;pGT-&gt;shift&#91;1], &quot;py&quot;, tagStr);\n\tsetFloatValue(&amp;pGT-&gt;shift&#91;2], &quot;pz&quot;, tagStr);\n\t\/\/angles\n\tsetFloatArray(pGT-&gt;spin, 3, &quot;axyz&quot;, tagStr);\n\tsetFloatValue(&amp;pGT-&gt;spin&#91;0], &quot;ax&quot;, tagStr);\n\tsetFloatValue(&amp;pGT-&gt;spin&#91;1], &quot;ay&quot;, tagStr);\n\tsetFloatValue(&amp;pGT-&gt;spin&#91;2], &quot;az&quot;, tagStr);\n\t\/\/scale\n\tsetFloatArray(pGT-&gt;scale, 3, &quot;scale&quot;, tagStr);\n\n\tpGT-&gt;onThe = getStringValue(&quot;onThe&quot;, tagStr);\n\tpGT-&gt;allign = getStringValue(&quot;allign&quot;, tagStr);\n\tpGT-&gt;headZto = getStringValue(&quot;headZto&quot;, tagStr);\n\t\/\/limit to\n\tif(varExists(&quot;all&quot;,tagStr))\n\t\tpGT-&gt;pGroup = NULL;\n\tif (varExists(&quot;lastClosedGroup&quot;, tagStr))\n\t\tpGT-&gt;pGroup = pMB-&gt;pLastClosedGroup;\n\tif (varExists(&quot;markedAs&quot;, tagStr))\n\t\tpGT-&gt;limit2mark(pGT, getStringValue(&quot;markedAs&quot;, tagStr));\n\tsetFloatArray(pGT-&gt;pMin, 3, &quot;xyzMin&quot;, tagStr);\n\tsetFloatArray(pGT-&gt;pMax, 3, &quot;xyzMax&quot;, tagStr);\n\treturn 1;\n}\nint ModelLoader::processTag_do(ModelLoader* pML) {\n\tModelBuilder* pMB = pML-&gt;pModelBuilder;\n\tGroupTransform gt;\n\tfillProps_gt(&amp;gt, pMB, pML-&gt;currentTag);\n\tgt.flagSelection(&amp;gt, &amp;pMB-&gt;vertices, &amp;pMB-&gt;triangles);\n\tgt.transformFlagged(&amp;gt, &amp;pMB-&gt;vertices);\n\treturn 1;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>Also modified:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>fillProps_gt(..)<\/li><li>processTag_clone(..)<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>So, we wanted to mark right side and shift it out to the right by 5 units.<\/p>\n\n\n\n<p>12. Build and run. Result:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c32\/01.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Shifted. However, gilded letters which were left <strong>behind <\/strong>are overlapping shifted right side. That&#8217;s because we still didn&#8217;t care about <strong>depth buffer<\/strong>. Closer objects should cover more distant ones. For that we need to enable <em>depth buffer testing<\/em>.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>13. Open <em>TheGame.cpp<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [22,23,24,50]; title: ; notranslate\" title=\"\">\n#include &quot;TheGame.h&quot;\n#include &quot;platform.h&quot;\n#include &quot;utils.h&quot;\n#include &quot;linmath.h&quot;\n#include &quot;Texture.h&quot;\n#include &quot;Shader.h&quot;\n#include &quot;DrawJob.h&quot;\n#include &quot;ModelBuilder.h&quot;\n#include &quot;TexCoords.h&quot;\n#include &quot;ModelLoader.h&quot;\n\nextern std::string filesRoot;\nextern float degrees2radians;\n\nstd::vector&lt;GameSubj*&gt; TheGame::gameSubjs;\n\nint TheGame::getReady() {\n    bExitGame = false;\n    Shader::loadShaders();\n    glEnable(GL_CULL_FACE);\n\n    glEnable(GL_DEPTH_TEST);\n    glDepthFunc(GL_LEQUAL);\n    glDepthMask(GL_TRUE);\n\n    int subjN = ModelLoader::loadModel(&amp;gameSubjs, &quot;\/dt\/models\/misc\/marlboro01\/root01.txt&quot;, &quot;&quot;);\n    GameSubj* pGS = gameSubjs.at(subjN);\n    pGS-&gt;name.assign(&quot;box1&quot;);\n    pGS-&gt;ownSpeed.setDegrees(0, 2, 0);\n    \/\/pGS-&gt;ownCoords.setDegrees(0, 90, 0);\n\n    \/\/===== set up camera\n    mainCamera.ownCoords.setDegrees(15, 180, 0); \/\/set camera angles\/orientation\n    mainCamera.viewRangeDg = 30;\n    mainCamera.stageSize&#91;0] = 80;\n    mainCamera.stageSize&#91;1] = 120;\n    memcpy(mainCamera.lookAtPoint, pGS-&gt;ownCoords.pos, sizeof(float) * 3);\n    mainCamera.onScreenResize();\n\n    \/\/===== set up light\n    v3set(dirToMainLight, -1, 1, 1);\n    vec3_norm(dirToMainLight, dirToMainLight);\n\n    return 1;\n}\nint TheGame::drawFrame() {\n    myPollEvents();\n\n    \/\/glClearColor(0.0, 0.0, 0.5, 1.0);\n    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n\n    \/\/calculate halfVector\n    float dirToCamera&#91;4] = { 0,0,-1,0 }; \/\/-z\n    mat4x4_mul_vec4plus(dirToCamera, *mainCamera.ownCoords.getRotationMatrix(), dirToCamera, 0);\n\n    float uHalfVector&#91;4] = { 0,0,0,0 };\n    for (int i = 0; i &lt; 3; i++)\n        uHalfVector&#91;i] = (dirToCamera&#91;i] + dirToMainLight&#91;i]) \/ 2;\n    vec3_norm(uHalfVector, uHalfVector);\n\n    mat4x4 mProjection, mViewProjection, mMVP, mMV4x4;\n    \/\/mat4x4_ortho(mProjection, -(float)screenSize&#91;0] \/ 2, (float)screenSize&#91;0] \/ 2, -(float)screenSize&#91;1] \/ 2, (float)screenSize&#91;1] \/ 2, 100.f, 500.f);\n    float nearClip = mainCamera.focusDistance - 50;\n    float farClip = mainCamera.focusDistance + 50;\n    if (nearClip &lt; 0) nearClip = 0;\n    mat4x4_perspective(mProjection, mainCamera.viewRangeDg * degrees2radians, screenAspectRatio, nearClip, farClip);\n    mat4x4_mul(mViewProjection, mProjection, mainCamera.lookAtMatrix);\n    mViewProjection&#91;1]&#91;3] = 0; \/\/keystone effect\n\n    \/\/scan subjects\n    int subjsN = gameSubjs.size();\n    for (int subjN = 0; subjN &lt; subjsN; subjN++) {\n        GameSubj* pGS = gameSubjs.at(subjN);\n        \/\/behavior - apply rotation speed\n        pGS-&gt;moveSubj();\n        \/\/prepare subject for rendering\n        pGS-&gt;buildModelMatrix(pGS);\n        \/\/build MVP matrix for given subject\n        mat4x4_mul(mMVP, mViewProjection, pGS-&gt;ownModelMatrix);\n        \/\/build Model-View (rotation) matrix for normals\n        mat4x4_mul(mMV4x4, mainCamera.lookAtMatrix, (vec4*)pGS-&gt;ownCoords.getRotationMatrix());\n        \/\/convert to 3x3 matrix\n        float mMV3x3&#91;3]&#91;3];\n        for (int y = 0; y &lt; 3; y++)\n            for (int x = 0; x &lt; 3; x++)\n                mMV3x3&#91;y]&#91;x] = mMV4x4&#91;y]&#91;x];\n        \/\/render subject\n        for (int i = 0; i &lt; pGS-&gt;djTotalN; i++) {\n            DrawJob* pDJ = DrawJob::drawJobs.at(pGS-&gt;djStartN + i);\n            pDJ-&gt;execute((float*)mMVP, *mMV3x3, dirToMainLight, uHalfVector, NULL);\n        }\n    }\n    \/\/synchronization\n    while (1) {\n        long long int currentMillis = getSystemMillis();\n        long long int millisSinceLastFrame = currentMillis - lastFrameMillis;\n        if (millisSinceLastFrame &gt;= millisPerFrame) {\n            lastFrameMillis = currentMillis;\n            break;\n        }\n    }\n    mySwapBuffers();\n    return 1;\n}\n\nint TheGame::cleanUp() {\n    int itemsN = gameSubjs.size();\n    \/\/delete all UISubjs\n    for (int i = 0; i &lt; itemsN; i++) {\n        GameSubj* pGS = gameSubjs.at(i);\n        delete pGS;\n    }\n    gameSubjs.clear();\n    \/\/clear all other classes\n    Texture::cleanUp();\n    Shader::cleanUp();\n    DrawJob::cleanUp();\n    return 1;\n}\nint TheGame::onScreenResize(int width, int height) {\n    if (screenSize&#91;0] == width &amp;&amp; screenSize&#91;1] == height)\n        return 0;\n    screenSize&#91;0] = width;\n    screenSize&#91;1] = height;\n    screenAspectRatio = (float)width \/ height;\n    glViewport(0, 0, width, height);\n    mainCamera.onScreenResize();\n    mylog(&quot; screen size %d x %d\\n&quot;, width, height);\n    return 1;\n}\nint TheGame::run() {\n    getReady();\n    while (!bExitGame) {\n        drawFrame();\n    }\n    cleanUp();\n    return 1;\n}\nGameSubj* TheGame::newGameSubj(std::string subjClass) {\n    return (new GameSubj());\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>See highlighted lines. Added:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>glEnable(GL_DEPTH_TEST);<\/li><li>glDepthFunc(GL_LEQUAL);<\/li><li>glDepthMask(GL_TRUE);<\/li><li>glClear(GL_COLOR_BUFFER_BIT<strong> | GL_DEPTH_BUFFER_BIT<\/strong>);<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>14. Build and run. Result:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c32\/02.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Now &#8211; correct, depth buffer works.<\/p>\n\n\n\n<p>So, we <strong>are<\/strong> able to mark sub-meshes and to modify them, the goal is achieved.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Now &#8211; on<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Android<\/h2>\n\n\n\n<p>To my big surprise <em>depth buffer<\/em> on Android didn&#8217;t work! Turned out that it just wasn&#8217;t included in GL initialization in <em>main.cpp<\/em>.<\/p>\n\n\n\n<p>15. Close and re-start VS. Open&nbsp;<em>C:\\CPP\\a997modeler\\p_android\\p_android.sln<\/em>.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>16. Open  <em>main.cpp<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [39]; title: ; notranslate\" title=\"\">\n#include &quot;platform.h&quot;\n#include &quot;TheGame.h&quot;\n\n#include &lt;string&gt;\n#include &lt;vector&gt;\n\n#include &lt;sys\/stat.h&gt;\t\/\/mkdir for Android\n\nstd::string filesRoot;\n\nTheGame theGame;\n\nstruct android_app* androidApp;\n\nASensorManager* sensorManager;\nconst ASensor* accelerometerSensor;\nASensorEventQueue* sensorEventQueue;\n\nEGLDisplay androidDisplay;\nEGLSurface androidSurface;\nEGLContext androidContext;\n\n\/**\n* Initialize an EGL context for the current display.\n*\/\nstatic int engine_init_display(struct engine* engine) {\n\t\/\/ initialize OpenGL ES and EGL\n\n\t\/*\n\t* Here specify the attributes of the desired configuration.\n\t* Below, we select an EGLConfig with at least 8 bits per color\n\t* component compatible with on-screen windows\n\t*\/\n\tconst EGLint attribs&#91;] = {\n\t\tEGL_SURFACE_TYPE, EGL_WINDOW_BIT,\n\t\tEGL_BLUE_SIZE, 8,\n\t\tEGL_GREEN_SIZE, 8,\n\t\tEGL_RED_SIZE, 8,\n\t\tEGL_DEPTH_SIZE, 16,\n\t\tEGL_NONE\n\t};\n\tEGLint format;\n\tEGLint numConfigs;\n\tEGLConfig config;\n\tEGLSurface surface;\n\tEGLContext context;\n\n\tEGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);\n\n\teglInitialize(display, 0, 0);\n\n\t\/* Here, the application chooses the configuration it desires. In this\n\t* sample, we have a very simplified selection process, where we pick\n\t* the first EGLConfig that matches our criteria *\/\n\teglChooseConfig(display, attribs, &amp;config, 1, &amp;numConfigs);\n\n\t\/* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is\n\t* guaranteed to be accepted by ANativeWindow_setBuffersGeometry().\n\t* As soon as we picked a EGLConfig, we can safely reconfigure the\n\t* ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. *\/\n\teglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &amp;format);\n\n\tANativeWindow_setBuffersGeometry(androidApp-&gt;window, 0, 0, format);\n\n\tsurface = eglCreateWindowSurface(display, config, androidApp-&gt;window, NULL);\n\n\tEGLint contextAttribs&#91;] =\n\t{\n\t\tEGL_CONTEXT_CLIENT_VERSION, 3,\n\t\tEGL_NONE\n\t};\n\tcontext = eglCreateContext(display, config, NULL, contextAttribs);\n\n\n\tif (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {\n\t\tmylog(&quot;ERROR: Unable to eglMakeCurrent&quot;);\n\t\treturn -1;\n\t}\n\n\tandroidDisplay = display;\n\tandroidContext = context;\n\tandroidSurface = surface;\n\n\treturn 0;\n}\n\n\/**\n* Tear down the EGL context currently associated with the display.\n*\/\nstatic void engine_term_display() {\n\n\tif (androidDisplay != EGL_NO_DISPLAY) {\n\t\teglMakeCurrent(androidDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);\n\t\tif (androidContext != EGL_NO_CONTEXT) {\n\t\t\teglDestroyContext(androidDisplay, androidContext);\n\t\t}\n\t\tif (androidSurface != EGL_NO_SURFACE) {\n\t\t\teglDestroySurface(androidDisplay, androidSurface);\n\t\t}\n\t\teglTerminate(androidDisplay);\n\t}\n\tandroidDisplay = EGL_NO_DISPLAY;\n\tandroidContext = EGL_NO_CONTEXT;\n\tandroidSurface = EGL_NO_SURFACE;\n}\n\n\/**\n* Process the next input event.\n*\/\nstatic int32_t engine_handle_input(struct android_app* app, AInputEvent* event) {\n\tif (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {\n\t\t\/\/engine-&gt;state.x = AMotionEvent_getX(event, 0);\n\t\t\/\/engine-&gt;state.y = AMotionEvent_getY(event, 0);\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\n\/**\n* Process the next main command.\n*\/\nstatic void engine_handle_cmd(struct android_app* app, int32_t cmd) {\n\tstruct engine* engine = (struct engine*)app-&gt;userData;\n\tswitch (cmd) {\n\tcase APP_CMD_INIT_WINDOW:\n\t\t\/\/ The window is being shown, get it ready.\n\t\tif (androidApp-&gt;window != NULL) {\n\t\t\tengine_init_display(engine);\n\t\t\t\/\/engine_draw_frame(engine);\n\t\t}\n\t\tbreak;\n\tcase APP_CMD_TERM_WINDOW:\n\t\t\/\/ The window is being hidden or closed, clean it up.\n\t\tengine_term_display();\n\t\tbreak;\n\tcase APP_CMD_GAINED_FOCUS:\n\t\t\/\/ When our app gains focus, we start monitoring the accelerometer.\n\t\tif (accelerometerSensor != NULL) {\n\t\t\tASensorEventQueue_enableSensor(sensorEventQueue,\n\t\t\t\taccelerometerSensor);\n\t\t\t\/\/ We&#039;d like to get 60 events per second (in microseconds).\n\t\t\tASensorEventQueue_setEventRate(sensorEventQueue,\n\t\t\t\taccelerometerSensor, (1000L \/ 60) * 1000);\n\t\t}\n\t\tbreak;\n\tcase APP_CMD_LOST_FOCUS:\n\t\t\/\/ When our app loses focus, we stop monitoring the accelerometer.\n\t\t\/\/ This is to avoid consuming battery while not being used.\n\t\tif (accelerometerSensor != NULL) {\n\t\t\tASensorEventQueue_disableSensor(sensorEventQueue,\n\t\t\t\taccelerometerSensor);\n\t\t}\n\t\t\/\/ Also stop animating.\n\t\t\/\/engine_draw_frame(engine);\n\t\tbreak;\n\t}\n}\nstatic std::vector&lt;std::string&gt; list_assets(android_app* app, const char* asset_path)\n{ \/\/by Marcel Smit, stolen from https:\/\/github.com\/android\/ndk-samples\/issues\/603\n\tstd::vector&lt;std::string&gt; result;\n\n\tJNIEnv* env = nullptr;\n\tapp-&gt;activity-&gt;vm-&gt;AttachCurrentThread(&amp;env, nullptr);\n\n\tauto context_object = app-&gt;activity-&gt;clazz;\n\tauto getAssets_method = env-&gt;GetMethodID(env-&gt;GetObjectClass(context_object), &quot;getAssets&quot;, &quot;()Landroid\/content\/res\/AssetManager;&quot;);\n\tauto assetManager_object = env-&gt;CallObjectMethod(context_object, getAssets_method);\n\tauto list_method = env-&gt;GetMethodID(env-&gt;GetObjectClass(assetManager_object), &quot;list&quot;, &quot;(Ljava\/lang\/String;)&#91;Ljava\/lang\/String;&quot;);\n\n\tjstring path_object = env-&gt;NewStringUTF(asset_path);\n\tauto files_object = (jobjectArray)env-&gt;CallObjectMethod(assetManager_object, list_method, path_object);\n\tenv-&gt;DeleteLocalRef(path_object);\n\tauto length = env-&gt;GetArrayLength(files_object);\n\n\tfor (int i = 0; i &lt; length; i++)\n\t{\n\t\tjstring jstr = (jstring)env-&gt;GetObjectArrayElement(files_object, i);\n\t\tconst char* filename = env-&gt;GetStringUTFChars(jstr, nullptr);\n\t\tif (filename != nullptr)\n\t\t{\n\t\t\tresult.push_back(filename);\n\t\t\tenv-&gt;ReleaseStringUTFChars(jstr, filename);\n\t\t}\n\t\tenv-&gt;DeleteLocalRef(jstr);\n\t}\n\tapp-&gt;activity-&gt;vm-&gt;DetachCurrentThread();\n\treturn result;\n}\n\nint updateAssets() {\n\t\/\/get APK apkLastUpdateTime timestamp\n\tJNIEnv* env = nullptr;\n\tandroidApp-&gt;activity-&gt;vm-&gt;AttachCurrentThread(&amp;env, nullptr);\n\tjobject context_object = androidApp-&gt;activity-&gt;clazz;\n\tjmethodID getPackageNameMid_method = env-&gt;GetMethodID(env-&gt;GetObjectClass(context_object), &quot;getPackageName&quot;, &quot;()Ljava\/lang\/String;&quot;);\n\tjstring packageName = (jstring)env-&gt;CallObjectMethod(context_object, getPackageNameMid_method);\n\tjmethodID getPackageManager_method = env-&gt;GetMethodID(env-&gt;GetObjectClass(context_object), &quot;getPackageManager&quot;, &quot;()Landroid\/content\/pm\/PackageManager;&quot;);\n\tjobject packageManager_object = env-&gt;CallObjectMethod(context_object, getPackageManager_method);\n\tjmethodID getPackageInfo_method = env-&gt;GetMethodID(env-&gt;GetObjectClass(packageManager_object), &quot;getPackageInfo&quot;, &quot;(Ljava\/lang\/String;I)Landroid\/content\/pm\/PackageInfo;&quot;);\n\tjobject packageInfo_object = env-&gt;CallObjectMethod(packageManager_object, getPackageInfo_method, packageName, 0x0);\n\tjfieldID updateTimeFid = env-&gt;GetFieldID(env-&gt;GetObjectClass(packageInfo_object), &quot;lastUpdateTime&quot;, &quot;J&quot;);\n\tlong int apkLastUpdateTime = env-&gt;GetLongField(packageInfo_object, updateTimeFid);\n\t\/\/ APK updateTime timestamp retrieved\n\t\/\/ compare with saved timestamp\n\tstd::string updateTimeFilePath = filesRoot + &quot;\/dt\/apk_update_time.bin&quot;;\n\tFILE* inFile = fopen(updateTimeFilePath.c_str(), &quot;r&quot;);\n\tif (inFile != NULL)\n\t{\n\t\tlong int savedUpdateTime;\n\t\tfread(&amp;savedUpdateTime, 1, sizeof(savedUpdateTime), inFile);\n\t\tfclose(inFile);\n\t\tif (savedUpdateTime == apkLastUpdateTime) {\n\t\t\tmylog(&quot;Assets are up to date.\\n&quot;);\n\t\t\treturn 0;\n\t\t}\n\t}\n\t\/\/ if here - need to update assets\n\tAAssetManager* am = androidApp-&gt;activity-&gt;assetManager;\n\tint buffSize = 1000000; \/\/guess, should be enough?\n\tchar* buff = new char&#91;buffSize];\n\n\tstd::vector&lt;std::string&gt; dirsToCheck; \/\/list of assets folders to check\n\tdirsToCheck.push_back(&quot;dt&quot;); \/\/root folder\n\twhile (dirsToCheck.size() &gt; 0) {\n\t\t\/\/open last element from directories vector\n\t\tstd::string dirPath = dirsToCheck.back();\n\t\tdirsToCheck.pop_back(); \/\/delete last element\n\t\t\/\/mylog(&quot;Scanning directory &lt;%s&gt;\\n&quot;, dirPath.c_str());\n\t\t\/\/make sure folder exists on local drive\n\t\tstd::string outPath = filesRoot + &quot;\/&quot; + dirPath; \/\/ .c_str();\n\t\tstruct stat info;\n\t\tint statRC = stat(outPath.c_str(), &amp;info);\n\t\tif (statRC == 0)\n\t\t\tmylog(&quot;%s folder exists.\\n&quot;, outPath.c_str());\n\t\telse {\n\t\t\t\/\/ mylog(&quot;Try to create %s\\n&quot;, outPath.c_str());\n\t\t\tint status = mkdir(outPath.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);\n\t\t\tif (status == 0)\n\t\t\t\tmylog(&quot;%s folder added.\\n&quot;, outPath.c_str());\n\t\t\telse {\n\t\t\t\tmylog(&quot;ERROR creating, status=%d, errno: %s.\\n&quot;, status, std::strerror(errno));\n\t\t\t}\n\t\t}\n\t\t\/\/get folder&#039;s content\n\t\tstd::vector&lt;std::string&gt; dirItems = list_assets(androidApp, dirPath.c_str());\n\t\tint itemsN = dirItems.size();\n\t\t\/\/scan directory items\n\t\tfor (int i = 0; i &lt; itemsN; i++) {\n\t\t\tstd::string itemPath = dirPath + &quot;\/&quot; + dirItems.at(i).c_str();\n\t\t\t\/\/mylog(&quot;New item: &lt;%s&gt; - &quot;, itemPath.c_str());\n\t\t\t\/\/try to open it to see if it&#039;s a file\n\t\t\tAAsset* asset = AAssetManager_open(am, itemPath.c_str(), AASSET_MODE_UNKNOWN);\n\t\t\tif (asset != NULL) {\n\t\t\t\tlong size = AAsset_getLength(asset);\n\t\t\t\t\/\/mylog(&quot;It&#039;s a file, size = %d - &quot;, size);\n\t\t\t\tif (size &gt; buffSize) {\n\t\t\t\t\tmylog(&quot;ERROR in main.cpp-&gt;updateAssets(): File %s is too big, skipped.\\n&quot;, itemPath.c_str());\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tAAsset_read(asset, buff, size);\n\t\t\t\t\toutPath = filesRoot + &quot;\/&quot; + itemPath;\n\t\t\t\t\tFILE* outFile = fopen(outPath.c_str(), &quot;w+&quot;);\n\t\t\t\t\tif (outFile != NULL)\n\t\t\t\t\t{\n\t\t\t\t\t\tfwrite(buff, 1, size, outFile);\n\t\t\t\t\t\tfflush(outFile);\n\t\t\t\t\t\tfclose(outFile);\n\t\t\t\t\t\tmylog(&quot;%s saved\\n&quot;, outPath.c_str());\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tmylog(&quot;ERROR in main.cpp-&gt;updateAssets(): Can&#039;t create file %s\\n&quot;, itemPath.c_str());\n\t\t\t\t}\n\t\t\t\tAAsset_close(asset);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tdirsToCheck.push_back(itemPath);\n\t\t\t\t\/\/mylog(&quot;It&#039;s a folder, add to folders list to check.\\n&quot;);\n\t\t\t}\n\t\t}\n\t\tdirItems.clear();\n\t}\n\tdelete&#91;] buff;\n\t\/\/ save updateTime\n\tFILE* outFile = fopen(updateTimeFilePath.c_str(), &quot;w+&quot;);\n\tif (outFile != NULL)\n\t{\n\t\tfwrite(&amp;apkLastUpdateTime, 1, sizeof(apkLastUpdateTime), outFile);\n\t\tfflush(outFile);\n\t\tfclose(outFile);\n\t}\n\telse\n\t\tmylog(&quot;ERROR creating %s\\n&quot;, updateTimeFilePath.c_str());\n\treturn 1;\n}\n\/**\n* This is the main entry point of a native application that is using\n* android_native_app_glue.  It runs in its own thread, with its own\n* event loop for receiving input events and doing other things.\n*\/\nvoid android_main(struct android_app* state) {\n\n\t\/\/state-&gt;userData = &amp;engine;\n\tstate-&gt;onAppCmd = engine_handle_cmd;\n\tstate-&gt;onInputEvent = engine_handle_input;\n\tandroidApp = state;\n\n\t\/\/ Prepare to monitor accelerometer\n\tsensorManager = ASensorManager_getInstance();\n\taccelerometerSensor = ASensorManager_getDefaultSensor(sensorManager,\n\t\tASENSOR_TYPE_ACCELEROMETER);\n\tsensorEventQueue = ASensorManager_createEventQueue(sensorManager,\n\t\tstate-&gt;looper, LOOPER_ID_USER, NULL, NULL);\n\n\t\/\/ Read all pending events.\n\tint ident;\n\tint events;\n\tstruct android_poll_source* source;\n\t\/\/wait for display\n\twhile (androidDisplay == NULL) {\n\t\t\/\/ No display yet.\n\t\t\/\/std::this_thread::sleep_for(std::chrono::seconds(1));\n\t\t\/\/mylog(&quot;No display yet\\n&quot;);\n\t\t\/\/wait for event\n\t\twhile ((ident = ALooper_pollAll(0, NULL, &amp;events,\n\t\t\t(void**)&amp;source)) &gt;= 0) {\n\t\t\t\/\/ Process this event.\n\t\t\tif (source != NULL) {\n\t\t\t\tsource-&gt;process(state, source);\n\t\t\t}\n\t\t}\n\t}\n\n\tEGLint w, h;\n\teglQuerySurface(androidDisplay, androidSurface, EGL_WIDTH, &amp;w);\n\teglQuerySurface(androidDisplay, androidSurface, EGL_HEIGHT, &amp;h);\n\ttheGame.onScreenResize(w, h);\n\n\t\/\/retrieving files root\n\tfilesRoot.assign(androidApp-&gt;activity-&gt;internalDataPath);\n\tmylog(&quot;filesRoot = %s\\n&quot;, filesRoot.c_str());\n\n\tupdateAssets();\n\n\ttheGame.run();\n\n\tengine_term_display();\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>Just 1 missed line&#8230; (line 39).<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>17. Switch on, unlock, plug in, allow.<\/p>\n\n\n\n<p>Build and run. Now &#8211; works.<\/p>\n\n\n\n<p>VS top menu <em>-&gt; Debug -&gt; Stop Debugging<\/em><\/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\">Next I want to &#8220;draw&#8221; the joint (slit) between the pack and the lid, like a small normal map applied to existing textured mesh&#8217;s fragment, not to entire mesh and not to VirtualShape as we did before. This will require a few new concepts. Windows 1. Start VS, open&nbsp;C:\\CPP\\a997modeler\\p_windows\\p_windows.sln. The first new concept &#8211; marked [&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-1003","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\/1003","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=1003"}],"version-history":[{"count":45,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/1003\/revisions"}],"predecessor-version":[{"id":1095,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/1003\/revisions\/1095"}],"wp:attachment":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media?parent=1003"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=1003"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=1003"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}