{"id":522,"date":"2021-12-05T20:05:01","date_gmt":"2021-12-05T20:05:01","guid":{"rendered":"https:\/\/writingagame.com\/?p=522"},"modified":"2023-05-15T20:52:04","modified_gmt":"2023-05-15T20:52:04","slug":"chapter-14-3d-coordinates-and-game-subjects","status":"publish","type":"post","link":"https:\/\/writingagame.com\/index.php\/2021\/12\/05\/chapter-14-3d-coordinates-and-game-subjects\/","title":{"rendered":"Chapter 14. 3D Coordinates and Game Subjects"},"content":{"rendered":"\n<p>3D subject will require 3D coordinates including orientation angles. We will need Euler angles (yaw, pitch, and roll in aviation terminology or heading, attitude and bank in naval). We&#8217;ll start with Euler angles in degrees and in radians and a <em>rotation Matrix<\/em>. We&#8217;ll call this class <strong>Coords<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Windows<\/h2>\n\n\n\n<p>1. Start VS. Open <em>C:\\CPP\\a998engine\\p_windows\\p_windows.sln<\/em>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p>2. Under <em>xEngine <\/em>add new header file <strong>Coords.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;linmath.h&quot;\n\nclass Coords\n{\nprivate:\n\tfloat eulerDg&#91;3] = { 0,0,0 }; \/\/Euler angles (yaw, pitch, and roll) in degrees\n\tfloat eulerRd&#91;3] = { 0,0,0 }; \/\/Euler angles in radians\n\tmat4x4 rotationMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };\n\t\/\/mat4x4 rotationMatrix = { {1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0,0,1} };\npublic:\n\tfloat pos&#91;4] = { 0,0,0,0 }; \/\/x,y,z position + 4-th element for compatibility with 3D 4x4 matrices math\npublic:\n\tvoid setDegrees(float ax, float ay, float az) { setDegrees(this, ax, ay, az); };\n\tstatic void setDegrees(Coords* pC, float ax, float ay, float az);\n\tfloat getRd(int i) { return eulerRd&#91;i]; }; \/\/get angle in radians\n\t\/\/float getDg(int i) { return eulerDg&#91;i]; }; \/\/get angle in degrees\n\tvoid setPosition(float kx, float ky, float kz) { setPosition(this, kx, ky, kz); };\n\tstatic void setPosition(Coords* pC, float kx, float ky, float kz);\n\tmat4x4* getRotationMatrix() { return &amp;rotationMatrix; };\n\tvoid setRotationMatrix(mat4x4 m) { setRotationMatrix(this, m); };\n\tstatic void setRotationMatrix(Coords* pC, mat4x4 m);\n\tstatic void eulerRdToMatrix(mat4x4 rotationMatrix, float* eulerRd);\n\tstatic void matrixToEulerRd(float* eulerRd, mat4x4 m);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p>3. Under <em>xEngine <\/em>add new C++ file <strong>Coords.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;Coords.h&quot;\n#include &quot;platform.h&quot;\n#include &lt;string&gt;\n\nfloat PI = 3.141592f;\nfloat degrees2radians = PI \/ 180.0f;\nfloat radians2degrees = 180.0f \/ PI;\n\nvoid Coords::setDegrees(Coords* pC, float ax, float ay, float az) {\n\tif (pC-&gt;eulerDg&#91;0] == ax &amp;&amp; pC-&gt;eulerDg&#91;1] == ay &amp;&amp; pC-&gt;eulerDg&#91;2] == az)\n\t\treturn;\n\tpC-&gt;eulerDg&#91;0] = ax;\n\tpC-&gt;eulerDg&#91;1] = ay;\n\tpC-&gt;eulerDg&#91;2] = az;\n\t\/\/convert to radians\n\tpC-&gt;eulerRd&#91;0] = pC-&gt;eulerDg&#91;0] * degrees2radians;\n\tpC-&gt;eulerRd&#91;1] = pC-&gt;eulerDg&#91;1] * degrees2radians;\n\tpC-&gt;eulerRd&#91;2] = pC-&gt;eulerDg&#91;2] * degrees2radians;\n\t\/\/re-build rotation matrix\n\teulerRdToMatrix(pC-&gt;rotationMatrix, pC-&gt;eulerRd);\n}\nvoid Coords::eulerRdToMatrix(mat4x4 rotationMatrix, float* eulerRd){\n\t\/\/builds rotation matrix from Euler angles (in radians)\n\tmat4x4_identity(rotationMatrix);\n\t\/\/rotation order: Z-X-Y\n\tfloat a = eulerRd&#91;1];\n\tif (a != 0)\n\t\tmat4x4_rotate_Y(rotationMatrix, rotationMatrix, a);\n\ta = eulerRd&#91;0];\n\tif (a != 0)\n\t\tmat4x4_rotate_X(rotationMatrix, rotationMatrix, a);\n\ta = eulerRd&#91;2];\n\tif (a != 0)\n\t\tmat4x4_rotate_Z(rotationMatrix, rotationMatrix, a);\n}\nvoid Coords::setPosition(Coords* pC, float kx, float ky, float kz) {\n\tpC-&gt;pos&#91;0] = kx;\n\tpC-&gt;pos&#91;1] = ky;\n\tpC-&gt;pos&#91;2] = kz;\n}\nvoid Coords::setRotationMatrix(Coords* pC, mat4x4 m) {\n\tmemcpy(pC-&gt;rotationMatrix, m, sizeof(pC-&gt;rotationMatrix));\n\t\/\/update Euler angles\n\tmatrixToEulerRd(pC-&gt;eulerRd, pC-&gt;rotationMatrix);\n\n\tpC-&gt;eulerDg&#91;0] = pC-&gt;eulerRd&#91;0] * radians2degrees;\n\tpC-&gt;eulerDg&#91;1] = pC-&gt;eulerRd&#91;1] * radians2degrees;\n\tpC-&gt;eulerDg&#91;2] = pC-&gt;eulerRd&#91;2] * radians2degrees;\n}\nvoid Coords::matrixToEulerRd(float* eulerRd, mat4x4 m) {\n\t\/\/calculates Euler angles (in radians) from matrix\n\tfloat yaw, pitch, roll;\n\n\tif (m&#91;1]&#91;2] &gt; 0.998 || m&#91;1]&#91;2] &lt; -0.998) { \/\/ singularity at south or north pole\n\t\tyaw = atan2f(-m&#91;2]&#91;0], m&#91;0]&#91;0]);\n\t\troll = 0;\n\t}\n\telse {\n\t\tyaw = atan2f(-m&#91;0]&#91;2], m&#91;2]&#91;2]);\n\t\troll = atan2f(-m&#91;1]&#91;0], m&#91;1]&#91;1]);\n\t}\n\tpitch = asinf(m&#91;1]&#91;2]);\n\n\teulerRd&#91;0] = pitch;\n\teulerRd&#91;1] = yaw;\n\teulerRd&#91;2] = roll;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p><strong>GameSubj <\/strong>class for beginning will include coordinates, speed (at this point we&#8217;re mostly interested in rotation speed), <em>modelMatrix <\/em>for rendering, and a reference to involved <em>DrawJobs <\/em>(it&#8217;s 2 variables: <em>djStartN <\/em>and <em>djTotalN<\/em>)<\/p>\n\n\n\n<p>4. Under <em>xEngine <\/em>add new header file <strong>GameSubj.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;Material.h&quot;\n#include &lt;string&gt;\n\nclass GameSubj\n{\npublic:\n\tstd::string name;\n\tCoords ownCoords;\n\tCoords ownSpeed;\n\tfloat scale&#91;3] = { 1,1,1 };\n\tint djStartN = 0; \/\/first DJ N in DJs array (DrawJob::drawJobs)\n\tint djTotalN = 0; \/\/number of DJs\n\tmat4x4 ownModelMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };\n\n\tMaterial* pAltMaterial = NULL;\n\npublic:\n\tvirtual ~GameSubj();\n\tvoid buildModelMatrix() { buildModelMatrix(this); };\n\tstatic void buildModelMatrix(GameSubj* pGS);\n\tvirtual int moveSubj() { return moveSubj(this); };\n\tstatic int moveSubj(GameSubj* pGS);\n};\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p>5. Under <em>xEngine <\/em>add new C++ file <strong>GameSubj.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;GameSubj.h&quot;\n#include &quot;platform.h&quot;\n\nGameSubj::~GameSubj() {\n    if (pAltMaterial != NULL)\n        delete pAltMaterial;\n}\nvoid GameSubj::buildModelMatrix(GameSubj* pGS) {\n    mat4x4_translate(pGS-&gt;ownModelMatrix, pGS-&gt;ownCoords.pos&#91;0], pGS-&gt;ownCoords.pos&#91;1], pGS-&gt;ownCoords.pos&#91;2]);\n    \/\/rotation order: Z-X-Y\n    mat4x4_mul(pGS-&gt;ownModelMatrix, pGS-&gt;ownModelMatrix, *(pGS-&gt;ownCoords.getRotationMatrix()));\n\n    if (pGS-&gt;scale&#91;0] != 1 || pGS-&gt;scale&#91;1] != 1 || pGS-&gt;scale&#91;2] != 1)\n        mat4x4_scale_aniso(pGS-&gt;ownModelMatrix, pGS-&gt;ownModelMatrix, pGS-&gt;scale&#91;0], pGS-&gt;scale&#91;1], pGS-&gt;scale&#91;2]);\n}\nint GameSubj::moveSubj(GameSubj* pGS) {\n    if (pGS-&gt;ownSpeed.getRd(0) != 0 || pGS-&gt;ownSpeed.getRd(1) != 0 || pGS-&gt;ownSpeed.getRd(2) != 0) {\n        \/\/apply angle speed\n        mat4x4 newRotationMatrix;\n        mat4x4_mul(newRotationMatrix, *(pGS-&gt;ownCoords.getRotationMatrix()), *(pGS-&gt;ownSpeed.getRotationMatrix()));\n        pGS-&gt;ownCoords.setRotationMatrix(newRotationMatrix);\n    }\n    return 1;\n}\n\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n\n<p>We have 2 functions here so far:<\/p>\n\n\n\n<p><em>buildModelMatrix()<\/em> which builds <em>ownModelMatrix <\/em>for rendering from given coordinates and<br><em>moveSubj()<\/em> which at this point applies rotation speed to model&#8217;s coordinates (in matrix form) and forces to recalculate model&#8217;s Euler angles from resulting matrix by calling <em>setRotationMatrix()<\/em> and <em>matrixToEulerRd()<\/em> functions.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p>In <em>TheGame.h<\/em> we&#8217;ll need an array (vector) of 3D gameSubjs. <\/p>\n\n\n\n<p>6. So, replace <em>TheGame.h<\/em> code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#pragma once\n#include &lt;vector&gt;\n#include &quot;GameSubj.h&quot;\n\nclass TheGame\n{\npublic:\n\tint screenSize&#91;2];\n\tfloat screenRatio;\n\tbool bExitGame;\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 has-css-opacity\"\/>\n\n\n\n<p>In <em>TheGame.cpp<\/em> we will use our textured rectangle as a simple 3D model. The command <em>glEnable(GL_CULL_FACE)<\/em> will instruct OpenGL to render only side, that facing us and to ignore another one. OpenGL considers surface &#8220;facing us&#8221; if vertices are going in counter-clockwise order.<\/p>\n\n\n\n<p>In the <em>getReady()<\/em> function we will create a <em>GameSubj<\/em>, a <em>VBO <\/em>and corresponding <em>DrawJob<\/em>.<\/p>\n\n\n\n<p>In the <em>drawFrame()<\/em> we will scan <em>gameSubjs <\/em>array (consisting of 1 subject), for each (only 1 at this point) subject we&#8217;ll call <em>pGS-&gt;moveSubj()<\/em> function which will apply rotation speed to angle coordinates, then it will build MVP matrix and execute involved <em>DrawJobs<\/em>.<\/p>\n\n\n\n<p>7. Open  <em>TheGame.cpp<\/em>  and replace code by:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#include &quot;TheGame.h&quot;\n#include &quot;platform.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\nextern std::string filesRoot;\n\n\/\/static array (vector) for loaded gameSubjs\nstd::vector&lt;GameSubj*&gt; TheGame::gameSubjs;\n\nstatic const struct\n{\n    float x, y, z, tu, tv;\n} frontVertices&#91;4] =\n{\n    { -0.5f,  0.5f, 0.f, 0.f, 0.f }, \/\/top-left\n    { -0.5f, -0.5f, 0.f, 0.f, 1.f }, \/\/bottom-left\n    {  0.5f,  0.5f, 0.f, 1.f, 0.f }, \/\/top-right\n    {  0.5f, -0.5f, 0.f, 1.f, 1.f }  \/\/bottom-right\n};\n\nint TheGame::getReady() {\n    bExitGame = false;\n    Shader::loadShaders();\n    glEnable(GL_CULL_FACE);\n\n    GameSubj* pGS = new GameSubj();\n    gameSubjs.push_back(pGS);\n    pGS-&gt;djStartN = DrawJob::drawJobs.size();\n\n    pGS-&gt;name.assign(&quot;img1&quot;);\n    \/\/pGS-&gt;ownCoords.setPosition(-50, 50, 0);\n    pGS-&gt;ownSpeed.setDegrees(1, 2, 3);\n    pGS-&gt;scale&#91;0] = 400;\n    pGS-&gt;scale&#91;1] = pGS-&gt;scale&#91;0] \/ 2;\n\n    \/\/face DrawJob\n    \/\/build VBO\n    unsigned int VBOid = DrawJob::newBufferId();\n    glBindBuffer(GL_ARRAY_BUFFER, VBOid);\n    glBufferData(GL_ARRAY_BUFFER, sizeof(frontVertices), frontVertices, GL_STATIC_DRAW);\n    int stride = sizeof(float) * 5;\n    \/\/add DrawJob\n    DrawJob* pDJ = new DrawJob();\n    pDJ-&gt;pointsN = 4; \/\/number of vertices\n    \/\/define material\n    pDJ-&gt;mt.shaderN = Shader::spN_flat_tex;\n    pDJ-&gt;mt.primitiveType = GL_TRIANGLE_STRIP;\n    pDJ-&gt;mt.uTex0 = Texture::loadTexture(filesRoot + &quot;\/dt\/sample_img.png&quot;);\n    \/\/attributes references\n    AttribRef* pAR;\n    pAR = &amp;pDJ-&gt;aPos; pAR-&gt;offset = 0;                 pAR-&gt;glVBOid = VBOid; pAR-&gt;stride = stride;\n    pAR = &amp;pDJ-&gt;aTuv; pAR-&gt;offset = sizeof(float) * 3; pAR-&gt;glVBOid = VBOid; pAR-&gt;stride = stride;\n    \/\/create and fill vertex attributes array (VAO)\n    pDJ-&gt;buildVAO();\n    pGS-&gt;djTotalN = DrawJob::drawJobs.size() - pGS-&gt;djStartN;\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, 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, 200.f, -200.f);\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, mProjection, 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    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 has-css-opacity\"\/>\n\n\n\n<p>8. Build and run:<\/p>\n\n\n\n<p>The image is spinning smoothly in all 3 dimensions, so our 3D functions are working properly.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p><strong>Quaternions<\/strong><br>Although they are in vogue lately, I still don&#8217;t see where they can replace matrices, even in complex 3D calculations involving all 3 axes. Just an alternative way to represent 3D rotation. Or am I missing something? Please correct me if I am wrong.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Clarification: Eventually I did find them quite handy and rewrote this class accordingly.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Android<\/h2>\n\n\n\n<p>9. Re-start VS. Open&nbsp;<em>C:\\CPP\\a998engine\\p_android\\p_android.sln<\/em>.<\/p>\n\n\n\n<p>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<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Coords.cpp<\/li>\n\n\n\n<li>Coords.h<\/li>\n\n\n\n<li>GameSubj.cpp<\/li>\n\n\n\n<li>GameSubj.h<\/li>\n<\/ul>\n\n\n\n<p><strong>Add<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p><br>10. Switch on, unlock, plug in Android, allow.<\/p>\n\n\n\n<p>Build and run. Cool.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity\"\/>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p class=\"mb-2\">3D subject will require 3D coordinates including orientation angles. We will need Euler angles (yaw, pitch, and roll in aviation terminology or heading, attitude and bank in naval). We&#8217;ll start with Euler angles in degrees and in radians and a rotation Matrix. We&#8217;ll call this class Coords. Windows 1. Start VS. Open C:\\CPP\\a998engine\\p_windows\\p_windows.sln. 2. Under [&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-522","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\/522","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=522"}],"version-history":[{"count":14,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/522\/revisions"}],"predecessor-version":[{"id":1957,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/posts\/522\/revisions\/1957"}],"wp:attachment":[{"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/media?parent=522"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/categories?post=522"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writingagame.com\/index.php\/wp-json\/wp\/v2\/tags?post=522"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}