mirror of
https://github.com/luksamuk/engine-psx.git
synced 2025-04-28 21:38:02 +03:00
Add collision height mask generation script. YAY!!!
This commit is contained in:
parent
8c36d08617
commit
b3e697c192
10 changed files with 1557 additions and 17 deletions
|
@ -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)
|
||||||
|
|
||||||
|
|
14
Makefile
14
Makefile
|
@ -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
|
||||||
|
|
BIN
assets/levels/R0/R0.COLLISION
Normal file
BIN
assets/levels/R0/R0.COLLISION
Normal file
Binary file not shown.
1277
assets/pre/levels/R0/collision16.json
Normal file
1277
assets/pre/levels/R0/collision16.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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">
|
||||||
|
|
7
iso.xml
7
iso.xml
|
@ -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" />
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
218
tools/cookcollision.py
Executable 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()
|
39
tools/layouts/collision.hexpat
Normal file
39
tools/layouts/collision.hexpat
Normal 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;
|
Loading…
Add table
Add a link
Reference in a new issue