{"id":18,"date":"2015-09-18T18:05:53","date_gmt":"2015-09-18T18:05:53","guid":{"rendered":"http:\/\/jonathanlessard.net\/?p=18"},"modified":"2016-05-12T15:04:00","modified_gmt":"2016-05-12T15:04:00","slug":"mesh-to-voxel-shaders-and-script","status":"publish","type":"post","link":"https:\/\/www.jonathanlessard.net\/?p=18","title":{"rendered":"Mesh to voxel shaders and script"},"content":{"rendered":"<p>I wrote a real-time directx geometry shader that converts mesh objects into voxel-like cubes:<\/p>\n<p><a href=\"http:\/\/jonathanlessard.net\/wp-content\/uploads\/2015\/09\/voxelteapot.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-23\" src=\"http:\/\/jonathanlessard.net\/wp-content\/uploads\/2015\/09\/voxelteapot.png\" alt=\"voxelteapot\" width=\"701\" height=\"465\" srcset=\"https:\/\/www.jonathanlessard.net\/wp-content\/uploads\/2015\/09\/voxelteapot.png 701w, https:\/\/www.jonathanlessard.net\/wp-content\/uploads\/2015\/09\/voxelteapot-300x199.png 300w\" sizes=\"auto, (max-width: 701px) 100vw, 701px\" \/><\/a><\/p>\n<p>In action:<br \/>\n<iframe loading=\"lazy\" src=\"https:\/\/www.youtube.com\/embed\/NYggkPSsnsw\" width=\"640\" height=\"480\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p>\n<p>I also wrote a python version for Blender:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.luxrender.net\/gallery\/main.php?g2_view=core.DownloadItem&amp;g2_itemId=17757&amp;g2_serialNumber=1\" alt=\"http:\/\/www.luxrender.net\/gallery\/main.php?g2_view=core.DownloadItem&amp;g2_itemId=17757&amp;g2_serialNumber=1\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>Code for the real-time shader&#8230; no comments, no explanation&#8230; sorry.<\/p>\n<blockquote>\n<pre><code>\r\nfloat Script : STANDARDSGLOBAL &lt; string UIWidget = \"none\"; string ScriptClass = \"object\"; string ScriptOrder = \"standard\"; string ScriptOutput = \"color\"; string Script = \"Technique=Lambert?Main:Main10;\"; &gt; = 0.8;\r\n\r\n\/\/\/\/ UN-TWEAKABLES - AUTOMATICALLY-TRACKED TRANSFORMS \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n\r\nfloat4x4 WorldITXf : WorldInverseTranspose &lt; string UIWidget=\"None\"; &gt;;\r\nfloat4x4 WvpXf : WorldViewProjection &lt; string UIWidget=\"None\"; &gt;;\r\nfloat4x4 WorldXf : World &lt; string UIWidget=\"None\"; &gt;;\r\nfloat4x4 ViewIXf : ViewProjection &lt; string UIWidget=\"None\"; &gt;;\r\n\r\n\/\/\/\/ TWEAKABLE PARAMETERS \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n\r\n\/\/\/ Point Lamp 0 \/\/\/\/\/\/\/\/\/\/\/\/\r\nfloat3 Lamp0Pos : Position &lt; string Object = \"PointLight0\"; string UIName = \"Lamp 0 Position\"; string Space = \"World\"; &gt; = {-0.5f,2.0f,1.25f};\r\nfloat3 Lamp0Color : Specular &lt; string UIName = \"Lamp 0\"; string Object = \"Pointlight0\"; string UIWidget = \"Color\"; &gt; = {2.0f,2.0f,2.0f};\r\n\r\n\/\/\/\/\/\/\/\/ COLOR &amp; TEXTURE \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n\r\ntexture ColorTexture : DIFFUSE &lt; string ResourceName = \"default_color.dds\"; string UIName = \"Diffuse Texture\"; string ResourceType = \"2D\"; &gt;;\r\n\r\nsampler2D ColorSampler = sampler_state {\r\nTexture = ;\r\nFILTER = MIN_MAG_MIP_LINEAR;\r\nAddressU = Wrap;\r\nAddressV = Wrap;\r\n};\r\n\r\n\/\/ shared shadow mapping supported in Cg version\r\n\r\n\/\/\/\/\/\/\/\/ CONNECTOR DATA STRUCTURES \/\/\/\/\/\/\/\/\/\/\/\r\n\r\n\/* data from application vertex buffer *\/\r\nstruct appdata {\r\nfloat3 Position : POSITION;\r\nfloat4 UV : TEXCOORD0;\r\nfloat4 Normal : NORMAL;\r\nfloat4 Tangent : TANGENT0;\r\nfloat4 Binormal : BINORMAL0;\r\n};\r\n\r\n\/* data passed from vertex shader to pixel shader *\/\r\nstruct vertexOutput {\r\nfloat4 HPosition : POSITION;\r\nfloat2 UV : TEXCOORD0;\r\n\/\/ The following values are passed in \"World\" coordinates since\r\n\/\/ it tends to be the most flexible and easy for handling\r\n\/\/ reflections, sky lighting, and other \"global\" effects.\r\n\/\/ float3 LightVec : TEXCOORD1;\r\n\/\/ float3 WorldNormal : TEXCOORD2;\r\nfloat3 WorldTangent : TEXCOORD3;\r\nfloat3 WorldBinormal : TEXCOORD4;\r\nfloat3 WorldView : TEXCOORD5;\r\nfloat3 Norm : NORMAL;\r\n\/\/float3 Color : COLOR1;\r\n};\r\nstruct geoOutput {\r\nfloat4 HPosition : POSITION;\r\nfloat2 UV : TEXCOORD0;\r\nfloat4 Color : COLOR1 ;\r\n\/\/float4 Cubes[100];\r\n\/\/float3 LightVec : TEXCOORD1;\r\n\/\/float3 WorldNormal : TEXCOORD2;\r\n\/\/float3 Norm : NORMAL;\r\n};\r\n\r\n\/\/\/\/\/\/\/\/\/ VERTEX SHADING \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n\r\n\/*********** Generic Vertex Shader ******\/\r\n\r\nvertexOutput std_VS(appdata IN) {\r\nvertexOutput OUT = (vertexOutput)0;\r\nfloat4 Po = float4(IN.Position.xyz,1);\r\nOUT.HPosition = Po;\r\nOUT.UV = IN.UV;\r\nOUT.Norm = IN.Normal;\r\n\/\/OUT.Color = tex2D(ColorSampler,IN.UV);\r\n\/\/OUT.WorldNormal = mul(IN.Normal,WorldITXf).xyz;\r\n\/\/OUT.Norm = IN.Normal;\r\n\/\/float3 Pw = mul(Po,WorldXf).xyz;\r\n\/\/ OUT.LightVec = (Lamp0Pos - Pw);\r\n\r\nreturn OUT;\r\n}\r\nbool SameSide(float4 p1,float4 p2,float4 a,float4 b){\r\nfloat3 cp1 = cross(b-a, p1-a);\r\nfloat3 cp2 = cross(b-a, p2-a);\r\nif(dot(cp1, cp2) &gt;= 0){return true;}\r\nelse return false;\r\n}\r\n\r\nbool PointInTriangle(float4 p, float4 a,float4 b,float4 c){\r\n\r\nif(SameSide(p,a, b,c) &amp;&amp; SameSide(p,b, a,c) &amp;&amp; SameSide(p,c, a,b))\r\n{\r\nreturn true;\r\n}\r\nelse {return false;}\r\n}\r\n[maxvertexcount(102)]\r\n\r\nvoid GS( triangle vertexOutput In[3], inout TriangleStream TriStream )\r\n{\r\ngeoOutput Out;\r\n\r\nfloat size = .5;\r\nfloat UV = In[0].UV;\r\nfloat3 normal;\/\/ = mul((In[0].HPosition+In[1].HPosition+In[2].HPosition),0.33);\r\nfloat4 position;\/\/ = mul((In[0].HPosition+In[1].HPosition+In[2].HPosition),0.33);\r\n\r\nfloat4 wpos1 = mul(In[0].HPosition, WorldXf);\r\nfloat4 wpos2 = mul(In[1].HPosition, WorldXf);\r\nfloat4 wpos3 = mul(In[2].HPosition, WorldXf);\r\n\/\/Trouver Ax et Bx\r\nint Ax = wpos1.x;\r\nint Bx;\r\nif(Ax&gt;wpos2.x){\r\nBx = Ax;\r\nAx = wpos2.x;\r\n}\r\nelse{\r\nBx=wpos2.x;\r\n}\r\nif(Ax&gt;wpos3.x){\r\nAx = wpos3.x;\r\n}\r\nif(Bx&lt;wpos3.x){ Bx=wpos3.x; } \/\/Trouver Ay et By int Ay = wpos1.y; int By; if(Ay&gt;wpos2.y){\r\nBy = Ay;\r\nAy = wpos2.y;\r\n}\r\nelse{\r\nBy=wpos2.y;\r\n}\r\nif(Ay&gt;wpos3.y){\r\nAy = wpos3.y;\r\n}\r\nif(By&lt;wpos3.y){ By=wpos3.y; } \/\/Trouver Az et Bz int Az = wpos1.z; int Bz; if(Az&gt;wpos2.z){\r\nBz = Az;\r\nAz = wpos2.z;\r\n}\r\nelse{\r\nBz=wpos2.z;\r\n}\r\nif(Az&gt;wpos3.z){\r\nAz = wpos3.z;\r\n}\r\nif(Bz&lt;wpos3.z){\r\nBz=wpos3.z;\r\n}\r\n\r\nfloat4 cubes[100];\r\nint numCubes = 0;\r\n\/*cubes[0] = float4(Ax, Ay, Az, 1);\r\ncubes[1] = float4(Bx, By, Bz, 1);\r\ncubes[2] = wpos1;\r\ncubes[3] = wpos2;\r\ncubes[4] = wpos3;*\/\r\n\r\n\/\/Calculer delta du plan form\u00e9 par le triangle\r\n\/\/D'abord, trouver la normale du triangle\r\nfloat4 edgeA = wpos2-wpos1;\r\nfloat4 edgeB = wpos3-wpos1;\r\nfloat3 wNormal = normalize(cross(edgeA,edgeB));\r\nfloat delta = dot(wpos1,wNormal);\r\n\r\n\/\/Trouver les boxels dans le triangle\r\nfloat3 curPos;\r\nfloat3 tempPos;\r\nfor(int x=Ax;x&lt;Bx+1;x++){\r\nfor(int y=Ay;y&lt;By+1;y++){\r\nfor(int z=Az;z&lt;Bz+1;z++){\r\n\/\/Trouver le point le plus proche sur le plan du triangle\r\ncurPos = float3(x,y,z);\r\ntempPos = curPos+(delta-dot(curPos,wNormal))*wNormal;\r\n\/\/garder si distance entre original et nouveau &lt; size\r\nfloat tempDist = distance(curPos,tempPos);\r\nif(tempDist&lt;(1)){\r\n\/\/ Est-ce que le point est \u00e0 l'int\u00e9rieur du triangle?\r\n\/\/ (faire)\r\nif(PointInTriangle(float4(curPos,0), wpos1,wpos2,wpos3)){\r\ncubes[numCubes]=float4(curPos,1-tempDist);\r\nnumCubes++;\r\n}\r\n\r\n}\r\n}\r\n}\r\n}\r\n\/*if(numCubes==0){\r\nnumCubes++;\r\ncubes[0] = float4(Ax,Ay,Az,1); }*\/\r\n\r\n\/\/numCubes=2;\r\nfor(int i=0;i&lt;numCubes;i++){\r\nposition = float4(cubes[i].rgb,1);\r\n\/\/Cr\u00e9e un cube, n\u00e9cessite \"position\", \"normal\", \"size\".\r\nfloat4 pos1 = position + float4(-size, size, -size,0);\r\nfloat4 pos2 = position + float4(-size, -size, -size,0);\r\nfloat4 pos3 = position + float4(size, size, -size,0);\r\nfloat4 pos4 = position + float4(size, -size, -size,0);\r\nfloat4 pos5 = position + float4(-size, size, size,0);\r\nfloat4 pos6 = position + float4(-size, -size, size,0);\r\nfloat4 pos7 = position + float4(size, size, size,0);\r\nfloat4 pos8 = position + float4(size, -size, size,0);\r\n\r\n\/\/pr\u00e9calculer normales et \u00e9clairage (\u00e0 faire)\r\nfloat alpha = cubes[0].a;\r\nalpha = 1;\r\n\/\/alpha = 0.5f;\r\nfloat4 couleur = float4(1,1,1,1);\r\n\/\/face 1\r\nedgeA = pos2-pos1;\r\nedgeB = pos3-pos1;\r\nfloat3 wNormal = normalize(cross(edgeA,edgeB));\r\nfloat3 lightVector = normalize(Lamp0Pos - position);\r\nfloat diffuseValue = dot(lightVector, wNormal);\r\ndiffuseValue = max(diffuseValue, 0.0);\r\nfloat3 couleur1 = Lamp0Color*couleur*diffuseValue;\r\n\r\n\/\/face 2\r\nedgeA = pos7-pos3;\r\nedgeB = pos4-pos3;\r\nwNormal = normalize(cross(edgeA,edgeB));\r\nlightVector = normalize(Lamp0Pos - position);\r\ndiffuseValue = dot(lightVector, wNormal);\r\ndiffuseValue = max(diffuseValue, 0.0);\r\nfloat3 couleur2 = Lamp0Color*couleur*diffuseValue;\r\n\r\n\/\/face 3\r\nedgeA = pos8-pos4;\r\nedgeB = pos2-pos4;\r\nwNormal = normalize(cross(edgeA,edgeB));\r\nlightVector = normalize(Lamp0Pos - position);\r\ndiffuseValue = dot(lightVector, wNormal);\r\ndiffuseValue = max(diffuseValue, 0.0);\r\nfloat3 couleur3 = Lamp0Color*couleur*diffuseValue;\r\n\r\n\/\/face 4\r\nedgeA = pos7-pos3;\r\nedgeB = pos1-pos3;\r\nwNormal = normalize(cross(edgeA,edgeB));\r\nlightVector = normalize(Lamp0Pos - position);\r\ndiffuseValue = dot(lightVector, wNormal);\r\ndiffuseValue = max(diffuseValue, 0.0);\r\nfloat3 couleur4 = Lamp0Color*couleur*diffuseValue;\r\n\r\n\/\/face 5\r\nedgeA = pos5-pos1;\r\nedgeB = pos2-pos1;\r\nwNormal = normalize(cross(edgeA,edgeB));\r\nlightVector = normalize(Lamp0Pos - position);\r\ndiffuseValue = dot(lightVector, wNormal);\r\ndiffuseValue = max(diffuseValue, 0.0);\r\nfloat3 couleur5 = Lamp0Color*couleur*diffuseValue;\r\n\r\n\/\/face 6\r\nedgeA = pos7-pos5;\r\nedgeB = pos6-pos5;\r\nwNormal = normalize(cross(edgeA,edgeB));\r\nlightVector = normalize(Lamp0Pos - position);\r\ndiffuseValue = dot(lightVector, wNormal);\r\ndiffuseValue = max(diffuseValue, 0.0);\r\nfloat3 couleur6 = Lamp0Color*couleur*diffuseValue;\r\n\r\n\/\/1\r\nOut.HPosition = mul(pos4,ViewIXf);\r\nOut.Color = float4((couleur1+couleur2+couleur3)\/3,alpha);\r\nOut.UV = UV;\r\nTriStream.Append( Out );\r\n\r\n\/\/2\r\nOut.HPosition = mul(pos2,ViewIXf);\r\nOut.Color = float4((couleur1+couleur3+couleur5)\/3,alpha);\r\nOut.UV = UV;\r\nTriStream.Append( Out );\r\n\r\n\/\/3\r\nOut.HPosition = mul(pos3,ViewIXf);\r\nOut.Color = float4((couleur1+couleur2+couleur4)\/3,alpha);\r\nOut.UV = UV;\r\nTriStream.Append( Out );\r\n\r\n\/\/4\r\nOut.HPosition = mul(pos1,ViewIXf);\r\nOut.Color = float4((couleur1+couleur4+couleur5)\/3,alpha);\r\nOut.UV = UV;\r\nTriStream.Append( Out );\r\n\r\n\/\/5\r\nOut.HPosition = mul(pos5,ViewIXf);\r\nOut.Color = float4((couleur4+couleur5+couleur6)\/3,alpha);\r\nOut.UV = UV;\r\nTriStream.Append( Out );\r\n\r\n\/\/6\r\nOut.HPosition = mul(pos2,ViewIXf);\r\nOut.Color = float4((couleur1+couleur3+couleur5)\/3,alpha);\r\nOut.UV = UV;\r\nTriStream.Append( Out );\r\n\r\n\/\/7\r\nOut.HPosition = mul(pos6,ViewIXf);\r\nOut.Color = float4((couleur3+couleur5+couleur6)\/3,alpha);\r\nOut.UV = UV;\r\nTriStream.Append( Out );\r\n\r\n\/\/8\r\nOut.HPosition = mul(pos4,ViewIXf);\r\nOut.Color = float4((couleur1+couleur2+couleur3)\/3,alpha);\r\nOut.UV = UV;\r\nTriStream.Append( Out );\r\n\r\n\/\/9\r\nOut.HPosition = mul(pos8,ViewIXf);\r\nOut.Color = float4((couleur2+couleur3+couleur6)\/3,alpha);\r\nOut.UV = UV;\r\nTriStream.Append( Out );\r\n\r\n\/\/10\r\nOut.HPosition = mul(pos3,ViewIXf);\r\nOut.Color = float4((couleur1+couleur2+couleur4)\/3,alpha);\r\nOut.UV = UV;\r\nTriStream.Append( Out );\r\n\r\n\/\/11\r\nOut.HPosition = mul(pos7,ViewIXf);\r\nOut.Color = float4((couleur1+couleur4+couleur5)\/3,alpha);\r\nOut.UV = UV;\r\nTriStream.Append( Out );\r\n\r\n\/\/12\r\nOut.HPosition = mul(pos5,ViewIXf);\r\nOut.Color = float4((couleur4+couleur5+couleur6)\/3,alpha);\r\nOut.UV = UV;\r\nTriStream.Append( Out );\r\n\r\n\/\/13\r\nOut.HPosition = mul(pos8,ViewIXf);\r\nOut.Color = float4((couleur2+couleur3+couleur6)\/3,alpha);\r\nOut.UV = UV;\r\nTriStream.Append( Out );\r\n\r\n\/\/14\r\nOut.HPosition = mul(pos6,ViewIXf);\r\nOut.Color = float4((couleur3+couleur5+couleur6)\/3,alpha);\r\nOut.UV = UV;\r\nTriStream.Append( Out );\r\n\r\nTriStream.RestartStrip();\/\/to end the 2nd triangle*\/\r\n\r\n}\r\n\r\n}\r\n\r\n\/\/\/\/\/\/\/\/\/ PIXEL SHADING \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n\r\nfloat4 std_PS(geoOutput IN) : COLOR {\r\n\r\n\/* \/\/float3 Ln = normalize(IN.LightVec);\r\n\/\/ float3 Nn = normalize(IN.WorldNormal);\r\nfloat ldn = dot(Ln,Nn);\r\nldn = max(ldn,0.0);\r\nfloat3 diffuseColor = (0.0f,1.0f,1.0f);\r\n\/\/ float3 diffuseColor = tex2D(ColorSampler,IN.UV).rgb;\r\nfloat3 result = diffuseColor * (ldn * Lamp0Color + AmbiColor);\r\n\/\/ return as float4\r\nreturn float4(result,1);\r\nreturn float4(tex2D(ColorSampler,IN.UV).rgb,1);*\/\r\nreturn IN.Color*float4(tex2D(ColorSampler,IN.UV).rgb,1);\r\n\/\/float4 bob = (.1,.1,.11);\r\n\/\/return float4(0,1,0.5,1);\r\n}\r\n\r\n\/\/\/\/\/ TECHNIQUES \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\r\n\r\nRasterizerState DisableCulling\r\n{\r\nCullMode = 1;\r\n};\r\n\r\nDepthStencilState DepthEnabling\r\n{\r\nDepthEnable = TRUE;\r\n};\r\n\r\nBlendState DisableBlend\r\n{\r\nBlendEnable[0] = false;\r\n\r\n};\r\nBlendState SrcAlphaBlendingAdd\r\n{\r\nBlendEnable[0] = TRUE;\r\nSrcBlend = SRC_ALPHA;\r\nDestBlend = INV_SRC_ALPHA;\r\nBlendOp = ADD;\r\nSrcBlendAlpha = ONE;\r\nDestBlendAlpha = ONE;\r\nBlendOpAlpha = ADD;\r\nRenderTargetWriteMask[0] = 0x0F;\r\n\r\n};\r\n\r\ntechnique10 Main10 &lt; string Script = \"Pass=p0;\"; &gt; {\r\n\r\npass p0 &lt; string Script = \"Draw=geometry;\"; &gt; {\r\n\r\nSetVertexShader( CompileShader( vs_4_0, std_VS() ) );\r\n\/* ZEnable = true;\r\nZWriteEnable = true;\r\nAlphaBlendEnable = true;\r\nSrcBlend = One;\r\nDestBlend = InvSrcColor;\r\nCullMode = CW; *\/\r\nSetGeometryShader( CompileShader( gs_4_0, GS() ) );\r\n\r\nSetPixelShader( CompileShader( ps_4_0, std_PS() ) );\r\n\r\nSetRasterizerState(DisableCulling);\r\n\r\nSetDepthStencilState(DepthEnabling, 1);\r\nSetBlendState( SrcAlphaBlendingAdd, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );\r\n\r\n}\r\n}\r\n\r\n\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ eof \/\/\r\n\r\n<\/code><\/pre>\n<p>&nbsp;<\/p><\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>I wrote a real-time directx geometry shader that converts mesh objects into voxel-like cubes: In action: I also wrote a python version for Blender: &nbsp; Code for the real-time shader&#8230; no comments, no explanation&#8230; sorry. float Script : STANDARDSGLOBAL &lt; string UIWidget = &#8220;none&#8221;; string ScriptClass = &#8220;object&#8221;; string ScriptOrder = &#8220;standard&#8221;; string ScriptOutput = [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":23,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6,7],"tags":[],"class_list":["post-18","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-3d","category-shaders-and-scripts"],"_links":{"self":[{"href":"https:\/\/www.jonathanlessard.net\/index.php?rest_route=\/wp\/v2\/posts\/18","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.jonathanlessard.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.jonathanlessard.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.jonathanlessard.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.jonathanlessard.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=18"}],"version-history":[{"count":7,"href":"https:\/\/www.jonathanlessard.net\/index.php?rest_route=\/wp\/v2\/posts\/18\/revisions"}],"predecessor-version":[{"id":119,"href":"https:\/\/www.jonathanlessard.net\/index.php?rest_route=\/wp\/v2\/posts\/18\/revisions\/119"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.jonathanlessard.net\/index.php?rest_route=\/wp\/v2\/media\/23"}],"wp:attachment":[{"href":"https:\/\/www.jonathanlessard.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=18"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.jonathanlessard.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=18"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.jonathanlessard.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=18"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}