Player character walks around a navmesh on touch.

master
immibis 2025-02-18 02:04:17 +01:00
parent 621d92a554
commit 04a3a748ce
11 changed files with 493 additions and 22 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -2,7 +2,9 @@ PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
OBJS = pinetab2_framework.o game.o
SPRITES = $(patsubst $(PROJECT_ROOT)sprites/%.xcf,%,$(wildcard $(PROJECT_ROOT)sprites/*.xcf))
NAVMESHES = $(patsubst $(PROJECT_ROOT)navmesh/%.tmx,%,$(wildcard $(PROJECT_ROOT)navmesh/*.tmx))
BUILD_MODE := debug
ifeq ($(BUILD_MODE),debug)
CFLAGS += -g
else ifeq ($(BUILD_MODE),run)
@ -16,12 +18,13 @@ else
$(error Build mode $(BUILD_MODE) not supported by this Makefile)
endif
CXX := aarch64-linux-gnu-$(CXX)
CC := aarch64-linux-gnu-$(CC)
CXX := aarch64-linux-gnu-g++
CC := aarch64-linux-gnu-gcc
OBJCOPY := aarch64-linux-gnu-objcopy -O elf64-littleaarch64
CFLAGS += -I/usr/include
OBJS += $(patsubst %,sprite_%.o,$(SPRITES))
OBJS += $(patsubst %,navmesh_%.o,$(NAVMESHES))
all: pinetab2_framework $(patsubst %,%.png,$(SPRITES))
@ -35,12 +38,15 @@ pinetab2_framework: $(OBJS)
%.o: $(PROJECT_ROOT)%.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
%.o: %.c
$(CXX) -c $(CFLAGS) $(CXXFLAGS) $(CPPFLAGS) -o $@ $< -iquote "$(PROJECT_ROOT)"
clean:
rm -fr pinetab2_framework $(OBJS) $(EXTRA_CLEAN)
sprite_%.o: $(PROJECT_ROOT)sprites/%.xcf
gimp -in -b '(let ((image (car (gimp-xcf-load 0 "$<" "$(notdir $<)")))) ;\
(gimp-image-scale image 1280 800) ;\
;(gimp-image-scale image 1280 800) ;\
(gimp-image-rotate image ROTATE-90) ;\
(let ((layer (car (gimp-image-merge-visible-layers image CLIP-TO-IMAGE)))) ;\
(plug-in-colors-channel-mixer RUN-NONINTERACTIVE image layer 0 0 0 1 0 1 0 1 0 0) ; swap red and blue channels \
@ -51,9 +57,12 @@ sprite_%.o: $(PROJECT_ROOT)sprites/%.xcf
rm $(patsubst %.o,%.raw,$@)
@# symbols are _binary_sprite_%_raw_start / _end / _size
navmesh_%.c: $(PROJECT_ROOT)navmesh/%.tmx $(PROJECT_ROOT)compile_navmesh.py
python3 "$(PROJECT_ROOT)compile_navmesh.py" "$<" "$@" "$(basename $@)"
%.png: $(PROJECT_ROOT)sprites/%.xcf
gimp -in -b '(let ((image (car (gimp-xcf-load 0 "$<" "$(notdir $<)")))) ;\
(gimp-image-scale image 1280 800) ;\
;(gimp-image-scale image 1280 800) ;\
(let ((layer (car (gimp-image-merge-visible-layers image CLIP-TO-IMAGE)))) ;\
(file-png-save 1 image layer "$@" "$@" 0 9 0 0 0 0 0) ;\
) ;\

110
compile_navmesh.py Normal file
View File

@ -0,0 +1,110 @@
#!/usr/bin/python3
import lxml.etree, math, sys
_, inpath, outpath, basename = sys.argv
tree = lxml.etree.parse(inpath)
tris = []
edge2tri = {}
for polygon in tree.findall("objectgroup[@name='navmesh']/object/polygon"):
basex = int(polygon.getparent().attrib["x"])
basey = int(polygon.getparent().attrib["y"])
points=[]
for point in polygon.attrib["points"].split(" "):
x,y=point.split(",")
x,y=int(x)+basex,int(y)+basey
points.append((x,y))
#print(points)
ax,ay=points[0]
bx,by=points[1]
cx,cy=points[2]
abx,aby=bx-ax,by-ay
acx,acy=cx-ax,cy-ay
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
assert len(points)==3, "triangles only"
tri_id = len(tris)
tris.append(points)
for edge in [(points[0],points[1]), (points[1],points[2]), (points[2],points[0])]:
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)
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_matrix = [[255 for _1 in range(len(tris))] for _2 in range(len(tris))]
# 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.
openset = [dest]
while len(openset)>0:
cur, openset = openset[0], openset[1:]
for adjacent in pathfind_edges[cur]:
if adjacent != dest and pathfind_matrix[adjacent][dest] == 255:
pathfind_matrix[adjacent][dest] = cur
openset.append(adjacent)
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
# 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
# Line equation >0 for points inside the triangle.
# Normalize so that P^2+Q^2=1 so that the equation result tells us the distance. This can be used to find the closest triangle to a point.
norm=1/math.sqrt(P*P+Q*Q)
P *= norm
Q *= norm
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]
else:
other_tri_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("\t\t},\n")
out.write("\t\t{" + ",".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("\t")
for source in range(len(tris)):
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")

27
compiled_structures.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef COMPILED_STRUCTURES_H_
#define COMPILED_STRUCTURES_H_
struct navmesh_point {
int x, y;
};
struct navmesh_tri_edge {
float a,b,c; // line equation a*x + b*y + c > 0 if point is in triangle, and value gives distance from line.
int other_tri; // -1 if this edge is a border of navmesh, else triangle it connects to
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];
// edges[0]: points[0]-points[1]
// edges[1]: points[1]-points[2]
// edges[2]: points[2]-points[0]
};
struct navmesh {
int num_tris;
const struct navmesh_tri *tris;
const unsigned char *pathfindgrid; // [dest*num_tris + source] gives index of next tri. Placeholder value is used when dest==source.
};
#endif /* COMPILED_STRUCTURES_H_ */

View File

@ -1,4 +1,4 @@
#define MAX_GAME_OBJECTS 300
#define MAX_GAME_OBJECTS 100
extern struct object {
int id; // 0 if slot unused
int x;
@ -8,15 +8,51 @@ extern struct object {
const char *pixels;
} objects[MAX_GAME_OBJECTS];
#define MAX_GAME_SCRIPTS 100
typedef void (*script_wake_fn)(struct script *scr, int wakeupMode, int arg1, int arg2, int arg3, int arg4);
#define SCRIPT_HEADER \
int id /* 0 if slot unused */; \
int wakeupMode; \
script_wake_fn wakeupFn; \
int wakeupArg1; /* for SCRIPT_WAKEUP_OTHER_SCRIPT */
extern struct script {
SCRIPT_HEADER
int vars[100];
} scripts[MAX_GAME_SCRIPTS];
struct script_player_walk {
SCRIPT_HEADER
int targetX;
int targetY;
int targetNavmeshTri;
int currentNavmeshTri;
};
#define SCRIPT_WAKEUP_VIDEO_FRAME 1
#define SCRIPT_WAKEUP_OTHER_SCRIPT 2
#define SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED 3 // Delivered to script that registers for SCRIPT_WAKEUP_OTHER_SCRIPT if that script is interrupted instead of completing.
// engine
void transition_scene(int scene); // Immediately stops all event processing for current scene, which is unloaded before this function returns
void scene_add_clickrect(int id, int x, int y, int width, int height);
void scene_add_object(int id, int x, int y, int width, int height, const char *pixels);
void scene_set_navmesh(struct navmesh *navmesh);
struct object *find_object_by_id(int id);
struct script *scene_add_script(int id, bool interrupt_existing);
struct script *scene_get_script(int id);
bool deliver_script_wakeup(int wakeupMode, int wakeupArg1, int wakeupType, int arg1, int arg2, int arg3, int arg4); // deliver to scripts with given wakeupMode and wakeupArg1. Returns true if scene changed.
// game-specific constants
#define SCENE_LOBBY 1
#define SCENE_MANAGERS_OFFICE 2
// The player ID and walk script are globally relevant
#define OBJID_PLAYER 4
#define OBJID_PLAYER_WALK_SCRIPT 5
// game.cpp
void scene_setup(int scene);
void onclick(int curscene, int objid);

View File

@ -6,19 +6,28 @@
extern const char _binary_sprite_lobby_raw_start[];
extern const char _binary_sprite_managers_office_raw_start[];
extern const char _binary_sprite_stickman_raw_start[];
extern struct navmesh navmesh_lobby;
#define OBJID_BACKGROUND 1
#define OBJID_DOOR_TO_MANAGERS_OFFICE_FROM_LOBBY 2
#define OBJID_DOOR_TO_LOBBY_FROM_MANAGERS_OFFICE 3
// #define OBJID_PLAYER 4
// #define SCRIPTID_PLAYER_WALK 5
void scene_setup(int scene) {
switch(scene) {
case SCENE_LOBBY:
scene_add_object(OBJID_BACKGROUND, 0, 0, BGWIDTH, BGHEIGHT, _binary_sprite_lobby_raw_start);
scene_add_object(OBJID_DOOR_TO_MANAGERS_OFFICE_FROM_LOBBY, 273, 313, 76, 128, nullptr);
scene_add_object(OBJID_PLAYER, 424, 575, 51, 111, _binary_sprite_stickman_raw_start);
scene_set_navmesh(&navmesh_lobby);
break;
case SCENE_MANAGERS_OFFICE:
scene_add_object(OBJID_BACKGROUND, 0, 0, BGWIDTH, BGHEIGHT, _binary_sprite_managers_office_raw_start);
scene_add_object(OBJID_DOOR_TO_LOBBY_FROM_MANAGERS_OFFICE, 273, 313, 76, 128, nullptr);
scene_add_object(OBJID_PLAYER, 424, 575, 51, 111, _binary_sprite_stickman_raw_start);
break;
}
}
@ -41,3 +50,4 @@ void onclick(int curscene, int objid) {
break;
}
}

68
navmesh/lobby.tmx Normal file
View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.9" tiledversion="1.9.2" orientation="orthogonal" renderorder="right-down" width="40" height="25" tilewidth="32" tileheight="32" infinite="0" nextlayerid="5" nextobjectid="19">
<layer id="1" name="Tile Layer 1" width="40" height="25">
<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,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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/lobby.png" width="1280" height="800"/>
</imagelayer>
<objectgroup id="3" name="Object Layer 1">
<object id="1" x="273" y="313" width="76" height="128"/>
<object id="3" x="908" y="322" width="53" height="117"/>
</objectgroup>
<objectgroup id="4" name="navmesh">
<object id="10" x="213" y="441">
<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"/>
</object>
<object id="13" x="2" y="742">
<polygon points="0,0 0,57 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"/>
</object>
<object id="16" x="860" y="440">
<polygon points="0,0 77,118 180,0"/>
</object>
<object id="17" x="937" y="558">
<polygon points="0,0 343,242 103,-118"/>
</object>
<object id="18" x="939" y="559">
<polygon points="-2,-1 -4,66 341,241"/>
</object>
</objectgroup>
</map>

View File

@ -17,14 +17,22 @@
#include <sys/poll.h>
#include <inttypes.h>
#include "engine.h"
#include "compiled_structures.h"
#define PLAYFIELD_WIDTH 1280
#define PLAYFIELD_HEIGHT 800
static int touchscreen_fd;
static struct navmesh null_navmesh = {0};
struct navmesh *cur_navmesh = &null_navmesh;
struct object objects[MAX_GAME_OBJECTS];
struct script scripts[MAX_GAME_SCRIPTS];
bool need_rerender = false;
#define MAX_MT_SLOTS 16
static struct mtslot {
int x, y, touch, lasttouch;
@ -131,7 +139,8 @@ struct sprite {
static uint32_t *curfb; // used during render cycle
static void blit(int x, int y, const struct sprite *spr) {
uint32_t *inbase = spr->pixels;
printf("blit %dx%d at %d,%d\n", spr->width, spr->height, x, y);
//printf("blit %dx%d at %d,%d\n", spr->width, spr->height, x, y);
y = PLAYFIELD_HEIGHT - spr->height - y;
if(y >= PLAYFIELD_HEIGHT || x >= PLAYFIELD_WIDTH || y <= -spr->height || x <= -spr->width) return;
if(spr->height > PLAYFIELD_HEIGHT) error(1, 0, "sprite bigger than playfield not supported"); // would need to clip top and bottom simultaneously - not implemented for now
@ -153,8 +162,9 @@ static void blit(int x, int y, const struct sprite *spr) {
}
}
struct object objects[MAX_GAME_OBJECTS];
bool need_rerender = false;
void scene_set_navmesh(struct navmesh *navmesh) {
cur_navmesh = navmesh;
}
static uint32_t scene_generation_count = 0;
static int curscene;
@ -162,6 +172,8 @@ void transition_scene(int scene) {
scene_generation_count++;
curscene = scene;
memset(objects, 0, sizeof objects);
memset(scripts, 0, sizeof scripts); // script state is local to scene, so no cancel notification
cur_navmesh = &null_navmesh;
scene_setup(scene);
}
@ -182,6 +194,106 @@ void scene_add_object(int id, int x, int y, int width, int height, const char *p
error(1, 0, "too many game objects");
}
struct object *find_object_by_id(int id) {
for(int i = 0; i < MAX_GAME_OBJECTS; i++) {
if(objects[i].id == id)
return &objects[i];
}
return nullptr;
}
struct script *scene_add_script(int id, bool interrupt_existing) {
if(interrupt_existing && id != OBJID_PLAYER_WALK_SCRIPT) error(1, 0, "scene_add_script: interrupt not implemented");
for(int i = 0; i < MAX_GAME_SCRIPTS; i++) {
if(scripts[i].id == id && interrupt_existing) {
// interrupt existing script?
// player walk script is safe to trivially cancel, at least
scripts[i].id = 0;
}
if(scripts[i].id == 0) {
memset(&scripts[i], 0, sizeof(scripts[i]));
scripts[i].id = id;
return &scripts[i];
}
}
error(1, 0, "too many game scripts");
__builtin_unreachable();
}
static void closest_point_inside_tri(int *x, int *y, const struct navmesh_tri *tri) {
double fx = *x, fy = *y;
int edgeHitMask = 0;
// Moving a point outside of one half-plane can move it into another half-plane, so check again.
// 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++) {
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.
fx -= tri->edges[i].a*edgeval;
fy -= tri->edges[i].b*edgeval;
edgeHitMask |= 1 << i;
}
}
}
switch(edgeHitMask) {
case 3: // hit edge 0-1 and 1-2
*x = tri->points[1].x;
*y = tri->points[1].y;
return;
case 5: // hit edge 0-1 and 2-0
*x = tri->points[0].x;
*y = tri->points[0].y;
return;
case 6: // hit edge 1-2 and 2-0
*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);
*y = (int)(fy + 0.5);
}
static double point_to_point_dist(int x, int y, const struct navmesh_point *pt) {
x -= pt->x;
y -= pt->y;
return sqrt(x*x + y*y);
}
// 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
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[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];
return 0; // branch should be unreachable; maybe within epsilon of triangle border?
}
static int find_navmesh_tri(int x, int y, int tolerance) {
int best_tri = -1;
double best_dist = tolerance;
for(int i = 0; i < cur_navmesh->num_tris; i++) {
double dist = is_point_in_tri(x, y, &cur_navmesh->tris[i]);
if(dist == 0)
return i;
if(dist < best_dist) {
best_dist = dist;
best_tri = i;
}
}
return best_tri;
}
static void update_player_walk_script_on_frame(struct script *scr, int wakeupMode, int, int, int, int);
void handle_tap(int x, int y) {
uint32_t gencount = scene_generation_count;
for(int i = 0; i < MAX_GAME_OBJECTS; i++) {
@ -190,7 +302,107 @@ void handle_tap(int x, int y) {
onclick(curscene, objects[i].id);
}
}
struct script_player_walk *player_walk_script = (struct script_player_walk *)scene_add_script(OBJID_PLAYER_WALK_SCRIPT, true);
struct object *player = find_object_by_id(OBJID_PLAYER);
if(player) {
int destTri = find_navmesh_tri(x, y, 50);
if(destTri != -1) {
int targetX = x, targetY = y;
closest_point_inside_tri(&targetX, &targetY, &cur_navmesh->tris[destTri]);
player_walk_script->targetX = targetX;
player_walk_script->targetY = targetY;
player_walk_script->targetNavmeshTri = destTri;
player_walk_script->wakeupMode = SCRIPT_WAKEUP_VIDEO_FRAME;
player_walk_script->wakeupFn = update_player_walk_script_on_frame;
int sourceTri = find_navmesh_tri(player->x+player->width*2, player->y+player->height, 1000000);
player_walk_script->currentNavmeshTri = sourceTri;
printf("set course from triangle %d to %d\n", sourceTri, destTri);
} else {
player_walk_script->id = 0;
}
} else {
player_walk_script->id = 0;
}
}
static int move_towards(int cur, int target, int maxrate) {
if(target > cur+maxrate) return cur+maxrate;
if(target < cur-maxrate) return cur-maxrate;
return target;
}
static void update_player_walk_script_on_frame(struct script *scr, int wakeupMode, int, int, int, int) {
struct script_player_walk *player_walk_script = (struct script_player_walk *)scr;
if(!player_walk_script) return;
struct object *player = find_object_by_id(OBJID_PLAYER);
if(!player) {
player_walk_script->id = 0;
return;
}
if(player_walk_script->currentNavmeshTri == -1) {
// something weird happened. just teleport player and end movement.
player->x = player_walk_script->targetX - player->width/2;
player->y = player_walk_script->targetY - player->height;
player_walk_script->id = 0;
need_rerender = true;
} else {
int x = player->x+player->width/2;
int y = player->y+player->height;
int nmtri = player_walk_script->currentNavmeshTri;
int nextX;
int nextY;
int nextTri = -1;
if(nmtri == player_walk_script->targetNavmeshTri) {
nextX = player_walk_script->targetX;
nextY = player_walk_script->targetY;
} else {
nextTri = cur_navmesh->pathfindgrid[player_walk_script->targetNavmeshTri * cur_navmesh->num_tris + nmtri];
nextX = -1;
for(int edge = 0; edge < 3; 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;
goto center_found;
}
}
// shouldn't happen
player_walk_script->id = 0;
return;
center_found:;
}
if(nextX == x && nextY == y) {
printf("in triangle %d, next triangle %d\n", nmtri, nextTri);
if(x == player_walk_script->targetX && y == player_walk_script->targetY) {
player_walk_script->id = 0;
} else {
player_walk_script->currentNavmeshTri = nextTri; // even if -1
}
} else {
x = move_towards(x, nextX, 5);
y = move_towards(y, nextY, 2);
player->x = x - player->width/2;
player->y = y - player->height;
}
need_rerender = true;
}
}
bool deliver_script_wakeup(int wakeupMode, int wakeupArg1, int wakeupType, int arg1, int arg2, int arg3, int arg4) {
uint32_t gen = scene_generation_count;
for(int i = 0; i < MAX_GAME_SCRIPTS; i++) {
if(scripts[i].id && scripts[i].wakeupMode == wakeupMode && scripts[i].wakeupArg1 == wakeupArg1) {
scripts[i].wakeupFn(&scripts[i], wakeupType, arg1, arg2, arg3, arg4);
if(scene_generation_count != gen) return true; // scene changed - abort early
}
}
return false;
}
int main(int argc, char **argv) {
@ -296,7 +508,7 @@ int main(int argc, char **argv) {
uint64_t dummy; // don't care about expiration count
if(read(timerfd, &dummy, sizeof dummy) < 0) error(1, errno, "read (timerfd)");
current_animframe++;
printf("animation frame %u\n", (unsigned int)current_animframe);
//printf("animation frame %u\n", (unsigned int)current_animframe);
need_rerender = true;
}
if(polls[2].revents & POLLIN) {
@ -319,20 +531,18 @@ int main(int argc, char **argv) {
}
}
if(!awaiting_vblank) {
// return value ignored
// What is this really doing? It's giving scripts the opportunity to trigger a video frame.
// But if no script actually does, this can run over and over many times in the same video frame.
// It's not saying a video frame happened, it's saying one can happen.
deliver_script_wakeup(SCRIPT_WAKEUP_VIDEO_FRAME, 0, SCRIPT_WAKEUP_VIDEO_FRAME, 0, 0, 0, 0);
}
if(need_rerender && !awaiting_vblank) {
cur_buffer = 1-cur_buffer; // for testing, just keep writing into the same buffer
curfb = fbs[cur_buffer];
//printf("%d %d\n", sizeof basement_pixels, 1280*800*4);
/*struct sprite bgsprite = {
.width = 1280,
.height = 800,
.pixels = (uint32_t*)_binary_sprite_lobby_raw_start
};
memset(curfb, 0, 1280*800*4);
blit(mtslots[0].y-640, mtslots[0].x-400, &bgsprite);*/
for(int i = 0; i < MAX_GAME_OBJECTS; i++) {
if(objects[i].id != 0 && objects[i].pixels) {
struct sprite spr = {.width = objects[i].width, .height = objects[i].height, .pixels = (uint32_t*)objects[i].pixels};

Binary file not shown.

Binary file not shown.

BIN
sprites/stickman.xcf Normal file

Binary file not shown.