diff --git a/CMakeLists.txt b/CMakeLists.txt index 0959ba3..b9f9827 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 $) -psn00bsdk_add_cd_image(iso sonicengine +psn00bsdk_add_cd_image(iso engine iso.xml - DEPENDS sonic system.cnf) + DEPENDS engine system.cnf) diff --git a/Makefile b/Makefile index 4fb54c2..3028fd9 100644 --- a/Makefile +++ b/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 diff --git a/assets/levels/R0/R0.COLLISION b/assets/levels/R0/R0.COLLISION new file mode 100644 index 0000000..b02d496 Binary files /dev/null and b/assets/levels/R0/R0.COLLISION differ diff --git a/assets/pre/levels/R0/collision16.json b/assets/pre/levels/R0/collision16.json new file mode 100644 index 0000000..cc551e5 --- /dev/null +++ b/assets/pre/levels/R0/collision16.json @@ -0,0 +1,1277 @@ +{ "columns":16, + "image":"tiles16.png", + "imageheight":48, + "imagewidth":256, + "margin":0, + "name":"tiles16", + "spacing":0, + "tilecount":48, + "tiledversion":"1.11.0", + "tileheight":16, + "tiles":[ + { + "id":1, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":16, + "id":1, + "name":"", + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":0, + "y":0 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":2, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":16, + "id":1, + "name":"", + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":0, + "y":0 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":3, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":16, + "id":1, + "name":"", + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":0, + "y":0 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":4, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":3, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":16, + "y":16 + }, + { + "x":0, + "y":16 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":0, + "y":0 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":5, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":-16, + "y":16 + }, + { + "x":0, + "y":16 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":16, + "y":0 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":6, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":0, + "y":16 + }, + { + "x":-16, + "y":16 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":16, + "y":0 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":7, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":0, + "y":16 + }, + { + "x":16, + "y":16 + }, + { + "x":16, + "y":8.02222 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":0, + "y":0 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":8, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":-0.0078125, + "y":7.96094 + }, + { + "x":15.9922, + "y":7.96094 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":0, + "y":8 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":9, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":0, + "y":16 + }, + { + "x":16, + "y":16 + }, + { + "x":16, + "y":8.02222 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":0, + "y":0 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":10, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":2, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":-0.0078125, + "y":7.96094 + }, + { + "x":15.9922, + "y":7.96094 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":0, + "y":8 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":11, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":0.0078125, + "y":7.96094 + }, + { + "x":-15.9922, + "y":7.96094 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":16, + "y":8 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":12, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":0, + "y":16 + }, + { + "x":-16, + "y":16 + }, + { + "x":-16, + "y":8.02222 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":16, + "y":0 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":13, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":0.0078125, + "y":7.96094 + }, + { + "x":-15.9922, + "y":7.96094 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":16, + "y":8 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":14, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":0, + "y":16 + }, + { + "x":-16, + "y":16 + }, + { + "x":-16, + "y":8.02222 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":16, + "y":0 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":15, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":0, + "y":16 + }, + { + "x":-16, + "y":16 + }, + { + "x":-16, + "y":8.02222 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":16, + "y":0 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":16, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":-16, + "y":-16 + }, + { + "x":0, + "y":-16 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":16, + "y":16 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":17, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":16, + "id":1, + "name":"", + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":0, + "y":0 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":18, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":-16, + "y":-16 + }, + { + "x":0, + "y":-16 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":16, + "y":16 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":19, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":16, + "id":1, + "name":"", + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":0, + "y":0 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":20, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":16, + "y":-16 + }, + { + "x":0, + "y":-16 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":0, + "y":16 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":21, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":16, + "y":-16 + }, + { + "x":0, + "y":-16 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":0, + "y":16 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":22, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":16, + "id":1, + "name":"", + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":0, + "y":0 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":23, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":16, + "y":-16 + }, + { + "x":0, + "y":-16 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":0, + "y":16 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":24, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":0.0078125, + "y":-7.96094 + }, + { + "x":-15.9922, + "y":-7.96094 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":16, + "y":8 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":25, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":0, + "y":-16 + }, + { + "x":-16, + "y":-16 + }, + { + "x":-16, + "y":-8.02222 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":16, + "y":16 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":26, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":0.0078125, + "y":-7.96094 + }, + { + "x":-15.9922, + "y":-7.96094 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":16, + "y":8 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":27, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":0, + "y":-16 + }, + { + "x":-16, + "y":-16 + }, + { + "x":-16, + "y":-8.02222 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":16, + "y":16 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":28, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":0, + "y":-16 + }, + { + "x":16, + "y":-16 + }, + { + "x":16, + "y":-8.02222 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":0, + "y":16 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":29, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":-0.0078125, + "y":-7.96094 + }, + { + "x":15.9922, + "y":-7.96094 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":0, + "y":8 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":30, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":0, + "y":-16 + }, + { + "x":16, + "y":-16 + }, + { + "x":16, + "y":-8.02222 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":0, + "y":16 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":31, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":0, + "y":-16 + }, + { + "x":16, + "y":-16 + }, + { + "x":16, + "y":-8.02222 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":0, + "y":16 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":32, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":0, + "id":1, + "name":"", + "polygon":[ + { + "x":0, + "y":0 + }, + { + "x":-0.0078125, + "y":-7.96094 + }, + { + "x":15.9922, + "y":-7.96094 + }], + "rotation":0, + "type":"", + "visible":true, + "width":0, + "x":0, + "y":8 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":33, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":16, + "id":1, + "name":"", + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":0, + "y":0 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }, + { + "id":34, + "objectgroup": + { + "draworder":"index", + "id":2, + "name":"", + "objects":[ + { + "height":16, + "id":1, + "name":"", + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":0, + "y":0 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + } + }], + "tilewidth":16, + "type":"tileset", + "version":"1.10" +} \ No newline at end of file diff --git a/assets/pre/levels/R0/collision16.tsx b/assets/pre/levels/R0/collision16.tsx index 8e88703..276a71a 100644 --- a/assets/pre/levels/R0/collision16.tsx +++ b/assets/pre/levels/R0/collision16.tsx @@ -1,5 +1,8 @@ + + + diff --git a/iso.xml b/iso.xml index 96fdd14..54382c5 100644 --- a/iso.xml +++ b/iso.xml @@ -3,7 +3,7 @@ - + + diff --git a/src/level.c b/src/level.c index 701dc12..d1b1da2 100644 --- a/src/level.c +++ b/src/level.c @@ -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(); diff --git a/system.cnf b/system.cnf index 59216b1..accee5b 100644 --- a/system.cnf +++ b/system.cnf @@ -1,4 +1,4 @@ -BOOT=cdrom:\SONIC.EXE;1 +BOOT=cdrom:\ENGINE.EXE;1 TCB=4 EVENT=10 STACK=801FFFF0 diff --git a/tools/cookcollision.py b/tools/cookcollision.py new file mode 100755 index 0000000..ab79076 --- /dev/null +++ b/tools/cookcollision.py @@ -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() diff --git a/tools/layouts/collision.hexpat b/tools/layouts/collision.hexpat new file mode 100644 index 0000000..58d4e9c --- /dev/null +++ b/tools/layouts/collision.hexpat @@ -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;