<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":1058,"date":"2017-04-02T14:49:21","date_gmt":"2017-04-02T14:49:21","guid":{"rendered":"http:\/\/barradeau.com\/blog\/?p=1058"},"modified":"2017-04-03T12:27:25","modified_gmt":"2017-04-03T12:27:25","slug":"volume-distribution","status":"publish","type":"post","link":"http:\/\/barradeau.com\/blog\/?p=1058","title":{"rendered":"3D volume distribution"},"content":{"rendered":"<p>I was doing some R&amp;D for a project with my <strong><em>BROS<\/em> <\/strong>Frank &amp; David when I came across a problem I had thought\u00a0of for a while but never tackled for real. the\u00a0idea is to distribute (scatter) a set of random 3D points inside a volume, preferably an\u00a0arbitrary mesh.<\/p>\n<p>distributing \u00a0random points in geometric volumes is quite straight forward. if you know the formula of\u00a0a sphere, a cube, a cylinder or any shape, you can create a\u00a0point inside the said shape. also, and much like Constructive Solid Geometry, it&#8217;s fairly easy to use boolean operations (union, subtraction, intersection &amp; XOR) between the shapes and obtain\u00a0a broad variety\u00a0of complex shapes.<\/p>\n<p>if this approach is elegant, probably fast and efficient, it has a big downside ; arbitrary meshes are hard &#8211;\u00a0if not impossible\u00a0&#8211; to model with equations. it is therefore often limited to simple use cases like procedural or fractals shapes.<\/p>\n<p>being a long time 3DS max user, I\u00a0often used the &#8220;Scatter&#8221; modifier\u00a0to distribute point on or inside a mesh. when\u00a0I used a <em>non-manifold<\/em>\u00a0polyhedron &#8211; a &#8220;broken&#8221; mesh, with holes, no <em>inside<\/em> or <em>outside<\/em>\u00a0&#8211; the modifier failed or 3DS crashed. that&#8217;s when\u00a0I decided to try\u00a0to code something.<\/p>\n<p>the algorithm goes as follow:<\/p>\n<ol>\n<li>pick a random point on the bounding volume of the mesh\u00a0and a random direction<\/li>\n<li>shoot a ray at the\u00a0mesh from that origin, in that direction<\/li>\n<li>evaluate the result to keep or discard the intersection points<\/li>\n<\/ol>\n<p>it implies shooting many rays at a mesh\u00a0so\u00a0depending on the mesh&#8217;s complexity, it can take a lot of time\u00a0and anyway it&#8217;s more suited as a mesh &#8220;pre-processor&#8221; than as a\u00a0real time modifier.<\/p>\n<p>step 1 is trivial, for\u00a0step 2,\u00a0THREE.js has a built-in raycaster\u00a0and\u00a0for step 3, we&#8217;ll mostly need to determine if we&#8217;re &#8216;inside&#8217; or\u00a0&#8216;outside&#8217; the mesh.<\/p>\n<p>to determine\u00a0if a point is inside or outside a polyhedron, assuming the mesh is a <em>manifold<\/em>\u00a0(it has an &#8216;inside&#8217; and an &#8216;outside&#8217;), we\u00a0can shoot a ray from that point in any direction and count how many times the ray intersects the polyhedron.<\/p>\n<p>if the number is even, your\u00a0point is outside, if it&#8217;s odd, it&#8217;s inside. a visual explanation:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-1065\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/in_out_test.png\" alt=\"in_out_test\" width=\"800\" height=\"800\" srcset=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/in_out_test.png 800w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/in_out_test-150x150.png 150w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/in_out_test-300x300.png 300w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/in_out_test-768x768.png 768w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><\/p>\n<p>I\u00a0think it also works with actual octopuses\u00a0but I would recommend <strong>not<\/strong> to try.<\/p>\n<p>essentially, we&#8217;ll\u00a0use the number of times a\u00a0ray intersects the mesh to determine wether we&#8217;re inside or outside. here&#8217;s\u00a0my implementation:<\/p>\n<pre class=\"lang:js decode:true\">var raycaster;\/\/THREE.raycaster: used to shoot rays at the mesh\r\nvar o;\/\/ray origin\r\nvar d;\/\/ray direction\r\nvar intersections;\/\/stores the result of the raycasting\r\nvar a;\/\/DOM element tag to download the result ( see particlesToString )\r\n\r\nfunction distribute( mesh, count ) {\r\n\r\n    \/\/this will store the results\r\n    var coords = [];\r\n    var dests = [];\r\n    \/\/temporary vars to store the position and destination\r\n    var p0, p1;\r\n\r\n    \/\/this has an influence as to how the raycasting is performed\r\n    var side = mesh.material.side;\r\n    mesh.material.side = THREE.DoubleSide;\r\n\r\n    \/\/we'll need normals (it's probably done implicitly when we raycast though)\r\n    mesh.geometry.computeFaceNormals();\r\n\r\n    \/\/this is used to distributte the origins of the rays\r\n    mesh.geometry.computeBoundingBox();\r\n    var bbox = mesh.geometry.boundingBox;\r\n\r\n    \/\/ 'inflates' the box by 10% to prevent colinearity\r\n    \/\/ or coplanarity of the origin with the mesh\r\n    bbox.min.multiplyScalar( 1.1 );\r\n    bbox.max.multiplyScalar( 1.1 );\r\n\r\n    \/\/computes the box' size to compute random points\r\n    var size = bbox.max.sub( bbox.min );\r\n\r\n    \/\/to perform raycast\r\n    raycaster = raycaster || new THREE.Raycaster();\r\n    o = o || new THREE.Vector3();\r\n    d = d || new THREE.Vector3();\r\n\r\n    for( var i = 0; i &lt; count; i++ ){\r\n\r\n        \/\/ randomize the rays origin\r\n        o.x = bbox.min.x + Math.random() * size.x;\r\n        o.y = bbox.min.y + Math.random() * size.y;\r\n        o.z = bbox.min.z + Math.random() * size.z;\r\n\r\n        \/\/randomize the ray's direction\r\n        d.x = ( Math.random() - .5 );\r\n        d.y = ( Math.random() - .5 );\r\n        d.z = ( Math.random() - .5 );\r\n        d.normalize();\r\n\r\n        \/\/sets the raycaster\r\n        raycaster.set( o, d );\r\n\r\n        \/\/shoots the ray\r\n        intersections = raycaster.intersectObject( mesh, false );\r\n\r\n        \/\/no result\r\n        if( intersections.length == 0 ){\r\n\r\n            \/\/bail out &amp; continue\r\n            i--;\r\n\r\n        }else{\r\n\r\n            \/\/checks if we meet the conditions:\r\n            \/\/the origin must be outside\r\n            var valid = intersections.length &gt;= 2 &amp;&amp; (intersections.length % 2==0);\r\n            if (valid) {\r\n\r\n                \/\/tests all the intersection pairs\r\n                var additions = -1;\r\n                for( var j = 0; j &lt; intersections.length; j+= 2 ){\r\n\r\n                    \/\/ make sure that the origin -&gt; direction vector have the same\r\n                    \/\/ direction as the normal of the face they hit\r\n\r\n                    \/\/test the direction against the outwards' face's normal\r\n                    var dp0 = d.dot(intersections[ j + 1 ].face.normal) &lt;= 0;\r\n\r\n                    \/\/flips the direction to make it 'look at' the origin\r\n                    d.negate();\r\n\r\n                    \/\/test the direction against the inwards' face's normal\r\n                    var dp1 = d.dot(intersections[ j ].face.normal) &lt;= 0;\r\n\r\n                    \/\/flips the direction again for the next test\r\n                    d.negate();\r\n\r\n                    \/\/ if both vectors pairs head in the same direction\r\n                    \/\/ the point is guarranteed to be inside\r\n                    if( dp0 || dp1){\r\n                        continue;\r\n                    }\r\n\r\n                    \/\/adds the points\r\n                    if( coords.length &lt; count * 3 ){\r\n                        console.log(\"ok\")\r\n                        p0 = intersections[j].point;\r\n                        coords.push( p0.x, p0.y, p0.z);\r\n                        p1 = intersections[j+1].point;\r\n                        dests.push( p1.x, p1.y, p1.z);\r\n                        additions++;\r\n                    }\r\n                }\r\n                \/\/increments the counter by the number of additions\r\n                i += additions;\r\n\r\n            }else{\r\n                \/\/invalid intersection, try again...\r\n                i--;\r\n            }\r\n        }\r\n    }\r\n    \/\/resets the material side\r\n    mesh.material.side = side;\r\n    return {\r\n        pos:coords,\r\n        dst:dests\r\n    };\r\n};<\/pre>\n<p>this is rather self-explanatory (hopefully)\u00a0;\u00a0we get the bounding box of the mesh, &#8216;inflate&#8217; it a bit to avoid some colinearity \/ coplanarity issues, then randomize the ray&#8217;s origin\/direction. performa ray casting onto the mesh then classify the result.<\/p>\n<p>let&#8217;s focus on the &#8216;classify&#8217;\u00a0bit a minute:<\/p>\n<pre class=\"lang:js decode:true\">\/\/the origin must be outside\r\nvar valid = intersections.length &gt;= 2 &amp;&amp; ( intersections.length % 2 == 0 );\r\nif (valid) {\r\n[...]<\/pre>\n<p>we&#8217;re in a valid state if we have: at least 2 intersections and an even number of intersections which means that we hit the mesh at least once and our origin is outside the mesh ( as seen above with that glorious octopus illustration ). we have something like this when valid is true:<br \/>\n<img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-1070\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/valid.png\" alt=\"valid\" width=\"800\" height=\"800\" srcset=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/valid.png 800w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/valid-150x150.png 150w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/valid-300x300.png 300w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/valid-768x768.png 768w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><\/p>\n<p>the next bit takes all pairs of points and retrieves the associated normals like so:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-1069\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/normals.png\" alt=\"normals\" width=\"800\" height=\"800\" srcset=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/normals.png 800w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/normals-150x150.png 150w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/normals-300x300.png 300w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/normals-768x768.png 768w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><\/p>\n<p>and computes the <em>dot product<\/em> between the 2 vectors of the same color.\u00a0if the dot product of 2 vectors is positive, they&#8217;re heading in the same direction, if the dot product is negative, they are heading in opposite directions and if it&#8217;s 0, they are perpendicular. this dreadful 4 liners will do this for all intersections pairs.<\/p>\n<pre class=\"lang:js decode:true\">\/\/test the direction against the outwards' face's normal\r\nvar dp0 = d.dot(intersections[ j + 1 ].face.normal) &lt;= 0;\r\n\r\n\/\/flips the direction to make it 'look at' the origin\r\nd.negate();\r\n\r\n\/\/test the direction against the inwards' face's normal\r\nvar dp1 = d.dot(intersections[ j ].face.normal) &lt;= 0;\r\n\r\n\/\/flips the direction again for the next test\r\nd.negate();\r\n\r\n\/\/ if both vectors pairs head in the same direction\r\n\/\/ the point is guarranteed to be inside\r\nif( dp0 || dp1){\r\n    continue;\r\n}<\/pre>\n<p>this ensures that the &#8220;broken polyhedra&#8221; work too ; if the 2 faces&#8217; normals head in the same direction, the object is broken ; it has no inside or outside. the code above\u00a0checks this\u00a0case and bails out if need be. it\u00a0can also collect more than 2 points per ray which is nice :)<\/p>\n<p>you would call it like:<\/p>\n<pre class=\"lang:js decode:true\">var model = CHTHULU;\/\/a THREE.Mesh\r\nvar count = Math.pow( 2, 12 );\/\/ -&gt; 4096 point pairs\r\n\r\n\/\/call the method and strore the result in a prticles object\r\nvar inside = distribute( model, count );<\/pre>\n<p>the result of the distribute function is a pair of <em>float32Arrays<\/em> containing XYZ triplets for the in\u00a0&amp; \u00a0out positions\u00a0respectively. to place an object &#8216;inside&#8217; the polyhedron, you&#8217;d do something like:<\/p>\n<pre class=\"lang:js decode:true\">\/\/midpoint of the 2 intersection points\r\nvar p = new THREE.Vector3(\r\n\t( inside.pos[ id ].x + inside.dst[ id ].x ) * .5,\r\n\t( inside.pos[ id ].y + inside.dst[ id ].y ) * .5,\r\n\t( inside.pos[ id ].z + inside.dst[ id ].z ) * .5\r\n);<\/pre>\n<p>or more likely, pass <em>inside.dst<\/em> as an attribute and ask the shader to <em>mix<\/em>\u00a0the two:<\/p>\n<pre class=\"lang:js decode:true\">float t = abs( sin( time ) );\r\nvec3 pos = mix( position, dest, t );<\/pre>\n<p>with a value\u00a0<em>t<\/em>\u00a0between 0 &amp; 1 which guarantees that the position will remain inside the\u00a0mesh.<\/p>\n<p>of course we can change the above to &#8216;carve&#8217; the mesh inside a cloud of points and keep only the &#8220;outside&#8221;. :)<\/p>\n<p>HAMMER TIME!<br \/>\nthis is a model with 65K particles floating around (click)<\/p>\n<p><a href=\"http:\/\/www.barradeau.com\/2017\/volume\/suzanne.html\" target=\"_blank\"><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter wp-image-1102 size-full\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/suzanne.jpg\" alt=\"suzanne\" width=\"1080\" height=\"1080\" srcset=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/suzanne.jpg 1080w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/suzanne-150x150.jpg 150w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/suzanne-300x300.jpg 300w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/suzanne-768x768.jpg 768w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/suzanne-1024x1024.jpg 1024w\" sizes=\"(max-width: 1080px) 100vw, 1080px\" \/><\/a><\/p>\n<p>if you notice some particles floating around, it&#8217;s because the distribution mesh is the mother of all non-manifold polyhedra\u00a0^^<br \/>\n<img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-1103\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/suzanne_distribution.jpg\" alt=\"suzanne_distribution\" width=\"1080\" height=\"1080\" srcset=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/suzanne_distribution.jpg 1080w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/suzanne_distribution-150x150.jpg 150w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/suzanne_distribution-300x300.jpg 300w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/suzanne_distribution-768x768.jpg 768w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/suzanne_distribution-1024x1024.jpg 1024w\" sizes=\"(max-width: 1080px) 100vw, 1080px\" \/><br \/>\nthis is again 65K particles distributed inside a mesh. notice how it retained the fine details (on the legs for instance)<\/p>\n<p><a href=\"http:\/\/www.barradeau.com\/2017\/volume\/spider.html\" target=\"_blank\"><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter wp-image-1101 size-full\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/spider.jpg\" alt=\"spider\" width=\"1080\" height=\"1080\" srcset=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/spider.jpg 1080w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/spider-150x150.jpg 150w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/spider-300x300.jpg 300w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/spider-768x768.jpg 768w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/spider-1024x1024.jpg 1024w\" sizes=\"(max-width: 1080px) 100vw, 1080px\" \/><\/a><\/p>\n<p>&nbsp;<\/p>\n<p>still 65K particles, this time rendered as line segments<\/p>\n<p><a href=\"http:\/\/www.barradeau.com\/2017\/volume\/plane.html\" target=\"_blank\"><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter wp-image-1100 size-full\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/plane.jpg\" alt=\"plane\" width=\"1080\" height=\"1080\" srcset=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/plane.jpg 1080w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/plane-150x150.jpg 150w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/plane-300x300.jpg 300w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/plane-768x768.jpg 768w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/plane-1024x1024.jpg 1024w\" sizes=\"(max-width: 1080px) 100vw, 1080px\" \/><\/a><\/p>\n<p>finally a splendid rendition of 2K particles as line sets, using <a href=\"https:\/\/www.clicktorelease.com\/code\/THREE.MeshLine\/demo\/index.html\" target=\"_blank\">the Spite&#8217;s MeshLines<\/a>. my code is ugly as fuck but hey&#8230;<\/p>\n<p><a href=\"http:\/\/www.barradeau.com\/2017\/volume\/dwarf.html\" target=\"_blank\"><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter wp-image-1099 size-full\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/dwarf.jpg\" alt=\"dwarf\" width=\"1080\" height=\"1080\" srcset=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/dwarf.jpg 1080w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/dwarf-150x150.jpg 150w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/dwarf-300x300.jpg 300w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/dwarf-768x768.jpg 768w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/dwarf-1024x1024.jpg 1024w\" sizes=\"(max-width: 1080px) 100vw, 1080px\" \/><\/a><\/p>\n<p>and that&#8217;s it! well, I gues you get the idea ^^<\/p>\n<p>there&#8217;s even a <a href=\"https:\/\/github.com\/nicoptere\/volume_distribution\">github repo for that<\/a><\/p>\n<p>enjoy :)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I was doing some R&amp;D for a project with my BROS Frank &amp; David when I came across a problem I had thought\u00a0of for a while but never tackled for real. the\u00a0idea is to distribute (scatter) a set of random 3D points inside a volume, preferably an\u00a0arbitrary mesh. distributing \u00a0random points in geometric volumes is &#8230; <span class=\"more\"><a class=\"more-link\" href=\"http:\/\/barradeau.com\/blog\/?p=1058\">[Read more&#8230;]<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":1093,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"sharing_disabled":false,"spay_email":"","jetpack_publicize_message":""},"categories":[3],"tags":[7,2],"jetpack_featured_media_url":"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/04\/2017-04-03-11.png","jetpack_publicize_connections":[],"jetpack_shortlink":"https:\/\/wp.me\/p4oXhx-h4","_links":{"self":[{"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1058"}],"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=1058"}],"version-history":[{"count":31,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1058\/revisions"}],"predecessor-version":[{"id":1060,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1058\/revisions\/1060"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/media\/1093"}],"wp:attachment":[{"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1058"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1058"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1058"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}