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)
|
||||
|
||||
project(SonicEngine
|
||||
project(PSXEngine
|
||||
LANGUAGES C CXX ASM
|
||||
VERSION 1.0.0
|
||||
DESCRIPTION "Sonic Engine for PSX"
|
||||
DESCRIPTION "Platformer Engine for PSX"
|
||||
HOMEPAGE_URL "https://luksamuk.codes")
|
||||
|
||||
file(GLOB ENGINE_SRC ${CMAKE_CURRENT_LIST_DIR}/src/*.c)
|
||||
|
||||
psn00bsdk_add_executable(sonic
|
||||
psn00bsdk_add_executable(engine
|
||||
GPREL
|
||||
${ENGINE_SRC})
|
||||
|
||||
target_include_directories(sonic PUBLIC
|
||||
target_include_directories(engine PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include/>)
|
||||
|
||||
psn00bsdk_add_cd_image(iso sonicengine
|
||||
psn00bsdk_add_cd_image(iso engine
|
||||
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 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
|
||||
chd: sonicengine.chd
|
||||
chd: engine.chd
|
||||
|
||||
run: ./build/sonicengine.cue
|
||||
pcsx-redux-appimage -gdb -run -interpreter -fastboot -stdout -iso ./build/sonicengine.cue
|
||||
run: ./build/engine.cue
|
||||
pcsx-redux-appimage -gdb -run -interpreter -fastboot -stdout -iso ./build/engine.cue
|
||||
|
||||
./build/sonicengine.cue: ./build
|
||||
./build/engine.cue: ./build
|
||||
cmake --build ./build
|
||||
|
||||
sonicengine.chd: ./build/sonicengine.cue
|
||||
engine.chd: ./build/engine.cue
|
||||
tochd -d . -- $<
|
||||
|
||||
./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"?>
|
||||
<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"/>
|
||||
<tile id="1">
|
||||
<objectgroup draworder="index" id="2">
|
||||
|
|
7
iso.xml
7
iso.xml
|
@ -3,7 +3,7 @@
|
|||
<track type="data">
|
||||
<identifiers
|
||||
system = "PLAYSTATION"
|
||||
volume = "SONICENGINE"
|
||||
volume = "SCUS-00001"
|
||||
volume_set = "SONICENGINE"
|
||||
publisher = "luksamuk"
|
||||
data_preparer = "PSN00BSDK ${PSN00BSDK_VERSION}"
|
||||
|
@ -13,7 +13,7 @@
|
|||
<directory_tree>
|
||||
<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">
|
||||
<file name="SONIC.TIM"
|
||||
|
@ -35,6 +35,9 @@
|
|||
<file name="MAP16.MAP"
|
||||
type="data"
|
||||
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"
|
||||
type="data"
|
||||
source="${PROJECT_SOURCE_DIR}/assets/levels/R0/TILES.TIM" />
|
||||
|
|
|
@ -255,7 +255,7 @@ render_lvl(
|
|||
{
|
||||
int16_t cx = (cam_x >> 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);
|
||||
|
||||
DR_TPAGE *tpage = get_next_prim();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
BOOT=cdrom:\SONIC.EXE;1
|
||||
BOOT=cdrom:\ENGINE.EXE;1
|
||||
TCB=4
|
||||
EVENT=10
|
||||
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