<br />
<b>Warning</b>:  Declaration of Jetpack_IXR_Client::query() should be compatible with IXR_Client::query(...$args) in <b>/home/clients/7267bc096562fcdb78c0ab60d3ac51fb/web/blog/wp-content/plugins/jetpack/class.jetpack-ixr-client.php</b> on line <b>91</b><br />
{"id":1109,"date":"2017-07-26T10:29:36","date_gmt":"2017-07-26T10:29:36","guid":{"rendered":"http:\/\/barradeau.com\/blog\/?p=1109"},"modified":"2017-08-07T19:31:26","modified_gmt":"2017-08-07T19:31:26","slug":"aligning-2-triangles-in-three-js","status":"publish","type":"post","link":"http:\/\/barradeau.com\/blog\/?p=1109","title":{"rendered":"Fluffy predator with THREE.js &#038; instanced geometry"},"content":{"rendered":"<p>For a recent project I had to manipulate a potentially large number of meshes. I chose to use\u00a0<a href=\"https:\/\/threejs.org\/docs\/#api\/core\/InstancedBufferGeometry\" target=\"_blank\">THREE.InstancedBufferGeometry<\/a> as it is very efficient ; it allows\u00a0to draw the same mesh many times, with different attributes, in a single drawcall.<\/p>\n<p>Working with instances is slightly different from using regular meshes. In a nutshell, you give the <em>blueprint\u00a0<\/em>of the mesh you want to draw, adding attributes as you would with a <a href=\"https:\/\/threejs.org\/docs\/#api\/core\/BufferGeometry\" target=\"_blank\">THREE.Buffergeometry<\/a>\u00a0and set some special <a href=\"https:\/\/threejs.org\/docs\/#api\/core\/InstancedBufferAttribute\" target=\"_blank\">THREE.InstancedBufferAttribute<\/a>\u00a0that will affect each <em>instance<\/em> of the <em>blueprint.<\/em><\/p>\n<p>If you understand the concept of <em>blueprint<\/em>\u00a0(the <em>actual<\/em>\u00a0geometry), <em>instance<\/em> (the <em>placeholder<\/em> for a <em>blueprint<\/em>) and <em>InstancedBufferAttributes<\/em> (the <em>variables<\/em> applied to each <em>instance<\/em> of the <em>blueprint<\/em>), you&#8217;re good to go.<\/p>\n<p>The big difference with <em>regular<\/em> meshes is that\u00a0instead of setting the meshes&#8217; translation\/rotation\/scale (<strong>TRS<\/strong> for short) directly on the mesh object, you have to either, manipulate all the instances&#8217; BufferAttributes on the CPU then re-upload them to the GPU or perform procedural transforms directly on the vertex shader.<\/p>\n<p>Anyhow, a <em>vertex shader<\/em> is involved to transform the <em>blueprint<\/em>. This may seem scary but it&#8217;s not, especially as <a href=\"https:\/\/threejs.org\/examples\/?q=instanc#webgl_interactive_instances_gpu\" target=\"_blank\">brilliant people already did most of it<\/a> :)<\/p>\n<p>There is a limitation when\u00a0using Instances\u00a0; in WebGL, you can&#8217;t pass more than 4 floats\u00a0per <em>attribute<\/em>.\u00a0This may not sound like much\u00a0but it has a big impact on how you manipulate the meshes. You can&#8217;t pass\u00a0a Matrix4\u00a0(4*4 floats) to transform an instance. This was a surprise\u00a0for me, a Matrix4 is the way meshes&#8217; store\u00a0their transformations internally, it would be convenient to\u00a0pass the elements of a matrix and use this <em>matrix InstancedAttribute<\/em> it to transform each instance of the\u00a0<em>blueprint<\/em> but it is not technically feasible.<\/p>\n<blockquote><p>When life gives you lemons&#8230;<\/p><\/blockquote>\n<p>Therefore, the easiest way to transform an <em>instance<\/em> is to use 3\u00a0InstancedBufferAttributes to represent the TRS :<\/p>\n<ul>\n<li><strong>translation<\/strong>: a Vector3, equivalent to\u00a0the<em> mesh.position<\/em><\/li>\n<li><strong>rotation<\/strong>:\u00a0a Vector4,\u00a0equivalent\u00a0to\u00a0the <em>mesh.<strong>quaternion<\/strong><\/em> (not the\u00a0<em>mesh.<strong>rotation<\/strong><\/em>!)<\/li>\n<li><strong>scale<\/strong>:\u00a0a Vector3,\u00a0equivalent\u00a0to\u00a0the <em>mesh.scale<\/em><\/li>\n<\/ul>\n<p>here&#8217;s a method that instantiates <strong>count<\/strong>\u00a0isosceles triangles and randomizes them:<\/p>\n<pre class=\"lang:js decode:true \">function getInstances( count ){\r\n\r\n    \/\/creates an instancedBufferGeometry\r\n    var geometry = new THREE.InstancedBufferGeometry();\r\n\r\n    \/\/a vertex buffer for the 'blueprint' representing a single triangle\r\n    var blueprint = [];\r\n    for ( var i = 0; i &lt; 3; i++){\r\n        var a = Math.PI \/ 180 * 120 * i;\r\n        blueprint.push( Math.cos( a ), Math.sin( a ), 0 );\r\n    }\r\n\r\n    \/\/assign the positions as a 'regular' BufferAttribute\r\n    var attribute = new THREE.BufferAttribute( new Float32Array( blueprint ), 3);\r\n    geometry.addAttribute( 'position', attribute );\r\n\r\n    \/\/and that's it for the 'blueprint' ; all instances will share these data\r\n    \/\/we can add more ; normals and uvs are very often used to shade the mesh.\r\n\r\n    \/\/now for the InstancedBufferAttributes, what makes each instance different.\r\n\r\n    \/\/we create some float buffers to store the properties of each instance\r\n    var translation = new Float32Array( count * 3 );\r\n    var rotation = new Float32Array( count * 4 );\r\n    var scale = new Float32Array( count * 3 );\r\n\r\n    \/\/and iterators for convenience :)\r\n    var translationIterator = 0;\r\n    var rotationIterator = 0;\r\n    var scaleIterator = 0;\r\n\r\n    \/\/and a temp quaternion (rotations are represented by Quaternions, not Eulers)\r\n    var q = new THREE.Quaternion();\r\n\r\n    \/\/now let's feed some random values to transform the instances\r\n    for ( i = 0; i &lt; count; i++ ){\r\n\r\n        \/\/a random position\r\n        translation[ translationIterator++ ] = ( Math.random() - .5 ) * 1000;\r\n        translation[ translationIterator++ ] = ( Math.random() - .5 ) * 1000;\r\n        translation[ translationIterator++ ] = ( Math.random() - .5 ) * 1000;\r\n\r\n        \/\/a random rotation\r\n\r\n        \/\/randomize quaternion not sure if it's how you do it but it looks random\r\n        q.set(  ( Math.random() - .5 ) * 2,\r\n                ( Math.random() - .5 ) * 2,\r\n                ( Math.random() - .5 ) * 2,\r\n                Math.random() * Math.PI );\r\n        q.normalize();\r\n\r\n        \/\/assign to bufferAttribute\r\n        rotation[ rotationIterator++ ] = q.x;\r\n        rotation[ rotationIterator++ ] = q.y;\r\n        rotation[ rotationIterator++ ] = q.z;\r\n        rotation[ rotationIterator++ ] = q.w;\r\n\r\n        \/\/a random scale\r\n        scale[ scaleIterator++ ] = 0.1 + ( Math.random() * 4 );\r\n        scale[ scaleIterator++ ] = 0.1 + ( Math.random() * 4 );\r\n        scale[ scaleIterator++ ] = 0.1 + ( Math.random() * 4 );\r\n\r\n    }\r\n\r\n    \/\/create the InstancedBufferAttributes from our float buffers\r\n    geometry.addAttribute( 'translation', new THREE.InstancedBufferAttribute( translation, 3, 1 ) );\r\n    geometry.addAttribute( 'rotation', new THREE.InstancedBufferAttribute( rotation, 4, 1 ) );\r\n    geometry.addAttribute( 'scale', new THREE.InstancedBufferAttribute( scale, 3, 1 ) );\r\n\r\n    \/\/ create a material\r\n    var material = new THREE.RawShaderMaterial( {\r\n        vertexShader: vertexShader,\r\n        fragmentShader: fragmentShader,\r\n        side:THREE.DoubleSide\r\n    } );\r\n    return new THREE.Mesh( geometry, material );\r\n}<\/pre>\n<p>For\u00a0the vertex shader, you can do something like this (picked from the THREE examples):<\/p>\n<pre class=\"lang:js decode:true\">precision highp float;\r\nuniform mat4 modelViewMatrix;\r\nuniform mat4 projectionMatrix;\r\nuniform mat3 normalMatrix;\r\n\r\n\/\/'blueprint' attribute\r\nattribute vec3 position;\r\n\r\n\/\/instance attributes \r\nattribute vec3 translation;\r\nattribute vec4 rotation;\r\nattribute vec3 scale;\r\n\r\n\/\/ transforms the 'blueprint' geometry with instance attributes\r\nvec3 transform( inout vec3 position, vec3 T, vec4 R, vec3 S ) {\r\n    \/\/applies the scale\r\n    position *= S;\r\n    \/\/computes the rotation where R is a (vec4) quaternion\r\n    position += 2.0 * cross( R.xyz, cross( R.xyz, position ) + R.w * position );\r\n    \/\/translates the transformed 'blueprint'\r\n    position += T;\r\n    \/\/return the transformed position\r\n    return position;\r\n}\r\n\r\n\/\/re-use position for shading\r\nvarying vec3 vPos;\r\nvoid main() {\r\n    \/\/collects the 'blueprint' coordinates\r\n    vec3 pos = position;\r\n    \/\/transform it\r\n    transform( pos, translation, rotation, scale );\r\n    \/\/project to get the fragment position\r\n    gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 );\r\n    \/\/just to render something :)\r\n    vPos = pos;\r\n}<\/pre>\n<p>and the fragment uses the world position as the fragment color:<\/p>\n<pre class=\"\">precision highp float;\r\nvarying vec3 vPos;\r\nvoid main() {\r\n    gl_FragColor = vec4( normalize( vPos ), 1. );\r\n}<\/pre>\n<p>With the code above, the <em>instances\u00a0<\/em>should be properly positioned, rotated and scaled\u00a0and the mesh should look something like this:<\/p>\n<p><iframe loading=\"lazy\" width=\"720\" height=\"720\" src=\"https:\/\/cdn.rawgit.com\/nicoptere\/FluffyPredator\/6d127e33\/basic.html\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p>\n<p>so with this, you can already do a lot of funny stuff, anything that needs many objects to move individually.\u00a0My project\u00a0was to animate a wall of panels, much like how Portal levels build up. I ended up with this mesmerizing *ahem* demo:<\/p>\n<p><span class=\"embed-youtube\" style=\"text-align:center; display: block;\"><iframe class='youtube-player' type='text\/html' width='700' height='394' src='https:\/\/www.youtube.com\/embed\/d1DjaQIw46s?version=3&#038;rel=1&#038;fs=1&#038;autohide=2&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;wmode=transparent' allowfullscreen='true' style='border:0;'><\/iframe><\/span><\/p>\n<p>Then\u00a0I tried to assign\u00a0<em>uvs<\/em> as <em>InstancedBufferAttributes <\/em>(related to each<em> instance)<\/em>\u00a0rather than <em>BufferAttributes<\/em>\u00a0(related to the <em>blueprint<\/em>).<\/p>\n<p><span class=\"embed-youtube\" style=\"text-align:center; display: block;\"><iframe class='youtube-player' type='text\/html' width='700' height='394' src='https:\/\/www.youtube.com\/embed\/4nQiM43PJrs?version=3&#038;rel=1&#038;fs=1&#038;autohide=2&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;wmode=transparent' allowfullscreen='true' style='border:0;'><\/iframe><\/span><\/p>\n<p>Each <em>instance<\/em>\u00a0now has it&#8217;s own uvs instead of using the <em>blueprint<\/em>&#8216;s uvs.<br \/>\nYou may wonder how the\u00a0instances are animated, that&#8217;s quite simple ; you need two sets of positions (two meshes), then create a <em>target<\/em> attribute on the <em>blueprint <\/em>geometry (as it will be the same target for all the <em>instances<\/em>) and in the vertex shader, instead of writing:<\/p>\n<pre class=\"\">vec3 pos = position;<\/pre>\n<p>you&#8217;d write :<\/p>\n<pre class=\"\">vec3 pos = mix( position, target, ratio );<\/pre>\n<p>where <em>ratio<\/em>\u00a0is a value between 0 and 1. When the ratio is 0, the instance is in the original position, when the ratio is 1, it is in the target position, if ratio is between 0 and 1, the values are interpolated between the 2.<\/p>\n<p>The ratio can be passed as an <em>InstancedBufferAttribute<\/em> so that each instance opens gradually and\/or with a delay\u00a0and of course, the ratio can be a noise function to get fancy animations.<\/p>\n<p>It\u00a0is also trivial to use a distribution object\u00a0&#8211;\u00a0the <em>translation<\/em>\u00a0InstanceBufferAttributes are the vertices of the distribution mesh, the <em>rotation<\/em> can be computed with a <em>lookAt()\u00a0&#8211;<\/em>\u00a0which gives this kind of things:<\/p>\n<p><span class=\"embed-youtube\" style=\"text-align:center; display: block;\"><iframe class='youtube-player' type='text\/html' width='700' height='394' src='https:\/\/www.youtube.com\/embed\/oPxAc24iWzU?version=3&#038;rel=1&#038;fs=1&#038;autohide=2&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;wmode=transparent' allowfullscreen='true' style='border:0;'><\/iframe><\/span><\/p>\n<p>Btw the background music was recorded randomly, it is\u00a0&#8220;I bet you look good on the dancefloor&#8221; by the arctic monkeys :)<\/p>\n<p>The next natural step for me was to try to <em>cover<\/em> a mesh with <em>instances<\/em> of triangles so that any\u00a0mesh with a triangular basis would fit nicely and wrap the distribution mesh.<\/p>\n<p>The source triangle should be\u00a0scaled, skewed, rotated and translated so that a single matrix can transform it\u00a0into any triangle in space. Little did I know that\u00a0aligning a triangle to another triangle using a transform matrix requires a special type of transform\u00a0called an\u00a0<em>affine transform\u00a0(<\/em>I couldn&#8217;t explain the difference between <em>homothetic<\/em> and <em>affine<\/em> really).<\/p>\n<blockquote><p>When life gives you affine transforms, run.<\/p><\/blockquote>\n<p>After creating a source unit triangle (like the one used in the basic demo),\u00a0my algorithm went as follows for each face\u00a0of the mesh:<\/p>\n<ol>\n<li>find its center and compute its normal<\/li>\n<li>transform an Object3D so that it is positioned and oriented like the\u00a0face<\/li>\n<li>use the inverse of the object&#8217;s matrix to transform the\u00a0triangle&#8217;s vertices so that they lie on the &#8216;XY&#8217; plane<\/li>\n<li>multiply the object&#8217;s transform matrix with the 2D affine transform (the one that\u00a0aligns 2 triangles)<\/li>\n<li>use this matrix to transform the instance<\/li>\n<\/ol>\n<p>steps 1 to 3 are really straight forward ; THREE has everything you need to do this, especially as, when using an old school THREE.Geometry, you can access the object&#8217;s\u00a0faces&#8217; list where every Face object holds the face normal. step 4 implied finding the affine transform&#8230; After 2 days of research (and headaches), I found <a href=\"http:\/\/jsfiddle.net\/dFrHS\/1\/\">this great JavaScript example<\/a>\u00a0and\u00a0after some refactoring, I ended up\u00a0with this glorious piece of code:<\/p>\n<pre class=\"\">var alignTriangles = function( exports ){\r\n\r\n    \/**\r\n     * find the transform matrix that aligns 3 source Vector3 to 3 destination Vector3\r\n     * @param src array of 3 source Vector3\r\n     * @param dst array of 3 destination Vector3\r\n     * @returns the return matrix {THREE.Matrix4}\r\n     *\/\r\n    var object, center, normal, transform, rotation, inverse;\r\n    function getTransform( src, dst, faceNormal ){\r\n\r\n        \/\/init vars if need be\r\n        if( object === undefined ){\r\n            object = new THREE.Object3D();\r\n            center = new THREE.Vector3();\r\n            normal = new THREE.Vector3();\r\n            transform = new THREE.Matrix4();\r\n            rotation = new THREE.Matrix4();\r\n            inverse = new THREE.Matrix4();\r\n        }\r\n\r\n        \/\/1\r\n        \/\/ computes the face center and copies the normal\r\n        center.set( 0,0,0 ).add(dst[0]).add(dst[1]).add(dst[2]).multiplyScalar(1\/3);\r\n        normal.copy( faceNormal );\r\n\r\n        \/\/2\r\n        \/\/ positions the generic object at the center\r\n        object.position.copy( center );\r\n        \/\/and make it point in the direction of the face\r\n        object.lookAt( center.add( normal.multiplyScalar( 100 ) ) );\r\n        object.updateMatrixWorld();\r\n\r\n        \/\/store this transform\r\n        transform.copy( object.matrixWorld );\r\n\r\n        \/\/3\r\n        \/\/ use the inverse of the object's matrix to transform the triangle's vertices\r\n        inverse = inverse.getInverse(transform);\r\n        var flat = [];\r\n        dst.forEach(function( p ) {\r\n            var v = p.clone().applyMatrix4(inverse);\r\n            v.z = 0;\r\n            flat.push(v);\r\n        });\r\n        \/\/4\r\n        \/\/ multiply the object's transform matrix with the 2D affine transform\r\n        return transform.multiply( getTransformMatrix( rotation, src, flat ) );\r\n\r\n    }\r\n    function adjugate(m) {\r\n        return [\r\n            m[4] * m[8] - m[5] * m[7], m[2] * m[7] - m[1] * m[8], m[1] * m[5] - m[2] * m[4],\r\n            m[5] * m[6] - m[3] * m[8], m[0] * m[8] - m[2] * m[6], m[2] * m[3] - m[0] * m[5],\r\n            m[3] * m[7] - m[4] * m[6], m[1] * m[6] - m[0] * m[7], m[0] * m[4] - m[1] * m[3]\r\n        ];\r\n    }\r\n    function multiplyMatrices(a, b) {\r\n        var c = new Float32Array(9);\r\n        for (var i = 0; i &lt; 3; ++i) {\r\n            for (var j = 0; j &lt; 3; ++j) {\r\n                var cij = 0;\r\n                for (var k = 0; k &lt; 3; ++k) {\r\n                    cij += a[3 * i + k] * b[3 * k + j];\r\n                }\r\n                c[3 * i + j] = cij;\r\n            }\r\n        }\r\n        return c;\r\n    }\r\n    function getTransformMatrix( mat, source, dest ){\r\n        mat.identity();\r\n        var src = [ source[0].x, source[0].y, source[1].x, source[1].y, source[2].x, source[2].y];\r\n        var dst = [ dest[0].x, dest[0].y, dest[1].x, dest[1].y, dest[2].x, dest[2].y];\r\n        var t = multiplyMatrices(\r\n            [dst[0], dst[2], dst[4], dst[1], dst[3], dst[5], 1, 1, 1],\r\n            adjugate([src[0], src[2], src[4], src[1], src[3], src[5], 1, 1, 1])\r\n        );\r\n        mat.elements = [t[0], t[3], 0, t[6], t[1], t[4], 0, t[7], 0, 0, 1, 0, t[2], t[5], 0, t[8]];\r\n        return mat;\r\n    }\r\n\r\n    exports.getTransform = getTransform;\r\n    return exports;\r\n\r\n}({});<\/pre>\n<p>and to use it:<\/p>\n<pre class=\"\">for( i = 0; i &lt; faces.length; i++ ){\r\n    var f = faces[i];\r\n    var t = alignTriangles.getTransform( vs, [vertices[f.a],vertices[f.b],vertices[f.c]], f.normal );\r\n}\r\n<\/pre>\n<p>now<em> t\u00a0<\/em>contains transform from our source triangle to the current face.<\/p>\n<p>I thought it would work but nope! In fact, I obtained a completely valid Matrix4 <strong>but<\/strong>, as\u00a0we need to <em>decompose<\/em> the\u00a0Matrix4 into the <em>T,R &amp; S\u00a0<\/em>components to pass them as attributes to the GPU, I suspect some data, required for the affine transform, went missing. In other words, it\u00a0<em>got schwifty<\/em> and the triangles wouldn&#8217;t fit.<\/p>\n<p>After some further research, I found <a href=\"https:\/\/stackoverflow.com\/questions\/40100640\/three-js-read-a-three-instancedbufferattribute-of-type-mat4-from-the-shader\" target=\"_blank\">this StackOverflow thread explaining how to pass a Matrix4 as 4 vec4<\/a>, then recompose it on the vertex shader. I followed the example and got it to work.<\/p>\n<p>And there you have it: a fluffy Predator!<\/p>\n<p><span class=\"embed-youtube\" style=\"text-align:center; display: block;\"><iframe class='youtube-player' type='text\/html' width='700' height='394' src='https:\/\/www.youtube.com\/embed\/zelfQJQ67q8?version=3&#038;rel=1&#038;fs=1&#038;autohide=2&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;wmode=transparent' allowfullscreen='true' style='border:0;'><\/iframe><\/span><\/p>\n<p>the HI-RES mesh is fairly\u00a0big (250K faces, 8Mo) so it may turn your computer into a toaster.<br \/>\n<strong>UPDATE:<\/strong> I&#8217;ve added a LO-RES setting (65k faces, 2Mo ) that should work better on regular computers, click to load:<br \/>\n<iframe loading=\"lazy\" width=\"720\" height=\"720\" src=\"https:\/\/cdn.rawgit.com\/nicoptere\/FluffyPredator\/a857d75c\/fluffy_predator.html\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p>\n<p>On\u00a0\u00a0a side note, if your mesh disappears when you zoom in, it\u00a0probably gets frustrum culled, try to disable culling on the Mesh:<\/p>\n<pre class=\"\">mesh.frustumCulled = false;<\/pre>\n<p>another reason why it would disappear is if the geometry&#8217;s bounding sphere is too small, you can (hackily) fix this by doing so:<\/p>\n<pre class=\"\">geometry.computeBoundingSphere(); \/\/recompute the bounding sphere\r\ngeometry.boundingSphere.radius = 1000; \/\/ assign an arbitrarily large radius<\/pre>\n<p>note that this will force the render of your mesh, use with caution :)<\/p>\n<p>I can think\u00a0of many silly use\u00a0cases for this, instanced geometry (finally) allows control over hundreds of thousands of meshes, not particles, actual meshes!\u00a0I put the files on GIT repo:\u00a0<a href=\"https:\/\/github.com\/nicoptere\/FluffyPredator\/\" target=\"_blank\">https:\/\/github.com\/nicoptere\/FluffyPredator\/<\/a><\/p>\n<p>enjoy :)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>For a recent project I had to manipulate a potentially large number of meshes. I chose to use\u00a0THREE.InstancedBufferGeometry as it is very efficient ; it allows\u00a0to draw the same mesh many times, with different attributes, in a single drawcall. Working with instances is slightly different from using regular meshes. In a nutshell, you give the &#8230; <span class=\"more\"><a class=\"more-link\" href=\"http:\/\/barradeau.com\/blog\/?p=1109\">[Read more&#8230;]<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":1152,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"sharing_disabled":false,"spay_email":"","jetpack_publicize_message":""},"categories":[3],"tags":[],"jetpack_featured_media_url":"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/07\/2017-07-27-20.png","jetpack_publicize_connections":[],"jetpack_shortlink":"https:\/\/wp.me\/p4oXhx-hT","_links":{"self":[{"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1109"}],"collection":[{"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1109"}],"version-history":[{"count":47,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1109\/revisions"}],"predecessor-version":[{"id":1163,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1109\/revisions\/1163"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/media\/1152"}],"wp:attachment":[{"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1109"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1109"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1109"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}