{"id":591,"date":"2021-12-06T22:10:25","date_gmt":"2021-12-06T22:10:25","guid":{"rendered":"https:\/\/writingagame.com\/?p=591"},"modified":"2021-12-07T17:30:44","modified_gmt":"2021-12-07T17:30:44","slug":"chapter-18-camera-view","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2021\/12\/06\/chapter-18-camera-view\/","title":{"rendered":"Chapter 18. Camera view"},"content":{"rendered":"\n<p>In our previous chapters we used straight projection of our subject to the &#8220;screen surface&#8221;. For more realistic picture we need <em>Camera<\/em>. OpenGL ES doesn&#8217;t offer an embedded camera object, so we&#8217;ll need to create one.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Windows<\/h2>\n\n\n\n<p>1. Start VS. Open&nbsp;<em>C:\\CPP\\<em>a997modeler<\/em>\\p_windows\\p_windows.sln<\/em>.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>To see the difference with <em>Camera view<\/em> let&#8217;s first simplify our box movement.<\/p>\n\n\n\n<p>2. Open <em>TheGame.cpp<\/em> and replace rotations speed setting (line 26) by<\/p>\n\n\n\n<p><em>pGS-&gt;ownSpeed.setDegrees(0,1,0);<\/em><\/p>\n\n\n\n<p>Build and run. Now box is spinning simply around Y axis. Looks a bit weird, isn&#8217;t it?<\/p>\n\n\n\n<iframe loading=\"lazy\" width=\"100%\" height=\"400\" src=\"https:\/\/www.youtube.com\/embed\/hrB-gBiLBuE?controls=0&amp;autoplay=1&amp;loop=1&amp;playlist=hrB-gBiLBuE\" title=\"Straight view\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen=\"\"><\/iframe>\n\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>3. Under <em>xEngine <\/em>add new header file <strong>Camera.h<\/strong><\/p>\n\n\n\n<p>Location: <em>C:\\CPP\\engine<\/em><\/p>\n\n\n\n<p>Code:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#pragma once\n#include &quot;Coords.h&quot;\n#include &quot;linmath.h&quot;\n\nclass Camera\n{\npublic:\n\tCoords ownCoords;\n\tmat4x4 lookAtMatrix;\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>We will need to set up camera position and orientation. Eventually we will need to calculate a <em>lookAtMatrix<\/em> in order to build MVP matrix (Model-View-Projection) for rendering.<\/p>\n\n\n\n<p>Our box size is 100x200x400 and location &#8211; 0x0x0. Let&#8217;s place camera at a some distance from the box closer to us at a some height in order to look at the box slightly from above. Position 0x200x1000 will do.<\/p>\n\n\n\n<p>View direction will be direction from camera position to the box position, in our case 0x-200x-1000.<\/p>\n\n\n\n<p>To define view matrix we&#8217;ll need camera position, view direction, which we just calculated, and camera UP vector, which we don&#8217;t have yet, but we can calculate it from direction vector. From this vector we can extract camera&#8217;s yaw and pitch, which in this case are 180 and 11.3 degrees. Then we can set up camera orientation and <em>rotationMatrix<\/em>, and then apply this matrix to vertical vector 0x1x0. Then we can call <em>mat4x4_look_at()<\/em> function to calculate camera&#8217;s <em>lookAtMatrix<\/em>.<\/p>\n\n\n\n<p>Yaw and pitch calculations we will place in <strong>utils<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>4. Open <em>utils.h<\/em> and replace code by<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [10,11,12,13]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &lt;string&gt;\n#include &quot;linmath.h&quot;\n\nint checkGLerrors(std::string ref);\nvoid mat4x4_mul_vec4plus(vec4 vOut, mat4x4 M, vec4 vIn, int v3);\n\nvoid v3set(float* vOut, float x, float y, float z);\nvoid v3copy(float* vOut, float* vIn);\nfloat v3pitchRd(float* vIn);\nfloat v3yawRd(float* vIn);\nfloat v3pitchDg(float* vIn);\nfloat v3yawDg(float* vIn);\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>5. Open <em>utils.cpp<\/em> and replace code by<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [4,42,45,48,51]; title: ; notranslate\" title=\"\">\n#include &quot;utils.h&quot;\n#include &quot;platform.h&quot;\n\nextern float radians2degrees;\n\nint checkGLerrors(std::string ref) {\n    \/\/can be used after any GL call\n    int res = glGetError();\n    if (res == 0)\n        return 0;\n    std::string errCode;\n    switch (res) {\n        \/\/case GL_NO_ERROR: errCode = &quot;GL_NO_ERROR&quot;; break;\n        case GL_INVALID_ENUM: errCode = &quot;GL_INVALID_ENUM&quot;; break;\n        case GL_INVALID_VALUE: errCode = &quot;GL_INVALID_VALUE&quot;; break;\n        case GL_INVALID_OPERATION: errCode = &quot;GL_INVALID_OPERATION&quot;; break;\n        case GL_INVALID_FRAMEBUFFER_OPERATION: errCode = &quot;GL_INVALID_FRAMEBUFFER_OPERATION&quot;; break;\n        case GL_OUT_OF_MEMORY: errCode = &quot;GL_OUT_OF_MEMORY&quot;; break;\n        default: errCode = &quot;??&quot;; break;\n    }\n    mylog(&quot;GL ERROR %d-%s in %s\\n&quot;, res, errCode.c_str(), ref.c_str());\n    return -1;\n}\nvoid mat4x4_mul_vec4plus(vec4 vOut, mat4x4 M, vec4 vIn, int v3) {\n    vec4 v2;\n    if (vOut == vIn) {\n        memcpy(&amp;v2, vIn, sizeof(vec4));\n        vIn = v2;\n    }\n    vIn&#91;3] = (float)v3;\n    mat4x4_mul_vec4(vOut, M, vIn);\n}\nvoid v3set(float* vOut, float x, float y, float z) {\n    vOut&#91;0] = x;\n    vOut&#91;1] = y;\n    vOut&#91;2] = z;\n}\nvoid v3copy(float* vOut, float* vIn) {\n    for (int i = 0; i &lt; 3; i++)\n        vOut&#91;i] = vIn&#91;i];\n}\nfloat v3yawRd(float* vIn) {\n    return atan2f(vIn&#91;0], vIn&#91;2]);\n}\nfloat v3pitchRd(float* vIn) {\n    return -atan2f(vIn&#91;1], sqrtf(vIn&#91;0] * vIn&#91;0] + vIn&#91;2] * vIn&#91;2]));\n}\nfloat v3pitchDg(float* vIn) { \n    return v3pitchRd(vIn) * radians2degrees; \n}\nfloat v3yawDg(float* vIn) { \n    return v3yawRd(vIn) * radians2degrees; \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 just straight <em>mProjection <\/em>matrix we will use perspective <em>mProjection <\/em>and <em>mViewProjection<\/em>, which is a multiplication of <em>mProjection <\/em>and camera&#8217;s <em>lookAtMatrix<\/em>.<\/p>\n\n\n\n<p>Perspective mProjection matrix call is:<\/p>\n\n\n\n<p><em>mat4x4_perspective(mProjection, 3.14f \/ 6.0f, (float)screenSize[0]\/screenSize[1], 700.f, 1300.f);<\/em><\/p>\n\n\n\n<p>Parameters:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><em>mProjection <\/em>&#8211; where to save resulting matrix<\/li><li><em>3.14f \/ 6.0f<\/em> &#8211; view angle, in this case &#8211; 30 degrees in radians<\/li><li><em>(float)screenSize[0]\/screenSize[1]<\/em> &#8211; screen size ratio<\/li><li><em>700.f, 1300.f <\/em>&#8211; near clip and far clip<\/li><\/ul>\n\n\n\n<p>6. Open <em>TheGame.cpp<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [58,59,60,61,62,63,64,65,66,67,68,69,70,81,82,83]; 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\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.type.assign(&quot;box&quot;);\n    vs.whl&#91;0] = 100;\n    vs.whl&#91;1] = 200;\n    vs.whl&#91;2] = 400;\n\n    Material mt;\n    \/\/define material - flat red\n    mt.shaderN = Shader::spN_flat_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(&quot;front&quot;, &amp;vs);\n    pMB-&gt;buildBoxFace(&quot;back&quot;, &amp;vs);\n    pMB-&gt;buildBoxFace(&quot;top&quot;, &amp;vs);\n    pMB-&gt;buildBoxFace(&quot;bottom&quot;, &amp;vs);\n    pMB-&gt;buildBoxFace(&quot;left&quot;, &amp;vs);\n\n    mt.uColor.setRGBA(0, 0, 255,255); pMB-&gt;useMaterial(&amp;mt); \/\/blue\n    pMB-&gt;buildBoxFace(&quot;right&quot;, &amp;vs);\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    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    mat4x4 mProjection, mViewProjection, mMVP;\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        \/\/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, NULL);\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    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><em>TheGame.h<\/em> is modified too, <em>mainCamera <\/em>was added. <\/p>\n\n\n\n<p>7. Replace <em>TheGame.h<\/em> code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [4,12]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &lt;vector&gt;\n#include &quot;GameSubj.h&quot;\n#include &quot;Camera.h&quot;\n\nclass TheGame\n{\npublic:\n\tint screenSize&#91;2];\n\tfloat screenRatio;\n\tbool bExitGame;\n\tCamera mainCamera;\n\n\t\/\/static arrays (vectors) of active GameSubjs\n\tstatic std::vector&lt;GameSubj*&gt; gameSubjs;\npublic:\n\tint run();\n\tint getReady();\n\tint drawFrame();\n\tint cleanUp();\n\tint onScreenResize(int width, int height);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>8. Build and run. Result:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c18\/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\/-YjYuQW9maE?controls=0&amp;autoplay=1&amp;loop=1&amp;playlist=-YjYuQW9maE\" title=\"Camera view\" 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>Much better, isn&#8217;t it?<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>9. However, we have a<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Keystone (trapezoid) effect<\/h2>\n\n\n\n<p>Everyone who took photos in the city faced this problem. This is an image perspective distortion. This is when the lines in the photo, which supposed to be vertical, are not:<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c18\/01.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>A little trick can help fix this. In a <em>mViewProjection <\/em>matrix we can tell that we don&#8217;t want screen coordinates to be affected by vertical (Y) component. It&#8217;s a [1][3] position in the matrix. Just set it to zero.<\/p>\n\n\n\n<p>It&#8217;s line 83 in <em>TheGame.cpp<\/em>.<\/p>\n\n\n\n<p><strong>Before:<\/strong><\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c18\/02.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><strong>After:<\/strong><\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c18\/03.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Now &#8211; on<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Android<\/h2>\n\n\n\n<p>10. Re-start 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>11. Under <em>xEngine <\/em>add Existing Item<\/p>\n\n\n\n<p>Go to <em>C:\\CPP\\engine<\/em><\/p>\n\n\n\n<p>Pick <em>Camera.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>12. Switch on, unlock, plug in, allow. <\/p>\n\n\n\n<p>Rebuild solution, run. <\/p>\n\n\n\n<p>Doesn&#8217;t fit the screen well, but works!<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p class=\"mb-2\">In our previous chapters we used straight projection of our subject to the &#8220;screen surface&#8221;. For more realistic picture we need Camera. OpenGL ES doesn&#8217;t offer an embedded camera object, so we&#8217;ll need to create one. Windows 1. Start VS. Open&nbsp;C:\\CPP\\a997modeler\\p_windows\\p_windows.sln. To see the difference with Camera view let&#8217;s first simplify our box movement. 2. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":599,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-591","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\/591","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=591"}],"version-history":[{"count":15,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/591\/revisions"}],"predecessor-version":[{"id":673,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/591\/revisions\/673"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media\/599"}],"wp:attachment":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media?parent=591"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=591"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=591"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}