{"id":437,"date":"2016-03-07T11:22:26","date_gmt":"2016-03-07T16:22:26","guid":{"rendered":"https:\/\/blogs.mathworks.com\/graphics\/?p=437"},"modified":"2016-03-07T11:22:26","modified_gmt":"2016-03-07T16:22:26","slug":"signed-distance-fields","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/graphics\/2016\/03\/07\/signed-distance-fields\/","title":{"rendered":"Signed Distance Fields"},"content":{"rendered":"\r\n<div class=\"content\"><p>Recently I heard from a MATLAB user who was trying to draw tubes along a curve using <a href=\"https:\/\/blogs.mathworks.com\/graphics\/2014\/12\/16\/down-the-tubes\/\">this blog post<\/a> I wrote a while back. Unfortunately her curve was a bit more complex than the ones I used in that post. That approach of sweeping a 2D shape along a curve has a number of interesting edge cases that you'll encounter when your curves are complex.<\/p><p>In particular, her curve actually had a \"fork\" in it. This is a particularly tough case for the sweep approach. You basically need to sweep each of the two halves of the fork, and then slice off the parts of one sweep that are inside the other one. The math for this gets rather tricky.<\/p><p>Luckily there's another approach to this problem. This one is fairly compute intensive, but it's quite a bit simpler to implement. It's called <a href=\"https:\/\/en.wikipedia.org\/wiki\/Signed_distance_function\">signed distance fields<\/a>. Let's take a look at how you would solve this problem using an SDF.<\/p><p>First we'll need some sample data. Here's one I made. I have 4 vertices.<\/p><pre class=\"codeinput\">verts = [-1    0 0; <span class=\"keyword\">...<\/span>\r\n        1\/2    0 0; <span class=\"keyword\">...<\/span>\r\n          2  3\/4 0; <span class=\"keyword\">...<\/span>\r\n          2 -3\/4 0];\r\nradius = .5;\r\n<\/pre><p>And I have 3 line segments which connect the four vertices.<\/p><pre class=\"codeinput\">segments = [1 2; 2 3; 2 4];\r\n<\/pre><p>Let's draw the segments.<\/p><pre class=\"codeinput\"><span class=\"keyword\">for<\/span> i=1:size(segments,1)\r\n    line(verts(segments(i,:),1),verts(segments(i,:),2),verts(segments(i,:),3))\r\n<span class=\"keyword\">end<\/span>\r\nbox <span class=\"string\">on<\/span>\r\ndaspect([1 1 1])\r\nview(3)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2016\/sdf_example_01.png\" alt=\"\"> <p>So we want a tube-like surface which follows those line segments, and smoothly blends between two which fork off to the right.<\/p><p>The first thing we'll do is build a 3D grid over the space we're interested in. The bigger you make the grid, the better your surface will be, but the amount of memory you&#8217;re using will climb quickly.<\/p><pre class=\"codeinput\">[y,x,z] = ndgrid(linspace(-1.25,1.25,90),linspace(-1,2.25,90),linspace(-0.75,0.75,45));\r\n<\/pre><p>And now we'll create a 4th array to hold our signed distance field.<\/p><pre class=\"codeinput\">d = zeros(size(z));\r\n<\/pre><p>Now we loop over all of the grid points. At each point in the grid, we'll loop over all of the line segments and calculate the distance from the grid point to the closest point on the line segment. We'll keep the smallest distance and save it in d.<\/p><pre class=\"codeinput\"><span class=\"keyword\">for<\/span> i=1:numel(z)\r\n    p = [x(i),y(i),z(i)];\r\n\r\n    <span class=\"comment\">% Intialize our distance to inf.<\/span>\r\n    closest = inf;\r\n\r\n    <span class=\"comment\">% Loop over all of the line segments.<\/span>\r\n    <span class=\"keyword\">for<\/span> j=1:size(segments,1)\r\n        q1 = verts(segments(j,1),:);\r\n        q2 = verts(segments(j,2),:);\r\n\r\n        <span class=\"comment\">% For line segment q1 + t*(q2-q1), compute the value of t where<\/span>\r\n        <span class=\"comment\">% distance to p is minimized. Clamp to [0 1] so we don't go off<\/span>\r\n        <span class=\"comment\">% the end of the line segment.<\/span>\r\n        invlen2 = 1\/dot(q2-q1,q2-q1);\r\n        t = max(0,min(1,-dot(q1-p,q2-q1)*invlen2));\r\n        v = q1 + t*(q2-q1);\r\n\r\n        <span class=\"comment\">% Is that  the smallest we've seen for this grid element?<\/span>\r\n        closest = min(closest, norm(v-p)-radius);\r\n    <span class=\"keyword\">end<\/span>\r\n\r\n    <span class=\"comment\">% Insert the distance into the array.<\/span>\r\n    d(i) = closest;\r\n<span class=\"keyword\">end<\/span>\r\n<\/pre><p>Notice that the value I saved into the array d was the distance from that grid point to a line segment, minus the radius of the surface at that point on the line segment. That means that anywhere in our grid that d is less than 0 is inside the tube, and anywhere that d is greater than 0 is outside the tube. As we saw in <a href=\"https:\/\/blogs.mathworks.com\/graphics\/2015\/03\/03\/implicit-curves\/\">this post<\/a> about implicit surfaces, this is a job for the <a href=\"https:\/\/www.mathworks.com\/help\/matlab\/ref\/isosurface.html\">isosurface function<\/a>.<\/p><pre class=\"codeinput\">isosurface(x,y,z,d,0)\r\ncamlight\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2016\/sdf_example_02.png\" alt=\"\"> <p>And we can see that this surface lines up nicely with our original model, and does a nice job of blending that fork.<\/p><p>Let's delete the lines and change the view so we can see inside the fork. See how tidy that intersection is? That would be hard to do with the other approach.<\/p><p>Also notice how it rounds off the ends. We can see inside because I made the grid pretty tight. If my grid had been a bit larger, then each of the three ends would be capped by a hemisphere. This is characteristic of the signed distance fields approach.<\/p><pre class=\"codeinput\">delete(findobj(gca,<span class=\"string\">'Type'<\/span>,<span class=\"string\">'line'<\/span>))\r\nview([41 18])\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2016\/sdf_example_03.png\" alt=\"\"> <p>But what if we had a different radius at each vertex, and wanted the tube to interpolate between the radii at the end of each segment?<\/p><p>Let's say that we wanted the following radii at each vertex.<\/p><pre class=\"codeinput\">radii = [.5 .45 .125 .25];\r\n\r\ncla\r\n<span class=\"keyword\">for<\/span> i=1:size(segments,1)\r\n    line(verts(segments(i,:),1),verts(segments(i,:),2),verts(segments(i,:),3))\r\n<span class=\"keyword\">end<\/span>\r\n[xs,ys,zs] = sphere;\r\n<span class=\"keyword\">for<\/span> i=1:size(verts,1)\r\n    surface(verts(i,1)+radii(i)*xs, <span class=\"keyword\">...<\/span>\r\n            verts(i,2)+radii(i)*ys, <span class=\"keyword\">...<\/span>\r\n            verts(i,3)+radii(i)*zs, <span class=\"keyword\">...<\/span>\r\n        <span class=\"string\">'FaceColor'<\/span>,<span class=\"string\">'yellow'<\/span>,<span class=\"string\">'EdgeColor'<\/span>,<span class=\"string\">'none'<\/span>);\r\n<span class=\"keyword\">end<\/span>\r\ncamlight\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2016\/sdf_example_04.png\" alt=\"\"> <p>This is a little trickier, but it's basically just a matter of changing our distance function. We can no longer use the simple formula for the distance between a point and the centerline.<\/p><p>I found a distance function that will do the job in <a href=\"http:\/\/liris.cnrs.fr\/Documents\/Liris-1297.pdf\">this paper<\/a>:<\/p><p>\r\n<table border=0>\r\n<tr><td>Fast distance computation between a point and cylinders, cones,\r\n  line swept spheres and cone-spheres<\/td><\/tr>\r\n<tr><td>Aurelien Barbier and Eric Galin<\/td><\/tr>\r\n<tr><td>LIRIS - CNRS<\/td><\/tr>\r\n<tr><td>Universite Claude Bernard Lyon 1<\/td><\/tr>\r\n<tr><td>69622 Villeurbanne Cedex, France<\/td><\/tr>\r\n<\/table>\r\n<\/p><p>The authors call this primitive with a centerline and a radius at each end the \"cone-sphere\" primitive.<\/p><p>This distance equation is more complex, so I'll precompute some terms to help with performance.<\/p><pre class=\"codeinput\">nsegments = size(segments,1);\r\ninvlen2 = zeros(1,nsegments);\r\ntrange = zeros(nsegments,2);\r\nadj_radii = zeros(nsegments,2);\r\n<span class=\"keyword\">for<\/span> i=1:nsegments\r\n    q1 = verts(segments(i,1),:);\r\n    q2 = verts(segments(i,2),:);\r\n    len2 = dot(q2-q1,q2-q1);\r\n    invlen2(i) = 1\/len2;\r\n\r\n    r1 = radii(segments(i,1));\r\n    r2 = radii(segments(i,2));\r\n    delta = r1-r2;\r\n    s = sqrt(len2 - delta^2);\r\n\r\n    dl = delta * invlen2(i);\r\n    sl = s * sqrt(invlen2(i));\r\n\r\n    trange(i,1) = r1 * dl;\r\n    trange(i,2) = 1 + r2 * dl;\r\n    adj_radii(i,1) = r1 * sl;\r\n    adj_radii(i,2) = r2 * sl;\r\n<span class=\"keyword\">end<\/span>\r\n<\/pre><p>Now we can repeat our loop with this new distance formula.<\/p><pre class=\"codeinput\"><span class=\"keyword\">for<\/span> i=1:numel(z)\r\n    p = [x(i),y(i),z(i)];\r\n\r\n    closest = inf;\r\n    <span class=\"keyword\">for<\/span> j=1:size(segments,1)\r\n        q1 = verts(segments(j,1),:);\r\n        q2 = verts(segments(j,2),:);\r\n        r1 = radii(segments(j,1));\r\n        r2 = radii(segments(j,2));\r\n\r\n        t = -dot(q1-p,q2-q1)*invlen2(j);\r\n        <span class=\"keyword\">if<\/span> t&lt;trange(j,1)\r\n            v = q1;\r\n            r = r1;\r\n        <span class=\"keyword\">elseif<\/span> t&gt;trange(j,2)\r\n            v = q2;\r\n            r = r2;\r\n        <span class=\"keyword\">else<\/span>\r\n            v = q1 + t*(q2-q1);\r\n            adj_t = (t-trange(j,1)) \/ (trange(j,2)-trange(j,1));\r\n            r = adj_radii(j,1) + adj_t*(adj_radii(j,2)-adj_radii(j,1));\r\n        <span class=\"keyword\">end<\/span>\r\n\r\n        closest = min(closest, norm(v-p)-r);\r\n    <span class=\"keyword\">end<\/span>\r\n\r\n    d(i) = closest;\r\n<span class=\"keyword\">end<\/span>\r\n<\/pre><p>And then we can use isosurface, just like we did before.<\/p><pre class=\"codeinput\">isosurface(x,y,z,d,0)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2016\/sdf_example_05.png\" alt=\"\"> <p>See how the surface lines up with the spheres? Now we'll delete them to get a better look.<\/p><pre class=\"codeinput\">delete(findobj(gca,<span class=\"string\">'Type'<\/span>,<span class=\"string\">'line'<\/span>))\r\ndelete(findobj(gca,<span class=\"string\">'Type'<\/span>,<span class=\"string\">'surface'<\/span>))\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2016\/sdf_example_06.png\" alt=\"\"> <p>Here's another isosurface I made from these 6 edges of a tetrahedron using this technique.<\/p><pre class=\"codeinput\">verts = randn(4,3);\r\nsegments = nchoosek(1:4,2);\r\nradii = 5\/8 * rand(1,4);\r\n<\/pre><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/graphics\/2016\/tetra_sdf.png\" alt=\"\"> <\/p><p>That took quite a while to compute, but it would have been awful tricky to draw it with the sweep approach.<\/p><p>Whenever you want to wrap a surface around a complex shape, then signed distance functions are a technique that you should consider.<\/p><script language=\"JavaScript\"> <!-- \r\n    function grabCode_0886127509d14a78a09e9c9665d5cb81() {\r\n        \/\/ Remember the title so we can use it in the new page\r\n        title = document.title;\r\n\r\n        \/\/ Break up these strings so that their presence\r\n        \/\/ in the Javascript doesn't mess up the search for\r\n        \/\/ the MATLAB code.\r\n        t1='0886127509d14a78a09e9c9665d5cb81 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' 0886127509d14a78a09e9c9665d5cb81';\r\n    \r\n        b=document.getElementsByTagName('body')[0];\r\n        i1=b.innerHTML.indexOf(t1)+t1.length;\r\n        i2=b.innerHTML.indexOf(t2);\r\n \r\n        code_string = b.innerHTML.substring(i1, i2);\r\n        code_string = code_string.replace(\/REPLACE_WITH_DASH_DASH\/g,'--');\r\n\r\n        \/\/ Use \/x3C\/g instead of the less-than character to avoid errors \r\n        \/\/ in the XML parser.\r\n        \/\/ Use '\\x26#60;' instead of '<' so that the XML parser\r\n        \/\/ doesn't go ahead and substitute the less-than character. \r\n        code_string = code_string.replace(\/\\x3C\/g, '\\x26#60;');\r\n\r\n        copyright = 'Copyright 2016 The MathWorks, Inc.';\r\n\r\n        w = window.open();\r\n        d = w.document;\r\n        d.write('<pre>\\n');\r\n        d.write(code_string);\r\n\r\n        \/\/ Add copyright line at the bottom if specified.\r\n        if (copyright.length > 0) {\r\n            d.writeln('');\r\n            d.writeln('%%');\r\n            if (copyright.length > 0) {\r\n                d.writeln('% _' + copyright + '_');\r\n            }\r\n        }\r\n\r\n        d.write('<\/pre>\\n');\r\n\r\n        d.title = title + ' (MATLAB code)';\r\n        d.close();\r\n    }   \r\n     --> <\/script><p style=\"text-align: right; font-size: xx-small; font-weight:lighter;   font-style: italic; color: gray\"><br><a href=\"javascript:grabCode_0886127509d14a78a09e9c9665d5cb81()\"><span style=\"font-size: x-small;        font-style: italic;\">Get \r\n      the MATLAB code <noscript>(requires JavaScript)<\/noscript><\/span><\/a><br><br>\r\n      Published with MATLAB&reg; R2015b<br><\/p><\/div><!--\r\n0886127509d14a78a09e9c9665d5cb81 ##### SOURCE BEGIN #####\r\n%%\r\n% Recently I heard from a MATLAB user who was trying to draw tubes along a\r\n% curve using\r\n% <https:\/\/blogs.mathworks.com\/graphics\/2014\/12\/16\/down-the-tubes\/ this blog\r\n% post> I wrote a while back. Unfortunately her curve was a bit more\r\n% complex than the ones I used in that post. That approach of sweeping a 2D\r\n% shape along a curve has a number of interesting edge cases that you'll\r\n% encounter when your curves are complex.\r\n%\r\n% In particular, her curve actually had a \"fork\" in it. This is a\r\n% particularly tough case for the sweep approach. You basically need to\r\n% sweep each of the two halves of the fork, and then slice off the parts of\r\n% one sweep that are inside the other one. The math for this gets rather\r\n% tricky.\r\n%\r\n% Luckily there's another approach to this problem. This one is fairly\r\n% compute intensive, but it's quite a bit simpler to implement. It's called\r\n% <https:\/\/en.wikipedia.org\/wiki\/Signed_distance_function signed distance fields>. Let's take a look at how you would solve this\r\n% problem using an SDF.\r\n%\r\n% First we'll need some sample data. Here's one I made. I have 4 vertices.\r\n%\r\nverts = [-1    0 0; ...\r\n        1\/2    0 0; ...\r\n          2  3\/4 0; ...\r\n          2 -3\/4 0];\r\nradius = .5;\r\n\r\n%%\r\n% And I have 3 line segments which connect the four vertices.\r\n%\r\nsegments = [1 2; 2 3; 2 4];\r\n\r\n%%\r\n% Let's draw the segments.\r\n%\r\nfor i=1:size(segments,1)\r\n    line(verts(segments(i,:),1),verts(segments(i,:),2),verts(segments(i,:),3))\r\nend\r\nbox on\r\ndaspect([1 1 1])\r\nview(3)\r\n\r\n%%\r\n% So we want a tube-like surface which follows those line segments, and\r\n% smoothly blends between two which fork off to the right.\r\n%\r\n% The first thing we'll do is build a 3D grid over the space we're interested \r\n% in. The bigger you make the grid, the better your surface will be, but \r\n% the amount of memory you\u00e2\u20ac\u2122re using will climb quickly.\r\n%\r\n[y,x,z] = ndgrid(linspace(-1.25,1.25,90),linspace(-1,2.25,90),linspace(-0.75,0.75,45));\r\n\r\n%%\r\n% And now we'll create a 4th array to hold our signed distance field.\r\n%\r\nd = zeros(size(z));\r\n\r\n%%\r\n% Now we loop over all of the grid points. At each point in the grid, we'll\r\n% loop over all of the line segments and calculate the distance from the\r\n% grid point to the closest point on the line segment. We'll keep the\r\n% smallest distance and save it in d.\r\n%\r\nfor i=1:numel(z)\r\n    p = [x(i),y(i),z(i)];\r\n    \r\n    % Intialize our distance to inf.\r\n    closest = inf;\r\n    \r\n    % Loop over all of the line segments.\r\n    for j=1:size(segments,1)\r\n        q1 = verts(segments(j,1),:);\r\n        q2 = verts(segments(j,2),:);\r\n        \r\n        % For line segment q1 + t*(q2-q1), compute the value of t where\r\n        % distance to p is minimized. Clamp to [0 1] so we don't go off\r\n        % the end of the line segment.\r\n        invlen2 = 1\/dot(q2-q1,q2-q1);\r\n        t = max(0,min(1,-dot(q1-p,q2-q1)*invlen2));\r\n        v = q1 + t*(q2-q1);\r\n        \r\n        % Is that  the smallest we've seen for this grid element?\r\n        closest = min(closest, norm(v-p)-radius);\r\n    end\r\n    \r\n    % Insert the distance into the array.\r\n    d(i) = closest;\r\nend\r\n\r\n%%\r\n% Notice that the value I saved into the array d was the distance from that\r\n% grid point to a line segment, minus the radius of the surface at that\r\n% point on the line segment. That means that anywhere in our grid that d is\r\n% less than 0 is inside the tube, and anywhere that d is greater than 0 is\r\n% outside the tube. As we saw in\r\n% <https:\/\/blogs.mathworks.com\/graphics\/2015\/03\/03\/implicit-curves\/ this\r\n% post> about implicit surfaces, this is a job for the\r\n% <https:\/\/www.mathworks.com\/help\/matlab\/ref\/isosurface.html isosurface\r\n% function>.\r\n%\r\nisosurface(x,y,z,d,0)\r\ncamlight\r\n\r\n%%\r\n% And we can see that this surface lines up nicely with our original model,\r\n% and does a nice job of blending that fork.\r\n%\r\n% Let's delete the lines and change the view so we can see inside the fork.\r\n% See how tidy that intersection is? That would be hard to do with the other\r\n% approach.\r\n%\r\n% Also notice how it rounds off the ends. We can see inside because I made\r\n% the grid pretty tight. If my grid had been a bit larger, then each of the\r\n% three ends would be capped by a hemisphere. This is characteristic of\r\n% the signed distance fields approach.\r\n%\r\ndelete(findobj(gca,'Type','line'))\r\nview([41 18])\r\n\r\n%%\r\n% But what if we had a different radius at each vertex, and wanted the tube\r\n% to interpolate between the radii at the end of each segment?\r\n%\r\n% Let's say that we wanted the following radii at each vertex.\r\n%\r\nradii = [.5 .45 .125 .25];\r\n\r\ncla\r\nfor i=1:size(segments,1)\r\n    line(verts(segments(i,:),1),verts(segments(i,:),2),verts(segments(i,:),3))\r\nend\r\n[xs,ys,zs] = sphere;\r\nfor i=1:size(verts,1)\r\n    surface(verts(i,1)+radii(i)*xs, ...\r\n            verts(i,2)+radii(i)*ys, ...\r\n            verts(i,3)+radii(i)*zs, ...\r\n        'FaceColor','yellow','EdgeColor','none');\r\nend\r\ncamlight\r\n\r\n%%\r\n% This is a little trickier, but it's basically just a matter of changing\r\n% our distance function. We can no longer use the simple formula for the\r\n% distance between a point and the centerline. \r\n%\r\n% I found a distance function that will do the job in \r\n% <http:\/\/liris.cnrs.fr\/Documents\/Liris-1297.pdf this paper>:\r\n%\r\n% <html>\r\n% <table border=0>\r\n% <tr><td>Fast distance computation between a point and cylinders, cones,\r\n%   line swept spheres and cone-spheres<\/td><\/tr>\r\n% <tr><td>Aurelien Barbier and Eric Galin<\/td><\/tr>\r\n% <tr><td>LIRIS - CNRS<\/td><\/tr>\r\n% <tr><td>Universite Claude Bernard Lyon 1<\/td><\/tr>\r\n% <tr><td>69622 Villeurbanne Cedex, France<\/td><\/tr>\r\n% <\/table>\r\n% <\/html>\r\n%\r\n% The authors call this primitive with a centerline and a radius at each end the \r\n% \"cone-sphere\" primitive. \r\n%\r\n% This distance equation is more complex, so I'll precompute some terms to \r\n% help with performance.\r\n%\r\nnsegments = size(segments,1);\r\ninvlen2 = zeros(1,nsegments);\r\ntrange = zeros(nsegments,2);\r\nadj_radii = zeros(nsegments,2);\r\nfor i=1:nsegments\r\n    q1 = verts(segments(i,1),:);\r\n    q2 = verts(segments(i,2),:);\r\n    len2 = dot(q2-q1,q2-q1);\r\n    invlen2(i) = 1\/len2;\r\n\r\n    r1 = radii(segments(i,1));\r\n    r2 = radii(segments(i,2));\r\n    delta = r1-r2;\r\n    s = sqrt(len2 - delta^2);\r\n    \r\n    dl = delta * invlen2(i);\r\n    sl = s * sqrt(invlen2(i));\r\n    \r\n    trange(i,1) = r1 * dl;\r\n    trange(i,2) = 1 + r2 * dl;\r\n    adj_radii(i,1) = r1 * sl;\r\n    adj_radii(i,2) = r2 * sl;\r\nend\r\n\r\n%%\r\n% Now we can repeat our loop with this new distance formula.\r\n%\r\nfor i=1:numel(z)\r\n    p = [x(i),y(i),z(i)];\r\n    \r\n    closest = inf;\r\n    for j=1:size(segments,1)\r\n        q1 = verts(segments(j,1),:);\r\n        q2 = verts(segments(j,2),:);\r\n        r1 = radii(segments(j,1));\r\n        r2 = radii(segments(j,2));\r\n        \r\n        t = -dot(q1-p,q2-q1)*invlen2(j);\r\n        if t<trange(j,1)\r\n            v = q1;\r\n            r = r1;\r\n        elseif t>trange(j,2)\r\n            v = q2;\r\n            r = r2;\r\n        else\r\n            v = q1 + t*(q2-q1);\r\n            adj_t = (t-trange(j,1)) \/ (trange(j,2)-trange(j,1));\r\n            r = adj_radii(j,1) + adj_t*(adj_radii(j,2)-adj_radii(j,1));\r\n        end\r\n        \r\n        closest = min(closest, norm(v-p)-r);\r\n    end\r\n    \r\n    d(i) = closest;\r\nend\r\n\r\n%%\r\n% And then we can use isosurface, just like we did before.\r\n%\r\nisosurface(x,y,z,d,0)\r\n\r\n%%\r\n% See how the surface lines up with the spheres? Now we'll delete them to\r\n% get a better look.\r\n%\r\ndelete(findobj(gca,'Type','line'))\r\ndelete(findobj(gca,'Type','surface'))\r\n\r\n\r\n%%\r\n% Here's another isosurface I made from these 6 edges of a tetrahedron \r\n% using this technique.\r\n%\r\nverts = randn(4,3);\r\nsegments = nchoosek(1:4,2);\r\nradii = 5\/8 * rand(1,4);\r\n\r\n%%\r\n%\r\n% <<tetra_sdf.png>>\r\n%\r\n% That took quite a while to compute, but it would have been awful tricky\r\n% to draw it with the sweep approach. \r\n%\r\n% Whenever you want to wrap a surface around a complex shape, then signed\r\n% distance functions are a technique that you should consider.\r\n%\r\n##### SOURCE END ##### 0886127509d14a78a09e9c9665d5cb81\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img src=\"https:\/\/blogs.mathworks.com\/graphics\/files\/feature_image\/sdf_thumbnail.png\" class=\"img-responsive attachment-post-thumbnail size-post-thumbnail wp-post-image\" alt=\"\" decoding=\"async\" loading=\"lazy\" \/><\/div><p>\r\nRecently I heard from a MATLAB user who was trying to draw tubes along a curve using this blog post I wrote a while back. Unfortunately her curve was a bit more complex than the ones I used in that... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/graphics\/2016\/03\/07\/signed-distance-fields\/\">read more >><\/a><\/p>","protected":false},"author":89,"featured_media":440,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[5],"tags":[],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/posts\/437"}],"collection":[{"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/users\/89"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/comments?post=437"}],"version-history":[{"count":6,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/posts\/437\/revisions"}],"predecessor-version":[{"id":444,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/posts\/437\/revisions\/444"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/media\/440"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/media?parent=437"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/categories?post=437"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/graphics\/wp-json\/wp\/v2\/tags?post=437"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}