navmeshes can use any convex polygons, not just triangles

master
immibis 2025-02-18 17:22:39 +01:00
parent 491a5b3331
commit 4c62c864ea
6 changed files with 186 additions and 68 deletions

View File

@ -5,8 +5,14 @@ _, inpath, outpath, basename = sys.argv
tree = lxml.etree.parse(inpath) tree = lxml.etree.parse(inpath)
tris = [] polys = []
edge2tri = {} 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"): for polygon in tree.findall("objectgroup[@name='navmesh']/object/polygon"):
basex = int(polygon.getparent().attrib["x"]) 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 cross = abx*acy-acx*aby
# positive for clockwise winding order. Example clockwise winding order: (0,0), (1,0), (0,1) # positive for clockwise winding order. Example clockwise winding order: (0,0), (1,0), (0,1)
if cross < 0: if cross < 0:
points = points[0],points[2],points[1] points = points[::-1]
bx,by,cx,cy=cx,cy,bx,by
cross = -cross
assert len(points)==3, "triangles only" poly_id = len(polys)
tri_id = len(tris) polys.append(points)
tris.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)) edge_id = tuple(sorted(edge))
if edge_id not in edge2tri: edge2tri[edge_id] = [] print(edge, edge_id)
edge2tri[edge_id].append(tri_id) if edge_id not in edge2poly: edge2poly[edge_id] = []
assert len(edge2tri[edge_id]) <= 2, "more than 2 triangles share edge "+str(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))] pathfind_edges = [[] for _ in range(len(polys))]
for edgetris in edge2tri.values(): for edgepolys in edge2poly.values():
assert len(edgetris) in (1,2) assert len(edgepolys) in (1,2)
if len(edgetris) == 1: continue if len(edgepolys) == 1: continue
t1,t2 = edgetris p1,p2 = edgepolys
pathfind_edges[t1].append(t2) pathfind_edges[p1].append(p2)
pathfind_edges[t2].append(t1) 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 # pathfind_matrix[source][dest] is how to get to dest from source
for dest in range(len(tris)): for dest in range(len(polys)):
# Find paths to tri by breadth-first search. We don't take the size of the triangle into account yet. # Find paths to poly by breadth-first search. We don't take the size of the polygon into account yet.
openset = [dest] openset = [dest]
while len(openset)>0: while len(openset)>0:
cur, openset = openset[0], openset[1:] cur, openset = openset[0], openset[1:]
@ -66,11 +71,13 @@ for dest in range(len(tris)):
out = open(outpath,"w") out = open(outpath,"w")
out.write("#include \"compiled_structures.h\"\n") out.write("#include \"compiled_structures.h\"\n")
out.write(f"static const struct navmesh_tri {basename}_triangles[{len(tris)}] = {{\n"); out.write(f"static const struct navmesh_tri {basename}_triangles[{len(polys)}] = {{\n");
for tri_id,points in enumerate(tris): for poly_id,points in enumerate(polys):
out.write("\t{\n\t\t{\n"); out.write("\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])]: out.write("\t\t"+str(len(points))+",\n")
(ax,ay),(bx,by),(cx,cy) = a,b,c 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 # 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 # 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 P,Q,R=ay-by,bx-ax,ax*by-bx*ay
@ -82,29 +89,29 @@ for tri_id,points in enumerate(tris):
R *= norm R *= norm
edge_id = tuple(sorted((a,b))) edge_id = tuple(sorted((a,b)))
tris_on_edge = edge2tri[edge_id] polys_on_edge = edge2poly[edge_id]
assert len(tris_on_edge) in (1,2) assert len(polys_on_edge) in (1,2)
assert tri_id in tris_on_edge assert poly_id in polys_on_edge
if len(tris_on_edge) == 2: if len(polys_on_edge) == 2:
other_tri_id = [x for x in tris_on_edge if x!=tri_id][0] other_poly_id = [x for x in polys_on_edge if x!=poly_id][0]
else: else:
other_tri_id = -1 other_poly_id = -1
centx, centy = (ax+bx)/2, (ay+by)/2 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},\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("\t},\n");
out.write("};\n") out.write("};\n")
out.write(f"static const unsigned char {basename}_pathfind[{len(tris)*len(tris)}] = {{\n") out.write(f"static const unsigned char {basename}_pathfind[{len(polys)*len(polys)}] = {{\n")
for dest in range(len(tris)): for dest in range(len(polys)):
out.write("\t") out.write("\t")
for source in range(len(tris)): for source in range(len(polys)):
out.write(str(pathfind_matrix[source][dest])+",") out.write(str(pathfind_matrix[source][dest])+",")
out.write("\n") out.write("\n")
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")

View File

