{"id":832,"date":"2021-12-12T02:01:44","date_gmt":"2021-12-12T02:01:44","guid":{"rendered":"https:\/\/writingagame.com\/?p=832"},"modified":"2022-01-11T01:31:53","modified_gmt":"2022-01-11T01:31:53","slug":"chapter-28-model-loader","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2021\/12\/12\/chapter-28-model-loader\/","title":{"rendered":"Chapter 28. Model Loader"},"content":{"rendered":"\n<p>Our next goal is to move model building process <strong>out <\/strong>of <em>TheGame <\/em>class.<\/p>\n\n\n\n<p>We will need some kind of models <em>text descriptor<\/em>, XML format seems quite suitable. We will keep models descriptions outside of executable, in <strong>\/dt<\/strong> folder. Then we&#8217;ll need a class that will load, read and process these descriptors and build actual 3D models in the memory. <\/p>\n\n\n\n<p>We already have <strong>FileLoader <\/strong>class. Based on it, we will create a class that can parse XML formatted txt files. Will call it <strong>XMLParser<\/strong>. It will be a part of <em>engine<\/em>.<\/p>\n\n\n\n<p>1. Start VS, open&nbsp;<em>C:\\CPP\\<em>a997modeler<\/em>\\p_windows\\p_windows.sln<\/em>.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>2. Under <em>xEngine<\/em> add new header file <strong>XMLParser.h<\/strong><\/p>\n\n\n\n<p>Location: <em>C:\\CPP\\engine<\/em> <\/p>\n\n\n\n<p>Code:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [2,4,19]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &quot;FileLoader.h&quot;\n\nclass XMLparser : public FileLoader\n{\npublic:\n\tchar* readFrom;\n\tstd::string currentTag = &quot;&quot;;\n\tint tagLength = 0;\n\tstd::string tagName = &quot;&quot;;\n\tbool closedTag = false; \/\/ &gt; or \/&gt;\npublic:\n\tXMLparser(std::string filePath) : FileLoader(filePath) { removeComments(this); readFrom = pData; };\n\tstatic int removeComments(XMLparser* pXP);\n\tstatic int processSource(XMLparser* pXP);\n\tint nextTag() { return nextTag(this); }; \/\/returns 0 if no more tags, 1 - tag extractedb\n\tstatic bool nextTag(XMLparser* pXP); \/\/returns 0 if no more tags, 1 - tag extractedb\n\tstatic int nameEndsAt(std::string varName, std::string tag);\n\tvirtual int processTag() { return 1; };\n\tstatic std::string buildFullPath(XMLparser* pXP, std::string filePath);\n\n\tstatic bool varExists(std::string varName, std::string tag);\n\tstatic std::string getStringValue(std::string varName, std::string tag);\n\tstatic int setCharsValue(char* pChars, int charsLength, std::string varName, std::string tag);\n\tstatic int setIntValue(int* pInt, std::string varName, std::string tag);\n\tstatic int setFloatValue(float* pFloat, std::string varName, std::string tag);\n\tstatic int setIntArray(int* pInts, int arrayLength, std::string varName, std::string tag);\n\tstatic int setFloatArray(float* pFloats, int arrayLength, std::string varName, std::string tag);\n\tstatic int setUintColorValue(unsigned int* pInt, std::string varName, std::string tag);\n\tstatic int setIntBoolValue(int* pInt, std::string varName, std::string tag);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>Please note, it inherits <em>FileLoader <\/em>class.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p> 3. Under <em>xEngine<\/em> add new C++ file <strong>XMLParser.cpp<\/strong> <\/p>\n\n\n\n<p>Location: <em>C:\\CPP\\engine<\/em><\/p>\n\n\n\n<p>Code:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; 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 != 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 != 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><strong>XMLParser <\/strong>class will load given xml\/txt file, will remove comments, then will scan it tag by tag and will call <strong>virtual <\/strong><em>processTag()<\/em> function for each tag for execution. This function is <em>virtual <\/em>because <em>XMLParser <\/em>by itself doesn&#8217;t know what to do with these tags. Sibling class <strong>ModelLoader <\/strong>will know and will have it&#8217;s own  <em>processTag()<\/em> implementation.<\/p>\n\n\n\n<p>Also  <strong>XMLParser <\/strong> class has a set of functions for reading tag&#8217;s variables\/properties values.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Now, using <em>XMLParser<\/em>, we will create <strong>ModelLoader <\/strong>class, which will read given XML descriptor file and will execute it&#8217;s instructions in <em>ModelBuilder<\/em>. This class will belong to the <strong><em>modeler<\/em><\/strong>.<\/p>\n\n\n\n<p>4. Under <em>modeler <\/em>add new header file <strong>ModelLoader.h<\/strong> <\/p>\n\n\n\n<p>Location: <em>C:\\CPP\\engine\\modeler<\/em><\/p>\n\n\n\n<p>Code:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [2,5]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &quot;XMLparser.h&quot;\n#include &quot;ModelBuilder.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}\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 fillProps_vs(VirtualShape* pVS, std::string tagStr); \/\/virtual shape\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_mt(Material* pMT, std::string tagStr, ModelLoader* pML); \/\/Material\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};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p> Please note, it inherits <em>XMLParser<\/em> class.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>5. Under <em>modeler <\/em>add new C++ file <strong>ModelLoader.cpp<\/strong> <\/p>\n\n\n\n<p>Location: <em>C:\\CPP\\engine\\modeler<\/em><\/p>\n\n\n\n<p>Code:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; 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::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\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\tfor (int aN = 0; aN &lt; (int)applyTosVector.size(); aN++) {\n\t\tpMB-&gt;buildFace(pMB, applyTosVector.at(aN), pMB-&gt;pCurrentVShape, &amp;tc, &amp;tc2nm);\n\t}\n\t\/\/mylog(&quot;vertsN=%d\\n&quot;,pMB-&gt;vertices.size());\n\n\tpMB-&gt;releaseGroup(pMB);\n\treturn 1;\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\treturn 1;\n\t}\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\tif (pML-&gt;tagName.compare(&quot;\/group&quot;) == 0) {\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\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}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Also it will require some changes in <strong>ModelBuilder1base<\/strong>.<\/p>\n\n\n\n<p>6. Open <em>ModelBuilder1base.h<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [10,28,30]; 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\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\tint useSubjN(int subjN) { return useSubjN(this, subjN); };\n\tstatic int useSubjN(ModelBuilder1base* pMB, int subjN);\n\tint useMaterial(Material* pMT) { return useMaterial(this, pMT); };\n\tstatic int useMaterial(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\tint buildDrawJobs(std::vector&lt;GameSubj*&gt; gameSubjs) { return buildDrawJobs(this, gameSubjs); };\n\tstatic int buildDrawJobs(ModelBuilder1base* pMB, std::vector&lt;GameSubj*&gt; gameSubjs);\n\tstatic int rearrangeArraysForDrawJob(ModelBuilder1base* pMB, std::vector&lt;Vertex01*&gt; allVertices, std::vector&lt;Vertex01*&gt; useVertices, std::vector&lt;Triangle01*&gt; useTriangles);\n\tstatic int buildSingleDrawJob(Material* pMT, std::vector&lt;Vertex01*&gt; useVertices, std::vector&lt;Triangle01*&gt; useTriangles);\n\tstatic int moveGroupDg(ModelBuilder1base* pMB, float aX, float aY, float aZ, float kX, float kY, float kZ);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>Important changes:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><em>texturesHashMap<\/em> will allow <em>ModelLoader <\/em>address textures by given name.<\/li><li><em>materialsStack<\/em> will add an opportunity to switch between <em>Materials<\/em>.<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>7. Open <em>ModelBuilder1base.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;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\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\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\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\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\t\/\/if (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}\nvoid ModelBuilder1base::lockGroup(ModelBuilder1base* pMB) {\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}\nvoid ModelBuilder1base::releaseGroup(ModelBuilder1base* pMB) {\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<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Now &#8211; <strong>model<\/strong>.<\/p>\n\n\n\n<p>Having such a nice set of shaders, we can think about some REAL model. <\/p>\n\n\n\n<p>For example:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c28\/00.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>It is recognizable, simple, square and pretty enough.  <\/p>\n\n\n\n<p>Besides, it was under my hand.<\/p>\n\n\n\n<p>I combined involved projections into a single 1024&#215;512 PNG image:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c28\/marlboro03small.png\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Bluish images on the right are Normal Maps. We will need them later.<\/li><\/ul>\n\n\n\n<p>8. The model will belong to the <strong>Project<\/strong>, not to <em>engine<\/em>. So, in <strong>Windows File Explorer<\/strong> under <\/p>\n\n\n\n<p><em>C:\\CPP\\<strong>a997modeler<\/strong>\\dt<\/em> <\/p>\n\n\n\n<p>add new subfolder <\/p>\n\n\n\n<p><em>C:\\CPP\\a997modeler\\dt<strong>\\models\\misc\\marlboro01<\/strong><\/em>.<\/p>\n\n\n\nDownload <a href=\"https:\/\/writingagame.com\/img\/b01\/c28\/marlboro03small.png\" download=\"\">PNG file here<\/a>\n\n\n\n<p>and save it to  <em>C:\\CPP\\a997modeler\\dt\\models\\misc\\marlboro01\\<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Now &#8211; <strong>model descriptor<\/strong>.<\/p>\n\n\n\n<p>9. Copy this code to a <strong>Text Editor<\/strong> and save it to\/as<\/p>\n\n\n\n<p> <em>C:\\CPP\\a997modeler\\dt\\models\\misc\\marlboro01\\<strong>root01.txt<\/strong><\/em><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n&lt;texture_as=&quot;tx0&quot; src=&quot;marlboro03small.png&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,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;\/&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\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>Hope, it&#8217;s understandable what it does. Just in case:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Line 1: Loads the <em>Texture<\/em>. &#8220;tx0&#8221; &#8211; is just a name to be used in materials.<\/li><li>Line 2: Creates <em>Material<\/em> using <em>tx0<\/em>: textured Phong. All following &#8220;a&#8221; commands will use this material.<\/li><li>Line 3: Sets a <em>Virtual Shape<\/em>. The box is 55x85x23 mm. &#8220;whl&#8221; (width\/height\/length) has smaller numbers because we also have 1 mm <em>extensions <\/em>on all sides. &#8220;sectR=1&#8221; is a number of radial sections for extensions.<\/li><li>The rest: Applies projections to all sides. &#8220;xywh&#8221; &#8211; texture fragment coordinates (x, y, width, height).<\/li><li><em>ModelLoader&#8217;s<\/em> functionality is defined by these commands.<\/li><\/ul>\n\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Currently in <em>TheGame.cpp<\/em> model creation takes <strong>51 lines<\/strong> (lines from 22 to 72). Now it will be just 4.<\/p>\n\n\n\n<p>10. 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,25,31,32,59,60,61,64,135]; 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    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);\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>Other changes:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>We have a new function <em>newGameSubj(..)<\/em>. <em>ModelLoader <\/em>will use it to create new <em>GameSubjs<\/em>.<\/li><li><em>stageSize <\/em> now is 80&#215;120<\/li><li><em>nearClip<\/em> and <em>farClip<\/em> are a bit different<\/li><li>keystone effect handling (line 64)<\/li><\/ul>\n\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p><\/p>\n\n\n\n<p>11. In <em>TheGame.h<\/em> new declaration, <em>newGameSubj(..)<\/em> .<\/p>\n\n\n\n<p>Open  <em>TheGame.h<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [28]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &lt;vector&gt;\n#include &quot;GameSubj.h&quot;\n#include &quot;Camera.h&quot;\n\nclass TheGame\n{\npublic:\n\tint screenSize&#91;2];\n\tfloat screenAspectRatio = 1;\n\t\/\/synchronization\n\tlong long int lastFrameMillis = 0;\n\tint targetFPS = 30;\n\tint millisPerFrame = 1000 \/ targetFPS;\n\n\tbool bExitGame;\n\tCamera mainCamera;\n\tfloat dirToMainLight&#91;4] = { 1,1,1,0 };\n\n\t\/\/static arrays (vectors) of active GameSubjs\n\tstatic std::vector&lt;GameSubj*&gt; gameSubjs;\npublic:\n\tint run();\n\tint getReady();\n\tint drawFrame();\n\tint cleanUp();\n\tint onScreenResize(int width, int height);\n\tstatic GameSubj* newGameSubj(std::string subjClass);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\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\/c28\/01.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<iframe loading=\"lazy\" width=\"100%\" height=\"400\" src=\"https:\/\/www.youtube.com\/embed\/O476Sb5Koqs?controls=0&amp;autoplay=1&amp;loop=1&amp;playlist=O476Sb5Koqs\" title=\"Model Loader\" 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<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Now<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Android<\/h2>\n\n\n\n<p>13. Close and re-open VS. Open&nbsp;<em>C:\\CPP\\a997modeler<\/em><strong><em>\\p_android\\p_android.sln<\/em>.<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>14. Under <em>xEngine <\/em>add <strong>Existing <\/strong>Item<\/p>\n\n\n\n<p>from <em>C:\\CPP\\engine<\/em>.<\/p>\n\n\n\n<p>Files <em>XMLparser.cpp<\/em> and <em>XMLparser.h<\/em><\/p>\n\n\n\n<p><strong>Add<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>15. Under <em><strong>modeler <\/strong><\/em>add <strong>Existing <\/strong>Item <\/p>\n\n\n\n<p>from <em>C:\\CPP\\engine\\modeler<\/em><\/p>\n\n\n\n<p>Files <em>ModelLoader.cpp<\/em> and <em>ModelLoader.h<\/em><\/p>\n\n\n\n<p><strong>Add<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>16. Turn on, unlock, plug in, allow.<\/p>\n\n\n\n<p>Build and run.<\/p>\n\n\n\n<p>Good.<\/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\">Our next goal is to move model building process out of TheGame class. We will need some kind of models text descriptor, XML format seems quite suitable. We will keep models descriptions outside of executable, in \/dt folder. Then we&#8217;ll need a class that will load, read and process these descriptors and build actual 3D [&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-832","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\/832","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=832"}],"version-history":[{"count":49,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/832\/revisions"}],"predecessor-version":[{"id":1249,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/832\/revisions\/1249"}],"wp:attachment":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media?parent=832"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=832"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=832"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}