/*************************************************************************/ /* polygon_path_finder.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "polygon_path_finder.h" #include "geometry.h" bool PolygonPathFinder::_is_point_inside(const Vector2& p_point) const { int crosses=0; for (Set::Element *E=edges.front();E;E=E->next()) { const Edge& e=E->get(); Vector2 a = points[e.points[0]].pos; Vector2 b = points[e.points[1]].pos; if (Geometry::segment_intersects_segment_2d(a,b,p_point,outside_point,NULL)) { crosses++; } } return crosses&1; } void PolygonPathFinder::setup(const Vector& p_points, const Vector& p_connections) { ERR_FAIL_COND(p_connections.size()&1); points.clear(); edges.clear(); //insert points int point_count=p_points.size(); points.resize(point_count+2); bounds=Rect2(); for(int i=0;i::Element *E=edges.front();E;E=E->next()) { const Edge& e=E->get(); if (e.points[0]==i || e.points[1]==i || e.points[0]==j || e.points[1]==j ) continue; Vector2 a = points[e.points[0]].pos; Vector2 b = points[e.points[1]].pos; if (Geometry::segment_intersects_segment_2d(a,b,from,to,NULL)) { valid=false; break; } } if (valid) { points[i].connections.insert(j); points[j].connections.insert(i); } } } } Vector PolygonPathFinder::find_path(const Vector2& p_from, const Vector2& p_to) { Vector path; Vector2 from=p_from; Vector2 to=p_to; Edge ignore_from_edge(-1,-1); Edge ignore_to_edge(-1,-1); if (!_is_point_inside(from)) { float closest_dist=1e20; Vector2 closest_point; for (Set::Element *E=edges.front();E;E=E->next()) { const Edge& e=E->get(); Vector2 seg[2]={ points[e.points[0]].pos, points[e.points[1]].pos }; Vector2 closest = Geometry::get_closest_point_to_segment_2d(from,seg); float d = from.distance_squared_to(closest); if (dget(); closest_dist=d; closest_point=closest; } } from=closest_point; }; if (!_is_point_inside(to)) { float closest_dist=1e20; Vector2 closest_point; for (Set::Element *E=edges.front();E;E=E->next()) { const Edge& e=E->get(); Vector2 seg[2]={ points[e.points[0]].pos, points[e.points[1]].pos }; Vector2 closest = Geometry::get_closest_point_to_segment_2d(to,seg); float d = to.distance_squared_to(closest); if (dget(); closest_dist=d; closest_point=closest; } } to=closest_point; }; //test direct connection { bool can_see_eachother=true; for (Set::Element *E=edges.front();E;E=E->next()) { const Edge& e=E->get(); if (e.points[0]==ignore_from_edge.points[0] && e.points[1]==ignore_from_edge.points[1]) continue; if (e.points[0]==ignore_to_edge.points[0] && e.points[1]==ignore_to_edge.points[1]) continue; Vector2 a = points[e.points[0]].pos; Vector2 b = points[e.points[1]].pos; if (Geometry::segment_intersects_segment_2d(a,b,from,to,NULL)) { can_see_eachother=false; break; } } if (can_see_eachother) { path.push_back(from); path.push_back(to); return path; } } //add to graph int aidx = points.size()-2; int bidx = points.size()-1; points[aidx].pos=from; points[bidx].pos=to; points[aidx].distance=0; points[bidx].distance=0; points[aidx].prev=-1; points[bidx].prev=-1; points[aidx].penalty=0; points[bidx].penalty=0; for(int i=0;i::Element *E=edges.front();E;E=E->next()) { const Edge& e=E->get(); if (e.points[0]==i || e.points[1]==i) continue; Vector2 a = points[e.points[0]].pos; Vector2 b = points[e.points[1]].pos; if (valid_a) { if (e.points[0]!=ignore_from_edge.points[1] && e.points[1]!=ignore_from_edge.points[1] && e.points[0]!=ignore_from_edge.points[0] && e.points[1]!=ignore_from_edge.points[0]) { if (Geometry::segment_intersects_segment_2d(a,b,from,points[i].pos,NULL)) { valid_a=false; } } } if (valid_b) { if (e.points[0]!=ignore_to_edge.points[1] && e.points[1]!=ignore_to_edge.points[1] && e.points[0]!=ignore_to_edge.points[0] && e.points[1]!=ignore_to_edge.points[0]) { if (Geometry::segment_intersects_segment_2d(a,b,to,points[i].pos,NULL)) { valid_b=false; } } } if (!valid_a && !valid_b) break; } if (valid_a) { points[i].connections.insert(aidx); points[aidx].connections.insert(i); } if (valid_b) { points[i].connections.insert(bidx); points[bidx].connections.insert(i); } } //solve graph Set open_list; points[aidx].distance=0; points[aidx].prev=aidx; for(Set::Element *E=points[aidx].connections.front();E;E=E->next()) { open_list.insert(E->get()); points[E->get()].distance=from.distance_to(points[E->get()].pos); points[E->get()].prev=aidx; } bool found_route=false; while(true) { if (open_list.size()==0) { printf("open list empty\n"); break; } //check open list int least_cost_point=-1; float least_cost=1e30; //this could be faster (cache previous results) for (Set::Element *E=open_list.front();E;E=E->next()) { const Point& p =points[E->get()]; float cost = p.distance; cost+=p.pos.distance_to(to); cost+=p.penalty; if (costget(); least_cost=cost; } } Point &np = points[least_cost_point]; //open the neighbours for search for(Set::Element *E=np.connections.front();E;E=E->next()) { Point& p =points[E->get()]; float distance = np.pos.distance_to(p.pos) + np.distance; if (p.prev!=-1) { //oh this was visited already, can we win the cost? if (p.distance>distance) { p.prev=least_cost_point; //reasign previous p.distance=distance; } } else { //add to open neighbours p.prev=least_cost_point; p.distance=distance; open_list.insert(E->get()); if (E->get()==bidx) { //oh my reached end! stop algorithm found_route=true; break; } } } if (found_route) break; open_list.erase(least_cost_point); } if (found_route) { int at = bidx; path.push_back(points[at].pos); do { at=points[at].prev; path.push_back(points[at].pos); } while (at!=aidx); path.invert(); } for(int i=0;i p=p_data["points"]; Array c=p_data["connections"]; ERR_FAIL_COND(c.size()!=p.size()); if (c.size()) return; int pc = p.size(); points.resize(pc+2); PoolVector::Read pr=p.read(); for(int i=0;i con=c[i]; PoolVector::Read cr=con.read(); int cc=con.size(); for(int j=0;j penalties=p_data["penalties"]; if (penalties.size()==pc) { PoolVector::Read pr = penalties.read(); for(int i=0;i segs=p_data["segments"]; int sc=segs.size(); ERR_FAIL_COND(sc&1); PoolVector::Read sr = segs.read(); for(int i=0;i p; PoolVector ind; Array connections; p.resize(points.size()-2); connections.resize(points.size()-2); ind.resize(edges.size()*2); PoolVector penalties; penalties.resize(points.size()-2); { PoolVector::Write wp=p.write(); PoolVector::Write pw=penalties.write(); for(int i=0;i c; c.resize(points[i].connections.size()); { PoolVector::Write cw=c.write(); int idx=0; for (Set::Element *E=points[i].connections.front();E;E=E->next()) { cw[idx++]=E->get(); } } connections[i]=c; } } { PoolVector::Write iw=ind.write(); int idx=0; for (Set::Element *E=edges.front();E;E=E->next()) { iw[idx++]=E->get().points[0]; iw[idx++]=E->get().points[1]; } } d["bounds"]=bounds; d["points"]=p; d["penalties"]=penalties; d["connections"]=connections; d["segments"]=ind; return d; } bool PolygonPathFinder::is_point_inside(const Vector2& p_point) const { return _is_point_inside(p_point); } Vector2 PolygonPathFinder::get_closest_point(const Vector2& p_point) const { float closest_dist=1e20; Vector2 closest_point; for (Set::Element *E=edges.front();E;E=E->next()) { const Edge& e=E->get(); Vector2 seg[2]={ points[e.points[0]].pos, points[e.points[1]].pos }; Vector2 closest = Geometry::get_closest_point_to_segment_2d(p_point,seg); float d = p_point.distance_squared_to(closest); if (d PolygonPathFinder::get_intersections(const Vector2& p_from, const Vector2& p_to) const { Vector inters; for (Set::Element *E=edges.front();E;E=E->next()) { Vector2 a = points[E->get().points[0]].pos; Vector2 b = points[E->get().points[1]].pos; Vector2 res; if (Geometry::segment_intersects_segment_2d(a,b,p_from,p_to,&res)) { inters.push_back(res); } } return inters; } Rect2 PolygonPathFinder::get_bounds() const { return bounds; } void PolygonPathFinder::set_point_penalty(int p_point,float p_penalty) { ERR_FAIL_INDEX(p_point,points.size()-2); points[p_point].penalty=p_penalty; } float PolygonPathFinder::get_point_penalty(int p_point) const { ERR_FAIL_INDEX_V(p_point,points.size()-2,0); return points[p_point].penalty; } void PolygonPathFinder::_bind_methods() { ClassDB::bind_method(_MD("setup","points","connections"),&PolygonPathFinder::setup); ClassDB::bind_method(_MD("find_path","from","to"),&PolygonPathFinder::find_path); ClassDB::bind_method(_MD("get_intersections","from","to"),&PolygonPathFinder::get_intersections); ClassDB::bind_method(_MD("get_closest_point","point"),&PolygonPathFinder::get_closest_point); ClassDB::bind_method(_MD("is_point_inside","point"),&PolygonPathFinder::is_point_inside); ClassDB::bind_method(_MD("set_point_penalty","idx","penalty"),&PolygonPathFinder::set_point_penalty); ClassDB::bind_method(_MD("get_point_penalty","idx"),&PolygonPathFinder::get_point_penalty); ClassDB::bind_method(_MD("get_bounds"),&PolygonPathFinder::get_bounds); ClassDB::bind_method(_MD("_set_data"),&PolygonPathFinder::_set_data); ClassDB::bind_method(_MD("_get_data"),&PolygonPathFinder::_get_data); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY,"data",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR),_SCS("_set_data"),_SCS("_get_data")); } PolygonPathFinder::PolygonPathFinder() { }