<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":194,"date":"2014-05-10T12:00:15","date_gmt":"2014-05-10T12:00:15","guid":{"rendered":"http:\/\/barradeau.com\/blog\/?p=194"},"modified":"2019-06-28T19:28:44","modified_gmt":"2019-06-28T19:28:44","slug":"hide-behind","status":"publish","type":"post","link":"http:\/\/barradeau.com\/blog\/?p=194","title":{"rendered":"hidebehind"},"content":{"rendered":"<p>a month ago, for no specific reason, I decided to implement a 2D top-down visibility algorithm.\u00a0I always found the output beautiful both graphically &amp; conceptually yet, as I don&#8217;t make games, I never tacckled it.<\/p>\n<p>visibility algorithms are often used to simulate lights ; to determine which areas of a space are enlightened and which lie in the shadow but they can also be used to power an AI ; make an agent aware of its whereabouts.<\/p>\n<p>if you&#8217;re after a robust solution or just curious, I&#8217;d suggest you open one of those links:<\/p>\n<ul>\n<li><a href=\"http:\/\/simblob.blogspot.fr\/2012\/07\/2d-visibility.html\" target=\"_blank\" rel=\"noopener\">Amit Patel&#8217;s articles and resources<\/a><\/li>\n<li><a href=\"http:\/\/ncase.me\/sight-and-light\/\" target=\"_blank\" rel=\"noopener\">this article by Nicky Case<\/a>\u00a0because it looks good<\/li>\n<li>this js implementation by Byron Knoll\u00a0<a href=\"https:\/\/code.google.com\/p\/visibility-polygon-js\/\" target=\"_blank\" rel=\"noopener\">visibility-polygon.js<\/a><\/li>\n<li>the <a href=\"http:\/\/gamemechanicexplorer.com\/#raycasting-2\" target=\"_blank\" rel=\"noopener\">Game Mechanic Explorer<\/a> addressed the topic\u00a0in a clear &amp; concise manner<\/li>\n<\/ul>\n<p>also, if you only need the graphic data, you might be interested in GPU solutions ; <a href=\"http:\/\/willyg302.wordpress.com\/2012\/11\/04\/2d-visibility\/\" target=\"_blank\" rel=\"noopener\">this article explains the shader based approach<\/a>. and if you&#8217;re more into the <em>game-making<\/em> thing, the <em><a href=\"http:\/\/sadumbrella.blogspot.se\/2010\/11\/beamcasting.html\" target=\"_blank\" rel=\"noopener\">beamcasting<\/a><\/em>\u00a0approach\u00a0seems very efficient, especially as the triangular structure offers many advantages to handle the game&#8217;s logic.<\/p>\n<p>I know you&#8217;ll find something that suits your needs along with valuable insights.<\/p>\n<blockquote><p>the algorithm<\/p><\/blockquote>\n<p>as mentioned above, I&#8217;m only interested in the algorithm itself so\u00a0this article gathers a couple of thoughts on the topic,\u00a0some sample animations and some JavaScript code.<\/p>\n<p>I wanted to keep the algorithm <em>topology-agnostic ;<\/em> it should work on <em>any<\/em> series of segments (edges) &amp; make as few assumptions about the data structure as possible. in real life, one would probably adapt what follows to perform faster ; for instance re-using the edges instead of rebuilding them each time might be a good start.<\/p>\n<p>I kept the examples intentionally unoptimized here, a naive pseudo-code\u00a0goes as follow:<\/p>\n<ol>\n<li><span style=\"line-height: 1.5;\">create edges <\/span><\/li>\n<li><span style=\"line-height: 1.5;\">store them in a an array<\/span><\/li>\n<li><span style=\"line-height: 1.5;\">compute &amp; store the edge&#8217;s start &amp;\u00a0end angles<\/span><\/li>\n<li>sort the angles in ascending order<\/li>\n<li>for each angle of the angles&#8217; list<\/li>\n<li>cast a ray from the light source at this angle<\/li>\n<li>with all the edges<\/li>\n<li><span style=\"line-height: 1.5;\">\u00a0 \u00a0 if the edge contains the angle<\/span><\/li>\n<li><span style=\"line-height: 1.5;\">\u00a0 \u00a0 compute &amp; store the closest intersection of this ray with the edge<\/span><\/li>\n<li>push the closest intersection point to the solution<\/li>\n<\/ol>\n<p>which translated into (a somewhat obfuscated) code gives:<\/p>\n<pre class=\"width-set:true width-mode:2 width:100 width-unit:1 lang:js decode:true\">function compute( points, light )\r\n{\r\n\t\/\/local variables\r\n\tvar solution = [], edge = null, edges = [], angles = [];\r\n\tvar API2 = 0, A = 0, B = 0, C = 0, closest = null;\r\n\r\n\t\/\/start\r\n\tpoints.forEach( function( p0, i, a )\r\n\t{\r\n\t\t\/\/ 1 - create edges\r\n\t\tedge = new Edge( p0, a[ ( i+1 ) % a.length ], light );\r\n\r\n\t\t\/\/ 2 - store them in an array\r\n\t\tedges.push( edge );\r\n\r\n\t\t\/\/ 3 - compute &amp; store the edge\u2019s angles\r\n\t\t\/\/ this is done inside the Edge object\r\n\t\t\/\/ and stored as edge.start \/ edge.end\r\n\t\tif( angles.indexOf( edge.start ) == -1 )\tangles.push( edge.start );\r\n\t\tif( angles.indexOf( edge.end ) == -1 )\t\tangles.push( edge.end );\r\n\r\n\t} );\r\n\r\n\t\/\/ 4 - sort the angles in ascending order\r\n\tangles.sort( function(a,b){ return a - b; } );\r\n\r\n\t\/\/ 5 - for each angle of the angles\u2019 list\r\n\tangles.forEach( function( angle )\r\n\t{\r\n\t\tif( angle &gt;= angles[0] + PI2 ) return;\/\/we've done a complete 360\u00b0 &gt; quit\r\n\r\n\t\t\/\/ 6 - cast a ray from the light source at that angle\r\n\t\tAPI2 = angle + PI2;\r\n\t\tA = -Math.cos( angle );\r\n\t\tB = Math.sin( angle );\r\n\t\tC = light.x * ( light.y + B ) - light.y * ( light.x - A );\r\n\r\n\t\t\/\/ 7 - with all the edges\r\n\t\tclosest = null;\r\n\t\tedges.forEach( function( edge )\r\n\t\t{\r\n\r\n\t\t\t\/\/ 8 - if the edge contains the angle\r\n\t\t\tif( (   angle &lt; edge.start ||  angle &gt; edge.end )\r\n\t\t\t&amp;&amp;  (   API2 &lt; edge.start ||  API2 &gt; edge.end ))return;\r\n\r\n\t\t\t\/\/ 9 - compute the intersection of this ray with this edge\r\n\t\t\tvar pp = project( light, A,B,C,edge.D,edge.E,edge.F);\r\n\r\n\t\t\t\/\/ &amp; store this intersection point if it is closer to the light source\r\n\t\t\tif( closest == null )\tclosest = pp;\r\n\t\t\telse if( closest.distance &gt; pp.distance )\tclosest = pp;\r\n\r\n\t\t} );\r\n\t\t\/\/ 10 - push the closest intersection point to the solution\r\n\t\tif( closest != null )\tsolution.push( closest );\r\n\t} );\r\n\treturn solution;\r\n}<\/pre>\n<p>now if you suspect some trickery here, you&#8217;re\u00a0right. this\u00a0snippet alone won&#8217;t do much for\u00a0it\u00a0needs 2 data-holders (Point &amp; Edge) along with some methods (angle, distance, project) to work.<\/p>\n<p>the Point\u00a0object simply stores a X, Y coordinates &amp;\u00a0a distance value,\u00a0now the Edge object is slightly more tricky as it needs to ensure the points\u00a0are\u00a0properly oriented (CW).<\/p>\n<pre class=\"theme:tomorrow-night width-set:true width-mode:2 width:100 width-unit:1 lang:js decode:true\">\/**\r\n * Edge is a dataHolder that keeps references to 2 points\r\n * @param p0 start of the edge\r\n * @param p1 end of the edge\r\n * @param light the light source\r\n *\/\r\nvar Edge = function( p0, p1, light )\r\n{\r\n\t\/\/compute:\r\n\r\n\t\/\/ A: the angle from the light source to point 0\r\n\tvar a = ( angle( light, p0 ) + PI  );\r\n\r\n\t\/\/ B: the angle from the light source to point 1\r\n\tvar b = ( angle( light, p1 ) + PI  );\r\n\r\n\t\/\/ span: the angle difference between A &amp; B\r\n\tvar span = Math.atan2( Math.sin( b - a ), Math.cos( b - a ) );\r\n\r\n\t\/\/the winding of the edge: its orientation\r\n\t\/\/ if A + span is smaller than A, the edge is oriented Counter ClockWise\r\n\tvar CW = a &lt; a + span;\r\n\r\n\t\/\/if the angle is sorted ClockWise, nothing to do\r\n\tif( CW )\r\n\t{\r\n\t\tthis.p0 = p0;\r\n\t\tthis.p1 = p1;\r\n\t\tthis.start  = a;\r\n\t\tthis.end    = a + span;\r\n\t}\r\n\t\/\/otherwise, flip the edge\r\n\telse\r\n\t{\r\n\t\tthis.p0 = p1;\r\n\t\tthis.p1 = p0;\r\n\t\tthis.start  = b;\r\n\t\tthis.end    = b - span;\r\n\t}\r\n\r\n\t\/\/ &amp; finally these values are precomputed to make projections faster\r\n\tthis.D = this.p0.x - this.p1.x;\r\n\tthis.E = this.p1.y - this.p0.y;\r\n\tthis.F = this.p0.x * this.p1.y - this.p0.y * this.p1.x;\r\n\r\n};<\/pre>\n<p>the orientation is important because the angles are sorted in ascending order so the <em>scanline<\/em> rotates clockwise\u00a0&amp; if an edge is oriented CounterClockwise (CCW), the computation will fail.<\/p>\n<p>angle &amp; distance methods are fairly\u00a0easy to find, so here\u00a0is the\u00a0projection method:<\/p>\n<pre class=\"lang:js decode:true\">\/**\r\n * projection method\r\n * @returns {Point}\r\n *\/\r\nfunction project( light, A, B, C, D, E, F )\r\n{\r\n\r\n\tvar denominator = 1 \/ ( B * D - E * A );\r\n\r\n\t\/\/this is the projected point\r\n\r\n\tvar out = new Point();\r\n\tout.x = ( C * D - F * A ) * denominator;\r\n\tout.y = ( F * B - C * E ) * denominator;\r\n\tout.distance = squareDistance( light, out );\r\n\r\n\treturn out;\r\n\r\n}<\/pre>\n<p>I used <a href=\"http:\/\/actionsnippet.com\/?p=956\" target=\"_blank\" rel=\"noopener\">the projection method found here<\/a>\u00a0&amp; precomputed most of the values: A,B,C represent the ray &amp; are set on step 6 of the algorithm, D, E,\u00a0F represent the Edge and are computed during the edge&#8217;s instantiation (see Edge Object). note that we can use Square Distance (faster) instead of the actual distance as we only need to know if a value is smaller than another. <span style=\"line-height: 1.5;\">below is a little demo staging a caribou<\/span><\/p>\n<div id=\"naive\" style=\"width: 100%; height: 540px;\"><\/div>\n<p><a href=\"http:\/\/www.barradeau.com\/js\/hidebehind\/naive\/naive_demo.html\" target=\"_blank\" rel=\"noopener\">naive approach fullscreen<\/a><br \/>\nit\u00a0works nicely when the light is inside the shape, yet some major flaws happen<\/p>\n<ol>\n<li>all edges are computed which is not a good idea<\/li>\n<li>the algorithm does not make a difference between the inside \/ outside<\/li>\n<li>from &#8220;outside&#8221;, the visibility polygon is not closed properly<\/li>\n<li>some mid-edge projections\u00a0are lost as the scanline moves forward<\/li>\n<\/ol>\n<p>in other words, it sucks.<\/p>\n<blockquote><p>which\u00a0edges?<\/p><\/blockquote>\n<p><span style=\"line-height: 1.5;\">the first step is to determine which edges to process.\u00a0<\/span><span style=\"line-height: 1.5;\">we need to process an edge only if it is <\/span><em style=\"line-height: 1.5;\">potentially<\/em><span style=\"line-height: 1.5;\"> visible from the light source.\u00a0this is achieved by computing the dot product of the edge&#8217;s normal vector &amp; the vector going from the light source towards one of the edge&#8217;s vertices.\u00a0if the result is negative, the vectors are pointing in opposite directions meaning the edge is &#8220;facing&#8221; the light source &amp; needs to be processed.<\/span><\/p>\n<p>this is the code you need:<\/p>\n<pre class=\"decode-attributes:false lang:js decode:true prettyprint linenums\">function normalize( point )\r\n{\r\n\tvar length = Math.sqrt( point.x * point.x + point.y * point.y );\r\n\tpoint.x \/= length;\r\n\tpoint.y \/= length;\r\n\treturn point;\r\n}\r\n\r\nfunction normal( p0, p1 )\r\n{\r\n\treturn new Point( -( p0.y - p1.y ), ( p0.x - p1.x ) );\r\n}\r\n\r\nfunction dotProduct( p0, p1 )\r\n{\r\n\treturn ( p0.x * p1.x + p0.y * p1.y );\r\n}\r\n\r\n\/\/ p0 \/ p1 are the two edge's ends\r\n\/\/ light is the light source\r\n\/\/ dir is a temporary vector from one end of the edge\r\n\/\/ to the light source\r\n\r\nvar dir = new Point();\r\ndir.x = p0.x - light.x;\r\ndir.y = p0.y - light.y;\r\n\r\nvar norm = normalize( normal( p0, p1 ) );\r\n\r\n\/\/edge is visible\r\nif( dotProduct( norm, dir ) &lt; 0 )\r\n{\r\n\t\/\/visible edge code here\r\n}<\/pre>\n<p>statistically, this removes 50% of the\u00a0computations. note that I said <em>as few assumptions as possible concerning the edges<\/em> but this is a strong one ; I assume that edges are opaque on both sides. by using a more sophisticated edge data structure, we could specify which side is visible or have one-sided walls ( like a stainless mirror ).<\/p>\n<p>a nice (&amp; necessary) addition is the <em style=\"line-height: 1.5;\">visibility range<\/em><span style=\"line-height: 1.5;\"> ; beyond a given distance from the light source, we don&#8217;t have to compute anything.<br \/>\nby adding a simple distance check to the previous test, we can decimate the potentially visible edges&#8217; list further.<\/span><\/p>\n<pre class=\"decode-attributes:false lang:js decode:true prettyprint linenums\">function squareDistance( a,b )\r\n{\r\n\treturn ( a.x - b.x ) * ( a.x - b.x ) + ( a.y - b.y ) * ( a.y - b.y );\r\n}\r\n[...]\r\n\/\/the light's range\r\nvar range = 200;\r\n\r\n\/\/using square distances saves some square roots computations\r\nvar square_range = range * range;\r\nif(\tsquareDistance( p0, light ) &gt; square_range\r\n&amp;&amp;\tsquareDistance( p1, light ) &gt; square_range )\r\n{\r\n\t\/\/ both ends of this edge are too far, no need to process\r\n\tcontinue;\r\n}<\/pre>\n<p>overmore, if we consider the edges as walls &amp;\u00a0we know that we can have closed shapes, an interesting test can be performed before actually processing anything to determine whether or not a polygon\u00a0<em>contains<\/em>\u00a0the light source &amp; eventually stop the computation there. in my previous article, I&#8217;ve shown a method that does this.<\/p>\n<p>here&#8217;s a live demo:<\/p>\n<div id=\"potential_visibility\" style=\"width: 100%; height: 720px;\"><\/div>\n<p><a href=\"https:\/\/www.barradeau.com\/js\/hidebehind\/potential_visibility\/potential_visibility.html\" target=\"_blank\" rel=\"noopener\">potential visibility fullscreen<\/a><br \/>\nmove around to see the potentially visible edges (blue) <strong>click &amp; drag<\/strong> to use the visibility range, when the shape turns grey, it means that the shape\u00a0contains the light so nothing is computed.<\/p>\n<p>when clicked, the pink edges are out of range\u00a0&amp;\u00a0don&#8217;t need to be processed which leaves us only with the blue edges. I&#8217;ve displayed the\u00a0<em>decimation<\/em> ratio that indicates how\u00a0much of the original edges\u00a0are valid and need to be processed.<\/p>\n<blockquote><p>closing the visibility polygon<\/p><\/blockquote>\n<p>the visibility range is a nice addition yet it induces\u00a0a greater problem ; how to close the Visibility Polygon.<\/p>\n<p>the\u00a0simple solution is to\u00a0enclose all\u00a0the polygons inside a bounding polygon (usually a quadrilateral) &amp;\u00a0use\u00a0infinite rays\u00a0; this way we are certain that the rays\u00a0will\u00a0hit something&#8230; some time.<\/p>\n<p>in the next example, I&#8217;ve added a series of 4 (yellow), 8 (red) &amp; 60(blue) edges forming a circle around the light.<br \/>\nI also perform the tests described above so when running, only the visible edges within the light range are being processed. this should run much faster than the naive approach (roughly twice).<\/p>\n<p>here&#8217;s the result:<\/p>\n<div id=\"educated\" style=\"width: 100%; height: 540px;\"><\/div>\n<p><a href=\"http:\/\/www.barradeau.com\/js\/hidebehind\/educated\/educated.html\" target=\"_blank\" rel=\"noopener\">educated approach fullscreen<\/a><\/p>\n<p>before starting the animation, you should see the outlines of the visible polygons.<\/p>\n<p>even though it works, this is not a very elegant solution ; we have to create extra geometry or we&#8217;ll lose the benefit of a visibility range. in the case of AI, the agents would have an infinite line of sight, in the case of graphics, the light would flood offscreen which is not the expected result. overmore, this approach doesn&#8217;t solve the mid-edge projection issue &amp; the precision of the boundaries depends on the number of edges added (the edge count is discrete as opposed to continuous arcs).<\/p>\n<p>I had the intuition that the problem\u00a0could boil\u00a0down to determining when a ray would <em>switch<\/em>\u00a0between distances\u00a0from the light source. indeed if we convert all cartesian coordinates to polar coordinates, we get the 2 values we need to solve the algorithm:<\/p>\n<ul>\n<li>the angles at which we should project rays<\/li>\n<li>the distances from each point to the light source<\/li>\n<\/ul>\n<p>as it&#8217;s not a trivial concept to understand, here&#8217;s a demo of polarization<\/p>\n<div id=\"polar\" style=\"width: 100%; height: 720px;\"><\/div>\n<p><a href=\"http:\/\/www.barradeau.com\/js\/hidebehind\/polar\/polar.html\" target=\"_blank\" rel=\"noopener\">polar fullscreen<\/a><br \/>\nmove your mouse around, you&#8217;ll see the arcs described by the edges&#8217; start and end angles projected on the visibility range around the mouse.<\/p>\n<p>the important bit here is what happens at the bottom where all the angles are represented as vertical lines and all the edges as segments.<br \/>\nbefore going further, computing the edge&#8217;s projection against the visibility circle allows for a nifty trick ; instead of computing the visible polygon, what if we compute the invisible polygon?<\/p>\n<p>instead of casting light, why not casting shadows?<\/p>\n<p>here&#8217;s how the code would go:<\/p>\n<pre class=\"decode-attributes:false lang:js decode:true prettyprint linenums\">ctx.fillStyle = \"rgba(0,0,0,.15);\/\/15% opacity\r\nctx.beginPath();\r\nvar dir = new Point();\r\nfor( var i =0; i &lt; points.length; i++ )\r\n{\r\n    var p0 = points[ i ];\r\n    var p1 = points[ ( i+1 ) % points.length ];\r\n\r\n    \/\/same old visibility test to spare computations\r\n    dir.x = p0.x - light.x;\r\n    dir.y = p0.y - light.y;\r\n    var norm = normalize( normal( p0, p1 ) );\r\n\r\n    \/\/edge is visible\r\n    if( dotProduct( norm, dir ) &lt;= 0 )\r\n    {\r\n        edge = new Edge( p0, p1, light );\r\n\r\n        \/\/this is the projection of the edge's ends on the visibility range\r\n\r\n        var pp0 = new Point(    light.x + Math.cos( edge.start ) * range,\r\n                                light.y + Math.sin( edge.start ) * range );\r\n\r\n        var pp1 = new Point(    light.x + Math.cos( edge.end   ) * range,\r\n                                light.y + Math.sin( edge.end   ) * range );\r\n\r\n        ctx.moveTo( edge.p0.x, edge.p0.y);\r\n        ctx.lineTo( edge.p1.x, edge.p1.y);\r\n        ctx.lineTo( pp1.x, pp1.y );\r\n        ctx.lineTo( pp0.x, pp0.y );\r\n    }\r\n}\r\nctx.fill();\r\n<\/pre>\n<p>&amp; this would be the result (click maintain &amp; drag):<\/p>\n<div id=\"shadows\" style=\"width: 100%; height: 540px;\"><\/div>\n<p><a href=\"http:\/\/www.barradeau.com\/js\/hidebehind\/shadow_casting\/shadow_casting.html\" target=\"_blank\" rel=\"noopener\">shadow casting fullscreen<\/a><\/p>\n<p>note: you&#8217;ll need a big <em>range<\/em> for it to work as it is supposed to overflow the screen.<br \/>\nif the light source is too close from the edge, it will fail. as the edge&#8217;s end are projected on a huge circle, the closer you get to a wall, the closer the arc&#8217;s chord will be to the back of the wall. it&#8217;s still a fast and easy solution for graphics rendering of shadows.<\/p>\n<p>back to the polar view, we saw it&#8217;s possible to consider the edges&#8217; projections as angle-distance rather than X Y coordinates (polar coordinates vs cartesian coordinates).<\/p>\n<p>to solve the problem in the polar space, we have to change the approach a bit. the scanline would move from left to right &amp; the light would cast a ray from the top towards the bottom. the picture below sums up the cases we need to solve.<br \/>\n<img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-328\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2014\/04\/polarized.png\" alt=\"polarized\" width=\"660\" height=\"582\" srcset=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2014\/04\/polarized.png 660w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2014\/04\/polarized-300x264.png 300w\" sizes=\"(max-width: 660px) 100vw, 660px\" \/><\/p>\n<p>we&#8217;re after the thick blue line.<\/p>\n<p>the code remains the same as before but instead of systematically picking the closest intersection, we&#8217;ll have to perform some extra tests depending on the nature of the point being processed &amp; for this we need&#8230;<\/p>\n<blockquote><p>a less stupid data structure<\/p><\/blockquote>\n<p>a less stupid Point<\/p>\n<pre class=\"lang:js decode:true \">var EdgePoint = function( x, y, angle, distance, light )\r\n{\r\n\r\n\tthis.x          = x||0;\r\n\tthis.y          = y||0;\r\n\tthis.angle      = angle||0;\r\n\tthis.distance   = distance||0;\r\n\r\n\tthis.A          = -Math.cos( this.angle );\r\n\tthis.B          = Math.sin( this.angle );\r\n\tthis.C          = light.x * ( light.y + this.B ) - light.y * ( light.x - this.A );\r\n\r\n\tthis.edge       = null;\r\n\tthis.isChained  = false;\r\n\tthis.isArc      = false;\r\n\r\n};\r\n<\/pre>\n<p>now the point is aware of the edge it belongs to, stores its angle and knows if it belongs to a chain or if it is the end of an edge. in addition, it computes the values (ABC) required to perform the projection test &amp; can be flagged as an arc (see render method below).<\/p>\n<p>a less stupid edge<\/p>\n<pre class=\"lang:js decode:true \">var Edge = function()\r\n{\r\n\r\n\tthis.p0     = null;\r\n\tthis.p1     = null;\r\n\tthis.a0     = 0;\r\n\tthis.a1     = 0;\r\n\r\n\tthis.init = function( p0, a0, d0, p1, a1, d1, light )\r\n\t{\r\n\r\n\t\ta0 = ( a0 + PI2 );\r\n\t\ta1 = ( a1 + PI2 );\r\n\r\n\t\tthis.p0 = new EdgePoint( p0.x, p0.y, a0, d0, light );\r\n\t\tthis.a0 = a0;\r\n\t\tthis.p1 = new EdgePoint( p1.x, p1.y, a1, d1, light );\r\n\t\tthis.a1 = a1;\r\n\r\n\t\tthis.D = this.p0.x - this.p1.x;\r\n\t\tthis.E = this.p1.y - this.p0.y;\r\n\t\tthis.F = this.p0.x * this.p1.y - this.p0.y * this.p1.x;\r\n\r\n\t\tthis.p0.edge = this;\r\n\t\tthis.p1.edge = this;\r\n\r\n\t};\r\n\r\n};\r\n<\/pre>\n<p>it stores the two points, the two angles &amp; precomutes the projection value D,E,F.<\/p>\n<p>another thing we have to determine is whether or not a point is <em>occluded<\/em> ; indeed if a point is completely hidden behind an edge, it cannot belong to the solution.<br \/>\nduring the projection loop, I&#8217;ve added an <em>occluded<\/em> boolean that is set to false for each point &amp; switches to true if the point&#8217;s projection is closer to the light source than the point itself (those are the red crosses the picture above).<\/p>\n<p>with this less stupid data structure, we can refine the tests after finding the closest point:<\/p>\n<pre class=\"lang:js decode:true \">if( !occluded )\r\n{\r\n\t\/\/if the point doesn't belong to a chain\r\n\tif( !p.isChained )\r\n\t{\r\n\t\t\/\/ and was not projected on any other segment\r\n\t\tif( closest == null )\r\n\t\t{\r\n\t\t\t\/\/ it belongs to the visibility range\r\n\t\t\t\/\/we have to create this point\r\n\t\t\tclosest             = polarToCartesian( p.angle, range, light );\r\n\t\t\tclosest.angle       = A;\r\n\t\t\t\/\/and flag it as an arc\r\n\t\t\tclosest.isArc       = true;\r\n\t\t}\r\n\t\tif( p == p.edge.p0 )\/\/ if p is the start point of the edge\r\n\t\t{\r\n\t\t\tsolution.push( closest, p );\r\n\t\t}\r\n\t\telse \/\/ other wise p is the end point\r\n\t\t{\r\n\t\t\tsolution.push( p, closest );\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\t\/\/the point belongs to a chain, add it\r\n\t\tsolution.push( p );\r\n\t}\r\n}<\/pre>\n<p>when a point belongs to the visibility range, we need to create a point using the <em>polarToCartesian()<\/em> method. no black magic here either:<\/p>\n<pre class=\"lang:js decode:true \">function polarToCartesian( angle, distance, origin )\r\n{\r\n\tvar out = new Point();\r\n\tout.x = origin.x + Math.cos( angle ) * distance;\r\n\tout.y = origin.y + Math.sin( angle ) * distance;\r\n\treturn out;\r\n}<\/pre>\n<p>which gives us this glorious demo:<\/p>\n<div id=\"hyperion\" style=\"width: 100%; height: 720px;\"><\/div>\n<p><a title=\"ye glorious demo\" href=\"http:\/\/www.barradeau.com\/js\/hidebehind\/hyperion\/hyperion.html\" target=\"_blank\" rel=\"noopener\">behold YE glorious fullscreen<\/a> &#8211; <a title=\"source\" href=\"http:\/\/www.barradeau.com\/js\/hidebehind\/hyperion\/hyperion_source.js\" target=\"_blank\" rel=\"noopener\">here&#8217;s the source code<\/a><br \/>\nthe source code is not the cleanest you can dream of but at least it is standalone.<\/p>\n<p>another thing is that to properly close the the visibility polygon, we need to draw arcs. that&#8217;s where the <em>EdgePoint.<strong>isArc<\/strong><\/em> comes to the rescue.<br \/>\nthe arcs are always drawn between 2 points of the solution so the rendering method only needs to know when 2 <em>consecutive<\/em> points of the solution have their <strong>.isArc<\/strong> flag set to true &amp; draw a sector (a.k.a. <em>pie slice<\/em>) otherwise draw a triangle.<\/p>\n<pre class=\"lang:js decode:true \">var solution = compute( points, light, range );\r\ncontext.beginPath();\r\nfor( i = 0; i &lt; solution.length; i++ )\r\n{\r\n\tcontext.moveTo( light.x, light.y );\r\n\r\n\tp = solution[ i ];\r\n\tn = solution[ ( i+1 ) % solution.length ];\r\n\tif( p.isArc &amp;&amp; n.isArc )\r\n\t{\r\n\t\tcontext.arc( light.x, light.y, range, p.angle, n.angle, false );\r\n\t}\r\n\telse\r\n\t{\r\n\t\tcontext.lineTo( p.x, p.y );\r\n\t\tcontext.lineTo( n.x, n.y );\r\n\t}\r\n\tcontext.lineTo( light.x, light.y );\r\n\r\n}\r\ncontext.closePath();\r\ncontext.fill();<\/pre>\n<p>this takes advantage of the way the Canvas API works : beginPath() opens a buffer of mixed instructions until the closePath() or a draw method is called. as our solution is guaranteed to be convex &amp; to have a CW winding, we can iterate on the solution&#8217;s points linearly.<\/p>\n<p>it&#8217;s pretty similar to having created the circular frame around the light with the notable difference that it doesn&#8217;t create extra geometry. unfortunately there are still some cases where it breaks ; the arcs are not drawn properly so the safest is still to use a bounding polygon.<\/p>\n<blockquote><p>penumbra<\/p><\/blockquote>\n<p>I tried to implement a <em>penumbra<\/em> (soft shadows) <a href=\"http:\/\/archive.gamedev.net\/archive\/reference\/programming\/features\/2dsoftshadow\/default.html\" target=\"_blank\" rel=\"noopener\">as described in this article<\/a> but failed miserably.<br \/>\nthe plan was simple, as we know when we render an arc, we should be able to draw an area that simulates the light fading away. to do this &#8211; as the shading is not linear &#8211; we need a texture like the one below:<br \/>\n<img data-attachment-id=\"328\" data-permalink=\"http:\/\/barradeau.com\/blog\/?attachment_id=328\" data-orig-file=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2014\/04\/polarized.png\" data-orig-size=\"660,582\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}\" data-image-title=\"polarized\" data-image-description=\"\" data-medium-file=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2014\/04\/polarized-300x264.png\" data-large-file=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2014\/04\/polarized.png\" decoding=\"async\" id=\"texture\" class=\"aligncenter size-full wp-image-328\" style=\"display: visible;\" src=\"http:\/\/www.barradeau.com\/js\/hidebehind\/soft_shadows\/soft_shadow_map.png\" alt=\"\" \/><br \/>\n(in photoshop, it&#8217;s a transparent &#8220;angle gradient&#8221; with a very small (&lt;2%) opaque black span at both ends) so whenever we hit an arc, we translate the canvas at the location of the point before the arc point, rotate the canvas to orient it along this edge. then we set the texture as fill pattern &amp; draw an arc of 45\u00b0 (= PI \/ 4). the code:<\/p>\n<pre class=\"lang:js decode:true \">var pid = 0,\r\n\tnid = 0,\r\n\tprevious = null,\r\n\tnext = null,\r\n\tdist = 0;\r\n\r\nfor( i = 0; i &lt; solution.length; i++ )\r\n{\r\n\tp = solution[ i ];\r\n\tn = solution[ ( i+1 ) % solution.length ];\r\n\r\n\tif( p.isArc &amp;&amp; n.isArc )\r\n\t{\r\n\t\r\n\t\t\/\/left shadow\r\n\t\tpid         = ( i-1 ) &lt; 0 ? solution.length - 1 : ( i - 1 );\r\n\t\tprevious    = solution[ pid ];\r\n\t\r\n\t\tdist = range - previous.distance;\r\n\t\r\n\t\tcontext.save();\r\n\t\r\n\t\t\tcontext.translate( previous.x, previous.y );\r\n\t\t\tcontext.rotate( p.angle - PI \/ 4 );\r\n\t\r\n\t\t\tcontext.beginPath();\r\n\t\t\tcontext.moveTo( 0,0 );\r\n\t\t\tcontext.arc( 0,0, dist, 0, PI \/ 4, false );\r\n\t\t\tcontext.lineTo( 0,0 );\r\n\t\t\tcontext.closePath();\r\n\t\t\tcontext.fill();\r\n\t\r\n\t\tcontext.restore();\r\n\t\r\n\t\t\/\/right shadow\r\n\t\tnid  = ( i+2 ) % solution.length;\r\n\t\tnext = solution[ nid ];\r\n\t\r\n\t\tdist = range - next.distance;\r\n\t\r\n\t\tcontext.save();\r\n\t\r\n\t\t\tcontext.translate( next.x, next.y );\r\n\t\t\tcontext.rotate( n.angle - PI \/ 4 );\r\n\t\r\n\t\t\tcontext.beginPath();\r\n\t\t\tcontext.moveTo( 0,0 );\r\n\t\t\tcontext.arc( 0,0, dist, PI \/ 4, PI \/ 2, false );\r\n\t\t\tcontext.lineTo( 0,0 );\r\n\t\t\tcontext.closePath();\r\n\t\t\tcontext.fill();\r\n\t\r\n\t\tcontext.restore();\r\n\t\r\n\t}\r\n}<\/pre>\n<p>&amp; here&#8217;s the result:<\/p>\n<div id=\"soft_shadows\" style=\"width: 100%; height: 720px;\"><\/div>\n<p><a href=\"http:\/\/www.barradeau.com\/js\/hidebehind\/soft_shadows\/soft_shadows.html\" target=\"_blank\" rel=\"noopener\">fullscreen<\/a> &#8211; <a href=\"http:\/\/www.barradeau.com\/js\/hidebehind\/soft_shadows\/soft_shadows.js\" target=\"_blank\" rel=\"noopener\">source<\/a> &#8211; <a href=\"http:\/\/www.barradeau.com\/js\/hidebehind\/soft_shadows\/soft_shadow_map.png\" target=\"_blank\" rel=\"noopener\" data-rel=\"lightbox-image-0\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\">texture<\/a><\/p>\n<p>there are flaws here too ; the self-projections are not taken into account ; it would mean we know which side fades in which direction. as we use a texture, it can&#8217;t be faded away along the edge&#8217;s axis like a radial gradient would so the length of the soft shadow depends on the texture itself.<\/p>\n<blockquote><p>holes<\/p><\/blockquote>\n<p>this basically relies on the way edges are built &amp; oriented ; the same algorithm works with &#8220;compound&#8221; objects.<br \/>\nhere&#8217;s a compound object for instance ; the eyes and the nose have a different orientation then the rest.<\/p>\n<div id=\"compound\" style=\"width: 100%; height: 720px;\"><\/div>\n<p><a href=\"http:\/\/www.barradeau.com\/js\/hidebehind\/compound\/compound.html\" target=\"_blank\" rel=\"noopener\">fullscreen<\/a> &#8211; <a href=\"http:\/\/www.barradeau.com\/js\/hidebehind\/compound\/compound.js\" target=\"_blank\" rel=\"noopener\">source<\/a><br \/>\n20 light sources are computed on click (might take some time&#8230;), sometimes the light lies within a &#8220;hole&#8221; &amp; doesn&#8217;t spread around or is being blocked by the shape containing the &#8220;hole&#8221;.<br \/>\nI used 2 color palettes: <a href=\"http:\/\/www.colourlovers.com\/palette\/372674\/Canary\" target=\"_blank\" rel=\"noopener\">canary<\/a> &amp; <a href=\"http:\/\/www.colourlovers.com\/palette\/3350257\/Hibiki\" target=\"_blank\" rel=\"noopener\">Hibiki<\/a> note that the shapes are not being drawn at all ; it&#8217;s only made of light.<\/p>\n<blockquote><p>extending<\/p><\/blockquote>\n<p>along the way, I thought of a couple of <em>extensions<\/em> that could be done to improve the thing further<\/p>\n<ul>\n<li>various light shapes: I used only point lights but why not using a direction &amp; an angle span to create a spot light? or a line that would emit light along its normals?<\/li>\n<li>translucency: if the ends of the edge know how much light they let through, they can affect the visibility range thus creating a cheap &#8220;translucency&#8221;<\/li>\n<li>caustics: as for translucency, caustics would bounce some of the light it recieives either <em>outwards <\/em>(reflection) or <em>inwards<\/em>(refraction)<\/li>\n<li>curves: I&#8217;m only using line\/line intersections here but with a more clever data format &amp; the appropriate intersection methods, it is possible to use curves (for instance using <a href=\"http:\/\/www.kevlindev.com\/geometry\/2D\/intersections\/index.htm\" target=\"_blank\" rel=\"noopener\">Kevin Lindsey&#8217;s intersection routines<\/a>)<\/li>\n<li>handling the primitives such as circles or regular polygons separately ; there are &#8220;shortcuts&#8221; to perform the projections on those shapes<\/li>\n<\/ul>\n<blockquote><p>&#8230; the hidebehind?<\/p><\/blockquote>\n<p>I like all kinds of insects, chimeras, monsters\u00a0(dragons seems trendy those days) &amp;\u00a0I often envision the algorithms as <em>creatures with an autonomous life<\/em>. I first read about the<em>\u00a0hidebehind\u00a0<\/em>in the\u00a0<em>Book of Imaginary Beings<\/em>\u00a0by<em>\u00a0Jorge Luis Borges.\u00a0<\/em>here&#8217;s a description of this creature (<em><a href=\" http:\/\/en.wikipedia.org\/wiki\/Hidebehind \" target=\"_blank\" rel=\"noopener\"> wikipedia source\u00a0<\/a><\/em>)<\/p>\n<blockquote>\n<h4>[&#8230;] As its name suggests, the hidebehind is noted for its ability to conceal itself.\u00a0When an observer attempts to look directly at it, the creature hides again behind an object or the observer and therefore can&#8217;t be directly seen\u00a0[&#8230;]<\/h4>\n<\/blockquote>\n<p>the <em>hidebehind<\/em> was a good match for what I was after: a creature that can&#8217;t be seen.<\/p>\n<p style=\"display: none;\"><script src=\"http:\/\/www.barradeau.com\/js\/utils\/Injector.js\"><\/script><br \/>\n<script>\/\/ <![CDATA[ var baseUrl = \"http:\/\/www.barradeau.com\/js\/\"; \/\/var baseUrl = \"..\/\"; var scripts = [ baseUrl +\"vendor\/hammer.min.js\", baseUrl +\"utils\/CanvasUtils.js\", baseUrl +\"utils\/ContextUtils.js\", baseUrl +\"utils\/GeomUtils.js\", baseUrl +\"utils\/PRNG.js\", baseUrl +\"utils\/Utils.js\", baseUrl +\"utils\/DomUtils.js\", baseUrl +\"utils\/geometry\/Triangulator.js\", baseUrl +\"hidebehind\/meshes.js\", baseUrl +\"hidebehind\/naive\/naive_demo.js\", baseUrl +\"hidebehind\/potential_visibility\/potentially_visible.js\", baseUrl +\"hidebehind\/educated\/educated.js\", baseUrl +\"hidebehind\/polar\/polar.js\", baseUrl +\"hidebehind\/shadow_casting\/shadowCasting.js\", baseUrl +\"hidebehind\/hyperion\/hyperion.js\", baseUrl +\"hidebehind\/soft_shadows\/soft_shadows.js\", baseUrl +\"hidebehind\/compound\/compound.js\" ]; new Injector( scripts, null, null ); \/\/ ]]><\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>a month ago, for no specific reason, I decided to implement a 2D top-down visibility algorithm.\u00a0I always found the output beautiful both graphically &amp; conceptually yet, as I don&#8217;t make games, I never tacckled it. visibility algorithms are often used to simulate lights ; to determine which areas of a space are enlightened and which &#8230; <span class=\"more\"><a class=\"more-link\" href=\"http:\/\/barradeau.com\/blog\/?p=194\">[Read more&#8230;]<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":542,"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\/2014\/05\/Capture-d\u00e9cran-2015-08-15-12.46.22.png","jetpack_publicize_connections":[],"jetpack_shortlink":"https:\/\/wp.me\/p4oXhx-38","_links":{"self":[{"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/194"}],"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=194"}],"version-history":[{"count":158,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/194\/revisions"}],"predecessor-version":[{"id":1196,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/194\/revisions\/1196"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/media\/542"}],"wp:attachment":[{"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=194"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=194"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=194"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}