Advertisement
Guest User

Untitled

a guest
Aug 22nd, 2014
183
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Haxe 15.20 KB | None | 0 0
  1. // Calculate visible area from a position
  2. // Copyright 2012 Red Blob Games
  3. // License: Apache v2
  4.  
  5. /*
  6.    Licensed under the Apache License, Version 2.0 (the "License");
  7.    you may not use this file except in compliance with the License.
  8.    You may obtain a copy of the License at
  9.  
  10.        http://www.apache.org/licenses/LICENSE-2.0
  11.  
  12.    Unless required by applicable law or agreed to in writing, software
  13.    distributed under the License is distributed on an "AS IS" BASIS,
  14.    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15.    See the License for the specific language governing permissions and
  16.    limitations under the License.
  17. */
  18.  
  19. /*
  20.    This code uses the Haxe compiler, including some of the basic Haxe
  21.    libraries, which are under the two-clause BSD license: http://haxe.org/doc/license
  22.  
  23.    Copyright (c) 2005, the haXe Project Contributors
  24.    All rights reserved.
  25.    Redistribution and use in source and binary forms, with or without
  26.    modification, are permitted provided that the following conditions are met:
  27.  
  28.    * Redistributions of source code must retain the above copyright
  29.      notice, this list of conditions and the following disclaimer.
  30.  
  31.    * Redistributions in binary form must reproduce the above copyright
  32.      notice, this list of conditions and the following disclaimer in the
  33.      documentation and/or other materials provided with the distribution.
  34.  
  35.    THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND
  36.    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  37.    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  38.    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT
  39.    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  40.    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  41.    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  42.    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  43.    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  44.    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  45.    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  46. */
  47.  
  48. /*
  49.    This code also uses a linked list datastructure class from
  50.    Polygonal, which is Copyright (c) 2009-2010 Michael Baczynski,
  51.    http://www.polygonal.de. It is available under the new BSD license,
  52.    except for two algorithms, which I do not use. See
  53.    https://github.com/polygonal/polygonal/blob/master/LICENSE
  54. */
  55.  
  56. import de.polygonal.ds.DLL;
  57.  
  58. typedef Block = {x:Float, y:Float, r:Float};
  59. typedef Point = {x:Float, y:Float};
  60. typedef Segment = {p1:EndPoint, p2:EndPoint, d:Float};
  61. typedef EndPoint = {x:Float, y:Float, begin:Bool, segment:Segment, angle:Float, visualize:Bool};
  62.  
  63. /* 2d visibility algorithm, for demo
  64.    Usage:
  65.       new Visibility()
  66.    Whenever map data changes:
  67.       loadMap
  68.    Whenever light source changes:
  69.       setLightLocation
  70.    To calculate the area:
  71.       sweep
  72. */
  73.  
  74. @:expose @:keep class Visibility {
  75.     // Note: DLL is a doubly linked list but an array would be ok too
  76.  
  77.     // These represent the map and the light location:
  78.     public var segments:DLL<Segment>;
  79.     public var endpoints:DLL<EndPoint>;
  80.     public var center:Point;
  81.  
  82.     // These are currently 'open' line segments, sorted so that the nearest
  83.     // segment is first. It's used only during the sweep algorithm, and exposed
  84.     // as a public field here so that the demo can display it.
  85.     public var open:DLL<Segment>;
  86.  
  87.     // The output is a series of points that forms a visible area polygon
  88.     public var output:Array<Point>;
  89.  
  90.     // For the demo, keep track of wall intersections
  91.     public var demo_intersectionsDetected:Array<Array<Point>>;
  92.  
  93.  
  94.     // Construct an empty visibility set
  95.     public function new() {
  96.         segments = new DLL<Segment>();
  97.         endpoints = new DLL<EndPoint>();
  98.         open = new DLL<Segment>();
  99.         center = {x: 0.0, y: 0.0};
  100.         output = new Array();
  101.         demo_intersectionsDetected = [];
  102.     }
  103.  
  104.  
  105.     // Helper function to construct segments along the outside perimeter
  106.     private function loadEdgeOfMap(size:Int, margin:Int) {
  107.         addSegment(margin, margin, margin, size-margin);
  108.         addSegment(margin, size-margin, size-margin, size-margin);
  109.         addSegment(size-margin, size-margin, size-margin, margin);
  110.         addSegment(size-margin, margin, margin, margin);
  111.         // NOTE: if using the simpler distance function (a.d < b.d)
  112.         // then we need segments to be similarly sized, so the edge of
  113.         // the map needs to be broken up into smaller segments.
  114.     }
  115.  
  116.    
  117.     // Load a set of square blocks, plus any other line segments
  118.     public function loadMap(size, margin, blocks:Array<Block>, walls:Array<Segment>) {
  119.         segments.clear();
  120.         endpoints.clear();
  121.         loadEdgeOfMap(size, margin);
  122.        
  123.         for (block in blocks) {
  124.             var x = block.x;
  125.             var y = block.y;
  126.             var r = block.r;
  127.             addSegment(x-r, y-r, x-r, y+r);
  128.             addSegment(x-r, y+r, x+r, y+r);
  129.             addSegment(x+r, y+r, x+r, y-r);
  130.             addSegment(x+r, y-r, x-r, y-r);
  131.         }
  132.         for (wall in walls) {
  133.             addSegment(wall.p1.x, wall.p1.y, wall.p2.x, wall.p2.y);
  134.         }
  135.     }
  136.  
  137.  
  138.     // Add a segment, where the first point shows up in the
  139.     // visualization but the second one does not. (Every endpoint is
  140.     // part of two segments, but we want to only show them once.)
  141.     private function addSegment(x1:Float, y1:Float, x2:Float, y2:Float) {
  142.         var segment:Segment = null;
  143.         var p1:EndPoint = {begin: false, x: 0.0, y: 0.0, angle: 0.0,
  144.                            segment: segment, visualize: true};
  145.         var p2:EndPoint = {begin: false, x: 0.0, y: 0.0, angle: 0.0,
  146.                            segment: segment, visualize: false};
  147.         segment = {p1: p1, p2: p2, d: 0.0};
  148.         p1.x = x1; p1.y = y1;
  149.         p2.x = x2; p2.y = y2;
  150.         p1.segment = segment;
  151.         p2.segment = segment;
  152.         segment.p1 = p1;
  153.         segment.p2 = p2;
  154.  
  155.         segments.append(segment);
  156.         endpoints.append(p1);
  157.         endpoints.append(p2);
  158.     }
  159.  
  160.  
  161.     // Set the light location. Segment and EndPoint data can't be
  162.     // processed until the light location is known.
  163.     public function setLightLocation(x:Float, y:Float) {
  164.         center.x = x;
  165.         center.y = y;
  166.        
  167.         for (segment in segments) {
  168.             var dx = 0.5 * (segment.p1.x + segment.p2.x) - x;
  169.             var dy = 0.5 * (segment.p1.y + segment.p2.y) - y;
  170.             // NOTE: we only use this for comparison so we can use
  171.             // distance squared instead of distance
  172.             segment.d = dx*dx + dy*dy;
  173.  
  174.             // NOTE: future optimization: we could record the quadrant
  175.             // and the y/x or x/y ratio, and sort by (quadrant,
  176.             // ratio), instead of calling atan2. See
  177.             // <https://github.com/mikolalysenko/compare-slope> for a
  178.             // library that does this.
  179.             segment.p1.angle = Math.atan2(segment.p1.y - y, segment.p1.x - x);
  180.             segment.p2.angle = Math.atan2(segment.p2.y - y, segment.p2.x - x);
  181.  
  182.             var dAngle = segment.p2.angle - segment.p1.angle;
  183.             if (dAngle <= -Math.PI) { dAngle += 2*Math.PI; }
  184.             if (dAngle > Math.PI) { dAngle -= 2*Math.PI; }
  185.             segment.p1.begin = (dAngle > 0.0);
  186.             segment.p2.begin = !segment.p1.begin;
  187.         }
  188.     }
  189.  
  190.  
  191.     // Helper: comparison function for sorting points by angle
  192.     static private function _endpoint_compare(a:EndPoint, b:EndPoint):Int {
  193.         // Traverse in angle order
  194.         if (a.angle > b.angle) return 1;
  195.         if (a.angle < b.angle) return -1;
  196.         // But for ties (common), we want Begin nodes before End nodes
  197.         if (!a.begin && b.begin) return 1;
  198.         if (a.begin && !b.begin) return -1;
  199.         return 0;
  200.     }
  201.  
  202.     // Helper: leftOf(segment, point) returns true if point is "left"
  203.     // of segment treated as a vector. Note that this assumes a 2D
  204.     // coordinate system in which the Y axis grows downwards, which
  205.     // matches common 2D graphics libraries, but is the opposite of
  206.     // the usual convention from mathematics and in 3D graphics
  207.     // libraries.
  208.     static inline private function leftOf(s:Segment, p:Point):Bool {
  209.         // This is based on a 3d cross product, but we don't need to
  210.         // use z coordinate inputs (they're 0), and we only need the
  211.         // sign. If you're annoyed that cross product is only defined
  212.         // in 3d, see "outer product" in Geometric Algebra.
  213.         // <http://en.wikipedia.org/wiki/Geometric_algebra>
  214.         var cross = (s.p2.x - s.p1.x) * (p.y - s.p1.y)
  215.                   - (s.p2.y - s.p1.y) * (p.x - s.p1.x);
  216.         return cross < 0;
  217.         // Also note that this is the naive version of the test and
  218.         // isn't numerically robust. See
  219.         // <https://github.com/mikolalysenko/robust-arithmetic> for a
  220.         // demo of how this fails when a point is very close to the
  221.         // line.
  222.     }
  223.  
  224.     // Return p*(1-f) + q*f
  225.     static private function interpolate(p:Point, q:Point, f:Float):Point {
  226.         return {x: p.x*(1-f) + q.x*f, y: p.y*(1-f) + q.y*f};
  227.     }
  228.    
  229.     // Helper: do we know that segment a is in front of b?
  230.     // Implementation not anti-symmetric (that is to say,
  231.     // _segment_in_front_of(a, b) != (!_segment_in_front_of(b, a)).
  232.     // Also note that it only has to work in a restricted set of cases
  233.     // in the visibility algorithm; I don't think it handles all
  234.     // cases. See http://www.redblobgames.com/articles/visibility/segment-sorting.html
  235.     private function _segment_in_front_of(a:Segment, b:Segment, relativeTo:Point):Bool {
  236.         // NOTE: we slightly shorten the segments so that
  237.         // intersections of the endpoints (common) don't count as
  238.         // intersections in this algorithm
  239.         var A1 = leftOf(a, interpolate(b.p1, b.p2, 0.01));
  240.         var A2 = leftOf(a, interpolate(b.p2, b.p1, 0.01));
  241.         var A3 = leftOf(a, relativeTo);
  242.         var B1 = leftOf(b, interpolate(a.p1, a.p2, 0.01));
  243.         var B2 = leftOf(b, interpolate(a.p2, a.p1, 0.01));
  244.         var B3 = leftOf(b, relativeTo);
  245.  
  246.         // NOTE: this algorithm is probably worthy of a short article
  247.         // but for now, draw it on paper to see how it works. Consider
  248.         // the line A1-A2. If both B1 and B2 are on one side and
  249.         // relativeTo is on the other side, then A is in between the
  250.         // viewer and B. We can do the same with B1-B2: if A1 and A2
  251.         // are on one side, and relativeTo is on the other side, then
  252.         // B is in between the viewer and A.
  253.         if (B1 == B2 && B2 != B3) return true;
  254.         if (A1 == A2 && A2 == A3) return true;
  255.         if (A1 == A2 && A2 != A3) return false;
  256.         if (B1 == B2 && B2 == B3) return false;
  257.        
  258.         // If A1 != A2 and B1 != B2 then we have an intersection.
  259.         // Expose it for the GUI to show a message. A more robust
  260.         // implementation would split segments at intersections so
  261.         // that part of the segment is in front and part is behind.
  262.         demo_intersectionsDetected.push([a.p1, a.p2, b.p1, b.p2]);
  263.         return false;
  264.  
  265.         // NOTE: previous implementation was a.d < b.d. That's simpler
  266.         // but trouble when the segments are of dissimilar sizes. If
  267.         // you're on a grid and the segments are similarly sized, then
  268.         // using distance will be a simpler and faster implementation.
  269.     }
  270.    
  271.  
  272.     // Run the algorithm, sweeping over all or part of the circle to find
  273.     // the visible area, represented as a set of triangles
  274.     public function sweep(maxAngle:Float=999.0) {
  275.         output = [];  // output set of triangles
  276.         demo_intersectionsDetected = [];
  277.         endpoints.sort(_endpoint_compare, true);
  278.        
  279.         open.clear();
  280.         var beginAngle = 0.0;
  281.  
  282.         // At the beginning of the sweep we want to know which
  283.         // segments are active. The simplest way to do this is to make
  284.         // a pass collecting the segments, and make another pass to
  285.         // both collect and process them. However it would be more
  286.         // efficient to go through all the segments, figure out which
  287.         // ones intersect the initial sweep line, and then sort them.
  288.         for (pass in 0...2) {
  289.             for (p in endpoints) {
  290.                 if (pass == 1 && p.angle > maxAngle) {
  291.                     // Early exit for the visualization to show the sweep process
  292.                     break;
  293.                 }
  294.                
  295.                 var current_old = open.isEmpty()? null : open.head.val;
  296.                
  297.                 if (p.begin) {
  298.                     // Insert into the right place in the list
  299.                     var node = open.head;
  300.                     while (node != null && _segment_in_front_of(p.segment, node.val, center)) {
  301.                         node = node.next;
  302.                     }
  303.                     if (node == null) {
  304.                         open.append(p.segment);
  305.                     } else {
  306.                         open.insertBefore(node, p.segment);
  307.                     }
  308.                 }
  309.                 else {
  310.                     open.remove(p.segment);
  311.                 }
  312.                
  313.                 var current_new = open.isEmpty()? null : open.head.val;
  314.                 if (current_old != current_new) {
  315.                     if (pass == 1) {
  316.                         addTriangle(beginAngle, p.angle, current_old);
  317.                     }
  318.                     beginAngle = p.angle;
  319.                 }
  320.             }
  321.         }
  322.     }
  323.  
  324.  
  325.     public function lineIntersection(p1:Point, p2:Point, p3:Point, p4:Point):Point {
  326.         // From http://paulbourke.net/geometry/lineline2d/
  327.         var s = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x))
  328.             / ((p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y));
  329.         return {x: p1.x + s * (p2.x - p1.x), y: p1.y + s * (p2.y - p1.y)};
  330.     }
  331.    
  332.            
  333.     private function addTriangle(angle1:Float, angle2:Float, segment:Segment) {
  334.         var p1:Point = center;
  335.         var p2:Point = {x:center.x + Math.cos(angle1), y:center.y + Math.sin(angle1)};
  336.         var p3:Point = {x:0.0, y:0.0};
  337.         var p4:Point = {x:0.0, y:0.0};
  338.  
  339.         if (segment != null) {
  340.             // Stop the triangle at the intersecting segment
  341.             p3.x = segment.p1.x;
  342.             p3.y = segment.p1.y;
  343.             p4.x = segment.p2.x;
  344.             p4.y = segment.p2.y;
  345.         } else {
  346.             // Stop the triangle at a fixed distance; this probably is
  347.             // not what we want, but it never gets used in the demo
  348.             p3.x = center.x + Math.cos(angle1) * 500;
  349.             p3.y = center.y + Math.sin(angle1) * 500;
  350.             p4.x = center.x + Math.cos(angle2) * 500;
  351.             p4.y = center.y + Math.sin(angle2) * 500;
  352.         }
  353.    
  354.         var pBegin = lineIntersection(p3, p4, p1, p2);
  355.  
  356.         p2.x = center.x + Math.cos(angle2);
  357.         p2.y = center.y + Math.sin(angle2);
  358.         var pEnd = lineIntersection(p3, p4, p1, p2);
  359.  
  360.         output.push(pBegin);
  361.         output.push(pEnd);
  362.     }
  363. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement