Add collision height mask generation script. YAY!!!

This commit is contained in:
Lucas S. Vieira 2024-07-31 23:54:35 -03:00
parent 8c36d08617
commit b3e697c192
10 changed files with 1557 additions and 17 deletions

View file

@ -1,21 +1,21 @@
cmake_minimum_required(VERSION 3.21) cmake_minimum_required(VERSION 3.21)
project(SonicEngine project(PSXEngine
LANGUAGES C CXX ASM LANGUAGES C CXX ASM
VERSION 1.0.0 VERSION 1.0.0
DESCRIPTION "Sonic Engine for PSX" DESCRIPTION "Platformer Engine for PSX"
HOMEPAGE_URL "https://luksamuk.codes") HOMEPAGE_URL "https://luksamuk.codes")
file(GLOB ENGINE_SRC ${CMAKE_CURRENT_LIST_DIR}/src/*.c) file(GLOB ENGINE_SRC ${CMAKE_CURRENT_LIST_DIR}/src/*.c)
psn00bsdk_add_executable(sonic psn00bsdk_add_executable(engine
GPREL GPREL
${ENGINE_SRC}) ${ENGINE_SRC})
target_include_directories(sonic PUBLIC target_include_directories(engine PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include/>) $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include/>)
psn00bsdk_add_cd_image(iso sonicengine psn00bsdk_add_cd_image(iso engine
iso.xml iso.xml
DEPENDS sonic system.cnf) DEPENDS engine system.cnf)

View file

@ -1,19 +1,19 @@
export PATH := /opt/psn00bsdk/bin:$(PATH) export PATH := /opt/psn00bsdk/bin:$(PATH)
export PSN00BSDK_LIBS := /opt/psn00bsdk/lib/libpsn00b export PSN00BSDK_LIBS := /opt/psn00bsdk/lib/libpsn00b
.PHONY: clean ./build/sonicengine.cue run configure chd .PHONY: clean ./build/engine.cue run configure chd
all: ./build/sonicengine.cue all: ./build/engine.cue
dir: ./build dir: ./build
chd: sonicengine.chd chd: engine.chd
run: ./build/sonicengine.cue run: ./build/engine.cue
pcsx-redux-appimage -gdb -run -interpreter -fastboot -stdout -iso ./build/sonicengine.cue pcsx-redux-appimage -gdb -run -interpreter -fastboot -stdout -iso ./build/engine.cue
./build/sonicengine.cue: ./build ./build/engine.cue: ./build
cmake --build ./build cmake --build ./build
sonicengine.chd: ./build/sonicengine.cue engine.chd: ./build/engine.cue
tochd -d . -- $< tochd -d . -- $<
./build: configure ./build: configure

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.11.0" name="tiles16" tilewidth="16" tileheight="16" tilecount="48" columns="16"> <tileset version="1.10" tiledversion="1.11.0" name="tiles16" tilewidth="16" tileheight="16" tilecount="48" columns="16">
<editorsettings>
<export target="collision16.json" format="json"/>
</editorsettings>
<image source="tiles16.png" width="256" height="48"/> <image source="tiles16.png" width="256" height="48"/>
<tile id="1"> <tile id="1">
<objectgroup draworder="index" id="2"> <objectgroup draworder="index" id="2">

View file

@ -3,7 +3,7 @@
<track type="data"> <track type="data">
<identifiers <identifiers
system = "PLAYSTATION" system = "PLAYSTATION"
volume = "SONICENGINE" volume = "SCUS-00001"
volume_set = "SONICENGINE" volume_set = "SONICENGINE"
publisher = "luksamuk" publisher = "luksamuk"
data_preparer = "PSN00BSDK ${PSN00BSDK_VERSION}" data_preparer = "PSN00BSDK ${PSN00BSDK_VERSION}"
@ -13,7 +13,7 @@
<directory_tree> <directory_tree>
<file name="SYSTEM.CNF" type="data" source="${PROJECT_SOURCE_DIR}/system.cnf" /> <file name="SYSTEM.CNF" type="data" source="${PROJECT_SOURCE_DIR}/system.cnf" />
<file name="SONIC.EXE" type="data" source="sonic.exe" /> <file name="ENGINE.EXE" type="data" source="engine.exe" />
<dir name="SPRITES"> <dir name="SPRITES">
<file name="SONIC.TIM" <file name="SONIC.TIM"
@ -35,6 +35,9 @@
<file name="MAP16.MAP" <file name="MAP16.MAP"
type="data" type="data"
source="${PROJECT_SOURCE_DIR}/assets/levels/R0/MAP16.MAP" /> source="${PROJECT_SOURCE_DIR}/assets/levels/R0/MAP16.MAP" />
<file name="R0.COLLISION"
type="data"
source="${PROJECT_SOURCE_DIR}/assets/levels/R0/R0.COLLISION" />
<file name="TILES.TIM" <file name="TILES.TIM"
type="data" type="data"
source="${PROJECT_SOURCE_DIR}/assets/levels/R0/TILES.TIM" /> source="${PROJECT_SOURCE_DIR}/assets/levels/R0/TILES.TIM" />

View file

@ -255,7 +255,7 @@ render_lvl(
{ {
int16_t cx = (cam_x >> 12), int16_t cx = (cam_x >> 12),
cy = (cam_y >> 12); cy = (cam_y >> 12);
_render_layer(lvl, map128, map16, cx, cy, 3, 0); _render_layer(lvl, map128, map16, cx, cy, 4, 0);
_render_layer(lvl, map128, map16, cx, cy, 2, 1); _render_layer(lvl, map128, map16, cx, cy, 2, 1);
DR_TPAGE *tpage = get_next_prim(); DR_TPAGE *tpage = get_next_prim();

View file

@ -1,4 +1,4 @@
BOOT=cdrom:\SONIC.EXE;1 BOOT=cdrom:\ENGINE.EXE;1
TCB=4 TCB=4
EVENT=10 EVENT=10
STACK=801FFFF0 STACK=801FFFF0

218
tools/cookcollision.py Executable file
View file

@ -0,0 +1,218 @@
#!/bin/env python
# cookcollision.py
# Cook 16x16 tile collision from Tiled tile data.
# Make sure you exported a 16x16 tile with proper collision data.
import json
import sys
from ctypes import c_ushort, c_ubyte
from enum import Enum
from pprint import pp as pprint
from math import sqrt
c_ushort = c_ushort.__ctype_be__
class Direction(Enum):
DOWN = 0
UP = 1
LEFT = 2
RIGHT = 3
def sign(p1, p2, p3):
return ((p1[0] - p3[0]) * (p2[1] - p3[1])) - ((p2[0] - p3[0]) * (p1[1] - p3[1]))
def point_in_triangle(p, v1, v2, v3):
d1 = sign(p, v1, v2)
d2 = sign(p, v2, v3)
d3 = sign(p, v3, v1)
has_neg = (d1 < 0) or (d2 < 0) or (d3 < 0)
has_pos = (d1 > 0) or (d2 > 0) or (d3 > 0)
return not (has_neg and has_pos)
def is_left(p0, p1, p2):
return ((p1[0] - p0[0]) * (p2[1] - p0[1])) - ((p2[0] - p0[0]) * (p1[1] - p0[1]))
def point_in_square(p, v1, v2, v3, v4):
return (
(is_left(v1, v2, p) > 0)
and (is_left(v2, v3, p) > 0)
and (is_left(v3, v4, p) > 0)
and (is_left(v4, v1, p) > 0)
)
def point_in_geometry(p, geometry, points):
if geometry == "quad":
return point_in_square(p, points[0], points[1], points[2], points[3])
return point_in_triangle(p, points[0], points[1], points[2])
def get_height_mask(d: Direction, geometry, points):
# Perform iterative linecast.
# Linecast checks for a point within a geometry starting at a height
# of 15 until 1 (inclusive). 0 means no collision at that height.
# We do that for each X spot on our geometry.
# Of course, if pointing downwards, we go from left to right, top to bottom.
# If using any other direction... flip it accordingly.
heightmask = []
for pos in range(16):
found = False
for height in reversed(range(1, 16)):
if d == Direction.DOWN:
x = pos
y = 16 - height
elif d == Direction.UP:
x = 15 - pos
y = height
elif d == Direction.LEFT:
x = height
y = pos
elif d == Direction.RIGHT:
x = 16 - height
y = 15 - pos
if point_in_geometry([x, y], geometry, points):
found = True
heightmask.append(height)
break
if not found:
heightmask.append(0)
return heightmask
def parse_masks(tiles):
res = []
for tile in tiles:
geometry = tile.get("type")
points = tile.get("points")
id = tile.get("id")
res.append(
{
"id": tile.get("id"),
"masks": {
"floor": get_height_mask(Direction.DOWN, geometry, points),
"ceiling": get_height_mask(Direction.UP, geometry, points),
"rwall": get_height_mask(Direction.RIGHT, geometry, points),
"lwall": get_height_mask(Direction.LEFT, geometry, points),
},
}
)
return res
def load_json(filename):
with open(filename) as fp:
return json.load(fp)
def parse_json(j):
tiles = j.get("tiles")
res = []
for tile in tiles:
grp = tile.get("objectgroup")
if grp:
objs = grp.get("objects")
if objs:
o = objs[0]
id = tile.get("id")
x = round(o.get("x"), 0)
y = round(o.get("y"), 0)
if o.get("polygon"):
# Treat as triangle
vertices = o.get("polygon")
points = [
# xy0
[
round(vertices[0].get("x"), 0) + x,
round(vertices[0].get("y"), 0) + y,
],
# xy1
[
round(vertices[1].get("x"), 0) + x,
round(vertices[1].get("y"), 0) + y,
],
# xy2
[
round(vertices[2].get("x"), 0) + x,
round(vertices[2].get("y"), 0) + y,
],
]
res.append(
{
"id": id,
"type": "triangle",
"points": points,
}
)
else:
# Treat as quad
width = round(o.get("width"), 0)
height = round(o.get("height"), 0)
points = [
# xy0
[x, y],
# xy1
[x + width, y],
# xy2
[x, y + height],
# xy3
[x + width, x + height],
]
res.append(
{
"id": id,
"type": "quad",
"points": points,
}
)
print(f"Number of collidable tiles: {len(res)}")
return res
def write_mask_data(f, mask_data):
# Join mask data. We have 16 heights; turn them into 8 bytes
data = []
for i in range(0, 16, 2):
a = (mask_data[i] & 0x0F) << 4
b = mask_data[i + 1] & 0x0F
data.append(c_ubyte(a | b))
for b in data:
f.write(b)
# Binary layout:
# 1. Number of tiles (ushort, 2 bytes)
# 2. Tile data
# 2.1. tile id (ushort, 2 bytes)
# 2.2. Floor mode data (8 bytes)
# 2.3. Right wall mode data (8 bytes)
# 2.4. Ceiling mode data (8 bytes)
# 2.5. Left wall mode data (8 bytes)
def write_file(f, tile_data):
f.write(c_ushort(len(tile_data)))
for tile in tile_data:
f.write(c_ushort(tile.get("id")))
write_mask_data(f, tile.get("masks").get("floor"))
write_mask_data(f, tile.get("masks").get("rwall"))
write_mask_data(f, tile.get("masks").get("ceiling"))
write_mask_data(f, tile.get("masks").get("lwall"))
def main():
jsonfile = sys.argv[1]
outfile = sys.argv[2]
j = load_json(jsonfile)
parsed = parse_json(j)
masks = parse_masks(parsed)
# pprint(list(filter(lambda x: x.get("id") == 4, masks))[0])
with open(outfile, "wb") as f:
write_file(f, masks)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,39 @@
// -*- mode: c; -*-
bitfield HeightMask {
unsigned c0 : 4;
unsigned c1 : 4;
unsigned c2 : 4;
unsigned c3 : 4;
unsigned c4 : 4;
unsigned c5 : 4;
unsigned c6 : 4;
unsigned c7 : 4;
unsigned c8 : 4;
unsigned c9 : 4;
unsigned cA : 4;
unsigned cB : 4;
unsigned cC : 4;
unsigned cD : 4;
unsigned cE : 4;
unsigned cF : 4;
};
struct Collision {
be u16 tile_id;
// Height mask downwards (left to right)
be HeightMask floor;
// Height mask to right (bottom to top)
be HeightMask rwall;
// Height mask upwards (right to left)
be HeightMask ceiling;
// Height mask to left (top to bottom)
be HeightMask lwall;
};
struct TileData {
be u16 num_tiles;
Collision tiles[num_tiles];
};
TileData tiles @ 0x0;