{"id":1149,"date":"2022-01-09T05:01:12","date_gmt":"2022-01-09T05:01:12","guid":{"rendered":"https:\/\/writingagame.com\/?p=1149"},"modified":"2022-01-15T03:02:43","modified_gmt":"2022-01-15T03:02:43","slug":"chapter-35-wire-shader","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2022\/01\/09\/chapter-35-wire-shader\/","title":{"rendered":"Chapter 35. &#8220;Wire&#8221; shader"},"content":{"rendered":"\n<p>Here I want to add a golden sealing ribbon on the pack. It will be a <strong>line<\/strong>, a sequence of points instead of regular triangulars mesh. It will require:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>A set of new tags in <em>ModelLoader<\/em><\/li><li>New <strong>lineWidth <\/strong>property in <em>Material <\/em>class<\/li><li>In <em>DrawJob <\/em>we&#8217;ll need to adjust <em>lineWidth <\/em>depending on distance<\/li><li>Additional shader&#8217;s functionality<\/li><li>And, of course, updated model descriptor<\/li><\/ul>\n\n\n\n<p>1. Copy following code to a <strong>Text Editor<\/strong> and save it (overwrite) to\/as<\/p>\n\n\n\n<p><em>C:\\CPP\\<strong>a997modeler<\/strong>\\dt\\models\\misc\\marlboro01\\<strong>root01.txt<\/strong><\/em><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; highlight: [41]; 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; mark=&quot;box_front&quot;\/&gt;\n&lt;a=&quot;back v&quot;  xywh=&quot;2,1,323,495&quot; mark=&quot;box_back&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; mark=&quot;box_left&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\/\/joint (slit) between the pack and the lid\n&lt;group&gt;\n\t&lt;mt_adjust uTex2nm_use=&quot;tx0&quot; &gt;\n\t\t&lt;a2mesh wh=&quot;50,1&quot; xywh2nm=&quot;582,497,1,4&quot; all markedAs=&quot;box_right&quot; onThe=&quot;right&quot; py=24.6 az=31 \/&gt;\n\t\t&lt;a2mesh wh=&quot;50,1&quot; xywh2nm=&quot;582,497,1,4&quot; all markedAs=&quot;box_left&quot;  onThe=&quot;left&quot;  py=24.6 az=-31 \/&gt;\n\t\t&lt;a2mesh wh=&quot;53,1&quot; xywh2nm=&quot;582,497,1,4&quot; all markedAs=&quot;box_front&quot;               py=17.8 \/&gt;\n\t\t&lt;a2mesh wh=&quot;6 ,1&quot; xywh2nm=&quot;582,497,1,4&quot; all markedAs=&quot;box_back&quot;  onThe=&quot;back&quot;  py=31.5 px=23.5 \/&gt;\n\t\t&lt;a2mesh wh=&quot;6 ,1&quot; xywh2nm=&quot;582,497,1,4&quot; all markedAs=&quot;box_back&quot;  onThe=&quot;back&quot;  py=31.5 px=-23.5 \/&gt;\n\t&lt;\/mt_adjust&gt; \n&lt;\/group sizeD=&quot;0.1,0,0.1&quot;&gt; \n\/\/sealing ribbon\n&lt;mt_type=&quot;wire&quot; lineWidth=1.5 uColor=&quot;130,90,0&quot; &gt;\n&lt;line&gt;\n\t&lt;p pxyz=&quot;-27.6,16.5 ,0&quot; \/&gt;\n\t&lt;p dz=10.5 \/&gt; \/\/left side half\n\t&lt;p dxyz=&quot;1.1,0,1.1&quot; \/&gt; \/\/front left rib\n\t&lt;p dx=53 \/&gt; \/\/front side\n\t&lt;p dxyz=&quot;1.1,0,-1.1&quot; \/&gt; \/\/front right rib\n\t&lt;p dz=-21 \/&gt; \/\/right side\n\t&lt;p dxyz=&quot;-1.1,0,-1.1&quot; \/&gt; \/\/back right rib\n\t&lt;p dx=-53 \/&gt; \/\/back side\n\t&lt;p dxyz=&quot;-1.1,0,1.1&quot; \/&gt; \/\/back left rib\n\t&lt;p dz=16 \/&gt; \/\/left half\n\t&lt;p dxyz=&quot;-1,0,5&quot; \/&gt; \/\/ribbon &quot;tail&quot;\n\t&lt;p dz=1 \/&gt;\n&lt;\/line &gt;\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>Please note:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>New mt_type &#8220;wire&#8221; (line 52)<\/li><li>New Material property &#8220;lineWidth&#8221;<\/li><li>New tag &#8220;line&#8221;<\/li><li>New tags &#8220;p&#8221; (for &#8220;point&#8221;)<\/li><li>New properties &#8220;dxyz&#8221;, &#8220;dx&#8221; and so on. This is &#8220;delta&#8221;(difference) from previous point<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Also we&#8217;ll need some extra code (related to lines) in shaders. Also, taking this opportunity, I decided to move <em>HalfVector <\/em>calculations from <em>TheGame.cpp<\/em> to vertex shaders. Before we calculated  <em>HalfVector <\/em> just once for entire model. Now it will be calculated for each vertex. The result will be much more realistic.<\/p>\n\n\n\n<p><strong>Phong vertex shader:<\/strong><\/p>\n\n\n\n<p>2. Copy following code to a <strong>Text Editor<\/strong> and save it (overwrite) to\/as<\/p>\n\n\n\n<p><em>C:\\CPP\\<strong>engine<\/strong>\\dt\\shaders\\phong_<strong>v<\/strong>.txt<\/em><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [19,21,22,23,39,40,41,42,43,44,45]; title: ; notranslate\" title=\"\">\n\/\/#version 320 es\nprecision lowp float;\nuniform mat4 uMVP; \/\/ transform matrix (Model-View-Projection)\nuniform mat3 uMV3x3; \/\/ Model-View matrix (for calculating normals into eye space)\n\nin vec3 aPos; \/\/ position attribute (x,y,z)\n#if defined(USE_NORMALS)\n\tin vec3 aNormal; \/\/ normal attribute (x,y,z)\n\tout vec3 vNormal; \/\/ varying normal (to pass to fragment shader)\n#endif\n#if defined(USE_TUV0)\n\tin vec2 aTuv; \/\/attribute TUV (texture coordinates)\n\tout vec2 vTuv; \/\/varying TUV (pass to fragment shader)\n#endif\n#if defined(MIRROR)\n\tout vec2 vTuvMirror; \/\/varying TUV (pass to fragment shader)\n#endif\n#if defined(PHONG)\n\tuniform mat4 uMM; \/\/ Model matrix (for vHalfVector for glares)\n\tuniform vec3 uVectorToLight;\n\tuniform vec3 uCameraPosition; \/\/for calculating half vector for glares\n\tuniform float uSpecularIntencity; \/\/for calculating half vector for glares\n\tout vec3 vHalfVector;\n#endif\n\nvoid main(void) { \n\tgl_Position = uMVP * vec4(aPos, 1.0);\n#if defined(USE_NORMALS)\t\n\t\/\/ Transform the normal&#039;s orientation into eye space. \n\tvNormal = uMV3x3 * aNormal;\t\n#endif\n#if defined(USE_TUV0)\n\tvTuv = aTuv;\n#endif\n#if defined(MIRROR)\n\tvTuvMirror&#91;0] =  (gl_Position&#91;0]\/gl_Position&#91;3]*0.1+vNormal&#91;0]*0.4)+0.5;\n\tvTuvMirror&#91;1] = -(gl_Position&#91;1]\/gl_Position&#91;3]*0.1+vNormal&#91;1]*0.4)+0.5;\n#endif\n#if defined(PHONG)\n\tif(uSpecularIntencity &gt; 0.0){ \/\/for glares\n\t\tvec4 vxPos = uMM * vec4(aPos, 1.0); \/\/vertex position\n\t\tvec3 dirToCamera = normalize(uCameraPosition - vec3(vxPos));\n\t\tvHalfVector = normalize(dirToCamera + uVectorToLight);\n\t}\n#endif\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>Please note:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>We don&#8217;t use <em>uHalfVector <\/em>any more<\/li><li>Now we use <em>uniform mat4 uMM;<\/em> (Model matrix for vHalfVector for glares)<\/li><li>and <em>uniform vec3 uCameraPosition;<\/em> (for calculating half vector for glares)<\/li><li>We calculating <strong>varying <\/strong><em>vHalfVector <\/em>for each vertex<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p><strong>Phong fragment shader:<\/strong><\/p>\n\n\n\n<p>3. Copy following code to a <strong>Text Editor<\/strong> and save it (overwrite) to\/as<\/p>\n\n\n\n<p><em>C:\\CPP\\<strong>engine<\/strong>\\dt\\shaders\\phong_<strong>f<\/strong>.txt<\/em><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [102,103,104,105,106,121,122,123,124,125]; title: ; notranslate\" title=\"\">\n\/\/#version 320 es\nprecision lowp float;\nout vec4 FragColor; \/\/output pixel color\nuniform float uAlphaFactor; \/\/for semi-transparency\nuniform int uAlphaBlending; \/\/for semi-transparency\n\n#if defined(USE_NORMALS)\n\tin vec3 vNormal; \/\/normal passed from rasterizer\n#endif\n#if defined(USE_TEX0)\n\tuniform sampler2D uTex0;  \/\/texture id\n\tuniform sampler2D uTex3;  \/\/translate texture id\n\tuniform int uTex0translateChannelN;\n#else\n\tuniform vec4 uColor;\n#endif\n#if defined(USE_TUV0)\n\tin vec2 vTuv; \/\/varying TUV (passed from vertex shader)\n#endif\n#if defined(MIRROR)\n\tin vec2 vTuvMirror; \/\/varying TUV (passed from vertex shader)\n#endif\n#if defined(OVERMASK)\n\tuniform sampler2D uTex1mask;  \/\/texture id\n\tuniform int uTex1alphaChannelN;\n\tuniform int uTex1alphaNegative;\n#endif\n\n#if defined(PHONG)\n\tuniform float uAmbient;\n\tuniform float uSpecularIntencity;\n\tuniform float uSpecularMinDot;\n\tuniform float uSpecularPowerOf;\n\n\tuniform vec3 uVectorToLight;\n\tin vec3 vHalfVector;\n#endif\n\nvoid main(void) {\n\n\tvec4 outColor;\n\tfloat alpha = 1.0;\n#if defined(OVERMASK)\n\toutColor = texture(uTex1mask, vTuv);\n\talpha = outColor&#91;uTex1alphaChannelN];\n\tif(uTex1alphaNegative &gt; 0)\n\t\talpha = 1.0 - alpha;\n\tif(alpha &lt; 0.5){\n\t\tif(uAlphaBlending &gt; 0){\n\t\t\tif(alpha == 0.0){\n\t\t\t\tdiscard;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\telse{ \/\/no AlphaBlending\n\t\t\tdiscard;\n\t\t\treturn;\n\t\t}\n\t}\n#endif\n#if defined(USE_TEX0)\n\t#if defined(MIRROR)\n\t\toutColor = texture(uTex0, vTuvMirror);\n\t#else\n\t\toutColor = texture(uTex0, vTuv);\n\t#endif\n\tif(uTex0translateChannelN &gt;= 0){ \/\/translate channel\n\t\tvec2 tuv3;\n\t\ttuv3&#91;0] = outColor&#91;uTex0translateChannelN];\n\t\ttuv3&#91;1] = 0.0;\n\t\toutColor = texture(uTex3, tuv3);\n\t}\n\tFragColor = outColor;\n#else\n\tFragColor = uColor;\n#endif\n\tif(FragColor.a != 1.0){\n\t\talpha *= FragColor.a;\n\t\tif(alpha &lt; 0.5){\n\t\t\tif(uAlphaBlending &gt; 0){\n\t\t\t\tif(alpha == 0.0){\n\t\t\t\t\tdiscard;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse{ \/\/no AlphaBlending\n\t\t\t\tdiscard;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n#if defined(USE_NORMALS)\n\tvec3 vNormalNormal = normalize(vNormal);\n#endif\n\n#if defined(PHONG)\n\tif(uAmbient&lt;1.0){\n\t\t\/\/ Calculate the dot product of the light vector and vertex normal. If the normal and light vector are\n\t\t\/\/ pointing in the same direction then it will get max illumination.\n\t\tfloat dotProduct = dot(vNormalNormal, uVectorToLight);\n#if defined(WIRE)\n\t\tif(dotProduct &lt; 0.0)\n\t\t\tdotProduct = -dotProduct;\n\t\tdotProduct = 1.0 - dotProduct;\n#endif\t\t\n\t\t\/\/ count ambient component\n\t\tdotProduct += uAmbient;\n\t\tif(dotProduct &lt; uAmbient)\n\t\t\tdotProduct = uAmbient;\n\n\t\t\/\/ Multiply the color by the lightIntencity illumination level to get final output color.\n\t\tFragColor *= dotProduct;\n\t}\n\tif(uSpecularIntencity&gt;0.0){\n\t\t\/\/specular light\n\t\t\/\/ INTENSITY OF THE SPECULAR LIGHT\n\t\t\/\/ DOT PRODUCT OF NORMAL VECTOR AND THE HALF VECTOR TO THE POWER OF THE SPECULAR HARDNESS\n\t\tvec3 vNormalHalfVector = normalize(vHalfVector);\n\t\tfloat dotProduct = dot(vNormalNormal, vNormalHalfVector);\n#if defined(WIRE)\n\t\tif(dotProduct &lt; 0.0)\n\t\t\tdotProduct = -dotProduct;\n\t\tdotProduct = 1.0 - dotProduct;\n#endif\t\t\n\t\tif(dotProduct&gt;uSpecularMinDot){\n\t\t\tfloat specularIntencity = pow(dotProduct, uSpecularPowerOf) * uSpecularIntencity;\t\t\n\t\t\tif(specularIntencity &gt; uSpecularIntencity)\n\t\t\t\tspecularIntencity = uSpecularIntencity;\n\t\t\tFragColor += specularIntencity;\n\t\t}\n\t}\n#endif\n\tif(uAlphaFactor != 1.0)\n\t\talpha *= uAlphaFactor;\t\n\tFragColor.a = alpha;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Line (wire) related changes are highlighted<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p><strong>Normal map vertex shader:<\/strong><\/p>\n\n\n\n<p>4. Copy following code to a&nbsp;<strong>Text Editor<\/strong>&nbsp;and save it (overwrite) to\/as<\/p>\n\n\n\n<p><em>C:\\CPP\\<strong>engine<\/strong>\\dt\\shaders\\nm_<strong>v<\/strong>.txt<\/em><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [5,14,15,47,48,49,50,51,52]; title: ; notranslate\" title=\"\">\n\/\/#version 320 es\nprecision lowp float;\nuniform mat4 uMVP; \/\/ transform matrix (Model-View-Projection)\nuniform mat3 uMV3x3; \/\/ Model-View matrix (for calculating normals into eye space)\nuniform mat4 uMM; \/\/ Model matrix (for vHalfVector for glares)\nin vec3 aPos; \/\/ position attribute (x,y,z)\nin vec3 aNormal; \/\/ normal attribute (x,y,z)\n\/\/normal map\nin vec3 aTangent;\nin vec3 aBinormal;\nin vec2 aTuv2; \/\/attribute TUV2 (texture coordinates)\nout vec2 vTuv2; \/\/varying TUV2 (pass to fragment shader)\nuniform vec3 uVectorToLight;\nuniform vec3 uCameraPosition; \/\/for calculating half vector for glares\nuniform float uSpecularIntencity; \/\/for calculating half vector for glares\n\nout vec3 tbnVectorToLight;\nout vec3 tbnHalfVector;\n#if defined(MIRROR)\n\tout vec2 vScreenPosition01;\n\tout mat3 inversedTBN;\n#endif\n#if defined(USE_TUV0)\n\tin vec2 aTuv; \/\/attribute TUV (texture coordinates)\n\tout vec2 vTuv; \/\/varying TUV (pass to fragment shader)\n#endif\n\nvoid main(void) { \n\tgl_Position = uMVP * vec4(aPos, 1.0);\n#if defined(USE_TUV0)\n\tvTuv = aTuv;\n#endif\n\n\tvTuv2 = aTuv2;\n\n\t\/\/ Transform the normal&#039;s orientation into eye space.    \n\tvec3 N = uMV3x3 * aNormal;\n\tvec3 T = uMV3x3 * aTangent;\n\tvec3 B = uMV3x3 * aBinormal;\n\t\/\/build TBN matrix\n\tmat3 TBN = mat3(\n\t\t\tT&#91;0],B&#91;0],N&#91;0],\n\t\t\tT&#91;1],B&#91;1],N&#91;1],\n\t\t\tT&#91;2],B&#91;2],N&#91;2]\n\t\t\t);\n\ttbnVectorToLight = TBN * uVectorToLight;\n\tif(uSpecularIntencity &gt; 0.0){ \/\/for glares\n\t\tvec4 vxPos = uMM * vec4(aPos, 1.0); \/\/vertex position\n\t\tvec3 dirToCamera = normalize(uCameraPosition - vec3(vxPos));\n\t\tvec3 vHalfVector = normalize(dirToCamera + uVectorToLight);\n\t\ttbnHalfVector = TBN * vHalfVector;\n\t}\n#if defined(MIRROR)\n\tvScreenPosition01&#91;0] =  (gl_Position&#91;0]\/gl_Position&#91;3])*0.1;\n\tvScreenPosition01&#91;1] = -(gl_Position&#91;1]\/gl_Position&#91;3])*0.1;\n\tinversedTBN = inverse(TBN);\n#endif\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>Normal map fragment shader:<\/strong><\/p>\n\n\n\n<p>5. Copy following code to a&nbsp;<strong>Text Editor<\/strong>&nbsp;and save it (overwrite) to\/as<\/p>\n\n\n\n<p><em>C:\\CPP\\<strong>engine<\/strong>\\dt\\shaders\\nm_<strong>f<\/strong>.txt<\/em><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [120,121,122,123,124,138,139,140,141,142]; title: ; notranslate\" title=\"\">\n\/\/#version 320 es\nprecision lowp float;\nout vec4 FragColor; \/\/output pixel color\nuniform float uAlphaFactor; \/\/for semi-transparency\nuniform int uAlphaBlending; \/\/for semi-transparency\n\nin vec2 vTuv2;\nuniform sampler2D uTex2nm;\nin vec3 tbnVectorToLight;\nin vec3 tbnHalfVector;\n\n#if defined(USE_TEX0)\n\tuniform sampler2D uTex0;  \/\/texture id\n\tuniform sampler2D uTex3;  \/\/translate texture id\n\tuniform int uTex0translateChannelN;\n#else\n\tuniform vec4 uColor;\n#endif\n#if defined(USE_TUV0)\n\tin vec2 vTuv; \/\/varying TUV (passed from vertex shader)\n#endif\n#if defined(OVERMASK)\n\tuniform sampler2D uTex1mask;  \/\/texture id\n\tuniform int uTex1alphaChannelN;\n\tuniform int uTex1alphaNegative;\n#endif\n#if defined(MIRROR)\n\tin vec2 vScreenPosition01;\n\tin mat3 inversedTBN;\n#endif\nuniform float uAmbient;\nuniform float uSpecularIntencity;\nuniform float uSpecularMinDot;\nuniform float uSpecularPowerOf;\n\nvoid main(void) {\n\tvec4 tbnNormal4 = texture(uTex2nm, vTuv2);\n\tfloat alpha = tbnNormal4.a;\n\n\tif(alpha &lt; 0.5){\n\t\tif(uAlphaBlending &gt; 0){\n\t\t\tif(alpha == 0.0){\n\t\t\t\tdiscard;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\telse{ \/\/no AlphaBlending\n\t\t\tdiscard;\n\t\t\treturn;\n\t\t}\n\t}\n\t\/\/black?\n\tif(tbnNormal4.b &lt; 0.3){\n\t\tFragColor = vec4(0.0,0.0,0.0,alpha);\n\t\treturn;\n\t}\t\n\tvec4 outColor;\n#if defined(OVERMASK)\n\toutColor = texture(uTex1mask, vTuv);\n\tfloat alpha2 = outColor&#91;uTex1alphaChannelN];\n\tif(uTex1alphaNegative &gt; 0)\n\t\talpha2 = 1.0 - alpha2;\n\tif(alpha2 &lt; 1.0){\n\t\talpha *= alpha2;\n\t\tif(alpha &lt; 0.5){\n\t\t\tif(uAlphaBlending &gt; 0){\n\t\t\t\tif(alpha == 0.0){\n\t\t\t\t\tdiscard;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse{ \/\/no AlphaBlending\n\t\t\t\tdiscard;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n#endif\n\tvec3 vNormalNormal = normalize(vec3(tbnNormal4) * 2.0 - 1.0);\n\n#if defined(USE_TEX0)\n\t#if defined(MIRROR)\n\t\tvec3 inversedNormal = normalize(inversedTBN * vNormalNormal);\n\t\tvec2 vTuvMirror;\n\t\tvTuvMirror&#91;0] =  (vScreenPosition01&#91;0]+inversedNormal&#91;0]*0.4)+0.5;\n\t\tvTuvMirror&#91;1] = -(vScreenPosition01&#91;1]+inversedNormal&#91;1]*0.4)+0.5;\n\t\toutColor = texture(uTex0, vTuvMirror);\n\t#else\n\t\toutColor = texture(uTex0, vTuv);\n\t#endif\n\tif(uTex0translateChannelN &gt;= 0){ \/\/translate channel\n\t\tvec2 tuv3;\n\t\ttuv3&#91;0] = outColor&#91;uTex0translateChannelN];\n\t\ttuv3&#91;1] = 0.0;\n\t\toutColor = texture(uTex3, tuv3);\n\t}\n\tFragColor = outColor;\n#else\n\tFragColor = uColor;\n#endif\n\tif(FragColor.a != 1.0){\n\t\talpha *= FragColor.a;\n\t\tif(alpha &lt; 0.5){\n\t\t\tif(uAlphaBlending &gt; 0){\n\t\t\t\tif(alpha == 0.0){\n\t\t\t\t\tdiscard;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse{ \/\/no AlphaBlending\n\t\t\t\tdiscard;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\tif(uAmbient&lt;1.0){\n\t\t \/\/ Calculate the dot product of the light vector and vertex normal. If the normal and light vector are\n\t\t \/\/ pointing in the same direction then it will get max illumination.\n\t\t float dotProduct = dot(vNormalNormal, normalize(tbnVectorToLight));\n#if defined(WIRE)\n\t\tif(dotProduct &lt; 0.0)\n\t\t\tdotProduct = -dotProduct;\n\t\tdotProduct = 1.0 - dotProduct;\n#endif\t\t\n\t\t \/\/ count ambient component\n\t\t dotProduct += uAmbient;\n\t\t if(dotProduct &lt; uAmbient)\n\t\t\tdotProduct = uAmbient;\n\n\t\t \/\/ Multiply the color by the lightIntencity illumination level to get final output color.\n\t\t FragColor *= dotProduct;\n\t}\n\tif(uSpecularIntencity&gt;0.0){\n\t\t\/\/specular light\n\t\t\/\/ INTENSITY OF THE SPECULAR LIGHT\n\t\t\/\/ DOT PRODUCT OF NORMAL VECTOR AND THE HALF VECTOR TO THE POWER OF THE SPECULAR HARDNESS\n\t\tfloat dotProduct = dot(vNormalNormal, normalize(tbnHalfVector));\n#if defined(WIRE)\n\t\tif(dotProduct &lt; 0.0)\n\t\t\tdotProduct = -dotProduct;\n\t\tdotProduct = 1.0 - dotProduct;\n#endif\t\t\n\t\tif(dotProduct&gt;uSpecularMinDot){\n\t\t\tfloat specularIntencity = pow(dotProduct, uSpecularPowerOf) * uSpecularIntencity;\t\t\n\t\t\tif(specularIntencity &gt; uSpecularIntencity)\n\t\t\t\tspecularIntencity = uSpecularIntencity;\n\t\t\tFragColor += specularIntencity;\n\t\t}\n\t}\n\tif(uAlphaFactor != 1.0)\n\t\talpha *= uAlphaFactor;\t\n\tFragColor.a = alpha;\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; SW part.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Windows<\/h2>\n\n\n\n<p>6. 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>7. Open  <em>Material.h<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [25,28]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &quot;MyColor.h&quot;\n#include &lt;string&gt;\n\nclass Material\n{\npublic:\n\tchar shaderType&#91;20] = &quot;&quot;;\n\tint shaderN = -1;\n\tint primitiveType = GL_TRIANGLES;\n\tMyColor uColor;\n\tint uTex0 = -1;\n\tint uTex1mask = -1;\n\tint uTex2nm = -1;\n\tint uTex3 = -1;\n\tint uTex1alphaChannelN = 3; \/\/default - alpha channel for mask\n\tint uTex1alphaNegative = 0; \/\/default - alpha channel not negative\n\tint uTex0translateChannelN = -1; \/\/translate tex0 to tex3 by channelN. Default -1 - don&#039;t translate\n\n\tint uAlphaBlending = 0; \/\/for semi-transparency\n\tfloat uAlphaFactor = 1; \/\/for semi-transparency\n\tfloat uAmbient = 0.4f; \/\/ambient light\n\t\/\/specular light parameters\n\tfloat uSpecularIntencity = 0.8f;\n\tfloat uSpecularMinDot = 0.8f;\n\tfloat uSpecularPowerOf = 20.0f;\n\n\tfloat lineWidth = 1;\n\npublic:\n\tint pickShaderNumber() { return pickShaderNumber(this); };\n\tstatic int pickShaderNumber(Material* pMT);\n\tvoid setShaderType(std::string needType) { setShaderType(this, needType); };\n\tstatic void setShaderType(Material* pMT, std::string needType) { myStrcpy_s(pMT-&gt;shaderType, 20, (char*)needType.c_str()); };\n\tvoid clear() { clear(this); };\n\tstatic void clear(Material* pMT);\n\tint assignShader(std::string needType) { return assignShader(this, needType); };\n\tstatic int assignShader(Material* pMT, std::string needType);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>New property  <strong>lineWidth <\/strong><\/li><li>Another change &#8211; <em>uSpecularMinDot = 0.8f;<\/em> (will make glares softer)<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>In <strong>ModelLoader <\/strong>class &#8211; new variable and new functionality (highlighted).<\/p>\n\n\n\n<p>8. Open&nbsp;<em> ModelLoader.h<\/em>&nbsp;and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [14]; 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#include &quot;MaterialAdjust.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;\n\tMaterialAdjust* pMaterialAdjust = NULL;\n\tint lineStartsAt = -1;\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(pModelBuilder,subjN);\n\t};\n\tvirtual ~ModelLoader() {\n\t\tif (!ownModelBuilder)\n\t\t\treturn;\n\t\tpModelBuilder-&gt;buildDrawJobs(pModelBuilder, *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\tvirtual int 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\tstatic int processTag_a2mesh(ModelLoader* pML);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>9.  Open&nbsp;<em> ModelLoader.cpp<\/em>&nbsp;and replace code by: <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258]; 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#include &quot;Polygon.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}\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\tbool resetTexture = false;\n\tstd::string varName = txName + &quot;_use&quot;;\n\tif (varExists(varName, pML-&gt;currentTag)) {\n\t\tif (setValueFromIntHashMap(pInt, pMB-&gt;texturesHashMap, varName, pML-&gt;currentTag) == 0) {\n\t\t\tmylog(&quot;ERROR in ModelLoader::setTexture: texture not in hashMap: %s\\n&quot;, pML-&gt;currentTag.c_str());\n\t\t\treturn -1;\n\t\t}\n\t\tresetTexture = true;\n\t}\n\telse{\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\tresetTexture = true;\n\t\t}\n\t}\n\tif(resetTexture)\n\t\treturn 1;\n\treturn 0; \/\/texture wasn&#039;t reset\n}\nint ModelLoader::setMaterialTextures(ModelLoader* pML, Material* pMT) {\n\tif (setTexture(pML, &amp;pMT-&gt;uTex0, &quot;uTex0&quot;) &gt; 0)\n\t\tpMT-&gt;uColor.clear();\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\tpMT-&gt;uTex0 = -1;\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\tsetFloatValue(&amp;pMT-&gt;lineWidth, &quot;lineWidth&quot;, tagStr);\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.compare(&quot;mt_type&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\tfillProps_mt(&amp;mt, pML-&gt;currentTag, pML);\n\t\tpMB-&gt;usingMaterialN = pMB-&gt;getMaterialN(pMB, &amp;mt);\n\t\treturn 1;\n\t}\n\tif (pML-&gt;tagName.compare(&quot;\/mt_type&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\tif (pML-&gt;tagName.compare(&quot;a2mesh&quot;) == 0)\n\t\treturn processTag_a2mesh(pML);\n\tif (pML-&gt;tagName.compare(&quot;mt_adjust&quot;) == 0) {\n\t\tif (pML-&gt;pMaterialAdjust != NULL)\n\t\t\tmylog(&quot;ERROR in ModelLoader::processTag %s, pMaterialAdjust is still busy. File: %s\\n&quot;, pML-&gt;currentTag.c_str(), pML-&gt;fullPath.c_str());\n\t\tpML-&gt;pMaterialAdjust = new (MaterialAdjust);\n\t\tfillProps_mt(pML-&gt;pMaterialAdjust, pML-&gt;currentTag, pML);\n\t\tpML-&gt;pMaterialAdjust-&gt;setWhat2adjust(pML-&gt;pMaterialAdjust, pML-&gt;currentTag);\n\t\treturn 1;\n\t}\n\tif (pML-&gt;tagName.compare(&quot;\/mt_adjust&quot;) == 0) {\n\t\tif (pML-&gt;pMaterialAdjust != NULL) {\n\t\t\tdelete pML-&gt;pMaterialAdjust;\n\t\t\tpML-&gt;pMaterialAdjust = NULL;\n\t\t}\n\t\treturn 1;\n\t}\n\tif (pML-&gt;tagName.compare(&quot;line&quot;) == 0) {\n\t\tMaterial mt;\n\t\t\/\/save previous material in stack\n\t\tif (pMB-&gt;usingMaterialN &gt;= 0){\n\t\t\tpMB-&gt;materialsStack.push_back(pMB-&gt;usingMaterialN);\n\t\t\tmemcpy(&amp;mt, pMB-&gt;materialsList.at(pMB-&gt;usingMaterialN),sizeof(Material));\n\t\t}\n\t\tmt.primitiveType = GL_LINE_STRIP;\n\t\tfillProps_mt(&amp;mt, pML-&gt;currentTag, pML);\n\t\tpMB-&gt;usingMaterialN = pMB-&gt;getMaterialN(pMB, &amp;mt);\n\t\t\/\/line starts\n\t\tpML-&gt;lineStartsAt = pMB-&gt;vertices.size();\n\t\treturn 1;\n\t}\n\tif (pML-&gt;tagName.compare(&quot;\/line&quot;) == 0) {\n\t\tpMB-&gt;vertices.back()-&gt;endOfSequence = 1;\n\t\tpML-&gt;lineStartsAt = -1;\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;p&quot;) == 0) {\n\t\t\/\/line point\n\t\tVertex01* pV = new Vertex01();\n\t\tif (pMB-&gt;vertices.size() &gt; pML-&gt;lineStartsAt)\n\t\t\tmemcpy(pV, pMB-&gt;vertices.back(), sizeof(Vertex01));\n\t\tpV-&gt;subjN = pMB-&gt;usingSubjN;\n\t\tpV-&gt;materialN = pMB-&gt;usingMaterialN;\n\t\tsetFloatArray(pV-&gt;aPos, 3, &quot;pxyz&quot;, pML-&gt;currentTag);\n\t\tsetFloatValue(&amp;pV-&gt;aPos&#91;0], &quot;px&quot;, pML-&gt;currentTag);\n\t\tsetFloatValue(&amp;pV-&gt;aPos&#91;1], &quot;py&quot;, pML-&gt;currentTag);\n\t\tsetFloatValue(&amp;pV-&gt;aPos&#91;2], &quot;pz&quot;, pML-&gt;currentTag);\n\t\tfloat dPos&#91;3] = { 0,0,0 };\n\t\tsetFloatArray(dPos, 3, &quot;dxyz&quot;, pML-&gt;currentTag);\n\t\tsetFloatValue(&amp;dPos&#91;0], &quot;dx&quot;, pML-&gt;currentTag);\n\t\tsetFloatValue(&amp;dPos&#91;1], &quot;dy&quot;, pML-&gt;currentTag);\n\t\tsetFloatValue(&amp;dPos&#91;2], &quot;dz&quot;, pML-&gt;currentTag);\n\t\tif (!v3equals(dPos, 0))\n\t\t\tfor (int i = 0; i &lt; 3; i++)\n\t\t\t\tpV-&gt;aPos&#91;i] += dPos&#91;i];\n\t\tpMB-&gt;vertices.push_back(pV);\n\t\treturn 1;\n\t}\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}\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\tTexCoords* pTC = NULL;\n\tif (varExists(&quot;xywh&quot;, tagStr)) {\n\t\tsetFloatArray(xywh, 4, &quot;xywh&quot;, tagStr);\n\t\tstd::string flipStr = getStringValue(&quot;flip&quot;, tagStr);\n\t\tTexCoords tc;\n\t\ttc.set(texN, xywh&#91;0], xywh&#91;1], xywh&#91;2], xywh&#91;3], flipStr);\n\t\tpTC = &amp;tc;\n\t}\n\tTexCoords* pTC2nm = NULL;\n\tif (varExists(&quot;xywh2nm&quot;, tagStr)) {\n\t\tsetFloatArray(xywh, 4, &quot;xywh2nm&quot;, tagStr);\n\t\tstd::string flipStr = getStringValue(&quot;flip2nm&quot;, tagStr);\n\t\tTexCoords tc2nm;\n\t\ttc2nm.set(pMT-&gt;uTex2nm, xywh&#91;0], xywh&#91;1], xywh&#91;2], xywh&#91;3], flipStr);\n\t\tpTC2nm = &amp;tc2nm;\n\t}\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\tpMB-&gt;buildFace(pMB, applyTosVector.at(aN), pVS_a, pTC, pTC2nm);\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}\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\n\tif (varExists(&quot;sizeD&quot;, tagStr)) { \/\/re-size\n\t\tfloat sizeD&#91;3];\n\t\tsetFloatArray(sizeD, 3, &quot;sizeD&quot;, tagStr);\n\t\t\/\/bounding box\n\t\tpGT-&gt;flagSelection(pGT, &amp;pMB-&gt;vertices, NULL);\n\t\tfloat bbMin&#91;3];\n\t\tfloat bbMax&#91;3];\n\t\tpGT-&gt;buildBoundingBoxFlagged(bbMin, bbMax, &amp;pMB-&gt;vertices);\n\t\tfor (int i = 0; i &lt; 3; i++) {\n\t\t\tfloat size = bbMax&#91;i] - bbMin&#91;i];\n\t\t\tpGT-&gt;scale&#91;i] = (size + sizeD&#91;i]) \/ size;\n\t\t}\n\t}\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}\nint ModelLoader::processTag_a2mesh(ModelLoader* pML) {\n\tModelBuilder* pMB = pML-&gt;pModelBuilder;\n\tstd::string tagStr = pML-&gt;currentTag;\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\t\/\/clone a copy\n\tstd::vector&lt;Vertex01*&gt; vx1;\n\tstd::vector&lt;Triangle01*&gt; tr1;\n\tgt.cloneFlagged(NULL, &amp;vx1, &amp;tr1, &amp;pMB-&gt;vertices, &amp;pMB-&gt;triangles);\n\t\/\/ build transform and inverted martrices\n\tmat4x4 transformMatrix;\n\tgt.buildTransformMatrix(&amp;gt, &amp;transformMatrix);\n\tmat4x4 transformMatrixInverted;\n\tmat4x4_invert(transformMatrixInverted, transformMatrix);\n\t\/\/move\/rotate cloned\n\tgt.flagAll(&amp;vx1, &amp;tr1);\n\t\/\/gt.transformFlagged(&amp;pMB-&gt;vertices, &amp;transformMatrixInverted);\n\tgt.transformFlaggedMx(&amp;vx1, &amp;transformMatrixInverted);\n\n\t\/\/gt.cloneFlagged(pMB, &amp;pMB-&gt;vertices, &amp;pMB-&gt;triangles, &amp;vx1, &amp;tr1);\n\n\tfloat wh&#91;2];\n\tsetFloatArray(wh, 2, &quot;wh&quot;, tagStr);\n\tPolygon frame;\n\tframe.setRectangle(&amp;frame, wh&#91;0], wh&#91;1]);\n\t\/\/destination arrays\n\tstd::vector&lt;Vertex01*&gt; vx2;\n\tstd::vector&lt;Triangle01*&gt; tr2;\n\tPolygon triangle;\n\tfor (int i = tr1.size() - 1; i &gt;= 0; i--) {\n\t\ttriangle.setTriangle(&amp;triangle, tr1.at(i), &amp;vx1);\n\t\tPolygon intersection;\n\t\tint pointsN = Polygon::xyIntersection(&amp;intersection, &amp;frame, &amp;triangle);\n\t\tif (pointsN &gt; 2) {\n\t\t\tPolygon::buildTriangles(&amp;intersection);\n\t\t\tGroupTransform::flagAll(&amp;intersection.vertices, &amp;intersection.triangles);\n\t\t\tGroupTransform::cloneFlagged(NULL, &amp;vx2, &amp;tr2, &amp;intersection.vertices, &amp;intersection.triangles);\n\t\t}\n\t}\n\tgt.flagAll(&amp;vx2, &amp;tr2);\n\t\/\/at this point we have cutted fragment facing us\n\tint vxTotal = vx2.size();\n\tint trTotal = tr2.size();\n\t\/\/apply adjusted material ?\n\tif (pML-&gt;pMaterialAdjust != NULL) {\n\t\t\/\/scan vertices to find new (unupdated) material\n\t\tint materialNsrc = -1; \/\/which N to replace\n\t\tint materialNdst = -1; \/\/replace by N \n\t\tfor (int vN = 0; vN &lt; vxTotal; vN++) {\n\t\t\tVertex01* pV = vx2.at(vN);\n\t\t\tif (pV-&gt;flag &lt; 0)\n\t\t\t\tcontinue;\n\t\t\tif (materialNsrc == pV-&gt;materialN)\n\t\t\t\tcontinue;\n\t\t\t\/\/have new material\n\t\t\tmaterialNsrc = pV-&gt;materialN;\n\t\t\tMaterial mt;\n\t\t\tMaterial* pMt0 = pMB-&gt;materialsList.at(materialNsrc);\n\t\t\tmemcpy(&amp;mt, pMt0, sizeof(Material));\n\t\t\t\/\/modify material\n\t\t\tMaterialAdjust::adjust(&amp;mt, pML-&gt;pMaterialAdjust);\n\t\t\tmaterialNdst = pMB-&gt;getMaterialN(pMB, &amp;mt);\n\t\t\tif (materialNsrc != materialNdst) {\n\t\t\t\t\/\/replace mtN in vx and tr arrays\n\t\t\t\tfor (int vN2 = vN; vN2 &lt; vxTotal; vN2++) {\n\t\t\t\t\tVertex01* pV2 = vx2.at(vN2);\n\t\t\t\t\tif (pV2-&gt;flag &lt; 0)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tif (materialNsrc == pV2-&gt;materialN)\n\t\t\t\t\t\tpV2-&gt;materialN = materialNdst;\n\t\t\t\t}\n\t\t\t\tfor (int tN2 = 0; tN2 &lt; trTotal; tN2++) {\n\t\t\t\t\tTriangle01* pT2 = tr2.at(tN2);\n\t\t\t\t\tif (pT2-&gt;flag &lt; 0)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tif (materialNsrc == pT2-&gt;materialN)\n\t\t\t\t\t\tpT2-&gt;materialN = materialNdst;\n\t\t\t\t}\n\t\t\t\tmaterialNsrc = materialNdst;\n\t\t\t}\n\t\t}\n\t}\n\telse { \/\/ pML-&gt;pMaterialAdjust == NULL, use pMB-&gt;usingMaterialN\n\t\tfor (int vN2 = 0; vN2 &lt; vxTotal; vN2++) {\n\t\t\tVertex01* pV2 = vx2.at(vN2);\n\t\t\tif (pV2-&gt;flag &lt; 0)\n\t\t\t\tcontinue;\n\t\t\tpV2-&gt;materialN = pMB-&gt;usingMaterialN;\n\t\t}\n\t\tfor (int tN2 = 0; tN2 &lt; trTotal; tN2++) {\n\t\t\tTriangle01* pT2 = tr2.at(tN2);\n\t\t\tif (pT2-&gt;flag &lt; 0)\n\t\t\t\tcontinue;\n\t\t\tpT2-&gt;materialN = pMB-&gt;usingMaterialN;\n\t\t}\n\t}\n\t\/\/apply xywh\/2nm ?\n\tif (varExists(&quot;xywh&quot;, tagStr) || varExists(&quot;xywh2nm&quot;, tagStr)) {\n\t\tMaterial* pMT = pMB-&gt;materialsList.at(vx2.at(0)-&gt;materialN);\n\t\tfloat xywh&#91;4] = { 0,0,1,1 };\n\t\tTexCoords* pTC = NULL;\n\t\tif (varExists(&quot;xywh&quot;, tagStr)) {\n\t\t\tsetFloatArray(xywh, 4, &quot;xywh&quot;, tagStr);\n\t\t\tstd::string flipStr = getStringValue(&quot;flip&quot;, tagStr);\n\t\t\tint texN = pMT-&gt;uTex1mask;\n\t\t\tif (texN &lt; 0)\n\t\t\t\ttexN = pMT-&gt;uTex0;\n\t\t\tTexCoords tc;\n\t\t\ttc.set(texN, xywh&#91;0], xywh&#91;1], xywh&#91;2], xywh&#91;3], flipStr);\n\t\t\tpTC = &amp;tc;\n\t\t}\n\t\tTexCoords* pTC2nm = NULL;\n\n\t\tif (varExists(&quot;xywh2nm&quot;, tagStr)) {\n\t\t\tsetFloatArray(xywh, 4, &quot;xywh2nm&quot;, tagStr);\n\t\t\tstd::string flipStr = getStringValue(&quot;flip2nm&quot;, tagStr);\n\t\t\tTexCoords tc2nm;\n\t\t\ttc2nm.set(pMT-&gt;uTex2nm, xywh&#91;0], xywh&#91;1], xywh&#91;2], xywh&#91;3], flipStr);\n\t\t\tpTC2nm = &amp;tc2nm;\n\t\t}\n\t\tpMB-&gt;applyTexture2flagged(&amp;vx2, &quot;front&quot;, pTC, false);\n\t\tpMB-&gt;applyTexture2flagged(&amp;vx2, &quot;front&quot;, pTC2nm, true);\n\t}\n\t\/\/move\/rotate\n\tgt.transformFlaggedMx(&amp;vx2, &amp;transformMatrix);\n\t\/\/clone back to modelBuilder arrays\n\tgt.cloneFlagged(pMB, &amp;pMB-&gt;vertices, &amp;pMB-&gt;triangles, &amp;vx2, &amp;tr2);\n\n\t\/\/clear memory\n\tfor (int i = vx1.size() - 1; i &gt;= 0; i--)\n\t\tdelete vx1.at(i);\n\tvx1.clear();\n\tfor (int i = tr1.size() - 1; i &gt;= 0; i--)\n\t\tdelete tr1.at(i);\n\ttr1.clear();\n\tfor (int i = vx2.size() - 1; i &gt;= 0; i--)\n\t\tdelete vx2.at(i);\n\tvx2.clear();\n\tfor (int i = tr2.size() - 1; i &gt;= 0; i--)\n\t\tdelete tr2.at(i);\n\ttr2.clear();\n\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>When we have a line loaded, we need to take care of <em>normals<\/em> to calculate light\/shading in the shader. However, in case of line, the <strong>only <\/strong>vector we can calculate is <em>line direction<\/em>, which is <strong>perpendicular <\/strong>to any line&#8217;s normal.<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c35\/01.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>An extra code in shaders uses <em>directions <\/em>instead of <em>normals<\/em>. We&#8217;ll calculate directions in  <strong>ModelBuilder<\/strong> class.<\/p>\n\n\n\n<p>10. Open\u00a0<em>ModelBuilder1base.h <\/em>and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [46]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &lt;string&gt;\n#include &lt;vector&gt;\n#include &quot;Vertex01.h&quot;\n#include &quot;Triangle01.h&quot;\n#include &quot;VirtualShape.h&quot;\n#include &quot;Group01.h&quot;\n#include &quot;Material.h&quot;\n#include &quot;GameSubj.h&quot;\n#include &lt;map&gt;\n\nclass ModelBuilder1base\n{\npublic:\n\tstd::vector&lt;Vertex01*&gt; vertices;\n\tstd::vector&lt;Triangle01*&gt; triangles;\n\tstd::vector&lt;int&gt; subjNumbersList;\n\tint usingSubjN = -1;\n\n\tstd::vector&lt;Group01*&gt; groupsStack;\n\tGroup01* pCurrentGroup = NULL;\n\tGroup01* pLastClosedGroup = NULL;\n\t\n\tstd::vector&lt;VirtualShape*&gt; vShapesStack;\n\tVirtualShape* pCurrentVShape = NULL;\n\n\tstd::vector&lt;Material*&gt; materialsList;\n\tint usingMaterialN = -1;\n\tstd::vector&lt;int&gt; materialsStack;\n\n\tstd::map&lt;std::string, int&gt; texturesHashMap;\npublic:\n\tvirtual ~ModelBuilder1base();\n\tstatic int useSubjN(ModelBuilder1base* pMB, int subjN);\n\tstatic int getMaterialN(ModelBuilder1base* pMB, Material* pMT);\n\tstatic void lockGroup(ModelBuilder1base* pMB);\n\tstatic void releaseGroup(ModelBuilder1base* pMB);\n\tstatic int addVertex(ModelBuilder1base* pMB, float kx, float ky, float kz, float nx, float ny, float nz);\n\tstatic int add2triangles(ModelBuilder1base* pMB, int nNW, int nNE, int nSW, int nSE, int n);\n\tstatic int addTriangle(ModelBuilder1base* pMB, int n0, int n1, int n2);\n\tstatic int buildDrawJobs(ModelBuilder1base* pMB, std::vector&lt;GameSubj*&gt; 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\tstatic int calculateTangentSpace(std::vector&lt;Vertex01*&gt; useVertices, std::vector&lt;Triangle01*&gt; useTriangles);\n\tstatic int finalizeLine(std::vector&lt;Vertex01*&gt; verts, int lineStartsAt=0, int lineEndsAt=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>11.  Open&nbsp;<em>ModelBuilder1base.cpp <\/em>and replace code by: <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [260,439]; title: ; notranslate\" title=\"\">\n#include &quot;ModelBuilder1base.h&quot;\n#include &quot;platform.h&quot;\n#include &quot;utils.h&quot;\n#include &quot;DrawJob.h&quot;\n#include &quot;Shader.h&quot;\n\nextern float degrees2radians;\n\nModelBuilder1base::~ModelBuilder1base() {\n\treleaseGroup(this);\n\n\t\/\/clear all vectors\n\tint itemsN = vertices.size();\n\tfor (int i = 0; i &lt; itemsN; i++)\n\t\tdelete vertices.at(i);\n\tvertices.clear();\n\n\titemsN = triangles.size();\n\tfor (int i = 0; i &lt; itemsN; i++)\n\t\tdelete triangles.at(i);\n\ttriangles.clear();\n\n\titemsN = vShapesStack.size();\n\tfor (int i = 0; i &lt; itemsN; i++)\n\t\tdelete vShapesStack.at(i);\n\tvShapesStack.clear();\n\n\titemsN = groupsStack.size();\n\tfor (int i = 0; i &lt; itemsN; i++)\n\t\tdelete groupsStack.at(i);\n\tgroupsStack.clear();\n\tif (pCurrentGroup != NULL)\n\t\tdelete pCurrentGroup;\n\tif (pLastClosedGroup != NULL)\n\t\tdelete pLastClosedGroup;\n\n\titemsN = materialsList.size();\n\tfor (int i = 0; i &lt; itemsN; i++)\n\t\tdelete materialsList.at(i);\n\tmaterialsList.clear();\n\n\tsubjNumbersList.clear();\n}\nint ModelBuilder1base::useSubjN(ModelBuilder1base* pMB, int subjN) {\n\tpMB-&gt;usingSubjN = subjN;\n\tint itemsN = pMB-&gt;subjNumbersList.size();\n\tbool newN = true;\n\tif (itemsN &gt; 0)\n\t\tfor (int i = 0; i &lt; itemsN; i++)\n\t\t\tif (pMB-&gt;subjNumbersList.at(i) == subjN) {\n\t\t\t\tnewN = false;\n\t\t\t\tbreak;\n\t\t\t}\n\tif (newN)\n\t\tpMB-&gt;subjNumbersList.push_back(subjN);\n\treturn subjN;\n}\nint ModelBuilder1base::getMaterialN(ModelBuilder1base* pMB, Material* pMT) {\n\tint itemsN = pMB-&gt;materialsList.size();\n\tif (itemsN &gt; 0)\n\t\tfor (int i = 0; i &lt; itemsN; i++)\n\t\t\tif (memcmp(pMB-&gt;materialsList.at(i), pMT, sizeof(Material)) == 0) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\/\/if here - add new material to the list\n\tMaterial* pMTnew = new Material(*pMT);\n\tpMB-&gt;materialsList.push_back(pMTnew);\n\treturn itemsN;\n}\nint ModelBuilder1base::add2triangles(ModelBuilder1base* pMB, int nNW, int nNE, int nSW, int nSE, int n) {\n\t\/\/indexes: NorthWest, NorthEast, SouthWest,SouthEast\n\tif (n % 2 == 0) { \/\/even number\n\t\taddTriangle(pMB, nNW, nSW, nNE);\n\t\taddTriangle(pMB, nNE, nSW, nSE);\n\t}\n\telse { \/\/odd number\n\t\taddTriangle(pMB, nNW, nSE, nNE);\n\t\taddTriangle(pMB, nNW, nSW, nSE);\n\t}\n\treturn pMB-&gt;triangles.size() - 1;\n}\nint ModelBuilder1base::addTriangle(ModelBuilder1base* pMB, int i0, int i1, int i2) {\n\tTriangle01* pTR = new Triangle01();\n\tpMB-&gt;triangles.push_back(pTR);\n\tpTR-&gt;idx&#91;0] = i0;\n\tpTR-&gt;idx&#91;1] = i1;\n\tpTR-&gt;idx&#91;2] = i2;\n\tpTR-&gt;subjN = pMB-&gt;usingSubjN;\n\tpTR-&gt;materialN = pMB-&gt;usingMaterialN;\n\t\/\/mark\n\tif (pMB-&gt;pCurrentGroup != NULL)\n\t\tif (strcmp(pMB-&gt;pCurrentGroup-&gt;marks, &quot;&quot;) != 0)\n\t\t\tmyStrcpy_s(pTR-&gt;marks, 124, pMB-&gt;pCurrentGroup-&gt;marks);\n\treturn pMB-&gt;triangles.size() - 1;\n}\nint ModelBuilder1base::addVertex(ModelBuilder1base* pMB, float kx, float ky, float kz, float nx, float ny, float nz) {\n\tVertex01* pVX = new Vertex01();\n\tpMB-&gt;vertices.push_back(pVX);\n\tpVX-&gt;aPos&#91;0] = kx;\n\tpVX-&gt;aPos&#91;1] = ky;\n\tpVX-&gt;aPos&#91;2] = kz;\n\t\/\/normal\n\tpVX-&gt;aNormal&#91;0] = nx;\n\tpVX-&gt;aNormal&#91;1] = ny;\n\tpVX-&gt;aNormal&#91;2] = nz;\n\tpVX-&gt;subjN = pMB-&gt;usingSubjN;\n\tpVX-&gt;materialN = pMB-&gt;usingMaterialN;\n\t\/\/mark\n\tif (pMB-&gt;pCurrentGroup != NULL)\n\t\tif (strcmp(pMB-&gt;pCurrentGroup-&gt;marks, &quot;&quot;) != 0)\n\t\t\tmyStrcpy_s(pVX-&gt;marks, 124, pMB-&gt;pCurrentGroup-&gt;marks);\n\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\t\/\/rearrangeArraysForDrawJob(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}\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 (DrawJob::lineWidthIsImportant(pMT-&gt;primitiveType))\n\t\tif(strcmp(pMT-&gt;shaderType,&quot;wire&quot;)==0)\n\t\t\tfinalizeLine(useVertices);\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}\nint ModelBuilder1base::finalizeLine(std::vector&lt;Vertex01*&gt; verts, int lineStartsAt, int lineEndsAt) {\n\tif (lineEndsAt &lt;= 0)\n\t\tlineEndsAt = verts.size() - 1;\n\tVertex01* pV0 = verts.at(lineStartsAt);\n\tVertex01* pV2 = verts.at(lineEndsAt);\n\tbool closedLine = false;\n\tif (v3match(pV0-&gt;aPos, pV2-&gt;aPos))\n\t\tclosedLine = true;\n\tfor (int vN = lineStartsAt; vN &lt;= lineEndsAt; vN++) {\n\t\tVertex01* pV = verts.at(vN);\n\t\t\/\/prev point\n\t\tif (vN == lineStartsAt) {\n\t\t\t\/\/first point\n\t\t\tif (closedLine)\n\t\t\t\tpV0 = verts.at(lineEndsAt);\n\t\t\telse\n\t\t\t\tpV0 = NULL;\n\t\t}\n\t\telse\n\t\t\tpV0 = verts.at(vN - 1);\n\t\t\/\/next point\n\t\tif (vN == lineEndsAt) {\n\t\t\t\/\/last point\n\t\t\tif (closedLine)\n\t\t\t\tpV2 = verts.at(lineStartsAt);\n\t\t\telse\n\t\t\t\tpV2 = NULL;\n\t\t}\n\t\telse\n\t\t\tpV2 = verts.at(vN + 1);\n\t\t\/\/distances to neighbor points\n\t\tfloat distFromPrev = 0;\n\t\tfloat dirFromPrev&#91;3] = { 0,0,0 };\n\t\tif (pV0 != NULL) {\n\t\t\tdistFromPrev = v3lengthFromTo(pV0-&gt;aPos, pV-&gt;aPos);\n\t\t\tv3dirFromTo(dirFromPrev, pV0-&gt;aPos, pV-&gt;aPos);\n\t\t}\n\t\tfloat distToNext = 0;\n\t\tfloat dirToNext&#91;3] = { 0,0,0 };\n\t\tif (pV2 != NULL) {\n\t\t\tdistToNext = v3lengthFromTo(pV-&gt;aPos, pV2-&gt;aPos);\n\t\t\tv3dirFromTo(dirToNext, pV-&gt;aPos, pV2-&gt;aPos);\n\t\t}\n\t\tfloat distTotal = distFromPrev + distToNext;\n\t\tfloat kPrev = distFromPrev \/ distTotal;\n\t\tfloat kNext = distToNext \/ distTotal;\n\t\tif (kPrev &gt; kNext * 3)\n\t\t\tv3copy(pV-&gt;aNormal, dirFromPrev);\n\t\telse if (kNext &gt; kPrev * 3)\n\t\t\tv3copy(pV-&gt;aNormal, dirToNext);\n\t\telse\n\t\t\tfor (int i = 0; i &lt; 3; i++)\n\t\t\t\tpV-&gt;aNormal&#91;i] = kPrev * dirFromPrev&#91;i] + kNext * dirToNext&#91;i];\n\t\tvec3_norm(pV-&gt;aNormal, pV-&gt;aNormal);\n\t}\n\treturn 1;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Another change here (line 260) &#8211; points number for <em>DrawJob <\/em>when we don&#8217;t have triangles (indices).<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>In <strong>DrawJob <\/strong>class we have new function <em> lineWidthIsImportant()<\/em> and new parameters in <em>executeDrawJob()<\/em>:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>uMM &#8211; Model&#8217;s transform matrix (for <em>HalfVector <\/em>calculations)<\/li><li>uCameraPosition <\/li><li>sizeUnitPixelsSize &#8211; for setting line width for rendering<\/li><\/ul>\n\n\n\n<p>12. Open<em> DrawJob.h<\/em>&nbsp;and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [43,44,45]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &quot;Material.h&quot;\n#include &lt;vector&gt;\n\nstruct AttribRef \/\/attribute reference\/description\n{\n\tunsigned int glVBOid = 0; \/\/buffer object id\n\tint offset = 0; \/\/variable&#039;s offset inside of VBO&#039;s element\n\tint stride = 0; \/\/Buffer&#039;s element size in bytes\n};\n\nclass DrawJob\n{\npublic:\n\tMaterial mt;\n\tint pointsN = 0; \/\/N of points to draw\n\tunsigned int glVAOid = 0; \/\/will hold data stream attributes mapping\/positions\n\tunsigned int glEBOid = 0; \/\/Element Buffer Object (vertex indices)\n\n\t\/\/common attributes\n\tAttribRef aPos;\n\tAttribRef aNormal;\n\tAttribRef aTuv;\n\tAttribRef aTuv2; \/\/for normal map\n\tAttribRef aTangent; \/\/for normal map\n\tAttribRef aBinormal; \/\/for normal map\n\n\t\/\/static arrays (vectors) of all loaded DrawJobs, VBO ids\n\tstatic std::vector&lt;DrawJob*&gt; drawJobs;\n\tstatic std::vector&lt;unsigned int&gt; buffersIds;\npublic:\n\tDrawJob();\n\tvirtual ~DrawJob(); \/\/destructor\n\tstatic int cleanUp();\n\tstatic int newBufferId();\n\tint buildVAO() { return buildVAOforShader(this, mt.shaderN); };\n\tstatic int buildVAOforShader(DrawJob* pDJ, int shaderN);\n\tstatic int attachAttribute(int varLocationInShader, int attributeSizeInFloats, AttribRef* pAttribRef);\n\n\tvirtual int setDesirableOffsets(int* pStride, int shaderN, int VBOid) { return setDesirableOffsetsForSingleVBO(this, pStride, shaderN, VBOid); };\n\tstatic int setDesirableOffsetsForSingleVBO(DrawJob* pDJ, int* pStride, int shaderN, int VBOid);\n\n\tint execute(float* uMVP, float* uMV, float* uMM, float* uVectorToLight, float* uCameraPosition, float sizeUnitPixelsSize = 0, Material* pMt=NULL) { return executeDrawJob(this, uMVP, uMV, uMM, uVectorToLight, uCameraPosition, sizeUnitPixelsSize, pMt); };\n\tstatic int executeDrawJob(DrawJob* pDJ, float* uMVP, float* uMV, float* uMM, float* uVectorToLight, float* uCameraPosition, float sizeUnitPixelsSize = 0, Material* pMt=NULL);\n\tstatic bool lineWidthIsImportant(int primitiveType);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>13. Open<em>&nbsp;DrawJob.cpp<\/em>&nbsp;and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [73,83,84,87,88,154,155,156,157,227]; title: ; notranslate\" title=\"\">\n#include &quot;DrawJob.h&quot;\n#include &quot;platform.h&quot;\n#include &quot;utils.h&quot;\n#include &quot;Shader.h&quot;\n#include &quot;Texture.h&quot;\n\n\/\/static arrays (vectors) of all loaded DrawJobs, VBO ids\nstd::vector&lt;DrawJob*&gt; DrawJob::drawJobs;\nstd::vector&lt;unsigned int&gt; DrawJob::buffersIds;\n\nDrawJob::DrawJob() {\n\tdrawJobs.push_back(this);\n}\nDrawJob::~DrawJob() {\n\tglBindBuffer(GL_ARRAY_BUFFER, 0);\n\tglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);\n\tif (glVAOid &gt; 0)\n\t\tglDeleteVertexArrays(1, &amp;glVAOid);\n}\nint DrawJob::newBufferId() {\n\tunsigned int bufferId;\n\tglGenBuffers(1, &amp;bufferId);\n\tbuffersIds.push_back(bufferId);\n\treturn (int)bufferId;\n}\nunsigned int activeVBOid;\nint DrawJob::buildVAOforShader(DrawJob* pDJ, int shaderN) {\n\t\/\/delete VAO if exists already\n\tif (pDJ-&gt;glVAOid &gt; 0) {\n\t\tglBindBuffer(GL_ARRAY_BUFFER, 0);\n\t\tglDeleteVertexArrays(1, &amp;(pDJ-&gt;glVAOid));\n\t}\n\tglGenVertexArrays(1, &amp;pDJ-&gt;glVAOid);\n\tglBindVertexArray(pDJ-&gt;glVAOid);\n\n\t\/\/open shader descriptor to access variables locations\n\tShader* pShader = Shader::shaders.at(pDJ-&gt;mt.shaderN);\n\n\tactiveVBOid = 0;\n\tattachAttribute(pShader-&gt;l_aPos, 3, &amp;pDJ-&gt;aPos);\n\tattachAttribute(pShader-&gt;l_aNormal, 3, &amp;pDJ-&gt;aNormal);\n\tattachAttribute(pShader-&gt;l_aTuv, 2, &amp;pDJ-&gt;aTuv);\n\tattachAttribute(pShader-&gt;l_aTuv2, 2, &amp;pDJ-&gt;aTuv2); \/\/for normal map\n\tattachAttribute(pShader-&gt;l_aTangent, 3, &amp;pDJ-&gt;aTangent); \/\/for normal map\n\tattachAttribute(pShader-&gt;l_aBinormal, 3, &amp;pDJ-&gt;aBinormal); \/\/for normal map\n\n\tif (pDJ-&gt;glEBOid &gt; 0)\n\t\tglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, pDJ-&gt;glEBOid);\n\n\tglBindVertexArray(0);\n\tglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);\n\tglBindBuffer(GL_ARRAY_BUFFER, 0);\n\treturn 1;\n}\n\nint DrawJob::attachAttribute(int varLocationInShader, int attributeSizeInFloats, AttribRef* pAR) {\n\tif (varLocationInShader &lt; 0)\n\t\treturn 0; \/\/not used in this shader\n\tif (pAR-&gt;glVBOid == 0) {\n\t\tmylog(&quot;ERROR in DrawJob::attachAttribute, nk such attribute\/VBO\\n&quot;);\n\t\treturn -1;\n\t}\n\tglEnableVertexAttribArray(varLocationInShader);\n\tif (activeVBOid != pAR-&gt;glVBOid) {\n\t\tactiveVBOid = pAR-&gt;glVBOid;\n\t\t\/\/attach input stream data\n\t\tglBindBuffer(GL_ARRAY_BUFFER, activeVBOid);\n\t}\n\tglVertexAttribPointer(varLocationInShader, attributeSizeInFloats, GL_FLOAT, GL_FALSE, pAR-&gt;stride, (void*)(long)pAR-&gt;offset);\n\treturn 1;\n}\n\nint DrawJob::executeDrawJob(DrawJob* pDJ, float* uMVP, float* uMV3x3, float* uMM, float* uVectorToLight, float* uCameraPosition, float sizeUnitPixelsSize, Material* pMt) {\n\tif (pMt == NULL)\n\t\tpMt = &amp;(pDJ-&gt;mt);\n\tglBindVertexArray(pDJ-&gt;glVAOid);\n\tShader* pShader = Shader::shaders.at(pMt-&gt;shaderN);\n\tglUseProgram(pShader-&gt;GLid);\n\t\/\/input uniforms\n\tglUniformMatrix4fv(pShader-&gt;l_uMVP, 1, GL_FALSE, (const GLfloat*)uMVP);\n\tif (pShader-&gt;l_uMV3x3 &gt;= 0)\n\t\tglUniformMatrix3fv(pShader-&gt;l_uMV3x3, 1, GL_FALSE, (const GLfloat*)uMV3x3);\n\tif (pShader-&gt;l_uMM &gt;= 0)\n\t\tglUniformMatrix4fv(pShader-&gt;l_uMM, 1, GL_FALSE, (const GLfloat*)uMM);\n\tif (pShader-&gt;l_uVectorToLight &gt;= 0)\n\t\tglUniform3fv(pShader-&gt;l_uVectorToLight, 1, (const GLfloat*)uVectorToLight);\n\tif (pShader-&gt;l_uCameraPosition &gt;= 0)\n\t\tglUniform3fv(pShader-&gt;l_uCameraPosition, 1, (const GLfloat*)uCameraPosition);\n\n\t\/\/attach textures\n\tif (pShader-&gt;l_uTex0 &gt;= 0) {\n\t\tint textureId = Texture::getGLid(pMt-&gt;uTex0);\n\t\t\/\/pass textureId to shader program\n\t\tglActiveTexture(GL_TEXTURE0); \/\/ activate the texture unit first before binding texture\n\t\tglBindTexture(GL_TEXTURE_2D, textureId);\n\t\t\/\/ Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.    \n\t\tglUniform1i(pShader-&gt;l_uTex0, 0);\n\t}\n\tif (pShader-&gt;l_uTex1mask &gt;= 0) {\n\t\tint textureId = Texture::getGLid(pMt-&gt;uTex1mask);\n\t\t\/\/pass textureId to shader program\n\t\tglActiveTexture(GL_TEXTURE1); \/\/ activate the texture unit first before binding texture\n\t\tglBindTexture(GL_TEXTURE_2D, textureId);\n\t\t\/\/ Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 1.    \n\t\tglUniform1i(pShader-&gt;l_uTex1mask, 1);\n\t}\n\tif (pShader-&gt;l_uTex2nm &gt;= 0) {\n\t\tint textureId = Texture::getGLid(pMt-&gt;uTex2nm);\n\t\t\/\/pass textureId to shader program\n\t\tglActiveTexture(GL_TEXTURE2); \/\/ activate the texture unit first before binding texture\n\t\tglBindTexture(GL_TEXTURE_2D, textureId);\n\t\t\/\/ Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 2.    \n\t\tglUniform1i(pShader-&gt;l_uTex2nm, 2);\n\t}\n\tif (pShader-&gt;l_uTex0translateChannelN &gt;= 0) {\n\t\tglUniform1i(pShader-&gt;l_uTex0translateChannelN, pMt-&gt;uTex0translateChannelN);\n\t\tif (pShader-&gt;l_uTex3 &gt;= 0 &amp;&amp; pMt-&gt;uTex3 &gt;= 0) {\n\t\t\tint textureId = Texture::getGLid(pMt-&gt;uTex3);\n\t\t\t\/\/pass textureId to shader program\n\t\t\tglActiveTexture(GL_TEXTURE3); \/\/ activate the texture unit first before binding texture\n\t\t\tglBindTexture(GL_TEXTURE_2D, textureId);\n\t\t\t\/\/ Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 3.    \n\t\t\tglUniform1i(pShader-&gt;l_uTex3, 3);\n\t\t}\n\t}\n\t\/\/material uniforms\n\tif (pShader-&gt;l_uTex1alphaChannelN &gt;= 0)\n\t\tglUniform1i(pShader-&gt;l_uTex1alphaChannelN, pMt-&gt;uTex1alphaChannelN);\n\tif (pShader-&gt;l_uTex1alphaNegative &gt;= 0)\n\t\tglUniform1i(pShader-&gt;l_uTex1alphaNegative, pMt-&gt;uTex1alphaNegative);\n\tif (pShader-&gt;l_uColor &gt;= 0)\n\t\tglUniform4fv(pShader-&gt;l_uColor, 1, pMt-&gt;uColor.forGL());\n\tif (pShader-&gt;l_uAlphaFactor &gt;= 0)\n\t\tglUniform1f(pShader-&gt;l_uAlphaFactor, pMt-&gt;uAlphaFactor);\n\tif (pShader-&gt;l_uAlphaBlending &gt;= 0)\n\t\tglUniform1i(pShader-&gt;l_uAlphaBlending, pMt-&gt;uAlphaBlending);\n\tif (pShader-&gt;l_uAmbient &gt;= 0)\n\t\tglUniform1f(pShader-&gt;l_uAmbient, pMt-&gt;uAmbient);\n\tif (pShader-&gt;l_uSpecularIntencity &gt;= 0)\n\t\tglUniform1f(pShader-&gt;l_uSpecularIntencity, pMt-&gt;uSpecularIntencity);\n\tif (pShader-&gt;l_uSpecularMinDot &gt;= 0)\n\t\tglUniform1f(pShader-&gt;l_uSpecularMinDot, pMt-&gt;uSpecularMinDot);\n\tif (pShader-&gt;l_uSpecularPowerOf &gt;= 0)\n\t\tglUniform1f(pShader-&gt;l_uSpecularPowerOf, pMt-&gt;uSpecularPowerOf);\n\n\t\/\/adjust render settings\n\tif (pShader-&gt;l_uAlphaBlending &gt;= 0 &amp;&amp; pMt-&gt;uAlphaBlending &gt; 0) {\n\t\tglEnable(GL_BLEND);\n\t\tglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n\t}\n\telse\n\t\tglDisable(GL_BLEND);\n\n\tif (lineWidthIsImportant(pMt-&gt;primitiveType)) {\n\t\tfloat lw = sizeUnitPixelsSize * pMt-&gt;lineWidth;\n\t\tglLineWidth(lw);\n\t}\n\n\t\/\/execute\n\tif (pDJ-&gt;glEBOid == 0) {\n\t\tglDrawArrays(pMt-&gt;primitiveType, 0, pDJ-&gt;pointsN);\n\t}\n\telse { \/\/use EBO\n\t\tglDrawElements(pMt-&gt;primitiveType, pDJ-&gt;pointsN, GL_UNSIGNED_SHORT, 0);\n\t}\n\tglBindVertexArray(0);\n\treturn 1;\n}\nint DrawJob::cleanUp() {\n\tint itemsN = drawJobs.size();\n\t\/\/delete all drawJobs\n\tfor (int i = 0; i &lt; itemsN; i++) {\n\t\tDrawJob* pDJ = drawJobs.at(i);\n\t\tdelete pDJ;\n\t}\n\tdrawJobs.clear();\n\t\/\/delete Buffers\n\titemsN = buffersIds.size();\n\t\/\/delete all buffers\n\tfor (int i = 0; i &lt; itemsN; i++) {\n\t\tunsigned int id = buffersIds.at(i);\n\t\tglDeleteBuffers(1, &amp;id);\n\t}\n\tbuffersIds.clear();\n\n\treturn 1;\n}\nint DrawJob::setDesirableOffsetsForSingleVBO(DrawJob* pDJ, int* pStride, int shaderN, int VBOid) {\n\t\/\/sets desirable offsets and stride according to given shader needs\n\t\/\/assuming that we have 1 single VBO\n\tShader* pSh = Shader::shaders.at(shaderN);\n\tint stride = 0;\n\tpDJ-&gt;aPos.offset = 0; \/\/attribute o_aPos, always 0\n\tstride += sizeof(float) * 3; \/\/aPos size - 3 floats (x,y,z)\n\tif (pSh-&gt;l_aNormal &gt;= 0) { \/\/attribute normal\n\t\tpDJ-&gt;aNormal.offset = stride;\n\t\tstride += sizeof(float) * 3;\n\t}\n\tif (pSh-&gt;l_aTuv &gt;= 0) { \/\/attribute TUV (texture coordinates)\n\t\tpDJ-&gt;aTuv.offset = stride; \/\/attribute TUV (texture coordinates)\n\t\tstride += sizeof(float) * 2;\n\t}\n\tif (pSh-&gt;l_aTuv2 &gt;= 0) { \/\/for normal map\n\t\tpDJ-&gt;aTuv2.offset = stride;\n\t\tstride += sizeof(float) * 2;\n\t}\n\tif (pSh-&gt;l_aTangent &gt;= 0) { \/\/for normal map\n\t\tpDJ-&gt;aTangent.offset = stride;\n\t\tstride += sizeof(float) * 3;\n\t}\n\tif (pSh-&gt;l_aBinormal &gt;= 0) { \/\/for normal map\n\t\tpDJ-&gt;aBinormal.offset = stride;\n\t\tstride += sizeof(float) * 3;\n\t}\n\t*pStride = stride;\n\t\/\/add stride and VBOid to all attributes\n\tAttribRef* pAR = NULL;\n\tpAR = &amp;pDJ-&gt;aPos; pAR-&gt;glVBOid = VBOid; pAR-&gt;stride = stride;\n\tpAR = &amp;pDJ-&gt;aNormal; pAR-&gt;glVBOid = VBOid; pAR-&gt;stride = stride;\n\tpAR = &amp;pDJ-&gt;aTuv; pAR-&gt;glVBOid = VBOid; pAR-&gt;stride = stride;\n\tpAR = &amp;pDJ-&gt;aTuv2; pAR-&gt;glVBOid = VBOid; pAR-&gt;stride = stride;\n\tpAR = &amp;pDJ-&gt;aTangent; pAR-&gt;glVBOid = VBOid; pAR-&gt;stride = stride;\n\tpAR = &amp;pDJ-&gt;aBinormal; pAR-&gt;glVBOid = VBOid; pAR-&gt;stride = stride;\n\n\treturn 1;\n}\nbool DrawJob::lineWidthIsImportant(int primitiveType) {\n\tif (primitiveType == GL_TRIANGLES) return false;\n\tif (primitiveType == GL_TRIANGLE_STRIP) return false;\n\tif (primitiveType == GL_TRIANGLE_FAN) return false;\n\treturn true;\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; back to shaders. We have new variables: <em>uMM <\/em>(model matrix) and <em>uCameraPosition<\/em> instead of  <em>uHalfVector<\/em>. Plus now we have a new <em>#define<\/em> &#8211; <strong>WIRE<\/strong><\/p>\n\n\n\n<p>14. Open<em> Shader.h<\/em>&nbsp;and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [23,25]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &quot;platform.h&quot;\n#include &lt;string&gt;\n#include &lt;vector&gt;\n\nclass Shader\n{\npublic:\n    \/\/Shader program&#039;s individual descriptor:\n    unsigned int GLid = -1; \/\/ GL shader id\n    char shaderType&#91;20] = &quot;&quot;;\n    \/\/common variables, &quot;l_&quot; for &quot;location&quot;\n    \/\/attributes\n    int l_aPos; \/\/attribute position (3D coordinates)\n    int l_aTuv; \/\/attribute TUV (texture coordinates)\n    int l_aTuv2; \/\/attribute TUV (texture coordinates for normal map)\n    int l_aNormal; \/\/attribute normal (3D vector)\n    int l_aTangent; \/\/for normal map\n    int l_aBinormal; \/\/for normal map\n    \/\/uniforms\n    int l_uMVP; \/\/ transform matrix (Model-View-Projection)\n    int l_uMV3x3; \/\/ Model-View matrix for normals\n    int l_uMM; \/\/ Model matrix for HalfVector\n    int l_uVectorToLight; \/\/required for light\n    int l_uCameraPosition; \/\/required for specular light\n    \/\/material&#039;s properties\n    int l_uColor;\n    int l_uTex0; \/\/texture id\n    int l_uTex1mask; \/\/transparency map\n    int l_uTex2nm; \/\/normal map\n    int l_uTex3; \/\/texture id\n    int l_uTex1alphaChannelN; \/\/alpha channel for mask\n    int l_uTex1alphaNegative; \/\/alpha channel negative\n    int l_uTex0translateChannelN; \/\/translate tex0 to tex3 by channelN.\n    int l_uAlphaFactor; \/\/for semi-transparency\n    int l_uAlphaBlending; \/\/for semi-transparency\n    \/\/light:\n    int l_uAmbient; \/\/ambient light\n    \/\/specular light parameters\n    int l_uSpecularIntencity;\n    int l_uSpecularMinDot;\n    int l_uSpecularPowerOf;\n    \/\/end of descriptor\n\n    \/\/static array (vector) of all loaded shaders\n    static std::vector&lt;Shader*&gt; shaders;\n\npublic:\n    static int loadShaders();\n    static int cleanUp();\n    static unsigned int getGLid(int shN) { return shaders.at(shN)-&gt;GLid; };\n    static int shaderErrorCheck(int shaderId, std::string ref);\n    static int programErrorCheck(int programId, std::string ref);\n    static int fillLocations(Shader* pSh);\n\n    static int buildShaderObjectFromFiles(std::string filePathVertexS, std::string filePathFragmentS);\n    static int linkShaderProgramFromFiles(const char* filePathVertexS, const char* filePathFragmentS);\n\tstatic int compileShaderFromFile(const char* filePath, GLenum shaderType);\n\n    static int buildShaderObjectWithDefines(std::string shaderType, std::string definesString, char* sourceVertex, char* sourceFragment);\n    static int linkShaderProgramWithDefines(std::string definesString, char* sourceVertex, char* sourceFragment);\n    static int compileShaderWithDefines(std::string definesString, char* shaderSource, GLenum shaderType);\n\n    static int loadShadersGroup(std::string shaderType, std::string optionsString, char* sourceVertex, char* sourceFragment);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>15. Open<em> Shader.cpp<\/em>&nbsp;and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [17,53,55]; title: ; notranslate\" title=\"\">\n#include &quot;Shader.h&quot;\n#include &quot;platform.h&quot;\n#include &quot;utils.h&quot;\n#include &quot;FileLoader.h&quot;\n\nextern std::string filesRoot;\n\n\/\/static array (vector) of all loaded shaders\nstd::vector&lt;Shader*&gt; Shader::shaders;\n\nint Shader::loadShaders() {\n    FileLoader* pFLvertex = new FileLoader(&quot;\/dt\/shaders\/phong_v.txt&quot;);\n    FileLoader* pFLfragment = new FileLoader(&quot;\/dt\/shaders\/phong_f.txt&quot;);\n    loadShadersGroup(&quot;flat&quot;, &quot;FLAT; COLOR | TEXTURE; NONE | OVERMASK&quot;, pFLvertex-&gt;pData, pFLfragment-&gt;pData);\n    loadShadersGroup(&quot;phong&quot;, &quot;PHONG; COLOR | TEXTURE; NONE | OVERMASK&quot;, pFLvertex-&gt;pData, pFLfragment-&gt;pData);\n    loadShadersGroup(&quot;mirror&quot;, &quot;PHONG;MIRROR; NONE | OVERMASK&quot;, pFLvertex-&gt;pData, pFLfragment-&gt;pData);\n    loadShadersGroup(&quot;wire&quot;, &quot;WIRE;PHONG; COLOR | TEXTURE&quot;, pFLvertex-&gt;pData, pFLfragment-&gt;pData);\n    delete pFLvertex;\n    delete pFLfragment;\n    \/\/Normal Maps\n    pFLvertex = new FileLoader(&quot;\/dt\/shaders\/nm_v.txt&quot;);\n    pFLfragment = new FileLoader(&quot;\/dt\/shaders\/nm_f.txt&quot;);\n    loadShadersGroup(&quot;phong&quot;, &quot;COLOR | TEXTURE; NONE | OVERMASK&quot;, pFLvertex-&gt;pData, pFLfragment-&gt;pData);\n    loadShadersGroup(&quot;mirror&quot;, &quot;MIRROR; NONE | OVERMASK&quot;, pFLvertex-&gt;pData, pFLfragment-&gt;pData);\n    delete pFLvertex;\n    delete pFLfragment;\n    return 1;\n}\n\nint Shader::buildShaderObjectFromFiles(std::string filePathVertexS, std::string filePathFragmentS) {\n    \/\/create shader object\n    Shader* pSh = new Shader();\n    shaders.push_back(pSh);\n    pSh-&gt;GLid = linkShaderProgramFromFiles((filesRoot + filePathVertexS).c_str(), (filesRoot + filePathFragmentS).c_str());\n    \/\/common variables. If not presented, = -1;\n    fillLocations(pSh);\n\n    return (shaders.size() - 1);\n}\n\nint Shader::fillLocations(Shader* pSh) {\n    \/\/common variables. If not presented, = -1;\n    \/\/attributes\n    pSh-&gt;l_aPos = glGetAttribLocation(pSh-&gt;GLid, &quot;aPos&quot;); \/\/attribute position (3D coordinates)\n    pSh-&gt;l_aNormal = glGetAttribLocation(pSh-&gt;GLid, &quot;aNormal&quot;); \/\/attribute normal (3D vector)\n    pSh-&gt;l_aTangent = glGetAttribLocation(pSh-&gt;GLid, &quot;aTangent&quot;); \/\/for normal map\n    pSh-&gt;l_aBinormal = glGetAttribLocation(pSh-&gt;GLid, &quot;aBinormal&quot;); \/\/for normal map\n    pSh-&gt;l_aTuv = glGetAttribLocation(pSh-&gt;GLid, &quot;aTuv&quot;); \/\/attribute TUV (texture coordinates)\n    pSh-&gt;l_aTuv2 = glGetAttribLocation(pSh-&gt;GLid, &quot;aTuv2&quot;); \/\/attribute TUV (texture coordinates)\n    \/\/uniforms\n    pSh-&gt;l_uMVP = glGetUniformLocation(pSh-&gt;GLid, &quot;uMVP&quot;); \/\/ transform matrix (Model-View-Projection)\n    pSh-&gt;l_uMV3x3 = glGetUniformLocation(pSh-&gt;GLid, &quot;uMV3x3&quot;); \/\/ Model-View matrix for normals\n    pSh-&gt;l_uMM = glGetUniformLocation(pSh-&gt;GLid, &quot;uMM&quot;); \/\/ Model matrix for HalfVector\n    pSh-&gt;l_uVectorToLight = glGetUniformLocation(pSh-&gt;GLid, &quot;uVectorToLight&quot;); \/\/ \n    pSh-&gt;l_uCameraPosition = glGetUniformLocation(pSh-&gt;GLid, &quot;uCameraPosition&quot;); \/\/ required for specular light\n    \/\/material&#039;s properties\n    pSh-&gt;l_uColor = glGetUniformLocation(pSh-&gt;GLid, &quot;uColor&quot;);\n    pSh-&gt;l_uTex0 = glGetUniformLocation(pSh-&gt;GLid, &quot;uTex0&quot;); \/\/texture id\n    pSh-&gt;l_uTex1mask = glGetUniformLocation(pSh-&gt;GLid, &quot;uTex1mask&quot;); \/\/texture id\n    pSh-&gt;l_uTex2nm = glGetUniformLocation(pSh-&gt;GLid, &quot;uTex2nm&quot;); \/\/texture id\n    pSh-&gt;l_uTex3 = glGetUniformLocation(pSh-&gt;GLid, &quot;uTex3&quot;); \/\/texture id\n    pSh-&gt;l_uTex1alphaChannelN = glGetUniformLocation(pSh-&gt;GLid, &quot;uTex1alphaChannelN&quot;);\n    pSh-&gt;l_uTex1alphaNegative = glGetUniformLocation(pSh-&gt;GLid, &quot;uTex1alphaNegative&quot;);\n    pSh-&gt;l_uTex0translateChannelN = glGetUniformLocation(pSh-&gt;GLid, &quot;uTex0translateChannelN&quot;);\n    pSh-&gt;l_uAlphaFactor = glGetUniformLocation(pSh-&gt;GLid, &quot;uAlphaFactor&quot;); \/\/ for semi-transparency\n    pSh-&gt;l_uAlphaBlending = glGetUniformLocation(pSh-&gt;GLid, &quot;uAlphaBlending&quot;); \/\/ for semi-transparency\n    pSh-&gt;l_uAmbient = glGetUniformLocation(pSh-&gt;GLid, &quot;uAmbient&quot;); \/\/ ambient light\n    pSh-&gt;l_uSpecularIntencity = glGetUniformLocation(pSh-&gt;GLid, &quot;uSpecularIntencity&quot;); \/\/ \n    pSh-&gt;l_uSpecularMinDot = glGetUniformLocation(pSh-&gt;GLid, &quot;uSpecularMinDot&quot;); \/\/ \n    pSh-&gt;l_uSpecularPowerOf = glGetUniformLocation(pSh-&gt;GLid, &quot;uSpecularPowerOf&quot;); \/\/ \n    return 1;\n}\nint Shader::cleanUp() {\n    int shadersN = shaders.size();\n    if (shadersN &lt; 1)\n        return -1;\n    glUseProgram(0);\n    for (int i = 0; i &lt; shadersN; i++) {\n        Shader* pSh = shaders.at(i);\n        glDeleteProgram(pSh-&gt;GLid);\n        delete pSh;\n    }\n    shaders.clear();\n    return 1;\n}\n\nGLchar infoLog&#91;1024];\nint logLength;\nint Shader::shaderErrorCheck(int shaderId, std::string ref) {\n    \/\/use after glCompileShader()\n    if (checkGLerrors(ref) &gt; 0)\n        return -1;\n    glGetShaderInfoLog(shaderId, 1024, &amp;logLength, infoLog);\n    if (logLength == 0)\n        return 0;\n    mylog(&quot;%s shader infoLog:\\n%s\\n&quot;, ref.c_str(), infoLog);\n    return -1;\n}\nint Shader::programErrorCheck(int programId, std::string ref) {\n    \/\/use after glLinkProgram()\n    if (checkGLerrors(ref) &gt; 0)\n        return -1;\n    glGetProgramInfoLog(programId, 1024, &amp;logLength, infoLog);\n    if (logLength == 0)\n        return 0;\n    mylog(&quot;%s program infoLog:\\n%s\\n&quot;, ref.c_str(), infoLog);\n    return -1;\n}\n\nint Shader::compileShaderFromFile(const char* filePath, GLenum shaderType) {\n    int shaderId = glCreateShader(shaderType);\n    FILE* pFile;\n    myFopen_s(&amp;pFile, filePath, &quot;rt&quot;);\n    if (pFile != NULL)\n    {\n        \/\/ obtain file size:\n        fseek(pFile, 0, SEEK_END);\n        int fSize = ftell(pFile);\n        rewind(pFile);\n        \/\/ size obtained, create buffer\n        char* shaderSource = new char&#91;fSize + 1];\n        fSize = fread(shaderSource, 1, fSize, pFile);\n        shaderSource&#91;fSize] = 0;\n        fclose(pFile);\n        \/\/ source code loaded, compile\n        glShaderSource(shaderId, 1, (const GLchar**)&amp;shaderSource, NULL);\n        \/\/myglErrorCheck(&quot;glShaderSource&quot;);\n        glCompileShader(shaderId);\n        if (shaderErrorCheck(shaderId, &quot;glCompileShader&quot;) &lt; 0)\n            return -1;\n        delete&#91;] shaderSource;\n    }\n    else {\n        mylog(&quot;ERROR loading %s\\n&quot;, filePath);\n        return -1;\n    }\n    return shaderId;\n}\nint Shader::linkShaderProgramFromFiles(const char* filePathVertexS, const char* filePathFragmentS) {\n    int vertexShaderId = compileShaderFromFile(filePathVertexS, GL_VERTEX_SHADER);\n    int fragmentShaderId = compileShaderFromFile(filePathFragmentS, GL_FRAGMENT_SHADER);\n    int programId = glCreateProgram();\n    glAttachShader(programId, vertexShaderId);\n    glAttachShader(programId, fragmentShaderId);\n    glLinkProgram(programId);\n    if (programErrorCheck(programId, &quot;glLinkProgram&quot;) &lt; 0)\n        return -1;\n    \/\/don&#039;t need shaders any longer - detach and delete them\n    glDetachShader(programId, vertexShaderId);\n    glDetachShader(programId, fragmentShaderId);\n    glDeleteShader(vertexShaderId);\n    glDeleteShader(fragmentShaderId);\n    return programId;\n}\n\nint Shader::buildShaderObjectWithDefines(std::string shaderType, std::string definesString, char* sourceVertex, char* sourceFragment) {\n    \/\/create shader object\n    Shader* pSh = new Shader();\n    shaders.push_back(pSh);\n    myStrcpy_s(pSh-&gt;shaderType, 20, shaderType.c_str());\n\n    pSh-&gt;GLid = linkShaderProgramWithDefines(definesString, sourceVertex, sourceFragment);\n    \/\/common variables. If not presented, = -1;\n    fillLocations(pSh);\n\n    return (shaders.size() - 1);\n}\n\nint Shader::linkShaderProgramWithDefines(std::string definesString00, char* sourceVertex, char* sourceFragment) {\n    \/\/build extended definesString\n    bool bUSE_NORMALS = false;\n    bool bUSE_TEX0 = false;\n    bool bUSE_TUV0 = false;\n    if (definesString00.find(&quot; PHONG\\n&quot;) != std::string::npos)\n        bUSE_NORMALS = true;\n    if (definesString00.find(&quot; TEXTURE\\n&quot;) != std::string::npos) {\n        bUSE_TEX0 = true;\n        bUSE_TUV0 = true;\n    }\n    if (definesString00.find(&quot; MIRROR\\n&quot;) != std::string::npos) {\n        bUSE_NORMALS = true;\n        bUSE_TEX0 = true;\n    }\n    if (definesString00.find(&quot; OVERMASK\\n&quot;) != std::string::npos) {\n        bUSE_TUV0 = true;\n    }\n    std::string definesString;\n    definesString.assign(&quot;#version 320 es\\n&quot;);\n    definesString.append(definesString00);\n    if (bUSE_NORMALS)\n        definesString.append(&quot;#define USE_NORMALS\\n&quot;);\n    if (bUSE_TEX0)\n        definesString.append(&quot;#define USE_TEX0\\n&quot;);\n    if (bUSE_TUV0)\n        definesString.append(&quot;#define USE_TUV0\\n&quot;);\n\n    int vertexShaderId = compileShaderWithDefines(definesString, sourceVertex, GL_VERTEX_SHADER);\n    int fragmentShaderId = compileShaderWithDefines(definesString, sourceFragment, GL_FRAGMENT_SHADER);\n\n    int programId = glCreateProgram();\n    glAttachShader(programId, vertexShaderId);\n    glAttachShader(programId, fragmentShaderId);\n    glLinkProgram(programId);\n    if (programErrorCheck(programId, &quot;glLinkProgram&quot;) &lt; 0)\n        return -1;\n    \/\/don&#039;t need shaders any longer - detach and delete them\n    glDetachShader(programId, vertexShaderId);\n    glDetachShader(programId, fragmentShaderId);\n    glDeleteShader(vertexShaderId);\n    glDeleteShader(fragmentShaderId);\n    \/\/mylog(&quot;linking program\\n%s\\n&quot;, definesString.c_str());\n    return programId;\n}\nint Shader::compileShaderWithDefines(std::string definesString, char* shaderSource, GLenum shaderType) {\n    int shaderId = glCreateShader(shaderType);\n    if (definesString.empty())\n        glShaderSource(shaderId, 1, (const GLchar**)&amp;shaderSource, NULL);\n    else { \/\/2 strings\n        const char* sourceStrings&#91;2];\n        sourceStrings&#91;0] = definesString.c_str();\n        sourceStrings&#91;1] = shaderSource;\n        \/\/ source code loaded, compile\n        glShaderSource(shaderId, 2, (const GLchar**)sourceStrings, NULL);\n    }\n    \/\/myglErrorCheck(&quot;glShaderSource&quot;);\n    glCompileShader(shaderId);\n    if (shaderErrorCheck(shaderId, &quot;glCompileShader&quot;) &lt; 0) {\n        mylog(&quot;ERROR in compileShader,\\n%s\\n%s\\n&quot;, definesString.c_str(), shaderSource);\n        return -1;\n    }\n    return shaderId;\n}\n\nint Shader::loadShadersGroup(std::string shaderType, std::string optionsString, char* sourceVertex, char* sourceFragment) {\n    struct Terms {\n        std::vector&lt;std::string&gt; terms;\n        int totalN = 0;\n        int currentN = 0;\n    };\n    std::vector&lt;Terms*&gt; terms;\n    std::vector&lt;std::string&gt; termGroups = splitString(optionsString, &quot;;&quot;);\n    int groupsN = termGroups.size();\n    for (int groupN = 0; groupN &lt; groupsN; groupN++) {\n        Terms* pTerms = new Terms();\n        terms.push_back(pTerms);\n        pTerms-&gt;terms = splitString(termGroups.at(groupN), &quot;|&quot;);\n        pTerms-&gt;totalN = pTerms-&gt;terms.size();\n    }\n    while (1) {\n        std::string definesString = &quot;&quot;;\n        for (int groupN = 0; groupN &lt; groupsN; groupN++) {\n            Terms* pTerms = terms.at(groupN);\n            std::string term = pTerms-&gt;terms.at(pTerms-&gt;currentN);\n            if (term.compare(&quot;NONE&quot;) != 0) {\n                definesString.append(&quot;#define &quot;);\n                definesString.append(term);\n                definesString.append(&quot;\\n&quot;);\n            }\n        }\n        int shaderObjN = buildShaderObjectWithDefines(shaderType, definesString, sourceVertex, sourceFragment);\n        \/\/go to next terms combo\n        bool noMoreOptions = false;\n        for (int groupN = groupsN - 1; groupN &gt;= 0; groupN--) {\n            Terms* pTerms = terms.at(groupN);\n            if (pTerms-&gt;currentN &lt; pTerms-&gt;totalN - 1) {\n                pTerms-&gt;currentN++;\n                break;\n            }\n            else { \/\/ the level exhausted\n                pTerms-&gt;currentN = 0;\n                \/\/proceed to upper level\n                if (groupN == 0) {\n                    noMoreOptions = true;\n                    break;\n                }\n            }\n        }\n        if (noMoreOptions)\n            break;\n    }\n    return 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>And finally &#8211;  <em>TheGame<\/em>. We don&#8217;t have to calculate <em>uHalfVector <\/em>there any more, but have to calculate <em>sizeUnitPixelsSize<\/em> (for setting line width for rendering). Plus &#8211; new parameters in <em>pDJ-&gt;execute(..)<\/em> <\/p>\n\n\n\n<p>16. Open<em>&nbsp;TheGame.cpp<\/em>&nbsp;and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [79,80,81,82,83,84,85,89]; 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}\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    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        \/\/subj&#039;s distance from camera\n        float cameraSpacePos&#91;4];\n        mat4x4_mul_vec4plus(cameraSpacePos, mainCamera.lookAtMatrix, pGS-&gt;ownCoords.pos, 1);\n        float zDistance = abs(cameraSpacePos&#91;2]);\n        float cotangentA = 1.0f \/ tanf(degrees2radians * mainCamera.viewRangeDg \/ 2.0);\n        float halfScreenVertSizeInUnits = zDistance \/ cotangentA;\n        float sizeUnitPixelsSize = screenSize&#91;1] \/ 2.0 \/ halfScreenVertSizeInUnits;\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, (float*)pGS-&gt;ownModelMatrix, dirToMainLight, mainCamera.ownCoords.pos, sizeUnitPixelsSize, 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}\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<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>17. Build and run. Result:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c35\/02.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Now &#8211; with golden sealing ribbon.<\/p>\n\n\n\n<iframe loading=\"lazy\" width=\"100%\" height=\"400\" src=\"https:\/\/www.youtube.com\/embed\/c5y9s-SzT4Y?controls=0&amp;autoplay=1&amp;loop=1&amp;playlist=c5y9s-SzT4Y\" title=\"Wire shader\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen=\"\"><\/iframe>\n\n\n\n<p><\/p>\n\n\n\n<p>BTW, without per-vertex <em>HalfVector <\/em>calculation, we wouldn&#8217;t have such a nice sliding sparkle.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Verified on <strong>Android <\/strong>too, works fine.<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p class=\"mb-2\">Here I want to add a golden sealing ribbon on the pack. It will be a line, a sequence of points instead of regular triangulars mesh. It will require: A set of new tags in ModelLoader New lineWidth property in Material class In DrawJob we&#8217;ll need to adjust lineWidth depending on distance Additional shader&#8217;s functionality [&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-1149","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\/1149","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=1149"}],"version-history":[{"count":51,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/1149\/revisions"}],"predecessor-version":[{"id":1287,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/1149\/revisions\/1287"}],"wp:attachment":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media?parent=1149"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=1149"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=1149"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}