@ -11,8 +11,10 @@ struct navmesh_tri_edge {
struct navmesh_point center; // intermediate point for use in routing. could be improved later. struct navmesh_point center; // intermediate point for use in routing. could be improved later.
}; };
struct navmesh_tri { struct navmesh_tri {
struct navmesh_tri_edge edges[3]; int num_verts;
struct navmesh_point points[3]; const struct navmesh_tri_edge *edges;
const struct navmesh_point *points;
// For a triangle:
// edges[0]: points[0]-points[1] // edges[0]: points[0]-points[1]
// edges[1]: points[1]-points[2] // edges[1]: points[1]-points[2]
// edges[2]: points[2]-points[0] // edges[2]: points[2]-points[0]

View File

@ -44,20 +44,22 @@ static void transition_scene_on_walk_finish(struct script *scr, int wakeupMode,
assert(wakeupMode == SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED); assert(wakeupMode == SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED);
} }
void onclick(int curscene, int objid) { static void start_player_walk_to_point_then_transition_scene(int x, int y, int scene) {
switch(curscene) { start_player_walk_to_point(x, y);
case SCENE_LOBBY:
switch(objid) {
case OBJID_DOOR_TO_MANAGERS_OFFICE_FROM_LOBBY:
{
start_player_walk_to_point(312, 441);
struct script *scr = scene_add_script(OBJID_PLAYER_WALK_TO_DOOR_SCRIPT, true); struct script *scr = scene_add_script(OBJID_PLAYER_WALK_TO_DOOR_SCRIPT, true);
scr->wakeupMode = SCRIPT_WAKEUP_OTHER_SCRIPT; scr->wakeupMode = SCRIPT_WAKEUP_OTHER_SCRIPT;
scr->wakeupArg1 = OBJID_PLAYER_WALK_SCRIPT; scr->wakeupArg1 = OBJID_PLAYER_WALK_SCRIPT;
scr->wakeupFn = transition_scene_on_walk_finish; 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; return;
} }
break; break;

View File

@ -41,28 +41,22 @@
<polygon points="0,0 135,85 191,-1"/> <polygon points="0,0 135,85 191,-1"/>
</object> </object>
<object id="11" x="213" y="441"> <object id="11" x="213" y="441">
<polygon points="0,0 -211,301 135,85"/> <polygon points="0,0 -211,301 134,176 135,85"/>
</object>
<object id="12" x="2" y="741">
<polygon points="0,1 345,-124 346,-215"/>
</object> </object>
<object id="13" x="2" y="742"> <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>
<object id="14" x="2" y="799"> <object id="14" x="2" y="799">
<polygon points="0,0 1278,1 345,-182"/> <polygon points="330,1 959,1 933,-174 345,-182"/>
</object>
<object id="15" x="348" y="617">
<polygon points="-1,0 587,8 932,183"/>
</object> </object>
<object id="16" x="860" y="440"> <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>
<object id="17" x="937" y="558"> <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>
<object id="18" x="939" y="559"> <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> </object>
</objectgroup> </objectgroup>
</map> </map>

View File

@ -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>

View File

@ -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. // 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. // 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 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; double edgeval = fx*tri->edges[i].a + fy*tri->edges[i].b + tri->edges[i].c;
if(edgeval <= 0) { if(edgeval <= 0) {
// edge.a,b is normalized so that moving 1 unit of a and 1 unit of b changes edgeval by 1. // 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) { switch(edgeHitMask) {
case 3: // hit edge 0-1 and 1-2 case 3: // hit edge 0-1 and 1-2
*x = tri->points[1].x; *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; *x = tri->points[2].x;
*y = tri->points[2].y; *y = tri->points[2].y;
return; return;
} }*/
// may be slightly outside of triangle due to rounding errors, but good enough for us // may be slightly outside of triangle due to rounding errors, but good enough for us
*x = (int)(fx + 0.5); *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. // Returns distance from triangle, 0 if inside.
static double is_point_in_tri(int x, int y, const struct navmesh_tri *tri) { 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++) for(int edge = 0; edge < 3; edge++)
edgedist[edge] = -(x*tri->edges[edge].a + y*tri->edges[edge].b + tri->edges[edge].c); 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 && 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[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[2] >= 0 && edgedist[0] >= 0) return point_to_point_dist(x, y, &tri->points[2]);
if(edgedist[0] >= 0) return edgedist[0]; if(edgedist[0] >= 0) return edgedist[0];
if(edgedist[1] >= 0) return edgedist[1]; 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? return 0; // branch should be unreachable; maybe within epsilon of triangle border?
} }
static int find_navmesh_tri(int x, int y, int tolerance) { 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 { } else {
nextTri = cur_navmesh->pathfindgrid[player_walk_script->targetNavmeshTri * cur_navmesh->num_tris + nmtri]; nextTri = cur_navmesh->pathfindgrid[player_walk_script->targetNavmeshTri * cur_navmesh->num_tris + nmtri];
nextX = -1; 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) { if(cur_navmesh->tris[nmtri].edges[edge].other_tri == nextTri) {
nextX = cur_navmesh->tris[nmtri].edges[edge].center.x; nextX = cur_navmesh->tris[nmtri].edges[edge].center.x;
nextY = cur_navmesh->tris[nmtri].edges[edge].center.y; nextY = cur_navmesh->tris[nmtri].edges[edge].center.y;