sprites: initial implementation
This commit is contained in:
parent
3b9cbf82aa
commit
1b71c6cfb6
8
Makefile
8
Makefile
@ -6,6 +6,7 @@ LIB = ./saturn
|
||||
SRC = main.o input.o
|
||||
SRC += gen/maps.o
|
||||
SRC += gen/map_objects.o
|
||||
SRC += gen/sprites.o
|
||||
DEP = $(patsubst %.o,%.d,$(SRC))
|
||||
|
||||
res = $(subst pokered/,res/,$(patsubst %.$(1),%.$(1).o,$(wildcard $(2)*.$(1))))
|
||||
@ -13,9 +14,10 @@ res_png = $(subst pokered/,res/,$(patsubst %.png,%.$(1).o,$(wildcard $(2)*.png))
|
||||
|
||||
GFX_TILESETS = $(call res_png,2bpp,pokered/gfx/tilesets/)
|
||||
GFX_BLOCKSETS = $(call res,bst,pokered/gfx/blocksets/)
|
||||
GFX_SPRITES = $(call res_png,2bpp,pokered/gfx/sprites/)
|
||||
MAPS_BLOCKS = $(call res,blk,pokered/maps/)
|
||||
|
||||
GENERATED = $(GFX_TILESETS) $(GFX_BLOCKSETS) $(MAPS_BLOCKS)
|
||||
GENERATED = $(GFX_TILESETS) $(GFX_BLOCKSETS) $(GFX_SPRITES) $(MAPS_BLOCKS)
|
||||
OBJ = $(SRC) $(GENERATED)
|
||||
|
||||
all: main.cue
|
||||
@ -47,6 +49,10 @@ res/%.2bpp: pokered/%.png
|
||||
@mkdir -p $(dir $@)
|
||||
python tools/png_to_nbpp.py $< 2 $@
|
||||
|
||||
res/gfx/sprites/%.2bpp: pokered/gfx/sprites/%.png
|
||||
@mkdir -p $(dir $@)
|
||||
python tools/png_to_nbpp_sprite.py $< 2 $@
|
||||
|
||||
%.2bpp.h:
|
||||
$(BUILD_BINARY_H)
|
||||
|
||||
|
113
main.cpp
113
main.cpp
@ -1,6 +1,7 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "vdp2.h"
|
||||
#include "vdp1.h"
|
||||
#include "scu.h"
|
||||
#include "smpc.h"
|
||||
#include "sh2.h"
|
||||
@ -12,6 +13,7 @@
|
||||
#include "input.hpp"
|
||||
|
||||
#include "gen/maps.hpp"
|
||||
#include "gen/sprites.hpp"
|
||||
#include "map_objects.hpp"
|
||||
|
||||
constexpr inline uint16_t rgb15(int32_t r, int32_t g, int32_t b)
|
||||
@ -21,19 +23,14 @@ constexpr inline uint16_t rgb15(int32_t r, int32_t g, int32_t b)
|
||||
|
||||
void palette_data()
|
||||
{
|
||||
vdp2.cram.u16[0] = rgb15( 0, 0, 0);
|
||||
vdp2.cram.u16[1] = rgb15(10, 10, 10);
|
||||
vdp2.cram.u16[2] = rgb15(21, 21, 21);
|
||||
vdp2.cram.u16[3] = rgb15(31, 31, 31);
|
||||
vdp2.cram.u16[3] = rgb15( 0, 0, 0);
|
||||
vdp2.cram.u16[2] = rgb15(10, 10, 10);
|
||||
vdp2.cram.u16[1] = rgb15(21, 21, 21);
|
||||
vdp2.cram.u16[0] = rgb15(31, 31, 31);
|
||||
}
|
||||
|
||||
uint32_t cell_data(const start_size_t& buf, const uint32_t top)
|
||||
static inline void _2bpp_4bpp_vram_copy(uint32_t * vram, const start_size_t& buf)
|
||||
{
|
||||
// round to nearest multiple of 32
|
||||
const uint32_t table_size = ((buf.size * 2) + 0x20 - 1) & (-0x20);
|
||||
const uint32_t base_address = top - table_size; // in bytes
|
||||
|
||||
uint32_t * vram = &vdp2.vram.u32[(base_address / 4)];
|
||||
for (uint32_t ix = 0; ix < buf.size / 4; ix += 1) {
|
||||
const uint32_t pixels = reinterpret_cast<uint32_t const * const>(buf.start)[ix];
|
||||
const uint32_t px0 = pixels >> 16 & 0xffff;
|
||||
@ -52,6 +49,28 @@ uint32_t cell_data(const start_size_t& buf, const uint32_t top)
|
||||
#undef lshift
|
||||
#undef rshift
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t character_pattern_table(const start_size_t& buf, const uint32_t top)
|
||||
{
|
||||
// round to nearest multiple of 32
|
||||
const uint32_t table_size = ((buf.size * 2) + 0x20 - 1) & (-0x20);
|
||||
const uint32_t base_address = top - table_size;
|
||||
|
||||
uint32_t * vram = &vdp1.vram.u32[(base_address / 4)];
|
||||
_2bpp_4bpp_vram_copy(vram, buf);
|
||||
|
||||
return base_address;
|
||||
}
|
||||
|
||||
uint32_t cell_data(const start_size_t& buf, const uint32_t top)
|
||||
{
|
||||
// round to nearest multiple of 32
|
||||
const uint32_t table_size = ((buf.size * 2) + 0x20 - 1) & (-0x20);
|
||||
const uint32_t base_address = top - table_size; // in bytes
|
||||
|
||||
uint32_t * vram = &vdp2.vram.u32[(base_address / 4)];
|
||||
_2bpp_4bpp_vram_copy(vram, buf);
|
||||
|
||||
return base_address;
|
||||
}
|
||||
@ -187,10 +206,74 @@ void smpc_int(void)
|
||||
intback::fsm(digital_callback, nullptr);
|
||||
}
|
||||
|
||||
void sprite()
|
||||
{
|
||||
uint32_t top = (sizeof (union vdp1_vram));
|
||||
const spritesheet_t& spritesheet = spritesheets[spritesheet_t::oak];
|
||||
uint32_t character_address = top = character_pattern_table(spritesheet.spritesheet, top);
|
||||
uint32_t color_address = 0;
|
||||
|
||||
/* TVM settings must be performed from the second H-blank IN interrupt after the
|
||||
V-blank IN interrupt to the H-blank IN interrupt immediately after the V-blank
|
||||
OUT interrupt. */
|
||||
// "normal" display resolution, 16 bits per pixel, 512x256 framebuffer
|
||||
vdp1.reg.TVMR = TVMR__TVM__NORMAL;
|
||||
|
||||
// swap framebuffers every 1 cycle; non-interlace
|
||||
vdp1.reg.FBCR = 0;
|
||||
|
||||
// during a framebuffer erase cycle, write the color "black" to each pixel
|
||||
constexpr uint16_t black = 0x0000;
|
||||
vdp1.reg.EWDR = black;
|
||||
|
||||
// the EWLR/EWRR macros use somewhat nontrivial math for the X coordinates
|
||||
// erase upper-left coordinate
|
||||
vdp1.reg.EWLR = EWLR__16BPP_X1(0) | EWLR__Y1(0);
|
||||
|
||||
// erase lower-right coordinate
|
||||
vdp1.reg.EWRR = EWRR__16BPP_X3(319) | EWRR__Y3(239);
|
||||
|
||||
vdp1.vram.cmd[0].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__SYSTEM_CLIP_COORDINATES;
|
||||
vdp1.vram.cmd[0].LINK = 0;
|
||||
vdp1.vram.cmd[0].XC = 319;
|
||||
vdp1.vram.cmd[0].YC = 239;
|
||||
|
||||
vdp1.vram.cmd[1].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__LOCAL_COORDINATE;
|
||||
vdp1.vram.cmd[1].LINK = 0;
|
||||
vdp1.vram.cmd[1].XA = 0;
|
||||
vdp1.vram.cmd[1].YA = 0;
|
||||
|
||||
vdp1.vram.cmd[2].CTRL = CTRL__JP__JUMP_NEXT | CTRL__COMM__NORMAL_SPRITE;
|
||||
vdp1.vram.cmd[2].LINK = 0;
|
||||
// The "end code" is 0xf, which is being used in the mai sprite palette. If
|
||||
// both transparency and end codes are enabled, it seems there are only 14
|
||||
// usable colors in the 4-bit color mode.
|
||||
vdp1.vram.cmd[2].PMOD = PMOD__ECD | PMOD__COLOR_MODE__COLOR_BANK_16;
|
||||
// It appears Kronos does not correctly calculate the color address in the
|
||||
// VDP1 debugger. Kronos will report FFFC when the actual color table address
|
||||
// in this example is 7FFE0.
|
||||
vdp1.vram.cmd[2].COLR = color_address; // non-palettized (rgb15) color data
|
||||
vdp1.vram.cmd[2].SRCA = character_address >> 3;
|
||||
vdp1.vram.cmd[2].SIZE = SIZE__X(16) | SIZE__Y(16);
|
||||
vdp1.vram.cmd[2].XA = 5 * 16;
|
||||
vdp1.vram.cmd[2].YA = 5 * 16;
|
||||
|
||||
vdp1.vram.cmd[3].CTRL = CTRL__END;
|
||||
|
||||
// start drawing (execute the command list) on every frame
|
||||
vdp1.reg.PTMR = PTMR__PTM__FRAME_CHANGE;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
state.map_ix = map_t::celadon_city;
|
||||
|
||||
v_blank_in();
|
||||
|
||||
sprite();
|
||||
|
||||
vdp2.reg.PRISA = PRISA__S0PRIN(7); // Sprite register 0 PRIority Number
|
||||
|
||||
// DISP: Please make sure to change this bit from 0 to 1 during V blank.
|
||||
vdp2.reg.TVMD = ( TVMD__DISP | TVMD__LSMD__NON_INTERLACE
|
||||
| TVMD__VRESO__240 | TVMD__HRESO__NORMAL_320);
|
||||
@ -199,7 +282,7 @@ void main()
|
||||
vdp2.reg.RAMCTL = RAMCTL__CRKTE | RAMCTL__CRMD__RGB_5BIT_1024 | RAMCTL__VRAMD | RAMCTL__VRBMD;
|
||||
|
||||
/* enable display of NBG0 */
|
||||
vdp2.reg.BGON = BGON__N0ON;
|
||||
vdp2.reg.BGON = BGON__N0ON | BGON__N0TPON;
|
||||
|
||||
/* set character format for NBG0 to palettized 16 color
|
||||
set enable "cell format" for NBG0
|
||||
@ -216,10 +299,10 @@ void main()
|
||||
2-word: value of bit 5-0 * 0x4000
|
||||
*/
|
||||
constexpr int plane_a = 0;
|
||||
constexpr int plane_a_offset = plane_a * 0x2000;
|
||||
//constexpr int plane_a_offset = plane_a * 0x2000;
|
||||
|
||||
constexpr int page_size = 64 * 64 * 2; // N0PNB__1WORD (16-bit)
|
||||
constexpr int plane_size = page_size * 1;
|
||||
//constexpr int page_size = 64 * 64 * 2; // N0PNB__1WORD (16-bit)
|
||||
//constexpr int plane_size = page_size * 1;
|
||||
|
||||
vdp2.reg.MPOFN = MPOFN__N0MP(0); // bits 8~6
|
||||
vdp2.reg.MPABN0 = MPABN0__N0MPB(0) | MPABN0__N0MPA(plane_a); // bits 5~0
|
||||
@ -247,6 +330,4 @@ void main()
|
||||
|
||||
scu.reg.IST = 0;
|
||||
scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN);
|
||||
|
||||
state.map_ix = map_t::celadon_city;
|
||||
}
|
||||
|
6
start_size.hpp
Normal file
6
start_size.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
struct start_size_t {
|
||||
uint8_t const * const start;
|
||||
uint32_t size;
|
||||
};
|
67
tileset.hpp
67
tileset.hpp
@ -1,67 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "res/gfx/tilesets/cavern.4bpp.h"
|
||||
#include "res/gfx/tilesets/cemetery.4bpp.h"
|
||||
#include "res/gfx/tilesets/club.4bpp.h"
|
||||
#include "res/gfx/tilesets/facility.4bpp.h"
|
||||
#include "res/gfx/tilesets/forest.4bpp.h"
|
||||
#include "res/gfx/tilesets/gate.4bpp.h"
|
||||
#include "res/gfx/tilesets/gym.4bpp.h"
|
||||
#include "res/gfx/tilesets/house.4bpp.h"
|
||||
#include "res/gfx/tilesets/interior.4bpp.h"
|
||||
#include "res/gfx/tilesets/lab.4bpp.h"
|
||||
#include "res/gfx/tilesets/lobby.4bpp.h"
|
||||
#include "res/gfx/tilesets/mansion.4bpp.h"
|
||||
#include "res/gfx/tilesets/overworld.4bpp.h"
|
||||
#include "res/gfx/tilesets/plateau.4bpp.h"
|
||||
#include "res/gfx/tilesets/pokecenter.4bpp.h"
|
||||
#include "res/gfx/tilesets/reds_house.4bpp.h"
|
||||
#include "res/gfx/tilesets/ship.4bpp.h"
|
||||
#include "res/gfx/tilesets/ship_port.4bpp.h"
|
||||
#include "res/gfx/tilesets/underground.4bpp.h"
|
||||
|
||||
struct tileset {
|
||||
enum {
|
||||
cavern,
|
||||
cemetery,
|
||||
club,
|
||||
facility,
|
||||
forest,
|
||||
gate,
|
||||
gym,
|
||||
house,
|
||||
interior,
|
||||
lab,
|
||||
lobby,
|
||||
mansion,
|
||||
overworld,
|
||||
plateau,
|
||||
pokecenter,
|
||||
reds_house,
|
||||
ship,
|
||||
ship_port,
|
||||
underground,
|
||||
};
|
||||
|
||||
static uint32_t pattern_name(uint32_t graphics_ix);
|
||||
static uint32_t load(uint32_t top, uint32_t i);
|
||||
};
|
||||
|
||||
struct buf_t {
|
||||
uint32_t * buf;
|
||||
uint32_t size;
|
||||
uint32_t base_address;
|
||||
};
|
||||
|
||||
extern uint32_t cell_data(const buf_t& buf, const uint32_t top);
|
||||
|
||||
uint32_t tileset::pattern_name(uint32_t graphics_ix)
|
||||
{
|
||||
return tilesets[graphics_ix].base_address / 32;
|
||||
}
|
||||
|
||||
uint32_t tileset::load(uint32_t top, uint32_t i)
|
||||
{
|
||||
tilesets[i].base_address = top = cell_data(tilesets[i], top);
|
||||
return top;
|
||||
}
|
@ -4,14 +4,17 @@ import sys
|
||||
|
||||
from generate import maps
|
||||
from generate import map_objects
|
||||
from generate import sprites
|
||||
|
||||
files = [
|
||||
(maps.generate_maps_header, "maps.hpp"),
|
||||
(maps.generate_maps_source, "maps.cpp"),
|
||||
(map_objects.generate_map_objects_source, "map_objects.cpp"),
|
||||
(sprites.generate_sprites_header, "sprites.hpp"),
|
||||
(sprites.generate_sprites_source, "sprites.cpp"),
|
||||
]
|
||||
|
||||
def generate(base_path):
|
||||
files = [
|
||||
(maps.generate_maps_header, "maps.hpp"),
|
||||
(maps.generate_maps_source, "maps.cpp"),
|
||||
(map_objects.generate_map_objects_source, "map_objects.cpp")
|
||||
]
|
||||
|
||||
for func, filename in files:
|
||||
path = base_path / filename
|
||||
with open(path, 'w') as f:
|
||||
|
18
tools/generate/binary.py
Normal file
18
tools/generate/binary.py
Normal file
@ -0,0 +1,18 @@
|
||||
def binary_res(path, suffix):
|
||||
# _binary_res_gfx_blocksets_overworld_bst_start
|
||||
name = path.replace('/', '_').replace('.', '_')
|
||||
return f"&_binary_res_{name}_{suffix}"
|
||||
|
||||
def start_size_value(path):
|
||||
if path is None:
|
||||
return [
|
||||
"0,",
|
||||
"0,",
|
||||
]
|
||||
else:
|
||||
start = binary_res(path, "start")
|
||||
size = binary_res(path, "size")
|
||||
return [
|
||||
f"reinterpret_cast<uint8_t*>({start}),",
|
||||
f"reinterpret_cast<uint32_t>({size}),",
|
||||
]
|
@ -1,19 +1,51 @@
|
||||
"""
|
||||
map_headers[0].tileset == 'OVERWORLD'
|
||||
map_headers[0].name1 = 'PalletTown'
|
||||
map_headers[0].name2 = 'PALLET_TOWN'
|
||||
map_headers[0].blocks() == 'PalletTown_Blocks'
|
||||
map_constants_list['PALLET_TOWN'] == MapConstant(10, 19)
|
||||
maps_blocks_list['PalletTown_Blocks'] == 'maps/PalletTown.blk'
|
||||
tileset_constants_list['OVERWORLD'] == 0
|
||||
tileset_headers_list[0].name == 'Overworld'
|
||||
tileset_headers_list[0].blockset() == 'Overworld_Block'
|
||||
tileset_headers_list[0].gfx() == 'Overworld_GFX'
|
||||
gfx_tilesets_list['Overworld_Block'] == 'gfx/blocksets/overworld.bst'
|
||||
gfx_tilesets_list['Overworld_GFX'] == 'gfx/tilesets/overworld.2bpp'
|
||||
"""
|
||||
|
||||
from parse import parse
|
||||
|
||||
from generate.sort import default_sort
|
||||
from generate.binary import binary_res, start_size_value
|
||||
from generate.generate import renderer
|
||||
|
||||
def sorted_map_constants_list():
|
||||
return sorted(parse.map_constants_list.items(), key=default_sort)
|
||||
|
||||
def sorted_map_headers():
|
||||
map_constants_list = sorted_map_constants_list()
|
||||
map_headers_dict = dict((map_header.name2, map_header) for map_header in parse.map_headers)
|
||||
# hack to remove unused/duplicate underground_path_route_7
|
||||
map_headers = sorted(parse.map_headers, key=lambda m: m.name2)
|
||||
return filter(lambda m: m.name1 != "UndergroundPathRoute7Copy", map_headers)
|
||||
#map_headers = sorted(parse.map_headers, key=lambda m: m.name2)
|
||||
#return filter(lambda m: m.name1 != "UndergroundPathRoute7Copy", map_headers)
|
||||
return (
|
||||
map_headers_dict[map_name2] for map_name2, _ in map_constants_list
|
||||
if map_name2 in map_headers_dict
|
||||
# e.g CERULEAN_TRASHED_HOUSE_COPY has no map header
|
||||
)
|
||||
|
||||
def sorted_tilesets_constants_list():
|
||||
return sorted(parse.tileset_constants_list.items(), key=default_sort)
|
||||
|
||||
def includes_header():
|
||||
yield "#pragma once"
|
||||
yield ""
|
||||
yield '#include "../start_size.hpp"'
|
||||
yield ""
|
||||
for map_header in sorted_map_headers():
|
||||
block_path = parse.maps_blocks_list[map_header.blocks()]
|
||||
yield f'#include "../res/{block_path}.h"'
|
||||
for tileset_name in sorted(parse.tileset_constants_list):
|
||||
for tileset_name, _ in sorted_tilesets_constants_list():
|
||||
tileset_index = parse.tileset_constants_list[tileset_name]
|
||||
tileset_header = parse.tileset_headers_list[tileset_index]
|
||||
|
||||
@ -24,18 +56,10 @@ def includes_header():
|
||||
yield f'#include "../res/{gfx_path}.h"'
|
||||
yield ""
|
||||
|
||||
def struct_start_size_t():
|
||||
return [
|
||||
"struct start_size_t {",
|
||||
"uint8_t const * const start;",
|
||||
"uint32_t size;",
|
||||
"};",
|
||||
]
|
||||
|
||||
def struct_tileset_t():
|
||||
tileset_names = (
|
||||
f"{name.lower()},"
|
||||
for name in sorted(parse.tileset_constants_list)
|
||||
for name, _ in sorted_tilesets_constants_list()
|
||||
)
|
||||
return [
|
||||
"struct tileset_t {",
|
||||
@ -68,20 +92,6 @@ def struct_map_t():
|
||||
"};",
|
||||
]
|
||||
|
||||
def binary_res(path, suffix):
|
||||
# _binary_res_gfx_blocksets_overworld_bst_start
|
||||
name = path.replace('/', '_').replace('.', '_')
|
||||
return f"_binary_res_{name}_{suffix}"
|
||||
|
||||
def start_size_value(path):
|
||||
start = binary_res(path, "start")
|
||||
size = binary_res(path, "size")
|
||||
|
||||
return [
|
||||
f"reinterpret_cast<uint8_t*>(&{start}),",
|
||||
f"reinterpret_cast<uint32_t>(&{size}),",
|
||||
]
|
||||
|
||||
def blockset_tileset(name: str):
|
||||
tileset_index = parse.tileset_constants_list[name]
|
||||
tileset_header = parse.tileset_headers_list[tileset_index]
|
||||
@ -105,8 +115,8 @@ def tilesets_header():
|
||||
|
||||
def tilesets():
|
||||
yield "const tileset_t tilesets[] = {"
|
||||
for tileset in sorted(parse.tileset_constants_list):
|
||||
yield from blockset_tileset(tileset)
|
||||
for name, _ in sorted_tilesets_constants_list():
|
||||
yield from blockset_tileset(name)
|
||||
yield "};"
|
||||
|
||||
def map(map_header):
|
||||
@ -135,7 +145,6 @@ def maps():
|
||||
def generate_maps_header():
|
||||
render, out = renderer()
|
||||
render(includes_header())
|
||||
render(struct_start_size_t())
|
||||
render(struct_tileset_t())
|
||||
render(struct_map_t())
|
||||
render(tilesets_header());
|
||||
@ -154,19 +163,3 @@ def generate_maps_source():
|
||||
render(tilesets())
|
||||
render(maps())
|
||||
return out
|
||||
|
||||
|
||||
"""
|
||||
map_headers[0].tileset == 'OVERWORLD'
|
||||
map_headers[0].name1 = 'PalletTown'
|
||||
map_headers[0].name2 = 'PALLET_TOWN'
|
||||
map_headers[0].blocks() == 'PalletTown_Blocks'
|
||||
map_constants_list['PALLET_TOWN'] == MapConstant(10, 19)
|
||||
maps_blocks_list['PalletTown_Blocks'] == 'maps/PalletTown.blk'
|
||||
tileset_constants_list['OVERWORLD'] == 0
|
||||
tileset_headers_list[0].name == 'Overworld'
|
||||
tileset_headers_list[0].blockset() == 'Overworld_Block'
|
||||
tileset_headers_list[0].gfx() == 'Overworld_GFX'
|
||||
gfx_tilesets_list['Overworld_Block'] == 'gfx/blocksets/overworld.bst'
|
||||
gfx_tilesets_list['Overworld_GFX'] == 'gfx/tilesets/overworld.2bpp'
|
||||
"""
|
||||
|
5
tools/generate/sort.py
Normal file
5
tools/generate/sort.py
Normal file
@ -0,0 +1,5 @@
|
||||
from operator import itemgetter
|
||||
|
||||
by_name = itemgetter(0)
|
||||
by_index = itemgetter(1)
|
||||
default_sort = by_name
|
98
tools/generate/sprites.py
Normal file
98
tools/generate/sprites.py
Normal file
@ -0,0 +1,98 @@
|
||||
"""
|
||||
spritesheets_list[0] == Spritesheet('RedSprite', 3)
|
||||
sprite_constants_list[0] == 'SPRITE_NONE'
|
||||
gfx_sprites_list['RedSprite'] == 'gfx/sprites/red.2bpp'
|
||||
"""
|
||||
# insert a empty sprite for SPRITE_NONE
|
||||
# remove SPRITE_ prefix
|
||||
|
||||
|
||||
from parse import parse
|
||||
|
||||
from generate.sort import default_sort
|
||||
from generate.binary import binary_res, start_size_value
|
||||
from generate.generate import renderer
|
||||
|
||||
def sorted_sprite_constants_list():
|
||||
return sorted(parse.sprite_constants_list.items(), key=default_sort)
|
||||
|
||||
def includes_header():
|
||||
yield '#pragma once'
|
||||
yield ''
|
||||
yield '#include "../start_size.hpp"'
|
||||
yield ''
|
||||
for name, index in sorted_sprite_constants_list():
|
||||
if name == 'SPRITE_NONE':
|
||||
continue
|
||||
assert index != 0, index
|
||||
spritesheet = parse.spritesheets_list[index - 1]
|
||||
sprite_path = parse.gfx_sprites_list[spritesheet.name]
|
||||
yield f'#include "../res/{sprite_path}.h"'
|
||||
|
||||
def includes_source():
|
||||
yield '#include <cstdint>'
|
||||
yield ''
|
||||
yield '#include "sprites.hpp"'
|
||||
yield ''
|
||||
|
||||
def sprite_name(name):
|
||||
assert name.startswith('SPRITE_'), name
|
||||
return name.removeprefix('SPRITE_').lower()
|
||||
|
||||
def struct_spritesheet_t():
|
||||
sprite_names = (
|
||||
f"{sprite_name(name)},"
|
||||
for name, _ in sorted_sprite_constants_list()
|
||||
)
|
||||
return [
|
||||
"struct spritesheet_t {",
|
||||
"start_size_t spritesheet;",
|
||||
"uint8_t sprite_count;",
|
||||
"",
|
||||
"enum sprite {",
|
||||
*sprite_names,
|
||||
"};",
|
||||
"};",
|
||||
]
|
||||
|
||||
def sprites_header():
|
||||
yield "extern const spritesheet_t spritesheets[];"
|
||||
|
||||
def generate_sprites_header():
|
||||
render, out = renderer()
|
||||
render(includes_header())
|
||||
render(struct_spritesheet_t())
|
||||
render(sprites_header())
|
||||
return out
|
||||
|
||||
def sprite(name, index):
|
||||
if name == 'SPRITE_NONE':
|
||||
# special null sprite
|
||||
sprite_path = None
|
||||
sprite_count = 0
|
||||
else:
|
||||
# spritesheets_list does not include SPRITE_NULL at index 0
|
||||
assert index != 0, index
|
||||
spritesheet = parse.spritesheets_list[index - 1]
|
||||
sprite_path = parse.gfx_sprites_list[spritesheet.name]
|
||||
sprite_count = spritesheet.sprite_count
|
||||
return [
|
||||
f"[spritesheet_t::{sprite_name(name)}] = {{",
|
||||
".spritesheet = {",
|
||||
*start_size_value(sprite_path),
|
||||
"},",
|
||||
f".sprite_count = {sprite_count}",
|
||||
"},",
|
||||
]
|
||||
|
||||
def sprites():
|
||||
yield "const spritesheet_t spritesheets[] = {"
|
||||
for name, index in sorted_sprite_constants_list():
|
||||
yield from sprite(name, index)
|
||||
yield "};"
|
||||
|
||||
def generate_sprites_source():
|
||||
render, out = renderer()
|
||||
render(includes_source());
|
||||
render(sprites())
|
||||
return out
|
4
tools/palette.py
Normal file
4
tools/palette.py
Normal file
@ -0,0 +1,4 @@
|
||||
def intensity_to_index(px):
|
||||
indices = {0x00: 3, 0x55: 2, 0xaa: 1, 0xff: 0}
|
||||
assert px in indices, px
|
||||
return indices[px]
|
@ -1,5 +1,5 @@
|
||||
from pprint import pprint
|
||||
from parse import parse
|
||||
|
||||
for i in parse.map_objects_list.items():
|
||||
for i in parse.sprite_constants_list:
|
||||
pprint(i)
|
||||
|
@ -2,15 +2,16 @@ from parse.maps_blocks import tokenize_block, flatten
|
||||
|
||||
def tokenize_lines(lines):
|
||||
for line in lines:
|
||||
if '_GFX:' in line or '_Block:' in line:
|
||||
if '::' in line:
|
||||
yield tokenize_block(line, delim='::')
|
||||
|
||||
def parse(prefix):
|
||||
path = prefix / 'gfx/tilesets.asm'
|
||||
path = prefix / 'gfx/sprites.asm'
|
||||
with open(path) as f:
|
||||
tokens = tokenize_lines(f.read().split('\n'))
|
||||
return list(
|
||||
flatten(tokens,
|
||||
endings=['_GFX', '_Block'],
|
||||
base_path='gfx/')
|
||||
)
|
||||
l = list(flatten(tokens,
|
||||
endings=['Sprite'],
|
||||
base_path='gfx/'))
|
||||
d = dict(l)
|
||||
assert len(l) == len(d)
|
||||
return d
|
@ -12,6 +12,10 @@ from parse import map_objects
|
||||
from parse import hidden_objects
|
||||
from parse import map_constants
|
||||
|
||||
from parse import gfx_sprites
|
||||
from parse import spritesheets
|
||||
from parse import sprite_constants
|
||||
|
||||
prefix = Path(sys.argv[1])
|
||||
|
||||
map_headers = map_header.parse_all(prefix)
|
||||
@ -30,5 +34,9 @@ map_constants_list = map_constants.parse(prefix)
|
||||
#ledge_tiles.asm
|
||||
#cut_tree_blocks.asm
|
||||
|
||||
|
||||
# home/vcopy: animations
|
||||
|
||||
# sprites
|
||||
gfx_sprites_list = gfx_sprites.parse(prefix)
|
||||
spritesheets_list = spritesheets.parse(prefix)
|
||||
sprite_constants_list = sprite_constants.parse(prefix)
|
||||
|
6
tools/parse/sprite_constants.py
Normal file
6
tools/parse/sprite_constants.py
Normal file
@ -0,0 +1,6 @@
|
||||
from parse.tileset_constants import flatten, tokenize_lines
|
||||
|
||||
def parse(prefix):
|
||||
path = prefix / 'constants/sprite_constants.asm'
|
||||
with open(path) as f:
|
||||
return dict(flatten(tokenize_lines(f.read().split('\n'))))
|
39
tools/parse/spritesheets.py
Normal file
39
tools/parse/spritesheets.py
Normal file
@ -0,0 +1,39 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from parse.map_header import tokenize_line
|
||||
from parse import number
|
||||
|
||||
def tokenize_lines(lines):
|
||||
for line in lines:
|
||||
if 'overworld_sprite ' in line:
|
||||
yield tokenize_line(line)
|
||||
|
||||
@dataclass
|
||||
class Spritesheet:
|
||||
name: str
|
||||
sprite_count: int
|
||||
|
||||
def translate_tile_count(tile_count):
|
||||
# this is the number of non-animation sprites in the spritesheet
|
||||
# the animation sprite is always at tile_index * 2
|
||||
# all animated sprites have 2 animation frames
|
||||
return int(tile_count) // 4
|
||||
|
||||
def flatten(tokens):
|
||||
for ts in tokens:
|
||||
if ts[0] != 'overworld_sprite':
|
||||
continue
|
||||
_, (name, tile_count) = ts
|
||||
|
||||
sprite_count = translate_tile_count(tile_count)
|
||||
|
||||
yield Spritesheet(
|
||||
name=name,
|
||||
sprite_count=sprite_count,
|
||||
)
|
||||
|
||||
def parse(prefix):
|
||||
path = prefix / 'data/sprites/sprites.asm'
|
||||
with open(path) as f:
|
||||
tokens = tokenize_lines(f.read().split('\n'))
|
||||
return list(flatten(tokens))
|
@ -3,10 +3,7 @@ import sys
|
||||
|
||||
from PIL import Image
|
||||
|
||||
def intensity_to_index(px):
|
||||
indices = {0x00: 0, 0x55: 1, 0xaa: 2, 0xff: 3}
|
||||
assert px in indices, px
|
||||
return indices[px]
|
||||
from palette import intensity_to_index
|
||||
|
||||
def convert(image, bpp):
|
||||
assert bpp in {8, 4, 2}, bpp
|
||||
|
68
tools/png_to_nbpp_sprite.py
Normal file
68
tools/png_to_nbpp_sprite.py
Normal file
@ -0,0 +1,68 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from palette import intensity_to_index
|
||||
|
||||
cell_width = 16
|
||||
cell_height = 16
|
||||
|
||||
def convert(image, bpp):
|
||||
assert bpp in {8, 4, 2}, bpp
|
||||
bits_per_byte = 8
|
||||
px_per_byte = bits_per_byte // bpp
|
||||
px_per_row = cell_width
|
||||
bytes_per_row = (px_per_row // px_per_byte)
|
||||
|
||||
assert image.mode == 'L', image.mode
|
||||
width, height = image.size
|
||||
|
||||
buf = bytearray(width * height // px_per_byte)
|
||||
|
||||
for cell_y in range(height//cell_height):
|
||||
cell_y_start = cell_y * bytes_per_row * cell_height
|
||||
|
||||
for cell_x in range(width//cell_width):
|
||||
cell_x_start = cell_y_start + bytes_per_row * cell_x
|
||||
|
||||
for y in range(cell_height):
|
||||
for x in range(cell_width):
|
||||
px = im.getpixel((cell_x * cell_width + x, cell_y * cell_height + y))
|
||||
index = intensity_to_index(px)
|
||||
buf_ix = cell_x_start + x//px_per_byte + y * bytes_per_row
|
||||
|
||||
buf[buf_ix] |= (index << bpp * ((px_per_byte - 1) - (x % px_per_byte)))
|
||||
return buf
|
||||
|
||||
# (pixels/row)
|
||||
# ------------ *
|
||||
# (pixels/byte)
|
||||
|
||||
def debug(buf, bpp):
|
||||
assert bpp in {8, 4, 2}, bpp
|
||||
bits_per_byte = 8
|
||||
px_per_byte = bits_per_byte // bpp
|
||||
px_per_row = cell_width
|
||||
bytes_per_row = (px_per_row // px_per_byte)
|
||||
bit_mask = (2 ** bpp) - 1
|
||||
|
||||
for row in range(len(buf) // bytes_per_row):
|
||||
for x in range(bytes_per_row):
|
||||
px = buf[row * bytes_per_row + x]
|
||||
for shift in reversed(range(px_per_byte)):
|
||||
print((px >> (shift * bpp)) & bit_mask, end='')
|
||||
print()
|
||||
if (row % cell_height == (cell_height - 1)):
|
||||
print()
|
||||
|
||||
in_path = sys.argv[1]
|
||||
bpp = int(sys.argv[2])
|
||||
out_path = sys.argv[3]
|
||||
|
||||
im = Image.open(in_path)
|
||||
buf = convert(im, bpp)
|
||||
if 'NBPP_DEBUG' in os.environ:
|
||||
debug(buf, bpp)
|
||||
with open(out_path, 'wb') as f:
|
||||
f.write(buf)
|
Loading…
x
Reference in New Issue
Block a user