{"id":697,"date":"2021-12-08T01:12:29","date_gmt":"2021-12-08T01:12:29","guid":{"rendered":"https:\/\/writingagame.com\/?p=697"},"modified":"2021-12-08T18:17:46","modified_gmt":"2021-12-08T18:17:46","slug":"chapter-22-screen-resize","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2021\/12\/08\/chapter-22-screen-resize\/","title":{"rendered":"Chapter 22. Screen resize"},"content":{"rendered":"\n<p>When screen size changed, we need to re-position the camera to fit new screen size optimally. For example, in our case optimal scene\/&#8221;stage&#8221; size would be, let&#8217;s say, 500&#215;300 (to fit our spinning box into the screen). Our camera&#8217;s view angle is 30 degrees.<\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c22\/01.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>so, <em><strong>?<\/strong> \/ 250 = cosine(15) \/ sine(15) = cotangent(15)<\/em><\/p>\n\n\n\n<p><em><strong>?<\/strong> = 250 * cotangent(15) = 250 * 3.73 = 923<\/em><\/p>\n\n\n\n<p>which is almost the same as our hard-coded 1000 (line 69 in <em>TheGame.cpp<\/em>) and doesn&#8217;t explain why we don&#8217;t fit screen on Android.<\/p>\n\n\n\n<p>First, we don&#8217;t count screen resolution, and second:<\/p>\n\n\n\n<p>Start VS, open  <em>C:\\CPP\\<em>a997modeler<\/em>\\p_windows\\p_windows.sln<\/em>, run the project, play with window size (from tall and narrow to short and wide). You will note (it was a surprise to me) that our 1000 camera distance defines only VERTICAL fit, not horizontal one. But we don&#8217;t need to fit 500 size units vertically, vertically we need just 300, so<\/p>\n\n\n\n<p><em>cameraDistanceV (vertical) = 150 * cotangent(15)<\/em><\/p>\n\n\n\n<p>For horizontal fit we need to count also screen aspect ratio, which is screen width \/ height, so<\/p>\n\n\n\n<p><em>cameraDistanceH (horizontal) = 250 * cotangent(15) \/ screenAspectRatio<\/em><\/p>\n\n\n\n<p>For example, my S20 phone screen size is 1080 X 2400, so<\/p>\n\n\n\n<p><em>screenAspectRatio = 1080 \/ 2400 = 0.45<\/em><\/p>\n\n\n\n<p><em>cotangent(15) = 3.73<\/em>, so:<\/p>\n\n\n\n<p><em>cameraDistanceV = 150 * cotangent(15) = 150 * 3.73 = 560<\/em><\/p>\n\n\n\n<p><em>cameraDistanceH = 250 * cotangent(15) \/ screenAspectRatio = 250 * 3.73 \/ 0.45 = 2072<\/em><\/p>\n\n\n\n<p>For our camera we&#8217;ll pick the greater value, <strong>2072<\/strong>, which looks quite reasonable. From SUCH distance it surely will fit.<\/p>\n\n\n\n<p>Now, if we flip the phone horizontally, screen size will switch to 2400 X 1080. so<\/p>\n\n\n\n<p><em>screenAspectRatio = 2400 \/ 1080 = 2.22<\/em>, so:<\/p>\n\n\n\n<p><em>cameraDistance<strong>V<\/strong> <\/em>remains the same, <em>560<\/em>, when horizontal one<\/p>\n\n\n\n<p><em>cameraDistanceH = 250 * cotangent(15) \/ screenAspectRatio = 250 * 3.73 \/ 2.22 = 420<\/em>, so this time we&#8217;ll pick the greater vertical value, <strong>560<\/strong>, which is logical, now fitment will be defined by screen <strong>vertical <\/strong>size limit.<\/p>\n\n\n\n<p>NOW we have adaptive camera positioning.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p><strong>Implementation<\/strong>:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Let&#8217;s call new variable <em>screenAspectRatio<\/em>.<\/li><\/ol>\n\n\n\n<p>Open <em>TheGame.h<\/em> and replace the code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [10]; title: ; notranslate\" title=\"\">\n#pragma once\n#include &lt;vector&gt;\n#include &quot;GameSubj.h&quot;\n#include &quot;Camera.h&quot;\n\nclass TheGame\n{\npublic:\n\tint screenSize&#91;2];\n\tfloat screenAspectRatio = 1;\n\tbool bExitGame;\n\tCamera mainCamera;\n\tfloat dirToMainLight&#91;4] = { 1,1,1,0 };\n\n\t\/\/static arrays (vectors) of active GameSubjs\n\tstatic std::vector&lt;GameSubj*&gt; gameSubjs;\npublic:\n\tint run();\n\tint getReady();\n\tint drawFrame();\n\tint cleanUp();\n\tint onScreenResize(int width, int height);\n};\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::getReady(), TheGame::drawFrame()<\/em> and in <em>TheGame::onScreenResize()<\/em> we have camera related changes. Also we&#8217;ll move camera-related calculations from <em>TheGame <\/em>to the <em>Camera <\/em>class.<\/p>\n\n\n\n<p>2. Open <em>TheGame.cpp<\/em> and replace the code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [69,100,101,102,154]; 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;\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    \/\/=== 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    mainCamera.ownCoords.setDegrees(15, 180, 0); \/\/set camera angles\/orientation\n    mainCamera.viewRangeDg = 30;\n    mainCamera.stageSize&#91;0] = 500;\n    mainCamera.stageSize&#91;1] = 375;\n    memcpy(mainCamera.lookAtPoint, pGS-&gt;ownCoords.pos, sizeof(float) * 3);\n    mainCamera.onScreenResize();\n\n    \/\/===== set up light\n    v3set(dirToMainLight, -1, 1, 1);\n    vec3_norm(dirToMainLight, dirToMainLight);\n\n    return 1;\n}\nint TheGame::drawFrame() {\n    myPollEvents();\n\n    \/\/glClearColor(0.0, 0.0, 0.5, 1.0);\n    glClear(GL_COLOR_BUFFER_BIT);\n\n    \/\/calculate halfVector\n    float dirToCamera&#91;4] = { 0,0,-1,0 }; \/\/-z\n    mat4x4_mul_vec4plus(dirToCamera, *mainCamera.ownCoords.getRotationMatrix(), dirToCamera, 0);\n\n    float uHalfVector&#91;4] = { 0,0,0,0 };\n    for (int i = 0; i &lt; 3; i++)\n        uHalfVector&#91;i] = (dirToCamera&#91;i] + dirToMainLight&#91;i]) \/ 2;\n    vec3_norm(uHalfVector, uHalfVector);\n\n    mat4x4 mProjection, mViewProjection, mMVP, mMV4x4;\n    \/\/mat4x4_ortho(mProjection, -(float)screenSize&#91;0] \/ 2, (float)screenSize&#91;0] \/ 2, -(float)screenSize&#91;1] \/ 2, (float)screenSize&#91;1] \/ 2, 100.f, 500.f);\n    float nearClip = mainCamera.focusDistance - 250;\n    float farClip = mainCamera.focusDistance + 250;\n    mat4x4_perspective(mProjection, mainCamera.viewRangeDg * degrees2radians, screenAspectRatio, nearClip, farClip);\n    mat4x4_mul(mViewProjection, mProjection, mainCamera.lookAtMatrix);\n    \/\/mViewProjection&#91;1]&#91;3] = 0; \/\/keystone effect\n\n    \/\/scan subjects\n    int subjsN = gameSubjs.size();\n    for (int subjN = 0; subjN &lt; subjsN; subjN++) {\n        GameSubj* pGS = gameSubjs.at(subjN);\n        \/\/behavior - apply rotation speed\n        pGS-&gt;moveSubj();\n        \/\/prepare subject for rendering\n        pGS-&gt;buildModelMatrix(pGS);\n        \/\/build MVP matrix for given subject\n        mat4x4_mul(mMVP, mViewProjection, pGS-&gt;ownModelMatrix);\n        \/\/build Model-View (rotation) matrix for normals\n        mat4x4_mul(mMV4x4, mainCamera.lookAtMatrix, (vec4*)pGS-&gt;ownCoords.getRotationMatrix());\n        \/\/convert to 3x3 matrix\n        float mMV3x3&#91;3]&#91;3];\n        for (int y = 0; y &lt; 3; y++)\n            for (int x = 0; x &lt; 3; x++)\n                mMV3x3&#91;y]&#91;x] = mMV4x4&#91;y]&#91;x];\n        \/\/render subject\n        for (int i = 0; i &lt; pGS-&gt;djTotalN; i++) {\n            DrawJob* pDJ = DrawJob::drawJobs.at(pGS-&gt;djStartN + i);\n            pDJ-&gt;execute((float*)mMVP, *mMV3x3, dirToMainLight, uHalfVector, NULL);\n        }\n    }\n    mySwapBuffers();\n    return 1;\n}\n\nint TheGame::cleanUp() {\n    int itemsN = gameSubjs.size();\n    \/\/delete all UISubjs\n    for (int i = 0; i &lt; itemsN; i++) {\n        GameSubj* pGS = gameSubjs.at(i);\n        delete pGS;\n    }\n    gameSubjs.clear();\n    \/\/clear all other classes\n    Texture::cleanUp();\n    Shader::cleanUp();\n    DrawJob::cleanUp();\n    return 1;\n}\nint TheGame::onScreenResize(int width, int height) {\n    if (screenSize&#91;0] == width &amp;&amp; screenSize&#91;1] == height)\n        return 0;\n    screenSize&#91;0] = width;\n    screenSize&#91;1] = height;\n    screenAspectRatio = (float)width \/ height;\n    glViewport(0, 0, width, height);\n    mainCamera.onScreenResize();\n    mylog(&quot; screen size %d x %d\\n&quot;, width, height);\n    return 1;\n}\nint TheGame::run() {\n    getReady();\n    while (!bExitGame) {\n        drawFrame();\n    }\n    cleanUp();\n    return 1;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>3. In <em>Camera <\/em>class we have new variables and functionality.<\/p>\n\n\n\n<p>Open <em>Camera.h<\/em> and replace the code by:<\/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\tfloat lookAtPoint&#91;3] = { 0,0,0 };\n\tfloat focusDistance = 100;\n\tfloat viewRangeDg = 30;\n\tfloat stageSize&#91;2] = { 500, 375 };\npublic:\n\tfloat pickDistance() { return pickDistance(this); };\n\tstatic float pickDistance(Camera* pCam);\n\tvoid setCameraPosition() { setCameraPosition(this); };\n\tstatic void setCameraPosition(Camera* pCam);\n\tvoid buildLookAtMatrix() { buildLookAtMatrix(this); };\n\tstatic void buildLookAtMatrix(Camera* pCam);\n\tvoid onScreenResize() { onScreenResize(this); };\n\tstatic void onScreenResize(Camera* pCam);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>4. Under <em>xEngine <\/em>add new C++ file, name: <strong>Camera.cpp<\/strong><\/p>\n\n\n\n<p>Location: <em>C:\\CPP\\engine\\<\/em><\/p>\n\n\n\n<p>Code:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#include &quot;Camera.h&quot;\n#include &quot;TheGame.h&quot;\n#include &quot;utils.h&quot;\n\nextern TheGame theGame;\nextern float degrees2radians;\n\nfloat Camera::pickDistance(Camera* pCam) {\n\tfloat cotangentA = 1.0f \/ tanf(degrees2radians * pCam-&gt;viewRangeDg \/ 2);\n\tfloat cameraDistanceV = pCam-&gt;stageSize&#91;1] \/ 2 * cotangentA;\n\tfloat cameraDistanceH = pCam-&gt;stageSize&#91;0] \/ 2 * cotangentA \/ theGame.screenAspectRatio;\n\tpCam-&gt;focusDistance = fmax(cameraDistanceV, cameraDistanceH);\n\treturn pCam-&gt;focusDistance;\n}\nvoid Camera::setCameraPosition(Camera* pCam) {\n\tv3set(pCam-&gt;ownCoords.pos, 0, 0, -pCam-&gt;focusDistance);\n\tmat4x4_mul_vec4plus(pCam-&gt;ownCoords.pos, *pCam-&gt;ownCoords.getRotationMatrix(), pCam-&gt;ownCoords.pos, 1);\n\tfor (int i = 0; i &lt; 3; i++)\n\t\tpCam-&gt;ownCoords.pos&#91;i] += pCam-&gt;lookAtPoint&#91;i];\n}\nvoid Camera::buildLookAtMatrix(Camera* pCam) {\n\tfloat cameraUp&#91;4] = { 0,1,0,0 }; \/\/y - up\n\tmat4x4_mul_vec4plus(cameraUp, *pCam-&gt;ownCoords.getRotationMatrix(), cameraUp, 0);\n\tmat4x4_look_at(pCam-&gt;lookAtMatrix, pCam-&gt;ownCoords.pos, pCam-&gt;lookAtPoint, cameraUp);\n}\nvoid Camera::onScreenResize(Camera* pCam) {\n\tpCam-&gt;pickDistance();\n\tpCam-&gt;setCameraPosition();\n\tpCam-&gt;buildLookAtMatrix();\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>5. Build and run.<\/p>\n\n\n\n<p>Looks good.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Android<\/h2>\n\n\n\n<p>6. Close and 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>7. Under <em>xEngine <\/em>add existing item <em>Camera.cpp<\/em> from <em>C:\\CPP\\engine<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Add screen resize handling to <em>myglPollEvents()<\/em>.<\/p>\n\n\n\n<p>8. Open <em>myplatform.cpp<\/em> and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; highlight: [64,65,66,67,68]; title: ; notranslate\" title=\"\">\n#include &lt;android\/log.h&gt;\n#include &quot;stdio.h&quot;\n#include &quot;TheGame.h&quot;\n#include &lt;sys\/stat.h&gt;\t\/\/mkdir for Android\n\nextern struct android_app* androidApp;\nextern const ASensor* accelerometerSensor;\nextern ASensorEventQueue* sensorEventQueue;\n\nextern EGLDisplay androidDisplay;\nextern EGLSurface androidSurface;\nextern TheGame theGame;\n\nvoid mylog(const char* _Format, ...) {\n#ifdef _DEBUG\n    char outStr&#91;1024];\n    va_list _ArgList;\n    va_start(_ArgList, _Format);\n    vsprintf(outStr, _Format, _ArgList);\n    __android_log_print(ANDROID_LOG_INFO, &quot;mylog&quot;, outStr, NULL);\n    va_end(_ArgList);\n#endif\n};\n\nvoid mySwapBuffers() {\n\teglSwapBuffers(androidDisplay, androidSurface);\n}\nvoid myPollEvents() {\n\t\/\/ Read all pending events.\n\tint ident;\n\tint events;\n\tstruct android_poll_source* source;\n\n\t\/\/ If not animating, we will block forever waiting for events.\n\t\/\/ If animating, we loop until all events are read, then continue\n\t\/\/ to draw the next frame of animation.\n\twhile ((ident = ALooper_pollAll(0, NULL, &amp;events,\n\t\t(void**)&amp;source)) &gt;= 0) {\n\n\t\t\/\/ Process this event.\n\t\tif (source != NULL) {\n\t\t\tsource-&gt;process(androidApp, source);\n\t\t}\n\n\t\t\/\/ If a sensor has data, process it now.\n\t\tif (ident == LOOPER_ID_USER) {\n\t\t\tif (accelerometerSensor != NULL) {\n\t\t\t\tASensorEvent event;\n\t\t\t\twhile (ASensorEventQueue_getEvents(sensorEventQueue,\n\t\t\t\t\t&amp;event, 1) &gt; 0) {\n\t\t\t\t\t\/\/LOGI(&quot;accelerometer: x=%f y=%f z=%f&quot;,\n\t\t\t\t\t\/\/\tevent.acceleration.x, event.acceleration.y,\n\t\t\t\t\t\/\/\tevent.acceleration.z);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t\/\/ Check if we are exiting.\n\t\tif (androidApp-&gt;destroyRequested != 0) {\n\t\t\ttheGame.bExitGame = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\t\/\/check screen size\n\tEGLint w, h;\n\teglQuerySurface(androidDisplay, androidSurface, EGL_WIDTH, &amp;w);\n\teglQuerySurface(androidDisplay, androidSurface, EGL_HEIGHT, &amp;h);\n\ttheGame.onScreenResize(w, h);\n}\nint myFopen_s(FILE** pFile, const char* filePath, const char* mode) {\n\t*pFile = fopen(filePath, mode);\n\tif (*pFile == NULL) {\n\t\tmylog(&quot;ERROR: can&#039;t open file %s\\n&quot;, filePath);\n\t\treturn -1;\n\t}\n\treturn 1;\n}\nint myMkDir(const char* outPath) {\n\tstruct stat info;\n\tif (stat(outPath, &amp;info) == 0)\n\t\treturn 0; \/\/exists already\n\tint status = mkdir(outPath, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);\n\tif (status == 0)\n\t\treturn 1; \/\/Successfully created\n\tmylog(&quot;ERROR creating, status=%d, errno: %s.\\n&quot;, status, std::strerror(errno));\n\treturn -1;\n}\nvoid myStrcpy_s(char* dst, int maxSize, const char* src) {\n\tstrcpy(dst, src);\n\t\/\/fill tail by zeros\n\tint strLen = strlen(dst);\n\tif (strLen &lt; maxSize)\n\t\tfor (int i = strLen; i &lt; maxSize; i++)\n\t\t\tdst&#91;i] = 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>9. 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\/c22\/02.jpg\"><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><img decoding=\"async\" src=\"https:\/\/writingagame.com\/img\/b01\/c22\/03.jpg\"><\/p>\n\n\n\n<p><\/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\">When screen size changed, we need to re-position the camera to fit new screen size optimally. For example, in our case optimal scene\/&#8221;stage&#8221; size would be, let&#8217;s say, 500&#215;300 (to fit our spinning box into the screen). Our camera&#8217;s view angle is 30 degrees. so, ? \/ 250 = cosine(15) \/ sine(15) = cotangent(15) ? [&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-697","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\/697","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=697"}],"version-history":[{"count":11,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/697\/revisions"}],"predecessor-version":[{"id":719,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/697\/revisions\/719"}],"wp:attachment":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media?parent=697"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=697"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=697"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}