navmeshes can use any convex polygons, not just triangles
parent
491a5b3331
commit
4c62c864ea
|
@ -5,8 +5,14 @@ _, inpath, outpath, basename = sys.argv
|
|||
|
||||
tree = lxml.etree.parse(inpath)
|
||||
|
||||
tris = []
|
||||
edge2tri = {}
|
||||
polys = []
|
||||
edge2poly = {}
|
||||
|
||||
# Polygons must be convex to work correctly, but we don't verify that
|
||||
|
||||
def points_to_edges(points):
|
||||
points = points + [points[0]]
|
||||
return zip(points[:-1], points[1:])
|
||||
|
||||
for polygon in tree.findall("objectgroup[@name='navmesh']/object/polygon"):
|
||||
basex = int(polygon.getparent().attrib["x"])
|
||||
|
@ -26,33 +32,32 @@ for polygon in tree.findall("objectgroup[@name='navmesh']/object/polygon"):
|
|||
cross = abx*acy-acx*aby
|
||||
# positive for clockwise winding order. Example clockwise winding order: (0,0), (1,0), (0,1)
|
||||
if cross < 0:
|
||||
points = points[0],points[2],points[1]
|
||||
bx,by,cx,cy=cx,cy,bx,by
|
||||
cross = -cross
|
||||
points = points[::-1]
|
||||
|
||||
assert len(points)==3, "triangles only"
|
||||
tri_id = len(tris)
|
||||
tris.append(points)
|
||||
poly_id = len(polys)
|
||||
polys.append(points)
|
||||
|
||||
for edge in [(points[0],points[1]), (points[1],points[2]), (points[2],points[0])]:
|
||||
print(points)
|
||||
for edge in points_to_edges(points):
|
||||
edge_id = tuple(sorted(edge))
|
||||
if edge_id not in edge2tri: edge2tri[edge_id] = []
|
||||
edge2tri[edge_id].append(tri_id)
|
||||
assert len(edge2tri[edge_id]) <= 2, "more than 2 triangles share edge "+str(edge_id)
|
||||
print(edge, edge_id)
|
||||
if edge_id not in edge2poly: edge2poly[edge_id] = []
|
||||
edge2poly[edge_id].append(poly_id)
|
||||
assert len(edge2poly[edge_id]) <= 2, "more than 2 polygons share edge "+str(edge_id)
|
||||
|
||||
pathfind_edges = [[] for _ in range(len(tris))]
|
||||
for edgetris in edge2tri.values():
|
||||
assert len(edgetris) in (1,2)
|
||||
if len(edgetris) == 1: continue
|
||||
t1,t2 = edgetris
|
||||
pathfind_edges[t1].append(t2)
|
||||
pathfind_edges[t2].append(t1)
|
||||
pathfind_edges = [[] for _ in range(len(polys))]
|
||||
for edgepolys in edge2poly.values():
|
||||
assert len(edgepolys) in (1,2)
|
||||
if len(edgepolys) == 1: continue
|
||||
p1,p2 = edgepolys
|
||||
pathfind_edges[p1].append(p2)
|
||||
pathfind_edges[p2].append(p1)
|
||||
|
||||
pathfind_matrix = [[255 for _1 in range(len(tris))] for _2 in range(len(tris))]
|
||||
pathfind_matrix = [[255 for _1 in range(len(polys))] for _2 in range(len(polys))]
|
||||
# pathfind_matrix[source][dest] is how to get to dest from source
|
||||
|
||||
for dest in range(len(tris)):
|
||||
# Find paths to tri by breadth-first search. We don't take the size of the triangle into account yet.
|
||||
for dest in range(len(polys)):
|
||||
# Find paths to poly by breadth-first search. We don't take the size of the polygon into account yet.
|
||||
openset = [dest]
|
||||
while len(openset)>0:
|
||||
cur, openset = openset[0], openset[1:]
|
||||
|
@ -66,11 +71,13 @@ for dest in range(len(tris)):
|
|||
out = open(outpath,"w")
|
||||
|
||||
out.write("#include \"compiled_structures.h\"\n")
|
||||
out.write(f"static const struct navmesh_tri {basename}_triangles[{len(tris)}] = {{\n");
|
||||
for tri_id,points in enumerate(tris):
|
||||
out.write("\t{\n\t\t{\n");
|
||||
for a,b,c in [(points[0],points[1],points[2]), (points[1],points[2],points[0]), (points[2],points[0],points[1])]:
|
||||
(ax,ay),(bx,by),(cx,cy) = a,b,c
|
||||
out.write(f"static const struct navmesh_tri {basename}_triangles[{len(polys)}] = {{\n");
|
||||
for poly_id,points in enumerate(polys):
|
||||
out.write("\t{\n")
|
||||
out.write("\t\t"+str(len(points))+",\n")
|
||||
out.write("\t\t(const struct navmesh_tri_edge[]){\n")
|
||||
for a,b in points_to_edges(points):
|
||||
(ax,ay),(bx,by) = a,b
|
||||
# edge a-b and c is the other point
|
||||
# Line equation Px+Qy+R=0 from https://math.stackexchange.com/questions/422602/convert-two-points-to-line-eq-ax-by-c-0
|
||||
P,Q,R=ay-by,bx-ax,ax*by-bx*ay
|
||||
|
@ -82,29 +89,29 @@ for tri_id,points in enumerate(tris):
|
|||
R *= norm
|
||||
|
||||
edge_id = tuple(sorted((a,b)))
|
||||
tris_on_edge = edge2tri[edge_id]
|
||||
assert len(tris_on_edge) in (1,2)
|
||||
assert tri_id in tris_on_edge
|
||||
if len(tris_on_edge) == 2:
|
||||
other_tri_id = [x for x in tris_on_edge if x!=tri_id][0]
|
||||
polys_on_edge = edge2poly[edge_id]
|
||||
assert len(polys_on_edge) in (1,2)
|
||||
assert poly_id in polys_on_edge
|
||||
if len(polys_on_edge) == 2:
|
||||
other_poly_id = [x for x in polys_on_edge if x!=poly_id][0]
|
||||
else:
|
||||
other_tri_id = -1
|
||||
other_poly_id = -1
|
||||
|
||||
centx, centy = (ax+bx)/2, (ay+by)/2
|
||||
|
||||
out.write(f"\t\t\t{{{P},{Q},{R},{other_tri_id},{{{int(centx)},{int(centy)}}}}},\n")
|
||||
out.write(f"\t\t\t{{{P},{Q},{R},{other_poly_id},{{{int(centx)},{int(centy)}}}}},\n")
|
||||
|
||||
|
||||
|
||||
out.write("\t\t},\n")
|
||||
out.write("\t\t{" + ",".join(f"{{{x},{y}}}" for x,y in points) + "},\n")
|
||||
out.write("\t\t(const struct navmesh_point[]){" + ",".join(f"{{{x},{y}}}" for x,y in points) + "},\n")
|
||||
out.write("\t},\n");
|
||||
out.write("};\n")
|
||||
out.write(f"static const unsigned char {basename}_pathfind[{len(tris)*len(tris)}] = {{\n")
|
||||
for dest in range(len(tris)):
|
||||
out.write(f"static const unsigned char {basename}_pathfind[{len(polys)*len(polys)}] = {{\n")
|
||||
for dest in range(len(polys)):
|
||||
out.write("\t")
|
||||
for source in range(len(tris)):
|
||||
for source in range(len(polys)):
|
||||
out.write(str(pathfind_matrix[source][dest])+",")
|
||||
out.write("\n")
|
||||
out.write("};\n")
|
||||
out.write(f"extern const struct navmesh {basename} = {{{len(tris)}, {basename}_triangles, {basename}_pathfind}};\n")
|
||||
out.write(f"extern const struct navmesh {basename} = {{{len(polys)}, {basename}_triangles, {basename}_pathfind}};\n")
|
||||
|
|
|
@ -11,8 +11,10 @@ struct navmesh_tri_edge {
|
|||
struct navmesh_point center; // intermediate point for use in routing. could be improved later.
|
||||
};
|
||||
struct navmesh_tri {
|
||||
struct navmesh_tri_edge edges[3];
|
||||
struct navmesh_point points[3];
|
||||
int num_verts;
|
||||
const struct navmesh_tri_edge *edges;
|
||||
const struct navmesh_point *points;
|
||||
// For a triangle:
|
||||
// edges[0]: points[0]-points[1]
|
||||
// edges[1]: points[1]-points[2]
|
||||
// edges[2]: points[2]-points[0]
|
||||
|
|
20
game.cpp
20
game.cpp
|
@ -44,20 +44,22 @@ static void transition_scene_on_walk_finish(struct script *scr, int wakeupMode,
|
|||
assert(wakeupMode == SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED);
|
||||
}
|
||||
|
||||
void onclick(int curscene, int objid) {
|
||||
switch(curscene) {
|
||||
case SCENE_LOBBY:
|
||||
switch(objid) {
|
||||
case OBJID_DOOR_TO_MANAGERS_OFFICE_FROM_LOBBY:
|
||||
{
|
||||
start_player_walk_to_point(312, 441);
|
||||
static void start_player_walk_to_point_then_transition_scene(int x, int y, int scene) {
|
||||
start_player_walk_to_point(x, y);
|
||||
|
||||
struct script *scr = scene_add_script(OBJID_PLAYER_WALK_TO_DOOR_SCRIPT, true);
|
||||
scr->wakeupMode = SCRIPT_WAKEUP_OTHER_SCRIPT;
|
||||
scr->wakeupArg1 = OBJID_PLAYER_WALK_SCRIPT;
|
||||
scr->wakeupFn = transition_scene_on_walk_finish;
|
||||
scr->vars[0] = SCENE_MANAGERS_OFFICE;
|
||||
}
|
||||
scr->vars[0] = scene;
|
||||
}
|
||||
|
||||
void onclick(int curscene, int objid) {
|
||||
switch(curscene) {
|
||||
case SCENE_LOBBY:
|
||||
switch(objid) {
|
||||
case OBJID_DOOR_TO_MANAGERS_OFFICE_FROM_LOBBY:
|
||||
start_player_walk_to_point_then_transition_scene(312, 441, SCENE_MANAGERS_OFFICE);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -41,28 +41,22 @@
|
|||
<polygon points="0,0 135,85 191,-1"/>
|
||||
</object>
|
||||
<object id="11" x="213" y="441">
|
||||
<polygon points="0,0 -211,301 135,85"/>
|
||||
</object>
|
||||
<object id="12" x="2" y="741">
|
||||
<polygon points="0,1 345,-124 346,-215"/>
|
||||
<polygon points="0,0 -211,301 134,176 135,85"/>
|
||||
</object>
|
||||
<object id="13" x="2" y="742">
|
||||
<polygon points="0,0 0,57 345,-125"/>
|
||||
<polygon points="0,0 0,57 330,58 345,-125"/>
|
||||
</object>
|
||||
<object id="14" x="2" y="799">
|
||||
<polygon points="0,0 1278,1 345,-182"/>
|
||||
</object>
|
||||
<object id="15" x="348" y="617">
|
||||
<polygon points="-1,0 587,8 932,183"/>
|
||||
<polygon points="330,1 959,1 933,-174 345,-182"/>
|
||||
</object>
|
||||
<object id="16" x="860" y="440">
|
||||
<polygon points="0,0 77,118 180,0"/>
|
||||
<polygon points="0,0 76,118 180,0"/>
|
||||
</object>
|
||||
<object id="17" x="937" y="558">
|
||||
<polygon points="0,0 343,242 103,-118"/>
|
||||
<polygon points="-1,0 -2,67 245,95 103,-118"/>
|
||||
</object>
|
||||
<object id="18" x="939" y="559">
|
||||
<polygon points="-2,-1 -4,66 341,241"/>
|
||||
<polygon points="243,94 -4,66 22,241 341,241"/>
|
||||
</object>
|
||||
</objectgroup>
|
||||
</map>
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.9" tiledversion="1.9.2" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="32" tileheight="32" infinite="0" nextlayerid="4" nextobjectid="19">
|
||||
<layer id="1" name="Tile Layer 1" width="30" height="20">
|
||||
<data encoding="csv">
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
</data>
|
||||
</layer>
|
||||
<imagelayer id="2" name="Image Layer 1">
|
||||
<image source="../build/default/managers_office.png" width="1280" height="800"/>
|
||||
</imagelayer>
|
||||
<objectgroup id="3" name="navmesh">
|
||||
<object id="1" x="305" y="491">
|
||||
<polygon points="0,0 42,73 -93,163"/>
|
||||
</object>
|
||||
<object id="2" x="177" y="715">
|
||||
<polygon points="35,-61 170,-151 133,-61"/>
|
||||
</object>
|
||||
<object id="3" x="312" y="654">
|
||||
<polygon points="-2,0 -27,60 148,0"/>
|
||||
</object>
|
||||
<object id="4" x="212" y="654">
|
||||
<polygon points="0,0 -33,60 73,60"/>
|
||||
</object>
|
||||
<object id="5" x="212" y="654">
|
||||
<polygon points="0,0 98,0 73,60"/>
|
||||
</object>
|
||||
<object id="6" x="285" y="714">
|
||||
<polygon points="0,0 175,0 175,-60"/>
|
||||
</object>
|
||||
<object id="7" x="305" y="491">
|
||||
<polygon points="0,0 187,1 169,71"/>
|
||||
</object>
|
||||
<object id="8" x="305" y="491">
|
||||
<polygon points="0,0 42,73 169,71"/>
|
||||
</object>
|
||||
<object id="9" x="595" y="417">
|
||||
<polygon points="0,0 0,75 189,0"/>
|
||||
</object>
|
||||
<object id="10" x="595" y="492">
|
||||
<polygon points="0,0 229,0 189,-75"/>
|
||||
</object>
|
||||
<object id="11" x="824" y="492">
|
||||
<polygon points="0,0 105,222 278,222"/>
|
||||
</object>
|
||||
<object id="12" x="825" y="493">
|
||||
<polygon points="-1,-1 149,-1 277,221"/>
|
||||
</object>
|
||||
<object id="13" x="595" y="492">
|
||||
<polygon points="0,0 334,222 229,0"/>
|
||||
</object>
|
||||
<object id="14" x="595" y="492">
|
||||
<polygon points="0,0 0,222 334,222"/>
|
||||
</object>
|
||||
<object id="15" x="474" y="562">
|
||||
<polygon points="0,0 18,-70 121,-70"/>
|
||||
</object>
|
||||
<object id="16" x="474" y="562">
|
||||
<polygon points="0,0 121,-70 121,152"/>
|
||||
</object>
|
||||
<object id="17" x="474" y="562">
|
||||
<polygon points="0,0 -14,92 121,152"/>
|
||||
</object>
|
||||
<object id="18" x="460" y="654">
|
||||
<polygon points="0,0 0,60 135,60"/>
|
||||
</object>
|
||||
</objectgroup>
|
||||
</map>
|
|
@ -228,7 +228,7 @@ static void closest_point_inside_tri(int *x, int *y, const struct navmesh_tri *t
|
|||
// If this happens, we must be in a corner region.
|
||||
// Repeated normal-vector moves don't necessarily bring us to the corner, but we can just hardcode the corner.
|
||||
for(int j = 0; j < 2; j++) {
|
||||
for(int i = 0; i < 3; i++) {
|
||||
for(int i = 0; i < tri->num_verts; i++) {
|
||||
double edgeval = fx*tri->edges[i].a + fy*tri->edges[i].b + tri->edges[i].c;
|
||||
if(edgeval <= 0) {
|
||||
// edge.a,b is normalized so that moving 1 unit of a and 1 unit of b changes edgeval by 1.
|
||||
|
@ -239,6 +239,16 @@ static void closest_point_inside_tri(int *x, int *y, const struct navmesh_tri *t
|
|||
}
|
||||
}
|
||||
|
||||
for(int i = 0; i < tri->num_verts; i++) {
|
||||
int next = (i+1) % tri->num_verts;
|
||||
int mask = (1 << i) | (1 << next);
|
||||
if((edgeHitMask & mask) == mask) {
|
||||
*x = tri->points[next].x;
|
||||
*y = tri->points[next].y;
|
||||
return;
|
||||
}
|
||||
}
|
||||
/*
|
||||
switch(edgeHitMask) {
|
||||
case 3: // hit edge 0-1 and 1-2
|
||||
*x = tri->points[1].x;
|
||||
|
@ -252,7 +262,7 @@ static void closest_point_inside_tri(int *x, int *y, const struct navmesh_tri *t
|
|||
*x = tri->points[2].x;
|
||||
*y = tri->points[2].y;
|
||||
return;
|
||||
}
|
||||
}*/
|
||||
|
||||
// may be slightly outside of triangle due to rounding errors, but good enough for us
|
||||
*x = (int)(fx + 0.5);
|
||||
|
@ -267,16 +277,33 @@ static double point_to_point_dist(int x, int y, const struct navmesh_point *pt)
|
|||
|
||||
// Returns distance from triangle, 0 if inside.
|
||||
static double is_point_in_tri(int x, int y, const struct navmesh_tri *tri) {
|
||||
double edgedist[3]; // positive if outside
|
||||
/*double edgedist[3]; // positive if outside
|
||||
for(int edge = 0; edge < 3; edge++)
|
||||
edgedist[edge] = -(x*tri->edges[edge].a + y*tri->edges[edge].b + tri->edges[edge].c);
|
||||
if(edgedist[0] < 0 && edgedist[1] < 0 && edgedist[2] < 0) return 0; // inside
|
||||
if(edgedist[0] >= 0 && edgedist[1] >= 0) return point_to_point_dist(x, y, &tri->points[0]);
|
||||
if(edgedist[0] >= 0 && edgedist[1] >= 0) return point_to_point_dist(x, y, &tri->points[0]); // are these correct? shouldn't it be points 1,2,0?
|
||||
if(edgedist[1] >= 0 && edgedist[2] >= 0) return point_to_point_dist(x, y, &tri->points[1]);
|
||||
if(edgedist[2] >= 0 && edgedist[0] >= 0) return point_to_point_dist(x, y, &tri->points[2]);
|
||||
if(edgedist[0] >= 0) return edgedist[0];
|
||||
if(edgedist[1] >= 0) return edgedist[1];
|
||||
if(edgedist[2] >= 0) return edgedist[2];
|
||||
if(edgedist[2] >= 0) return edgedist[2];*/
|
||||
|
||||
double edgedist[tri->num_verts];
|
||||
bool all_inside = true;
|
||||
for(int edge = 0; edge < tri->num_verts; edge++) {
|
||||
edgedist[edge] = -(x*tri->edges[edge].a + y*tri->edges[edge].b + tri->edges[edge].c);
|
||||
if(edgedist[edge] > 0)
|
||||
all_inside = false;
|
||||
}
|
||||
if(all_inside) return 0;
|
||||
for(int edge = 0; edge < tri->num_verts; edge++) {
|
||||
int next = (edge+1)%tri->num_verts;
|
||||
if(edgedist[edge] >= 0 && edgedist[next] >= 0) return point_to_point_dist(x, y, &tri->points[next]);
|
||||
}
|
||||
for(int edge = 0; edge < tri->num_verts; edge++)
|
||||
if(edgedist[edge] >= 0)
|
||||
return edgedist[edge];
|
||||
|
||||
return 0; // branch should be unreachable; maybe within epsilon of triangle border?
|
||||
}
|
||||
static int find_navmesh_tri(int x, int y, int tolerance) {
|
||||
|
@ -378,7 +405,7 @@ static void update_player_walk_script_on_frame(struct script *scr, int wakeupMod
|
|||
} else {
|
||||
nextTri = cur_navmesh->pathfindgrid[player_walk_script->targetNavmeshTri * cur_navmesh->num_tris + nmtri];
|
||||
nextX = -1;
|
||||
for(int edge = 0; edge < 3; edge++) {
|
||||
for(int edge = 0; edge < cur_navmesh->tris[nmtri].num_verts; edge++) {
|
||||
if(cur_navmesh->tris[nmtri].edges[edge].other_tri == nextTri) {
|
||||
nextX = cur_navmesh->tris[nmtri].edges[edge].center.x;
|
||||
nextY = cur_navmesh->tris[nmtri].edges[edge].center.y;
|
||||
|
|
Loading…
Reference in New Issue