{"id":668,"date":"2021-12-08T00:02:52","date_gmt":"2021-12-08T00:02:52","guid":{"rendered":"https:\/\/writingagame.com\/?p=668"},"modified":"2021-12-11T00:21:12","modified_gmt":"2021-12-11T00:21:12","slug":"chapter-21-textured-surface","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2021\/12\/08\/chapter-21-textured-surface\/","title":{"rendered":"Chapter 21. Textured surface"},"content":{"rendered":"\n<p>Now let&#8217;s apply a texture to a complicated multi-vertex surface, to the right (blue) side for example. Hope, it&#8217;s clear enough how to write a textured Phong shader, but we&#8217;ll do it later, in some other chapter. For now the flat one (which we have already) will be good enough. We will need:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>to pass desirable image coordinates to the <em>Modeler<\/em><\/li><li>to calculate <em>tUV <\/em>coordinates for each involved vertex<\/li><\/ul>\n\n\n\n<p>1. Start VS, Open&nbsp;<em>C:\\CPP\\<em>a997modeler<\/em>\\p_windows\\p_windows.sln<\/em>.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Ok to delete old projects: <em>a998engine <\/em>and <em>a999hello<\/em>.<\/li><\/ul>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>We&#8217;ll use <em>C:\\CPP\\a997modeler\\dt\\<strong>sample_img.png<\/strong><\/em> picture from our previous exercises.<\/p>\n\n\n\n<p>Let&#8217;s say, we want to show not entire image, but just a 256&#215;128 pixels fragment from 11&#215;12 position (2 left knights):<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c21\/01.jpg\"><\/p>\n\n\n\n<p> <\/p>\n\n\n\n<p>Also, let&#8217;s say, we want to flip the image upside down.<\/p>\n\n\n\n<p>First, we need to recalculate these numbers into &#8220;tUV&#8221; 0 to 1 float OpenGL format. Then we&#8217;ll need to rearrange coords to reflect the flip. We&#8217;ll do it in a separate class.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>2. Under <em>modeler <\/em>add new item &#8211; header file <strong>TexCoords.h<\/strong><\/p>\n\n\n\n<p>Location &#8211; <em>C:\\CPP\\engine\\modeler<\/em><\/p>\n\n\n\n<p>Code:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#pragma once\n#include &lt;string&gt;\n\nclass TexCoords\n{\npublic:\n\tfloat tuvTopLeft&#91;2] = { 0.0f,0.0f };\n\tfloat tuvBottomRight&#91;2] = { 1.0f,1.0f };\npublic:\n\tvoid set(int texN, float x, float y, float w, float h, std::string flip) { set(this, texN, x, y, w, h, flip); };\n\tstatic void set(TexCoords* pTC, int texN, float x, float y, float w, float h, std::string flipStr);\n\tstatic void set_GL(TexCoords* pTC, float x, float y, float w, float h, std::string flipStr);\n\tstatic void flip(TexCoords* pTC, std::string flipStr); \/\/possible flips: &quot;90&quot; (CCW), &quot;-90&quot; (CW), &quot;180&quot;, &quot;h&quot; (horizontal), &quot;v&quot; (vertical) \n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>3. Under <em>modeler <\/em>add new C++ file <strong>TexCoords.cpp<\/strong><\/p>\n\n\n\n<p>Location &#8211; <em>C:\\CPP\\engine\\modeler<\/em><\/p>\n\n\n\n<p>Code:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#include &quot;TexCoords.h&quot;\n#include &quot;Texture.h&quot;\n\nvoid TexCoords::set(TexCoords* pTC, int texN, float x, float y, float w, float h, std::string flipStr) {\n\tif (texN &lt; 0)\n\t\treturn;\n\tTexture* pTex = Texture::textures.at(texN);\n\tx = x \/ pTex-&gt;size&#91;0];\n\ty = y \/ pTex-&gt;size&#91;1];\n\tw = w \/ pTex-&gt;size&#91;0];\n\th = h \/ pTex-&gt;size&#91;1];\n\tset_GL(pTC, x, y, w, h, flipStr);\n}\nvoid TexCoords::set_GL(TexCoords* pTC, float x, float y, float w, float h, std::string flipStr) {\n\t\/\/assuming that x,y,w,h - in GL 0-to-1 format\n\tpTC-&gt;tuvTopLeft&#91;0] = x;\n\tpTC-&gt;tuvTopLeft&#91;1] = y;\n\tpTC-&gt;tuvBottomRight&#91;0] = x + w;\n\tpTC-&gt;tuvBottomRight&#91;1] = y + h;\n\tflip(pTC, flipStr);\n}\nvoid TexCoords::flip(TexCoords* pTC, std::string flipStr) {\n\t\/\/possible flips: &quot;90&quot; (CCW), &quot;-90&quot; (CW), &quot;180&quot;, &quot;h&quot; (horizontal), &quot;v&quot; (vertical) \n\tif (flipStr.compare(&quot;&quot;) == 0)\n\t\treturn;\n\tTexCoords outTC;\n\tif (flipStr.find(&quot;90&quot;) == 0) { \/\/CCW\n\t\toutTC.tuvTopLeft&#91;0] = pTC-&gt;tuvBottomRight&#91;0];\n\t\toutTC.tuvTopLeft&#91;1] = pTC-&gt;tuvTopLeft&#91;1];\n\t\toutTC.tuvBottomRight&#91;0] = pTC-&gt;tuvTopLeft&#91;0];\n\t\toutTC.tuvBottomRight&#91;1] = pTC-&gt;tuvBottomRight&#91;1];\n\t}\n\telse if (flipStr.find(&quot;-90&quot;) == 0) { \/\/CW\n\t\toutTC.tuvTopLeft&#91;0] = pTC-&gt;tuvTopLeft&#91;0];\n\t\toutTC.tuvTopLeft&#91;1] = pTC-&gt;tuvBottomRight&#91;1];\n\t\toutTC.tuvBottomRight&#91;0] = pTC-&gt;tuvBottomRight&#91;0];\n\t\toutTC.tuvBottomRight&#91;1] = pTC-&gt;tuvTopLeft&#91;1];\n\t}\n\telse if (flipStr.find(&quot;180&quot;) == 0) {\n\t\toutTC.tuvTopLeft&#91;0] = pTC-&gt;tuvBottomRight&#91;0];\n\t\toutTC.tuvTopLeft&#91;1] = pTC-&gt;tuvBottomRight&#91;1];\n\t\toutTC.tuvBottomRight&#91;0] = pTC-&gt;tuvTopLeft&#91;0];\n\t\toutTC.tuvBottomRight&#91;1] = pTC-&gt;tuvTopLeft&#91;1];\n\t}\n\telse if (flipStr.find(&quot;h&quot;) == 0) { \/\/horizontal\n\t\toutTC.tuvTopLeft&#91;0] = pTC-&gt;tuvBottomRight&#91;0];\n\t\toutTC.tuvTopLeft&#91;1] = pTC-&gt;tuvTopLeft&#91;1];\n\t\toutTC.tuvBottomRight&#91;0] = pTC-&gt;tuvTopLeft&#91;0];\n\t\toutTC.tuvBottomRight&#91;1] = pTC-&gt;tuvBottomRight&#91;1];\n\t}\n\telse if (flipStr.find(&quot;v&quot;) == 0) { \/\/vertical\n\t\toutTC.tuvTopLeft&#91;0] = pTC-&gt;tuvTopLeft&#91;0];\n\t\toutTC.tuvTopLeft&#91;1] = pTC-&gt;tuvBottomRight&#91;1];\n\t\toutTC.tuvBottomRight&#91;0] = pTC-&gt;tuvBottomRight&#91;0];\n\t\toutTC.tuvBottomRight&#91;1] = pTC-&gt;tuvTopLeft&#91;1];\n\t}\n\tmemcpy(pTC, &amp;outTC, sizeof(TexCoords));\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>In <em>TheGame.cpp<\/em> instead of changing color for the right side, we&#8217;ll change entire Material. Plus now we need to pass <em>TexCoords <\/em>to the <em>pMB-&gt;buildBoxFace<\/em> function.<\/p>\n\n\n\n<p>4. Open <em>TheGame.cpp<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [9,56,57,58,59,60,61,62]; 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\nextern std::string filesRoot;\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    \/\/=== create box ========================\n    GameSubj* pGS = new GameSubj();\n    gameSubjs.push_back(pGS);\n\n    pGS-&gt;name.assign(&quot;box1&quot;);\n    pGS-&gt;ownCoords.setPosition(0, 0, 0);\n    pGS-&gt;ownCoords.setDegrees(0, 0, 0);\n    pGS-&gt;ownSpeed.setDegrees(0,1,0);\n\n    ModelBuilder* pMB = new ModelBuilder();\n    pMB-&gt;useSubjN(gameSubjs.size() - 1);\n\n    \/\/define VirtualShape\n    VirtualShape vs;\n    vs.setShapeType(&quot;box-tank&quot;);\n    vs.whl&#91;0] = 60;\n    vs.whl&#91;1] = 160;\n    vs.whl&#91;2] = 390;\n    vs.setExt(20);\n    vs.extD = 0;\n    vs.extF = 0; \/\/to make front face &quot;flat&quot;\n    vs.sectionsR = 2;\n\n    Material mt;\n    \/\/define material - flat red\n    mt.shaderN = Shader::spN_phong_ucolor;\n    mt.primitiveType = GL_TRIANGLES;\n    mt.uColor.setRGBA(255, 0, 0,255); \/\/red\n    pMB-&gt;useMaterial(&amp;mt);\n\n    pMB-&gt;buildBoxFace(pMB,&quot;front v&quot;, &amp;vs);\n    pMB-&gt;buildBoxFace(pMB, &quot;back v&quot;, &amp;vs);\n    pMB-&gt;buildBoxFace(pMB, &quot;top&quot;, &amp;vs);\n    pMB-&gt;buildBoxFace(pMB, &quot;bottom&quot;, &amp;vs);\n    pMB-&gt;buildBoxFace(pMB, &quot;left all&quot;, &amp;vs);\n\n    mt.uColor.clear(); \/\/ set to zero;\n    mt.uTex0 = Texture::loadTexture(filesRoot + &quot;\/dt\/sample_img.png&quot;);\n    mt.shaderN = Shader::spN_flat_tex;\n    pMB-&gt;useMaterial(&amp;mt);\n    TexCoords tc;\n    tc.set(mt.uTex0, 11, 12, 256, 128, &quot;180&quot;);\n    pMB-&gt;buildBoxFace(pMB, &quot;right all&quot;, &amp;vs, &amp;tc);\n\n    pMB-&gt;buildDrawJobs(gameSubjs);\n\n    delete pMB;\n\n    \/\/===== set up camera\n    v3set(mainCamera.ownCoords.pos, 0, 200, 1000); \/\/set position\n    float cameraDir&#91;3];\n    v3set(cameraDir, 0, -200, -1000); \/\/set direction vector\n    float cameraYawDg = v3yawDg(cameraDir);\n    float cameraPitchDg = v3pitchDg(cameraDir);\n    \/\/mylog(&quot;cameraYaw=%f, cameraPitch=%f\\n&quot;, cameraYawDg, cameraPitchDg);\n\n    mainCamera.ownCoords.setDegrees(cameraPitchDg, cameraYawDg, 0);\n    float cameraUp&#91;4] = { 0,1,0,0 }; \/\/y - up\n    mat4x4_mul_vec4plus(cameraUp, *mainCamera.ownCoords.getRotationMatrix(), cameraUp, 0);\n\n    mat4x4_look_at(mainCamera.lookAtMatrix, mainCamera.ownCoords.pos, pGS-&gt;ownCoords.pos, cameraUp);\n\n    \/\/===== set up light\n    v3set(dirToMainLight, -1, 1, 1);\n    vec3_norm(dirToMainLight, dirToMainLight);\n\n    return 1;\n}\nint TheGame::drawFrame() {\n    myPollEvents();\n\n    \/\/glClearColor(0.0, 0.0, 0.5, 1.0);\n    glClear(GL_COLOR_BUFFER_BIT);\n\n    \/\/calculate halfVector\n    float dirToCamera&#91;4] = { 0,0,-1,0 }; \/\/-z\n    mat4x4_mul_vec4plus(dirToCamera, *mainCamera.ownCoords.getRotationMatrix(), dirToCamera, 0);\n\n    float uHalfVector&#91;4] = { 0,0,0,0 };\n    for (int i = 0; i &lt; 3; i++)\n        uHalfVector&#91;i] = (dirToCamera&#91;i] + dirToMainLight&#91;i]) \/ 2;\n    vec3_norm(uHalfVector, uHalfVector);\n\n    mat4x4 mProjection, mViewProjection, mMVP, mMV4x4;\n    \/\/mat4x4_ortho(mProjection, -(float)screenSize&#91;0] \/ 2, (float)screenSize&#91;0] \/ 2, -(float)screenSize&#91;1] \/ 2, (float)screenSize&#91;1] \/ 2, 100.f, 500.f);\n    mat4x4_perspective(mProjection, 3.14f \/ 6.0f, (float)screenSize&#91;0] \/ screenSize&#91;1], 700.f, 1300.f);\n    mat4x4_mul(mViewProjection, mProjection, mainCamera.lookAtMatrix);\n    \/\/mViewProjection&#91;1]&#91;3] = 0; \/\/keystone effect\n\n    \/\/scan subjects\n    int subjsN = gameSubjs.size();\n    for (int subjN = 0; subjN &lt; subjsN; subjN++) {\n        GameSubj* pGS = gameSubjs.at(subjN);\n        \/\/behavior - apply rotation speed\n        pGS-&gt;moveSubj();\n        \/\/prepare subject for rendering\n        pGS-&gt;buildModelMatrix(pGS);\n        \/\/build MVP matrix for given subject\n        mat4x4_mul(mMVP, mViewProjection, pGS-&gt;ownModelMatrix);\n        \/\/build Model-View (rotation) matrix for normals\n        mat4x4_mul(mMV4x4, mainCamera.lookAtMatrix, (vec4*)pGS-&gt;ownCoords.getRotationMatrix());\n        \/\/convert to 3x3 matrix\n        float mMV3x3&#91;3]&#91;3];\n        for (int y = 0; y &lt; 3; y++)\n            for (int x = 0; x &lt; 3; x++)\n                mMV3x3&#91;y]&#91;x] = mMV4x4&#91;y]&#91;x];\n        \/\/render subject\n        for (int i = 0; i &lt; pGS-&gt;djTotalN; i++) {\n            DrawJob* pDJ = DrawJob::drawJobs.at(pGS-&gt;djStartN + i);\n            pDJ-&gt;execute((float*)mMVP, *mMV3x3, dirToMainLight, uHalfVector, NULL);\n        }\n    }\n    mySwapBuffers();\n    return 1;\n}\n\nint TheGame::cleanUp() {\n    int itemsN = gameSubjs.size();\n    \/\/delete all UISubjs\n    for (int i = 0; i &lt; itemsN; i++) {\n        GameSubj* pGS = gameSubjs.at(i);\n        delete pGS;\n    }\n    gameSubjs.clear();\n    \/\/clear all other classes\n    Texture::cleanUp();\n    Shader::cleanUp();\n    DrawJob::cleanUp();\n    return 1;\n}\nint TheGame::onScreenResize(int width, int height) {\n    if (screenSize&#91;0] == width &amp;&amp; screenSize&#91;1] == height)\n        return 0;\n    screenSize&#91;0] = width;\n    screenSize&#91;1] = height;\n    screenRatio = (float)width \/ (float)height;\n    glViewport(0, 0, width, height);\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}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Now in <em>ModelBuilder::buildBoxFace(\u2026)<\/em> function we have a new parameters <em>TexCoords*<\/em>. Plus new function <em>ModelBuilder::groupApplyTexture2(\u2026)<\/em>.<\/p>\n\n\n\n<p>5. Open <em>ModelBuilder.h<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [3,9,10,15]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &quot;ModelBuilder1base.h&quot;\n#include &quot;TexCoords.h&quot;\n\nclass ModelBuilder : public ModelBuilder1base\n{\npublic:\n\tvirtual ~ModelBuilder();\n\tstatic int buildFace(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS, TexCoords* pTC = NULL, TexCoords* pTC2nm = NULL);\n\tstatic int buildBoxFace(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS, TexCoords* pTC = NULL, TexCoords* pTC2nm = NULL);\n\tstatic int buildBoxFacePlain(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS);\n\tstatic int buildBoxFaceTank(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS);\n\tstatic int cylinderWrap(ModelBuilder* pMB, VirtualShape* pVS, float angleFrom, float angleTo);\n\tstatic int capWrap(ModelBuilder* pMB, VirtualShape* pVS, float angleFrom, float angleTo);\n\tstatic int groupApplyTexture2(ModelBuilder* pMB, std::string applyTo, TexCoords* pTC, bool isNormakMap);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>6. Open <em>ModelBuilder.cpp<\/em> and replace code by: <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [12,17,122,123,442]; title: ; notranslate\" title=\"\">\n#include &quot;ModelBuilder.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\nModelBuilder::~ModelBuilder() {\n}\n\nint ModelBuilder::buildFace(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS, TexCoords* pTC, TexCoords* pTC2nm) {\n\tif (strstr(pVS-&gt;shapeType, &quot;box&quot;) == pVS-&gt;shapeType)\n\t\treturn buildBoxFace(pMB, applyTo, pVS, pTC, pTC2nm);\n\treturn -1;\n}\nint ModelBuilder::buildBoxFace(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS, TexCoords* pTC, TexCoords* pTC2nm) {\n\t\/\/this code is for simple box\n\tVirtualShape vs; \/\/face VS, \n\tmat4x4 transformMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };\n\tvs.sectionsR = pVS-&gt;sectionsR;\n\t\/\/rotate desirable side to face us. \n\tif (applyTo.find(&quot;front&quot;) == 0) {\n\t\t\/\/Side &lt;front&gt; is facing us as is.\n\t\tvs.whl&#91;0] = pVS-&gt;whl&#91;0];\n\t\tvs.whl&#91;1] = pVS-&gt;whl&#91;1];\n\t\tvs.sections&#91;0] = pVS-&gt;sections&#91;0];\n\t\tvs.sections&#91;1] = pVS-&gt;sections&#91;1];\n\t\t\/\/extensions\n\t\tvs.extF = pVS-&gt;extF;\n\t\tvs.extL = pVS-&gt;extL;\n\t\tvs.extR = pVS-&gt;extR;\n\t\tvs.extU = pVS-&gt;extU;\n\t\tvs.extD = pVS-&gt;extD;\n\t\t\/\/define how to move\/place generated face back to the VirtualShape\n\t\t\/\/just shift closer to us by length\/2\n\t\tmat4x4_translate(transformMatrix, 0, 0, pVS-&gt;whl&#91;2] \/ 2);\n\t}\n\telse if (applyTo.find(&quot;back&quot;) == 0) {\n\t\tvs.whl&#91;0] = pVS-&gt;whl&#91;0];\n\t\tvs.whl&#91;1] = pVS-&gt;whl&#91;1];\n\t\tvs.sections&#91;0] = pVS-&gt;sections&#91;0];\n\t\tvs.sections&#91;1] = pVS-&gt;sections&#91;1];\n\t\t\/\/extensions\n\t\tvs.extF = pVS-&gt;extB;\n\t\tvs.extL = pVS-&gt;extR;\n\t\tvs.extR = pVS-&gt;extL;\n\t\tvs.extU = pVS-&gt;extU;\n\t\tvs.extD = pVS-&gt;extD;\n\t\t\/\/rotate 180 degrees around Y and shift farther from us by half-length\n\t\tmat4x4_translate(transformMatrix, 0, 0, -pVS-&gt;whl&#91;2] \/ 2);\n\t\tmat4x4_rotate_Y(transformMatrix, transformMatrix, degrees2radians * 180);\n\t}\n\telse if (applyTo.find(&quot;left&quot;) == 0) {\n\t\tvs.whl&#91;0] = pVS-&gt;whl&#91;2]; \/\/width = original length\n\t\tvs.whl&#91;1] = pVS-&gt;whl&#91;1];\n\t\tvs.sections&#91;0] = pVS-&gt;sections&#91;2];\n\t\tvs.sections&#91;1] = pVS-&gt;sections&#91;1];\n\t\t\/\/extensions\n\t\tvs.extF = pVS-&gt;extL;\n\t\tvs.extL = pVS-&gt;extB;\n\t\tvs.extR = pVS-&gt;extF;\n\t\tvs.extU = pVS-&gt;extU;\n\t\tvs.extD = pVS-&gt;extD;\n\t\t\/\/rotate -90 degrees around Y (CW) and shift half-width to the left\n\t\tmat4x4_translate(transformMatrix, -pVS-&gt;whl&#91;0] \/ 2, 0, 0);\n\t\tmat4x4_rotate_Y(transformMatrix, transformMatrix, -degrees2radians * 90);\n\t}\n\telse if (applyTo.find(&quot;right&quot;) == 0) {\n\t\tvs.whl&#91;0] = pVS-&gt;whl&#91;2]; \/\/width = original length\n\t\tvs.whl&#91;1] = pVS-&gt;whl&#91;1];\n\t\tvs.sections&#91;0] = pVS-&gt;sections&#91;2];\n\t\tvs.sections&#91;1] = pVS-&gt;sections&#91;1];\n\t\t\/\/extensions\n\t\tvs.extF = pVS-&gt;extR;\n\t\tvs.extL = pVS-&gt;extF;\n\t\tvs.extR = pVS-&gt;extB;\n\t\tvs.extU = pVS-&gt;extU;\n\t\tvs.extD = pVS-&gt;extD;\n\t\t\/\/rotate +90 degrees around Y (CCW) and shift half-width to the right\n\t\tmat4x4_translate(transformMatrix, pVS-&gt;whl&#91;0] \/ 2, 0, 0);\n\t\tmat4x4_rotate_Y(transformMatrix, transformMatrix, degrees2radians * 90);\n\t}\n\telse if (applyTo.find(&quot;top&quot;) == 0) {\n\t\tvs.whl&#91;0] = pVS-&gt;whl&#91;0];\n\t\tvs.whl&#91;1] = pVS-&gt;whl&#91;2]; \/\/height = original length\n\t\tvs.sections&#91;0] = pVS-&gt;sections&#91;0];\n\t\tvs.sections&#91;1] = pVS-&gt;sections&#91;2];\n\t\t\/\/extensions\n\t\tvs.extF = pVS-&gt;extU;\n\t\tvs.extL = pVS-&gt;extR;\n\t\tvs.extR = pVS-&gt;extL;\n\t\tvs.extU = pVS-&gt;extF;\n\t\tvs.extD = pVS-&gt;extB;\n\t\t\/\/rotate -90 degrees around X (CW) and 180 around Y, and shift half-height up\n\t\tmat4x4_translate(transformMatrix, 0, pVS-&gt;whl&#91;1] \/ 2, 0);\n\t\tmat4x4_rotate_Y(transformMatrix, transformMatrix, -degrees2radians * 180);\n\t\tmat4x4_rotate_X(transformMatrix, transformMatrix, -degrees2radians * 90);\n\t}\n\telse if (applyTo.find(&quot;bottom&quot;) == 0) {\n\t\tvs.whl&#91;0] = pVS-&gt;whl&#91;0];\n\t\tvs.whl&#91;1] = pVS-&gt;whl&#91;2]; \/\/height = original length\n\t\tvs.sections&#91;0] = pVS-&gt;sections&#91;0];\n\t\tvs.sections&#91;1] = pVS-&gt;sections&#91;2];\n\t\t\/\/extensions\n\t\tvs.extF = pVS-&gt;extD;\n\t\tvs.extL = pVS-&gt;extL;\n\t\tvs.extR = pVS-&gt;extR;\n\t\tvs.extU = pVS-&gt;extF;\n\t\tvs.extD = pVS-&gt;extB;\n\t\t\/\/rotate 90 around X (CCW) and shift half-height down\n\t\tmat4x4_translate(transformMatrix, 0, -pVS-&gt;whl&#91;1] \/ 2, 0);\n\t\tmat4x4_rotate_X(transformMatrix, transformMatrix, degrees2radians * 90);\n\t}\n\tlockGroup(pMB);\n\t\/\/create vertices\n\tif (strstr(pVS-&gt;shapeType, &quot;tank&quot;) != nullptr)\n\t\tbuildBoxFaceTank(pMB, applyTo, &amp;vs);\n\telse\n\t\tbuildBoxFacePlain(pMB, applyTo, &amp;vs);\n\n\tgroupApplyTexture2(pMB, &quot;front&quot;, pTC, false);\n\tgroupApplyTexture2(pMB, &quot;front&quot;, pTC2nm, true); \/\/for normal map\n\n\t\/\/move face to it&#039;s place (apply transform matrix)\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\treleaseGroup(pMB);\n\treturn 1;\n}\nint ModelBuilder::buildBoxFacePlain(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS) {\n\tif (pVS-&gt;whl&#91;0] == 0 || pVS-&gt;whl&#91;1] == 0)\n\t\treturn 0;\n\t\/\/create vertices\n\tint sectionsX = pVS-&gt;sections&#91;0];\n\tint sectionsY = pVS-&gt;sections&#91;1];\n\tint pointsX = sectionsX + 1;\n\tint pointsY = sectionsY + 1;\n\tfloat stepX = pVS-&gt;whl&#91;0] \/ sectionsX;\n\tfloat stepY = pVS-&gt;whl&#91;1] \/ sectionsY;\n\tfloat kY = pVS-&gt;whl&#91;1] \/ 2;\n\tfor (int iy = 0; iy &lt; pointsY; iy++) {\n\t\tfloat kX = -pVS-&gt;whl&#91;0] \/ 2;\n\t\tfor (int ix = 0; ix &lt; pointsX; ix++) {\n\t\t\tint nSE = addVertex(pMB, kX, kY, pVS-&gt;extF, 0, 0, 1); \/\/vertex number on south-east\n\t\t\tif (iy &gt; 0 &amp;&amp; ix &gt; 0) {\n\t\t\t\t\/\/add 2 triangles\n\t\t\t\tint nSW = nSE - 1; \/\/vertex number south-west\n\t\t\t\tint nNE = nSE - pointsX; \/\/north-east\n\t\t\t\tint nNW = nSW - pointsX; \/\/north-west\n\t\t\t\tadd2triangles(pMB, nNW, nNE, nSW, nSE, iy + ix);\n\t\t\t}\n\t\t\tkX += stepX;\n\t\t}\n\t\tkY -= stepY;\n\t}\n\treturn 1;\n}\nint ModelBuilder::buildBoxFaceTank(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS) {\n\t\/\/for diamond effect - sectionsRad=1, don&#039;t merge normals\n\tbool drawMiddle = true;\n\t\/\/edges\n\tbool drawTop = false;\n\tbool drawBottom = false;\n\tbool drawLeft = false;\n\tbool drawRight = false;\n\t\/\/corners\n\tbool drawTopLeft = false;\n\tbool drawTopRight = false;\n\tbool drawBottomLeft = false;\n\tbool drawBottomRight = false;\n\tif (pVS-&gt;extF == 0 || applyTo.find(&quot; all&quot;) != std::string::npos) {\n\t\tdrawTop = true;\n\t\tdrawBottom = true;\n\t\tdrawLeft = true;\n\t\tdrawRight = true;\n\t\tdrawTopLeft = true;\n\t\tdrawTopRight = true;\n\t\tdrawBottomLeft = true;\n\t\tdrawBottomRight = true;\n\t}\n\telse if (applyTo.find(&quot; h&quot;) != std::string::npos) {\n\t\tdrawLeft = true;\n\t\tdrawRight = true;\n\t}\n\telse if (applyTo.find(&quot; v&quot;) != std::string::npos) {\n\t\tdrawTop = true;\n\t\tdrawBottom = true;\n\t}\n\tif (applyTo.find(&quot; no&quot;) != std::string::npos) {\n\t\tif (applyTo.find(&quot; noM&quot;) != std::string::npos) {\n\t\t\t\/\/middle\n\t\t\tif (applyTo.find(&quot; noMrow&quot;) != std::string::npos) {\n\t\t\t\tdrawMiddle = false;\n\t\t\t\tdrawLeft = false;\n\t\t\t\tdrawRight = false;\n\t\t\t}\n\t\t\tif (applyTo.find(&quot; noMcol&quot;) != std::string::npos) {\n\t\t\t\tdrawMiddle = false;\n\t\t\t\tdrawTop = false;\n\t\t\t\tdrawBottom = false;\n\t\t\t}\n\t\t\tif (applyTo.find(&quot; noMid&quot;) != std::string::npos)\n\t\t\t\tdrawMiddle = false;\n\t\t}\n\t\tif (applyTo.find(&quot; noN&quot;) != std::string::npos) {\n\t\t\t\/\/north\n\t\t\tif (applyTo.find(&quot; noNrow&quot;) != std::string::npos) {\n\t\t\t\tdrawTop = false;\n\t\t\t\tdrawTopLeft = false;\n\t\t\t\tdrawTopRight = false;\n\t\t\t}\n\t\t\tif (applyTo.find(&quot; noNedge&quot;) != std::string::npos)\n\t\t\t\tdrawTop = false;\n\t\t\tif (applyTo.find(&quot; noNW&quot;) != std::string::npos)\n\t\t\t\tdrawTopLeft = false;\n\t\t\tif (applyTo.find(&quot; noNE&quot;) != std::string::npos)\n\t\t\t\tdrawTopRight = false;\n\t\t}\n\t\tif (applyTo.find(&quot; noS&quot;) != std::string::npos) {\n\t\t\t\/\/south\n\t\t\tif (applyTo.find(&quot; noSrow&quot;) != std::string::npos) {\n\t\t\t\tdrawBottom = false;\n\t\t\t\tdrawBottomLeft = false;\n\t\t\t\tdrawBottomRight = false;\n\t\t\t}\n\t\t\tif (applyTo.find(&quot; noSedge&quot;) != std::string::npos)\n\t\t\t\tdrawBottom = false;\n\t\t\tif (applyTo.find(&quot; noSW&quot;) != std::string::npos)\n\t\t\t\tdrawBottomLeft = false;\n\t\t\tif (applyTo.find(&quot; noSE&quot;) != std::string::npos)\n\t\t\t\tdrawBottomRight = false;\n\t\t}\n\t\tif (applyTo.find(&quot; noW&quot;) != std::string::npos) {\n\t\t\t\/\/west\n\t\t\tif (applyTo.find(&quot; noWcol&quot;) != std::string::npos) {\n\t\t\t\tdrawLeft = false;\n\t\t\t\tdrawTopLeft = false;\n\t\t\t\tdrawBottomLeft = false;\n\t\t\t}\n\t\t\tif (applyTo.find(&quot; noWedge&quot;) != std::string::npos)\n\t\t\t\tdrawLeft = false;\n\t\t}\n\t\tif (applyTo.find(&quot; noE&quot;) != std::string::npos) {\n\t\t\t\/\/east\n\t\t\tif (applyTo.find(&quot; noEcol&quot;) != std::string::npos) {\n\t\t\t\tdrawRight = false;\n\t\t\t\tdrawTopRight = false;\n\t\t\t\tdrawBottomRight = false;\n\t\t\t}\n\t\t\tif (applyTo.find(&quot; noEedge&quot;) != std::string::npos)\n\t\t\t\tdrawRight = false;\n\t\t}\n\t}\n\tlockGroup(pMB);\n\t\/\/middle\n\tif (pVS-&gt;whl&#91;0] &gt; 0 &amp;&amp; pVS-&gt;whl&#91;1] &gt; 0 &amp;&amp; drawMiddle) {\n\t\tbuildBoxFacePlain(pMB, applyTo, pVS);\n\t}\n\tVirtualShape vs;\n\t\/\/edges\n\t\/\/vs.type.assign(&quot;cylinder&quot;);\n\tvs.sectionsR = pVS-&gt;sectionsR;\n\tif (pVS-&gt;whl&#91;0] &gt; 0) {\n\t\tvs.sections&#91;2] = pVS-&gt;sections&#91;0]; \/\/cylinder Z sections n\n\t\tvs.whl&#91;2] = pVS-&gt;whl&#91;0]; \/\/cylinder length Z\n\t\tvs.whl&#91;0] = pVS-&gt;extF * 2; \/\/cylinder diameter X\n\t\tif (pVS-&gt;extU &gt; 0 &amp;&amp; drawTop) {\n\t\t\tvs.whl&#91;1] = pVS-&gt;extU * 2; \/\/cylinder diameter Y\n\t\t\tlockGroup(pMB);\n\t\t\tcylinderWrap(pMB, &amp;vs, 0, 90);\n\t\t\t\/\/rotate -90 degrees around Y and shift up\n\t\t\tmoveGroupDg(pMB, 0, -90, 0, 0, pVS-&gt;whl&#91;1] * 0.5f, 0);\n\t\t\treleaseGroup(pMB);\n\t\t}\n\t\tif (pVS-&gt;extD &gt; 0 &amp;&amp; drawBottom) {\n\t\t\tvs.whl&#91;1] = pVS-&gt;extD * 2; \/\/cylinder diameter Y\n\t\t\tlockGroup(pMB);\n\t\t\tcylinderWrap(pMB, &amp;vs, -90, 0);\n\t\t\t\/\/rotate -90 degrees around Y and shift down\n\t\t\tmoveGroupDg(pMB, 0, -90, 0, 0, -pVS-&gt;whl&#91;1] * 0.5f, 0);\n\t\t\treleaseGroup(pMB);\n\t\t}\n\t}\n\tif (pVS-&gt;whl&#91;1] &gt; 0) {\n\t\tvs.sections&#91;2] = pVS-&gt;sections&#91;1]; \/\/cylinder Z sections n\n\t\tvs.whl&#91;2] = pVS-&gt;whl&#91;1]; \/\/cylinder length Z\n\t\tvs.whl&#91;1] = pVS-&gt;extF * 2; \/\/cylinder diameter Y\n\t\tif (pVS-&gt;extL &gt; 0 &amp;&amp; drawLeft) {\n\t\t\tvs.whl&#91;0] = pVS-&gt;extL * 2; \/\/cylinder diameter X\n\t\t\tlockGroup(pMB);\n\t\t\tcylinderWrap(pMB, &amp;vs, 90, 180);\n\t\t\t\/\/rotate 90 degrees around Y and shift left\n\t\t\tmoveGroupDg(pMB, 90, 0, 0, -pVS-&gt;whl&#91;0] * 0.5f, 0, 0);\n\t\t\treleaseGroup(pMB);\n\t\t}\n\t\tif (pVS-&gt;extR &gt; 0 &amp;&amp; drawRight) {\n\t\t\tvs.whl&#91;0] = pVS-&gt;extR * 2; \/\/cylinder diameter X\n\t\t\tlockGroup(pMB);\n\t\t\tcylinderWrap(pMB, &amp;vs, 0, 90);\n\t\t\t\/\/rotate 90 degrees around Y and shift left\n\t\t\tmoveGroupDg(pMB, 90, 0, 0, pVS-&gt;whl&#91;0] * 0.5f, 0, 0);\n\t\t\treleaseGroup(pMB);\n\t\t}\n\t}\n\t\/\/corners\n\t\/\/vs.type.assign(&quot;cap&quot;);\n\tvs.sectionsR = pVS-&gt;sectionsR;\n\tvs.sections&#91;2] = pVS-&gt;sectionsR;\n\tvs.whl&#91;2] = pVS-&gt;extF;\n\tif (pVS-&gt;extU &gt; 0) {\n\t\t\/\/top corners\n\t\tvs.whl&#91;1] = pVS-&gt;extU * 2;\n\t\tif (pVS-&gt;extL &gt; 0 &amp;&amp; drawTopLeft) {\n\t\t\tvs.whl&#91;0] = pVS-&gt;extL * 2;\n\t\t\tlockGroup(pMB);\n\t\t\tcapWrap(pMB, &amp;vs, 90, 180);\n\t\t\t\/\/rotate 90 degrees around Y and shift left\n\t\t\tmoveGroupDg(pMB, 0, 0, 0, -pVS-&gt;whl&#91;0] * 0.5f, pVS-&gt;whl&#91;1] * 0.5f, 0);\n\t\t\treleaseGroup(pMB);\n\t\t}\n\t\tif (pVS-&gt;extR &gt; 0 &amp;&amp; drawTopRight) {\n\t\t\tvs.whl&#91;0] = pVS-&gt;extR * 2;\n\t\t\tlockGroup(pMB);\n\t\t\tcapWrap(pMB, &amp;vs, 0, 90);\n\t\t\t\/\/rotate 90 degrees around Y and shift left\n\t\t\tmoveGroupDg(pMB, 0, 0, 0, pVS-&gt;whl&#91;0] * 0.5f, pVS-&gt;whl&#91;1] * 0.5f, 0);\n\t\t\treleaseGroup(pMB);\n\n\t\t}\n\t}\n\tif (pVS-&gt;extD &gt; 0) {\n\t\t\/\/bottom corners\n\t\tvs.whl&#91;1] = pVS-&gt;extD * 2;\n\t\tif (pVS-&gt;extL &gt; 0 &amp;&amp; drawBottomLeft) {\n\t\t\tvs.whl&#91;0] = pVS-&gt;extL * 2;\n\t\t\tlockGroup(pMB);\n\t\t\tcapWrap(pMB, &amp;vs, -180, -90);\n\t\t\t\/\/rotate 90 degrees around Y and shift left\n\t\t\tmoveGroupDg(pMB, 0, 0, 0, -pVS-&gt;whl&#91;0] * 0.5f, -pVS-&gt;whl&#91;1] * 0.5f, 0);\n\t\t\treleaseGroup(pMB);\n\t\t}\n\t\tif (pVS-&gt;extR &gt; 0 &amp;&amp; drawBottomRight) {\n\t\t\tvs.whl&#91;0] = pVS-&gt;extR * 2;\n\t\t\tlockGroup(pMB);\n\t\t\tcapWrap(pMB, &amp;vs, -90, 0);\n\t\t\t\/\/rotate 90 degrees around Y and shift left\n\t\t\tmoveGroupDg(pMB, 0, 0, 0, pVS-&gt;whl&#91;0] * 0.5f, -pVS-&gt;whl&#91;1] * 0.5f, 0);\n\t\t\treleaseGroup(pMB);\n\t\t}\n\t}\n\tif (pVS-&gt;extF == 0) {\n\t\tint vertsN = pMB-&gt;vertices.size();\n\t\tfor (int i = pMB-&gt;pCurrentGroup-&gt;fromVertexN; i &lt; vertsN; i++) {\n\t\t\tVertex01* pVX = pMB-&gt;vertices.at(i);\n\t\t\t\/\/normal\n\t\t\tv3set(pVX-&gt;aNormal, 0, 0, 1);\n\t\t}\n\t}\n\treleaseGroup(pMB);\n\treturn 1;\n}\n\nint ModelBuilder::cylinderWrap(ModelBuilder* pMB, VirtualShape* pVS, float angleFrom, float angleTo) {\n\t\/\/ angleFrom\/To - in degrees\n\tlockGroup(pMB);\n\tfloat stepZ = pVS-&gt;whl&#91;2] \/ pVS-&gt;sections&#91;2];\n\tfloat stepDg = (angleTo - angleFrom) \/ pVS-&gt;sectionsR; \/\/in degrees\n\tfor (int nz = 0; nz &lt;= pVS-&gt;sections&#91;2]; nz++) {\n\t\tfloat kz = stepZ * nz - pVS-&gt;whl&#91;2] * 0.5f;\n\t\tfor (int rpn = 0; rpn &lt;= pVS-&gt;sectionsR; rpn++) {\n\t\t\t\/\/ rpn - radial point number\n\t\t\tfloat angleRd = (angleFrom + stepDg * rpn) * degrees2radians;\n\t\t\tfloat kx = cosf(angleRd);\n\t\t\tfloat ky = sinf(angleRd);\n\t\t\tint nSE = addVertex(pMB, kx, ky, kz, kx, ky, 0);\n\t\t\tif (nz &gt; 0 &amp;&amp; rpn &gt; 0) {\n\t\t\t\tint nSW = nSE - 1;\n\t\t\t\tint nNW = nSW - pVS-&gt;sectionsR - 1;\n\t\t\t\tint nNE = nSE - pVS-&gt;sectionsR - 1;\n\t\t\t\tadd2triangles(pMB, nNE, nNW, nSE, nSW, nz + rpn);\n\t\t\t}\n\t\t}\n\t}\n\t\/\/scale to desirable diameters\n\tmat4x4 transformMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };\n\tmat4x4_scale_aniso(transformMatrix, transformMatrix, pVS-&gt;whl&#91;0] * 0.5f, pVS-&gt;whl&#91;1] * 0.5f, 1);\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}\n\treleaseGroup(pMB);\n\treturn 1;\n}\nint ModelBuilder::capWrap(ModelBuilder* pMB, VirtualShape* pVS, float angleFrom, float angleTo) {\n\t\/\/ angleFrom\/To - in degrees\n\tlockGroup(pMB);\n\t\/\/center point\n\tint n0 = addVertex(pMB, 0, 0, 1, 0, 0, 1);\n\tfloat stepZdg = 90.0f \/ pVS-&gt;sections&#91;2]; \/\/in degrees\n\tfloat stepRdg = (angleTo - angleFrom) \/ pVS-&gt;sectionsR; \/\/in degrees\n\tfor (int nz = 1; nz &lt;= pVS-&gt;sections&#91;2]; nz++) {\n\t\tfloat angleZrd = stepZdg * nz * degrees2radians;\n\t\tfloat kz = cosf(angleZrd);\n\t\tfloat R = sinf(angleZrd);\n\t\tfor (int rpn = 0; rpn &lt;= pVS-&gt;sectionsR; rpn++) {\n\t\t\t\/\/ rpn - radial point number\n\t\t\tfloat angleRd = (angleFrom + stepRdg * rpn) * degrees2radians;\n\t\t\tfloat kx = cosf(angleRd) * R;\n\t\t\tfloat ky = sinf(angleRd) * R;\n\t\t\tint nSE = addVertex(pMB, kx, ky, kz, kx, ky, kz);\n\t\t\tif (rpn &gt; 0) {\n\t\t\t\tif (nz == 1) {\n\t\t\t\t\tint nSW = nSE - 1;\n\t\t\t\t\taddTriangle(pMB, n0, nSW, nSE);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tint nSW = nSE - 1;\n\t\t\t\t\tint nNW = nSW - pVS-&gt;sectionsR - 1;\n\t\t\t\t\tint nNE = nSE - pVS-&gt;sectionsR - 1;\n\t\t\t\t\tadd2triangles(pMB, nNW, nNE, nSW, nSE, nz + rpn);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t\/\/scale to desirable diameters\n\tmat4x4 transformMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };\n\tmat4x4_scale_aniso(transformMatrix, transformMatrix, pVS-&gt;whl&#91;0] * 0.5f, pVS-&gt;whl&#91;1] * 0.5f, pVS-&gt;whl&#91;2]);\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}\n\treleaseGroup(pMB);\n\treturn 1;\n}\nint ModelBuilder::groupApplyTexture2(ModelBuilder* pMB, std::string applyTo, TexCoords* pTC, bool isNormakMap) {\n\tif (pTC == NULL)\n\t\treturn 0;\n\tfloat posMin&#91;3];\n\tfloat posMax&#91;3];\n\tfloat posRange&#91;3];\n\tfor (int i = 0; i &lt; 3; i++) {\n\t\tposMin&#91;i] = 1000000;\n\t\tposMax&#91;i] = -1000000;\n\t}\n\tint vertsN = pMB-&gt;vertices.size();\n\tfor (int vN = pMB-&gt;pCurrentGroup-&gt;fromVertexN; vN &lt; vertsN; vN++) {\n\t\tVertex01* pVX = pMB-&gt;vertices.at(vN);\n\t\tif (pVX-&gt;flag &lt; 0) \/\/ignore\n\t\t\tcontinue;\n\t\tfor (int i = 0; i &lt; 3; i++) {\n\t\t\tif (posMin&#91;i] &gt; pVX-&gt;aPos&#91;i])\n\t\t\t\tposMin&#91;i] = pVX-&gt;aPos&#91;i];\n\t\t\tif (posMax&#91;i] &lt; pVX-&gt;aPos&#91;i])\n\t\t\t\tposMax&#91;i] = pVX-&gt;aPos&#91;i];\n\t\t}\n\t}\n\t\/\/here we have coordinates range\n\tfor (int i = 0; i &lt; 3; i++)\n\t\tposRange&#91;i] = posMax&#91;i] - posMin&#91;i];\n\t\/\/for &quot;front&quot;\n\tint xRateIndex = 0;\n\tbool xRateInverse = false;\n\tint yRateIndex = 1;\n\tbool yRateInverse = true;\n\tif (applyTo.find(&quot;front&quot;) == 0)\n\t\t; \/\/do nothing\n\telse if (applyTo.find(&quot;back&quot;) == 0)\n\t\txRateInverse = true;\n\telse if (applyTo.find(&quot;left&quot;) == 0)\n\t\txRateIndex = 2;\n\telse if (applyTo.find(&quot;right&quot;) == 0) {\n\t\txRateIndex = 2;\n\t\txRateInverse = true;\n\t}\n\telse if (applyTo.find(&quot;top&quot;) == 0) {\n\t\txRateInverse = true;\n\t\tyRateIndex = 2;\n\t}\n\telse if (applyTo.find(&quot;bottom&quot;) == 0)\n\t\tyRateIndex = 2;\n\n\tfloat xRate = 0;\n\tfloat yRate = 0;\n\tfloat tuvRange&#91;2];\n\ttuvRange&#91;0] = pTC-&gt;tuvBottomRight&#91;0] - pTC-&gt;tuvTopLeft&#91;0];\n\ttuvRange&#91;1] = pTC-&gt;tuvBottomRight&#91;1] - pTC-&gt;tuvTopLeft&#91;1];\n\tfor (int vN = pMB-&gt;pCurrentGroup-&gt;fromVertexN; vN &lt; vertsN; vN++) {\n\t\tVertex01* pVX = pMB-&gt;vertices.at(vN);\n\t\tif (pVX-&gt;flag &lt; 0) \/\/ignore\n\t\t\tcontinue;\n\n\t\tif (posRange&#91;xRateIndex] == 0)\n\t\t\txRate = 0;\n\t\telse {\n\t\t\txRate = (pVX-&gt;aPos&#91;xRateIndex] - posMin&#91;xRateIndex]) \/ posRange&#91;xRateIndex];\n\t\t\tif (xRateInverse)\n\t\t\t\txRate = 1.0f - xRate;\n\t\t}\n\t\tif (posRange&#91;yRateIndex] == 0)\n\t\t\tyRate = 0;\n\t\telse {\n\t\t\tyRate = (pVX-&gt;aPos&#91;yRateIndex] - posMin&#91;yRateIndex]) \/ posRange&#91;yRateIndex];\n\t\t\tif (yRateInverse)\n\t\t\t\tyRate = 1.0f - yRate;\n\t\t}\n\t\tfloat* pTuv = pVX-&gt;aTuv;\n\t\tif(isNormakMap)\n\t\t\tpTuv = pVX-&gt;aTuv2;\n\t\tpTuv&#91;0] = pTC-&gt;tuvTopLeft&#91;0] + tuvRange&#91;0] * xRate;\n\t\tpTuv&#91;1] = pTC-&gt;tuvTopLeft&#91;1] + tuvRange&#91;1] * yRate;\n\t}\n\treturn 1;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>7. Build and run:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c21\/00.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<iframe loading=\"lazy\" width=\"100%\" height=\"400\" src=\"https:\/\/www.youtube.com\/embed\/u4vnVa11l-E?controls=0&amp;autoplay=1&amp;loop=1&amp;playlist=u4vnVa11l-E\" title=\"Texture projection\" 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>Good.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Now let&#8217;s run it on<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Android<\/h2>\n\n\n\n<p>8. Close and re-open VS. Open  <em>C:\\CPP\\a997modeler\\p_android\\p_android.sln<\/em>. <\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>9. Under <em>modeler <\/em>add Existing Items from <em>C:\\CPP\\engine\\modeler<\/em><\/p>\n\n\n\n<p><em>TexCoords.cpp<\/em> and <em>TexCoords.h<\/em><\/p>\n\n\n\n<p><strong>Add<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>10. Switch on, unlock, plug in, allow. <\/p>\n\n\n\n<p>Build and run:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c21\/02.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Works. But, as usual, doesn&#8217;t fit well on the screen.<\/p>\n\n\n\n<p>Tried flipping the screen &#8211; even worse:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c21\/03.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Well, will address this issue in the next chapter\u2026<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n","protected":false},"excerpt":{"rendered":"<p class=\"mb-2\">Now let&#8217;s apply a texture to a complicated multi-vertex surface, to the right (blue) side for example. Hope, it&#8217;s clear enough how to write a textured Phong shader, but we&#8217;ll do it later, in some other chapter. For now the flat one (which we have already) will be good enough. We will need: to pass [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":693,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-668","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cross-platform-3d"],"_links":{"self":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/668","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=668"}],"version-history":[{"count":14,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/668\/revisions"}],"predecessor-version":[{"id":840,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/668\/revisions\/840"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media\/693"}],"wp:attachment":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media?parent=668"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=668"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=668"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}