refactor: reorganize pxl8 into client/src/ module structure
- core/: main entry, types, logging, I/O, RNG - asset/: ase loader, cart, save, embed - gfx/: graphics, animation, atlas, fonts, tilemap, transitions - sfx/: audio - script/: lua/fennel runtime, REPL - hal/: platform abstraction (SDL3) - world/: BSP, world, procedural gen - math/: math utilities - game/: GUI, replay - lua/: Lua API modules
This commit is contained in:
parent
272e0bc615
commit
39b604b333
106 changed files with 6078 additions and 3715 deletions
248
src/lua/pxl8.lua
248
src/lua/pxl8.lua
|
|
@ -1,248 +0,0 @@
|
|||
local core = require("pxl8.core")
|
||||
local gfx2d = require("pxl8.gfx2d")
|
||||
local input = require("pxl8.input")
|
||||
local vfx = require("pxl8.vfx")
|
||||
local particles = require("pxl8.particles")
|
||||
local tilemap = require("pxl8.tilemap")
|
||||
local gfx3d = require("pxl8.gfx3d")
|
||||
local math3d = require("pxl8.math")
|
||||
local gui = require("pxl8.gui")
|
||||
local world = require("pxl8.world")
|
||||
local transition = require("pxl8.transition")
|
||||
local anim = require("pxl8.anim")
|
||||
local sfx = require("pxl8.sfx")
|
||||
local pxl8 = {}
|
||||
|
||||
core.init(pxl8_gfx, pxl8_input, pxl8_rng, pxl8_sfx, pxl8_sys)
|
||||
|
||||
pxl8.get_fps = core.get_fps
|
||||
pxl8.get_height = core.get_height
|
||||
pxl8.get_title = core.get_title
|
||||
pxl8.get_width = core.get_width
|
||||
pxl8.info = core.info
|
||||
pxl8.warn = core.warn
|
||||
pxl8.error = core.error
|
||||
pxl8.debug = core.debug
|
||||
pxl8.trace = core.trace
|
||||
pxl8.quit = core.quit
|
||||
|
||||
pxl8.rng_seed = core.rng_seed
|
||||
pxl8.rng_next = core.rng_next
|
||||
pxl8.rng_f32 = core.rng_f32
|
||||
pxl8.rng_range = core.rng_range
|
||||
|
||||
pxl8.clear = gfx2d.clear
|
||||
pxl8.pixel = gfx2d.pixel
|
||||
pxl8.line = gfx2d.line
|
||||
pxl8.rect = gfx2d.rect
|
||||
pxl8.rect_fill = gfx2d.rect_fill
|
||||
pxl8.circle = gfx2d.circle
|
||||
pxl8.circle_fill = gfx2d.circle_fill
|
||||
pxl8.text = gfx2d.text
|
||||
pxl8.sprite = gfx2d.sprite
|
||||
pxl8.load_palette = gfx2d.load_palette
|
||||
pxl8.load_sprite = gfx2d.load_sprite
|
||||
pxl8.create_texture = gfx2d.create_texture
|
||||
pxl8.gfx_color_ramp = gfx2d.color_ramp
|
||||
pxl8.gfx_fade_palette = gfx2d.fade_palette
|
||||
pxl8.gfx_cycle_palette = gfx2d.cycle_palette
|
||||
pxl8.add_palette_cycle = gfx2d.add_palette_cycle
|
||||
pxl8.remove_palette_cycle = gfx2d.remove_palette_cycle
|
||||
pxl8.set_palette_cycle_speed = gfx2d.set_palette_cycle_speed
|
||||
pxl8.clear_palette_cycles = gfx2d.clear_palette_cycles
|
||||
|
||||
pxl8.key_down = input.key_down
|
||||
pxl8.key_pressed = input.key_pressed
|
||||
pxl8.key_released = input.key_released
|
||||
pxl8.mouse_dx = input.mouse_dx
|
||||
pxl8.mouse_dy = input.mouse_dy
|
||||
pxl8.mouse_wheel_x = input.mouse_wheel_x
|
||||
pxl8.mouse_wheel_y = input.mouse_wheel_y
|
||||
pxl8.mouse_x = input.mouse_x
|
||||
pxl8.mouse_y = input.mouse_y
|
||||
pxl8.get_mouse_pos = input.get_mouse_pos
|
||||
pxl8.mouse_pressed = input.mouse_pressed
|
||||
pxl8.mouse_released = input.mouse_released
|
||||
pxl8.center_cursor = input.center_cursor
|
||||
pxl8.set_cursor = input.set_cursor
|
||||
pxl8.set_relative_mouse_mode = input.set_relative_mouse_mode
|
||||
|
||||
pxl8.vfx_raster_bars = vfx.raster_bars
|
||||
pxl8.vfx_plasma = vfx.plasma
|
||||
pxl8.vfx_rotozoom = vfx.rotozoom
|
||||
pxl8.vfx_tunnel = vfx.tunnel
|
||||
pxl8.vfx_explosion = vfx.explosion
|
||||
pxl8.vfx_fire = vfx.fire
|
||||
pxl8.vfx_rain = vfx.rain
|
||||
pxl8.vfx_smoke = vfx.smoke
|
||||
pxl8.vfx_snow = vfx.snow
|
||||
pxl8.vfx_sparks = vfx.sparks
|
||||
pxl8.vfx_starfield = vfx.starfield
|
||||
|
||||
pxl8.particles_new = particles.new
|
||||
pxl8.particles_destroy = particles.destroy
|
||||
pxl8.particles_clear = particles.clear
|
||||
pxl8.particles_emit = particles.emit
|
||||
pxl8.particles_update = particles.update
|
||||
pxl8.particles_render = particles.render
|
||||
|
||||
pxl8.tilesheet_new = tilemap.tilesheet_new
|
||||
pxl8.tilesheet_destroy = tilemap.tilesheet_destroy
|
||||
pxl8.tilesheet_load = tilemap.tilesheet_load
|
||||
pxl8.tilemap_new = tilemap.new
|
||||
pxl8.tilemap_destroy = tilemap.destroy
|
||||
pxl8.tilemap_set_tilesheet = tilemap.set_tilesheet
|
||||
pxl8.tilemap_set_tile = tilemap.set_tile
|
||||
pxl8.tilemap_get_tile_id = tilemap.get_tile_id
|
||||
pxl8.tilemap_set_camera = tilemap.set_camera
|
||||
pxl8.tilemap_render = tilemap.render
|
||||
pxl8.tilemap_render_layer = tilemap.render_layer
|
||||
pxl8.tilemap_is_solid = tilemap.is_solid
|
||||
pxl8.tilemap_check_collision = tilemap.check_collision
|
||||
pxl8.tilemap_get_tile_data = tilemap.get_tile_data
|
||||
pxl8.tilemap_load_ase = tilemap.load_ase
|
||||
pxl8.tilemap_set_tile_data = tilemap.set_tile_data
|
||||
pxl8.TILE_FLIP_X = tilemap.TILE_FLIP_X
|
||||
pxl8.TILE_FLIP_Y = tilemap.TILE_FLIP_Y
|
||||
pxl8.TILE_SOLID = tilemap.TILE_SOLID
|
||||
pxl8.TILE_TRIGGER = tilemap.TILE_TRIGGER
|
||||
|
||||
pxl8.clear_zbuffer = gfx3d.clear_zbuffer
|
||||
pxl8.set_model = gfx3d.set_model
|
||||
pxl8.set_view = gfx3d.set_view
|
||||
pxl8.set_projection = gfx3d.set_projection
|
||||
pxl8.set_wireframe = gfx3d.set_wireframe
|
||||
pxl8.set_affine_textures = gfx3d.set_affine_textures
|
||||
pxl8.set_backface_culling = gfx3d.set_backface_culling
|
||||
pxl8.draw_triangle_3d = gfx3d.draw_triangle
|
||||
pxl8.draw_triangle_3d_textured = gfx3d.draw_triangle_textured
|
||||
pxl8.draw_line_3d = gfx3d.draw_line
|
||||
|
||||
pxl8.mat4_identity = math3d.mat4_identity
|
||||
pxl8.mat4_multiply = math3d.mat4_multiply
|
||||
pxl8.mat4_translate = math3d.mat4_translate
|
||||
pxl8.mat4_rotate_x = math3d.mat4_rotate_x
|
||||
pxl8.mat4_rotate_y = math3d.mat4_rotate_y
|
||||
pxl8.mat4_rotate_z = math3d.mat4_rotate_z
|
||||
pxl8.mat4_scale = math3d.mat4_scale
|
||||
pxl8.mat4_ortho = math3d.mat4_ortho
|
||||
pxl8.mat4_perspective = math3d.mat4_perspective
|
||||
pxl8.mat4_lookat = math3d.mat4_lookat
|
||||
pxl8.bounds = math3d.bounds
|
||||
|
||||
pxl8.gui_state_create = gui.state_create
|
||||
pxl8.gui_state_destroy = gui.state_destroy
|
||||
pxl8.gui_begin_frame = gui.begin_frame
|
||||
pxl8.gui_end_frame = gui.end_frame
|
||||
pxl8.gui_cursor_move = gui.cursor_move
|
||||
pxl8.gui_cursor_down = gui.cursor_down
|
||||
pxl8.gui_cursor_up = gui.cursor_up
|
||||
pxl8.gui_button = gui.button
|
||||
pxl8.gui_window = gui.window
|
||||
pxl8.gui_label = gui.label
|
||||
pxl8.gui_is_hovering = gui.is_hovering
|
||||
pxl8.gui_get_cursor_pos = gui.get_cursor_pos
|
||||
|
||||
pxl8.world_new = world.new
|
||||
pxl8.world_destroy = world.destroy
|
||||
pxl8.world_load = world.load
|
||||
pxl8.world_render = world.render
|
||||
pxl8.world_unload = world.unload
|
||||
pxl8.world_is_loaded = world.is_loaded
|
||||
pxl8.world_generate = world.generate
|
||||
pxl8.world_apply_textures = world.apply_textures
|
||||
pxl8.world_check_collision = world.check_collision
|
||||
pxl8.world_resolve_collision = world.resolve_collision
|
||||
pxl8.procgen_tex = world.procgen_tex
|
||||
pxl8.PROCGEN_ROOMS = world.PROCGEN_ROOMS
|
||||
pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN
|
||||
|
||||
pxl8.transition_create = transition.create
|
||||
pxl8.transition_destroy = transition.destroy
|
||||
pxl8.transition_get_progress = transition.get_progress
|
||||
pxl8.transition_is_active = transition.is_active
|
||||
pxl8.transition_is_complete = transition.is_complete
|
||||
pxl8.transition_render = transition.render
|
||||
pxl8.transition_reset = transition.reset
|
||||
pxl8.transition_set_color = transition.set_color
|
||||
pxl8.transition_set_reverse = transition.set_reverse
|
||||
pxl8.transition_start = transition.start
|
||||
pxl8.transition_stop = transition.stop
|
||||
pxl8.transition_update = transition.update
|
||||
|
||||
pxl8.anim_create = anim.create
|
||||
pxl8.anim_create_from_ase = anim.create_from_ase
|
||||
pxl8.anim_destroy = anim.destroy
|
||||
pxl8.anim_add_state = anim.add_state
|
||||
pxl8.anim_get_current_frame = anim.get_current_frame
|
||||
pxl8.anim_get_current_frame_id = anim.get_current_frame_id
|
||||
pxl8.anim_get_state = anim.get_state
|
||||
pxl8.anim_has_state_machine = anim.has_state_machine
|
||||
pxl8.anim_is_complete = anim.is_complete
|
||||
pxl8.anim_is_playing = anim.is_playing
|
||||
pxl8.anim_pause = anim.pause
|
||||
pxl8.anim_play = anim.play
|
||||
pxl8.anim_render_sprite = anim.render_sprite
|
||||
pxl8.anim_reset = anim.reset
|
||||
pxl8.anim_set_frame = anim.set_frame
|
||||
pxl8.anim_set_loop = anim.set_loop
|
||||
pxl8.anim_set_reverse = anim.set_reverse
|
||||
pxl8.anim_set_speed = anim.set_speed
|
||||
pxl8.anim_set_state = anim.set_state
|
||||
pxl8.anim_stop = anim.stop
|
||||
pxl8.anim_update = anim.update
|
||||
|
||||
pxl8.sfx_compressor_create = sfx.compressor_create
|
||||
pxl8.sfx_compressor_set_attack = sfx.compressor_set_attack
|
||||
pxl8.sfx_compressor_set_ratio = sfx.compressor_set_ratio
|
||||
pxl8.sfx_compressor_set_release = sfx.compressor_set_release
|
||||
pxl8.sfx_compressor_set_threshold = sfx.compressor_set_threshold
|
||||
pxl8.sfx_context_append_node = sfx.context_append_node
|
||||
pxl8.sfx_context_create = sfx.context_create
|
||||
pxl8.sfx_context_destroy = sfx.context_destroy
|
||||
pxl8.sfx_context_get_head = sfx.context_get_head
|
||||
pxl8.sfx_context_get_volume = sfx.context_get_volume
|
||||
pxl8.sfx_context_insert_node = sfx.context_insert_node
|
||||
pxl8.sfx_context_remove_node = sfx.context_remove_node
|
||||
pxl8.sfx_context_set_volume = sfx.context_set_volume
|
||||
pxl8.sfx_delay_create = sfx.delay_create
|
||||
pxl8.sfx_delay_set_feedback = sfx.delay_set_feedback
|
||||
pxl8.sfx_delay_set_mix = sfx.delay_set_mix
|
||||
pxl8.sfx_delay_set_time = sfx.delay_set_time
|
||||
pxl8.sfx_get_master_volume = sfx.get_master_volume
|
||||
pxl8.sfx_mixer_attach = sfx.mixer_attach
|
||||
pxl8.sfx_mixer_detach = sfx.mixer_detach
|
||||
pxl8.sfx_node_destroy = sfx.node_destroy
|
||||
pxl8.sfx_note_to_freq = sfx.note_to_freq
|
||||
pxl8.sfx_play_note = sfx.play_note
|
||||
pxl8.sfx_release_voice = sfx.release_voice
|
||||
pxl8.sfx_reverb_create = sfx.reverb_create
|
||||
pxl8.sfx_reverb_set_damping = sfx.reverb_set_damping
|
||||
pxl8.sfx_reverb_set_mix = sfx.reverb_set_mix
|
||||
pxl8.sfx_reverb_set_room = sfx.reverb_set_room
|
||||
pxl8.sfx_set_master_volume = sfx.set_master_volume
|
||||
pxl8.sfx_stop_all = sfx.stop_all
|
||||
pxl8.sfx_stop_voice = sfx.stop_voice
|
||||
pxl8.sfx_voice_params = sfx.voice_params
|
||||
|
||||
pxl8.SFX_FILTER_BANDPASS = sfx.FILTER_BANDPASS
|
||||
pxl8.SFX_FILTER_HIGHPASS = sfx.FILTER_HIGHPASS
|
||||
pxl8.SFX_FILTER_LOWPASS = sfx.FILTER_LOWPASS
|
||||
pxl8.SFX_FILTER_NONE = sfx.FILTER_NONE
|
||||
|
||||
pxl8.SFX_LFO_AMPLITUDE = sfx.LFO_AMPLITUDE
|
||||
pxl8.SFX_LFO_FILTER = sfx.LFO_FILTER
|
||||
pxl8.SFX_LFO_PITCH = sfx.LFO_PITCH
|
||||
|
||||
pxl8.SFX_NODE_COMPRESSOR = sfx.NODE_COMPRESSOR
|
||||
pxl8.SFX_NODE_DELAY = sfx.NODE_DELAY
|
||||
pxl8.SFX_NODE_REVERB = sfx.NODE_REVERB
|
||||
|
||||
pxl8.SFX_WAVE_NOISE = sfx.WAVE_NOISE
|
||||
pxl8.SFX_WAVE_PULSE = sfx.WAVE_PULSE
|
||||
pxl8.SFX_WAVE_SAW = sfx.WAVE_SAW
|
||||
pxl8.SFX_WAVE_SINE = sfx.WAVE_SINE
|
||||
pxl8.SFX_WAVE_SQUARE = sfx.WAVE_SQUARE
|
||||
pxl8.SFX_WAVE_TRIANGLE = sfx.WAVE_TRIANGLE
|
||||
|
||||
return pxl8
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local anim = {}
|
||||
|
||||
function anim.create(frame_ids, frame_durations)
|
||||
local frame_count = #frame_ids
|
||||
local c_frame_ids = ffi.new("u32[?]", frame_count)
|
||||
local c_frame_durations = nil
|
||||
|
||||
for i = 1, frame_count do
|
||||
c_frame_ids[i-1] = frame_ids[i]
|
||||
end
|
||||
|
||||
if frame_durations then
|
||||
c_frame_durations = ffi.new("u16[?]", frame_count)
|
||||
for i = 1, frame_count do
|
||||
c_frame_durations[i-1] = frame_durations[i]
|
||||
end
|
||||
end
|
||||
|
||||
return C.pxl8_anim_create(c_frame_ids, c_frame_durations, frame_count)
|
||||
end
|
||||
|
||||
function anim.create_from_ase(filepath)
|
||||
return C.pxl8_anim_create_from_ase(core.gfx, filepath)
|
||||
end
|
||||
|
||||
function anim.destroy(a)
|
||||
C.pxl8_anim_destroy(a)
|
||||
end
|
||||
|
||||
function anim.add_state(a, name, state_anim)
|
||||
return C.pxl8_anim_add_state(a, name, state_anim)
|
||||
end
|
||||
|
||||
function anim.get_current_frame(a)
|
||||
return C.pxl8_anim_get_current_frame(a)
|
||||
end
|
||||
|
||||
function anim.get_current_frame_id(a)
|
||||
return C.pxl8_anim_get_current_frame_id(a)
|
||||
end
|
||||
|
||||
function anim.get_state(a)
|
||||
local state_name = C.pxl8_anim_get_state(a)
|
||||
if state_name ~= nil then
|
||||
return ffi.string(state_name)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function anim.has_state_machine(a)
|
||||
return C.pxl8_anim_has_state_machine(a)
|
||||
end
|
||||
|
||||
function anim.is_complete(a)
|
||||
return C.pxl8_anim_is_complete(a)
|
||||
end
|
||||
|
||||
function anim.is_playing(a)
|
||||
return C.pxl8_anim_is_playing(a)
|
||||
end
|
||||
|
||||
function anim.pause(a)
|
||||
C.pxl8_anim_pause(a)
|
||||
end
|
||||
|
||||
function anim.play(a)
|
||||
C.pxl8_anim_play(a)
|
||||
end
|
||||
|
||||
function anim.render_sprite(a, x, y, w, h, flip_x, flip_y)
|
||||
C.pxl8_anim_render_sprite(a, core.gfx, x, y, w, h, flip_x or false, flip_y or false)
|
||||
end
|
||||
|
||||
function anim.reset(a)
|
||||
C.pxl8_anim_reset(a)
|
||||
end
|
||||
|
||||
function anim.set_frame(a, frame)
|
||||
C.pxl8_anim_set_frame(a, frame)
|
||||
end
|
||||
|
||||
function anim.set_loop(a, loop)
|
||||
C.pxl8_anim_set_loop(a, loop)
|
||||
end
|
||||
|
||||
function anim.set_reverse(a, reverse)
|
||||
C.pxl8_anim_set_reverse(a, reverse)
|
||||
end
|
||||
|
||||
function anim.set_speed(a, speed)
|
||||
C.pxl8_anim_set_speed(a, speed)
|
||||
end
|
||||
|
||||
function anim.set_state(a, name)
|
||||
return C.pxl8_anim_set_state(a, name)
|
||||
end
|
||||
|
||||
function anim.stop(a)
|
||||
C.pxl8_anim_stop(a)
|
||||
end
|
||||
|
||||
function anim.update(a, dt)
|
||||
C.pxl8_anim_update(a, dt)
|
||||
end
|
||||
|
||||
return anim
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
|
||||
local core = {}
|
||||
|
||||
function core.init(gfx, input, rng, sfx, sys)
|
||||
core.gfx = gfx
|
||||
core.input = input
|
||||
core.rng = rng
|
||||
core.sfx = sfx
|
||||
core.sys = sys
|
||||
end
|
||||
|
||||
function core.get_fps()
|
||||
return C.pxl8_get_fps(core.sys)
|
||||
end
|
||||
|
||||
function core.get_width()
|
||||
return C.pxl8_gfx_get_width(core.gfx)
|
||||
end
|
||||
|
||||
function core.get_height()
|
||||
return C.pxl8_gfx_get_height(core.gfx)
|
||||
end
|
||||
|
||||
function core.get_title()
|
||||
local cart = C.pxl8_get_cart()
|
||||
if cart ~= nil then
|
||||
local title = C.pxl8_cart_get_title(cart)
|
||||
if title ~= nil then
|
||||
return ffi.string(title)
|
||||
end
|
||||
end
|
||||
return "pxl8"
|
||||
end
|
||||
|
||||
local function is_user_script(info)
|
||||
local src = info and info.short_src
|
||||
return src and (src:match("%.fnl$") or src:match("%.lua$"))
|
||||
end
|
||||
|
||||
local function get_caller_info()
|
||||
for level = 2, 10 do
|
||||
local info = debug.getinfo(level, "Sl")
|
||||
if not info then break end
|
||||
if is_user_script(info) then
|
||||
return info.short_src, info.currentline or 0
|
||||
end
|
||||
end
|
||||
return "?", 0
|
||||
end
|
||||
|
||||
core.LOG_TRACE = 0
|
||||
core.LOG_DEBUG = 1
|
||||
core.LOG_INFO = 2
|
||||
core.LOG_WARN = 3
|
||||
core.LOG_ERROR = 4
|
||||
|
||||
local function make_logger(level)
|
||||
return function(msg)
|
||||
local src, line = get_caller_info()
|
||||
C.pxl8_lua_log(level, src, line, msg)
|
||||
end
|
||||
end
|
||||
|
||||
core.trace = make_logger(core.LOG_TRACE)
|
||||
core.debug = make_logger(core.LOG_DEBUG)
|
||||
core.info = make_logger(core.LOG_INFO)
|
||||
core.warn = make_logger(core.LOG_WARN)
|
||||
core.error = make_logger(core.LOG_ERROR)
|
||||
|
||||
function core.quit()
|
||||
C.pxl8_set_running(core.sys, false)
|
||||
end
|
||||
|
||||
function core.rng_seed(seed)
|
||||
C.pxl8_rng_seed(core.rng, seed)
|
||||
end
|
||||
|
||||
function core.rng_next()
|
||||
return C.pxl8_rng_next(core.rng)
|
||||
end
|
||||
|
||||
function core.rng_f32()
|
||||
return C.pxl8_rng_f32(core.rng)
|
||||
end
|
||||
|
||||
function core.rng_range(min, max)
|
||||
return C.pxl8_rng_range(core.rng, min, max)
|
||||
end
|
||||
|
||||
return core
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local graphics = {}
|
||||
|
||||
function graphics.clear(color)
|
||||
C.pxl8_clear(core.gfx, color or 0)
|
||||
end
|
||||
|
||||
function graphics.pixel(x, y, color)
|
||||
if color then
|
||||
C.pxl8_pixel(core.gfx, x, y, color)
|
||||
else
|
||||
return C.pxl8_get_pixel(core.gfx, x, y)
|
||||
end
|
||||
end
|
||||
|
||||
function graphics.line(x0, y0, x1, y1, color)
|
||||
C.pxl8_line(core.gfx, x0, y0, x1, y1, color)
|
||||
end
|
||||
|
||||
function graphics.rect(x, y, w, h, color)
|
||||
C.pxl8_rect(core.gfx, x, y, w, h, color)
|
||||
end
|
||||
|
||||
function graphics.rect_fill(x, y, w, h, color)
|
||||
C.pxl8_rect_fill(core.gfx, x, y, w, h, color)
|
||||
end
|
||||
|
||||
function graphics.circle(x, y, r, color)
|
||||
C.pxl8_circle(core.gfx, x, y, r, color)
|
||||
end
|
||||
|
||||
function graphics.circle_fill(x, y, r, color)
|
||||
C.pxl8_circle_fill(core.gfx, x, y, r, color)
|
||||
end
|
||||
|
||||
function graphics.text(str, x, y, color)
|
||||
C.pxl8_text(core.gfx, str, x or 0, y or 0, color or 15)
|
||||
end
|
||||
|
||||
function graphics.sprite(id, x, y, w, h, flip_x, flip_y)
|
||||
C.pxl8_sprite(core.gfx, id or 0, x or 0, y or 0, w or 16, h or 16, flip_x or false, flip_y or false)
|
||||
end
|
||||
|
||||
function graphics.load_palette(filepath)
|
||||
return C.pxl8_gfx_load_palette(core.gfx, filepath)
|
||||
end
|
||||
|
||||
function graphics.load_sprite(filepath)
|
||||
local sprite_id = ffi.new("unsigned int[1]")
|
||||
local result = C.pxl8_gfx_load_sprite(core.gfx, filepath, sprite_id)
|
||||
if result == 0 then
|
||||
return sprite_id[0]
|
||||
else
|
||||
return nil, result
|
||||
end
|
||||
end
|
||||
|
||||
function graphics.create_texture(pixels, width, height)
|
||||
local pixel_data = ffi.new("u8[?]", width * height)
|
||||
for i = 0, width * height - 1 do
|
||||
pixel_data[i] = pixels[i + 1] or 0
|
||||
end
|
||||
local result = C.pxl8_gfx_create_texture(core.gfx, pixel_data, width, height)
|
||||
if result < 0 then
|
||||
return nil
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function graphics.color_ramp(start, count, from_color, to_color)
|
||||
C.pxl8_gfx_color_ramp(core.gfx, start, count, from_color, to_color)
|
||||
end
|
||||
|
||||
function graphics.fade_palette(start, count, amount, target_color)
|
||||
C.pxl8_gfx_fade_palette(core.gfx, start, count, amount, target_color)
|
||||
end
|
||||
|
||||
function graphics.cycle_palette(start, count, step)
|
||||
C.pxl8_gfx_cycle_palette(core.gfx, start, count, step or 1)
|
||||
end
|
||||
|
||||
function graphics.add_palette_cycle(start_index, end_index, speed)
|
||||
return C.pxl8_gfx_add_palette_cycle(core.gfx, start_index, end_index, speed or 1.0)
|
||||
end
|
||||
|
||||
function graphics.remove_palette_cycle(cycle_id)
|
||||
C.pxl8_gfx_remove_palette_cycle(core.gfx, cycle_id)
|
||||
end
|
||||
|
||||
function graphics.set_palette_cycle_speed(cycle_id, speed)
|
||||
C.pxl8_gfx_set_palette_cycle_speed(core.gfx, cycle_id, speed)
|
||||
end
|
||||
|
||||
function graphics.clear_palette_cycles()
|
||||
C.pxl8_gfx_clear_palette_cycles(core.gfx)
|
||||
end
|
||||
|
||||
return graphics
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local gfx3d = {}
|
||||
|
||||
function gfx3d.clear_zbuffer()
|
||||
C.pxl8_3d_clear_zbuffer(core.gfx)
|
||||
end
|
||||
|
||||
function gfx3d.set_model(mat)
|
||||
C.pxl8_3d_set_model(core.gfx, mat)
|
||||
end
|
||||
|
||||
function gfx3d.set_view(mat)
|
||||
C.pxl8_3d_set_view(core.gfx, mat)
|
||||
end
|
||||
|
||||
function gfx3d.set_projection(mat)
|
||||
C.pxl8_3d_set_projection(core.gfx, mat)
|
||||
end
|
||||
|
||||
function gfx3d.set_wireframe(wireframe)
|
||||
C.pxl8_3d_set_wireframe(core.gfx, wireframe)
|
||||
end
|
||||
|
||||
function gfx3d.set_affine_textures(affine)
|
||||
C.pxl8_3d_set_affine_textures(core.gfx, affine)
|
||||
end
|
||||
|
||||
function gfx3d.set_backface_culling(culling)
|
||||
C.pxl8_3d_set_backface_culling(core.gfx, culling)
|
||||
end
|
||||
|
||||
function gfx3d.draw_triangle(v0, v1, v2, color)
|
||||
local vec0 = ffi.new("pxl8_vec3", {x = v0[1], y = v0[2], z = v0[3]})
|
||||
local vec1 = ffi.new("pxl8_vec3", {x = v1[1], y = v1[2], z = v1[3]})
|
||||
local vec2 = ffi.new("pxl8_vec3", {x = v2[1], y = v2[2], z = v2[3]})
|
||||
C.pxl8_3d_draw_triangle_raw(core.gfx, vec0, vec1, vec2, color)
|
||||
end
|
||||
|
||||
function gfx3d.draw_triangle_textured(v0, v1, v2, uv0, uv1, uv2, texture_id)
|
||||
local vec0 = ffi.new("pxl8_vec3", {x = v0[1], y = v0[2], z = v0[3]})
|
||||
local vec1 = ffi.new("pxl8_vec3", {x = v1[1], y = v1[2], z = v1[3]})
|
||||
local vec2 = ffi.new("pxl8_vec3", {x = v2[1], y = v2[2], z = v2[3]})
|
||||
C.pxl8_3d_draw_triangle_textured(core.gfx, vec0, vec1, vec2,
|
||||
uv0[1], uv0[2], uv1[1], uv1[2], uv2[1], uv2[2], texture_id)
|
||||
end
|
||||
|
||||
function gfx3d.draw_line(p0, p1, color)
|
||||
local vec0 = ffi.new("pxl8_vec3", {x = p0[1], y = p0[2], z = p0[3]})
|
||||
local vec1 = ffi.new("pxl8_vec3", {x = p1[1], y = p1[2], z = p1[3]})
|
||||
C.pxl8_3d_draw_line_3d(core.gfx, vec0, vec1, color)
|
||||
end
|
||||
|
||||
return gfx3d
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local gui = {}
|
||||
|
||||
local state = nil
|
||||
|
||||
local function gui_state()
|
||||
if not state then
|
||||
state = ffi.gc(C.pxl8_gui_state_create(), C.pxl8_gui_state_destroy)
|
||||
end
|
||||
return state
|
||||
end
|
||||
|
||||
function gui.begin_frame()
|
||||
C.pxl8_gui_begin_frame(gui_state())
|
||||
end
|
||||
|
||||
function gui.end_frame()
|
||||
C.pxl8_gui_end_frame(gui_state())
|
||||
end
|
||||
|
||||
function gui.cursor_move(x, y)
|
||||
C.pxl8_gui_cursor_move(gui_state(), x, y)
|
||||
end
|
||||
|
||||
function gui.cursor_down()
|
||||
C.pxl8_gui_cursor_down(gui_state())
|
||||
end
|
||||
|
||||
function gui.cursor_up()
|
||||
C.pxl8_gui_cursor_up(gui_state())
|
||||
end
|
||||
|
||||
function gui.button(id, x, y, w, h, label)
|
||||
return C.pxl8_gui_button(gui_state(), core.gfx, id, x, y, w, h, label)
|
||||
end
|
||||
|
||||
function gui.window(x, y, w, h, title)
|
||||
C.pxl8_gui_window(core.gfx, x, y, w, h, title)
|
||||
end
|
||||
|
||||
function gui.label(x, y, text, color)
|
||||
C.pxl8_gui_label(core.gfx, x, y, text, color)
|
||||
end
|
||||
|
||||
function gui.is_hovering()
|
||||
return C.pxl8_gui_is_hovering(gui_state())
|
||||
end
|
||||
|
||||
function gui.get_cursor_pos()
|
||||
local x = ffi.new("i32[1]")
|
||||
local y = ffi.new("i32[1]")
|
||||
C.pxl8_gui_get_cursor_pos(gui_state(), x, y)
|
||||
return x[0], y[0]
|
||||
end
|
||||
|
||||
return gui
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local input = {}
|
||||
|
||||
function input.key_down(key)
|
||||
return C.pxl8_key_down(core.input, key)
|
||||
end
|
||||
|
||||
function input.key_pressed(key)
|
||||
return C.pxl8_key_pressed(core.input, key)
|
||||
end
|
||||
|
||||
function input.key_released(key)
|
||||
return C.pxl8_key_released(core.input, key)
|
||||
end
|
||||
|
||||
function input.mouse_wheel_x()
|
||||
return C.pxl8_mouse_wheel_x(core.input)
|
||||
end
|
||||
|
||||
function input.mouse_wheel_y()
|
||||
return C.pxl8_mouse_wheel_y(core.input)
|
||||
end
|
||||
|
||||
function input.mouse_x()
|
||||
return C.pxl8_mouse_x(core.input)
|
||||
end
|
||||
|
||||
function input.mouse_y()
|
||||
return C.pxl8_mouse_y(core.input)
|
||||
end
|
||||
|
||||
function input.mouse_dx()
|
||||
return C.pxl8_mouse_dx(core.input)
|
||||
end
|
||||
|
||||
function input.mouse_dy()
|
||||
return C.pxl8_mouse_dy(core.input)
|
||||
end
|
||||
|
||||
function input.get_mouse_pos()
|
||||
return C.pxl8_mouse_x(core.input), C.pxl8_mouse_y(core.input)
|
||||
end
|
||||
|
||||
function input.mouse_pressed(button)
|
||||
return C.pxl8_mouse_pressed(core.input, button)
|
||||
end
|
||||
|
||||
function input.mouse_released(button)
|
||||
return C.pxl8_mouse_released(core.input, button)
|
||||
end
|
||||
|
||||
function input.set_relative_mouse_mode(enabled)
|
||||
C.pxl8_set_relative_mouse_mode(core.sys, enabled)
|
||||
end
|
||||
|
||||
function input.center_cursor()
|
||||
C.pxl8_center_cursor(core.sys)
|
||||
end
|
||||
|
||||
function input.set_cursor(cursor_type)
|
||||
local cursor_enum
|
||||
if cursor_type == "arrow" then
|
||||
cursor_enum = C.PXL8_CURSOR_ARROW
|
||||
elseif cursor_type == "hand" then
|
||||
cursor_enum = C.PXL8_CURSOR_HAND
|
||||
else
|
||||
cursor_enum = C.PXL8_CURSOR_ARROW
|
||||
end
|
||||
C.pxl8_set_cursor(core.sys, cursor_enum)
|
||||
end
|
||||
|
||||
return input
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
|
||||
local math3d = {}
|
||||
|
||||
function math3d.mat4_identity()
|
||||
return C.pxl8_mat4_identity()
|
||||
end
|
||||
|
||||
function math3d.mat4_multiply(a, b)
|
||||
return C.pxl8_mat4_multiply(a, b)
|
||||
end
|
||||
|
||||
function math3d.mat4_translate(x, y, z)
|
||||
return C.pxl8_mat4_translate(x, y, z)
|
||||
end
|
||||
|
||||
function math3d.mat4_rotate_x(angle)
|
||||
return C.pxl8_mat4_rotate_x(angle)
|
||||
end
|
||||
|
||||
function math3d.mat4_rotate_y(angle)
|
||||
return C.pxl8_mat4_rotate_y(angle)
|
||||
end
|
||||
|
||||
function math3d.mat4_rotate_z(angle)
|
||||
return C.pxl8_mat4_rotate_z(angle)
|
||||
end
|
||||
|
||||
function math3d.mat4_scale(x, y, z)
|
||||
return C.pxl8_mat4_scale(x, y, z)
|
||||
end
|
||||
|
||||
function math3d.mat4_ortho(left, right, bottom, top, near, far)
|
||||
return C.pxl8_mat4_ortho(left, right, bottom, top, near, far)
|
||||
end
|
||||
|
||||
function math3d.mat4_perspective(fov, aspect, near, far)
|
||||
return C.pxl8_mat4_perspective(fov, aspect, near, far)
|
||||
end
|
||||
|
||||
function math3d.mat4_lookat(eye, center, up)
|
||||
local eye_vec = ffi.new("pxl8_vec3", {x = eye[1], y = eye[2], z = eye[3]})
|
||||
local center_vec = ffi.new("pxl8_vec3", {x = center[1], y = center[2], z = center[3]})
|
||||
local up_vec = ffi.new("pxl8_vec3", {x = up[1], y = up[2], z = up[3]})
|
||||
return C.pxl8_mat4_lookat(eye_vec, center_vec, up_vec)
|
||||
end
|
||||
|
||||
function math3d.bounds(x, y, w, h)
|
||||
return ffi.new("pxl8_bounds", {x = x, y = y, w = w, h = h})
|
||||
end
|
||||
|
||||
return math3d
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local particles = {}
|
||||
|
||||
function particles.new(max_count)
|
||||
return C.pxl8_particles_create(max_count or 1000, core.rng)
|
||||
end
|
||||
|
||||
function particles.destroy(ps)
|
||||
C.pxl8_particles_destroy(ps)
|
||||
end
|
||||
|
||||
function particles.clear(ps)
|
||||
C.pxl8_particles_clear(ps)
|
||||
end
|
||||
|
||||
function particles.emit(ps, count)
|
||||
C.pxl8_particles_emit(ps, count or 1)
|
||||
end
|
||||
|
||||
function particles.update(ps, dt)
|
||||
C.pxl8_particles_update(ps, dt)
|
||||
end
|
||||
|
||||
function particles.render(ps)
|
||||
C.pxl8_particles_render(ps, core.gfx)
|
||||
end
|
||||
|
||||
return particles
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local sfx = {}
|
||||
|
||||
sfx.FILTER_BANDPASS = C.PXL8_SFX_FILTER_BANDPASS
|
||||
sfx.FILTER_HIGHPASS = C.PXL8_SFX_FILTER_HIGHPASS
|
||||
sfx.FILTER_LOWPASS = C.PXL8_SFX_FILTER_LOWPASS
|
||||
sfx.FILTER_NONE = C.PXL8_SFX_FILTER_NONE
|
||||
|
||||
sfx.LFO_AMPLITUDE = C.PXL8_SFX_LFO_AMPLITUDE
|
||||
sfx.LFO_FILTER = C.PXL8_SFX_LFO_FILTER
|
||||
sfx.LFO_PITCH = C.PXL8_SFX_LFO_PITCH
|
||||
|
||||
sfx.NODE_COMPRESSOR = C.PXL8_SFX_NODE_COMPRESSOR
|
||||
sfx.NODE_DELAY = C.PXL8_SFX_NODE_DELAY
|
||||
sfx.NODE_REVERB = C.PXL8_SFX_NODE_REVERB
|
||||
|
||||
sfx.WAVE_NOISE = C.PXL8_SFX_WAVE_NOISE
|
||||
sfx.WAVE_PULSE = C.PXL8_SFX_WAVE_PULSE
|
||||
sfx.WAVE_SAW = C.PXL8_SFX_WAVE_SAW
|
||||
sfx.WAVE_SINE = C.PXL8_SFX_WAVE_SINE
|
||||
sfx.WAVE_SQUARE = C.PXL8_SFX_WAVE_SQUARE
|
||||
sfx.WAVE_TRIANGLE = C.PXL8_SFX_WAVE_TRIANGLE
|
||||
|
||||
function sfx.compressor_create(opts)
|
||||
opts = opts or {}
|
||||
local cfg = ffi.new("pxl8_sfx_compressor_config")
|
||||
cfg.attack = opts.attack or 10
|
||||
cfg.ratio = opts.ratio or 4
|
||||
cfg.release = opts.release or 100
|
||||
cfg.threshold = opts.threshold or -12
|
||||
return C.pxl8_sfx_compressor_create(cfg)
|
||||
end
|
||||
|
||||
function sfx.compressor_set_attack(node, attack)
|
||||
C.pxl8_sfx_compressor_set_attack(node, attack)
|
||||
end
|
||||
|
||||
function sfx.compressor_set_ratio(node, ratio)
|
||||
C.pxl8_sfx_compressor_set_ratio(node, ratio)
|
||||
end
|
||||
|
||||
function sfx.compressor_set_release(node, release)
|
||||
C.pxl8_sfx_compressor_set_release(node, release)
|
||||
end
|
||||
|
||||
function sfx.compressor_set_threshold(node, threshold)
|
||||
C.pxl8_sfx_compressor_set_threshold(node, threshold)
|
||||
end
|
||||
|
||||
function sfx.context_append_node(ctx, node)
|
||||
C.pxl8_sfx_context_append_node(ctx, node)
|
||||
end
|
||||
|
||||
function sfx.context_create()
|
||||
return C.pxl8_sfx_context_create()
|
||||
end
|
||||
|
||||
function sfx.context_destroy(ctx)
|
||||
C.pxl8_sfx_context_destroy(ctx)
|
||||
end
|
||||
|
||||
function sfx.context_get_head(ctx)
|
||||
return C.pxl8_sfx_context_get_head(ctx)
|
||||
end
|
||||
|
||||
function sfx.context_get_volume(ctx)
|
||||
return C.pxl8_sfx_context_get_volume(ctx)
|
||||
end
|
||||
|
||||
function sfx.context_insert_node(ctx, after, node)
|
||||
C.pxl8_sfx_context_insert_node(ctx, after, node)
|
||||
end
|
||||
|
||||
function sfx.context_remove_node(ctx, node)
|
||||
C.pxl8_sfx_context_remove_node(ctx, node)
|
||||
end
|
||||
|
||||
function sfx.context_set_volume(ctx, volume)
|
||||
C.pxl8_sfx_context_set_volume(ctx, volume)
|
||||
end
|
||||
|
||||
function sfx.delay_create(opts)
|
||||
opts = opts or {}
|
||||
local cfg = ffi.new("pxl8_sfx_delay_config")
|
||||
cfg.feedback = opts.feedback or 0.4
|
||||
cfg.mix = opts.mix or 0.25
|
||||
cfg.time_l = opts.time_l or 350
|
||||
cfg.time_r = opts.time_r or 500
|
||||
return C.pxl8_sfx_delay_create(cfg)
|
||||
end
|
||||
|
||||
function sfx.delay_set_feedback(node, feedback)
|
||||
C.pxl8_sfx_delay_set_feedback(node, feedback)
|
||||
end
|
||||
|
||||
function sfx.delay_set_mix(node, mix)
|
||||
C.pxl8_sfx_delay_set_mix(node, mix)
|
||||
end
|
||||
|
||||
function sfx.delay_set_time(node, time_l, time_r)
|
||||
C.pxl8_sfx_delay_set_time(node, time_l, time_r)
|
||||
end
|
||||
|
||||
function sfx.get_master_volume()
|
||||
return C.pxl8_sfx_mixer_get_master_volume(core.sfx)
|
||||
end
|
||||
|
||||
function sfx.mixer_attach(ctx)
|
||||
C.pxl8_sfx_mixer_attach(core.sfx, ctx)
|
||||
end
|
||||
|
||||
function sfx.mixer_detach(ctx)
|
||||
C.pxl8_sfx_mixer_detach(core.sfx, ctx)
|
||||
end
|
||||
|
||||
function sfx.node_destroy(node)
|
||||
C.pxl8_sfx_node_destroy(node)
|
||||
end
|
||||
|
||||
function sfx.note_to_freq(note)
|
||||
return C.pxl8_sfx_note_to_freq(note)
|
||||
end
|
||||
|
||||
function sfx.play_note(ctx, note, params, volume, duration)
|
||||
return C.pxl8_sfx_play_note(ctx, note, params, volume or 0.8, duration or 0)
|
||||
end
|
||||
|
||||
function sfx.release_voice(ctx, voice_id)
|
||||
C.pxl8_sfx_release_voice(ctx, voice_id)
|
||||
end
|
||||
|
||||
function sfx.reverb_create(opts)
|
||||
opts = opts or {}
|
||||
local cfg = ffi.new("pxl8_sfx_reverb_config")
|
||||
cfg.damping = opts.damping or 0.5
|
||||
cfg.mix = opts.mix or 0.3
|
||||
cfg.room = opts.room or 0.5
|
||||
return C.pxl8_sfx_reverb_create(cfg)
|
||||
end
|
||||
|
||||
function sfx.reverb_set_damping(node, damping)
|
||||
C.pxl8_sfx_reverb_set_damping(node, damping)
|
||||
end
|
||||
|
||||
function sfx.reverb_set_mix(node, mix)
|
||||
C.pxl8_sfx_reverb_set_mix(node, mix)
|
||||
end
|
||||
|
||||
function sfx.reverb_set_room(node, room)
|
||||
C.pxl8_sfx_reverb_set_room(node, room)
|
||||
end
|
||||
|
||||
function sfx.set_master_volume(volume)
|
||||
C.pxl8_sfx_mixer_set_master_volume(core.sfx, volume)
|
||||
end
|
||||
|
||||
function sfx.stop_all(ctx)
|
||||
C.pxl8_sfx_stop_all(ctx)
|
||||
end
|
||||
|
||||
function sfx.stop_voice(ctx, voice_id)
|
||||
C.pxl8_sfx_stop_voice(ctx, voice_id)
|
||||
end
|
||||
|
||||
function sfx.voice_params(opts)
|
||||
opts = opts or {}
|
||||
local params = ffi.new("pxl8_sfx_voice_params")
|
||||
|
||||
params.amp_env.attack = opts.attack or 0.01
|
||||
params.amp_env.decay = opts.decay or 0.1
|
||||
params.amp_env.sustain = opts.sustain or 0.5
|
||||
params.amp_env.release = opts.release or 0.2
|
||||
|
||||
params.filter_env.attack = opts.filter_attack or 0.01
|
||||
params.filter_env.decay = opts.filter_decay or 0.1
|
||||
params.filter_env.sustain = opts.filter_sustain or 0.3
|
||||
params.filter_env.release = opts.filter_release or 0.1
|
||||
|
||||
params.filter_type = opts.filter_type or C.PXL8_SFX_FILTER_NONE
|
||||
params.lfo_target = opts.lfo_target or C.PXL8_SFX_LFO_PITCH
|
||||
params.lfo_waveform = opts.lfo_waveform or C.PXL8_SFX_WAVE_SINE
|
||||
params.waveform = opts.waveform or C.PXL8_SFX_WAVE_SINE
|
||||
|
||||
params.filter_cutoff = opts.filter_cutoff or 4000
|
||||
params.filter_env_depth = opts.filter_env_depth or 0
|
||||
params.filter_resonance = opts.filter_resonance or 0
|
||||
params.fx_send = opts.fx_send or 0
|
||||
params.lfo_depth = opts.lfo_depth or 0
|
||||
params.lfo_rate = opts.lfo_rate or 0
|
||||
params.pulse_width = opts.pulse_width or 0.5
|
||||
|
||||
return params
|
||||
end
|
||||
|
||||
return sfx
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local tilemap = {}
|
||||
|
||||
tilemap.TILE_FLIP_X = 1
|
||||
tilemap.TILE_FLIP_Y = 2
|
||||
tilemap.TILE_SOLID = 4
|
||||
tilemap.TILE_TRIGGER = 8
|
||||
|
||||
local tile_data = setmetatable({}, {__mode = "k"})
|
||||
|
||||
function tilemap.tilesheet_new(tile_size)
|
||||
return C.pxl8_tilesheet_create(tile_size or 16)
|
||||
end
|
||||
|
||||
function tilemap.tilesheet_destroy(tilesheet)
|
||||
C.pxl8_tilesheet_destroy(tilesheet)
|
||||
end
|
||||
|
||||
function tilemap.tilesheet_load(tilesheet, filepath)
|
||||
return C.pxl8_tilesheet_load(tilesheet, filepath, core.gfx)
|
||||
end
|
||||
|
||||
function tilemap.new(width, height, tile_size)
|
||||
return C.pxl8_tilemap_create(width, height, tile_size or 16)
|
||||
end
|
||||
|
||||
function tilemap.destroy(tm)
|
||||
C.pxl8_tilemap_destroy(tm)
|
||||
end
|
||||
|
||||
function tilemap.set_tilesheet(tm, tilesheet)
|
||||
return C.pxl8_tilemap_set_tilesheet(tm, tilesheet)
|
||||
end
|
||||
|
||||
function tilemap.set_tile(tm, layer, x, y, tile_id, flags)
|
||||
C.pxl8_tilemap_set_tile(tm, layer or 0, x, y, tile_id or 0, flags or 0)
|
||||
end
|
||||
|
||||
function tilemap.get_tile_id(tm, layer, x, y)
|
||||
return C.pxl8_tilemap_get_tile_id(tm, layer or 0, x, y)
|
||||
end
|
||||
|
||||
function tilemap.set_camera(tm, x, y)
|
||||
C.pxl8_tilemap_set_camera(tm, x, y)
|
||||
end
|
||||
|
||||
function tilemap.render(tm)
|
||||
C.pxl8_tilemap_render(tm, core.gfx)
|
||||
end
|
||||
|
||||
function tilemap.render_layer(tm, layer)
|
||||
C.pxl8_tilemap_render_layer(tm, core.gfx, layer)
|
||||
end
|
||||
|
||||
function tilemap.is_solid(tm, x, y)
|
||||
return C.pxl8_tilemap_is_solid(tm, x, y)
|
||||
end
|
||||
|
||||
function tilemap.check_collision(tm, x, y, w, h)
|
||||
return C.pxl8_tilemap_check_collision(tm, x, y, w, h)
|
||||
end
|
||||
|
||||
function tilemap.get_tile_data(tm, tile_id)
|
||||
if not tm or tile_id == 0 then return nil end
|
||||
if not tile_data[tm] then return nil end
|
||||
return tile_data[tm][tile_id]
|
||||
end
|
||||
|
||||
function tilemap.load_ase(tm, filepath, layer)
|
||||
return C.pxl8_tilemap_load_ase(tm, filepath, layer or 0)
|
||||
end
|
||||
|
||||
function tilemap.set_tile_data(tm, tile_id, data)
|
||||
if not tm or tile_id == 0 then return end
|
||||
if not tile_data[tm] then tile_data[tm] = {} end
|
||||
tile_data[tm][tile_id] = data
|
||||
end
|
||||
|
||||
return tilemap
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local transition = {}
|
||||
|
||||
local transition_types = {
|
||||
fade = 0,
|
||||
wipe_left = 1,
|
||||
wipe_right = 2,
|
||||
wipe_up = 3,
|
||||
wipe_down = 4,
|
||||
circle_open = 5,
|
||||
circle_close = 6,
|
||||
dissolve = 7,
|
||||
pixelate = 8
|
||||
}
|
||||
|
||||
function transition.create(type_name, duration)
|
||||
local type_id = transition_types[type_name] or 0
|
||||
return C.pxl8_transition_create(type_id, duration or 1.0)
|
||||
end
|
||||
|
||||
function transition.destroy(t)
|
||||
C.pxl8_transition_destroy(t)
|
||||
end
|
||||
|
||||
function transition.get_progress(t)
|
||||
return C.pxl8_transition_get_progress(t)
|
||||
end
|
||||
|
||||
function transition.is_active(t)
|
||||
return C.pxl8_transition_is_active(t)
|
||||
end
|
||||
|
||||
function transition.is_complete(t)
|
||||
return C.pxl8_transition_is_complete(t)
|
||||
end
|
||||
|
||||
function transition.render(t)
|
||||
C.pxl8_transition_render(t, core.gfx)
|
||||
end
|
||||
|
||||
function transition.reset(t)
|
||||
C.pxl8_transition_reset(t)
|
||||
end
|
||||
|
||||
function transition.set_color(t, color)
|
||||
C.pxl8_transition_set_color(t, color)
|
||||
end
|
||||
|
||||
function transition.set_reverse(t, reverse)
|
||||
C.pxl8_transition_set_reverse(t, reverse)
|
||||
end
|
||||
|
||||
function transition.start(t)
|
||||
C.pxl8_transition_start(t)
|
||||
end
|
||||
|
||||
function transition.stop(t)
|
||||
C.pxl8_transition_stop(t)
|
||||
end
|
||||
|
||||
function transition.update(t, dt)
|
||||
C.pxl8_transition_update(t, dt)
|
||||
end
|
||||
|
||||
return transition
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local vfx = {}
|
||||
|
||||
function vfx.raster_bars(bars, time)
|
||||
local c_bars = ffi.new("pxl8_raster_bar[?]", #bars)
|
||||
for i, bar in ipairs(bars) do
|
||||
c_bars[i-1].base_y = bar.base_y or 0
|
||||
c_bars[i-1].amplitude = bar.amplitude or 10
|
||||
c_bars[i-1].height = bar.height or 5
|
||||
c_bars[i-1].speed = bar.speed or 1.0
|
||||
c_bars[i-1].phase = bar.phase or 0
|
||||
c_bars[i-1].color = bar.color or 15
|
||||
c_bars[i-1].fade_color = bar.fade_color or bar.color or 15
|
||||
end
|
||||
C.pxl8_vfx_raster_bars(core.gfx, c_bars, #bars, time)
|
||||
end
|
||||
|
||||
function vfx.plasma(time, scale1, scale2, palette_offset)
|
||||
C.pxl8_vfx_plasma(core.gfx, time, scale1 or 0.05, scale2 or 0.03, palette_offset or 0)
|
||||
end
|
||||
|
||||
function vfx.rotozoom(angle, zoom, cx, cy)
|
||||
local width = C.pxl8_gfx_get_width(core.gfx)
|
||||
local height = C.pxl8_gfx_get_height(core.gfx)
|
||||
C.pxl8_vfx_rotozoom(core.gfx, angle, zoom, cx or width/2, cy or height/2)
|
||||
end
|
||||
|
||||
function vfx.tunnel(time, speed, twist)
|
||||
C.pxl8_vfx_tunnel(core.gfx, time, speed or 2.0, twist or 0.5)
|
||||
end
|
||||
|
||||
function vfx.explosion(ps, x, y, color, force)
|
||||
C.pxl8_vfx_explosion(ps, x, y, color or 15, force or 200.0)
|
||||
end
|
||||
|
||||
function vfx.fire(ps, x, y, width, palette_start)
|
||||
C.pxl8_vfx_fire(ps, x, y, width or 50, palette_start or 64)
|
||||
end
|
||||
|
||||
function vfx.rain(ps, width, wind)
|
||||
local w = width or C.pxl8_gfx_get_width(core.gfx)
|
||||
C.pxl8_vfx_rain(ps, w, wind or 0.0)
|
||||
end
|
||||
|
||||
function vfx.smoke(ps, x, y, color)
|
||||
C.pxl8_vfx_smoke(ps, x, y, color or 8)
|
||||
end
|
||||
|
||||
function vfx.snow(ps, width, wind)
|
||||
local w = width or C.pxl8_gfx_get_width(core.gfx)
|
||||
C.pxl8_vfx_snow(ps, w, wind or 10.0)
|
||||
end
|
||||
|
||||
function vfx.sparks(ps, x, y, color)
|
||||
C.pxl8_vfx_sparks(ps, x, y, color or 15)
|
||||
end
|
||||
|
||||
function vfx.starfield(ps, speed, spread)
|
||||
C.pxl8_vfx_starfield(ps, speed or 5.0, spread or 500.0)
|
||||
end
|
||||
|
||||
return vfx
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local world = {}
|
||||
|
||||
world.PROCGEN_ROOMS = C.PXL8_PROCGEN_ROOMS
|
||||
world.PROCGEN_TERRAIN = C.PXL8_PROCGEN_TERRAIN
|
||||
|
||||
function world.new()
|
||||
return C.pxl8_world_create()
|
||||
end
|
||||
|
||||
function world.destroy(w)
|
||||
C.pxl8_world_destroy(w)
|
||||
end
|
||||
|
||||
function world.load(w, filepath)
|
||||
return C.pxl8_world_load(w, filepath)
|
||||
end
|
||||
|
||||
function world.render(w, camera_pos)
|
||||
local vec = ffi.new("pxl8_vec3", {x = camera_pos[1], y = camera_pos[2], z = camera_pos[3]})
|
||||
C.pxl8_world_render(w, core.gfx, vec)
|
||||
end
|
||||
|
||||
function world.unload(w)
|
||||
C.pxl8_world_unload(w)
|
||||
end
|
||||
|
||||
function world.is_loaded(w)
|
||||
return C.pxl8_world_is_loaded(w)
|
||||
end
|
||||
|
||||
function world.generate(w, params)
|
||||
local c_params = ffi.new("pxl8_procgen_params")
|
||||
c_params.type = params.type or C.PXL8_PROCGEN_ROOMS
|
||||
c_params.width = params.width or 32
|
||||
c_params.height = params.height or 32
|
||||
c_params.depth = params.depth or 0
|
||||
c_params.seed = params.seed or 0
|
||||
c_params.min_room_size = params.min_room_size or 5
|
||||
c_params.max_room_size = params.max_room_size or 10
|
||||
c_params.num_rooms = params.num_rooms or 8
|
||||
return C.pxl8_world_generate(w, core.gfx, c_params)
|
||||
end
|
||||
|
||||
function world.procgen_tex(params)
|
||||
local width = params.width or 64
|
||||
local height = params.height or 64
|
||||
local buffer = ffi.new("u8[?]", width * height)
|
||||
local tex_params = ffi.new("pxl8_procgen_tex_params")
|
||||
|
||||
local name = params.name or ""
|
||||
ffi.copy(tex_params.name, name, math.min(#name, 15))
|
||||
|
||||
tex_params.seed = params.seed or 0
|
||||
tex_params.width = width
|
||||
tex_params.height = height
|
||||
tex_params.scale = params.scale or 1.0
|
||||
tex_params.roughness = params.roughness or 0.0
|
||||
tex_params.base_color = params.base_color or 0
|
||||
tex_params.variation = params.variation or 0
|
||||
|
||||
C.pxl8_procgen_tex(buffer, tex_params)
|
||||
|
||||
local tex_id = C.pxl8_gfx_create_texture(core.gfx, buffer, width, height)
|
||||
if tex_id < 0 then
|
||||
return nil
|
||||
end
|
||||
return tex_id
|
||||
end
|
||||
|
||||
function world.apply_textures(w, texture_defs)
|
||||
local count = #texture_defs
|
||||
local textures = ffi.new("pxl8_world_texture[?]", count)
|
||||
|
||||
for i, def in ipairs(texture_defs) do
|
||||
local idx = i - 1
|
||||
ffi.copy(textures[idx].name, def.name or "", math.min(#(def.name or ""), 15))
|
||||
textures[idx].texture_id = def.texture_id or 0
|
||||
|
||||
if def.rule then
|
||||
textures[idx].rule = ffi.cast("bool (*)(const pxl8_vec3*, const pxl8_bsp_face*, const pxl8_bsp*)",
|
||||
function(normal, face, bsp)
|
||||
return def.rule(normal[0], face, bsp)
|
||||
end)
|
||||
else
|
||||
textures[idx].rule = nil
|
||||
end
|
||||
end
|
||||
|
||||
local result = C.pxl8_world_apply_textures(w, textures, count)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
function world.check_collision(w, x, y, z, radius)
|
||||
local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z})
|
||||
return C.pxl8_world_check_collision(w, pos, radius)
|
||||
end
|
||||
|
||||
function world.resolve_collision(w, from_x, from_y, from_z, to_x, to_y, to_z, radius)
|
||||
local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z})
|
||||
local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z})
|
||||
local result = C.pxl8_world_resolve_collision(w, from, to, radius)
|
||||
return result.x, result.y, result.z
|
||||
end
|
||||
|
||||
return world
|
||||
485
src/pxl8.c
485
src/pxl8.c
|
|
@ -1,485 +0,0 @@
|
|||
#define PXL8_AUTHORS "asrael <asrael@pxl8.org>"
|
||||
#define PXL8_COPYRIGHT "Copyright (c) 2024-2025 pxl8.org"
|
||||
#define PXL8_VERSION "0.1.0"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "pxl8_game.h"
|
||||
#include "pxl8_hal.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_macros.h"
|
||||
#include "pxl8_repl.h"
|
||||
#include "pxl8_replay.h"
|
||||
#include "pxl8_script.h"
|
||||
#include "pxl8_sfx.h"
|
||||
#include "pxl8_sys.h"
|
||||
|
||||
struct pxl8 {
|
||||
pxl8_cart* cart;
|
||||
pxl8_game* game;
|
||||
pxl8_repl* repl;
|
||||
pxl8_log log;
|
||||
const pxl8_hal* hal;
|
||||
void* platform_data;
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
static void pxl8_audio_event_callback(u8 event_type, u8 context_id, u8 note, f32 volume, void* userdata) {
|
||||
pxl8_game* game = (pxl8_game*)userdata;
|
||||
if (game && game->debug_replay) {
|
||||
pxl8_replay_write_audio_event(game->debug_replay, game->frame_count, event_type, context_id, note, volume);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
pxl8* pxl8_create(const pxl8_hal* hal) {
|
||||
pxl8* sys = (pxl8*)calloc(1, sizeof(pxl8));
|
||||
if (!sys) return NULL;
|
||||
|
||||
pxl8_log_init(&sys->log);
|
||||
|
||||
if (!hal) {
|
||||
pxl8_error("hal cannot be null");
|
||||
free(sys);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sys->hal = hal;
|
||||
|
||||
sys->game = (pxl8_game*)calloc(1, sizeof(pxl8_game));
|
||||
if (!sys->game) {
|
||||
pxl8_error("failed to allocate game");
|
||||
free(sys);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return sys;
|
||||
}
|
||||
|
||||
void pxl8_destroy(pxl8* sys) {
|
||||
if (!sys) return;
|
||||
|
||||
if (sys->game) free(sys->game);
|
||||
if (sys->cart) pxl8_cart_destroy(sys->cart);
|
||||
if (sys->hal && sys->platform_data) sys->hal->destroy(sys->platform_data);
|
||||
|
||||
free(sys);
|
||||
}
|
||||
|
||||
static void pxl8_print_help(void) {
|
||||
printf("pxl8 %s - pixel art game framework\n", PXL8_VERSION);
|
||||
printf("%s\n\n", PXL8_COPYRIGHT);
|
||||
printf("Usage: pxl8 [path] [--repl]\n\n");
|
||||
printf(" pxl8 Run main.fnl from current directory\n");
|
||||
printf(" pxl8 ./game Run game from folder\n");
|
||||
printf(" pxl8 game.pxc Run packed cart file\n");
|
||||
printf(" pxl8 --repl Run with REPL enabled\n\n");
|
||||
printf("Other commands:\n");
|
||||
printf(" pxl8 pack <folder> <out.pxc> Pack folder into cart file\n");
|
||||
printf(" pxl8 bundle <in> <out> Bundle cart into executable\n");
|
||||
printf(" pxl8 help Show this help\n");
|
||||
}
|
||||
|
||||
pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
||||
if (!sys || !sys->game) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
pxl8_game* game = sys->game;
|
||||
const char* script_arg = NULL;
|
||||
bool bundle_mode = false;
|
||||
bool pack_mode = false;
|
||||
bool run_mode = false;
|
||||
const char* pack_input = NULL;
|
||||
const char* pack_output = NULL;
|
||||
|
||||
bool has_embedded = pxl8_cart_has_embedded(argv[0]);
|
||||
|
||||
for (i32 i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "help") == 0 || strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
|
||||
pxl8_print_help();
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
} else if (strcmp(argv[i], "run") == 0) {
|
||||
run_mode = true;
|
||||
} else if (strcmp(argv[i], "--repl") == 0) {
|
||||
game->repl_mode = true;
|
||||
} else if (strcmp(argv[i], "bundle") == 0 || strcmp(argv[i], "--bundle") == 0) {
|
||||
bundle_mode = true;
|
||||
if (i + 2 < argc) {
|
||||
pack_input = argv[++i];
|
||||
pack_output = argv[++i];
|
||||
} else {
|
||||
pxl8_error("bundle requires <folder|.pxc> <output>");
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
} else if (strcmp(argv[i], "pack") == 0 || strcmp(argv[i], "--pack") == 0) {
|
||||
pack_mode = true;
|
||||
if (i + 2 < argc) {
|
||||
pack_input = argv[++i];
|
||||
pack_output = argv[++i];
|
||||
} else {
|
||||
pxl8_error("pack requires <folder> <output.pxc>");
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
} else if (!script_arg) {
|
||||
script_arg = argv[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!run_mode && !bundle_mode && !pack_mode) {
|
||||
run_mode = true;
|
||||
}
|
||||
|
||||
if (bundle_mode) {
|
||||
char exe_path[1024];
|
||||
ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
|
||||
if (len == -1) {
|
||||
pxl8_error("failed to resolve executable path");
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
exe_path[len] = '\0';
|
||||
pxl8_result result = pxl8_cart_bundle(pack_input, pack_output, exe_path);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (pack_mode) {
|
||||
pxl8_result result = pxl8_cart_pack(pack_input, pack_output);
|
||||
return result;
|
||||
}
|
||||
|
||||
pxl8_info("Starting up");
|
||||
|
||||
game->script = pxl8_script_create(game->repl_mode);
|
||||
if (!game->script) {
|
||||
pxl8_error("failed to initialize scripting: %s", pxl8_script_get_last_error(game->script));
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
const char* cart_path = script_arg;
|
||||
char* original_cwd = getcwd(NULL, 0);
|
||||
|
||||
bool load_embedded = has_embedded && !run_mode;
|
||||
bool load_from_path = false;
|
||||
|
||||
if (!load_embedded && run_mode) {
|
||||
if (!cart_path) {
|
||||
if (access("main.fnl", F_OK) == 0 || access("main.lua", F_OK) == 0) {
|
||||
cart_path = ".";
|
||||
} else {
|
||||
pxl8_error("no main.fnl or main.lua found in current directory");
|
||||
free(original_cwd);
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
}
|
||||
struct stat st;
|
||||
load_from_path = (stat(cart_path, &st) == 0 && S_ISDIR(st.st_mode)) ||
|
||||
strstr(cart_path, ".pxc");
|
||||
}
|
||||
|
||||
if (load_embedded || load_from_path) {
|
||||
sys->cart = pxl8_cart_create();
|
||||
pxl8_result load_result = load_embedded
|
||||
? pxl8_cart_load_embedded(sys->cart, argv[0])
|
||||
: pxl8_cart_load(sys->cart, cart_path);
|
||||
|
||||
if (!sys->cart || load_result != PXL8_OK) {
|
||||
pxl8_error("failed to load cart%s%s", load_from_path ? ": " : "", load_from_path ? cart_path : "");
|
||||
if (sys->cart) pxl8_cart_destroy(sys->cart);
|
||||
sys->cart = NULL;
|
||||
free(original_cwd);
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
pxl8_cart_mount(sys->cart);
|
||||
pxl8_script_load_cart_manifest(game->script, sys->cart);
|
||||
if (load_from_path) {
|
||||
pxl8_script_set_cart_path(game->script, pxl8_cart_get_base_path(sys->cart), original_cwd);
|
||||
}
|
||||
pxl8_strncpy(game->script_path, "main.fnl", sizeof(game->script_path));
|
||||
} else if (script_arg) {
|
||||
pxl8_strncpy(game->script_path, script_arg, sizeof(game->script_path));
|
||||
}
|
||||
free(original_cwd);
|
||||
|
||||
const char* window_title = pxl8_cart_get_title(sys->cart);
|
||||
if (!window_title) window_title = "pxl8";
|
||||
pxl8_resolution resolution = pxl8_cart_get_resolution(sys->cart);
|
||||
pxl8_pixel_mode pixel_mode = pxl8_cart_get_pixel_mode(sys->cart);
|
||||
pxl8_size window_size = pxl8_cart_get_window_size(sys->cart);
|
||||
pxl8_size render_size = pxl8_get_resolution_dimensions(resolution);
|
||||
|
||||
sys->platform_data = sys->hal->create(render_size.w, render_size.h, window_title, window_size.w, window_size.h);
|
||||
if (!sys->platform_data) {
|
||||
pxl8_error("failed to create platform context");
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
game->gfx = pxl8_gfx_create(sys->hal, sys->platform_data, pixel_mode, resolution);
|
||||
if (!game->gfx) {
|
||||
pxl8_error("failed to create graphics context");
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
if (pxl8_gfx_load_font_atlas(game->gfx) != PXL8_OK) {
|
||||
pxl8_error("failed to load font atlas");
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
game->mixer = pxl8_sfx_mixer_create(sys->hal);
|
||||
if (!game->mixer) {
|
||||
pxl8_error("failed to create audio mixer");
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
pxl8_rng_seed(&game->rng, (u32)sys->hal->get_ticks());
|
||||
|
||||
#ifndef NDEBUG
|
||||
game->debug_replay = pxl8_replay_create_buffer(60, 60);
|
||||
pxl8_sfx_mixer_set_event_callback(game->mixer, pxl8_audio_event_callback, game);
|
||||
#endif
|
||||
|
||||
if (game->repl_mode) {
|
||||
pxl8_info("starting in REPL mode with script: %s", game->script_path);
|
||||
}
|
||||
|
||||
pxl8_script_set_gfx(game->script, game->gfx);
|
||||
pxl8_script_set_input(game->script, &game->input);
|
||||
pxl8_script_set_rng(game->script, &game->rng);
|
||||
pxl8_script_set_sfx(game->script, game->mixer);
|
||||
pxl8_script_set_sys(game->script, sys);
|
||||
|
||||
if (game->script_path[0] != '\0') {
|
||||
pxl8_result result = pxl8_script_load_main(game->script, game->script_path);
|
||||
game->script_loaded = (result == PXL8_OK);
|
||||
|
||||
if (game->script_loaded && !game->repl_mode) {
|
||||
pxl8_result init_result = pxl8_script_call_function(game->script, "init");
|
||||
if (init_result != PXL8_OK) {
|
||||
pxl8_script_error("%s", pxl8_script_get_last_error(game->script));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
game->last_time = sys->hal->get_ticks();
|
||||
game->running = true;
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_update(pxl8* sys) {
|
||||
if (!sys || !sys->game) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
pxl8_game* game = sys->game;
|
||||
u64 current_time = sys->hal->get_ticks();
|
||||
f32 dt = (f32)(current_time - game->last_time) / 1000000000.0f;
|
||||
|
||||
game->last_time = current_time;
|
||||
game->time += dt;
|
||||
game->fps_accumulator += dt;
|
||||
game->fps_frame_count++;
|
||||
|
||||
if (game->fps_accumulator >= 1.0f) {
|
||||
game->fps = (f32)game->fps_frame_count / game->fps_accumulator;
|
||||
game->fps_accumulator = 0.0f;
|
||||
game->fps_frame_count = 0;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
u32 rng_state_before_reload = game->rng.state;
|
||||
#endif
|
||||
bool reloaded = pxl8_script_check_reload(game->script);
|
||||
#ifndef NDEBUG
|
||||
if (reloaded) {
|
||||
game->rng.state = rng_state_before_reload;
|
||||
pxl8_debug("Hot-reload: restored RNG state 0x%08X", rng_state_before_reload);
|
||||
}
|
||||
#else
|
||||
(void)reloaded;
|
||||
#endif
|
||||
|
||||
if (game->repl_mode && !game->repl_started) {
|
||||
if (game->script_loaded) {
|
||||
pxl8_script_call_function(game->script, "init");
|
||||
}
|
||||
|
||||
if (pxl8_script_load_module(game->script, "pxl8") != PXL8_OK) {
|
||||
const char* err_msg = pxl8_script_get_last_error(game->script);
|
||||
pxl8_error("failed to setup pxl8 global: %s", err_msg);
|
||||
}
|
||||
|
||||
sys->repl = pxl8_repl_create();
|
||||
game->repl_started = true;
|
||||
}
|
||||
|
||||
if (game->repl_mode && sys->repl) {
|
||||
if (pxl8_repl_should_quit(sys->repl)) game->running = false;
|
||||
|
||||
pxl8_repl_command* cmd = pxl8_repl_pop_command(sys->repl);
|
||||
if (cmd) {
|
||||
pxl8_result result = pxl8_script_eval_repl(game->script, pxl8_repl_command_buffer(cmd));
|
||||
if (result != PXL8_OK) {
|
||||
if (pxl8_script_is_incomplete_input(game->script)) {
|
||||
pxl8_repl_signal_complete(sys->repl);
|
||||
} else {
|
||||
pxl8_error("%s", pxl8_script_get_last_error(game->script));
|
||||
pxl8_repl_clear_accumulator(sys->repl);
|
||||
pxl8_repl_signal_complete(sys->repl);
|
||||
}
|
||||
} else {
|
||||
pxl8_repl_clear_accumulator(sys->repl);
|
||||
pxl8_repl_signal_complete(sys->repl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_gfx_update(game->gfx, dt);
|
||||
pxl8_sfx_mixer_process(game->mixer);
|
||||
|
||||
if (game->script_loaded) {
|
||||
pxl8_script_call_function_f32(game->script, "update", dt);
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_frame(pxl8* sys) {
|
||||
if (!sys || !sys->game) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
pxl8_game* game = sys->game;
|
||||
pxl8_bounds bounds = pxl8_gfx_get_bounds(game->gfx);
|
||||
|
||||
if (game->script_loaded) {
|
||||
pxl8_result frame_result = pxl8_script_call_function(game->script, "frame");
|
||||
if (frame_result == PXL8_ERROR_SCRIPT_ERROR) {
|
||||
pxl8_error("error calling frame: %s", pxl8_script_get_last_error(game->script));
|
||||
}
|
||||
} else {
|
||||
pxl8_clear(game->gfx, 32);
|
||||
|
||||
i32 render_w = pxl8_gfx_get_width(game->gfx);
|
||||
i32 render_h = pxl8_gfx_get_height(game->gfx);
|
||||
|
||||
for (i32 y = 0; y < render_h; y += 24) {
|
||||
for (i32 x = 0; x < render_w; x += 32) {
|
||||
u32 color = ((x / 32) + (y / 24) + (i32)(game->time * 2)) % 8;
|
||||
pxl8_rect_fill(game->gfx, x, y, 31, 23, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_gfx_set_viewport(game->gfx, pxl8_gfx_viewport(bounds, pxl8_gfx_get_width(game->gfx), pxl8_gfx_get_height(game->gfx)));
|
||||
pxl8_gfx_upload_framebuffer(game->gfx);
|
||||
pxl8_gfx_present(game->gfx);
|
||||
|
||||
memset(game->input.keys_pressed, 0, sizeof(game->input.keys_pressed));
|
||||
memset(game->input.keys_released, 0, sizeof(game->input.keys_released));
|
||||
memset(game->input.mouse_buttons_pressed, 0, sizeof(game->input.mouse_buttons_pressed));
|
||||
memset(game->input.mouse_buttons_released, 0, sizeof(game->input.mouse_buttons_released));
|
||||
|
||||
game->frame_count++;
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (game->debug_replay) {
|
||||
if (game->frame_count % 60 == 0) {
|
||||
pxl8_replay_write_keyframe(game->debug_replay, game->frame_count, game->time, &game->rng, &game->input);
|
||||
} else {
|
||||
pxl8_replay_write_input(game->debug_replay, game->frame_count, &game->prev_input, &game->input);
|
||||
}
|
||||
game->prev_input = game->input;
|
||||
}
|
||||
#endif
|
||||
|
||||
game->input.mouse_dx = 0;
|
||||
game->input.mouse_dy = 0;
|
||||
game->input.mouse_wheel_x = 0;
|
||||
game->input.mouse_wheel_y = 0;
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_quit(pxl8* sys) {
|
||||
if (!sys || !sys->game) return;
|
||||
|
||||
pxl8_game* game = sys->game;
|
||||
|
||||
pxl8_info("Shutting down");
|
||||
|
||||
if (sys->cart) {
|
||||
pxl8_cart_unmount(sys->cart);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
pxl8_replay_destroy(game->debug_replay);
|
||||
#endif
|
||||
|
||||
pxl8_sfx_mixer_destroy(game->mixer);
|
||||
pxl8_gfx_destroy(game->gfx);
|
||||
pxl8_script_destroy(game->script);
|
||||
}
|
||||
|
||||
bool pxl8_is_running(const pxl8* sys) {
|
||||
return sys && sys->game && sys->game->running;
|
||||
}
|
||||
|
||||
void pxl8_set_running(pxl8* sys, bool running) {
|
||||
if (sys && sys->game) {
|
||||
sys->game->running = running;
|
||||
if (!running && sys->repl) {
|
||||
pxl8_repl_destroy(sys->repl);
|
||||
sys->repl = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f32 pxl8_get_fps(const pxl8* sys) {
|
||||
return (sys && sys->game) ? sys->game->fps : 0.0f;
|
||||
}
|
||||
|
||||
pxl8_gfx* pxl8_get_gfx(const pxl8* sys) {
|
||||
return (sys && sys->game) ? sys->game->gfx : NULL;
|
||||
}
|
||||
|
||||
pxl8_input_state* pxl8_get_input(const pxl8* sys) {
|
||||
return (sys && sys->game) ? &sys->game->input : NULL;
|
||||
}
|
||||
|
||||
pxl8_sfx_mixer* pxl8_get_sfx_mixer(const pxl8* sys) {
|
||||
return (sys && sys->game) ? sys->game->mixer : NULL;
|
||||
}
|
||||
|
||||
void pxl8_center_cursor(pxl8* sys) {
|
||||
if (!sys || !sys->hal || !sys->hal->center_cursor) return;
|
||||
sys->hal->center_cursor(sys->platform_data);
|
||||
}
|
||||
|
||||
void pxl8_set_cursor(pxl8* sys, pxl8_cursor cursor) {
|
||||
if (!sys || !sys->hal || !sys->hal->set_cursor) return;
|
||||
sys->hal->set_cursor(sys->platform_data, cursor);
|
||||
}
|
||||
|
||||
void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled) {
|
||||
if (!sys || !sys->hal || !sys->hal->set_relative_mouse_mode) return;
|
||||
sys->hal->set_relative_mouse_mode(sys->platform_data, enabled);
|
||||
if (sys->game) {
|
||||
sys->game->input.mouse_relative_mode = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_size pxl8_get_resolution_dimensions(pxl8_resolution resolution) {
|
||||
switch (resolution) {
|
||||
case PXL8_RESOLUTION_240x160: return (pxl8_size){240, 160};
|
||||
case PXL8_RESOLUTION_320x180: return (pxl8_size){320, 180};
|
||||
case PXL8_RESOLUTION_320x240: return (pxl8_size){320, 240};
|
||||
case PXL8_RESOLUTION_640x360: return (pxl8_size){640, 360};
|
||||
case PXL8_RESOLUTION_640x480: return (pxl8_size){640, 480};
|
||||
case PXL8_RESOLUTION_800x600: return (pxl8_size){800, 600};
|
||||
case PXL8_RESOLUTION_960x540: return (pxl8_size){960, 540};
|
||||
default: return (pxl8_size){640, 360};
|
||||
}
|
||||
}
|
||||
431
src/pxl8_anim.c
431
src/pxl8_anim.c
|
|
@ -1,431 +0,0 @@
|
|||
#include "pxl8_anim.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_ase.h"
|
||||
#include "pxl8_atlas.h"
|
||||
#include "pxl8_log.h"
|
||||
|
||||
#define PXL8_ANIM_MAX_STATES 32
|
||||
|
||||
typedef struct pxl8_anim_state {
|
||||
char* name;
|
||||
pxl8_anim* anim;
|
||||
} pxl8_anim_state;
|
||||
|
||||
typedef struct pxl8_anim_state_machine {
|
||||
pxl8_anim_state states[PXL8_ANIM_MAX_STATES];
|
||||
u16 state_count;
|
||||
u16 current_state;
|
||||
} pxl8_anim_state_machine;
|
||||
|
||||
static inline pxl8_anim* pxl8_anim_get_active(pxl8_anim* anim) {
|
||||
if (anim->state_machine &&
|
||||
anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) return state_anim;
|
||||
}
|
||||
return anim;
|
||||
}
|
||||
|
||||
pxl8_anim* pxl8_anim_create(const u32* frame_ids, const u16* frame_durations, u16 frame_count) {
|
||||
if (!frame_ids || frame_count == 0) {
|
||||
pxl8_error("Invalid animation parameters");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_anim* anim = (pxl8_anim*)calloc(1, sizeof(pxl8_anim));
|
||||
if (!anim) {
|
||||
pxl8_error("Failed to allocate animation");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
anim->frame_ids = (u32*)malloc(frame_count * sizeof(u32));
|
||||
if (!anim->frame_ids) {
|
||||
pxl8_error("Failed to allocate frame IDs");
|
||||
free(anim);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(anim->frame_ids, frame_ids, frame_count * sizeof(u32));
|
||||
|
||||
if (frame_durations) {
|
||||
anim->frame_durations = (u16*)malloc(frame_count * sizeof(u16));
|
||||
if (!anim->frame_durations) {
|
||||
pxl8_error("Failed to allocate frame durations");
|
||||
free(anim->frame_ids);
|
||||
free(anim);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(anim->frame_durations, frame_durations, frame_count * sizeof(u16));
|
||||
} else {
|
||||
anim->frame_durations = (u16*)calloc(frame_count, sizeof(u16));
|
||||
if (!anim->frame_durations) {
|
||||
pxl8_error("Failed to allocate frame durations");
|
||||
free(anim->frame_ids);
|
||||
free(anim);
|
||||
return NULL;
|
||||
}
|
||||
for (u16 i = 0; i < frame_count; i++) {
|
||||
anim->frame_durations[i] = 100;
|
||||
}
|
||||
}
|
||||
|
||||
anim->frame_count = frame_count;
|
||||
anim->current_frame = 0;
|
||||
anim->time_accumulator = 0.0f;
|
||||
anim->loop = true;
|
||||
anim->playing = true;
|
||||
anim->reverse = false;
|
||||
anim->speed = 1.0f;
|
||||
anim->state_machine = NULL;
|
||||
anim->on_complete = NULL;
|
||||
anim->on_frame_change = NULL;
|
||||
anim->userdata = NULL;
|
||||
|
||||
return anim;
|
||||
}
|
||||
|
||||
pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path) {
|
||||
if (!gfx || !path) {
|
||||
pxl8_error("Invalid parameters for ASE animation creation");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_ase_file ase_file;
|
||||
pxl8_result result = pxl8_ase_load(path, &ase_file);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to load ASE file: %s", path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ase_file.frame_count == 0) {
|
||||
pxl8_error("ASE file has no frames: %s", path);
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
u32* frame_ids = (u32*)malloc(ase_file.frame_count * sizeof(u32));
|
||||
u16* frame_durations = (u16*)malloc(ase_file.frame_count * sizeof(u16));
|
||||
if (!frame_ids || !frame_durations) {
|
||||
pxl8_error("Failed to allocate frame arrays");
|
||||
free(frame_ids);
|
||||
free(frame_durations);
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < ase_file.frame_count; i++) {
|
||||
pxl8_ase_frame* frame = &ase_file.frames[i];
|
||||
result = pxl8_gfx_create_texture(gfx, frame->pixels, frame->width, frame->height);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to create texture for frame %u", i);
|
||||
free(frame_ids);
|
||||
free(frame_durations);
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return NULL;
|
||||
}
|
||||
frame_ids[i] = i;
|
||||
frame_durations[i] = frame->duration;
|
||||
}
|
||||
|
||||
pxl8_anim* anim = pxl8_anim_create(frame_ids, frame_durations, ase_file.frame_count);
|
||||
|
||||
free(frame_ids);
|
||||
free(frame_durations);
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
|
||||
return anim;
|
||||
}
|
||||
|
||||
void pxl8_anim_destroy(pxl8_anim* anim) {
|
||||
if (!anim) return;
|
||||
|
||||
if (anim->state_machine) {
|
||||
for (u16 i = 0; i < anim->state_machine->state_count; i++) {
|
||||
free(anim->state_machine->states[i].name);
|
||||
pxl8_anim_destroy(anim->state_machine->states[i].anim);
|
||||
}
|
||||
free(anim->state_machine);
|
||||
}
|
||||
|
||||
free(anim->frame_ids);
|
||||
free(anim->frame_durations);
|
||||
free(anim);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* state_anim) {
|
||||
if (!anim || !name || !state_anim) {
|
||||
return PXL8_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
if (!anim->state_machine) {
|
||||
anim->state_machine = (pxl8_anim_state_machine*)calloc(1, sizeof(pxl8_anim_state_machine));
|
||||
if (!anim->state_machine) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
if (anim->state_machine->state_count >= PXL8_ANIM_MAX_STATES) {
|
||||
pxl8_error("Cannot add more states, maximum %d reached", PXL8_ANIM_MAX_STATES);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
u16 idx = anim->state_machine->state_count;
|
||||
anim->state_machine->states[idx].name = strdup(name);
|
||||
if (!anim->state_machine->states[idx].name) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
anim->state_machine->states[idx].anim = state_anim;
|
||||
anim->state_machine->state_count++;
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
u16 pxl8_anim_get_current_frame(const pxl8_anim* anim) {
|
||||
if (!anim) return 0;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) {
|
||||
return state_anim->current_frame;
|
||||
}
|
||||
}
|
||||
|
||||
return anim->current_frame;
|
||||
}
|
||||
|
||||
u32 pxl8_anim_get_current_frame_id(const pxl8_anim* anim) {
|
||||
if (!anim || !anim->frame_ids) return 0;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim && state_anim->frame_ids) {
|
||||
return state_anim->frame_ids[state_anim->current_frame];
|
||||
}
|
||||
}
|
||||
|
||||
return anim->frame_ids[anim->current_frame];
|
||||
}
|
||||
|
||||
const char* pxl8_anim_get_state(const pxl8_anim* anim) {
|
||||
if (!anim || !anim->state_machine) return NULL;
|
||||
|
||||
if (anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
return anim->state_machine->states[anim->state_machine->current_state].name;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool pxl8_anim_has_state_machine(const pxl8_anim* anim) {
|
||||
return anim && anim->state_machine && anim->state_machine->state_count > 0;
|
||||
}
|
||||
|
||||
bool pxl8_anim_is_complete(const pxl8_anim* anim) {
|
||||
if (!anim) return true;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) {
|
||||
return pxl8_anim_is_complete(state_anim);
|
||||
}
|
||||
}
|
||||
|
||||
if (anim->loop) return false;
|
||||
|
||||
if (anim->reverse) {
|
||||
return anim->current_frame == 0;
|
||||
} else {
|
||||
return anim->current_frame >= anim->frame_count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool pxl8_anim_is_playing(const pxl8_anim* anim) {
|
||||
if (!anim) return false;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) {
|
||||
return state_anim->playing;
|
||||
}
|
||||
}
|
||||
|
||||
return anim->playing;
|
||||
}
|
||||
|
||||
void pxl8_anim_pause(pxl8_anim* anim) {
|
||||
if (!anim) return;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) {
|
||||
state_anim->playing = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
anim->playing = false;
|
||||
}
|
||||
|
||||
void pxl8_anim_play(pxl8_anim* anim) {
|
||||
if (!anim) return;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) {
|
||||
state_anim->playing = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
anim->playing = true;
|
||||
}
|
||||
|
||||
void pxl8_anim_render_sprite(const pxl8_anim* anim, pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y) {
|
||||
if (!anim || !gfx) return;
|
||||
|
||||
u32 sprite_id = pxl8_anim_get_current_frame_id(anim);
|
||||
pxl8_sprite(gfx, sprite_id, x, y, w, h, flip_x, flip_y);
|
||||
}
|
||||
|
||||
void pxl8_anim_reset(pxl8_anim* anim) {
|
||||
if (!anim) return;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) {
|
||||
pxl8_anim_reset(state_anim);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
anim->current_frame = anim->reverse ? anim->frame_count - 1 : 0;
|
||||
anim->time_accumulator = 0.0f;
|
||||
}
|
||||
|
||||
void pxl8_anim_set_frame(pxl8_anim* anim, u16 frame) {
|
||||
if (!anim) return;
|
||||
pxl8_anim* target = pxl8_anim_get_active(anim);
|
||||
if (frame < target->frame_count) {
|
||||
target->current_frame = frame;
|
||||
target->time_accumulator = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_anim_set_loop(pxl8_anim* anim, bool loop) {
|
||||
if (!anim) return;
|
||||
pxl8_anim_get_active(anim)->loop = loop;
|
||||
}
|
||||
|
||||
void pxl8_anim_set_reverse(pxl8_anim* anim, bool reverse) {
|
||||
if (!anim) return;
|
||||
pxl8_anim_get_active(anim)->reverse = reverse;
|
||||
}
|
||||
|
||||
void pxl8_anim_set_speed(pxl8_anim* anim, f32 speed) {
|
||||
if (!anim) return;
|
||||
pxl8_anim_get_active(anim)->speed = speed;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_anim_set_state(pxl8_anim* anim, const char* name) {
|
||||
if (!anim || !name) {
|
||||
return PXL8_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
if (!anim->state_machine) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
for (u16 i = 0; i < anim->state_machine->state_count; i++) {
|
||||
if (strcmp(anim->state_machine->states[i].name, name) == 0) {
|
||||
if (anim->state_machine->current_state != i) {
|
||||
anim->state_machine->current_state = i;
|
||||
pxl8_anim_reset(anim->state_machine->states[i].anim);
|
||||
}
|
||||
return PXL8_OK;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_error("Animation state not found: %s", name);
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
void pxl8_anim_stop(pxl8_anim* anim) {
|
||||
if (!anim) return;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) {
|
||||
pxl8_anim_stop(state_anim);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
anim->playing = false;
|
||||
pxl8_anim_reset(anim);
|
||||
}
|
||||
|
||||
void pxl8_anim_update(pxl8_anim* anim, f32 dt) {
|
||||
if (!anim || !anim->playing) return;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) {
|
||||
pxl8_anim_update(state_anim, dt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (anim->frame_count == 0 || !anim->frame_durations) return;
|
||||
|
||||
anim->time_accumulator += dt * anim->speed * 1000.0f;
|
||||
|
||||
u16 current_duration = anim->frame_durations[anim->current_frame];
|
||||
if (current_duration == 0) return;
|
||||
|
||||
while (anim->time_accumulator >= current_duration) {
|
||||
anim->time_accumulator -= current_duration;
|
||||
|
||||
u16 old_frame = anim->current_frame;
|
||||
|
||||
if (anim->reverse) {
|
||||
if (anim->current_frame > 0) {
|
||||
anim->current_frame--;
|
||||
} else {
|
||||
if (anim->loop) {
|
||||
anim->current_frame = anim->frame_count - 1;
|
||||
} else {
|
||||
anim->playing = false;
|
||||
if (anim->on_complete) {
|
||||
anim->on_complete(anim->userdata);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (anim->current_frame < anim->frame_count - 1) {
|
||||
anim->current_frame++;
|
||||
} else {
|
||||
if (anim->loop) {
|
||||
anim->current_frame = 0;
|
||||
} else {
|
||||
anim->playing = false;
|
||||
if (anim->on_complete) {
|
||||
anim->on_complete(anim->userdata);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (old_frame != anim->current_frame && anim->on_frame_change) {
|
||||
anim->on_frame_change(anim->current_frame, anim->userdata);
|
||||
}
|
||||
|
||||
current_duration = anim->frame_durations[anim->current_frame];
|
||||
if (current_duration == 0) break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_anim_state_machine pxl8_anim_state_machine;
|
||||
|
||||
typedef struct pxl8_anim {
|
||||
u32* frame_ids;
|
||||
u16* frame_durations;
|
||||
u16 frame_count;
|
||||
u16 current_frame;
|
||||
f32 time_accumulator;
|
||||
|
||||
bool loop;
|
||||
bool playing;
|
||||
bool reverse;
|
||||
f32 speed;
|
||||
|
||||
pxl8_anim_state_machine* state_machine;
|
||||
|
||||
void (*on_complete)(void* userdata);
|
||||
void (*on_frame_change)(u16 frame, void* userdata);
|
||||
void* userdata;
|
||||
} pxl8_anim;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_anim* pxl8_anim_create(const u32* frame_ids, const u16* frame_durations, u16 frame_count);
|
||||
pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path);
|
||||
void pxl8_anim_destroy(pxl8_anim* anim);
|
||||
|
||||
pxl8_result pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* state_anim);
|
||||
u16 pxl8_anim_get_current_frame(const pxl8_anim* anim);
|
||||
u32 pxl8_anim_get_current_frame_id(const pxl8_anim* anim);
|
||||
const char* pxl8_anim_get_state(const pxl8_anim* anim);
|
||||
bool pxl8_anim_has_state_machine(const pxl8_anim* anim);
|
||||
bool pxl8_anim_is_complete(const pxl8_anim* anim);
|
||||
bool pxl8_anim_is_playing(const pxl8_anim* anim);
|
||||
void pxl8_anim_pause(pxl8_anim* anim);
|
||||
void pxl8_anim_play(pxl8_anim* anim);
|
||||
void pxl8_anim_render_sprite(const pxl8_anim* anim, pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y);
|
||||
void pxl8_anim_reset(pxl8_anim* anim);
|
||||
void pxl8_anim_set_frame(pxl8_anim* anim, u16 frame);
|
||||
void pxl8_anim_set_loop(pxl8_anim* anim, bool loop);
|
||||
void pxl8_anim_set_reverse(pxl8_anim* anim, bool reverse);
|
||||
void pxl8_anim_set_speed(pxl8_anim* anim, f32 speed);
|
||||
pxl8_result pxl8_anim_set_state(pxl8_anim* anim, const char* name);
|
||||
void pxl8_anim_stop(pxl8_anim* anim);
|
||||
void pxl8_anim_update(pxl8_anim* anim, f32 dt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
636
src/pxl8_ase.c
636
src/pxl8_ase.c
|
|
@ -1,636 +0,0 @@
|
|||
#include "pxl8_ase.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MINIZ_NO_STDIO
|
||||
#define MINIZ_NO_TIME
|
||||
#define MINIZ_NO_ARCHIVE_APIS
|
||||
#define MINIZ_NO_ARCHIVE_WRITING_APIS
|
||||
#define MINIZ_NO_DEFLATE_APIS
|
||||
#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES
|
||||
|
||||
#include <miniz.h>
|
||||
|
||||
#include "pxl8_io.h"
|
||||
#include "pxl8_log.h"
|
||||
|
||||
static pxl8_result parse_ase_header(pxl8_stream* stream, pxl8_ase_header* header) {
|
||||
header->file_size = pxl8_read_u32(stream);
|
||||
header->magic = pxl8_read_u16(stream);
|
||||
header->frames = pxl8_read_u16(stream);
|
||||
header->width = pxl8_read_u16(stream);
|
||||
header->height = pxl8_read_u16(stream);
|
||||
header->color_depth = pxl8_read_u16(stream);
|
||||
header->flags = pxl8_read_u32(stream);
|
||||
header->speed = pxl8_read_u16(stream);
|
||||
pxl8_skip_bytes(stream, 4);
|
||||
header->transparent_index = pxl8_read_u32(stream);
|
||||
header->n_colors = pxl8_read_u8(stream);
|
||||
header->pixel_width = pxl8_read_u8(stream);
|
||||
header->pixel_height = pxl8_read_u8(stream);
|
||||
header->grid_x = pxl8_read_i16(stream);
|
||||
header->grid_y = pxl8_read_i16(stream);
|
||||
header->grid_width = pxl8_read_u16(stream);
|
||||
header->grid_height = pxl8_read_u16(stream);
|
||||
|
||||
if (header->magic != PXL8_ASE_MAGIC) {
|
||||
pxl8_error("Invalid ASE file magic: 0x%04X", header->magic);
|
||||
return PXL8_ERROR_ASE_INVALID_MAGIC;
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result parse_old_palette_chunk(pxl8_stream* stream, pxl8_ase_palette* palette) {
|
||||
u16 packet_count = pxl8_read_u16(stream);
|
||||
|
||||
u32 total_colors = 0;
|
||||
u32 temp_pos = pxl8_stream_position(stream);
|
||||
for (u16 packet = 0; packet < packet_count; packet++) {
|
||||
u8 skip_colors = pxl8_read_u8(stream);
|
||||
u8 colors_in_packet = pxl8_read_u8(stream);
|
||||
u32 actual_colors = (colors_in_packet == 0) ? 256 : colors_in_packet;
|
||||
total_colors += skip_colors + actual_colors;
|
||||
pxl8_skip_bytes(stream, actual_colors * 3);
|
||||
}
|
||||
|
||||
palette->entry_count = total_colors;
|
||||
palette->first_color = 0;
|
||||
palette->last_color = total_colors - 1;
|
||||
palette->colors = (u32*)malloc(total_colors * sizeof(u32));
|
||||
if (!palette->colors) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
pxl8_stream_seek(stream, temp_pos);
|
||||
|
||||
u32 color_index = 0;
|
||||
for (u16 packet = 0; packet < packet_count; packet++) {
|
||||
u8 skip_colors = pxl8_read_u8(stream);
|
||||
u8 colors_in_packet = pxl8_read_u8(stream);
|
||||
u32 actual_colors = (colors_in_packet == 0) ? 256 : colors_in_packet;
|
||||
|
||||
for (u32 skip = 0; skip < skip_colors && color_index < total_colors; skip++) {
|
||||
palette->colors[color_index++] = 0xFF000000;
|
||||
}
|
||||
|
||||
for (u32 color = 0; color < actual_colors && color_index < total_colors; color++) {
|
||||
u8 r = pxl8_read_u8(stream);
|
||||
u8 g = pxl8_read_u8(stream);
|
||||
u8 b = pxl8_read_u8(stream);
|
||||
palette->colors[color_index++] = 0xFF000000 | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result parse_layer_chunk(pxl8_stream* stream, pxl8_ase_layer* layer) {
|
||||
layer->flags = pxl8_read_u16(stream);
|
||||
layer->layer_type = pxl8_read_u16(stream);
|
||||
layer->child_level = pxl8_read_u16(stream);
|
||||
pxl8_skip_bytes(stream, 4);
|
||||
layer->blend_mode = pxl8_read_u16(stream);
|
||||
layer->opacity = pxl8_read_u8(stream);
|
||||
pxl8_skip_bytes(stream, 3);
|
||||
|
||||
u16 name_len = pxl8_read_u16(stream);
|
||||
if (name_len > 0) {
|
||||
layer->name = (char*)malloc(name_len + 1);
|
||||
if (!layer->name) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
pxl8_read_bytes(stream, layer->name, name_len);
|
||||
layer->name[name_len] = '\0';
|
||||
} else {
|
||||
layer->name = NULL;
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result parse_palette_chunk(pxl8_stream* stream, pxl8_ase_palette* palette) {
|
||||
pxl8_skip_bytes(stream, 4);
|
||||
palette->first_color = pxl8_read_u32(stream);
|
||||
palette->last_color = pxl8_read_u32(stream);
|
||||
pxl8_skip_bytes(stream, 8);
|
||||
|
||||
u32 color_count = palette->last_color - palette->first_color + 1;
|
||||
|
||||
if (color_count == 0 || color_count > 65536) {
|
||||
return PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
||||
}
|
||||
|
||||
palette->colors = (u32*)malloc(color_count * sizeof(u32));
|
||||
if (!palette->colors) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
palette->entry_count = color_count;
|
||||
|
||||
for (u32 i = 0; i < color_count; i++) {
|
||||
u16 flags = pxl8_read_u16(stream);
|
||||
u8 r = pxl8_read_u8(stream);
|
||||
u8 g = pxl8_read_u8(stream);
|
||||
u8 b = pxl8_read_u8(stream);
|
||||
u8 a = pxl8_read_u8(stream);
|
||||
|
||||
palette->colors[i] = (a << 24) | (b << 16) | (g << 8) | r;
|
||||
|
||||
if (flags & 1) {
|
||||
u16 name_len = pxl8_read_u16(stream);
|
||||
pxl8_skip_bytes(stream, name_len);
|
||||
}
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result parse_user_data_chunk(pxl8_stream* stream, pxl8_ase_user_data* user_data) {
|
||||
u32 flags = pxl8_read_u32(stream);
|
||||
|
||||
user_data->has_text = (flags & 1) != 0;
|
||||
user_data->has_color = (flags & 2) != 0;
|
||||
|
||||
if (user_data->has_text) {
|
||||
u16 text_len = pxl8_read_u16(stream);
|
||||
if (text_len > 0) {
|
||||
user_data->text = (char*)malloc(text_len + 1);
|
||||
if (!user_data->text) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
pxl8_read_bytes(stream, user_data->text, text_len);
|
||||
user_data->text[text_len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
if (user_data->has_color) {
|
||||
u8 r = pxl8_read_u8(stream);
|
||||
u8 g = pxl8_read_u8(stream);
|
||||
u8 b = pxl8_read_u8(stream);
|
||||
u8 a = pxl8_read_u8(stream);
|
||||
user_data->color = (a << 24) | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
|
||||
if (flags & 4) {
|
||||
pxl8_skip_bytes(stream, 4);
|
||||
u32 num_properties = pxl8_read_u32(stream);
|
||||
|
||||
if (num_properties > 0) {
|
||||
user_data->properties = (pxl8_ase_property*)calloc(num_properties, sizeof(pxl8_ase_property));
|
||||
if (!user_data->properties) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
user_data->property_count = num_properties;
|
||||
|
||||
for (u32 i = 0; i < num_properties; i++) {
|
||||
u16 name_len = pxl8_read_u16(stream);
|
||||
if (name_len > 0) {
|
||||
user_data->properties[i].name = (char*)malloc(name_len + 1);
|
||||
if (!user_data->properties[i].name) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
pxl8_read_bytes(stream, user_data->properties[i].name, name_len);
|
||||
user_data->properties[i].name[name_len] = '\0';
|
||||
}
|
||||
|
||||
u16 type = pxl8_read_u16(stream);
|
||||
user_data->properties[i].type = type;
|
||||
|
||||
switch (type) {
|
||||
case 1:
|
||||
user_data->properties[i].bool_val = pxl8_read_u8(stream) != 0;
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
user_data->properties[i].int_val = pxl8_read_i32(stream);
|
||||
break;
|
||||
case 6:
|
||||
case 7:
|
||||
user_data->properties[i].float_val = pxl8_read_f32(stream);
|
||||
break;
|
||||
case 8: {
|
||||
u16 str_len = pxl8_read_u16(stream);
|
||||
if (str_len > 0) {
|
||||
user_data->properties[i].string_val = (char*)malloc(str_len + 1);
|
||||
if (!user_data->properties[i].string_val) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
pxl8_read_bytes(stream, user_data->properties[i].string_val, str_len);
|
||||
user_data->properties[i].string_val[str_len] = '\0';
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pxl8_skip_bytes(stream, 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result parse_tileset_chunk(pxl8_stream* stream, pxl8_ase_tileset* tileset) {
|
||||
tileset->id = pxl8_read_u32(stream);
|
||||
tileset->flags = pxl8_read_u32(stream);
|
||||
tileset->tile_count = pxl8_read_u32(stream);
|
||||
tileset->tile_width = pxl8_read_u16(stream);
|
||||
tileset->tile_height = pxl8_read_u16(stream);
|
||||
tileset->base_index = pxl8_read_i16(stream);
|
||||
pxl8_skip_bytes(stream, 14);
|
||||
|
||||
u16 name_len = pxl8_read_u16(stream);
|
||||
if (name_len > 0) {
|
||||
tileset->name = (char*)malloc(name_len + 1);
|
||||
if (!tileset->name) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
pxl8_read_bytes(stream, tileset->name, name_len);
|
||||
tileset->name[name_len] = '\0';
|
||||
}
|
||||
|
||||
if (tileset->flags & 1) {
|
||||
pxl8_skip_bytes(stream, 8); // external_file_id + tileset_id
|
||||
}
|
||||
|
||||
if (tileset->flags & 2) {
|
||||
u32 compressed_size = pxl8_read_u32(stream);
|
||||
tileset->pixels_size = tileset->tile_width * tileset->tile_height * tileset->tile_count;
|
||||
tileset->pixels = (u8*)malloc(tileset->pixels_size);
|
||||
if (!tileset->pixels) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
const u8* compressed_data = pxl8_read_ptr(stream, compressed_size);
|
||||
mz_ulong dest_len = tileset->pixels_size;
|
||||
i32 result = mz_uncompress(tileset->pixels, &dest_len, compressed_data, compressed_size);
|
||||
if (result != MZ_OK) {
|
||||
pxl8_error("Failed to decompress tileset data: miniz error %d", result);
|
||||
free(tileset->pixels);
|
||||
tileset->pixels = NULL;
|
||||
return PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
||||
}
|
||||
}
|
||||
|
||||
tileset->tile_user_data = (pxl8_ase_user_data*)calloc(tileset->tile_count, sizeof(pxl8_ase_user_data));
|
||||
if (!tileset->tile_user_data) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result parse_cel_chunk(pxl8_stream* stream, u32 chunk_size, pxl8_ase_cel* cel) {
|
||||
if (chunk_size < 9) return PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
||||
|
||||
cel->layer_index = pxl8_read_u16(stream);
|
||||
cel->x = pxl8_read_i16(stream);
|
||||
cel->y = pxl8_read_i16(stream);
|
||||
cel->opacity = pxl8_read_u8(stream);
|
||||
cel->cel_type = pxl8_read_u16(stream);
|
||||
pxl8_skip_bytes(stream, 7);
|
||||
|
||||
if (cel->cel_type == 2) {
|
||||
if (chunk_size < 20) return PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
||||
|
||||
cel->image.width = pxl8_read_u16(stream);
|
||||
cel->image.height = pxl8_read_u16(stream);
|
||||
|
||||
u32 pixels_size = cel->image.width * cel->image.height;
|
||||
u32 compressed_size = chunk_size - 20;
|
||||
|
||||
cel->image.pixels = (u8*)malloc(pixels_size);
|
||||
if (!cel->image.pixels) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
const u8* compressed_data = pxl8_read_ptr(stream, compressed_size);
|
||||
mz_ulong dest_len = pixels_size;
|
||||
i32 result = mz_uncompress(cel->image.pixels, &dest_len, compressed_data, compressed_size);
|
||||
if (result != MZ_OK) {
|
||||
pxl8_error("Failed to decompress cel data: miniz error %d", result);
|
||||
free(cel->image.pixels);
|
||||
cel->image.pixels = NULL;
|
||||
return PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
||||
}
|
||||
|
||||
if (dest_len != pixels_size) {
|
||||
pxl8_warn("Decompressed size mismatch: expected %u, got %lu", pixels_size, dest_len);
|
||||
}
|
||||
} else if (cel->cel_type == 3) {
|
||||
if (chunk_size < 36) return PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
||||
|
||||
cel->tilemap.width = pxl8_read_u16(stream);
|
||||
cel->tilemap.height = pxl8_read_u16(stream);
|
||||
cel->tilemap.bits_per_tile = pxl8_read_u16(stream);
|
||||
cel->tilemap.tile_id_mask = pxl8_read_u32(stream);
|
||||
cel->tilemap.x_flip_mask = pxl8_read_u32(stream);
|
||||
cel->tilemap.y_flip_mask = pxl8_read_u32(stream);
|
||||
cel->tilemap.diag_flip_mask = pxl8_read_u32(stream);
|
||||
pxl8_skip_bytes(stream, 10);
|
||||
|
||||
u32 tile_count = cel->tilemap.width * cel->tilemap.height;
|
||||
u32 bytes_per_tile = (cel->tilemap.bits_per_tile + 7) / 8;
|
||||
u32 uncompressed_size = tile_count * bytes_per_tile;
|
||||
u32 compressed_size = chunk_size - 36;
|
||||
|
||||
u8* temp_buffer = (u8*)malloc(uncompressed_size);
|
||||
if (!temp_buffer) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
const u8* compressed_data = pxl8_read_ptr(stream, compressed_size);
|
||||
mz_ulong dest_len = uncompressed_size;
|
||||
i32 result = mz_uncompress(temp_buffer, &dest_len, compressed_data, compressed_size);
|
||||
if (result != MZ_OK) {
|
||||
pxl8_error("Failed to decompress tilemap data: miniz error %d", result);
|
||||
free(temp_buffer);
|
||||
return PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
||||
}
|
||||
|
||||
cel->tilemap.tiles = (u32*)calloc(tile_count, sizeof(u32));
|
||||
if (!cel->tilemap.tiles) {
|
||||
free(temp_buffer);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < tile_count; i++) {
|
||||
if (cel->tilemap.bits_per_tile == 32) {
|
||||
cel->tilemap.tiles[i] = ((u32)temp_buffer[i * 4 + 0]) |
|
||||
((u32)temp_buffer[i * 4 + 1] << 8) |
|
||||
((u32)temp_buffer[i * 4 + 2] << 16) |
|
||||
((u32)temp_buffer[i * 4 + 3] << 24);
|
||||
} else if (cel->tilemap.bits_per_tile == 16) {
|
||||
cel->tilemap.tiles[i] = ((u32)temp_buffer[i * 2 + 0]) |
|
||||
((u32)temp_buffer[i * 2 + 1] << 8);
|
||||
} else if (cel->tilemap.bits_per_tile == 8) {
|
||||
cel->tilemap.tiles[i] = temp_buffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
free(temp_buffer);
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
|
||||
if (!filepath || !ase_file) {
|
||||
return PXL8_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
memset(ase_file, 0, sizeof(pxl8_ase_file));
|
||||
|
||||
u8* file_data;
|
||||
size_t file_size;
|
||||
pxl8_result result = pxl8_io_read_binary_file(filepath, &file_data, &file_size);
|
||||
if (result != PXL8_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (file_size < 128) {
|
||||
pxl8_io_free_binary_data(file_data);
|
||||
return PXL8_ERROR_ASE_TRUNCATED_FILE;
|
||||
}
|
||||
|
||||
pxl8_stream stream = pxl8_stream_create(file_data, (u32)file_size);
|
||||
|
||||
result = parse_ase_header(&stream, &ase_file->header);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_io_free_binary_data(file_data);
|
||||
return result;
|
||||
}
|
||||
|
||||
ase_file->frame_count = ase_file->header.frames;
|
||||
ase_file->frames = (pxl8_ase_frame*)calloc(ase_file->frame_count, sizeof(pxl8_ase_frame));
|
||||
if (!ase_file->frames) {
|
||||
pxl8_io_free_binary_data(file_data);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
pxl8_stream_seek(&stream, 128);
|
||||
|
||||
for (u16 frame_idx = 0; frame_idx < ase_file->header.frames; frame_idx++) {
|
||||
u32 frame_start = pxl8_stream_position(&stream);
|
||||
|
||||
pxl8_ase_frame_header frame_header;
|
||||
frame_header.frame_bytes = pxl8_read_u32(&stream);
|
||||
frame_header.magic = pxl8_read_u16(&stream);
|
||||
u16 old_chunks = pxl8_read_u16(&stream);
|
||||
frame_header.duration = pxl8_read_u16(&stream);
|
||||
pxl8_skip_bytes(&stream, 2);
|
||||
|
||||
if (frame_header.magic != PXL8_ASE_FRAME_MAGIC) {
|
||||
pxl8_error("Invalid frame magic: 0x%04X", frame_header.magic);
|
||||
result = PXL8_ERROR_ASE_INVALID_FRAME_MAGIC;
|
||||
break;
|
||||
}
|
||||
|
||||
if (old_chunks == 0xFFFF || old_chunks == 0xFFFE) {
|
||||
frame_header.chunks = pxl8_read_u32(&stream);
|
||||
} else {
|
||||
frame_header.chunks = old_chunks;
|
||||
pxl8_skip_bytes(&stream, 4);
|
||||
}
|
||||
|
||||
pxl8_ase_frame* frame = &ase_file->frames[frame_idx];
|
||||
frame->frame_id = frame_idx;
|
||||
frame->width = ase_file->header.width;
|
||||
frame->height = ase_file->header.height;
|
||||
frame->duration = frame_header.duration;
|
||||
|
||||
u32 pixel_count = frame->width * frame->height;
|
||||
frame->pixels = (u8*)calloc(pixel_count, sizeof(u8));
|
||||
if (!frame->pixels) {
|
||||
result = PXL8_ERROR_OUT_OF_MEMORY;
|
||||
break;
|
||||
}
|
||||
|
||||
u32 last_tileset_idx = 0xFFFFFFFF;
|
||||
u32 tileset_tile_idx = 0;
|
||||
|
||||
for (u16 chunk_idx = 0; chunk_idx < frame_header.chunks; chunk_idx++) {
|
||||
u32 chunk_start = pxl8_stream_position(&stream);
|
||||
|
||||
pxl8_ase_chunk_header chunk_header;
|
||||
chunk_header.chunk_size = pxl8_read_u32(&stream);
|
||||
chunk_header.chunk_type = pxl8_read_u16(&stream);
|
||||
|
||||
if (chunk_header.chunk_size < 6 || chunk_header.chunk_size > frame_header.frame_bytes) {
|
||||
pxl8_error("Invalid chunk size %u in ASE file (frame_bytes=%u)",
|
||||
chunk_header.chunk_size, frame_header.frame_bytes);
|
||||
result = PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (chunk_header.chunk_type) {
|
||||
case PXL8_ASE_CHUNK_OLD_PALETTE:
|
||||
if (!ase_file->palette.colors) {
|
||||
result = parse_old_palette_chunk(&stream, &ase_file->palette);
|
||||
}
|
||||
break;
|
||||
|
||||
case PXL8_ASE_CHUNK_LAYER: {
|
||||
pxl8_ase_layer* new_layers =
|
||||
(pxl8_ase_layer*)realloc(ase_file->layers,
|
||||
(ase_file->layer_count + 1) * sizeof(pxl8_ase_layer));
|
||||
if (!new_layers) {
|
||||
result = PXL8_ERROR_OUT_OF_MEMORY;
|
||||
break;
|
||||
}
|
||||
ase_file->layers = new_layers;
|
||||
|
||||
result = parse_layer_chunk(&stream, &ase_file->layers[ase_file->layer_count]);
|
||||
if (result == PXL8_OK) {
|
||||
ase_file->layer_count++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_ASE_CHUNK_CEL: {
|
||||
pxl8_ase_cel cel = {0};
|
||||
result = parse_cel_chunk(&stream, chunk_header.chunk_size - 6, &cel);
|
||||
if (result == PXL8_OK) {
|
||||
pxl8_ase_cel* new_cels = (pxl8_ase_cel*)realloc(frame->cels, (frame->cel_count + 1) * sizeof(pxl8_ase_cel));
|
||||
if (!new_cels) {
|
||||
result = PXL8_ERROR_OUT_OF_MEMORY;
|
||||
break;
|
||||
}
|
||||
frame->cels = new_cels;
|
||||
frame->cels[frame->cel_count] = cel;
|
||||
frame->cel_count++;
|
||||
|
||||
if (cel.cel_type == 2 && cel.image.pixels) {
|
||||
u32 copy_width = (cel.image.width < frame->width) ? cel.image.width : frame->width;
|
||||
u32 copy_height = (cel.image.height < frame->height) ? cel.image.height : frame->height;
|
||||
|
||||
for (u32 y = 0; y < copy_height; y++) {
|
||||
u32 src_offset = y * cel.image.width;
|
||||
u32 dst_offset = (y + cel.y) * frame->width + cel.x;
|
||||
if (dst_offset + copy_width <= pixel_count) {
|
||||
for (u32 x = 0; x < copy_width; x++) {
|
||||
u8 src_pixel = cel.image.pixels[src_offset + x];
|
||||
bool is_transparent = false;
|
||||
|
||||
if (src_pixel < ase_file->palette.entry_count && ase_file->palette.colors) {
|
||||
u32 color = ase_file->palette.colors[src_pixel];
|
||||
is_transparent = ((color >> 24) & 0xFF) == 0;
|
||||
}
|
||||
|
||||
if (!is_transparent) {
|
||||
frame->pixels[dst_offset + x] = src_pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_ASE_CHUNK_PALETTE:
|
||||
if (ase_file->palette.colors) {
|
||||
free(ase_file->palette.colors);
|
||||
ase_file->palette.colors = NULL;
|
||||
}
|
||||
result = parse_palette_chunk(&stream, &ase_file->palette);
|
||||
break;
|
||||
|
||||
case PXL8_ASE_CHUNK_TILESET: {
|
||||
pxl8_ase_tileset* new_tilesets = (pxl8_ase_tileset*)realloc(ase_file->tilesets,
|
||||
(ase_file->tileset_count + 1) * sizeof(pxl8_ase_tileset));
|
||||
if (!new_tilesets) {
|
||||
result = PXL8_ERROR_OUT_OF_MEMORY;
|
||||
break;
|
||||
}
|
||||
ase_file->tilesets = new_tilesets;
|
||||
|
||||
memset(&ase_file->tilesets[ase_file->tileset_count], 0, sizeof(pxl8_ase_tileset));
|
||||
result = parse_tileset_chunk(&stream, &ase_file->tilesets[ase_file->tileset_count]);
|
||||
if (result == PXL8_OK) {
|
||||
last_tileset_idx = ase_file->tileset_count;
|
||||
tileset_tile_idx = 0;
|
||||
ase_file->tileset_count++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_ASE_CHUNK_USER_DATA: {
|
||||
if (last_tileset_idx != 0xFFFFFFFF) {
|
||||
pxl8_ase_tileset* tileset = &ase_file->tilesets[last_tileset_idx];
|
||||
if (tileset_tile_idx < tileset->tile_count) {
|
||||
result = parse_user_data_chunk(&stream, &tileset->tile_user_data[tileset_tile_idx]);
|
||||
tileset_tile_idx++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (result != PXL8_OK) break;
|
||||
|
||||
pxl8_stream_seek(&stream, chunk_start + chunk_header.chunk_size);
|
||||
}
|
||||
|
||||
if (result != PXL8_OK) break;
|
||||
|
||||
pxl8_stream_seek(&stream, frame_start + frame_header.frame_bytes);
|
||||
}
|
||||
|
||||
pxl8_io_free_binary_data(file_data);
|
||||
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_ase_destroy(ase_file);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void pxl8_ase_destroy(pxl8_ase_file* ase_file) {
|
||||
if (!ase_file) return;
|
||||
|
||||
if (ase_file->frames) {
|
||||
for (u32 i = 0; i < ase_file->frame_count; i++) {
|
||||
if (ase_file->frames[i].pixels) free(ase_file->frames[i].pixels);
|
||||
if (ase_file->frames[i].cels) {
|
||||
for (u32 j = 0; j < ase_file->frames[i].cel_count; j++) {
|
||||
pxl8_ase_cel* cel = &ase_file->frames[i].cels[j];
|
||||
if (cel->cel_type == 2 && cel->image.pixels) {
|
||||
free(cel->image.pixels);
|
||||
} else if (cel->cel_type == 3 && cel->tilemap.tiles) {
|
||||
free(cel->tilemap.tiles);
|
||||
}
|
||||
}
|
||||
free(ase_file->frames[i].cels);
|
||||
}
|
||||
}
|
||||
free(ase_file->frames);
|
||||
}
|
||||
|
||||
if (ase_file->palette.colors) {
|
||||
free(ase_file->palette.colors);
|
||||
}
|
||||
|
||||
if (ase_file->layers) {
|
||||
for (u32 i = 0; i < ase_file->layer_count; i++) {
|
||||
if (ase_file->layers[i].name) {
|
||||
free(ase_file->layers[i].name);
|
||||
}
|
||||
}
|
||||
free(ase_file->layers);
|
||||
}
|
||||
|
||||
if (ase_file->tilesets) {
|
||||
for (u32 i = 0; i < ase_file->tileset_count; i++) {
|
||||
if (ase_file->tilesets[i].name) free(ase_file->tilesets[i].name);
|
||||
if (ase_file->tilesets[i].pixels) free(ase_file->tilesets[i].pixels);
|
||||
if (ase_file->tilesets[i].tile_user_data) {
|
||||
for (u32 j = 0; j < ase_file->tilesets[i].tile_count; j++) {
|
||||
pxl8_ase_user_data* ud = &ase_file->tilesets[i].tile_user_data[j];
|
||||
if (ud->text) free(ud->text);
|
||||
if (ud->properties) {
|
||||
for (u32 k = 0; k < ud->property_count; k++) {
|
||||
if (ud->properties[k].name) free(ud->properties[k].name);
|
||||
if (ud->properties[k].type == 8 && ud->properties[k].string_val) {
|
||||
free(ud->properties[k].string_val);
|
||||
}
|
||||
}
|
||||
free(ud->properties);
|
||||
}
|
||||
}
|
||||
free(ase_file->tilesets[i].tile_user_data);
|
||||
}
|
||||
}
|
||||
free(ase_file->tilesets);
|
||||
}
|
||||
|
||||
memset(ase_file, 0, sizeof(pxl8_ase_file));
|
||||
}
|
||||
153
src/pxl8_ase.h
153
src/pxl8_ase.h
|
|
@ -1,153 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define PXL8_ASE_MAGIC 0xA5E0
|
||||
#define PXL8_ASE_FRAME_MAGIC 0xF1FA
|
||||
#define PXL8_ASE_CHUNK_CEL 0x2005
|
||||
#define PXL8_ASE_CHUNK_LAYER 0x2004
|
||||
#define PXL8_ASE_CHUNK_OLD_PALETTE 0x0004
|
||||
#define PXL8_ASE_CHUNK_PALETTE 0x2019
|
||||
#define PXL8_ASE_CHUNK_TILESET 0x2023
|
||||
#define PXL8_ASE_CHUNK_USER_DATA 0x2020
|
||||
|
||||
typedef struct pxl8_ase_header {
|
||||
u32 file_size;
|
||||
u16 magic;
|
||||
u16 frames;
|
||||
u16 width;
|
||||
u16 height;
|
||||
u16 color_depth;
|
||||
u32 flags;
|
||||
u16 speed;
|
||||
u32 transparent_index;
|
||||
u8 n_colors;
|
||||
u8 pixel_width;
|
||||
u8 pixel_height;
|
||||
i16 grid_x;
|
||||
i16 grid_y;
|
||||
u16 grid_width;
|
||||
u16 grid_height;
|
||||
} pxl8_ase_header;
|
||||
|
||||
typedef struct pxl8_ase_frame_header {
|
||||
u32 frame_bytes;
|
||||
u16 magic;
|
||||
u16 chunks;
|
||||
u16 duration;
|
||||
} pxl8_ase_frame_header;
|
||||
|
||||
typedef struct pxl8_ase_chunk_header {
|
||||
u32 chunk_size;
|
||||
u16 chunk_type;
|
||||
} pxl8_ase_chunk_header;
|
||||
|
||||
typedef struct pxl8_ase_cel {
|
||||
u16 layer_index;
|
||||
i16 x;
|
||||
i16 y;
|
||||
u8 opacity;
|
||||
u16 cel_type;
|
||||
|
||||
union {
|
||||
struct {
|
||||
u16 width;
|
||||
u16 height;
|
||||
u8* pixels;
|
||||
} image;
|
||||
|
||||
struct {
|
||||
u16 width;
|
||||
u16 height;
|
||||
u16 bits_per_tile;
|
||||
u32 tile_id_mask;
|
||||
u32 x_flip_mask;
|
||||
u32 y_flip_mask;
|
||||
u32 diag_flip_mask;
|
||||
u32* tiles;
|
||||
} tilemap;
|
||||
};
|
||||
} pxl8_ase_cel;
|
||||
|
||||
typedef struct pxl8_ase_layer {
|
||||
u16 flags;
|
||||
u16 layer_type;
|
||||
u16 child_level;
|
||||
u16 blend_mode;
|
||||
u8 opacity;
|
||||
char* name;
|
||||
} pxl8_ase_layer;
|
||||
|
||||
typedef struct pxl8_ase_palette {
|
||||
u32 entry_count;
|
||||
u32 first_color;
|
||||
u32 last_color;
|
||||
u32* colors;
|
||||
} pxl8_ase_palette;
|
||||
|
||||
typedef struct pxl8_ase_frame {
|
||||
u16 frame_id;
|
||||
u16 width;
|
||||
u16 height;
|
||||
i16 x;
|
||||
i16 y;
|
||||
u16 duration;
|
||||
u8* pixels;
|
||||
u32 cel_count;
|
||||
pxl8_ase_cel* cels;
|
||||
} pxl8_ase_frame;
|
||||
|
||||
typedef struct pxl8_ase_property {
|
||||
char* name;
|
||||
u32 type;
|
||||
union {
|
||||
bool bool_val;
|
||||
i32 int_val;
|
||||
f32 float_val;
|
||||
char* string_val;
|
||||
};
|
||||
} pxl8_ase_property;
|
||||
|
||||
typedef struct pxl8_ase_user_data {
|
||||
char* text;
|
||||
u32 color;
|
||||
bool has_color;
|
||||
bool has_text;
|
||||
u32 property_count;
|
||||
pxl8_ase_property* properties;
|
||||
} pxl8_ase_user_data;
|
||||
|
||||
typedef struct pxl8_ase_tileset {
|
||||
u32 id;
|
||||
u32 flags;
|
||||
u32 tile_count;
|
||||
u16 tile_width;
|
||||
u16 tile_height;
|
||||
i16 base_index;
|
||||
char* name;
|
||||
u32 pixels_size;
|
||||
u8* pixels;
|
||||
pxl8_ase_user_data* tile_user_data;
|
||||
} pxl8_ase_tileset;
|
||||
|
||||
typedef struct pxl8_ase_file {
|
||||
u32 frame_count;
|
||||
pxl8_ase_frame* frames;
|
||||
pxl8_ase_header header;
|
||||
u32 layer_count;
|
||||
pxl8_ase_layer* layers;
|
||||
pxl8_ase_palette palette;
|
||||
u32 tileset_count;
|
||||
pxl8_ase_tileset* tilesets;
|
||||
} pxl8_ase_file;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file);
|
||||
void pxl8_ase_destroy(pxl8_ase_file* ase_file);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
392
src/pxl8_atlas.c
392
src/pxl8_atlas.c
|
|
@ -1,392 +0,0 @@
|
|||
#include "pxl8_atlas.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_color.h"
|
||||
#include "pxl8_log.h"
|
||||
|
||||
typedef struct pxl8_skyline_fit {
|
||||
bool found;
|
||||
u32 node_idx;
|
||||
pxl8_point pos;
|
||||
} pxl8_skyline_fit;
|
||||
|
||||
typedef struct pxl8_skyline_node {
|
||||
i32 x, y, width;
|
||||
} pxl8_skyline_node;
|
||||
|
||||
typedef struct pxl8_skyline {
|
||||
pxl8_skyline_node* nodes;
|
||||
u32 count;
|
||||
u32 capacity;
|
||||
} pxl8_skyline;
|
||||
|
||||
struct pxl8_atlas {
|
||||
u32 height, width;
|
||||
u8* pixels;
|
||||
|
||||
bool dirty;
|
||||
|
||||
u32 entry_capacity, entry_count;
|
||||
pxl8_atlas_entry* entries;
|
||||
|
||||
u32 free_capacity, free_count;
|
||||
u32* free_list;
|
||||
|
||||
pxl8_skyline skyline;
|
||||
};
|
||||
|
||||
static pxl8_skyline_fit pxl8_skyline_find_position(
|
||||
const pxl8_skyline* skyline,
|
||||
u32 atlas_w,
|
||||
u32 atlas_h,
|
||||
u32 rect_w,
|
||||
u32 rect_h
|
||||
) {
|
||||
pxl8_skyline_fit result = {.found = false};
|
||||
i32 best_y = INT32_MAX;
|
||||
i32 best_x = 0;
|
||||
u32 best_idx = 0;
|
||||
|
||||
for (u32 i = 0; i < skyline->count; i++) {
|
||||
i32 x = skyline->nodes[i].x;
|
||||
i32 y = skyline->nodes[i].y;
|
||||
|
||||
if (x + (i32)rect_w > (i32)atlas_w) continue;
|
||||
|
||||
i32 max_y = y;
|
||||
for (u32 j = i; j < skyline->count && skyline->nodes[j].x < x + (i32)rect_w; j++) {
|
||||
if (skyline->nodes[j].y > max_y) {
|
||||
max_y = skyline->nodes[j].y;
|
||||
}
|
||||
}
|
||||
|
||||
if (max_y + (i32)rect_h > (i32)atlas_h) continue;
|
||||
|
||||
if (max_y < best_y || (max_y == best_y && x < best_x)) {
|
||||
best_y = max_y;
|
||||
best_x = x;
|
||||
best_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_y != INT32_MAX) {
|
||||
result.found = true;
|
||||
result.pos.x = best_x;
|
||||
result.pos.y = best_y;
|
||||
result.node_idx = best_idx;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool pxl8_skyline_add_rect(pxl8_skyline* skyline, pxl8_point pos, u32 w, u32 h) {
|
||||
u32 node_idx = 0;
|
||||
for (u32 i = 0; i < skyline->count; i++) {
|
||||
if (skyline->nodes[i].x == pos.x) {
|
||||
node_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
u32 nodes_to_remove = 0;
|
||||
for (u32 i = node_idx; i < skyline->count; i++) {
|
||||
if (skyline->nodes[i].x < pos.x + (i32)w) {
|
||||
nodes_to_remove++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (skyline->count - nodes_to_remove + 1 > skyline->capacity) {
|
||||
u32 new_capacity = (skyline->count - nodes_to_remove + 1) * 2;
|
||||
pxl8_skyline_node* new_nodes = (pxl8_skyline_node*)realloc(
|
||||
skyline->nodes,
|
||||
new_capacity * sizeof(pxl8_skyline_node)
|
||||
);
|
||||
if (!new_nodes) return false;
|
||||
skyline->nodes = new_nodes;
|
||||
skyline->capacity = new_capacity;
|
||||
}
|
||||
|
||||
if (nodes_to_remove > 0) {
|
||||
memmove(
|
||||
&skyline->nodes[node_idx + 1],
|
||||
&skyline->nodes[node_idx + nodes_to_remove],
|
||||
(skyline->count - node_idx - nodes_to_remove) * sizeof(pxl8_skyline_node)
|
||||
);
|
||||
}
|
||||
|
||||
skyline->nodes[node_idx] = (pxl8_skyline_node){pos.x, pos.y + (i32)h, (i32)w};
|
||||
skyline->count = skyline->count - nodes_to_remove + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void pxl8_skyline_compact(pxl8_skyline* skyline) {
|
||||
for (u32 i = 0; i < skyline->count - 1; ) {
|
||||
if (skyline->nodes[i].y == skyline->nodes[i + 1].y) {
|
||||
skyline->nodes[i].width += skyline->nodes[i + 1].width;
|
||||
memmove(
|
||||
&skyline->nodes[i + 1],
|
||||
&skyline->nodes[i + 2],
|
||||
(skyline->count - i - 2) * sizeof(pxl8_skyline_node)
|
||||
);
|
||||
skyline->count--;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode) {
|
||||
pxl8_atlas* atlas = (pxl8_atlas*)calloc(1, sizeof(pxl8_atlas));
|
||||
if (!atlas) return NULL;
|
||||
|
||||
atlas->height = height;
|
||||
atlas->width = width;
|
||||
|
||||
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
|
||||
atlas->pixels = (u8*)calloc(width * height, bytes_per_pixel);
|
||||
if (!atlas->pixels) {
|
||||
free(atlas);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
atlas->entry_capacity = PXL8_DEFAULT_ATLAS_ENTRY_CAPACITY;
|
||||
atlas->entries = (pxl8_atlas_entry*)calloc(atlas->entry_capacity, sizeof(pxl8_atlas_entry));
|
||||
if (!atlas->entries) {
|
||||
free(atlas->pixels);
|
||||
free(atlas);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
atlas->free_capacity = 16;
|
||||
atlas->free_list = (u32*)calloc(atlas->free_capacity, sizeof(u32));
|
||||
if (!atlas->free_list) {
|
||||
free(atlas->entries);
|
||||
free(atlas->pixels);
|
||||
free(atlas);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
atlas->skyline.capacity = 16;
|
||||
atlas->skyline.nodes =
|
||||
(pxl8_skyline_node*)calloc(atlas->skyline.capacity, sizeof(pxl8_skyline_node));
|
||||
if (!atlas->skyline.nodes) {
|
||||
free(atlas->free_list);
|
||||
free(atlas->entries);
|
||||
free(atlas->pixels);
|
||||
free(atlas);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
atlas->skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)width};
|
||||
atlas->skyline.count = 1;
|
||||
|
||||
return atlas;
|
||||
}
|
||||
|
||||
void pxl8_atlas_destroy(pxl8_atlas* atlas) {
|
||||
if (!atlas) return;
|
||||
|
||||
free(atlas->entries);
|
||||
free(atlas->free_list);
|
||||
free(atlas->pixels);
|
||||
free(atlas->skyline.nodes);
|
||||
free(atlas);
|
||||
}
|
||||
|
||||
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count) {
|
||||
if (!atlas) return;
|
||||
|
||||
for (u32 i = preserve_count; i < atlas->entry_count; i++) {
|
||||
atlas->entries[i].active = false;
|
||||
}
|
||||
|
||||
atlas->entry_count = preserve_count;
|
||||
atlas->free_count = 0;
|
||||
|
||||
atlas->skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)atlas->width};
|
||||
atlas->skyline.count = 1;
|
||||
|
||||
atlas->dirty = true;
|
||||
}
|
||||
|
||||
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) {
|
||||
if (!atlas || atlas->width >= 4096) return false;
|
||||
|
||||
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
|
||||
u32 new_size = atlas->width * 2;
|
||||
u32 old_width = atlas->width;
|
||||
|
||||
u8* new_pixels = (u8*)calloc(new_size * new_size, bytes_per_pixel);
|
||||
if (!new_pixels) return false;
|
||||
|
||||
pxl8_skyline new_skyline;
|
||||
new_skyline.nodes = (pxl8_skyline_node*)calloc(16, sizeof(pxl8_skyline_node));
|
||||
if (!new_skyline.nodes) {
|
||||
free(new_pixels);
|
||||
return false;
|
||||
}
|
||||
|
||||
new_skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)new_size};
|
||||
new_skyline.count = 1;
|
||||
new_skyline.capacity = 16;
|
||||
|
||||
for (u32 i = 0; i < atlas->entry_count; i++) {
|
||||
if (!atlas->entries[i].active) continue;
|
||||
|
||||
pxl8_skyline_fit fit = pxl8_skyline_find_position(
|
||||
&new_skyline,
|
||||
new_size,
|
||||
new_size,
|
||||
atlas->entries[i].w,
|
||||
atlas->entries[i].h
|
||||
);
|
||||
|
||||
if (!fit.found) {
|
||||
free(new_skyline.nodes);
|
||||
free(new_pixels);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (u32 y = 0; y < (u32)atlas->entries[i].h; y++) {
|
||||
for (u32 x = 0; x < (u32)atlas->entries[i].w; x++) {
|
||||
u32 src_idx = (atlas->entries[i].y + y) * old_width + (atlas->entries[i].x + x);
|
||||
u32 dst_idx = (fit.pos.y + y) * new_size + (fit.pos.x + x);
|
||||
if (bytes_per_pixel == 2) {
|
||||
((u16*)new_pixels)[dst_idx] = ((u16*)atlas->pixels)[src_idx];
|
||||
} else {
|
||||
new_pixels[dst_idx] = atlas->pixels[src_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
atlas->entries[i].x = fit.pos.x;
|
||||
atlas->entries[i].y = fit.pos.y;
|
||||
|
||||
if (!pxl8_skyline_add_rect(&new_skyline, fit.pos, atlas->entries[i].w, atlas->entries[i].h)) {
|
||||
free(new_skyline.nodes);
|
||||
free(new_pixels);
|
||||
return false;
|
||||
}
|
||||
pxl8_skyline_compact(&new_skyline);
|
||||
}
|
||||
|
||||
free(atlas->pixels);
|
||||
free(atlas->skyline.nodes);
|
||||
|
||||
atlas->pixels = new_pixels;
|
||||
atlas->skyline = new_skyline;
|
||||
atlas->width = new_size;
|
||||
atlas->height = new_size;
|
||||
atlas->dirty = true;
|
||||
|
||||
pxl8_debug("Atlas expanded to %ux%u", atlas->width, atlas->height);
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 pxl8_atlas_add_texture(
|
||||
pxl8_atlas* atlas,
|
||||
const u8* pixels,
|
||||
u32 w,
|
||||
u32 h,
|
||||
pxl8_pixel_mode pixel_mode
|
||||
) {
|
||||
if (!atlas || !pixels) return UINT32_MAX;
|
||||
|
||||
pxl8_skyline_fit fit =
|
||||
pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h);
|
||||
if (!fit.found) {
|
||||
if (!pxl8_atlas_expand(atlas, pixel_mode)) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
fit = pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h);
|
||||
|
||||
if (!fit.found) return UINT32_MAX;
|
||||
}
|
||||
|
||||
u32 texture_id;
|
||||
if (atlas->free_count > 0) {
|
||||
texture_id = atlas->free_list[--atlas->free_count];
|
||||
} else {
|
||||
if (atlas->entry_count >= atlas->entry_capacity) {
|
||||
u32 new_capacity = atlas->entry_capacity * 2;
|
||||
pxl8_atlas_entry* new_entries = (pxl8_atlas_entry*)realloc(
|
||||
atlas->entries,
|
||||
new_capacity * sizeof(pxl8_atlas_entry)
|
||||
);
|
||||
if (!new_entries) return UINT32_MAX;
|
||||
atlas->entries = new_entries;
|
||||
atlas->entry_capacity = new_capacity;
|
||||
}
|
||||
texture_id = atlas->entry_count++;
|
||||
}
|
||||
|
||||
pxl8_atlas_entry* entry = &atlas->entries[texture_id];
|
||||
entry->active = true;
|
||||
entry->texture_id = texture_id;
|
||||
entry->x = fit.pos.x;
|
||||
entry->y = fit.pos.y;
|
||||
entry->w = w;
|
||||
entry->h = h;
|
||||
|
||||
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
|
||||
for (u32 y = 0; y < h; y++) {
|
||||
for (u32 x = 0; x < w; x++) {
|
||||
u32 src_idx = y * w + x;
|
||||
u32 dst_idx = (fit.pos.y + y) * atlas->width + (fit.pos.x + x);
|
||||
|
||||
if (bytes_per_pixel == 2) {
|
||||
((u16*)atlas->pixels)[dst_idx] = ((const u16*)pixels)[src_idx];
|
||||
} else {
|
||||
atlas->pixels[dst_idx] = pixels[src_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!pxl8_skyline_add_rect(&atlas->skyline, fit.pos, w, h)) {
|
||||
entry->active = false;
|
||||
return UINT32_MAX;
|
||||
}
|
||||
pxl8_skyline_compact(&atlas->skyline);
|
||||
|
||||
atlas->dirty = true;
|
||||
|
||||
return texture_id;
|
||||
}
|
||||
|
||||
const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id) {
|
||||
if (!atlas || id >= atlas->entry_count) return NULL;
|
||||
return &atlas->entries[id];
|
||||
}
|
||||
|
||||
u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas) {
|
||||
return atlas ? atlas->entry_count : 0;
|
||||
}
|
||||
|
||||
u32 pxl8_atlas_get_height(const pxl8_atlas* atlas) {
|
||||
return atlas ? atlas->height : 0;
|
||||
}
|
||||
|
||||
const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas) {
|
||||
return atlas ? atlas->pixels : NULL;
|
||||
}
|
||||
|
||||
u32 pxl8_atlas_get_width(const pxl8_atlas* atlas) {
|
||||
return atlas ? atlas->width : 0;
|
||||
}
|
||||
|
||||
bool pxl8_atlas_is_dirty(const pxl8_atlas* atlas) {
|
||||
return atlas ? atlas->dirty : false;
|
||||
}
|
||||
|
||||
void pxl8_atlas_mark_clean(pxl8_atlas* atlas) {
|
||||
if (atlas) {
|
||||
atlas->dirty = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_atlas pxl8_atlas;
|
||||
|
||||
typedef struct pxl8_atlas_entry {
|
||||
bool active;
|
||||
u32 texture_id;
|
||||
i32 x, y, w, h;
|
||||
} pxl8_atlas_entry;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode);
|
||||
void pxl8_atlas_destroy(pxl8_atlas* atlas);
|
||||
|
||||
const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id);
|
||||
u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas);
|
||||
u32 pxl8_atlas_get_height(const pxl8_atlas* atlas);
|
||||
const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas);
|
||||
u32 pxl8_atlas_get_width(const pxl8_atlas* atlas);
|
||||
bool pxl8_atlas_is_dirty(const pxl8_atlas* atlas);
|
||||
|
||||
void pxl8_atlas_mark_clean(pxl8_atlas* atlas);
|
||||
|
||||
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_pixel_mode pixel_mode);
|
||||
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count);
|
||||
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
#include "pxl8_blit.h"
|
||||
|
||||
void pxl8_blit_hicolor(u16* fb, u32 fb_width, const u16* sprite, u32 atlas_width,
|
||||
i32 x, i32 y, u32 w, u32 h) {
|
||||
u16* dest_base = fb + y * fb_width + x;
|
||||
const u16* src_base = sprite;
|
||||
|
||||
for (u32 row = 0; row < h; row++) {
|
||||
u16* dest_row = dest_base + row * fb_width;
|
||||
const u16* src_row = src_base + row * atlas_width;
|
||||
|
||||
u32 col = 0;
|
||||
u32 count2 = w / 2;
|
||||
for (u32 i = 0; i < count2; i++) {
|
||||
u32 pixels = ((const u32*)src_row)[i];
|
||||
if (pixels == 0) {
|
||||
col += 2;
|
||||
continue;
|
||||
}
|
||||
dest_row[col] = pxl8_blend_hicolor((u16)(pixels), dest_row[col]);
|
||||
dest_row[col + 1] = pxl8_blend_hicolor((u16)(pixels >> 16), dest_row[col + 1]);
|
||||
col += 2;
|
||||
}
|
||||
if (w & 1) {
|
||||
dest_row[col] = pxl8_blend_hicolor(src_row[col], dest_row[col]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_blit_indexed(u8* fb, u32 fb_width, const u8* sprite, u32 atlas_width,
|
||||
i32 x, i32 y, u32 w, u32 h) {
|
||||
u8* dest_base = fb + y * fb_width + x;
|
||||
const u8* src_base = sprite;
|
||||
|
||||
for (u32 row = 0; row < h; row++) {
|
||||
u8* dest_row = dest_base + row * fb_width;
|
||||
const u8* src_row = src_base + row * atlas_width;
|
||||
|
||||
u32 col = 0;
|
||||
u32 count4 = w / 4;
|
||||
for (u32 i = 0; i < count4; i++) {
|
||||
u32 pixels = ((const u32*)src_row)[i];
|
||||
if (pixels == 0) {
|
||||
col += 4;
|
||||
continue;
|
||||
}
|
||||
dest_row[col] = pxl8_blend_indexed((u8)(pixels), dest_row[col]);
|
||||
dest_row[col + 1] = pxl8_blend_indexed((u8)(pixels >> 8), dest_row[col + 1]);
|
||||
dest_row[col + 2] = pxl8_blend_indexed((u8)(pixels >> 16), dest_row[col + 2]);
|
||||
dest_row[col + 3] = pxl8_blend_indexed((u8)(pixels >> 24), dest_row[col + 3]);
|
||||
col += 4;
|
||||
}
|
||||
for (; col < w; col++) {
|
||||
dest_row[col] = pxl8_blend_indexed(src_row[col], dest_row[col]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
static inline u8 pxl8_blend_indexed(u8 src, u8 dst) {
|
||||
u8 m = (u8)(-(src != 0));
|
||||
return (src & m) | (dst & ~m);
|
||||
}
|
||||
|
||||
static inline u16 pxl8_blend_hicolor(u16 src, u16 dst) {
|
||||
u16 m = (u16)(-(src != 0));
|
||||
return (src & m) | (dst & ~m);
|
||||
}
|
||||
|
||||
void pxl8_blit_hicolor(
|
||||
u16* fb, u32 fb_width,
|
||||
const u16* sprite, u32 atlas_width,
|
||||
i32 x, i32 y, u32 w, u32 h
|
||||
);
|
||||
void pxl8_blit_indexed(
|
||||
u8* fb, u32 fb_width,
|
||||
const u8* sprite, u32 atlas_width,
|
||||
i32 x, i32 y, u32 w, u32 h
|
||||
);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
546
src/pxl8_bsp.c
546
src/pxl8_bsp.c
|
|
@ -1,546 +0,0 @@
|
|||
#include "pxl8_bsp.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_io.h"
|
||||
#include "pxl8_log.h"
|
||||
|
||||
#define BSP_VERSION 29
|
||||
|
||||
typedef enum {
|
||||
CHUNK_ENTITIES = 0,
|
||||
CHUNK_PLANES = 1,
|
||||
CHUNK_TEXTURES = 2,
|
||||
CHUNK_VERTICES = 3,
|
||||
CHUNK_VISIBILITY = 4,
|
||||
CHUNK_NODES = 5,
|
||||
CHUNK_TEXINFO = 6,
|
||||
CHUNK_FACES = 7,
|
||||
CHUNK_LIGHTING = 8,
|
||||
CHUNK_CLIPNODES = 9,
|
||||
CHUNK_LEAFS = 10,
|
||||
CHUNK_MARKSURFACES = 11,
|
||||
CHUNK_EDGES = 12,
|
||||
CHUNK_SURFEDGES = 13,
|
||||
CHUNK_MODELS = 14,
|
||||
CHUNK_COUNT = 15
|
||||
} pxl8_bsp_chunk_type;
|
||||
|
||||
typedef struct {
|
||||
u32 offset;
|
||||
u32 size;
|
||||
} pxl8_bsp_chunk;
|
||||
|
||||
typedef struct {
|
||||
u32 version;
|
||||
pxl8_bsp_chunk chunks[CHUNK_COUNT];
|
||||
} pxl8_bsp_header;
|
||||
|
||||
static inline pxl8_vec3 read_vec3(pxl8_stream* stream) {
|
||||
pxl8_vec3 v;
|
||||
v.x = pxl8_read_f32(stream);
|
||||
v.y = pxl8_read_f32(stream);
|
||||
v.z = pxl8_read_f32(stream);
|
||||
return v;
|
||||
}
|
||||
|
||||
static bool validate_chunk(const pxl8_bsp_chunk* chunk, u32 element_size, size_t file_size) {
|
||||
if (chunk->size == 0) return true;
|
||||
if (chunk->offset >= file_size) return false;
|
||||
if (chunk->offset + chunk->size > file_size) return false;
|
||||
if (chunk->size % element_size != 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool pxl8_bsp_get_edge_vertex(const pxl8_bsp* bsp, i32 surfedge_idx, u32* out_vert_idx) {
|
||||
if (surfedge_idx >= (i32)bsp->num_surfedges) return false;
|
||||
|
||||
i32 edge_idx = bsp->surfedges[surfedge_idx];
|
||||
u32 vertex_index;
|
||||
|
||||
if (edge_idx >= 0) {
|
||||
if ((u32)edge_idx >= bsp->num_edges) return false;
|
||||
vertex_index = 0;
|
||||
} else {
|
||||
edge_idx = -edge_idx;
|
||||
if ((u32)edge_idx >= bsp->num_edges) return false;
|
||||
vertex_index = 1;
|
||||
}
|
||||
|
||||
*out_vert_idx = bsp->edges[edge_idx].vertex[vertex_index];
|
||||
return *out_vert_idx < bsp->num_vertices;
|
||||
}
|
||||
|
||||
static inline bool pxl8_bsp_get_edge_vertices(const pxl8_bsp* bsp, i32 surfedge_idx, u32* out_v0_idx, u32* out_v1_idx) {
|
||||
if (surfedge_idx >= (i32)bsp->num_surfedges) return false;
|
||||
|
||||
i32 edge_idx = bsp->surfedges[surfedge_idx];
|
||||
|
||||
if (edge_idx >= 0) {
|
||||
if ((u32)edge_idx >= bsp->num_edges) return false;
|
||||
*out_v0_idx = bsp->edges[edge_idx].vertex[0];
|
||||
*out_v1_idx = bsp->edges[edge_idx].vertex[1];
|
||||
} else {
|
||||
edge_idx = -edge_idx;
|
||||
if ((u32)edge_idx >= bsp->num_edges) return false;
|
||||
*out_v0_idx = bsp->edges[edge_idx].vertex[1];
|
||||
*out_v1_idx = bsp->edges[edge_idx].vertex[0];
|
||||
}
|
||||
|
||||
return *out_v0_idx < bsp->num_vertices && *out_v1_idx < bsp->num_vertices;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
|
||||
if (!path || !bsp) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
memset(bsp, 0, sizeof(*bsp));
|
||||
|
||||
u8* file_data = NULL;
|
||||
size_t file_size = 0;
|
||||
pxl8_result result = pxl8_io_read_binary_file(path, &file_data, &file_size);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to load BSP file: %s", path);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (file_size < sizeof(pxl8_bsp_header)) {
|
||||
pxl8_error("BSP file too small: %s", path);
|
||||
free(file_data);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
pxl8_stream stream = pxl8_stream_create(file_data, (u32)file_size);
|
||||
|
||||
pxl8_bsp_header header;
|
||||
header.version = pxl8_read_u32(&stream);
|
||||
|
||||
if (header.version != BSP_VERSION) {
|
||||
pxl8_error("Invalid BSP version: %u (expected %d)", header.version, BSP_VERSION);
|
||||
free(file_data);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
for (i32 i = 0; i < CHUNK_COUNT; i++) {
|
||||
header.chunks[i].offset = pxl8_read_u32(&stream);
|
||||
header.chunks[i].size = pxl8_read_u32(&stream);
|
||||
}
|
||||
|
||||
pxl8_bsp_chunk* chunk = &header.chunks[CHUNK_VERTICES];
|
||||
if (!validate_chunk(chunk, 12, file_size)) goto error_cleanup;
|
||||
bsp->num_vertices = chunk->size / 12;
|
||||
if (bsp->num_vertices > 0) {
|
||||
bsp->vertices = calloc(bsp->num_vertices, sizeof(pxl8_bsp_vertex));
|
||||
if (!bsp->vertices) goto error_cleanup;
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_vertices; i++) {
|
||||
bsp->vertices[i].position = read_vec3(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_EDGES];
|
||||
if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
|
||||
bsp->num_edges = chunk->size / 4;
|
||||
if (bsp->num_edges > 0) {
|
||||
bsp->edges = calloc(bsp->num_edges, sizeof(pxl8_bsp_edge));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_edges; i++) {
|
||||
bsp->edges[i].vertex[0] = pxl8_read_u16(&stream);
|
||||
bsp->edges[i].vertex[1] = pxl8_read_u16(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_SURFEDGES];
|
||||
if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
|
||||
bsp->num_surfedges = chunk->size / 4;
|
||||
if (bsp->num_surfedges > 0) {
|
||||
bsp->surfedges = calloc(bsp->num_surfedges, sizeof(i32));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_surfedges; i++) {
|
||||
bsp->surfedges[i] = pxl8_read_i32(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_PLANES];
|
||||
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
|
||||
bsp->num_planes = chunk->size / 20;
|
||||
if (bsp->num_planes > 0) {
|
||||
bsp->planes = calloc(bsp->num_planes, sizeof(pxl8_bsp_plane));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_planes; i++) {
|
||||
bsp->planes[i].normal = read_vec3(&stream);
|
||||
bsp->planes[i].dist = pxl8_read_f32(&stream);
|
||||
bsp->planes[i].type = pxl8_read_i32(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_TEXINFO];
|
||||
if (!validate_chunk(chunk, 40, file_size)) goto error_cleanup;
|
||||
bsp->num_texinfo = chunk->size / 40;
|
||||
if (bsp->num_texinfo > 0) {
|
||||
bsp->texinfo = calloc(bsp->num_texinfo, sizeof(pxl8_bsp_texinfo));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_texinfo; i++) {
|
||||
bsp->texinfo[i].u_axis = read_vec3(&stream);
|
||||
bsp->texinfo[i].u_offset = pxl8_read_f32(&stream);
|
||||
bsp->texinfo[i].v_axis = read_vec3(&stream);
|
||||
bsp->texinfo[i].v_offset = pxl8_read_f32(&stream);
|
||||
bsp->texinfo[i].miptex = pxl8_read_u32(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_FACES];
|
||||
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
|
||||
bsp->num_faces = chunk->size / 20;
|
||||
if (bsp->num_faces > 0) {
|
||||
bsp->faces = calloc(bsp->num_faces, sizeof(pxl8_bsp_face));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||
bsp->faces[i].plane_id = pxl8_read_u16(&stream);
|
||||
bsp->faces[i].side = pxl8_read_u16(&stream);
|
||||
bsp->faces[i].first_edge = pxl8_read_u32(&stream);
|
||||
bsp->faces[i].num_edges = pxl8_read_u16(&stream);
|
||||
bsp->faces[i].texinfo_id = pxl8_read_u16(&stream);
|
||||
bsp->faces[i].styles[0] = pxl8_read_u8(&stream);
|
||||
bsp->faces[i].styles[1] = pxl8_read_u8(&stream);
|
||||
bsp->faces[i].styles[2] = pxl8_read_u8(&stream);
|
||||
bsp->faces[i].styles[3] = pxl8_read_u8(&stream);
|
||||
bsp->faces[i].lightmap_offset = pxl8_read_u32(&stream);
|
||||
|
||||
bsp->faces[i].aabb_min = (pxl8_vec3){1e30f, 1e30f, 1e30f};
|
||||
bsp->faces[i].aabb_max = (pxl8_vec3){-1e30f, -1e30f, -1e30f};
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_NODES];
|
||||
if (!validate_chunk(chunk, 24, file_size)) goto error_cleanup;
|
||||
bsp->num_nodes = chunk->size / 24;
|
||||
if (bsp->num_nodes > 0) {
|
||||
bsp->nodes = calloc(bsp->num_nodes, sizeof(pxl8_bsp_node));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_nodes; i++) {
|
||||
bsp->nodes[i].plane_id = pxl8_read_u32(&stream);
|
||||
bsp->nodes[i].children[0] = pxl8_read_i16(&stream);
|
||||
bsp->nodes[i].children[1] = pxl8_read_i16(&stream);
|
||||
for (u32 j = 0; j < 3; j++) bsp->nodes[i].mins[j] = pxl8_read_i16(&stream);
|
||||
for (u32 j = 0; j < 3; j++) bsp->nodes[i].maxs[j] = pxl8_read_i16(&stream);
|
||||
bsp->nodes[i].first_face = pxl8_read_u16(&stream);
|
||||
bsp->nodes[i].num_faces = pxl8_read_u16(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_LEAFS];
|
||||
if (!validate_chunk(chunk, 28, file_size)) goto error_cleanup;
|
||||
bsp->num_leafs = chunk->size / 28;
|
||||
if (bsp->num_leafs > 0) {
|
||||
bsp->leafs = calloc(bsp->num_leafs, sizeof(pxl8_bsp_leaf));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_leafs; i++) {
|
||||
bsp->leafs[i].contents = pxl8_read_i32(&stream);
|
||||
bsp->leafs[i].visofs = pxl8_read_i32(&stream);
|
||||
for (u32 j = 0; j < 3; j++) bsp->leafs[i].mins[j] = pxl8_read_i16(&stream);
|
||||
for (u32 j = 0; j < 3; j++) bsp->leafs[i].maxs[j] = pxl8_read_i16(&stream);
|
||||
bsp->leafs[i].first_marksurface = pxl8_read_u16(&stream);
|
||||
bsp->leafs[i].num_marksurfaces = pxl8_read_u16(&stream);
|
||||
for (u32 j = 0; j < 4; j++) bsp->leafs[i].ambient_level[j] = pxl8_read_u8(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_MARKSURFACES];
|
||||
if (!validate_chunk(chunk, 2, file_size)) goto error_cleanup;
|
||||
bsp->num_marksurfaces = chunk->size / 2;
|
||||
if (bsp->num_marksurfaces > 0) {
|
||||
bsp->marksurfaces = calloc(bsp->num_marksurfaces, sizeof(u16));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_marksurfaces; i++) {
|
||||
bsp->marksurfaces[i] = pxl8_read_u16(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_MODELS];
|
||||
if (!validate_chunk(chunk, 64, file_size)) goto error_cleanup;
|
||||
bsp->num_models = chunk->size / 64;
|
||||
if (bsp->num_models > 0) {
|
||||
bsp->models = calloc(bsp->num_models, sizeof(pxl8_bsp_model));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_models; i++) {
|
||||
for (u32 j = 0; j < 3; j++) bsp->models[i].mins[j] = pxl8_read_f32(&stream);
|
||||
for (u32 j = 0; j < 3; j++) bsp->models[i].maxs[j] = pxl8_read_f32(&stream);
|
||||
bsp->models[i].origin = read_vec3(&stream);
|
||||
for (u32 j = 0; j < 4; j++) bsp->models[i].headnode[j] = pxl8_read_i32(&stream);
|
||||
bsp->models[i].visleafs = pxl8_read_i32(&stream);
|
||||
bsp->models[i].first_face = pxl8_read_i32(&stream);
|
||||
bsp->models[i].num_faces = pxl8_read_i32(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_VISIBILITY];
|
||||
if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
|
||||
bsp->visdata_size = chunk->size;
|
||||
if (bsp->visdata_size > 0) {
|
||||
bsp->visdata = malloc(bsp->visdata_size);
|
||||
memcpy(bsp->visdata, file_data + chunk->offset, bsp->visdata_size);
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_LIGHTING];
|
||||
if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
|
||||
bsp->lightdata_size = chunk->size;
|
||||
if (bsp->lightdata_size > 0) {
|
||||
bsp->lightdata = malloc(bsp->lightdata_size);
|
||||
memcpy(bsp->lightdata, file_data + chunk->offset, bsp->lightdata_size);
|
||||
}
|
||||
|
||||
free(file_data);
|
||||
|
||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||
pxl8_bsp_face* face = &bsp->faces[i];
|
||||
f32 min_x = 1e30f, min_y = 1e30f, min_z = 1e30f;
|
||||
f32 max_x = -1e30f, max_y = -1e30f, max_z = -1e30f;
|
||||
|
||||
for (u32 j = 0; j < face->num_edges; j++) {
|
||||
i32 surfedge_idx = face->first_edge + j;
|
||||
u32 vert_idx;
|
||||
|
||||
if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) continue;
|
||||
|
||||
pxl8_vec3 v = bsp->vertices[vert_idx].position;
|
||||
|
||||
if (v.x < min_x) min_x = v.x;
|
||||
if (v.x > max_x) max_x = v.x;
|
||||
if (v.y < min_y) min_y = v.y;
|
||||
if (v.y > max_y) max_y = v.y;
|
||||
if (v.z < min_z) min_z = v.z;
|
||||
if (v.z > max_z) max_z = v.z;
|
||||
}
|
||||
|
||||
face->aabb_min = (pxl8_vec3){min_x, min_y, min_z};
|
||||
face->aabb_max = (pxl8_vec3){max_x, max_y, max_z};
|
||||
}
|
||||
|
||||
pxl8_debug("Loaded BSP: %u verts, %u faces, %u nodes, %u leafs",
|
||||
bsp->num_vertices, bsp->num_faces, bsp->num_nodes, bsp->num_leafs);
|
||||
|
||||
return PXL8_OK;
|
||||
|
||||
error_cleanup:
|
||||
pxl8_error("BSP chunk validation failed: %s", path);
|
||||
free(file_data);
|
||||
pxl8_bsp_destroy(bsp);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
void pxl8_bsp_destroy(pxl8_bsp* bsp) {
|
||||
if (!bsp) return;
|
||||
|
||||
free(bsp->edges);
|
||||
free(bsp->faces);
|
||||
free(bsp->leafs);
|
||||
free(bsp->lightdata);
|
||||
free(bsp->marksurfaces);
|
||||
free(bsp->models);
|
||||
free(bsp->nodes);
|
||||
free(bsp->planes);
|
||||
free(bsp->surfedges);
|
||||
free(bsp->texinfo);
|
||||
free(bsp->vertices);
|
||||
free(bsp->visdata);
|
||||
|
||||
memset(bsp, 0, sizeof(*bsp));
|
||||
}
|
||||
|
||||
i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) {
|
||||
if (!bsp || bsp->num_nodes == 0) return -1;
|
||||
|
||||
i32 node_id = 0;
|
||||
|
||||
while (node_id >= 0) {
|
||||
const pxl8_bsp_node* node = &bsp->nodes[node_id];
|
||||
const pxl8_bsp_plane* plane = &bsp->planes[node->plane_id];
|
||||
|
||||
f32 dist = pxl8_vec3_dot(pos, plane->normal) - plane->dist;
|
||||
node_id = node->children[dist < 0 ? 1 : 0];
|
||||
}
|
||||
|
||||
return -(node_id + 1);
|
||||
}
|
||||
|
||||
bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to) {
|
||||
if (!bsp || !bsp->visdata || leaf_from < 0 || leaf_to < 0) return true;
|
||||
if ((u32)leaf_from >= bsp->num_leafs || (u32)leaf_to >= bsp->num_leafs) return true;
|
||||
|
||||
i32 visofs = bsp->leafs[leaf_from].visofs;
|
||||
if (visofs < 0) return true;
|
||||
|
||||
i32 byte_idx = leaf_to >> 3;
|
||||
i32 bit_idx = leaf_to & 7;
|
||||
|
||||
if ((u32)visofs + byte_idx >= bsp->visdata_size) return true;
|
||||
|
||||
return (bsp->visdata[visofs + byte_idx] & (1 << bit_idx)) != 0;
|
||||
}
|
||||
|
||||
static inline bool face_in_frustum(const pxl8_bsp* bsp, u32 face_id, const pxl8_frustum* frustum) {
|
||||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
||||
return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max);
|
||||
}
|
||||
|
||||
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 texture_id) {
|
||||
if (!gfx || !bsp || face_id >= bsp->num_faces) return;
|
||||
|
||||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
||||
if (face->num_edges < 3) return;
|
||||
|
||||
pxl8_vec3 verts[64];
|
||||
u32 num_verts = 0;
|
||||
|
||||
u32 color = 15;
|
||||
bool use_texture = (face->texinfo_id < bsp->num_texinfo);
|
||||
|
||||
|
||||
for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) {
|
||||
i32 surfedge_idx = face->first_edge + i;
|
||||
u32 vert_idx;
|
||||
|
||||
if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) continue;
|
||||
|
||||
verts[num_verts++] = bsp->vertices[vert_idx].position;
|
||||
}
|
||||
|
||||
if (num_verts < 3) return;
|
||||
|
||||
if (use_texture && face->texinfo_id < bsp->num_texinfo) {
|
||||
const pxl8_bsp_texinfo* texinfo = &bsp->texinfo[face->texinfo_id];
|
||||
f32 tex_scale = 64.0f;
|
||||
|
||||
for (u32 tri_idx = 1; tri_idx < num_verts - 1; tri_idx++) {
|
||||
pxl8_vec3 v0 = verts[0];
|
||||
pxl8_vec3 v1 = verts[tri_idx];
|
||||
pxl8_vec3 v2 = verts[tri_idx + 1];
|
||||
|
||||
f32 u0 = (pxl8_vec3_dot(v0, texinfo->u_axis) + texinfo->u_offset) / tex_scale;
|
||||
f32 v0_uv = (pxl8_vec3_dot(v0, texinfo->v_axis) + texinfo->v_offset) / tex_scale;
|
||||
f32 u1 = (pxl8_vec3_dot(v1, texinfo->u_axis) + texinfo->u_offset) / tex_scale;
|
||||
f32 v1_uv = (pxl8_vec3_dot(v1, texinfo->v_axis) + texinfo->v_offset) / tex_scale;
|
||||
f32 u2 = (pxl8_vec3_dot(v2, texinfo->u_axis) + texinfo->u_offset) / tex_scale;
|
||||
f32 v2_uv = (pxl8_vec3_dot(v2, texinfo->v_axis) + texinfo->v_offset) / tex_scale;
|
||||
|
||||
pxl8_3d_draw_triangle_textured(gfx, v0, v1, v2, u0, v0_uv, u1, v1_uv, u2, v2_uv, texture_id);
|
||||
}
|
||||
} else{
|
||||
for (u32 i = 1; i < num_verts - 1; i++) {
|
||||
pxl8_3d_draw_triangle_raw(gfx, verts[0], verts[i], verts[i + 1], color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_bsp_render_textured(
|
||||
pxl8_gfx* gfx,
|
||||
const pxl8_bsp* bsp,
|
||||
pxl8_vec3 camera_pos
|
||||
) {
|
||||
if (!gfx || !bsp || bsp->num_faces == 0) return;
|
||||
|
||||
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
|
||||
if (!frustum) return;
|
||||
|
||||
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
|
||||
|
||||
static u8* rendered_faces = NULL;
|
||||
static u32 rendered_faces_capacity = 0;
|
||||
|
||||
if (rendered_faces_capacity < bsp->num_faces) {
|
||||
u8* new_buffer = realloc(rendered_faces, bsp->num_faces);
|
||||
if (!new_buffer) return;
|
||||
rendered_faces = new_buffer;
|
||||
rendered_faces_capacity = bsp->num_faces;
|
||||
pxl8_debug("Allocated face tracking buffer: %u bytes", bsp->num_faces);
|
||||
}
|
||||
|
||||
memset(rendered_faces, 0, bsp->num_faces);
|
||||
|
||||
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
|
||||
if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) continue;
|
||||
|
||||
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
|
||||
|
||||
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
|
||||
u32 surf_idx = leaf->first_marksurface + i;
|
||||
if (surf_idx >= bsp->num_marksurfaces) continue;
|
||||
|
||||
u32 face_id = bsp->marksurfaces[surf_idx];
|
||||
if (face_id >= bsp->num_faces) continue;
|
||||
|
||||
if (rendered_faces[face_id]) continue;
|
||||
rendered_faces[face_id] = 1;
|
||||
|
||||
if (!face_in_frustum(bsp, face_id, frustum)) continue;
|
||||
|
||||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
||||
u32 texture_id = 0;
|
||||
if (face->texinfo_id < bsp->num_texinfo) {
|
||||
texture_id = bsp->texinfo[face->texinfo_id].miptex;
|
||||
} else {
|
||||
static bool warned = false;
|
||||
if (!warned) {
|
||||
pxl8_warn("Face %u has invalid texinfo_id %u (num_texinfo=%u)",
|
||||
face_id, face->texinfo_id, bsp->num_texinfo);
|
||||
warned = true;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_bsp_render_face(gfx, bsp, face_id, texture_id);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void pxl8_bsp_render_wireframe(
|
||||
pxl8_gfx* gfx,
|
||||
const pxl8_bsp* bsp,
|
||||
pxl8_vec3 camera_pos,
|
||||
u32 color
|
||||
) {
|
||||
if (!gfx || !bsp) return;
|
||||
|
||||
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
|
||||
if (!frustum) return;
|
||||
|
||||
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
|
||||
|
||||
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
|
||||
if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) continue;
|
||||
|
||||
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
|
||||
|
||||
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
|
||||
u32 surf_idx = leaf->first_marksurface + i;
|
||||
if (surf_idx >= bsp->num_marksurfaces) continue;
|
||||
|
||||
u32 face_id = bsp->marksurfaces[surf_idx];
|
||||
if (face_id >= bsp->num_faces) continue;
|
||||
|
||||
if (!face_in_frustum(bsp, face_id, frustum)) continue;
|
||||
|
||||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
||||
|
||||
for (u32 e = 0; e < face->num_edges; e++) {
|
||||
i32 surfedge_idx = face->first_edge + e;
|
||||
i32 next_surfedge_idx = face->first_edge + ((e + 1) % face->num_edges);
|
||||
|
||||
if (surfedge_idx >= (i32)bsp->num_surfedges ||
|
||||
next_surfedge_idx >= (i32)bsp->num_surfedges) continue;
|
||||
|
||||
u32 v0_idx, v1_idx;
|
||||
|
||||
if (!pxl8_bsp_get_edge_vertices(bsp, surfedge_idx, &v0_idx, &v1_idx)) continue;
|
||||
|
||||
pxl8_vec3 p0 = bsp->vertices[v0_idx].position;
|
||||
pxl8_vec3 p1 = bsp->vertices[v1_idx].position;
|
||||
|
||||
pxl8_3d_draw_line_3d(gfx, p0, p1, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
140
src/pxl8_bsp.h
140
src/pxl8_bsp.h
|
|
@ -1,140 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_bsp_edge {
|
||||
u16 vertex[2];
|
||||
} pxl8_bsp_edge;
|
||||
|
||||
typedef struct pxl8_bsp_face {
|
||||
u32 first_edge;
|
||||
u32 lightmap_offset;
|
||||
u16 num_edges;
|
||||
u16 plane_id;
|
||||
u16 side;
|
||||
|
||||
u8 styles[4];
|
||||
u16 texinfo_id;
|
||||
|
||||
pxl8_vec3 aabb_min;
|
||||
pxl8_vec3 aabb_max;
|
||||
} pxl8_bsp_face;
|
||||
|
||||
typedef struct pxl8_bsp_leaf {
|
||||
u8 ambient_level[4];
|
||||
i32 contents;
|
||||
|
||||
u16 first_marksurface;
|
||||
i16 maxs[3];
|
||||
i16 mins[3];
|
||||
u16 num_marksurfaces;
|
||||
|
||||
i32 visofs;
|
||||
} pxl8_bsp_leaf;
|
||||
|
||||
typedef struct pxl8_bsp_model {
|
||||
i32 first_face;
|
||||
i32 headnode[4];
|
||||
f32 maxs[3];
|
||||
f32 mins[3];
|
||||
i32 num_faces;
|
||||
|
||||
pxl8_vec3 origin;
|
||||
i32 visleafs;
|
||||
} pxl8_bsp_model;
|
||||
|
||||
typedef struct pxl8_bsp_node {
|
||||
i32 children[2];
|
||||
|
||||
u16 first_face;
|
||||
i16 maxs[3];
|
||||
i16 mins[3];
|
||||
u16 num_faces;
|
||||
|
||||
u32 plane_id;
|
||||
} pxl8_bsp_node;
|
||||
|
||||
typedef struct pxl8_bsp_plane {
|
||||
f32 dist;
|
||||
pxl8_vec3 normal;
|
||||
i32 type;
|
||||
} pxl8_bsp_plane;
|
||||
|
||||
typedef struct pxl8_bsp_texinfo {
|
||||
u32 miptex;
|
||||
char name[16];
|
||||
|
||||
f32 u_offset;
|
||||
pxl8_vec3 u_axis;
|
||||
|
||||
f32 v_offset;
|
||||
pxl8_vec3 v_axis;
|
||||
} pxl8_bsp_texinfo;
|
||||
|
||||
typedef struct pxl8_bsp_vertex {
|
||||
pxl8_vec3 position;
|
||||
} pxl8_bsp_vertex;
|
||||
|
||||
typedef struct pxl8_bsp {
|
||||
pxl8_bsp_edge* edges;
|
||||
pxl8_bsp_face* faces;
|
||||
pxl8_bsp_leaf* leafs;
|
||||
u8* lightdata;
|
||||
u16* marksurfaces;
|
||||
pxl8_bsp_model* models;
|
||||
pxl8_bsp_node* nodes;
|
||||
pxl8_bsp_plane* planes;
|
||||
i32* surfedges;
|
||||
pxl8_bsp_texinfo* texinfo;
|
||||
pxl8_bsp_vertex* vertices;
|
||||
u8* visdata;
|
||||
|
||||
u32 lightdata_size;
|
||||
u32 num_edges;
|
||||
u32 num_faces;
|
||||
u32 num_leafs;
|
||||
u32 num_marksurfaces;
|
||||
u32 num_models;
|
||||
u32 num_nodes;
|
||||
u32 num_planes;
|
||||
u32 num_surfedges;
|
||||
u32 num_texinfo;
|
||||
u32 num_vertices;
|
||||
u32 visdata_size;
|
||||
} pxl8_bsp;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp);
|
||||
void pxl8_bsp_destroy(pxl8_bsp* bsp);
|
||||
|
||||
i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos);
|
||||
bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to);
|
||||
|
||||
void pxl8_bsp_render_face(
|
||||
pxl8_gfx* gfx,
|
||||
const pxl8_bsp* bsp,
|
||||
u32 face_id,
|
||||
u32 texture_id
|
||||
);
|
||||
|
||||
void pxl8_bsp_render_textured(
|
||||
pxl8_gfx* gfx,
|
||||
const pxl8_bsp* bsp,
|
||||
pxl8_vec3 camera_pos
|
||||
);
|
||||
|
||||
void pxl8_bsp_render_wireframe(
|
||||
pxl8_gfx* gfx,
|
||||
const pxl8_bsp* bsp,
|
||||
pxl8_vec3 camera_pos,
|
||||
u32 color
|
||||
);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
634
src/pxl8_cart.c
634
src/pxl8_cart.c
|
|
@ -1,634 +0,0 @@
|
|||
#include "pxl8_cart.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "pxl8_log.h"
|
||||
|
||||
#define PXL8_CART_MAGIC 0x43585850
|
||||
#define PXL8_CART_VERSION 1
|
||||
#define PXL8_CART_TRAILER_MAGIC 0x544E4250
|
||||
|
||||
typedef struct {
|
||||
u32 magic;
|
||||
u16 version;
|
||||
u16 flags;
|
||||
u32 file_count;
|
||||
u32 toc_size;
|
||||
} pxl8_cart_header;
|
||||
|
||||
typedef struct {
|
||||
u32 offset;
|
||||
u32 size;
|
||||
u16 path_len;
|
||||
} pxl8_cart_entry;
|
||||
|
||||
typedef struct {
|
||||
u32 magic;
|
||||
u32 cart_offset;
|
||||
u32 cart_size;
|
||||
} pxl8_cart_trailer;
|
||||
|
||||
typedef struct {
|
||||
char* path;
|
||||
u32 offset;
|
||||
u32 size;
|
||||
} pxl8_cart_file;
|
||||
|
||||
struct pxl8_cart {
|
||||
u8* data;
|
||||
u32 data_size;
|
||||
pxl8_cart_file* files;
|
||||
u32 file_count;
|
||||
char* base_path;
|
||||
char* title;
|
||||
pxl8_resolution resolution;
|
||||
pxl8_size window_size;
|
||||
pxl8_pixel_mode pixel_mode;
|
||||
bool is_folder;
|
||||
bool is_mounted;
|
||||
};
|
||||
|
||||
static pxl8_cart* pxl8_current_cart = NULL;
|
||||
static char* pxl8_original_cwd = NULL;
|
||||
|
||||
static bool is_directory(const char* path) {
|
||||
struct stat st;
|
||||
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
|
||||
}
|
||||
|
||||
static bool is_pxc_file(const char* path) {
|
||||
size_t len = strlen(path);
|
||||
return len > 4 && strcmp(path + len - 4, ".pxc") == 0;
|
||||
}
|
||||
|
||||
static bool has_main_script(const char* base_path) {
|
||||
char path[512];
|
||||
snprintf(path, sizeof(path), "%s/main.fnl", base_path);
|
||||
if (access(path, F_OK) == 0) return true;
|
||||
snprintf(path, sizeof(path), "%s/main.lua", base_path);
|
||||
return access(path, F_OK) == 0;
|
||||
}
|
||||
|
||||
static void collect_files_recursive(const char* dir_path, const char* prefix,
|
||||
char*** paths, u32* count, u32* capacity) {
|
||||
DIR* dir = opendir(dir_path);
|
||||
if (!dir) return;
|
||||
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
||||
|
||||
char full_path[1024];
|
||||
char rel_path[1024];
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
|
||||
snprintf(rel_path, sizeof(rel_path), "%s%s", prefix, entry->d_name);
|
||||
|
||||
struct stat st;
|
||||
if (stat(full_path, &st) == 0) {
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
char new_prefix[1025];
|
||||
snprintf(new_prefix, sizeof(new_prefix), "%s/", rel_path);
|
||||
collect_files_recursive(full_path, new_prefix, paths, count, capacity);
|
||||
} else {
|
||||
if (*count >= *capacity) {
|
||||
*capacity = (*capacity == 0) ? 64 : (*capacity * 2);
|
||||
*paths = realloc(*paths, *capacity * sizeof(char*));
|
||||
}
|
||||
(*paths)[(*count)++] = strdup(rel_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
static pxl8_cart_file* find_file(const pxl8_cart* cart, const char* path) {
|
||||
if (!cart || !cart->files) return NULL;
|
||||
for (u32 i = 0; i < cart->file_count; i++) {
|
||||
if (strcmp(cart->files[i].path, path) == 0) {
|
||||
return &cart->files[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static pxl8_result load_packed_cart(pxl8_cart* cart, const u8* data, u32 size) {
|
||||
if (size < sizeof(pxl8_cart_header)) return PXL8_ERROR_INVALID_FORMAT;
|
||||
|
||||
const pxl8_cart_header* header = (const pxl8_cart_header*)data;
|
||||
if (header->magic != PXL8_CART_MAGIC) return PXL8_ERROR_INVALID_FORMAT;
|
||||
if (header->version > PXL8_CART_VERSION) return PXL8_ERROR_INVALID_FORMAT;
|
||||
|
||||
cart->data = malloc(size);
|
||||
if (!cart->data) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
memcpy(cart->data, data, size);
|
||||
cart->data_size = size;
|
||||
|
||||
cart->file_count = header->file_count;
|
||||
cart->files = calloc(cart->file_count, sizeof(pxl8_cart_file));
|
||||
if (!cart->files) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
const u8* toc = cart->data + sizeof(pxl8_cart_header);
|
||||
for (u32 i = 0; i < cart->file_count; i++) {
|
||||
const pxl8_cart_entry* entry = (const pxl8_cart_entry*)toc;
|
||||
toc += sizeof(pxl8_cart_entry);
|
||||
|
||||
cart->files[i].path = malloc(entry->path_len + 1);
|
||||
memcpy(cart->files[i].path, toc, entry->path_len);
|
||||
cart->files[i].path[entry->path_len] = '\0';
|
||||
toc += entry->path_len;
|
||||
|
||||
cart->files[i].offset = entry->offset;
|
||||
cart->files[i].size = entry->size;
|
||||
}
|
||||
|
||||
cart->is_folder = false;
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_cart* pxl8_cart_create(void) {
|
||||
pxl8_cart* cart = calloc(1, sizeof(pxl8_cart));
|
||||
if (cart) {
|
||||
cart->resolution = PXL8_RESOLUTION_640x360;
|
||||
cart->window_size = (pxl8_size){1280, 720};
|
||||
cart->pixel_mode = PXL8_PIXEL_INDEXED;
|
||||
}
|
||||
return cart;
|
||||
}
|
||||
|
||||
pxl8_cart* pxl8_get_cart(void) {
|
||||
return pxl8_current_cart;
|
||||
}
|
||||
|
||||
void pxl8_cart_destroy(pxl8_cart* cart) {
|
||||
if (!cart) return;
|
||||
pxl8_cart_unload(cart);
|
||||
free(cart);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
|
||||
if (!cart || !path) return PXL8_ERROR_NULL_POINTER;
|
||||
pxl8_cart_unload(cart);
|
||||
|
||||
if (is_directory(path)) {
|
||||
cart->base_path = realpath(path, NULL);
|
||||
if (!cart->base_path) {
|
||||
pxl8_error("Failed to resolve cart path: %s", path);
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
cart->is_folder = true;
|
||||
|
||||
if (!has_main_script(cart->base_path)) {
|
||||
pxl8_error("No main.fnl or main.lua found in cart: %s", path);
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
pxl8_info("Loaded cart");
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
if (is_pxc_file(path)) {
|
||||
FILE* file = fopen(path, "rb");
|
||||
if (!file) {
|
||||
pxl8_error("Failed to open cart: %s", path);
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
u32 size = (u32)ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
u8* data = malloc(size);
|
||||
if (!data || fread(data, 1, size, file) != size) {
|
||||
free(data);
|
||||
fclose(file);
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
pxl8_result result = load_packed_cart(cart, data, size);
|
||||
free(data);
|
||||
|
||||
if (result == PXL8_OK) {
|
||||
pxl8_info("Loaded cart");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
pxl8_error("Unknown cart format: %s", path);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_cart_load_embedded(pxl8_cart* cart, const char* exe_path) {
|
||||
if (!cart || !exe_path) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
FILE* file = fopen(exe_path, "rb");
|
||||
if (!file) return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
|
||||
fseek(file, -(i32)sizeof(pxl8_cart_trailer), SEEK_END);
|
||||
pxl8_cart_trailer trailer;
|
||||
if (fread(&trailer, sizeof(trailer), 1, file) != 1) {
|
||||
fclose(file);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
if (trailer.magic != PXL8_CART_TRAILER_MAGIC) {
|
||||
fclose(file);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
fseek(file, trailer.cart_offset, SEEK_SET);
|
||||
u8* data = malloc(trailer.cart_size);
|
||||
if (!data || fread(data, 1, trailer.cart_size, file) != trailer.cart_size) {
|
||||
free(data);
|
||||
fclose(file);
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
pxl8_result result = load_packed_cart(cart, data, trailer.cart_size);
|
||||
free(data);
|
||||
|
||||
if (result == PXL8_OK) {
|
||||
pxl8_info("Loaded embedded cart");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void pxl8_cart_unload(pxl8_cart* cart) {
|
||||
if (!cart) return;
|
||||
|
||||
if (cart->title) {
|
||||
pxl8_info("Unloaded cart: %s", cart->title);
|
||||
pxl8_cart_unmount(cart);
|
||||
free(cart->title);
|
||||
cart->title = NULL;
|
||||
} else if (cart->base_path || cart->data) {
|
||||
pxl8_info("Unloaded cart");
|
||||
pxl8_cart_unmount(cart);
|
||||
}
|
||||
|
||||
if (cart->files) {
|
||||
for (u32 i = 0; i < cart->file_count; i++) {
|
||||
free(cart->files[i].path);
|
||||
}
|
||||
free(cart->files);
|
||||
cart->files = NULL;
|
||||
}
|
||||
cart->file_count = 0;
|
||||
|
||||
free(cart->data);
|
||||
cart->data = NULL;
|
||||
cart->data_size = 0;
|
||||
|
||||
free(cart->base_path);
|
||||
cart->base_path = NULL;
|
||||
cart->is_folder = false;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_cart_mount(pxl8_cart* cart) {
|
||||
if (!cart) return PXL8_ERROR_NULL_POINTER;
|
||||
if (cart->is_mounted) return PXL8_OK;
|
||||
|
||||
if (pxl8_current_cart) {
|
||||
pxl8_cart_unmount(pxl8_current_cart);
|
||||
}
|
||||
|
||||
if (cart->is_folder) {
|
||||
pxl8_original_cwd = getcwd(NULL, 0);
|
||||
if (chdir(cart->base_path) != 0) {
|
||||
pxl8_error("Failed to change to cart directory: %s", cart->base_path);
|
||||
free(pxl8_original_cwd);
|
||||
pxl8_original_cwd = NULL;
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
cart->is_mounted = true;
|
||||
pxl8_current_cart = cart;
|
||||
if (cart->title) {
|
||||
pxl8_info("Mounted cart: %s", cart->title);
|
||||
} else {
|
||||
pxl8_info("Mounted cart");
|
||||
}
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_cart_unmount(pxl8_cart* cart) {
|
||||
if (!cart || !cart->is_mounted) return;
|
||||
|
||||
if (pxl8_original_cwd) {
|
||||
chdir(pxl8_original_cwd);
|
||||
free(pxl8_original_cwd);
|
||||
pxl8_original_cwd = NULL;
|
||||
}
|
||||
|
||||
cart->is_mounted = false;
|
||||
if (pxl8_current_cart == cart) {
|
||||
pxl8_current_cart = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const char* pxl8_cart_get_base_path(const pxl8_cart* cart) {
|
||||
return cart ? cart->base_path : NULL;
|
||||
}
|
||||
|
||||
const char* pxl8_cart_get_title(const pxl8_cart* cart) {
|
||||
return cart ? cart->title : NULL;
|
||||
}
|
||||
|
||||
void pxl8_cart_set_title(pxl8_cart* cart, const char* title) {
|
||||
if (!cart || !title) return;
|
||||
free(cart->title);
|
||||
cart->title = strdup(title);
|
||||
}
|
||||
|
||||
pxl8_resolution pxl8_cart_get_resolution(const pxl8_cart* cart) {
|
||||
return cart ? cart->resolution : PXL8_RESOLUTION_640x360;
|
||||
}
|
||||
|
||||
void pxl8_cart_set_resolution(pxl8_cart* cart, pxl8_resolution resolution) {
|
||||
if (cart) cart->resolution = resolution;
|
||||
}
|
||||
|
||||
pxl8_size pxl8_cart_get_window_size(const pxl8_cart* cart) {
|
||||
return cart ? cart->window_size : (pxl8_size){1280, 720};
|
||||
}
|
||||
|
||||
void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size) {
|
||||
if (cart) cart->window_size = size;
|
||||
}
|
||||
|
||||
pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart) {
|
||||
return cart ? cart->pixel_mode : PXL8_PIXEL_INDEXED;
|
||||
}
|
||||
|
||||
void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode) {
|
||||
if (cart) cart->pixel_mode = mode;
|
||||
}
|
||||
|
||||
bool pxl8_cart_is_packed(const pxl8_cart* cart) {
|
||||
return cart && !cart->is_folder;
|
||||
}
|
||||
|
||||
bool pxl8_cart_has_embedded(const char* exe_path) {
|
||||
FILE* file = fopen(exe_path, "rb");
|
||||
if (!file) return false;
|
||||
|
||||
fseek(file, -(i32)sizeof(pxl8_cart_trailer), SEEK_END);
|
||||
pxl8_cart_trailer trailer;
|
||||
bool has = (fread(&trailer, sizeof(trailer), 1, file) == 1 &&
|
||||
trailer.magic == PXL8_CART_TRAILER_MAGIC);
|
||||
fclose(file);
|
||||
return has;
|
||||
}
|
||||
|
||||
bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path) {
|
||||
if (!cart || !path) return false;
|
||||
|
||||
if (cart->is_folder) {
|
||||
char full_path[512];
|
||||
if (!pxl8_cart_resolve_path(cart, path, full_path, sizeof(full_path))) return false;
|
||||
return access(full_path, F_OK) == 0;
|
||||
}
|
||||
|
||||
return find_file(cart, path) != NULL;
|
||||
}
|
||||
|
||||
bool pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path, char* out_path, size_t out_size) {
|
||||
if (!cart || !relative_path || !out_path || out_size == 0) return false;
|
||||
|
||||
if (cart->is_folder && cart->base_path) {
|
||||
i32 written = snprintf(out_path, out_size, "%s/%s", cart->base_path, relative_path);
|
||||
return written >= 0 && (size_t)written < out_size;
|
||||
}
|
||||
|
||||
i32 written = snprintf(out_path, out_size, "%s", relative_path);
|
||||
return written >= 0 && (size_t)written < out_size;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** data_out, u32* size_out) {
|
||||
if (!cart || !path || !data_out || !size_out) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
if (cart->is_folder) {
|
||||
char full_path[512];
|
||||
if (!pxl8_cart_resolve_path(cart, path, full_path, sizeof(full_path))) {
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
FILE* file = fopen(full_path, "rb");
|
||||
if (!file) return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
*size_out = (u32)ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
*data_out = malloc(*size_out);
|
||||
if (!*data_out || fread(*data_out, 1, *size_out, file) != *size_out) {
|
||||
free(*data_out);
|
||||
*data_out = NULL;
|
||||
fclose(file);
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
fclose(file);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_cart_file* cf = find_file(cart, path);
|
||||
if (!cf) return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
|
||||
*size_out = cf->size;
|
||||
*data_out = malloc(cf->size);
|
||||
if (!*data_out) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
memcpy(*data_out, cart->data + cf->offset, cf->size);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_cart_free_file(u8* data) {
|
||||
free(data);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
|
||||
if (!folder_path || !output_path) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
if (!is_directory(folder_path)) {
|
||||
pxl8_error("Cart folder not found: %s", folder_path);
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (!has_main_script(folder_path)) {
|
||||
pxl8_error("No main.fnl or main.lua found in cart: %s", folder_path);
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
char** paths = NULL;
|
||||
u32 count = 0, capacity = 0;
|
||||
collect_files_recursive(folder_path, "", &paths, &count, &capacity);
|
||||
|
||||
if (count == 0) {
|
||||
pxl8_error("No files found in cart folder");
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
u32 toc_size = 0;
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
toc_size += sizeof(pxl8_cart_entry) + strlen(paths[i]);
|
||||
}
|
||||
|
||||
u32 data_offset = sizeof(pxl8_cart_header) + toc_size;
|
||||
u32 total_size = data_offset;
|
||||
|
||||
u32* file_sizes = malloc(count * sizeof(u32));
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
char full_path[1024];
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", folder_path, paths[i]);
|
||||
struct stat st;
|
||||
if (stat(full_path, &st) == 0) {
|
||||
file_sizes[i] = (u32)st.st_size;
|
||||
total_size += file_sizes[i];
|
||||
} else {
|
||||
file_sizes[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
u8* buffer = calloc(1, total_size);
|
||||
if (!buffer) {
|
||||
free(file_sizes);
|
||||
for (u32 i = 0; i < count; i++) free(paths[i]);
|
||||
free(paths);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
pxl8_cart_header* header = (pxl8_cart_header*)buffer;
|
||||
header->magic = PXL8_CART_MAGIC;
|
||||
header->version = PXL8_CART_VERSION;
|
||||
header->flags = 0;
|
||||
header->file_count = count;
|
||||
header->toc_size = toc_size;
|
||||
|
||||
u8* toc = buffer + sizeof(pxl8_cart_header);
|
||||
u32 file_offset = data_offset;
|
||||
|
||||
pxl8_info("Packing cart: %s -> %s", folder_path, output_path);
|
||||
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
pxl8_cart_entry* entry = (pxl8_cart_entry*)toc;
|
||||
entry->offset = file_offset;
|
||||
entry->size = file_sizes[i];
|
||||
entry->path_len = (u16)strlen(paths[i]);
|
||||
toc += sizeof(pxl8_cart_entry);
|
||||
|
||||
memcpy(toc, paths[i], entry->path_len);
|
||||
toc += entry->path_len;
|
||||
|
||||
char full_path[1024];
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", folder_path, paths[i]);
|
||||
FILE* file = fopen(full_path, "rb");
|
||||
if (file) {
|
||||
fread(buffer + file_offset, 1, file_sizes[i], file);
|
||||
fclose(file);
|
||||
pxl8_info(" %s (%u bytes)", paths[i], file_sizes[i]);
|
||||
}
|
||||
|
||||
file_offset += file_sizes[i];
|
||||
free(paths[i]);
|
||||
}
|
||||
free(paths);
|
||||
free(file_sizes);
|
||||
|
||||
FILE* out = fopen(output_path, "wb");
|
||||
if (!out) {
|
||||
free(buffer);
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
fwrite(buffer, 1, total_size, out);
|
||||
fclose(out);
|
||||
free(buffer);
|
||||
|
||||
pxl8_info("Cart packed: %u files, %u bytes", count, total_size);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, const char* exe_path) {
|
||||
if (!input_path || !output_path || !exe_path) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
u8* cart_data = NULL;
|
||||
u32 cart_size = 0;
|
||||
bool free_cart = false;
|
||||
|
||||
if (is_directory(input_path)) {
|
||||
char temp_pxc[256];
|
||||
snprintf(temp_pxc, sizeof(temp_pxc), "/tmp/pxl8_bundle_%d.pxc", getpid());
|
||||
pxl8_result result = pxl8_cart_pack(input_path, temp_pxc);
|
||||
if (result != PXL8_OK) return result;
|
||||
|
||||
FILE* f = fopen(temp_pxc, "rb");
|
||||
if (!f) return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
fseek(f, 0, SEEK_END);
|
||||
cart_size = (u32)ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
cart_data = malloc(cart_size);
|
||||
fread(cart_data, 1, cart_size, f);
|
||||
fclose(f);
|
||||
unlink(temp_pxc);
|
||||
free_cart = true;
|
||||
} else if (is_pxc_file(input_path)) {
|
||||
FILE* f = fopen(input_path, "rb");
|
||||
if (!f) return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
fseek(f, 0, SEEK_END);
|
||||
cart_size = (u32)ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
cart_data = malloc(cart_size);
|
||||
fread(cart_data, 1, cart_size, f);
|
||||
fclose(f);
|
||||
free_cart = true;
|
||||
} else {
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
FILE* exe = fopen(exe_path, "rb");
|
||||
if (!exe) {
|
||||
if (free_cart) free(cart_data);
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
fseek(exe, 0, SEEK_END);
|
||||
u32 exe_size = (u32)ftell(exe);
|
||||
fseek(exe, 0, SEEK_SET);
|
||||
|
||||
u8* exe_data = malloc(exe_size);
|
||||
fread(exe_data, 1, exe_size, exe);
|
||||
fclose(exe);
|
||||
|
||||
FILE* out = fopen(output_path, "wb");
|
||||
if (!out) {
|
||||
free(exe_data);
|
||||
if (free_cart) free(cart_data);
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
fwrite(exe_data, 1, exe_size, out);
|
||||
free(exe_data);
|
||||
|
||||
u32 cart_offset = exe_size;
|
||||
fwrite(cart_data, 1, cart_size, out);
|
||||
if (free_cart) free(cart_data);
|
||||
|
||||
pxl8_cart_trailer trailer = {
|
||||
.magic = PXL8_CART_TRAILER_MAGIC,
|
||||
.cart_offset = cart_offset,
|
||||
.cart_size = cart_size
|
||||
};
|
||||
fwrite(&trailer, sizeof(trailer), 1, out);
|
||||
fclose(out);
|
||||
|
||||
chmod(output_path, 0755);
|
||||
|
||||
pxl8_info("Bundle created: %s", output_path);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_cart pxl8_cart;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_cart* pxl8_cart_create(void);
|
||||
void pxl8_cart_destroy(pxl8_cart* cart);
|
||||
|
||||
pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, const char* exe_path);
|
||||
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path);
|
||||
|
||||
pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path);
|
||||
pxl8_result pxl8_cart_load_embedded(pxl8_cart* cart, const char* exe_path);
|
||||
void pxl8_cart_unload(pxl8_cart* cart);
|
||||
pxl8_result pxl8_cart_mount(pxl8_cart* cart);
|
||||
void pxl8_cart_unmount(pxl8_cart* cart);
|
||||
|
||||
pxl8_cart* pxl8_get_cart(void);
|
||||
const char* pxl8_cart_get_base_path(const pxl8_cart* cart);
|
||||
const char* pxl8_cart_get_title(const pxl8_cart* cart);
|
||||
pxl8_resolution pxl8_cart_get_resolution(const pxl8_cart* cart);
|
||||
pxl8_size pxl8_cart_get_window_size(const pxl8_cart* cart);
|
||||
pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart);
|
||||
void pxl8_cart_set_title(pxl8_cart* cart, const char* title);
|
||||
void pxl8_cart_set_resolution(pxl8_cart* cart, pxl8_resolution resolution);
|
||||
void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size);
|
||||
void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode);
|
||||
bool pxl8_cart_is_packed(const pxl8_cart* cart);
|
||||
bool pxl8_cart_has_embedded(const char* exe_path);
|
||||
|
||||
bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path);
|
||||
bool pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path, char* out_path, size_t out_size);
|
||||
pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** data_out, u32* size_out);
|
||||
void pxl8_cart_free_file(u8* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
static inline i32 pxl8_bytes_per_pixel(pxl8_pixel_mode mode) {
|
||||
return (mode == PXL8_PIXEL_HICOLOR) ? 2 : 1;
|
||||
}
|
||||
|
||||
static inline u16 pxl8_rgb565_pack(u8 r, u8 g, u8 b) {
|
||||
return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
|
||||
}
|
||||
|
||||
static inline void pxl8_rgb565_unpack(u16 color, u8* r, u8* g, u8* b) {
|
||||
*r = (color >> 11) << 3;
|
||||
*g = ((color >> 5) & 0x3F) << 2;
|
||||
*b = (color & 0x1F) << 3;
|
||||
}
|
||||
|
||||
static inline u16 pxl8_rgba32_to_rgb565(u32 rgba) {
|
||||
return pxl8_rgb565_pack(rgba & 0xFF, (rgba >> 8) & 0xFF, (rgba >> 16) & 0xFF);
|
||||
}
|
||||
|
||||
static inline u32 pxl8_rgb565_to_rgba32(u16 color) {
|
||||
u8 r, g, b;
|
||||
pxl8_rgb565_unpack(color, &r, &g, &b);
|
||||
return r | (g << 8) | (b << 16) | 0xFF000000;
|
||||
}
|
||||
|
||||
static inline void pxl8_rgba32_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) {
|
||||
*r = color & 0xFF;
|
||||
*g = (color >> 8) & 0xFF;
|
||||
*b = (color >> 16) & 0xFF;
|
||||
*a = (color >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
static inline u32 pxl8_rgba32_pack(u8 r, u8 g, u8 b, u8 a) {
|
||||
return r | (g << 8) | (b << 16) | (a << 24);
|
||||
}
|
||||
|
||||
static inline u8 pxl8_color_lerp_channel(u8 c1, u8 c2, f32 t) {
|
||||
return c1 + (i32)((c2 - c1) * t);
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
#include <string.h>
|
||||
|
||||
static const char embed_fennel[] = {
|
||||
#embed "lib/fennel/fennel.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8[] = {
|
||||
#embed "src/lua/pxl8.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_anim[] = {
|
||||
#embed "src/lua/pxl8/anim.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_core[] = {
|
||||
#embed "src/lua/pxl8/core.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_gfx2d[] = {
|
||||
#embed "src/lua/pxl8/gfx2d.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_gfx3d[] = {
|
||||
#embed "src/lua/pxl8/gfx3d.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_gui[] = {
|
||||
#embed "src/lua/pxl8/gui.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_input[] = {
|
||||
#embed "src/lua/pxl8/input.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_math[] = {
|
||||
#embed "src/lua/pxl8/math.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_particles[] = {
|
||||
#embed "src/lua/pxl8/particles.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_sfx[] = {
|
||||
#embed "src/lua/pxl8/sfx.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_tilemap[] = {
|
||||
#embed "src/lua/pxl8/tilemap.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_transition[] = {
|
||||
#embed "src/lua/pxl8/transition.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_vfx[] = {
|
||||
#embed "src/lua/pxl8/vfx.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_world[] = {
|
||||
#embed "src/lua/pxl8/world.lua"
|
||||
, 0 };
|
||||
|
||||
#define PXL8_EMBED_ENTRY(var, name) {name, var, sizeof(var) - 1}
|
||||
|
||||
typedef struct { const char* name; const char* data; u32 size; } pxl8_embed;
|
||||
|
||||
static const pxl8_embed pxl8_embeds[] = {
|
||||
PXL8_EMBED_ENTRY(embed_fennel, "fennel"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8, "pxl8"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_anim, "pxl8.anim"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_core, "pxl8.core"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_gfx2d, "pxl8.gfx2d"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_gfx3d, "pxl8.gfx3d"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_gui, "pxl8.gui"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_input, "pxl8.input"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_math, "pxl8.math"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_particles, "pxl8.particles"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_sfx, "pxl8.sfx"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_tilemap, "pxl8.tilemap"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_transition, "pxl8.transition"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_vfx, "pxl8.vfx"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_world, "pxl8.world"),
|
||||
{0}
|
||||
};
|
||||
|
||||
static inline const pxl8_embed* pxl8_embed_find(const char* name) {
|
||||
for (const pxl8_embed* e = pxl8_embeds; e->name; e++)
|
||||
if (strcmp(e->name, name) == 0) return e;
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
#include "pxl8_font.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
pxl8_result pxl8_font_create_atlas(const pxl8_font* font, u8** atlas_data, i32* atlas_width, i32* atlas_height) {
|
||||
if (!font || !atlas_data || !atlas_width || !atlas_height) {
|
||||
return PXL8_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
i32 glyphs_per_row = 16;
|
||||
i32 rows_needed = (font->glyph_count + glyphs_per_row - 1) / glyphs_per_row;
|
||||
|
||||
*atlas_width = glyphs_per_row * font->default_width;
|
||||
*atlas_height = rows_needed * font->default_height;
|
||||
|
||||
i32 atlas_size = (*atlas_width) * (*atlas_height);
|
||||
*atlas_data = (u8*)malloc(atlas_size);
|
||||
if (!*atlas_data) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
memset(*atlas_data, 0, atlas_size);
|
||||
|
||||
for (u32 i = 0; i < font->glyph_count; i++) {
|
||||
const pxl8_glyph* glyph = &font->glyphs[i];
|
||||
|
||||
i32 atlas_x = (i % glyphs_per_row) * font->default_width;
|
||||
i32 atlas_y = (i / glyphs_per_row) * font->default_height;
|
||||
|
||||
for (i32 y = 0; y < glyph->height && y < font->default_height; y++) {
|
||||
for (i32 x = 0; x < glyph->width && x < font->default_width; x++) {
|
||||
i32 atlas_idx = (atlas_y + y) * (*atlas_width) + (atlas_x + x);
|
||||
|
||||
if (glyph->format == PXL8_FONT_FORMAT_INDEXED) {
|
||||
u8 pixel_byte = glyph->data.indexed[y];
|
||||
u8 pixel_bit = (pixel_byte >> x) & 1;
|
||||
(*atlas_data)[atlas_idx] = pixel_bit ? 255 : 0;
|
||||
} else {
|
||||
i32 glyph_idx = y * 8 + x;
|
||||
u32 rgba_pixel = glyph->data.rgba[glyph_idx];
|
||||
(*atlas_data)[atlas_idx] = (rgba_pixel >> 24) & 0xFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
const pxl8_glyph* pxl8_font_find_glyph(const pxl8_font* font, u32 codepoint) {
|
||||
if (!font || !font->glyphs) return NULL;
|
||||
|
||||
for (u32 i = 0; i < font->glyph_count; i++) {
|
||||
if (font->glyphs[i].codepoint == codepoint) {
|
||||
return &font->glyphs[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
130
src/pxl8_font.h
130
src/pxl8_font.h
|
|
@ -1,130 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define PXL8_FONT_FORMAT_INDEXED 0
|
||||
#define PXL8_FONT_FORMAT_RGBA 1
|
||||
|
||||
typedef struct pxl8_glyph {
|
||||
u32 codepoint;
|
||||
u8 width;
|
||||
u8 height;
|
||||
u8 format;
|
||||
union {
|
||||
u8 indexed[64];
|
||||
u32 rgba[64];
|
||||
} data;
|
||||
} pxl8_glyph;
|
||||
|
||||
typedef struct pxl8_font {
|
||||
const pxl8_glyph* glyphs;
|
||||
u32 glyph_count;
|
||||
u8 default_width;
|
||||
u8 default_height;
|
||||
} pxl8_font;
|
||||
|
||||
static const pxl8_glyph pxl8_ascii_glyphs[] = {
|
||||
{ 32, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
|
||||
{ 33, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00 } } },
|
||||
{ 34, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
|
||||
{ 35, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00 } } },
|
||||
{ 36, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00 } } },
|
||||
{ 37, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00 } } },
|
||||
{ 38, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00 } } },
|
||||
{ 39, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
|
||||
{ 40, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00 } } },
|
||||
{ 41, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00 } } },
|
||||
{ 42, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00 } } },
|
||||
{ 43, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00 } } },
|
||||
{ 44, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x06, 0x00 } } },
|
||||
{ 45, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00 } } },
|
||||
{ 46, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00 } } },
|
||||
{ 47, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00 } } },
|
||||
{ 48, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00 } } },
|
||||
{ 49, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00 } } },
|
||||
{ 50, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00 } } },
|
||||
{ 51, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00 } } },
|
||||
{ 52, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00 } } },
|
||||
{ 53, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00 } } },
|
||||
{ 54, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00 } } },
|
||||
{ 55, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00 } } },
|
||||
{ 56, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00 } } },
|
||||
{ 57, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00 } } },
|
||||
{ 58, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00 } } },
|
||||
{ 59, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x06, 0x00 } } },
|
||||
{ 60, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00 } } },
|
||||
{ 61, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00 } } },
|
||||
{ 62, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00 } } },
|
||||
{ 63, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00 } } },
|
||||
{ 64, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00 } } },
|
||||
{ 65, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00 } } },
|
||||
{ 66, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00 } } },
|
||||
{ 67, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00 } } },
|
||||
{ 68, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00 } } },
|
||||
{ 69, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00 } } },
|
||||
{ 70, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00 } } },
|
||||
{ 71, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00 } } },
|
||||
{ 72, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00 } } },
|
||||
{ 73, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 } } },
|
||||
{ 74, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00 } } },
|
||||
{ 75, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00 } } },
|
||||
{ 76, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00 } } },
|
||||
{ 77, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00 } } },
|
||||
{ 78, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00 } } },
|
||||
{ 79, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00 } } },
|
||||
{ 80, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00 } } },
|
||||
{ 81, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00 } } },
|
||||
{ 82, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00 } } },
|
||||
{ 83, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00 } } },
|
||||
{ 84, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 } } },
|
||||
{ 85, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00 } } },
|
||||
{ 86, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00 } } },
|
||||
{ 87, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00 } } },
|
||||
{ 88, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00 } } },
|
||||
{ 89, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00 } } },
|
||||
{ 90, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00 } } },
|
||||
{ 97, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00 } } },
|
||||
{ 98, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00 } } },
|
||||
{ 99, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00 } } },
|
||||
{ 100, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x38, 0x30, 0x30, 0x3E, 0x33, 0x33, 0x6E, 0x00 } } },
|
||||
{ 101, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x1E, 0x33, 0x3F, 0x03, 0x1E, 0x00 } } },
|
||||
{ 102, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1C, 0x36, 0x06, 0x0F, 0x06, 0x06, 0x0F, 0x00 } } },
|
||||
{ 103, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F } } },
|
||||
{ 104, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00 } } },
|
||||
{ 105, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 } } },
|
||||
{ 106, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E } } },
|
||||
{ 107, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00 } } },
|
||||
{ 108, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 } } },
|
||||
{ 109, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00 } } },
|
||||
{ 110, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00 } } },
|
||||
{ 111, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00 } } },
|
||||
{ 112, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F } } },
|
||||
{ 113, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78 } } },
|
||||
{ 114, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00 } } },
|
||||
{ 115, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00 } } },
|
||||
{ 116, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00 } } },
|
||||
{ 117, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00 } } },
|
||||
{ 118, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00 } } },
|
||||
{ 119, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00 } } },
|
||||
{ 120, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00 } } },
|
||||
{ 121, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F } } },
|
||||
{ 122, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00 } } }
|
||||
};
|
||||
|
||||
static const pxl8_font pxl8_default_font = {
|
||||
pxl8_ascii_glyphs,
|
||||
sizeof(pxl8_ascii_glyphs) / sizeof(pxl8_glyph),
|
||||
8,
|
||||
8
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_result pxl8_font_create_atlas(const pxl8_font* font, u8** atlas_data, i32* atlas_width, i32* atlas_height);
|
||||
const pxl8_glyph* pxl8_font_find_glyph(const pxl8_font* font, u32 codepoint);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_rng.h"
|
||||
#include "pxl8_script.h"
|
||||
#include "pxl8_sfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_replay pxl8_replay;
|
||||
|
||||
typedef struct pxl8_game {
|
||||
pxl8_gfx* gfx;
|
||||
pxl8_script* script;
|
||||
pxl8_sfx_mixer* mixer;
|
||||
|
||||
pxl8_rng rng;
|
||||
i32 frame_count;
|
||||
u64 last_time;
|
||||
f32 time;
|
||||
|
||||
f32 fps_accumulator;
|
||||
i32 fps_frame_count;
|
||||
f32 fps;
|
||||
|
||||
pxl8_input_state input;
|
||||
pxl8_input_state prev_input;
|
||||
|
||||
#ifndef NDEBUG
|
||||
pxl8_replay* debug_replay;
|
||||
#endif
|
||||
|
||||
bool repl_mode;
|
||||
bool repl_started;
|
||||
bool running;
|
||||
bool script_loaded;
|
||||
char script_path[256];
|
||||
} pxl8_game;
|
||||
504
src/pxl8_gen.c
504
src/pxl8_gen.c
|
|
@ -1,504 +0,0 @@
|
|||
#include "pxl8_gen.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_rng.h"
|
||||
|
||||
typedef struct room_grid {
|
||||
u8* cells;
|
||||
i32 width;
|
||||
i32 height;
|
||||
} room_grid;
|
||||
|
||||
static bool room_grid_init(room_grid* grid, i32 width, i32 height) {
|
||||
grid->width = width;
|
||||
grid->height = height;
|
||||
grid->cells = calloc(width * height, sizeof(u8));
|
||||
|
||||
return grid->cells != NULL;
|
||||
}
|
||||
|
||||
static u8 room_grid_get(const room_grid* grid, i32 x, i32 y) {
|
||||
if (x < 0 || x >= grid->width || y < 0 || y >= grid->height) {
|
||||
return 1;
|
||||
}
|
||||
return grid->cells[y * grid->width + x];
|
||||
}
|
||||
|
||||
static void room_grid_set(room_grid* grid, i32 x, i32 y, u8 value) {
|
||||
if (x < 0 || x >= grid->width || y < 0 || y >= grid->height) {
|
||||
return;
|
||||
}
|
||||
grid->cells[y * grid->width + x] = value;
|
||||
}
|
||||
|
||||
static inline void compute_face_aabb(pxl8_bsp_face* face, const pxl8_bsp_vertex* verts, u32 vert_idx) {
|
||||
face->aabb_min = (pxl8_vec3){1e30f, 1e30f, 1e30f};
|
||||
face->aabb_max = (pxl8_vec3){-1e30f, -1e30f, -1e30f};
|
||||
|
||||
for (u32 i = 0; i < 4; i++) {
|
||||
pxl8_vec3 v = verts[vert_idx + i].position;
|
||||
if (v.x < face->aabb_min.x) face->aabb_min.x = v.x;
|
||||
if (v.x > face->aabb_max.x) face->aabb_max.x = v.x;
|
||||
if (v.y < face->aabb_min.y) face->aabb_min.y = v.y;
|
||||
if (v.y > face->aabb_max.y) face->aabb_max.y = v.y;
|
||||
if (v.z < face->aabb_min.z) face->aabb_min.z = v.z;
|
||||
if (v.z > face->aabb_max.z) face->aabb_max.z = v.z;
|
||||
}
|
||||
}
|
||||
|
||||
static void room_grid_fill(room_grid* grid, u8 value) {
|
||||
for (i32 y = 0; y < grid->height; y++) {
|
||||
for (i32 x = 0; x < grid->width; x++) {
|
||||
room_grid_set(grid, x, y, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
|
||||
i32 vertex_count = 0;
|
||||
i32 face_count = 0;
|
||||
i32 floor_ceiling_count = 0;
|
||||
|
||||
for (i32 y = 0; y < grid->height; y++) {
|
||||
for (i32 x = 0; x < grid->width; x++) {
|
||||
if (room_grid_get(grid, x, y) == 0) {
|
||||
if (room_grid_get(grid, x - 1, y) == 1) face_count++;
|
||||
if (room_grid_get(grid, x + 1, y) == 1) face_count++;
|
||||
if (room_grid_get(grid, x, y - 1) == 1) face_count++;
|
||||
if (room_grid_get(grid, x, y + 1) == 1) face_count++;
|
||||
floor_ceiling_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
face_count += floor_ceiling_count * 2;
|
||||
vertex_count = face_count * 4;
|
||||
|
||||
pxl8_debug("Cave generation: %dx%d grid -> %d faces, %d vertices",
|
||||
grid->width, grid->height, face_count, vertex_count);
|
||||
|
||||
bsp->vertices = calloc(vertex_count, sizeof(pxl8_bsp_vertex));
|
||||
bsp->faces = calloc(face_count, sizeof(pxl8_bsp_face));
|
||||
bsp->planes = calloc(face_count, sizeof(pxl8_bsp_plane));
|
||||
bsp->edges = calloc(vertex_count, sizeof(pxl8_bsp_edge));
|
||||
bsp->surfedges = calloc(vertex_count, sizeof(i32));
|
||||
|
||||
if (!bsp->vertices || !bsp->faces || !bsp->planes || !bsp->edges || !bsp->surfedges) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
bsp->texinfo = NULL;
|
||||
bsp->num_texinfo = 0;
|
||||
|
||||
i32 vert_idx = 0;
|
||||
i32 face_idx = 0;
|
||||
i32 edge_idx = 0;
|
||||
|
||||
const f32 cell_size = 64.0f;
|
||||
const f32 wall_height = 128.0f;
|
||||
|
||||
for (i32 y = 0; y < grid->height; y++) {
|
||||
for (i32 x = 0; x < grid->width; x++) {
|
||||
if (room_grid_get(grid, x, y) == 0) {
|
||||
f32 fx = (f32)x * cell_size;
|
||||
f32 fy = (f32)y * cell_size;
|
||||
|
||||
if (room_grid_get(grid, x - 1, y) == 1) {
|
||||
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
|
||||
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, wall_height, fy};
|
||||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx, wall_height, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, 0, fy + cell_size};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){-1, 0, 0};
|
||||
bsp->planes[face_idx].dist = -fx;
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
|
||||
bsp->surfedges[edge_idx + i] = edge_idx + i;
|
||||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx++;
|
||||
}
|
||||
|
||||
if (room_grid_get(grid, x + 1, y) == 1) {
|
||||
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx + cell_size, 0, fy};
|
||||
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){1, 0, 0};
|
||||
bsp->planes[face_idx].dist = fx + cell_size;
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
|
||||
bsp->surfedges[edge_idx + i] = edge_idx + i;
|
||||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx++;
|
||||
}
|
||||
|
||||
if (room_grid_get(grid, x, y - 1) == 1) {
|
||||
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
|
||||
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, 0, fy};
|
||||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, wall_height, fy};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, -1};
|
||||
bsp->planes[face_idx].dist = -fy;
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
|
||||
bsp->surfedges[edge_idx + i] = edge_idx + i;
|
||||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx++;
|
||||
}
|
||||
|
||||
if (room_grid_get(grid, x, y + 1) == 1) {
|
||||
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, wall_height, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, 1};
|
||||
bsp->planes[face_idx].dist = fy + cell_size;
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
|
||||
bsp->surfedges[edge_idx + i] = edge_idx + i;
|
||||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i32 y = 0; y < grid->height; y++) {
|
||||
for (i32 x = 0; x < grid->width; x++) {
|
||||
if (room_grid_get(grid, x, y) == 0) {
|
||||
f32 fx = (f32)x * cell_size;
|
||||
f32 fy = (f32)y * cell_size;
|
||||
|
||||
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
|
||||
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, 0, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, 0, fy};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){0, 1, 0};
|
||||
bsp->planes[face_idx].dist = 0;
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
|
||||
bsp->surfedges[edge_idx + i] = edge_idx + i;
|
||||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx++;
|
||||
|
||||
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, wall_height, fy};
|
||||
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
|
||||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, wall_height, fy + cell_size};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){0, -1, 0};
|
||||
bsp->planes[face_idx].dist = -wall_height;
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
|
||||
bsp->surfedges[edge_idx + i] = edge_idx + i;
|
||||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bsp->num_vertices = vertex_count;
|
||||
bsp->num_faces = face_count;
|
||||
bsp->num_planes = face_count;
|
||||
bsp->num_edges = vertex_count;
|
||||
bsp->num_surfedges = vertex_count;
|
||||
|
||||
bsp->leafs = calloc(1, sizeof(pxl8_bsp_leaf));
|
||||
bsp->marksurfaces = calloc(face_count, sizeof(u16));
|
||||
|
||||
if (!bsp->leafs || !bsp->marksurfaces) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
bsp->num_leafs = 1;
|
||||
bsp->num_marksurfaces = face_count;
|
||||
|
||||
bsp->leafs[0].first_marksurface = 0;
|
||||
bsp->leafs[0].num_marksurfaces = face_count;
|
||||
bsp->leafs[0].contents = -2;
|
||||
|
||||
for (i32 i = 0; i < face_count; i++) {
|
||||
bsp->marksurfaces[i] = i;
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static bool bounds_intersects(const pxl8_bounds* a, const pxl8_bounds* b) {
|
||||
return !(a->x + a->w <= b->x || b->x + b->w <= a->x ||
|
||||
a->y + a->h <= b->y || b->y + b->h <= a->y);
|
||||
}
|
||||
|
||||
static void carve_corridor_h(room_grid* grid, i32 x1, i32 x2, i32 y) {
|
||||
i32 start = (x1 < x2) ? x1 : x2;
|
||||
i32 end = (x1 > x2) ? x1 : x2;
|
||||
for (i32 x = start; x <= end; x++) {
|
||||
room_grid_set(grid, x, y, 0);
|
||||
room_grid_set(grid, x, y - 1, 0);
|
||||
room_grid_set(grid, x, y + 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void carve_corridor_v(room_grid* grid, i32 y1, i32 y2, i32 x) {
|
||||
i32 start = (y1 < y2) ? y1 : y2;
|
||||
i32 end = (y1 > y2) ? y1 : y2;
|
||||
for (i32 y = start; y <= end; y++) {
|
||||
room_grid_set(grid, x, y, 0);
|
||||
room_grid_set(grid, x - 1, y, 0);
|
||||
room_grid_set(grid, x + 1, y, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static pxl8_result procgen_rooms(pxl8_bsp* bsp, const pxl8_procgen_params* params) {
|
||||
pxl8_debug("procgen_rooms called: %dx%d, seed=%u, min=%d, max=%d, num=%d",
|
||||
params->width, params->height, params->seed,
|
||||
params->min_room_size, params->max_room_size, params->num_rooms);
|
||||
|
||||
pxl8_rng rng;
|
||||
pxl8_rng_seed(&rng, params->seed);
|
||||
|
||||
room_grid grid;
|
||||
if (!room_grid_init(&grid, params->width, params->height)) {
|
||||
pxl8_error("Failed to allocate room grid");
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
room_grid_fill(&grid, 1);
|
||||
|
||||
pxl8_bounds rooms[256];
|
||||
i32 room_count = 0;
|
||||
i32 max_attempts = params->num_rooms * 10;
|
||||
|
||||
for (i32 attempt = 0; attempt < max_attempts && room_count < params->num_rooms && room_count < 256; attempt++) {
|
||||
i32 w = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1));
|
||||
i32 h = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1));
|
||||
i32 x = 1 + (pxl8_rng_next(&rng) % (params->width - w - 2));
|
||||
i32 y = 1 + (pxl8_rng_next(&rng) % (params->height - h - 2));
|
||||
|
||||
pxl8_bounds new_room = {x, y, w, h};
|
||||
|
||||
bool overlaps = false;
|
||||
for (i32 i = 0; i < room_count; i++) {
|
||||
if (bounds_intersects(&new_room, &rooms[i])) {
|
||||
overlaps = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!overlaps) {
|
||||
for (i32 ry = y; ry < y + h; ry++) {
|
||||
for (i32 rx = x; rx < x + w; rx++) {
|
||||
room_grid_set(&grid, rx, ry, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (room_count > 0) {
|
||||
i32 new_cx = x + w / 2;
|
||||
i32 new_cy = y + h / 2;
|
||||
i32 prev_cx = rooms[room_count - 1].x + rooms[room_count - 1].w / 2;
|
||||
i32 prev_cy = rooms[room_count - 1].y + rooms[room_count - 1].h / 2;
|
||||
|
||||
if (pxl8_rng_next(&rng) % 2 == 0) {
|
||||
carve_corridor_h(&grid, prev_cx, new_cx, prev_cy);
|
||||
carve_corridor_v(&grid, prev_cy, new_cy, new_cx);
|
||||
} else {
|
||||
carve_corridor_v(&grid, prev_cy, new_cy, prev_cx);
|
||||
carve_corridor_h(&grid, prev_cx, new_cx, new_cy);
|
||||
}
|
||||
}
|
||||
|
||||
rooms[room_count++] = new_room;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_debug("Room generation: %dx%d grid -> %d rooms created",
|
||||
params->width, params->height, room_count);
|
||||
|
||||
pxl8_result result = grid_to_bsp(bsp, &grid);
|
||||
free(grid.cells);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params) {
|
||||
if (!bsp || !params) {
|
||||
return PXL8_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
switch (params->type) {
|
||||
case PXL8_PROCGEN_ROOMS:
|
||||
return procgen_rooms(bsp, params);
|
||||
|
||||
case PXL8_PROCGEN_TERRAIN:
|
||||
pxl8_error("Terrain generation not yet implemented");
|
||||
return PXL8_ERROR_NOT_INITIALIZED;
|
||||
|
||||
default:
|
||||
pxl8_error("Unknown procgen type: %d", params->type);
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 hash2d(i32 x, i32 y) {
|
||||
u32 h = ((u32)x * 374761393u) + ((u32)y * 668265263u);
|
||||
h ^= h >> 13;
|
||||
h ^= h << 17;
|
||||
h ^= h >> 5;
|
||||
return h;
|
||||
}
|
||||
|
||||
void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params) {
|
||||
if (!buffer || !params) return;
|
||||
|
||||
for (i32 y = 0; y < params->height; y++) {
|
||||
for (i32 x = 0; x < params->width; x++) {
|
||||
f32 u = (f32)x / (f32)params->width;
|
||||
f32 v = (f32)y / (f32)params->height;
|
||||
|
||||
u8 color = params->base_color;
|
||||
|
||||
// Tile-based pattern (floor style)
|
||||
if (params->seed == 11111) {
|
||||
i32 tile_x = (i32)floorf(u * 8.0f);
|
||||
i32 tile_y = (i32)floorf(v * 8.0f);
|
||||
u32 h = hash2d(tile_x, tile_y);
|
||||
|
||||
f32 pattern = (f32)(h & 0xFF) / 255.0f;
|
||||
i32 quantized = (pattern < 0.3f) ? 0 : (pattern < 0.7f) ? 1 : 2;
|
||||
|
||||
color = params->base_color + quantized;
|
||||
|
||||
// Checkerboard dither
|
||||
if (((tile_x + tile_y) & 1) == 0 && (h & 0x100)) {
|
||||
color = (color < 255) ? color + 1 : color;
|
||||
}
|
||||
}
|
||||
// Large tile pattern (ceiling style)
|
||||
else if (params->seed == 22222) {
|
||||
i32 coarse_x = (i32)floorf(u * 2.0f);
|
||||
i32 coarse_y = (i32)floorf(v * 2.0f);
|
||||
u32 coarse_h = hash2d(coarse_x, coarse_y);
|
||||
|
||||
i32 subdivision = (coarse_h >> 8) & 0x3;
|
||||
i32 tile_x, tile_y;
|
||||
|
||||
switch (subdivision) {
|
||||
case 0: tile_x = (i32)floorf(u * 3.0f); tile_y = (i32)floorf(v * 3.0f); break;
|
||||
case 1: tile_x = (i32)floorf(u * 5.0f); tile_y = (i32)floorf(v * 5.0f); break;
|
||||
case 2: tile_x = (i32)floorf(u * 2.0f); tile_y = (i32)floorf(v * 4.0f); break;
|
||||
default: tile_x = (i32)floorf(u * 4.0f); tile_y = (i32)floorf(v * 2.0f); break;
|
||||
}
|
||||
|
||||
u32 h = hash2d(tile_x, tile_y);
|
||||
f32 pattern = (f32)(h & 0xFF) / 255.0f;
|
||||
|
||||
if (pattern < 0.25f) color = params->base_color;
|
||||
else if (pattern < 0.50f) color = params->base_color + 1;
|
||||
else if (pattern < 0.75f) color = params->base_color + 2;
|
||||
else color = params->base_color + 3;
|
||||
}
|
||||
// Brick pattern (wall style)
|
||||
else {
|
||||
f32 brick_y = floorf(v * 4.0f);
|
||||
f32 offset = ((i32)brick_y & 1) ? 0.5f : 0.0f;
|
||||
i32 brick_x = (i32)floorf(u * 4.0f + offset);
|
||||
brick_y = (i32)brick_y;
|
||||
|
||||
f32 brick_u = fabsf((u * 4.0f + offset) - floorf(u * 4.0f + offset) - 0.5f);
|
||||
f32 brick_v = fabsf((v * 4.0f) - floorf(v * 4.0f) - 0.5f);
|
||||
|
||||
u32 h = hash2d(brick_x, (i32)brick_y);
|
||||
f32 noise = (f32)(h & 0xFF) / 255.0f;
|
||||
|
||||
// Mortar lines
|
||||
if (brick_u > 0.47f || brick_v > 0.47f) {
|
||||
color = params->base_color - 2;
|
||||
} else {
|
||||
i32 shade = (i32)(noise * 3.0f);
|
||||
color = params->base_color + shade;
|
||||
}
|
||||
}
|
||||
|
||||
buffer[y * params->width + x] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_bsp.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef enum pxl8_procgen_type {
|
||||
PXL8_PROCGEN_ROOMS,
|
||||
PXL8_PROCGEN_TERRAIN
|
||||
} pxl8_procgen_type;
|
||||
|
||||
typedef struct pxl8_procgen_params {
|
||||
pxl8_procgen_type type;
|
||||
|
||||
i32 width;
|
||||
i32 height;
|
||||
i32 depth;
|
||||
u32 seed;
|
||||
|
||||
i32 min_room_size;
|
||||
i32 max_room_size;
|
||||
i32 num_rooms;
|
||||
} pxl8_procgen_params;
|
||||
|
||||
typedef struct pxl8_procgen_tex_params {
|
||||
char name[16];
|
||||
u32 seed;
|
||||
i32 width;
|
||||
i32 height;
|
||||
f32 scale;
|
||||
f32 roughness;
|
||||
u8 base_color;
|
||||
u8 variation;
|
||||
u8 max_color;
|
||||
} pxl8_procgen_tex_params;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params);
|
||||
void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
1567
src/pxl8_gfx.c
1567
src/pxl8_gfx.c
File diff suppressed because it is too large
Load diff
|
|
@ -1,86 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_hal.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_gfx pxl8_gfx;
|
||||
|
||||
typedef struct pxl8_vertex {
|
||||
u32 color;
|
||||
pxl8_vec3 normal;
|
||||
pxl8_vec3 position;
|
||||
f32 u, v;
|
||||
} pxl8_vertex;
|
||||
|
||||
typedef struct pxl8_triangle {
|
||||
pxl8_vertex v[3];
|
||||
u32 texture_id;
|
||||
} pxl8_triangle;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_pixel_mode mode, pxl8_resolution resolution);
|
||||
void pxl8_gfx_destroy(pxl8_gfx* gfx);
|
||||
|
||||
void pxl8_gfx_present(pxl8_gfx* gfx);
|
||||
void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt);
|
||||
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx);
|
||||
|
||||
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
|
||||
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);
|
||||
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx);
|
||||
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx);
|
||||
u32 pxl8_gfx_get_palette_size(const pxl8_gfx* gfx);
|
||||
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx);
|
||||
i32 pxl8_gfx_get_width(const pxl8_gfx* gfx);
|
||||
|
||||
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
|
||||
void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp);
|
||||
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height);
|
||||
|
||||
void pxl8_gfx_clear_textures(pxl8_gfx* gfx);
|
||||
pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height);
|
||||
pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx);
|
||||
pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path);
|
||||
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path);
|
||||
|
||||
i32 pxl8_gfx_add_palette_cycle(pxl8_gfx* gfx, u8 start_index, u8 end_index, f32 speed);
|
||||
void pxl8_gfx_clear_palette_cycles(pxl8_gfx* gfx);
|
||||
void pxl8_gfx_color_ramp(pxl8_gfx* gfx, u8 start, u8 count, u32 from_color, u32 to_color);
|
||||
void pxl8_gfx_cycle_palette(pxl8_gfx* gfx, u8 start, u8 count, i32 step);
|
||||
void pxl8_gfx_fade_palette(pxl8_gfx* gfx, u8 start, u8 count, f32 amount, u32 target_color);
|
||||
void pxl8_gfx_interpolate_palettes(pxl8_gfx* gfx, u32* palette1, u32* palette2, u8 start, u8 count, f32 t);
|
||||
void pxl8_gfx_remove_palette_cycle(pxl8_gfx* gfx, i32 cycle_id);
|
||||
void pxl8_gfx_set_palette_cycle_speed(pxl8_gfx* gfx, i32 cycle_id, f32 speed);
|
||||
void pxl8_gfx_swap_palette(pxl8_gfx* gfx, u8 start, u8 count, u32* new_colors);
|
||||
|
||||
void pxl8_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color);
|
||||
void pxl8_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color);
|
||||
void pxl8_clear(pxl8_gfx* gfx, u32 color);
|
||||
u32 pxl8_get_pixel(pxl8_gfx* gfx, i32 x, i32 y);
|
||||
void pxl8_line(pxl8_gfx* gfx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color);
|
||||
void pxl8_pixel(pxl8_gfx* gfx, i32 x, i32 y, u32 color);
|
||||
void pxl8_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color);
|
||||
void pxl8_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color);
|
||||
void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y);
|
||||
void pxl8_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color);
|
||||
|
||||
void pxl8_3d_clear_zbuffer(pxl8_gfx* gfx);
|
||||
void pxl8_3d_draw_line_3d(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u32 color);
|
||||
void pxl8_3d_draw_triangle(pxl8_gfx* gfx, pxl8_triangle tri);
|
||||
void pxl8_3d_draw_triangle_raw(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, u32 color);
|
||||
void pxl8_3d_draw_triangle_textured(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, f32 u0, f32 v0f, f32 u1, f32 v1f, f32 u2, f32 v2f, u32 texture_id);
|
||||
const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx);
|
||||
void pxl8_3d_set_affine_textures(pxl8_gfx* gfx, bool affine);
|
||||
void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling);
|
||||
void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat);
|
||||
void pxl8_3d_set_projection(pxl8_gfx* gfx, pxl8_mat4 mat);
|
||||
void pxl8_3d_set_view(pxl8_gfx* gfx, pxl8_mat4 mat);
|
||||
void pxl8_3d_set_wireframe(pxl8_gfx* gfx, bool wireframe);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
131
src/pxl8_gui.c
131
src/pxl8_gui.c
|
|
@ -1,131 +0,0 @@
|
|||
#include "pxl8_gui.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
pxl8_gui_state* pxl8_gui_state_create(void) {
|
||||
pxl8_gui_state* state = (pxl8_gui_state*)malloc(sizeof(pxl8_gui_state));
|
||||
if (!state) return NULL;
|
||||
|
||||
memset(state, 0, sizeof(pxl8_gui_state));
|
||||
return state;
|
||||
}
|
||||
|
||||
void pxl8_gui_state_destroy(pxl8_gui_state* state) {
|
||||
if (!state) return;
|
||||
free(state);
|
||||
}
|
||||
|
||||
void pxl8_gui_begin_frame(pxl8_gui_state* state) {
|
||||
if (!state) return;
|
||||
state->hot_id = 0;
|
||||
}
|
||||
|
||||
void pxl8_gui_end_frame(pxl8_gui_state* state) {
|
||||
if (!state) return;
|
||||
|
||||
if (!state->cursor_down) {
|
||||
state->active_id = 0;
|
||||
}
|
||||
state->cursor_clicked = false;
|
||||
}
|
||||
|
||||
void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y) {
|
||||
if (!state) return;
|
||||
state->cursor_x = x;
|
||||
state->cursor_y = y;
|
||||
}
|
||||
|
||||
void pxl8_gui_cursor_down(pxl8_gui_state* state) {
|
||||
if (!state) return;
|
||||
state->cursor_down = true;
|
||||
}
|
||||
|
||||
void pxl8_gui_cursor_up(pxl8_gui_state* state) {
|
||||
if (!state) return;
|
||||
state->cursor_down = false;
|
||||
state->cursor_clicked = true;
|
||||
}
|
||||
|
||||
static bool is_cursor_over(const pxl8_gui_state* state, i32 x, i32 y, i32 w, i32 h) {
|
||||
return state->cursor_x >= x && state->cursor_x < (x + w) &&
|
||||
state->cursor_y >= y && state->cursor_y < (y + h);
|
||||
}
|
||||
|
||||
bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label) {
|
||||
if (!state || !gfx || !label) return false;
|
||||
|
||||
bool cursor_over = is_cursor_over(state, x, y, w, h);
|
||||
bool is_hot = (state->hot_id == id);
|
||||
bool is_active = (state->active_id == id);
|
||||
|
||||
if (cursor_over) {
|
||||
state->hot_id = id;
|
||||
}
|
||||
|
||||
if (cursor_over && state->cursor_down && state->active_id == 0) {
|
||||
state->active_id = id;
|
||||
}
|
||||
|
||||
bool clicked = is_active && state->cursor_clicked && cursor_over;
|
||||
if (clicked) {
|
||||
state->active_id = 0;
|
||||
}
|
||||
|
||||
u8 bg_color;
|
||||
u8 border_color;
|
||||
i32 offset_x = 0;
|
||||
i32 offset_y = 0;
|
||||
|
||||
if (is_active) {
|
||||
bg_color = 4;
|
||||
border_color = 3;
|
||||
offset_x = 1;
|
||||
offset_y = 1;
|
||||
} else if (is_hot || cursor_over) {
|
||||
bg_color = 4;
|
||||
border_color = 8;
|
||||
} else {
|
||||
bg_color = 3;
|
||||
border_color = 4;
|
||||
}
|
||||
|
||||
pxl8_rect_fill(gfx, x, y, w, h, bg_color);
|
||||
pxl8_rect(gfx, x, y, w, h, border_color);
|
||||
|
||||
i32 text_len = (i32)strlen(label);
|
||||
i32 text_x = x + (w / 2) - ((text_len * 8) / 2) + offset_x;
|
||||
i32 text_y = y + (h / 2) - 5 + offset_y;
|
||||
pxl8_text(gfx, label, text_x, text_y, 6);
|
||||
|
||||
return clicked;
|
||||
}
|
||||
|
||||
void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title) {
|
||||
if (!gfx || !title) return;
|
||||
|
||||
pxl8_rect_fill(gfx, x, y, w, 28, 1);
|
||||
pxl8_rect_fill(gfx, x, y + 28, w, h - 28, 2);
|
||||
pxl8_rect(gfx, x, y, w, h, 4);
|
||||
pxl8_rect_fill(gfx, x, y + 28, w, 1, 4);
|
||||
|
||||
i32 title_x = x + 10;
|
||||
i32 title_y = y + (28 / 2) - 5;
|
||||
pxl8_text(gfx, title, title_x, title_y, 8);
|
||||
}
|
||||
|
||||
void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color) {
|
||||
if (!gfx || !text) return;
|
||||
pxl8_text(gfx, text, x, y, color);
|
||||
}
|
||||
|
||||
bool pxl8_gui_is_hovering(const pxl8_gui_state* state) {
|
||||
if (!state) return false;
|
||||
return state->hot_id != 0;
|
||||
}
|
||||
|
||||
void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y) {
|
||||
if (!state) return;
|
||||
if (x) *x = state->cursor_x;
|
||||
if (y) *y = state->cursor_y;
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct {
|
||||
i32 cursor_x;
|
||||
i32 cursor_y;
|
||||
bool cursor_down;
|
||||
bool cursor_clicked;
|
||||
u32 hot_id;
|
||||
u32 active_id;
|
||||
} pxl8_gui_state;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_gui_state* pxl8_gui_state_create(void);
|
||||
void pxl8_gui_state_destroy(pxl8_gui_state* state);
|
||||
|
||||
void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);
|
||||
bool pxl8_gui_is_hovering(const pxl8_gui_state* state);
|
||||
|
||||
void pxl8_gui_begin_frame(pxl8_gui_state* state);
|
||||
void pxl8_gui_end_frame(pxl8_gui_state* state);
|
||||
|
||||
void pxl8_gui_cursor_down(pxl8_gui_state* state);
|
||||
void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);
|
||||
void pxl8_gui_cursor_up(pxl8_gui_state* state);
|
||||
|
||||
bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label);
|
||||
void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);
|
||||
void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_hal {
|
||||
void* (*create)(i32 render_w, i32 render_h,
|
||||
const char* title, i32 win_w, i32 win_h);
|
||||
void (*destroy)(void* platform_data);
|
||||
u64 (*get_ticks)(void);
|
||||
void (*center_cursor)(void* platform_data);
|
||||
void (*present)(void* platform_data);
|
||||
void (*set_cursor)(void* platform_data, u32 cursor);
|
||||
void (*set_relative_mouse_mode)(void* platform_data, bool enabled);
|
||||
void (*upload_texture)(void* platform_data, const u8* pixels, u32 w, u32 h,
|
||||
const u32* palette, u32 bpp);
|
||||
|
||||
void* (*audio_create)(i32 sample_rate, i32 channels);
|
||||
void (*audio_destroy)(void* audio_handle);
|
||||
void (*audio_start)(void* audio_handle);
|
||||
void (*audio_stop)(void* audio_handle);
|
||||
bool (*upload_audio)(void* audio_handle, const f32* stereo_samples, i32 sample_count);
|
||||
i32 (*audio_queued)(void* audio_handle);
|
||||
} pxl8_hal;
|
||||
221
src/pxl8_io.c
221
src/pxl8_io.c
|
|
@ -1,221 +0,0 @@
|
|||
#include "pxl8_io.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_cart.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
static inline char pxl8_to_lower(char c) {
|
||||
return (c >= 'A' && c <= 'Z') ? c + 32 : c;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
|
||||
if (!path || !content || !size) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
pxl8_cart* cart = pxl8_get_cart();
|
||||
if (cart && pxl8_cart_is_packed(cart)) {
|
||||
u8* data = NULL;
|
||||
u32 cart_size = 0;
|
||||
pxl8_result result = pxl8_cart_read_file(cart, path, &data, &cart_size);
|
||||
if (result == PXL8_OK) {
|
||||
*content = realloc(data, cart_size + 1);
|
||||
if (!*content) {
|
||||
pxl8_cart_free_file(data);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
(*content)[cart_size] = '\0';
|
||||
*size = cart_size;
|
||||
return PXL8_OK;
|
||||
}
|
||||
}
|
||||
|
||||
FILE* file = fopen(path, "rb");
|
||||
if (!file) {
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
long file_size = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
if (file_size < 0) {
|
||||
fclose(file);
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
*content = malloc(file_size + 1);
|
||||
if (!*content) {
|
||||
fclose(file);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
size_t bytes_read = fread(*content, 1, file_size, file);
|
||||
(*content)[bytes_read] = '\0';
|
||||
*size = bytes_read;
|
||||
|
||||
fclose(file);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size) {
|
||||
if (!path || !content) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
FILE* file = fopen(path, "wb");
|
||||
if (!file) {
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
size_t bytes_written = fwrite(content, 1, size, file);
|
||||
fclose(file);
|
||||
|
||||
return (bytes_written == size) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, size_t* size) {
|
||||
return pxl8_io_read_file(path, (char**)data, size);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size) {
|
||||
return pxl8_io_write_file(path, (const char*)data, size);
|
||||
}
|
||||
|
||||
bool pxl8_io_file_exists(const char* path) {
|
||||
if (!path) return false;
|
||||
|
||||
struct stat st;
|
||||
return stat(path, &st) == 0;
|
||||
}
|
||||
|
||||
f64 pxl8_io_get_file_modified_time(const char* path) {
|
||||
if (!path) return 0.0;
|
||||
|
||||
struct stat st;
|
||||
if (stat(path, &st) == 0) {
|
||||
return st.st_mtime;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_io_create_directory(const char* path) {
|
||||
if (!path) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (mkdir(path) != 0) {
|
||||
#else
|
||||
if (mkdir(path, 0755) != 0) {
|
||||
#endif
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_io_free_file_content(char* content) {
|
||||
if (content) {
|
||||
free(content);
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_io_free_binary_data(u8* data) {
|
||||
if (data) {
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
static i32 pxl8_key_code(const char* key_name) {
|
||||
if (!key_name || !key_name[0]) return 0;
|
||||
|
||||
typedef struct { const char* name; i32 code; } KeyMapping;
|
||||
static const KeyMapping keys[] = {
|
||||
{"a", 4}, {"b", 5}, {"c", 6}, {"d", 7}, {"e", 8}, {"f", 9}, {"g", 10}, {"h", 11},
|
||||
{"i", 12}, {"j", 13}, {"k", 14}, {"l", 15}, {"m", 16}, {"n", 17}, {"o", 18}, {"p", 19},
|
||||
{"q", 20}, {"r", 21}, {"s", 22}, {"t", 23}, {"u", 24}, {"v", 25}, {"w", 26}, {"x", 27},
|
||||
{"y", 28}, {"z", 29},
|
||||
{"1", 30}, {"2", 31}, {"3", 32}, {"4", 33}, {"5", 34},
|
||||
{"6", 35}, {"7", 36}, {"8", 37}, {"9", 38}, {"0", 39},
|
||||
{"return", 40}, {"escape", 41}, {"backspace", 42}, {"tab", 43}, {"space", 44},
|
||||
{"-", 45}, {"=", 46}, {"`", 53},
|
||||
{"left", 80}, {"right", 79}, {"up", 82}, {"down", 81},
|
||||
{"f1", 58}, {"f2", 59}, {"f3", 60}, {"f4", 61}, {"f5", 62}, {"f6", 63},
|
||||
{"f7", 64}, {"f8", 65}, {"f9", 66}, {"f10", 67}, {"f11", 68}, {"f12", 69},
|
||||
{"capslock", 57}, {"lshift", 225}, {"rshift", 229},
|
||||
{"lctrl", 224}, {"rctrl", 228}, {"lalt", 226}, {"ralt", 230},
|
||||
{NULL, 0}
|
||||
};
|
||||
|
||||
char lower_name[64];
|
||||
size_t i;
|
||||
for (i = 0; i < sizeof(lower_name) - 1 && key_name[i]; i++) {
|
||||
lower_name[i] = pxl8_to_lower(key_name[i]);
|
||||
}
|
||||
lower_name[i] = '\0';
|
||||
|
||||
for (i = 0; keys[i].name; i++) {
|
||||
if (strcmp(lower_name, keys[i].name) == 0) {
|
||||
return keys[i].code;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool pxl8_key_down(const pxl8_input_state* input, const char* key_name) {
|
||||
if (!input) return false;
|
||||
i32 key = pxl8_key_code(key_name);
|
||||
if (key < 0 || key >= PXL8_MAX_KEYS) return false;
|
||||
return input->keys_down[key];
|
||||
}
|
||||
|
||||
bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name) {
|
||||
if (!input) return false;
|
||||
i32 key = pxl8_key_code(key_name);
|
||||
if (key < 0 || key >= PXL8_MAX_KEYS) return false;
|
||||
return input->keys_pressed[key];
|
||||
}
|
||||
|
||||
bool pxl8_key_released(const pxl8_input_state* input, const char* key_name) {
|
||||
if (!input) return false;
|
||||
i32 key = pxl8_key_code(key_name);
|
||||
if (key < 0 || key >= PXL8_MAX_KEYS) return false;
|
||||
return input->keys_released[key];
|
||||
}
|
||||
|
||||
i32 pxl8_mouse_wheel_x(const pxl8_input_state* input) {
|
||||
if (!input) return 0;
|
||||
return input->mouse_wheel_x;
|
||||
}
|
||||
|
||||
i32 pxl8_mouse_wheel_y(const pxl8_input_state* input) {
|
||||
if (!input) return 0;
|
||||
return input->mouse_wheel_y;
|
||||
}
|
||||
|
||||
bool pxl8_mouse_pressed(const pxl8_input_state* input, i32 button) {
|
||||
if (!input || button < 1 || button > 3) return false;
|
||||
return input->mouse_buttons_pressed[button - 1];
|
||||
}
|
||||
|
||||
bool pxl8_mouse_released(const pxl8_input_state* input, i32 button) {
|
||||
if (!input || button < 1 || button > 3) return false;
|
||||
return input->mouse_buttons_released[button - 1];
|
||||
}
|
||||
|
||||
i32 pxl8_mouse_x(const pxl8_input_state* input) {
|
||||
if (!input) return 0;
|
||||
return input->mouse_x;
|
||||
}
|
||||
|
||||
i32 pxl8_mouse_y(const pxl8_input_state* input) {
|
||||
if (!input) return 0;
|
||||
return input->mouse_y;
|
||||
}
|
||||
|
||||
i32 pxl8_mouse_dx(const pxl8_input_state* input) {
|
||||
if (!input) return 0;
|
||||
return input->mouse_dx;
|
||||
}
|
||||
|
||||
i32 pxl8_mouse_dy(const pxl8_input_state* input) {
|
||||
if (!input) return 0;
|
||||
return input->mouse_dy;
|
||||
}
|
||||
126
src/pxl8_io.h
126
src/pxl8_io.h
|
|
@ -1,126 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct {
|
||||
const u8* bytes;
|
||||
u32 offset;
|
||||
u32 size;
|
||||
bool overflow;
|
||||
} pxl8_stream;
|
||||
|
||||
static inline pxl8_stream pxl8_stream_create(const u8* bytes, u32 size) {
|
||||
return (pxl8_stream){
|
||||
.bytes = bytes,
|
||||
.offset = 0,
|
||||
.size = size,
|
||||
.overflow = false
|
||||
};
|
||||
}
|
||||
|
||||
static inline bool pxl8_stream_can_read(const pxl8_stream* stream, u32 count) {
|
||||
return !stream->overflow && stream->offset + count <= stream->size;
|
||||
}
|
||||
|
||||
static inline bool pxl8_stream_has_overflow(const pxl8_stream* stream) {
|
||||
return stream->overflow;
|
||||
}
|
||||
|
||||
static inline void pxl8_stream_seek(pxl8_stream* stream, u32 offset) {
|
||||
stream->offset = offset;
|
||||
}
|
||||
|
||||
static inline u32 pxl8_stream_position(const pxl8_stream* stream) {
|
||||
return stream->offset;
|
||||
}
|
||||
|
||||
static inline u8 pxl8_read_u8(pxl8_stream* stream) {
|
||||
if (stream->offset + 1 > stream->size) { stream->overflow = true; return 0; }
|
||||
return stream->bytes[stream->offset++];
|
||||
}
|
||||
|
||||
static inline u16 pxl8_read_u16(pxl8_stream* stream) {
|
||||
if (stream->offset + 2 > stream->size) { stream->overflow = true; return 0; }
|
||||
u16 val = (u16)stream->bytes[stream->offset] | ((u16)stream->bytes[stream->offset + 1] << 8);
|
||||
stream->offset += 2;
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline u32 pxl8_read_u32(pxl8_stream* stream) {
|
||||
if (stream->offset + 4 > stream->size) { stream->overflow = true; return 0; }
|
||||
u32 val = (u32)stream->bytes[stream->offset] |
|
||||
((u32)stream->bytes[stream->offset + 1] << 8) |
|
||||
((u32)stream->bytes[stream->offset + 2] << 16) |
|
||||
((u32)stream->bytes[stream->offset + 3] << 24);
|
||||
stream->offset += 4;
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline i16 pxl8_read_i16(pxl8_stream* stream) {
|
||||
return (i16)pxl8_read_u16(stream);
|
||||
}
|
||||
|
||||
static inline i32 pxl8_read_i32(pxl8_stream* stream) {
|
||||
return (i32)pxl8_read_u32(stream);
|
||||
}
|
||||
|
||||
static inline f32 pxl8_read_f32(pxl8_stream* stream) {
|
||||
u32 val = pxl8_read_u32(stream);
|
||||
f32 result;
|
||||
memcpy(&result, &val, sizeof(f32));
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline void pxl8_read_bytes(pxl8_stream* stream, void* dest, u32 count) {
|
||||
if (stream->offset + count > stream->size) { stream->overflow = true; return; }
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
((u8*)dest)[i] = stream->bytes[stream->offset++];
|
||||
}
|
||||
}
|
||||
|
||||
static inline void pxl8_skip_bytes(pxl8_stream* stream, u32 count) {
|
||||
if (stream->offset + count > stream->size) { stream->overflow = true; return; }
|
||||
stream->offset += count;
|
||||
}
|
||||
|
||||
static inline const u8* pxl8_read_ptr(pxl8_stream* stream, u32 count) {
|
||||
if (stream->offset + count > stream->size) { stream->overflow = true; return NULL; }
|
||||
const u8* ptr = &stream->bytes[stream->offset];
|
||||
stream->offset += count;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_result pxl8_io_create_directory(const char* path);
|
||||
bool pxl8_io_file_exists(const char* path);
|
||||
void pxl8_io_free_binary_data(u8* data);
|
||||
void pxl8_io_free_file_content(char* content);
|
||||
f64 pxl8_io_get_file_modified_time(const char* path);
|
||||
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, size_t* size);
|
||||
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size);
|
||||
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size);
|
||||
pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size);
|
||||
|
||||
bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);
|
||||
bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);
|
||||
bool pxl8_key_released(const pxl8_input_state* input, const char* key_name);
|
||||
|
||||
i32 pxl8_mouse_dx(const pxl8_input_state* input);
|
||||
i32 pxl8_mouse_dy(const pxl8_input_state* input);
|
||||
bool pxl8_mouse_pressed(const pxl8_input_state* input, i32 button);
|
||||
bool pxl8_mouse_released(const pxl8_input_state* input, i32 button);
|
||||
i32 pxl8_mouse_wheel_x(const pxl8_input_state* input);
|
||||
i32 pxl8_mouse_wheel_y(const pxl8_input_state* input);
|
||||
i32 pxl8_mouse_x(const pxl8_input_state* input);
|
||||
i32 pxl8_mouse_y(const pxl8_input_state* input);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
113
src/pxl8_log.c
113
src/pxl8_log.c
|
|
@ -1,113 +0,0 @@
|
|||
#include "pxl8_log.h"
|
||||
#include "pxl8_repl.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#define PXL8_LOG_COLOR_ERROR "\033[38;2;251;73;52m"
|
||||
#define PXL8_LOG_COLOR_WARN "\033[38;2;250;189;47m"
|
||||
#define PXL8_LOG_COLOR_INFO "\033[38;2;184;187;38m"
|
||||
#define PXL8_LOG_COLOR_DEBUG "\033[38;2;131;165;152m"
|
||||
#define PXL8_LOG_COLOR_TRACE "\033[38;2;211;134;155m"
|
||||
#define PXL8_LOG_COLOR_RESET "\033[0m"
|
||||
|
||||
static pxl8_log* g_log = NULL;
|
||||
|
||||
void pxl8_log_init(pxl8_log* log) {
|
||||
g_log = log;
|
||||
g_log->level = PXL8_LOG_LEVEL_DEBUG;
|
||||
|
||||
const char* env_level = getenv("PXL8_LOG_LEVEL");
|
||||
if (env_level) {
|
||||
if (strcmp(env_level, "trace") == 0) g_log->level = PXL8_LOG_LEVEL_TRACE;
|
||||
else if (strcmp(env_level, "debug") == 0) g_log->level = PXL8_LOG_LEVEL_DEBUG;
|
||||
else if (strcmp(env_level, "info") == 0) g_log->level = PXL8_LOG_LEVEL_INFO;
|
||||
else if (strcmp(env_level, "warn") == 0) g_log->level = PXL8_LOG_LEVEL_WARN;
|
||||
else if (strcmp(env_level, "error") == 0) g_log->level = PXL8_LOG_LEVEL_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_log_set_level(pxl8_log_level level) {
|
||||
if (g_log) g_log->level = level;
|
||||
}
|
||||
|
||||
static void log_timestamp(char* buffer, size_t size) {
|
||||
time_t now = time(NULL);
|
||||
struct tm* tm_info = localtime(&now);
|
||||
strftime(buffer, size, "%H:%M:%S", tm_info);
|
||||
}
|
||||
|
||||
static void log_output(const char* color, const char* level,
|
||||
const char* file, int line, const char* fmt, va_list args) {
|
||||
char buffer[4096];
|
||||
char timestamp[16];
|
||||
log_timestamp(timestamp, sizeof(timestamp));
|
||||
|
||||
int pos = 0;
|
||||
if (file) {
|
||||
pos = snprintf(buffer, sizeof(buffer), "%s[%s %s]" PXL8_LOG_COLOR_RESET " %s:%d: ",
|
||||
color, timestamp, level, file, line);
|
||||
} else {
|
||||
pos = snprintf(buffer, sizeof(buffer), "%s[%s %s]" PXL8_LOG_COLOR_RESET " ",
|
||||
color, timestamp, level);
|
||||
}
|
||||
|
||||
if (pos > 0 && pos < (int)sizeof(buffer)) {
|
||||
vsnprintf(buffer + pos, sizeof(buffer) - pos, fmt, args);
|
||||
}
|
||||
|
||||
strncat(buffer, "\n", sizeof(buffer) - strlen(buffer) - 1);
|
||||
|
||||
if (!pxl8_repl_push_log(buffer)) {
|
||||
printf("%s", buffer);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_log_write_trace(const char* file, int line, const char* fmt, ...) {
|
||||
if (!g_log || g_log->level > PXL8_LOG_LEVEL_TRACE) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
log_output(PXL8_LOG_COLOR_TRACE, "TRACE", file, line, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void pxl8_log_write_debug(const char* file, int line, const char* fmt, ...) {
|
||||
if (!g_log || g_log->level > PXL8_LOG_LEVEL_DEBUG) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
log_output(PXL8_LOG_COLOR_DEBUG, "DEBUG", file, line, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void pxl8_log_write_info(const char* fmt, ...) {
|
||||
if (!g_log || g_log->level > PXL8_LOG_LEVEL_INFO) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
log_output(PXL8_LOG_COLOR_INFO, "INFO", NULL, 0, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void pxl8_log_write_warn(const char* file, int line, const char* fmt, ...) {
|
||||
if (!g_log || g_log->level > PXL8_LOG_LEVEL_WARN) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
log_output(PXL8_LOG_COLOR_WARN, "WARN", file, line, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void pxl8_log_write_error(const char* file, int line, const char* fmt, ...) {
|
||||
if (!g_log || g_log->level > PXL8_LOG_LEVEL_ERROR) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
log_output(PXL8_LOG_COLOR_ERROR, "ERROR", file, line, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
PXL8_LOG_LEVEL_TRACE = 0,
|
||||
PXL8_LOG_LEVEL_DEBUG = 1,
|
||||
PXL8_LOG_LEVEL_INFO = 2,
|
||||
PXL8_LOG_LEVEL_WARN = 3,
|
||||
PXL8_LOG_LEVEL_ERROR = 4,
|
||||
} pxl8_log_level;
|
||||
|
||||
typedef struct pxl8_log {
|
||||
pxl8_log_level level;
|
||||
} pxl8_log;
|
||||
|
||||
void pxl8_log_init(pxl8_log* log);
|
||||
void pxl8_log_set_level(pxl8_log_level level);
|
||||
|
||||
void pxl8_log_write_trace(const char* file, int line, const char* fmt, ...);
|
||||
void pxl8_log_write_debug(const char* file, int line, const char* fmt, ...);
|
||||
void pxl8_log_write_info(const char* fmt, ...);
|
||||
void pxl8_log_write_warn(const char* file, int line, const char* fmt, ...);
|
||||
void pxl8_log_write_error(const char* file, int line, const char* fmt, ...);
|
||||
|
||||
#define pxl8_trace(...) pxl8_log_write_trace(__FILE__, __LINE__, __VA_ARGS__)
|
||||
#define pxl8_debug(...) pxl8_log_write_debug(__FILE__, __LINE__, __VA_ARGS__)
|
||||
#define pxl8_info(...) pxl8_log_write_info(__VA_ARGS__)
|
||||
#define pxl8_warn(...) pxl8_log_write_warn(__FILE__, __LINE__, __VA_ARGS__)
|
||||
#define pxl8_error(...) pxl8_log_write_error(__FILE__, __LINE__, __VA_ARGS__)
|
||||
#define pxl8_script_error(...) pxl8_log_write_error(NULL, 0, __VA_ARGS__)
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#ifndef pxl8_min
|
||||
#define pxl8_min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#ifndef pxl8_max
|
||||
#define pxl8_max(a, b) ((a) > (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#ifndef pxl8_strncpy
|
||||
#define pxl8_strncpy(dst, src, size) do { \
|
||||
strncpy((dst), (src), (size) - 1); \
|
||||
(dst)[(size) - 1] = '\0'; \
|
||||
} while (0)
|
||||
#endif
|
||||
294
src/pxl8_math.c
294
src/pxl8_math.c
|
|
@ -1,294 +0,0 @@
|
|||
#include "pxl8_math.h"
|
||||
|
||||
pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b) {
|
||||
return (pxl8_vec2){
|
||||
.x = a.x + b.x,
|
||||
.y = a.y + b.y,
|
||||
};
|
||||
}
|
||||
|
||||
pxl8_vec2 pxl8_vec2_sub(pxl8_vec2 a, pxl8_vec2 b) {
|
||||
return (pxl8_vec2){
|
||||
.x = a.x - b.x,
|
||||
.y = a.y - b.y,
|
||||
};
|
||||
}
|
||||
|
||||
pxl8_vec2 pxl8_vec2_scale(pxl8_vec2 v, f32 s) {
|
||||
return (pxl8_vec2){
|
||||
.x = v.x * s,
|
||||
.y = v.y * s,
|
||||
};
|
||||
}
|
||||
|
||||
f32 pxl8_vec2_dot(pxl8_vec2 a, pxl8_vec2 b) {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
f32 pxl8_vec2_length(pxl8_vec2 v) {
|
||||
return sqrtf(v.x * v.x + v.y * v.y);
|
||||
}
|
||||
|
||||
pxl8_vec2 pxl8_vec2_normalize(pxl8_vec2 v) {
|
||||
f32 len = pxl8_vec2_length(v);
|
||||
|
||||
if (len < 1e-6f) return (pxl8_vec2){0};
|
||||
|
||||
return pxl8_vec2_scale(v, 1.0f / len);
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_vec3_add(pxl8_vec3 a, pxl8_vec3 b) {
|
||||
return (pxl8_vec3){
|
||||
.x = a.x + b.x,
|
||||
.y = a.y + b.y,
|
||||
.z = a.z + b.z,
|
||||
};
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_vec3_sub(pxl8_vec3 a, pxl8_vec3 b) {
|
||||
return (pxl8_vec3){
|
||||
.x = a.x - b.x,
|
||||
.y = a.y - b.y,
|
||||
.z = a.z - b.z,
|
||||
};
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_vec3_scale(pxl8_vec3 v, f32 s) {
|
||||
return (pxl8_vec3){
|
||||
.x = v.x * s,
|
||||
.y = v.y * s,
|
||||
.z = v.z * s,
|
||||
};
|
||||
}
|
||||
|
||||
f32 pxl8_vec3_dot(pxl8_vec3 a, pxl8_vec3 b) {
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z;
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_vec3_cross(pxl8_vec3 a, pxl8_vec3 b) {
|
||||
return (pxl8_vec3){
|
||||
.x = a.y * b.z - a.z * b.y,
|
||||
.y = a.z * b.x - a.x * b.z,
|
||||
.z = a.x * b.y - a.y * b.x,
|
||||
};
|
||||
}
|
||||
|
||||
f32 pxl8_vec3_length(pxl8_vec3 v) {
|
||||
return sqrtf(pxl8_vec3_dot(v, v));
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_vec3_normalize(pxl8_vec3 v) {
|
||||
f32 len = pxl8_vec3_length(v);
|
||||
|
||||
if (len < 1e-6f) return (pxl8_vec3){0};
|
||||
|
||||
return pxl8_vec3_scale(v, 1.0f / len);
|
||||
}
|
||||
|
||||
pxl8_mat4 pxl8_mat4_identity(void) {
|
||||
pxl8_mat4 mat = {0};
|
||||
|
||||
mat.m[0] = mat.m[5] = mat.m[10] = mat.m[15] = 1.0f;
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b) {
|
||||
pxl8_mat4 mat = {0};
|
||||
|
||||
for (i32 col = 0; col < 4; col++) {
|
||||
for (i32 row = 0; row < 4; row++) {
|
||||
mat.m[col * 4 + row] =
|
||||
a.m[0 * 4 + row] * b.m[col * 4 + 0] +
|
||||
a.m[1 * 4 + row] * b.m[col * 4 + 1] +
|
||||
a.m[2 * 4 + row] * b.m[col * 4 + 2] +
|
||||
a.m[3 * 4 + row] * b.m[col * 4 + 3];
|
||||
}
|
||||
}
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
pxl8_vec4 pxl8_mat4_multiply_vec4(pxl8_mat4 m, pxl8_vec4 v) {
|
||||
return (pxl8_vec4){
|
||||
.x = m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z + m.m[12] * v.w,
|
||||
.y = m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z + m.m[13] * v.w,
|
||||
.z = m.m[2] * v.x + m.m[6] * v.y + m.m[10] * v.z + m.m[14] * v.w,
|
||||
.w = m.m[3] * v.x + m.m[7] * v.y + m.m[11] * v.z + m.m[15] * v.w,
|
||||
};
|
||||
}
|
||||
|
||||
pxl8_mat4 pxl8_mat4_translate(f32 x, f32 y, f32 z) {
|
||||
pxl8_mat4 mat = pxl8_mat4_identity();
|
||||
|
||||
mat.m[12] = x;
|
||||
mat.m[13] = y;
|
||||
mat.m[14] = z;
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
pxl8_mat4 pxl8_mat4_rotate_x(f32 angle) {
|
||||
pxl8_mat4 mat = pxl8_mat4_identity();
|
||||
f32 c = cosf(angle);
|
||||
f32 s = sinf(angle);
|
||||
|
||||
mat.m[5] = c;
|
||||
mat.m[9] = -s;
|
||||
mat.m[6] = s;
|
||||
mat.m[10] = c;
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
pxl8_mat4 pxl8_mat4_rotate_y(f32 angle) {
|
||||
pxl8_mat4 mat = pxl8_mat4_identity();
|
||||
f32 c = cosf(angle);
|
||||
f32 s = sinf(angle);
|
||||
|
||||
mat.m[0] = c;
|
||||
mat.m[8] = s;
|
||||
mat.m[2] = -s;
|
||||
mat.m[10] = c;
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
pxl8_mat4 pxl8_mat4_rotate_z(f32 angle) {
|
||||
pxl8_mat4 mat = pxl8_mat4_identity();
|
||||
f32 c = cosf(angle);
|
||||
f32 s = sinf(angle);
|
||||
|
||||
mat.m[0] = c;
|
||||
mat.m[4] = -s;
|
||||
mat.m[1] = s;
|
||||
mat.m[5] = c;
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
pxl8_mat4 pxl8_mat4_scale(f32 x, f32 y, f32 z) {
|
||||
pxl8_mat4 mat = pxl8_mat4_identity();
|
||||
|
||||
mat.m[0] = x;
|
||||
mat.m[5] = y;
|
||||
mat.m[10] = z;
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
pxl8_mat4 pxl8_mat4_ortho(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far) {
|
||||
pxl8_mat4 mat = {0};
|
||||
|
||||
mat.m[0] = 2.0f / (right - left);
|
||||
mat.m[5] = 2.0f / (top - bottom);
|
||||
mat.m[10] = -2.0f / (far - near);
|
||||
mat.m[12] = -(right + left) / (right - left);
|
||||
mat.m[13] = -(top + bottom) / (top - bottom);
|
||||
mat.m[14] = -(far + near) / (far - near);
|
||||
mat.m[15] = 1.0f;
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
pxl8_mat4 pxl8_mat4_perspective(f32 fov, f32 aspect, f32 near, f32 far) {
|
||||
pxl8_mat4 mat = {0};
|
||||
f32 tan_half_fov = tanf(fov / 2.0f);
|
||||
|
||||
mat.m[0] = 1.0f / (aspect * tan_half_fov);
|
||||
mat.m[5] = 1.0f / tan_half_fov;
|
||||
mat.m[10] = -(far + near) / (far - near);
|
||||
mat.m[14] = -(2.0f * far * near) / (far - near);
|
||||
mat.m[11] = -1.0f;
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up) {
|
||||
pxl8_mat4 mat = pxl8_mat4_identity();
|
||||
pxl8_vec3 f = pxl8_vec3_normalize(pxl8_vec3_sub(center, eye));
|
||||
pxl8_vec3 s = pxl8_vec3_normalize(pxl8_vec3_cross(f, up));
|
||||
pxl8_vec3 u = pxl8_vec3_cross(s, f);
|
||||
|
||||
mat.m[0] = s.x;
|
||||
mat.m[4] = s.y;
|
||||
mat.m[8] = s.z;
|
||||
mat.m[1] = u.x;
|
||||
mat.m[5] = u.y;
|
||||
mat.m[9] = u.z;
|
||||
mat.m[2] = -f.x;
|
||||
mat.m[6] = -f.y;
|
||||
mat.m[10] = -f.z;
|
||||
mat.m[12] = -pxl8_vec3_dot(s, eye);
|
||||
mat.m[13] = -pxl8_vec3_dot(u, eye);
|
||||
mat.m[14] = pxl8_vec3_dot(f, eye);
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp) {
|
||||
pxl8_frustum frustum;
|
||||
const f32* m = vp.m;
|
||||
|
||||
frustum.planes[0].normal.x = m[3] - m[0];
|
||||
frustum.planes[0].normal.y = m[7] - m[4];
|
||||
frustum.planes[0].normal.z = m[11] - m[8];
|
||||
frustum.planes[0].distance = m[15] - m[12];
|
||||
|
||||
frustum.planes[1].normal.x = m[3] + m[0];
|
||||
frustum.planes[1].normal.y = m[7] + m[4];
|
||||
frustum.planes[1].normal.z = m[11] + m[8];
|
||||
frustum.planes[1].distance = m[15] + m[12];
|
||||
|
||||
frustum.planes[2].normal.x = m[3] + m[1];
|
||||
frustum.planes[2].normal.y = m[7] + m[5];
|
||||
frustum.planes[2].normal.z = m[11] + m[9];
|
||||
frustum.planes[2].distance = m[15] + m[13];
|
||||
|
||||
frustum.planes[3].normal.x = m[3] - m[1];
|
||||
frustum.planes[3].normal.y = m[7] - m[5];
|
||||
frustum.planes[3].normal.z = m[11] - m[9];
|
||||
frustum.planes[3].distance = m[15] - m[13];
|
||||
|
||||
frustum.planes[4].normal.x = m[3] - m[2];
|
||||
frustum.planes[4].normal.y = m[7] - m[6];
|
||||
frustum.planes[4].normal.z = m[11] - m[10];
|
||||
frustum.planes[4].distance = m[15] - m[14];
|
||||
|
||||
frustum.planes[5].normal.x = m[3] + m[2];
|
||||
frustum.planes[5].normal.y = m[7] + m[6];
|
||||
frustum.planes[5].normal.z = m[11] + m[10];
|
||||
frustum.planes[5].distance = m[15] + m[14];
|
||||
|
||||
for (i32 i = 0; i < 6; i++) {
|
||||
f32 len = pxl8_vec3_length(frustum.planes[i].normal);
|
||||
if (len > 1e-6f) {
|
||||
f32 inv_len = 1.0f / len;
|
||||
frustum.planes[i].normal = pxl8_vec3_scale(frustum.planes[i].normal, inv_len);
|
||||
frustum.planes[i].distance *= inv_len;
|
||||
}
|
||||
}
|
||||
|
||||
return frustum;
|
||||
}
|
||||
|
||||
bool pxl8_frustum_test_aabb(const pxl8_frustum* frustum, pxl8_vec3 min, pxl8_vec3 max) {
|
||||
for (i32 i = 0; i < 6; i++) {
|
||||
pxl8_vec3 normal = frustum->planes[i].normal;
|
||||
f32 d = frustum->planes[i].distance;
|
||||
|
||||
pxl8_vec3 p_vertex = {
|
||||
(normal.x >= 0.0f) ? max.x : min.x,
|
||||
(normal.y >= 0.0f) ? max.y : min.y,
|
||||
(normal.z >= 0.0f) ? max.z : min.z
|
||||
};
|
||||
|
||||
f32 p_dist = pxl8_vec3_dot(normal, p_vertex) + d;
|
||||
|
||||
if (p_dist < 0.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define PXL8_PI 3.14159265358979323846f
|
||||
#define PXL8_TAU (PXL8_PI * 2.0f)
|
||||
|
||||
typedef struct pxl8_vec2 {
|
||||
f32 x, y;
|
||||
} pxl8_vec2;
|
||||
|
||||
typedef struct pxl8_vec3 {
|
||||
f32 x, y, z;
|
||||
} pxl8_vec3;
|
||||
|
||||
typedef struct pxl8_vec4 {
|
||||
f32 x, y, z, w;
|
||||
} pxl8_vec4;
|
||||
|
||||
typedef struct pxl8_mat4 {
|
||||
f32 m[16];
|
||||
} pxl8_mat4;
|
||||
|
||||
typedef struct pxl8_plane {
|
||||
pxl8_vec3 normal;
|
||||
f32 distance;
|
||||
} pxl8_plane;
|
||||
|
||||
typedef struct pxl8_frustum {
|
||||
pxl8_plane planes[6];
|
||||
} pxl8_frustum;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b);
|
||||
f32 pxl8_vec2_dot(pxl8_vec2 a, pxl8_vec2 b);
|
||||
f32 pxl8_vec2_length(pxl8_vec2 v);
|
||||
pxl8_vec2 pxl8_vec2_normalize(pxl8_vec2 v);
|
||||
pxl8_vec2 pxl8_vec2_scale(pxl8_vec2 v, f32 s);
|
||||
pxl8_vec2 pxl8_vec2_sub(pxl8_vec2 a, pxl8_vec2 b);
|
||||
|
||||
pxl8_vec3 pxl8_vec3_add(pxl8_vec3 a, pxl8_vec3 b);
|
||||
pxl8_vec3 pxl8_vec3_cross(pxl8_vec3 a, pxl8_vec3 b);
|
||||
f32 pxl8_vec3_dot(pxl8_vec3 a, pxl8_vec3 b);
|
||||
f32 pxl8_vec3_length(pxl8_vec3 v);
|
||||
pxl8_vec3 pxl8_vec3_normalize(pxl8_vec3 v);
|
||||
pxl8_vec3 pxl8_vec3_scale(pxl8_vec3 v, f32 s);
|
||||
pxl8_vec3 pxl8_vec3_sub(pxl8_vec3 a, pxl8_vec3 b);
|
||||
|
||||
pxl8_mat4 pxl8_mat4_identity(void);
|
||||
pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);
|
||||
pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b);
|
||||
pxl8_vec4 pxl8_mat4_multiply_vec4(pxl8_mat4 m, pxl8_vec4 v);
|
||||
pxl8_mat4 pxl8_mat4_ortho(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far);
|
||||
pxl8_mat4 pxl8_mat4_perspective(f32 fov, f32 aspect, f32 near, f32 far);
|
||||
pxl8_mat4 pxl8_mat4_rotate_x(f32 angle);
|
||||
pxl8_mat4 pxl8_mat4_rotate_y(f32 angle);
|
||||
pxl8_mat4 pxl8_mat4_rotate_z(f32 angle);
|
||||
pxl8_mat4 pxl8_mat4_scale(f32 x, f32 y, f32 z);
|
||||
pxl8_mat4 pxl8_mat4_translate(f32 x, f32 y, f32 z);
|
||||
|
||||
pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp);
|
||||
bool pxl8_frustum_test_aabb(const pxl8_frustum* frustum, pxl8_vec3 min, pxl8_vec3 max);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
316
src/pxl8_repl.c
316
src/pxl8_repl.c
|
|
@ -1,316 +0,0 @@
|
|||
#include "pxl8_repl.h"
|
||||
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linenoise.h>
|
||||
|
||||
#define PXL8_MAX_REPL_COMMAND_SIZE 4096
|
||||
#define PXL8_REPL_QUEUE_SIZE 8
|
||||
|
||||
struct pxl8_repl_command {
|
||||
char buffer[PXL8_MAX_REPL_COMMAND_SIZE];
|
||||
};
|
||||
|
||||
struct pxl8_repl {
|
||||
char commands[PXL8_REPL_QUEUE_SIZE][PXL8_MAX_REPL_COMMAND_SIZE];
|
||||
atomic_uint cmd_write_idx;
|
||||
atomic_uint cmd_read_idx;
|
||||
atomic_bool cmd_complete;
|
||||
|
||||
char logs[PXL8_REPL_QUEUE_SIZE][PXL8_MAX_REPL_COMMAND_SIZE];
|
||||
atomic_uint log_write_idx;
|
||||
atomic_uint log_read_idx;
|
||||
|
||||
atomic_bool should_quit;
|
||||
pthread_t thread;
|
||||
char accumulator[PXL8_MAX_REPL_COMMAND_SIZE];
|
||||
pxl8_repl_command command;
|
||||
};
|
||||
|
||||
static pxl8_repl* g_repl = NULL;
|
||||
|
||||
static void pxl8_repl_completion(const char* buf, linenoiseCompletions* lc) {
|
||||
const char* fennel_keywords[] = {
|
||||
"fn", "let", "var", "set", "global", "local",
|
||||
"if", "when", "do", "while", "for", "each",
|
||||
"lambda", "λ", "partial", "macro", "macros",
|
||||
"require", "include", "import-macros",
|
||||
"values", "select", "table", "length",
|
||||
".", "..", ":", "->", "->>", "-?>", "-?>>",
|
||||
"doto", "match", "case", "pick-values",
|
||||
"collect", "icollect", "accumulate"
|
||||
};
|
||||
|
||||
const char* pxl8_functions[] = {
|
||||
"pxl8.clr", "pxl8.pixel", "pxl8.get_pixel",
|
||||
"pxl8.line", "pxl8.rect", "pxl8.rect_fill",
|
||||
"pxl8.circle", "pxl8.circle_fill", "pxl8.text",
|
||||
"pxl8.get_screen", "pxl8.info", "pxl8.warn",
|
||||
"pxl8.error", "pxl8.debug", "pxl8.trace"
|
||||
};
|
||||
|
||||
size_t buf_len = strlen(buf);
|
||||
|
||||
for (size_t i = 0; i < sizeof(fennel_keywords) / sizeof(fennel_keywords[0]); i++) {
|
||||
if (strncmp(buf, fennel_keywords[i], buf_len) == 0) {
|
||||
linenoiseAddCompletion(lc, fennel_keywords[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof(pxl8_functions) / sizeof(pxl8_functions[0]); i++) {
|
||||
if (strncmp(buf, pxl8_functions[i], buf_len) == 0) {
|
||||
linenoiseAddCompletion(lc, pxl8_functions[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static char* pxl8_repl_hints(const char* buf, int* color, int* bold) {
|
||||
if (strncmp(buf, "pxl8.", 5) == 0 && strlen(buf) == 5) {
|
||||
*color = 35;
|
||||
*bold = 0;
|
||||
return "clr|pixel|line|rect|circle|text|get_screen";
|
||||
}
|
||||
|
||||
if (strcmp(buf, "(fn") == 0) {
|
||||
*color = 36;
|
||||
*bold = 0;
|
||||
return " [args] body)";
|
||||
}
|
||||
|
||||
if (strcmp(buf, "(let") == 0) {
|
||||
*color = 36;
|
||||
*bold = 0;
|
||||
return " [bindings] body)";
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void pxl8_repl_flush_logs(pxl8_repl* repl) {
|
||||
u32 log_read_idx = atomic_load(&repl->log_read_idx);
|
||||
u32 log_write_idx = atomic_load(&repl->log_write_idx);
|
||||
while (log_read_idx != log_write_idx) {
|
||||
printf("%s", repl->logs[log_read_idx]);
|
||||
atomic_store(&repl->log_read_idx, (log_read_idx + 1) % PXL8_REPL_QUEUE_SIZE);
|
||||
log_read_idx = atomic_load(&repl->log_read_idx);
|
||||
log_write_idx = atomic_load(&repl->log_write_idx);
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void* pxl8_repl_thread(void* arg) {
|
||||
pxl8_repl* repl = (pxl8_repl*)arg;
|
||||
|
||||
printf("[pxl8 REPL] Fennel 1.6.0 - Tab for completion, Ctrl-D to exit\n");
|
||||
fflush(stdout);
|
||||
|
||||
struct linenoiseState ls;
|
||||
char input_buf[PXL8_MAX_REPL_COMMAND_SIZE];
|
||||
bool editing = false;
|
||||
|
||||
struct pollfd pfd = {
|
||||
.fd = STDIN_FILENO,
|
||||
.events = POLLIN
|
||||
};
|
||||
|
||||
while (!atomic_load(&repl->should_quit)) {
|
||||
u32 log_read_idx = atomic_load(&repl->log_read_idx);
|
||||
u32 log_write_idx = atomic_load(&repl->log_write_idx);
|
||||
|
||||
if (log_read_idx != log_write_idx) {
|
||||
printf("\r\033[K");
|
||||
if (editing) {
|
||||
linenoiseEditStop(&ls);
|
||||
editing = false;
|
||||
printf("\033[A\r\033[K");
|
||||
}
|
||||
while (log_read_idx != log_write_idx) {
|
||||
printf("%s", repl->logs[log_read_idx]);
|
||||
atomic_store(&repl->log_read_idx, (log_read_idx + 1) % PXL8_REPL_QUEUE_SIZE);
|
||||
log_read_idx = atomic_load(&repl->log_read_idx);
|
||||
log_write_idx = atomic_load(&repl->log_write_idx);
|
||||
}
|
||||
fflush(stdout);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!editing && !atomic_load(&repl->should_quit)) {
|
||||
const char* prompt = (repl->accumulator[0] != '\0') ? ".. " : ">> ";
|
||||
if (linenoiseEditStart(&ls, STDIN_FILENO, STDOUT_FILENO, input_buf, sizeof(input_buf), prompt) == -1) {
|
||||
atomic_store(&repl->should_quit, true);
|
||||
break;
|
||||
}
|
||||
editing = true;
|
||||
}
|
||||
|
||||
if (poll(&pfd, 1, 1) <= 0) continue;
|
||||
|
||||
char* line = linenoiseEditFeed(&ls);
|
||||
|
||||
if (line == NULL) {
|
||||
atomic_store(&repl->should_quit, true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (line == linenoiseEditMore) continue;
|
||||
|
||||
linenoiseEditStop(&ls);
|
||||
editing = false;
|
||||
|
||||
bool in_multiline = (repl->accumulator[0] != '\0');
|
||||
|
||||
if (strlen(line) > 0 || in_multiline) {
|
||||
if (!in_multiline) {
|
||||
linenoiseHistoryAdd(line);
|
||||
linenoiseHistorySave(".pxl8_history");
|
||||
}
|
||||
|
||||
if (repl->accumulator[0] != '\0') {
|
||||
strncat(repl->accumulator, "\n",
|
||||
PXL8_MAX_REPL_COMMAND_SIZE - strlen(repl->accumulator) - 1);
|
||||
}
|
||||
strncat(repl->accumulator, line,
|
||||
PXL8_MAX_REPL_COMMAND_SIZE - strlen(repl->accumulator) - 1);
|
||||
|
||||
u32 write_idx = atomic_load(&repl->cmd_write_idx);
|
||||
u32 read_idx = atomic_load(&repl->cmd_read_idx);
|
||||
u32 next_write = (write_idx + 1) % PXL8_REPL_QUEUE_SIZE;
|
||||
|
||||
if (next_write != read_idx) {
|
||||
strncpy(repl->commands[write_idx], repl->accumulator, PXL8_MAX_REPL_COMMAND_SIZE - 1);
|
||||
repl->commands[write_idx][PXL8_MAX_REPL_COMMAND_SIZE - 1] = '\0';
|
||||
atomic_store(&repl->cmd_write_idx, next_write);
|
||||
}
|
||||
}
|
||||
|
||||
linenoiseFree(line);
|
||||
|
||||
while (!atomic_load(&repl->should_quit) &&
|
||||
(atomic_load(&repl->cmd_write_idx) != atomic_load(&repl->cmd_read_idx) ||
|
||||
!atomic_load(&repl->cmd_complete))) {
|
||||
u32 lr = atomic_load(&repl->log_read_idx);
|
||||
u32 lw = atomic_load(&repl->log_write_idx);
|
||||
while (lr != lw) {
|
||||
printf("%s", repl->logs[lr]);
|
||||
atomic_store(&repl->log_read_idx, (lr + 1) % PXL8_REPL_QUEUE_SIZE);
|
||||
lr = atomic_load(&repl->log_read_idx);
|
||||
lw = atomic_load(&repl->log_write_idx);
|
||||
}
|
||||
fflush(stdout);
|
||||
struct timespec ts = {.tv_sec = 0, .tv_nsec = 1000000};
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
atomic_store(&repl->cmd_complete, false);
|
||||
}
|
||||
|
||||
if (editing) linenoiseEditStop(&ls);
|
||||
|
||||
pxl8_repl_flush_logs(repl);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_repl* pxl8_repl_create(void) {
|
||||
pxl8_repl* repl = (pxl8_repl*)calloc(1, sizeof(pxl8_repl));
|
||||
if (!repl) return NULL;
|
||||
|
||||
repl->accumulator[0] = '\0';
|
||||
atomic_store(&repl->cmd_write_idx, 0);
|
||||
atomic_store(&repl->cmd_read_idx, 0);
|
||||
atomic_store(&repl->cmd_complete, true);
|
||||
atomic_store(&repl->log_write_idx, 0);
|
||||
atomic_store(&repl->log_read_idx, 0);
|
||||
atomic_store(&repl->should_quit, false);
|
||||
|
||||
linenoiseHistorySetMaxLen(100);
|
||||
linenoiseSetMultiLine(1);
|
||||
linenoiseSetCompletionCallback(pxl8_repl_completion);
|
||||
linenoiseSetHintsCallback(pxl8_repl_hints);
|
||||
linenoiseHistoryLoad(".pxl8_history");
|
||||
|
||||
g_repl = repl;
|
||||
|
||||
if (pthread_create(&repl->thread, NULL, pxl8_repl_thread, repl) != 0) {
|
||||
free(repl);
|
||||
g_repl = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return repl;
|
||||
}
|
||||
|
||||
void pxl8_repl_destroy(pxl8_repl* repl) {
|
||||
if (!repl) return;
|
||||
|
||||
atomic_store(&repl->should_quit, true);
|
||||
|
||||
struct timespec ts = {.tv_sec = 0, .tv_nsec = 2000000};
|
||||
nanosleep(&ts, NULL);
|
||||
|
||||
printf("\r\033[K");
|
||||
fflush(stdout);
|
||||
|
||||
pthread_join(repl->thread, NULL);
|
||||
pxl8_repl_flush_logs(repl);
|
||||
|
||||
g_repl = NULL;
|
||||
|
||||
system("stty sane 2>/dev/null");
|
||||
free(repl);
|
||||
}
|
||||
|
||||
pxl8_repl_command* pxl8_repl_pop_command(pxl8_repl* repl) {
|
||||
if (!repl) return NULL;
|
||||
|
||||
u32 read_idx = atomic_load(&repl->cmd_read_idx);
|
||||
u32 write_idx = atomic_load(&repl->cmd_write_idx);
|
||||
|
||||
if (read_idx == write_idx) return NULL;
|
||||
|
||||
strncpy(repl->command.buffer, repl->commands[read_idx], PXL8_MAX_REPL_COMMAND_SIZE - 1);
|
||||
repl->command.buffer[PXL8_MAX_REPL_COMMAND_SIZE - 1] = '\0';
|
||||
|
||||
atomic_store(&repl->cmd_read_idx, (read_idx + 1) % PXL8_REPL_QUEUE_SIZE);
|
||||
|
||||
return &repl->command;
|
||||
}
|
||||
|
||||
const char* pxl8_repl_command_buffer(pxl8_repl_command* cmd) {
|
||||
return cmd ? cmd->buffer : NULL;
|
||||
}
|
||||
|
||||
bool pxl8_repl_should_quit(pxl8_repl* repl) {
|
||||
return repl ? atomic_load(&repl->should_quit) : false;
|
||||
}
|
||||
|
||||
bool pxl8_repl_push_log(const char* message) {
|
||||
if (!g_repl || !message) return false;
|
||||
|
||||
u32 write_idx = atomic_load(&g_repl->log_write_idx);
|
||||
u32 read_idx = atomic_load(&g_repl->log_read_idx);
|
||||
u32 next_write = (write_idx + 1) % PXL8_REPL_QUEUE_SIZE;
|
||||
|
||||
if (next_write != read_idx) {
|
||||
strncpy(g_repl->logs[write_idx], message, PXL8_MAX_REPL_COMMAND_SIZE - 1);
|
||||
g_repl->logs[write_idx][PXL8_MAX_REPL_COMMAND_SIZE - 1] = '\0';
|
||||
atomic_store(&g_repl->log_write_idx, next_write);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void pxl8_repl_clear_accumulator(pxl8_repl* repl) {
|
||||
if (!repl) return;
|
||||
repl->accumulator[0] = '\0';
|
||||
}
|
||||
|
||||
void pxl8_repl_signal_complete(pxl8_repl* repl) {
|
||||
if (!repl) return;
|
||||
atomic_store(&repl->cmd_complete, true);
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_repl pxl8_repl;
|
||||
typedef struct pxl8_repl_command pxl8_repl_command;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_repl* pxl8_repl_create(void);
|
||||
void pxl8_repl_destroy(pxl8_repl* repl);
|
||||
|
||||
pxl8_repl_command* pxl8_repl_pop_command(pxl8_repl* repl);
|
||||
const char* pxl8_repl_command_buffer(pxl8_repl_command* cmd);
|
||||
bool pxl8_repl_push_log(const char* message);
|
||||
void pxl8_repl_clear_accumulator(pxl8_repl* repl);
|
||||
bool pxl8_repl_should_quit(pxl8_repl* repl);
|
||||
void pxl8_repl_signal_complete(pxl8_repl* repl);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,619 +0,0 @@
|
|||
#include "pxl8_replay.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_sys.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct pxl8_replay_chunk {
|
||||
u8 type;
|
||||
u32 size;
|
||||
u8* data;
|
||||
struct pxl8_replay_chunk* next;
|
||||
} pxl8_replay_chunk;
|
||||
|
||||
typedef struct pxl8_keyframe_entry {
|
||||
pxl8_keyframe keyframe;
|
||||
pxl8_replay_chunk* input_deltas;
|
||||
struct pxl8_keyframe_entry* next;
|
||||
struct pxl8_keyframe_entry* prev;
|
||||
} pxl8_keyframe_entry;
|
||||
|
||||
struct pxl8_replay {
|
||||
FILE* file;
|
||||
pxl8_replay_header header;
|
||||
bool recording;
|
||||
bool playing;
|
||||
u32 current_frame;
|
||||
|
||||
pxl8_keyframe_entry* keyframes;
|
||||
pxl8_keyframe_entry* current_keyframe;
|
||||
u32 keyframe_count;
|
||||
u32 max_keyframes;
|
||||
|
||||
pxl8_replay_chunk* pending_inputs;
|
||||
pxl8_replay_chunk* pending_inputs_tail;
|
||||
|
||||
pxl8_replay_chunk* audio_events;
|
||||
pxl8_replay_chunk* audio_events_tail;
|
||||
};
|
||||
|
||||
static void pxl8_replay_chunk_free(pxl8_replay_chunk* chunk) {
|
||||
while (chunk) {
|
||||
pxl8_replay_chunk* next = chunk->next;
|
||||
free(chunk->data);
|
||||
free(chunk);
|
||||
chunk = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void pxl8_replay_keyframe_entry_free(pxl8_keyframe_entry* entry) {
|
||||
while (entry) {
|
||||
pxl8_keyframe_entry* next = entry->next;
|
||||
pxl8_replay_chunk_free(entry->input_deltas);
|
||||
free(entry);
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_replay* pxl8_replay_create(const char* path, u32 keyframe_interval) {
|
||||
FILE* f = fopen(path, "wb");
|
||||
if (!f) {
|
||||
pxl8_error("Failed to create replay file: %s", path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_replay* r = calloc(1, sizeof(pxl8_replay));
|
||||
if (!r) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
r->file = f;
|
||||
r->recording = true;
|
||||
r->playing = false;
|
||||
r->header.magic = PXL8_REPLAY_MAGIC;
|
||||
r->header.version = PXL8_REPLAY_VERSION;
|
||||
r->header.keyframe_interval = keyframe_interval;
|
||||
|
||||
fwrite(&r->header, sizeof(pxl8_replay_header), 1, f);
|
||||
fflush(f);
|
||||
|
||||
pxl8_info("Created replay file: %s (keyframe interval: %u)", path, keyframe_interval);
|
||||
return r;
|
||||
}
|
||||
|
||||
pxl8_replay* pxl8_replay_create_buffer(u32 keyframe_interval, u32 max_keyframes) {
|
||||
pxl8_replay* r = calloc(1, sizeof(pxl8_replay));
|
||||
if (!r) return NULL;
|
||||
|
||||
r->recording = true;
|
||||
r->playing = false;
|
||||
r->max_keyframes = max_keyframes;
|
||||
r->header.magic = PXL8_REPLAY_MAGIC;
|
||||
r->header.version = PXL8_REPLAY_VERSION;
|
||||
r->header.keyframe_interval = keyframe_interval;
|
||||
|
||||
pxl8_debug("Created replay buffer (keyframe interval: %u, max: %u)", keyframe_interval, max_keyframes);
|
||||
return r;
|
||||
}
|
||||
|
||||
pxl8_replay* pxl8_replay_open(const char* path) {
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (!f) {
|
||||
pxl8_error("Failed to open replay file: %s", path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_replay_header header;
|
||||
if (fread(&header, sizeof(header), 1, f) != 1) {
|
||||
pxl8_error("Failed to read replay header");
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (header.magic != PXL8_REPLAY_MAGIC) {
|
||||
pxl8_error("Invalid replay magic: 0x%08X (expected 0x%08X)", header.magic, PXL8_REPLAY_MAGIC);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (header.version > PXL8_REPLAY_VERSION) {
|
||||
pxl8_error("Unsupported replay version: %u (max supported: %u)", header.version, PXL8_REPLAY_VERSION);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_replay* r = calloc(1, sizeof(pxl8_replay));
|
||||
if (!r) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
r->file = f;
|
||||
r->header = header;
|
||||
r->recording = false;
|
||||
r->playing = true;
|
||||
|
||||
while (!feof(f)) {
|
||||
u8 chunk_type;
|
||||
if (fread(&chunk_type, 1, 1, f) != 1) break;
|
||||
|
||||
if (chunk_type == PXL8_REPLAY_CHUNK_END) break;
|
||||
|
||||
u8 size_bytes[3];
|
||||
if (fread(size_bytes, 3, 1, f) != 1) break;
|
||||
u32 size = size_bytes[0] | (size_bytes[1] << 8) | (size_bytes[2] << 16);
|
||||
|
||||
u8* data = malloc(size);
|
||||
if (!data || fread(data, size, 1, f) != 1) {
|
||||
free(data);
|
||||
break;
|
||||
}
|
||||
|
||||
if (chunk_type == PXL8_REPLAY_CHUNK_KEYFRAME) {
|
||||
pxl8_keyframe_entry* entry = calloc(1, sizeof(pxl8_keyframe_entry));
|
||||
if (entry && size >= sizeof(pxl8_keyframe)) {
|
||||
memcpy(&entry->keyframe, data, sizeof(pxl8_keyframe));
|
||||
entry->prev = r->current_keyframe;
|
||||
if (r->current_keyframe) {
|
||||
r->current_keyframe->next = entry;
|
||||
}
|
||||
r->current_keyframe = entry;
|
||||
if (!r->keyframes) {
|
||||
r->keyframes = entry;
|
||||
}
|
||||
r->keyframe_count++;
|
||||
}
|
||||
free(data);
|
||||
} else if (chunk_type == PXL8_REPLAY_CHUNK_INPUT) {
|
||||
if (r->current_keyframe) {
|
||||
pxl8_replay_chunk* chunk = calloc(1, sizeof(pxl8_replay_chunk));
|
||||
if (chunk) {
|
||||
chunk->type = chunk_type;
|
||||
chunk->size = size;
|
||||
chunk->data = data;
|
||||
data = NULL;
|
||||
|
||||
if (!r->current_keyframe->input_deltas) {
|
||||
r->current_keyframe->input_deltas = chunk;
|
||||
} else {
|
||||
pxl8_replay_chunk* tail = r->current_keyframe->input_deltas;
|
||||
while (tail->next) tail = tail->next;
|
||||
tail->next = chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(data);
|
||||
} else if (chunk_type == PXL8_REPLAY_CHUNK_AUDIO_EVENT) {
|
||||
pxl8_replay_chunk* chunk = calloc(1, sizeof(pxl8_replay_chunk));
|
||||
if (chunk) {
|
||||
chunk->type = chunk_type;
|
||||
chunk->size = size;
|
||||
chunk->data = data;
|
||||
data = NULL;
|
||||
|
||||
if (!r->audio_events) {
|
||||
r->audio_events = chunk;
|
||||
r->audio_events_tail = chunk;
|
||||
} else {
|
||||
r->audio_events_tail->next = chunk;
|
||||
r->audio_events_tail = chunk;
|
||||
}
|
||||
}
|
||||
free(data);
|
||||
} else {
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
r->current_keyframe = r->keyframes;
|
||||
pxl8_info("Opened replay: %u frames, %u keyframes", r->header.total_frames, r->keyframe_count);
|
||||
return r;
|
||||
}
|
||||
|
||||
void pxl8_replay_destroy(pxl8_replay* r) {
|
||||
if (!r) return;
|
||||
|
||||
if (r->file) {
|
||||
if (r->recording) {
|
||||
u8 end_chunk = PXL8_REPLAY_CHUNK_END;
|
||||
fwrite(&end_chunk, 1, 1, r->file);
|
||||
|
||||
fseek(r->file, 0, SEEK_SET);
|
||||
fwrite(&r->header, sizeof(pxl8_replay_header), 1, r->file);
|
||||
}
|
||||
fclose(r->file);
|
||||
}
|
||||
|
||||
pxl8_replay_keyframe_entry_free(r->keyframes);
|
||||
pxl8_replay_chunk_free(r->pending_inputs);
|
||||
pxl8_replay_chunk_free(r->audio_events);
|
||||
|
||||
free(r);
|
||||
}
|
||||
|
||||
bool pxl8_replay_is_recording(pxl8_replay* r) {
|
||||
return r && r->recording;
|
||||
}
|
||||
|
||||
bool pxl8_replay_is_playing(pxl8_replay* r) {
|
||||
return r && r->playing;
|
||||
}
|
||||
|
||||
u32 pxl8_replay_get_frame(pxl8_replay* r) {
|
||||
return r ? r->current_frame : 0;
|
||||
}
|
||||
|
||||
u32 pxl8_replay_get_total_frames(pxl8_replay* r) {
|
||||
return r ? r->header.total_frames : 0;
|
||||
}
|
||||
|
||||
static void write_chunk(FILE* f, u8 type, const void* data, u32 size) {
|
||||
fwrite(&type, 1, 1, f);
|
||||
u8 size_bytes[3] = {
|
||||
(u8)(size & 0xFF),
|
||||
(u8)((size >> 8) & 0xFF),
|
||||
(u8)((size >> 16) & 0xFF)
|
||||
};
|
||||
fwrite(size_bytes, 3, 1, f);
|
||||
fwrite(data, size, 1, f);
|
||||
}
|
||||
|
||||
static void add_chunk_to_buffer(pxl8_replay* r, u8 type, const void* data, u32 size) {
|
||||
pxl8_replay_chunk* chunk = calloc(1, sizeof(pxl8_replay_chunk));
|
||||
if (!chunk) return;
|
||||
|
||||
chunk->type = type;
|
||||
chunk->size = size;
|
||||
chunk->data = malloc(size);
|
||||
if (!chunk->data) {
|
||||
free(chunk);
|
||||
return;
|
||||
}
|
||||
memcpy(chunk->data, data, size);
|
||||
|
||||
if (!r->pending_inputs) {
|
||||
r->pending_inputs = chunk;
|
||||
r->pending_inputs_tail = chunk;
|
||||
} else {
|
||||
r->pending_inputs_tail->next = chunk;
|
||||
r->pending_inputs_tail = chunk;
|
||||
}
|
||||
}
|
||||
|
||||
static void pack_keys(const bool* keys, u8* packed) {
|
||||
memset(packed, 0, 32);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
if (keys[i]) {
|
||||
packed[i / 8] |= (1 << (i % 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void unpack_keys(const u8* packed, bool* keys) {
|
||||
for (int i = 0; i < 256; i++) {
|
||||
keys[i] = (packed[i / 8] >> (i % 8)) & 1;
|
||||
}
|
||||
}
|
||||
|
||||
static u8 pack_mouse_buttons(const bool* buttons) {
|
||||
return (buttons[0] ? 1 : 0) | (buttons[1] ? 2 : 0) | (buttons[2] ? 4 : 0);
|
||||
}
|
||||
|
||||
static void unpack_mouse_buttons(u8 packed, bool* buttons) {
|
||||
buttons[0] = (packed & 1) != 0;
|
||||
buttons[1] = (packed & 2) != 0;
|
||||
buttons[2] = (packed & 4) != 0;
|
||||
}
|
||||
|
||||
void pxl8_replay_write_keyframe(pxl8_replay* r, u32 frame, f32 time, pxl8_rng* rng, pxl8_input_state* input) {
|
||||
if (!r || !r->recording) return;
|
||||
|
||||
pxl8_keyframe kf = {0};
|
||||
kf.frame_number = frame;
|
||||
kf.time = time;
|
||||
kf.rng_state = rng ? rng->state : 0;
|
||||
|
||||
if (input) {
|
||||
pack_keys(input->keys_down, kf.keys_down);
|
||||
kf.mouse_buttons = pack_mouse_buttons(input->mouse_buttons_down);
|
||||
kf.mouse_x = (i16)input->mouse_x;
|
||||
kf.mouse_y = (i16)input->mouse_y;
|
||||
}
|
||||
|
||||
if (r->file) {
|
||||
write_chunk(r->file, PXL8_REPLAY_CHUNK_KEYFRAME, &kf, sizeof(kf));
|
||||
fflush(r->file);
|
||||
} else {
|
||||
pxl8_keyframe_entry* entry = calloc(1, sizeof(pxl8_keyframe_entry));
|
||||
if (!entry) return;
|
||||
|
||||
entry->keyframe = kf;
|
||||
entry->input_deltas = r->pending_inputs;
|
||||
r->pending_inputs = NULL;
|
||||
r->pending_inputs_tail = NULL;
|
||||
|
||||
if (r->keyframe_count >= r->max_keyframes && r->keyframes) {
|
||||
pxl8_keyframe_entry* oldest = r->keyframes;
|
||||
r->keyframes = oldest->next;
|
||||
if (r->keyframes) {
|
||||
r->keyframes->prev = NULL;
|
||||
}
|
||||
pxl8_replay_chunk_free(oldest->input_deltas);
|
||||
free(oldest);
|
||||
r->keyframe_count--;
|
||||
}
|
||||
|
||||
entry->prev = r->current_keyframe;
|
||||
if (r->current_keyframe) {
|
||||
r->current_keyframe->next = entry;
|
||||
}
|
||||
r->current_keyframe = entry;
|
||||
if (!r->keyframes) {
|
||||
r->keyframes = entry;
|
||||
}
|
||||
r->keyframe_count++;
|
||||
}
|
||||
|
||||
r->current_frame = frame;
|
||||
r->header.total_frames = frame;
|
||||
}
|
||||
|
||||
void pxl8_replay_write_input(pxl8_replay* r, u32 frame, pxl8_input_state* prev, pxl8_input_state* curr) {
|
||||
if (!r || !r->recording || !prev || !curr) return;
|
||||
|
||||
u8 buffer[256];
|
||||
u32 offset = 0;
|
||||
|
||||
buffer[offset++] = (u8)(frame & 0xFF);
|
||||
buffer[offset++] = (u8)((frame >> 8) & 0xFF);
|
||||
buffer[offset++] = (u8)((frame >> 16) & 0xFF);
|
||||
buffer[offset++] = (u8)((frame >> 24) & 0xFF);
|
||||
|
||||
u8 key_event_count = 0;
|
||||
u8 key_events[64];
|
||||
u32 key_offset = 0;
|
||||
|
||||
for (int i = 0; i < 256 && key_event_count < 32; i++) {
|
||||
if (prev->keys_down[i] != curr->keys_down[i]) {
|
||||
key_events[key_offset++] = (u8)i;
|
||||
key_events[key_offset++] = curr->keys_down[i] ? 1 : 0;
|
||||
key_event_count++;
|
||||
}
|
||||
}
|
||||
|
||||
buffer[offset++] = key_event_count;
|
||||
memcpy(buffer + offset, key_events, key_offset);
|
||||
offset += key_offset;
|
||||
|
||||
u8 prev_mouse_btns = pack_mouse_buttons(prev->mouse_buttons_down);
|
||||
u8 curr_mouse_btns = pack_mouse_buttons(curr->mouse_buttons_down);
|
||||
|
||||
u8 mouse_flags = 0;
|
||||
if (curr->mouse_x != prev->mouse_x || curr->mouse_y != prev->mouse_y) {
|
||||
mouse_flags |= 0x01;
|
||||
}
|
||||
if (curr_mouse_btns != prev_mouse_btns) {
|
||||
mouse_flags |= 0x02;
|
||||
}
|
||||
if (curr->mouse_wheel_x != 0 || curr->mouse_wheel_y != 0) {
|
||||
mouse_flags |= 0x04;
|
||||
}
|
||||
|
||||
buffer[offset++] = mouse_flags;
|
||||
|
||||
if (mouse_flags & 0x01) {
|
||||
i16 dx = (i16)(curr->mouse_x - prev->mouse_x);
|
||||
i16 dy = (i16)(curr->mouse_y - prev->mouse_y);
|
||||
buffer[offset++] = (u8)(dx & 0xFF);
|
||||
buffer[offset++] = (u8)((dx >> 8) & 0xFF);
|
||||
buffer[offset++] = (u8)(dy & 0xFF);
|
||||
buffer[offset++] = (u8)((dy >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
if (mouse_flags & 0x02) {
|
||||
buffer[offset++] = curr_mouse_btns;
|
||||
}
|
||||
|
||||
if (mouse_flags & 0x04) {
|
||||
buffer[offset++] = (i8)curr->mouse_wheel_x;
|
||||
buffer[offset++] = (i8)curr->mouse_wheel_y;
|
||||
}
|
||||
|
||||
if (r->file) {
|
||||
write_chunk(r->file, PXL8_REPLAY_CHUNK_INPUT, buffer, offset);
|
||||
} else {
|
||||
add_chunk_to_buffer(r, PXL8_REPLAY_CHUNK_INPUT, buffer, offset);
|
||||
}
|
||||
|
||||
r->current_frame = frame;
|
||||
r->header.total_frames = frame;
|
||||
}
|
||||
|
||||
void pxl8_replay_write_audio_event(pxl8_replay* r, u32 frame, u8 event_type, u8 context_id, u8 note, f32 volume) {
|
||||
if (!r || !r->recording) return;
|
||||
|
||||
pxl8_audio_event evt = {
|
||||
.frame_number = frame,
|
||||
.event_type = event_type,
|
||||
.context_id = context_id,
|
||||
.note = note,
|
||||
.volume = volume
|
||||
};
|
||||
|
||||
if (r->file) {
|
||||
write_chunk(r->file, PXL8_REPLAY_CHUNK_AUDIO_EVENT, &evt, sizeof(evt));
|
||||
} else {
|
||||
pxl8_replay_chunk* chunk = calloc(1, sizeof(pxl8_replay_chunk));
|
||||
if (!chunk) return;
|
||||
|
||||
chunk->type = PXL8_REPLAY_CHUNK_AUDIO_EVENT;
|
||||
chunk->size = sizeof(evt);
|
||||
chunk->data = malloc(sizeof(evt));
|
||||
if (!chunk->data) {
|
||||
free(chunk);
|
||||
return;
|
||||
}
|
||||
memcpy(chunk->data, &evt, sizeof(evt));
|
||||
|
||||
if (!r->audio_events) {
|
||||
r->audio_events = chunk;
|
||||
r->audio_events_tail = chunk;
|
||||
} else {
|
||||
r->audio_events_tail->next = chunk;
|
||||
r->audio_events_tail = chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_replay_close(pxl8_replay* r) {
|
||||
pxl8_replay_destroy(r);
|
||||
}
|
||||
|
||||
bool pxl8_replay_seek_frame(pxl8_replay* r, u32 frame) {
|
||||
if (!r || !r->playing) return false;
|
||||
|
||||
pxl8_keyframe_entry* target = NULL;
|
||||
for (pxl8_keyframe_entry* e = r->keyframes; e; e = e->next) {
|
||||
if (e->keyframe.frame_number <= frame) {
|
||||
target = e;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!target) return false;
|
||||
|
||||
r->current_keyframe = target;
|
||||
r->current_frame = target->keyframe.frame_number;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pxl8_replay_read_frame(pxl8_replay* r, pxl8_input_state* input_out) {
|
||||
if (!r || !r->playing || !r->current_keyframe) return false;
|
||||
|
||||
u32 target_frame = r->current_frame + 1;
|
||||
|
||||
if (target_frame > r->header.total_frames) return false;
|
||||
|
||||
pxl8_replay_chunk* delta = r->current_keyframe->input_deltas;
|
||||
while (delta) {
|
||||
if (delta->size >= 5) {
|
||||
u32 delta_frame = delta->data[0] | (delta->data[1] << 8) | (delta->data[2] << 16) | (delta->data[3] << 24);
|
||||
if (delta_frame == target_frame) {
|
||||
u32 offset = 4;
|
||||
u8 key_event_count = delta->data[offset++];
|
||||
|
||||
for (u8 i = 0; i < key_event_count && offset + 1 < delta->size; i++) {
|
||||
u8 scancode = delta->data[offset++];
|
||||
u8 pressed = delta->data[offset++];
|
||||
input_out->keys_down[scancode] = pressed != 0;
|
||||
}
|
||||
|
||||
if (offset < delta->size) {
|
||||
u8 mouse_flags = delta->data[offset++];
|
||||
|
||||
if (mouse_flags & 0x01 && offset + 3 < delta->size) {
|
||||
i16 dx = (i16)(delta->data[offset] | (delta->data[offset + 1] << 8));
|
||||
i16 dy = (i16)(delta->data[offset + 2] | (delta->data[offset + 3] << 8));
|
||||
offset += 4;
|
||||
input_out->mouse_x += dx;
|
||||
input_out->mouse_y += dy;
|
||||
}
|
||||
|
||||
if (mouse_flags & 0x02 && offset < delta->size) {
|
||||
u8 mouse_btns = delta->data[offset++];
|
||||
unpack_mouse_buttons(mouse_btns, input_out->mouse_buttons_down);
|
||||
}
|
||||
|
||||
if (mouse_flags & 0x04 && offset + 1 < delta->size) {
|
||||
input_out->mouse_wheel_x = (i8)delta->data[offset++];
|
||||
input_out->mouse_wheel_y = (i8)delta->data[offset++];
|
||||
}
|
||||
}
|
||||
|
||||
r->current_frame = target_frame;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
delta = delta->next;
|
||||
}
|
||||
|
||||
if (r->current_keyframe->next && r->current_keyframe->next->keyframe.frame_number <= target_frame) {
|
||||
r->current_keyframe = r->current_keyframe->next;
|
||||
}
|
||||
|
||||
r->current_frame = target_frame;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pxl8_replay_get_keyframe(pxl8_replay* r, u32 frame, pxl8_keyframe* out) {
|
||||
if (!r || !out) return false;
|
||||
|
||||
pxl8_keyframe_entry* target = NULL;
|
||||
for (pxl8_keyframe_entry* e = r->keyframes; e; e = e->next) {
|
||||
if (e->keyframe.frame_number <= frame) {
|
||||
target = e;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!target) return false;
|
||||
|
||||
*out = target->keyframe;
|
||||
return true;
|
||||
}
|
||||
|
||||
void pxl8_replay_apply_keyframe(pxl8_replay* r, pxl8_keyframe* kf, pxl8_rng* rng, pxl8_input_state* input) {
|
||||
if (!kf) return;
|
||||
|
||||
if (rng) {
|
||||
rng->state = kf->rng_state;
|
||||
}
|
||||
|
||||
if (input) {
|
||||
unpack_keys(kf->keys_down, input->keys_down);
|
||||
unpack_mouse_buttons(kf->mouse_buttons, input->mouse_buttons_down);
|
||||
input->mouse_x = kf->mouse_x;
|
||||
input->mouse_y = kf->mouse_y;
|
||||
}
|
||||
|
||||
if (r) {
|
||||
r->current_frame = kf->frame_number;
|
||||
}
|
||||
}
|
||||
|
||||
bool pxl8_replay_export(pxl8_replay* r, const char* path) {
|
||||
if (!r || !r->keyframes) return false;
|
||||
|
||||
FILE* f = fopen(path, "wb");
|
||||
if (!f) {
|
||||
pxl8_error("Failed to create export file: %s", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
pxl8_replay_header header = r->header;
|
||||
fwrite(&header, sizeof(header), 1, f);
|
||||
|
||||
for (pxl8_keyframe_entry* e = r->keyframes; e; e = e->next) {
|
||||
write_chunk(f, PXL8_REPLAY_CHUNK_KEYFRAME, &e->keyframe, sizeof(pxl8_keyframe));
|
||||
|
||||
for (pxl8_replay_chunk* c = e->input_deltas; c; c = c->next) {
|
||||
write_chunk(f, c->type, c->data, c->size);
|
||||
}
|
||||
}
|
||||
|
||||
for (pxl8_replay_chunk* c = r->audio_events; c; c = c->next) {
|
||||
write_chunk(f, c->type, c->data, c->size);
|
||||
}
|
||||
|
||||
u8 end_chunk = PXL8_REPLAY_CHUNK_END;
|
||||
fwrite(&end_chunk, 1, 1, f);
|
||||
|
||||
fclose(f);
|
||||
pxl8_info("Exported replay to: %s (%u frames)", path, header.total_frames);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_io.h"
|
||||
#include "pxl8_rng.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define PXL8_REPLAY_MAGIC 0x31525850
|
||||
#define PXL8_REPLAY_VERSION 1
|
||||
|
||||
#define PXL8_REPLAY_CHUNK_KEYFRAME 0x01
|
||||
#define PXL8_REPLAY_CHUNK_INPUT 0x02
|
||||
#define PXL8_REPLAY_CHUNK_AUDIO_EVENT 0x03
|
||||
#define PXL8_REPLAY_CHUNK_END 0xFF
|
||||
|
||||
#define PXL8_REPLAY_FLAG_HAS_PALETTE (1 << 0)
|
||||
#define PXL8_REPLAY_FLAG_HAS_GLOBALS (1 << 1)
|
||||
|
||||
typedef struct pxl8_replay pxl8_replay;
|
||||
|
||||
typedef struct pxl8_replay_header {
|
||||
u32 magic;
|
||||
u32 version;
|
||||
u32 flags;
|
||||
u32 keyframe_interval;
|
||||
u32 total_frames;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 reserved;
|
||||
} pxl8_replay_header;
|
||||
|
||||
typedef struct pxl8_keyframe {
|
||||
u32 frame_number;
|
||||
f32 time;
|
||||
u32 rng_state;
|
||||
u8 keys_down[32];
|
||||
u8 mouse_buttons;
|
||||
i16 mouse_x;
|
||||
i16 mouse_y;
|
||||
u8 flags;
|
||||
} pxl8_keyframe;
|
||||
|
||||
typedef struct pxl8_input_delta {
|
||||
u32 frame_number;
|
||||
u8 key_event_count;
|
||||
u8 mouse_flags;
|
||||
} pxl8_input_delta;
|
||||
|
||||
typedef struct pxl8_audio_event {
|
||||
u32 frame_number;
|
||||
u8 event_type;
|
||||
u8 context_id;
|
||||
u8 note;
|
||||
f32 volume;
|
||||
} pxl8_audio_event;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_replay* pxl8_replay_create(const char* path, u32 keyframe_interval);
|
||||
pxl8_replay* pxl8_replay_create_buffer(u32 keyframe_interval, u32 max_keyframes);
|
||||
pxl8_replay* pxl8_replay_open(const char* path);
|
||||
void pxl8_replay_destroy(pxl8_replay* r);
|
||||
|
||||
bool pxl8_replay_is_recording(pxl8_replay* r);
|
||||
bool pxl8_replay_is_playing(pxl8_replay* r);
|
||||
u32 pxl8_replay_get_frame(pxl8_replay* r);
|
||||
u32 pxl8_replay_get_total_frames(pxl8_replay* r);
|
||||
|
||||
void pxl8_replay_write_keyframe(pxl8_replay* r, u32 frame, f32 time, pxl8_rng* rng, pxl8_input_state* input);
|
||||
void pxl8_replay_write_input(pxl8_replay* r, u32 frame, pxl8_input_state* prev, pxl8_input_state* curr);
|
||||
void pxl8_replay_write_audio_event(pxl8_replay* r, u32 frame, u8 event_type, u8 context_id, u8 note, f32 volume);
|
||||
void pxl8_replay_close(pxl8_replay* r);
|
||||
|
||||
bool pxl8_replay_seek_frame(pxl8_replay* r, u32 frame);
|
||||
bool pxl8_replay_read_frame(pxl8_replay* r, pxl8_input_state* input_out);
|
||||
bool pxl8_replay_get_keyframe(pxl8_replay* r, u32 frame, pxl8_keyframe* out);
|
||||
void pxl8_replay_apply_keyframe(pxl8_replay* r, pxl8_keyframe* kf, pxl8_rng* rng, pxl8_input_state* input);
|
||||
|
||||
bool pxl8_replay_export(pxl8_replay* r, const char* path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
#include "pxl8_rng.h"
|
||||
|
||||
void pxl8_rng_seed(pxl8_rng* rng, u32 seed) {
|
||||
if (!rng) return;
|
||||
rng->state = seed ? seed : 1;
|
||||
}
|
||||
|
||||
u32 pxl8_rng_next(pxl8_rng* rng) {
|
||||
if (!rng) return 0;
|
||||
rng->state ^= rng->state << 13;
|
||||
rng->state ^= rng->state >> 17;
|
||||
rng->state ^= rng->state << 5;
|
||||
return rng->state;
|
||||
}
|
||||
|
||||
f32 pxl8_rng_f32(pxl8_rng* rng) {
|
||||
return (f32)pxl8_rng_next(rng) / (f32)0xFFFFFFFF;
|
||||
}
|
||||
|
||||
i32 pxl8_rng_range(pxl8_rng* rng, i32 min, i32 max) {
|
||||
if (min >= max) return min;
|
||||
u32 range = (u32)(max - min);
|
||||
return min + (i32)(pxl8_rng_next(rng) % range);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_rng {
|
||||
u32 state;
|
||||
} pxl8_rng;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void pxl8_rng_seed(pxl8_rng* rng, u32 seed);
|
||||
u32 pxl8_rng_next(pxl8_rng* rng);
|
||||
f32 pxl8_rng_f32(pxl8_rng* rng);
|
||||
i32 pxl8_rng_range(pxl8_rng* rng, i32 min, i32 max);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
272
src/pxl8_save.c
272
src/pxl8_save.c
|
|
@ -1,272 +0,0 @@
|
|||
#include "pxl8_save.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#include <shlobj.h>
|
||||
#define PATH_SEP '\\'
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#define PATH_SEP '/'
|
||||
#endif
|
||||
|
||||
#include "pxl8_log.h"
|
||||
|
||||
typedef struct {
|
||||
u32 magic;
|
||||
u32 version;
|
||||
u32 size;
|
||||
u32 checksum;
|
||||
} pxl8_save_header;
|
||||
|
||||
struct pxl8_save {
|
||||
char directory[PXL8_SAVE_MAX_PATH];
|
||||
u32 magic;
|
||||
u32 version;
|
||||
};
|
||||
|
||||
static u32 pxl8_save_checksum(const u8* data, u32 size) {
|
||||
u32 hash = 2166136261u;
|
||||
for (u32 i = 0; i < size; i++) {
|
||||
hash ^= data[i];
|
||||
hash *= 16777619u;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static void pxl8_save_get_slot_path(pxl8_save* save, u8 slot, char* path, size_t path_size) {
|
||||
if (slot == PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||
snprintf(path, path_size, "%s%chotreload.sav", save->directory, PATH_SEP);
|
||||
} else {
|
||||
snprintf(path, path_size, "%s%csave%d.sav", save->directory, PATH_SEP, slot);
|
||||
}
|
||||
}
|
||||
|
||||
static pxl8_result pxl8_save_ensure_directory(const char* path) {
|
||||
struct stat st;
|
||||
if (stat(path, &st) == 0) {
|
||||
return S_ISDIR(st.st_mode) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (_mkdir(path) != 0 && errno != EEXIST) {
|
||||
#else
|
||||
if (mkdir(path, 0755) != 0 && errno != EEXIST) {
|
||||
#endif
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
|
||||
if (!game_name) return NULL;
|
||||
|
||||
pxl8_save* save = (pxl8_save*)calloc(1, sizeof(pxl8_save));
|
||||
if (!save) return NULL;
|
||||
|
||||
save->magic = magic;
|
||||
save->version = version;
|
||||
|
||||
char base_dir[PXL8_SAVE_MAX_PATH];
|
||||
|
||||
#ifdef _WIN32
|
||||
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, base_dir))) {
|
||||
snprintf(save->directory, sizeof(save->directory),
|
||||
"%s%cpxl8%c%s", base_dir, PATH_SEP, PATH_SEP, game_name);
|
||||
} else {
|
||||
free(save);
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
const char* home = getenv("HOME");
|
||||
if (!home) {
|
||||
struct passwd* pw = getpwuid(getuid());
|
||||
if (pw) home = pw->pw_dir;
|
||||
}
|
||||
if (!home) {
|
||||
free(save);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
snprintf(base_dir, sizeof(base_dir), "%s/.local/share", home);
|
||||
pxl8_save_ensure_directory(base_dir);
|
||||
|
||||
snprintf(base_dir, sizeof(base_dir), "%s/.local/share/pxl8", home);
|
||||
pxl8_save_ensure_directory(base_dir);
|
||||
|
||||
snprintf(save->directory, sizeof(save->directory),
|
||||
"%s/.local/share/pxl8/%s", home, game_name);
|
||||
#endif
|
||||
|
||||
if (pxl8_save_ensure_directory(save->directory) != PXL8_OK) {
|
||||
free(save);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_info("Save system initialized: %s", save->directory);
|
||||
return save;
|
||||
}
|
||||
|
||||
void pxl8_save_destroy(pxl8_save* save) {
|
||||
if (save) {
|
||||
free(save);
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_result pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size) {
|
||||
if (!save) return PXL8_ERROR_NULL_POINTER;
|
||||
if (!data || size == 0) return PXL8_ERROR_NULL_POINTER;
|
||||
if (slot >= PXL8_SAVE_MAX_SLOTS && slot != PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
char path[PXL8_SAVE_MAX_PATH];
|
||||
pxl8_save_get_slot_path(save, slot, path, sizeof(path));
|
||||
|
||||
FILE* file = fopen(path, "wb");
|
||||
if (!file) {
|
||||
pxl8_error("Failed to open save file for writing: %s", path);
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
pxl8_save_header header = {
|
||||
.magic = save->magic,
|
||||
.version = save->version,
|
||||
.size = size,
|
||||
.checksum = pxl8_save_checksum(data, size)
|
||||
};
|
||||
|
||||
bool success = true;
|
||||
if (fwrite(&header, sizeof(header), 1, file) != 1) success = false;
|
||||
if (success && fwrite(data, 1, size, file) != size) success = false;
|
||||
|
||||
fclose(file);
|
||||
|
||||
if (!success) {
|
||||
pxl8_error("Failed to write save data");
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
if (slot == PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||
pxl8_debug("Hot reload state saved (%u bytes)", size);
|
||||
} else {
|
||||
pxl8_info("Game saved to slot %d (%u bytes)", slot, size);
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_out) {
|
||||
if (!save) return PXL8_ERROR_NULL_POINTER;
|
||||
if (!data_out || !size_out) return PXL8_ERROR_NULL_POINTER;
|
||||
if (slot >= PXL8_SAVE_MAX_SLOTS && slot != PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
*data_out = NULL;
|
||||
*size_out = 0;
|
||||
|
||||
char path[PXL8_SAVE_MAX_PATH];
|
||||
pxl8_save_get_slot_path(save, slot, path, sizeof(path));
|
||||
|
||||
FILE* file = fopen(path, "rb");
|
||||
if (!file) {
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
pxl8_save_header header;
|
||||
if (fread(&header, sizeof(header), 1, file) != 1) {
|
||||
fclose(file);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
if (header.magic != save->magic) {
|
||||
fclose(file);
|
||||
pxl8_error("Invalid save file magic");
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
if (header.version > save->version) {
|
||||
fclose(file);
|
||||
pxl8_error("Save file version too new: %u", header.version);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
u8* data = (u8*)malloc(header.size);
|
||||
if (!data) {
|
||||
fclose(file);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if (fread(data, 1, header.size, file) != header.size) {
|
||||
free(data);
|
||||
fclose(file);
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
u32 checksum = pxl8_save_checksum(data, header.size);
|
||||
if (checksum != header.checksum) {
|
||||
free(data);
|
||||
pxl8_error("Save file checksum mismatch");
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
*data_out = data;
|
||||
*size_out = header.size;
|
||||
|
||||
if (slot == PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||
pxl8_debug("Hot reload state loaded (%u bytes)", header.size);
|
||||
} else {
|
||||
pxl8_info("Game loaded from slot %d (%u bytes)", slot, header.size);
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_save_free(u8* data) {
|
||||
if (data) {
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
bool pxl8_save_exists(pxl8_save* save, u8 slot) {
|
||||
if (!save) return false;
|
||||
if (slot >= PXL8_SAVE_MAX_SLOTS && slot != PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char path[PXL8_SAVE_MAX_PATH];
|
||||
pxl8_save_get_slot_path(save, slot, path, sizeof(path));
|
||||
|
||||
struct stat st;
|
||||
return stat(path, &st) == 0;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_save_delete(pxl8_save* save, u8 slot) {
|
||||
if (!save) return PXL8_ERROR_NULL_POINTER;
|
||||
if (slot >= PXL8_SAVE_MAX_SLOTS && slot != PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
char path[PXL8_SAVE_MAX_PATH];
|
||||
pxl8_save_get_slot_path(save, slot, path, sizeof(path));
|
||||
|
||||
if (remove(path) != 0 && errno != ENOENT) {
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
const char* pxl8_save_get_directory(pxl8_save* save) {
|
||||
return save ? save->directory : NULL;
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define PXL8_SAVE_MAX_SLOTS 10
|
||||
#define PXL8_SAVE_HOTRELOAD_SLOT 255
|
||||
#define PXL8_SAVE_MAX_PATH 512
|
||||
|
||||
typedef struct pxl8_save pxl8_save;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version);
|
||||
void pxl8_save_destroy(pxl8_save* save);
|
||||
|
||||
pxl8_result pxl8_save_delete(pxl8_save* save, u8 slot);
|
||||
bool pxl8_save_exists(pxl8_save* save, u8 slot);
|
||||
void pxl8_save_free(u8* data);
|
||||
const char* pxl8_save_get_directory(pxl8_save* save);
|
||||
pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_out);
|
||||
pxl8_result pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
1605
src/pxl8_script.c
1605
src/pxl8_script.c
File diff suppressed because it is too large
Load diff
|
|
@ -1,44 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_cart.h"
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_sfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_script pxl8_script;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_script* pxl8_script_create(bool repl_mode);
|
||||
void pxl8_script_destroy(pxl8_script* script);
|
||||
|
||||
const char* pxl8_script_get_last_error(pxl8_script* script);
|
||||
bool pxl8_script_is_incomplete_input(pxl8_script* script);
|
||||
|
||||
void pxl8_script_set_cart_path(pxl8_script* script, const char* cart_path, const char* original_cwd);
|
||||
void pxl8_script_set_gfx(pxl8_script* script, pxl8_gfx* gfx);
|
||||
void pxl8_script_set_input(pxl8_script* script, pxl8_input_state* input);
|
||||
void pxl8_script_set_rng(pxl8_script* script, void* rng);
|
||||
void pxl8_script_set_sfx(pxl8_script* script, pxl8_sfx_mixer* mixer);
|
||||
void pxl8_script_set_sys(pxl8_script* script, void* sys);
|
||||
|
||||
pxl8_result pxl8_script_call_function(pxl8_script* script, const char* name);
|
||||
pxl8_result pxl8_script_call_function_f32(pxl8_script* script, const char* name, f32 arg);
|
||||
bool pxl8_script_check_reload(pxl8_script* script);
|
||||
pxl8_result pxl8_script_eval(pxl8_script* script, const char* code);
|
||||
pxl8_result pxl8_script_eval_repl(pxl8_script* script, const char* code);
|
||||
pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart);
|
||||
pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path);
|
||||
pxl8_result pxl8_script_load_module(pxl8_script* script, const char* module_name);
|
||||
pxl8_result pxl8_script_run_fennel_file(pxl8_script* script, const char* filename);
|
||||
pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename);
|
||||
|
||||
u32 pxl8_script_serialize_globals(pxl8_script* script, u8** out_data);
|
||||
void pxl8_script_deserialize_globals(pxl8_script* script, const u8* data, u32 size);
|
||||
void pxl8_script_free_serialized(u8* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
405
src/pxl8_sdl3.c
405
src/pxl8_sdl3.c
|
|
@ -1,405 +0,0 @@
|
|||
#include "pxl8_sdl3.h"
|
||||
|
||||
#define SDL_MAIN_USE_CALLBACKS
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_main.h>
|
||||
|
||||
#include "pxl8_color.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_sys.h"
|
||||
|
||||
typedef struct pxl8_sdl3_context {
|
||||
SDL_Texture* framebuffer;
|
||||
SDL_Renderer* renderer;
|
||||
SDL_Window* window;
|
||||
|
||||
u32* rgba_buffer;
|
||||
size_t rgba_buffer_size;
|
||||
} pxl8_sdl3_context;
|
||||
|
||||
static void* sdl3_create(i32 render_w, i32 render_h,
|
||||
const char* title, i32 win_w, i32 win_h) {
|
||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)SDL_calloc(1, sizeof(pxl8_sdl3_context));
|
||||
if (!ctx) {
|
||||
pxl8_error("Failed to allocate SDL3 context");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx->window = SDL_CreateWindow(title, win_w, win_h, SDL_WINDOW_RESIZABLE);
|
||||
if (!ctx->window) {
|
||||
pxl8_error("Failed to create window: %s", SDL_GetError());
|
||||
SDL_free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx->renderer = SDL_CreateRenderer(ctx->window, NULL);
|
||||
if (!ctx->renderer) {
|
||||
pxl8_error("Failed to create renderer: %s", SDL_GetError());
|
||||
SDL_DestroyWindow(ctx->window);
|
||||
SDL_free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!SDL_SetRenderVSync(ctx->renderer, 1)) {
|
||||
pxl8_error("Failed to set vsync: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
ctx->framebuffer = SDL_CreateTexture(ctx->renderer,
|
||||
SDL_PIXELFORMAT_RGBA32,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
render_w, render_h);
|
||||
if (!ctx->framebuffer) {
|
||||
pxl8_error("Failed to create framebuffer texture: %s", SDL_GetError());
|
||||
SDL_DestroyRenderer(ctx->renderer);
|
||||
SDL_DestroyWindow(ctx->window);
|
||||
SDL_free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_SetTextureScaleMode(ctx->framebuffer, SDL_SCALEMODE_NEAREST);
|
||||
|
||||
ctx->rgba_buffer = NULL;
|
||||
ctx->rgba_buffer_size = 0;
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static void sdl3_destroy(void* platform_data) {
|
||||
if (!platform_data) return;
|
||||
|
||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
||||
|
||||
if (ctx->rgba_buffer) SDL_free(ctx->rgba_buffer);
|
||||
if (ctx->framebuffer) SDL_DestroyTexture(ctx->framebuffer);
|
||||
if (ctx->renderer) SDL_DestroyRenderer(ctx->renderer);
|
||||
if (ctx->window) SDL_DestroyWindow(ctx->window);
|
||||
|
||||
SDL_free(ctx);
|
||||
}
|
||||
|
||||
static u64 sdl3_get_ticks(void) {
|
||||
return SDL_GetTicksNS();
|
||||
}
|
||||
|
||||
|
||||
static void sdl3_present(void* platform_data) {
|
||||
if (!platform_data) return;
|
||||
|
||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
||||
|
||||
SDL_SetRenderDrawColor(ctx->renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(ctx->renderer);
|
||||
|
||||
if (ctx->framebuffer) {
|
||||
SDL_RenderTexture(ctx->renderer, ctx->framebuffer, NULL, NULL);
|
||||
}
|
||||
|
||||
SDL_RenderPresent(ctx->renderer);
|
||||
}
|
||||
|
||||
static void sdl3_upload_texture(void* platform_data, const u8* pixels, u32 w, u32 h,
|
||||
const u32* palette, u32 bpp) {
|
||||
if (!platform_data || !pixels) return;
|
||||
|
||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
||||
size_t needed_size = w * h;
|
||||
|
||||
if (ctx->rgba_buffer_size < needed_size) {
|
||||
u32* new_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, needed_size * 4);
|
||||
if (!new_buffer) return;
|
||||
ctx->rgba_buffer = new_buffer;
|
||||
ctx->rgba_buffer_size = needed_size;
|
||||
}
|
||||
|
||||
if (bpp == 2) {
|
||||
const u16* pixels16 = (const u16*)pixels;
|
||||
for (u32 i = 0; i < w * h; i++) {
|
||||
ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(pixels16[i]);
|
||||
}
|
||||
} else {
|
||||
for (u32 i = 0; i < w * h; i++) {
|
||||
ctx->rgba_buffer[i] = palette[pixels[i]];
|
||||
}
|
||||
}
|
||||
|
||||
SDL_UpdateTexture(ctx->framebuffer, NULL, ctx->rgba_buffer, w * 4);
|
||||
}
|
||||
|
||||
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
|
||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_AUDIO)) {
|
||||
pxl8_error("SDL_Init failed: %s", SDL_GetError());
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
pxl8* sys = pxl8_create(&pxl8_hal_sdl3);
|
||||
if (!sys) {
|
||||
pxl8_error("Failed to create pxl8 system");
|
||||
SDL_Quit();
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
pxl8_result result = pxl8_init(sys, argc, argv);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_destroy(sys);
|
||||
SDL_Quit();
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
*appstate = sys;
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
SDL_AppResult SDL_AppIterate(void* appstate) {
|
||||
pxl8* sys = (pxl8*)appstate;
|
||||
if (!sys) {
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
pxl8_result update_result = pxl8_update(sys);
|
||||
if (update_result != PXL8_OK) {
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
pxl8_result frame_result = pxl8_frame(sys);
|
||||
if (frame_result != PXL8_OK) {
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
return pxl8_is_running(sys) ? SDL_APP_CONTINUE : SDL_APP_SUCCESS;
|
||||
}
|
||||
|
||||
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) {
|
||||
pxl8* sys = (pxl8*)appstate;
|
||||
if (!sys) {
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
pxl8_input_state* input = pxl8_get_input(sys);
|
||||
if (!input) {
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
switch (event->type) {
|
||||
case SDL_EVENT_QUIT:
|
||||
pxl8_set_running(sys, false);
|
||||
break;
|
||||
|
||||
case SDL_EVENT_KEY_DOWN: {
|
||||
SDL_Scancode scancode = event->key.scancode;
|
||||
if (scancode < PXL8_MAX_KEYS) {
|
||||
if (!input->keys_down[scancode]) {
|
||||
input->keys_pressed[scancode] = true;
|
||||
}
|
||||
input->keys_down[scancode] = true;
|
||||
input->keys_released[scancode] = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_KEY_UP: {
|
||||
SDL_Scancode scancode = event->key.scancode;
|
||||
if (scancode < PXL8_MAX_KEYS) {
|
||||
input->keys_down[scancode] = false;
|
||||
input->keys_pressed[scancode] = false;
|
||||
input->keys_released[scancode] = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN: {
|
||||
u8 button = event->button.button - 1;
|
||||
if (button < 3) {
|
||||
if (!input->mouse_buttons_down[button]) {
|
||||
input->mouse_buttons_pressed[button] = true;
|
||||
}
|
||||
input->mouse_buttons_down[button] = true;
|
||||
input->mouse_buttons_released[button] = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP: {
|
||||
u8 button = event->button.button - 1;
|
||||
if (button < 3) {
|
||||
input->mouse_buttons_down[button] = false;
|
||||
input->mouse_buttons_pressed[button] = false;
|
||||
input->mouse_buttons_released[button] = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_MOUSE_MOTION: {
|
||||
pxl8_gfx* gfx = pxl8_get_gfx(sys);
|
||||
if (!gfx) break;
|
||||
|
||||
input->mouse_dx = (i32)event->motion.xrel;
|
||||
input->mouse_dy = (i32)event->motion.yrel;
|
||||
|
||||
i32 window_mouse_x = (i32)event->motion.x;
|
||||
i32 window_mouse_y = (i32)event->motion.y;
|
||||
|
||||
SDL_Window* window = SDL_GetWindowFromID(event->motion.windowID);
|
||||
if (!window) break;
|
||||
|
||||
i32 window_width, window_height;
|
||||
SDL_GetWindowSize(window, &window_width, &window_height);
|
||||
|
||||
i32 render_w = pxl8_gfx_get_width(gfx);
|
||||
i32 render_h = pxl8_gfx_get_height(gfx);
|
||||
|
||||
pxl8_bounds window_bounds = {0, 0, window_width, window_height};
|
||||
pxl8_viewport vp = pxl8_gfx_viewport(window_bounds, render_w, render_h);
|
||||
|
||||
input->mouse_x = (i32)((window_mouse_x - vp.offset_x) / vp.scale);
|
||||
input->mouse_y = (i32)((window_mouse_y - vp.offset_y) / vp.scale);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_MOUSE_WHEEL: {
|
||||
input->mouse_wheel_x = (i32)event->wheel.x;
|
||||
input->mouse_wheel_y = (i32)event->wheel.y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
void SDL_AppQuit(void* appstate, SDL_AppResult result) {
|
||||
(void)result;
|
||||
|
||||
pxl8* sys = (pxl8*)appstate;
|
||||
if (sys) {
|
||||
pxl8_quit(sys);
|
||||
pxl8_destroy(sys);
|
||||
}
|
||||
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
static void sdl3_set_relative_mouse_mode(void* platform_data, bool enabled) {
|
||||
if (!platform_data) return;
|
||||
|
||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
||||
if (!SDL_SetWindowRelativeMouseMode(ctx->window, enabled)) {
|
||||
pxl8_error("Failed to set relative mouse mode: %s", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
static void sdl3_set_cursor(void* platform_data, u32 cursor) {
|
||||
if (!platform_data) return;
|
||||
|
||||
SDL_SystemCursor sdl_cursor;
|
||||
switch (cursor) {
|
||||
case PXL8_CURSOR_ARROW:
|
||||
sdl_cursor = SDL_SYSTEM_CURSOR_DEFAULT;
|
||||
break;
|
||||
case PXL8_CURSOR_HAND:
|
||||
sdl_cursor = SDL_SYSTEM_CURSOR_POINTER;
|
||||
break;
|
||||
default:
|
||||
sdl_cursor = SDL_SYSTEM_CURSOR_DEFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
SDL_Cursor* cursor_obj = SDL_CreateSystemCursor(sdl_cursor);
|
||||
if (cursor_obj) {
|
||||
SDL_SetCursor(cursor_obj);
|
||||
}
|
||||
}
|
||||
|
||||
static void sdl3_center_cursor(void* platform_data) {
|
||||
if (!platform_data) return;
|
||||
|
||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
||||
i32 w, h;
|
||||
if (SDL_GetWindowSize(ctx->window, &w, &h)) {
|
||||
SDL_WarpMouseInWindow(ctx->window, w / 2, h / 2);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct pxl8_sdl3_audio {
|
||||
SDL_AudioStream* stream;
|
||||
i32 sample_rate;
|
||||
i32 channels;
|
||||
} pxl8_sdl3_audio;
|
||||
|
||||
static void* sdl3_audio_create(i32 sample_rate, i32 channels) {
|
||||
pxl8_sdl3_audio* audio = (pxl8_sdl3_audio*)SDL_calloc(1, sizeof(pxl8_sdl3_audio));
|
||||
if (!audio) return NULL;
|
||||
|
||||
SDL_AudioSpec spec = {
|
||||
.freq = sample_rate,
|
||||
.channels = channels,
|
||||
.format = SDL_AUDIO_F32
|
||||
};
|
||||
|
||||
audio->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL, NULL);
|
||||
if (!audio->stream) {
|
||||
pxl8_error("Failed to open audio device: %s", SDL_GetError());
|
||||
SDL_free(audio);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
audio->sample_rate = sample_rate;
|
||||
audio->channels = channels;
|
||||
|
||||
return audio;
|
||||
}
|
||||
|
||||
static void sdl3_audio_destroy(void* audio_handle) {
|
||||
if (!audio_handle) return;
|
||||
|
||||
pxl8_sdl3_audio* audio = (pxl8_sdl3_audio*)audio_handle;
|
||||
if (audio->stream) {
|
||||
SDL_DestroyAudioStream(audio->stream);
|
||||
}
|
||||
SDL_free(audio);
|
||||
}
|
||||
|
||||
static void sdl3_audio_start(void* audio_handle) {
|
||||
if (!audio_handle) return;
|
||||
|
||||
pxl8_sdl3_audio* audio = (pxl8_sdl3_audio*)audio_handle;
|
||||
SDL_ResumeAudioStreamDevice(audio->stream);
|
||||
}
|
||||
|
||||
static void sdl3_audio_stop(void* audio_handle) {
|
||||
if (!audio_handle) return;
|
||||
|
||||
pxl8_sdl3_audio* audio = (pxl8_sdl3_audio*)audio_handle;
|
||||
SDL_PauseAudioStreamDevice(audio->stream);
|
||||
}
|
||||
|
||||
static bool sdl3_upload_audio(void* audio_handle, const f32* stereo_samples, i32 sample_count) {
|
||||
if (!audio_handle || !stereo_samples) return false;
|
||||
|
||||
pxl8_sdl3_audio* audio = (pxl8_sdl3_audio*)audio_handle;
|
||||
return SDL_PutAudioStreamData(audio->stream, stereo_samples,
|
||||
sample_count * audio->channels * sizeof(f32));
|
||||
}
|
||||
|
||||
static i32 sdl3_audio_queued(void* audio_handle) {
|
||||
if (!audio_handle) return 0;
|
||||
|
||||
pxl8_sdl3_audio* audio = (pxl8_sdl3_audio*)audio_handle;
|
||||
i32 bytes = SDL_GetAudioStreamQueued(audio->stream);
|
||||
return bytes / (audio->channels * sizeof(f32));
|
||||
}
|
||||
|
||||
const pxl8_hal pxl8_hal_sdl3 = {
|
||||
.create = sdl3_create,
|
||||
.destroy = sdl3_destroy,
|
||||
.get_ticks = sdl3_get_ticks,
|
||||
.center_cursor = sdl3_center_cursor,
|
||||
.present = sdl3_present,
|
||||
.set_cursor = sdl3_set_cursor,
|
||||
.set_relative_mouse_mode = sdl3_set_relative_mouse_mode,
|
||||
.upload_texture = sdl3_upload_texture,
|
||||
.audio_create = sdl3_audio_create,
|
||||
.audio_destroy = sdl3_audio_destroy,
|
||||
.audio_start = sdl3_audio_start,
|
||||
.audio_stop = sdl3_audio_stop,
|
||||
.upload_audio = sdl3_upload_audio,
|
||||
.audio_queued = sdl3_audio_queued,
|
||||
};
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_hal.h"
|
||||
|
||||
extern const pxl8_hal pxl8_hal_sdl3;
|
||||
1121
src/pxl8_sfx.c
1121
src/pxl8_sfx.c
File diff suppressed because it is too large
Load diff
141
src/pxl8_sfx.h
141
src/pxl8_sfx.h
|
|
@ -1,141 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_hal.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define PXL8_SFX_BUFFER_SIZE 1024
|
||||
#define PXL8_SFX_MAX_CONTEXTS 8
|
||||
#define PXL8_SFX_MAX_DELAY_SAMPLES 48000
|
||||
#define PXL8_SFX_MAX_VOICES 16
|
||||
#define PXL8_SFX_SAMPLE_RATE 48000
|
||||
|
||||
typedef struct pxl8_sfx_context pxl8_sfx_context;
|
||||
typedef struct pxl8_sfx_mixer pxl8_sfx_mixer;
|
||||
typedef struct pxl8_sfx_node pxl8_sfx_node;
|
||||
|
||||
typedef enum pxl8_sfx_filter_type {
|
||||
PXL8_SFX_FILTER_BANDPASS = 0,
|
||||
PXL8_SFX_FILTER_HIGHPASS,
|
||||
PXL8_SFX_FILTER_LOWPASS,
|
||||
PXL8_SFX_FILTER_NONE
|
||||
} pxl8_sfx_filter_type;
|
||||
|
||||
typedef enum pxl8_sfx_lfo_target {
|
||||
PXL8_SFX_LFO_AMPLITUDE = 0,
|
||||
PXL8_SFX_LFO_FILTER,
|
||||
PXL8_SFX_LFO_PITCH
|
||||
} pxl8_sfx_lfo_target;
|
||||
|
||||
typedef enum pxl8_sfx_node_type {
|
||||
PXL8_SFX_NODE_COMPRESSOR,
|
||||
PXL8_SFX_NODE_DELAY,
|
||||
PXL8_SFX_NODE_REVERB
|
||||
} pxl8_sfx_node_type;
|
||||
|
||||
typedef enum pxl8_sfx_waveform {
|
||||
PXL8_SFX_WAVE_NOISE = 0,
|
||||
PXL8_SFX_WAVE_PULSE,
|
||||
PXL8_SFX_WAVE_SAW,
|
||||
PXL8_SFX_WAVE_SINE,
|
||||
PXL8_SFX_WAVE_SQUARE,
|
||||
PXL8_SFX_WAVE_TRIANGLE
|
||||
} pxl8_sfx_waveform;
|
||||
|
||||
typedef struct pxl8_sfx_adsr {
|
||||
f32 attack;
|
||||
f32 decay;
|
||||
f32 sustain;
|
||||
f32 release;
|
||||
} pxl8_sfx_adsr;
|
||||
|
||||
typedef struct pxl8_sfx_compressor_config {
|
||||
f32 attack;
|
||||
f32 ratio;
|
||||
f32 release;
|
||||
f32 threshold;
|
||||
} pxl8_sfx_compressor_config;
|
||||
|
||||
typedef struct pxl8_sfx_delay_config {
|
||||
f32 feedback;
|
||||
f32 mix;
|
||||
u32 time_l;
|
||||
u32 time_r;
|
||||
} pxl8_sfx_delay_config;
|
||||
|
||||
typedef struct pxl8_sfx_reverb_config {
|
||||
f32 damping;
|
||||
f32 mix;
|
||||
f32 room;
|
||||
} pxl8_sfx_reverb_config;
|
||||
|
||||
typedef struct pxl8_sfx_voice_params {
|
||||
pxl8_sfx_adsr amp_env;
|
||||
pxl8_sfx_adsr filter_env;
|
||||
pxl8_sfx_filter_type filter_type;
|
||||
pxl8_sfx_lfo_target lfo_target;
|
||||
pxl8_sfx_waveform lfo_waveform;
|
||||
pxl8_sfx_waveform waveform;
|
||||
f32 filter_cutoff;
|
||||
f32 filter_env_depth;
|
||||
f32 filter_resonance;
|
||||
f32 fx_send;
|
||||
f32 lfo_depth;
|
||||
f32 lfo_rate;
|
||||
f32 pulse_width;
|
||||
} pxl8_sfx_voice_params;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_sfx_node* pxl8_sfx_compressor_create(pxl8_sfx_compressor_config cfg);
|
||||
void pxl8_sfx_compressor_set_attack(pxl8_sfx_node* node, f32 attack);
|
||||
void pxl8_sfx_compressor_set_ratio(pxl8_sfx_node* node, f32 ratio);
|
||||
void pxl8_sfx_compressor_set_release(pxl8_sfx_node* node, f32 release);
|
||||
void pxl8_sfx_compressor_set_threshold(pxl8_sfx_node* node, f32 threshold);
|
||||
|
||||
void pxl8_sfx_context_append_node(pxl8_sfx_context* ctx, pxl8_sfx_node* node);
|
||||
pxl8_sfx_context* pxl8_sfx_context_create(void);
|
||||
void pxl8_sfx_context_destroy(pxl8_sfx_context* ctx);
|
||||
pxl8_sfx_node* pxl8_sfx_context_get_head(pxl8_sfx_context* ctx);
|
||||
f32 pxl8_sfx_context_get_volume(const pxl8_sfx_context* ctx);
|
||||
void pxl8_sfx_context_insert_node(pxl8_sfx_context* ctx, pxl8_sfx_node* after, pxl8_sfx_node* node);
|
||||
void pxl8_sfx_context_remove_node(pxl8_sfx_context* ctx, pxl8_sfx_node* node);
|
||||
void pxl8_sfx_context_set_volume(pxl8_sfx_context* ctx, f32 volume);
|
||||
|
||||
pxl8_sfx_node* pxl8_sfx_delay_create(pxl8_sfx_delay_config cfg);
|
||||
void pxl8_sfx_delay_set_feedback(pxl8_sfx_node* node, f32 feedback);
|
||||
void pxl8_sfx_delay_set_mix(pxl8_sfx_node* node, f32 mix);
|
||||
void pxl8_sfx_delay_set_time(pxl8_sfx_node* node, u32 time_l, u32 time_r);
|
||||
|
||||
#define PXL8_SFX_EVENT_NOTE_ON 1
|
||||
#define PXL8_SFX_EVENT_NOTE_OFF 2
|
||||
|
||||
typedef void (*pxl8_sfx_event_callback)(u8 event_type, u8 context_id, u8 note, f32 volume, void* userdata);
|
||||
|
||||
void pxl8_sfx_mixer_attach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx);
|
||||
void pxl8_sfx_mixer_clear(pxl8_sfx_mixer* mixer);
|
||||
pxl8_sfx_mixer* pxl8_sfx_mixer_create(const pxl8_hal* hal);
|
||||
void pxl8_sfx_mixer_destroy(pxl8_sfx_mixer* mixer);
|
||||
void pxl8_sfx_mixer_detach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx);
|
||||
f32 pxl8_sfx_mixer_get_master_volume(const pxl8_sfx_mixer* mixer);
|
||||
void pxl8_sfx_mixer_process(pxl8_sfx_mixer* mixer);
|
||||
void pxl8_sfx_mixer_set_event_callback(pxl8_sfx_mixer* mixer, pxl8_sfx_event_callback cb, void* userdata);
|
||||
void pxl8_sfx_mixer_set_master_volume(pxl8_sfx_mixer* mixer, f32 volume);
|
||||
|
||||
void pxl8_sfx_node_destroy(pxl8_sfx_node* node);
|
||||
f32 pxl8_sfx_note_to_freq(u8 note);
|
||||
u16 pxl8_sfx_play_note(pxl8_sfx_context* ctx, u8 note, const pxl8_sfx_voice_params* params, f32 volume, f32 duration);
|
||||
void pxl8_sfx_release_voice(pxl8_sfx_context* ctx, u16 voice_id);
|
||||
|
||||
pxl8_sfx_node* pxl8_sfx_reverb_create(pxl8_sfx_reverb_config cfg);
|
||||
void pxl8_sfx_reverb_set_damping(pxl8_sfx_node* node, f32 damping);
|
||||
void pxl8_sfx_reverb_set_mix(pxl8_sfx_node* node, f32 mix);
|
||||
void pxl8_sfx_reverb_set_room(pxl8_sfx_node* node, f32 room);
|
||||
|
||||
void pxl8_sfx_stop_all(pxl8_sfx_context* ctx);
|
||||
void pxl8_sfx_stop_voice(pxl8_sfx_context* ctx, u16 voice_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_hal.h"
|
||||
#include "pxl8_io.h"
|
||||
#include "pxl8_sfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8 pxl8;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8* pxl8_create(const pxl8_hal* hal);
|
||||
void pxl8_destroy(pxl8* sys);
|
||||
|
||||
pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]);
|
||||
pxl8_result pxl8_update(pxl8* sys);
|
||||
pxl8_result pxl8_frame(pxl8* sys);
|
||||
void pxl8_quit(pxl8* sys);
|
||||
|
||||
f32 pxl8_get_fps(const pxl8* sys);
|
||||
pxl8_gfx* pxl8_get_gfx(const pxl8* sys);
|
||||
pxl8_input_state* pxl8_get_input(const pxl8* sys);
|
||||
pxl8_size pxl8_get_resolution_dimensions(pxl8_resolution resolution);
|
||||
pxl8_sfx_mixer* pxl8_get_sfx_mixer(const pxl8* sys);
|
||||
bool pxl8_is_running(const pxl8* sys);
|
||||
|
||||
void pxl8_center_cursor(pxl8* sys);
|
||||
void pxl8_set_cursor(pxl8* sys, pxl8_cursor cursor);
|
||||
void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled);
|
||||
void pxl8_set_running(pxl8* sys, bool running);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,614 +0,0 @@
|
|||
#include "pxl8_tilemap.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_ase.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_macros.h"
|
||||
#include "pxl8_tilesheet.h"
|
||||
|
||||
struct pxl8_tilesheet {
|
||||
u8* data;
|
||||
bool* tile_valid;
|
||||
u32 height;
|
||||
u32 tile_size;
|
||||
u32 tiles_per_row;
|
||||
u32 total_tiles;
|
||||
u32 width;
|
||||
pxl8_pixel_mode pixel_mode;
|
||||
u32 ref_count;
|
||||
pxl8_tile_animation* animations;
|
||||
u32 animation_count;
|
||||
pxl8_tile_properties* properties;
|
||||
};
|
||||
|
||||
struct pxl8_tilemap_layer {
|
||||
u32 allocated_chunks;
|
||||
u32 chunk_count;
|
||||
pxl8_tile_chunk** chunks;
|
||||
u32 chunks_high;
|
||||
u32 chunks_wide;
|
||||
u8 opacity;
|
||||
bool visible;
|
||||
};
|
||||
|
||||
struct pxl8_tilemap {
|
||||
u32 active_layers;
|
||||
i32 camera_x;
|
||||
i32 camera_y;
|
||||
u32 height;
|
||||
pxl8_tilemap_layer layers[PXL8_MAX_TILE_LAYERS];
|
||||
u32 tile_size;
|
||||
pxl8_tilesheet* tilesheet;
|
||||
u32 tile_user_data_capacity;
|
||||
void** tile_user_data;
|
||||
u32 width;
|
||||
};
|
||||
|
||||
static inline u32 pxl8_chunk_index(u32 x, u32 y, u32 chunks_wide) {
|
||||
return (y >> 4) * chunks_wide + (x >> 4);
|
||||
}
|
||||
|
||||
static pxl8_tile_chunk* pxl8_get_or_create_chunk(pxl8_tilemap_layer* layer, u32 x, u32 y) {
|
||||
u32 chunk_x = x >> 4;
|
||||
u32 chunk_y = y >> 4;
|
||||
u32 idx = chunk_y * layer->chunks_wide + chunk_x;
|
||||
|
||||
if (idx >= layer->chunks_wide * layer->chunks_high) return NULL;
|
||||
|
||||
if (!layer->chunks[idx]) {
|
||||
layer->chunks[idx] = calloc(1, sizeof(pxl8_tile_chunk));
|
||||
if (!layer->chunks[idx]) return NULL;
|
||||
|
||||
layer->chunks[idx]->chunk_x = chunk_x;
|
||||
layer->chunks[idx]->chunk_y = chunk_y;
|
||||
layer->chunks[idx]->empty = true;
|
||||
layer->allocated_chunks++;
|
||||
}
|
||||
|
||||
return layer->chunks[idx];
|
||||
}
|
||||
|
||||
pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size) {
|
||||
if (width > PXL8_MAX_TILEMAP_WIDTH || height > PXL8_MAX_TILEMAP_HEIGHT) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_tilemap* tilemap = calloc(1, sizeof(pxl8_tilemap));
|
||||
if (!tilemap) return NULL;
|
||||
|
||||
tilemap->width = width;
|
||||
tilemap->height = height;
|
||||
tilemap->tile_size = tile_size ? tile_size : PXL8_TILE_SIZE;
|
||||
tilemap->active_layers = 1;
|
||||
|
||||
tilemap->tilesheet = pxl8_tilesheet_create(tilemap->tile_size);
|
||||
if (!tilemap->tilesheet) {
|
||||
free(tilemap);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tilemap->tile_user_data = NULL;
|
||||
tilemap->tile_user_data_capacity = 0;
|
||||
|
||||
u32 chunks_wide = (width + PXL8_CHUNK_SIZE - 1) / PXL8_CHUNK_SIZE;
|
||||
u32 chunks_high = (height + PXL8_CHUNK_SIZE - 1) / PXL8_CHUNK_SIZE;
|
||||
|
||||
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
|
||||
pxl8_tilemap_layer* layer = &tilemap->layers[i];
|
||||
layer->chunks_wide = chunks_wide;
|
||||
layer->chunks_high = chunks_high;
|
||||
layer->chunk_count = chunks_wide * chunks_high;
|
||||
layer->visible = (i == 0);
|
||||
layer->opacity = 255;
|
||||
|
||||
layer->chunks = calloc(layer->chunk_count, sizeof(pxl8_tile_chunk*));
|
||||
if (!layer->chunks) {
|
||||
for (u32 j = 0; j < i; j++) {
|
||||
free(tilemap->layers[j].chunks);
|
||||
}
|
||||
if (tilemap->tilesheet) pxl8_tilesheet_destroy(tilemap->tilesheet);
|
||||
free(tilemap);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return tilemap;
|
||||
}
|
||||
|
||||
void pxl8_tilemap_destroy(pxl8_tilemap* tilemap) {
|
||||
if (!tilemap) return;
|
||||
|
||||
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
|
||||
pxl8_tilemap_layer* layer = &tilemap->layers[i];
|
||||
if (layer->chunks) {
|
||||
for (u32 j = 0; j < layer->chunk_count; j++) {
|
||||
if (layer->chunks[j]) {
|
||||
free(layer->chunks[j]);
|
||||
}
|
||||
}
|
||||
free(layer->chunks);
|
||||
}
|
||||
}
|
||||
|
||||
if (tilemap->tilesheet) pxl8_tilesheet_unref(tilemap->tilesheet);
|
||||
if (tilemap->tile_user_data) free(tilemap->tile_user_data);
|
||||
|
||||
free(tilemap);
|
||||
}
|
||||
|
||||
u32 pxl8_tilemap_get_width(const pxl8_tilemap* tilemap) {
|
||||
return tilemap ? tilemap->width : 0;
|
||||
}
|
||||
|
||||
u32 pxl8_tilemap_get_height(const pxl8_tilemap* tilemap) {
|
||||
return tilemap ? tilemap->height : 0;
|
||||
}
|
||||
|
||||
u32 pxl8_tilemap_get_tile_size(const pxl8_tilemap* tilemap) {
|
||||
return tilemap ? tilemap->tile_size : 0;
|
||||
}
|
||||
|
||||
void pxl8_tilemap_set_tile_user_data(pxl8_tilemap* tilemap, u16 tile_id, void* user_data) {
|
||||
if (!tilemap || tile_id == 0) return;
|
||||
|
||||
if (tile_id >= tilemap->tile_user_data_capacity) {
|
||||
u32 new_capacity = tile_id + 64;
|
||||
void** new_data = realloc(tilemap->tile_user_data, new_capacity * sizeof(void*));
|
||||
if (!new_data) return;
|
||||
|
||||
for (u32 i = tilemap->tile_user_data_capacity; i < new_capacity; i++) {
|
||||
new_data[i] = NULL;
|
||||
}
|
||||
|
||||
tilemap->tile_user_data = new_data;
|
||||
tilemap->tile_user_data_capacity = new_capacity;
|
||||
}
|
||||
|
||||
tilemap->tile_user_data[tile_id] = user_data;
|
||||
}
|
||||
|
||||
void* pxl8_tilemap_get_tile_user_data(const pxl8_tilemap* tilemap, u16 tile_id) {
|
||||
if (!tilemap || tile_id == 0 || tile_id >= tilemap->tile_user_data_capacity) return NULL;
|
||||
return tilemap->tile_user_data[tile_id];
|
||||
}
|
||||
|
||||
pxl8_result pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet) {
|
||||
if (!tilemap || !tilesheet) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
if (tilemap->tilesheet) {
|
||||
pxl8_tilesheet_unref(tilemap->tilesheet);
|
||||
}
|
||||
|
||||
tilemap->tilesheet = tilesheet;
|
||||
pxl8_tilesheet_ref(tilesheet);
|
||||
|
||||
u32 tilesheet_size = pxl8_tilesheet_get_tile_size(tilesheet);
|
||||
if (tilesheet_size != tilemap->tile_size) {
|
||||
pxl8_warn("Tilesheet tile size (%d) differs from tilemap tile size (%d)",
|
||||
tilesheet_size, tilemap->tile_size);
|
||||
tilemap->tile_size = tilesheet_size;
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_tilemap_set_tile(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 tile_id, u8 flags) {
|
||||
if (!tilemap) return PXL8_ERROR_NULL_POINTER;
|
||||
if (layer >= PXL8_MAX_TILE_LAYERS) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
if (x >= tilemap->width || y >= tilemap->height) return PXL8_ERROR_INVALID_COORDINATE;
|
||||
|
||||
pxl8_tilemap_layer* l = &tilemap->layers[layer];
|
||||
pxl8_tile_chunk* chunk = pxl8_get_or_create_chunk(l, x, y);
|
||||
if (!chunk) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
u32 local_x = x & PXL8_CHUNK_MASK;
|
||||
u32 local_y = y & PXL8_CHUNK_MASK;
|
||||
u32 idx = local_y * PXL8_CHUNK_SIZE + local_x;
|
||||
|
||||
chunk->tiles[idx] = pxl8_tile_pack(tile_id, flags, 0);
|
||||
|
||||
if (tile_id != 0) {
|
||||
chunk->empty = false;
|
||||
}
|
||||
|
||||
if (layer >= tilemap->active_layers) {
|
||||
tilemap->active_layers = layer + 1;
|
||||
l->visible = true;
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_tile pxl8_tilemap_get_tile(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y) {
|
||||
if (!tilemap || layer >= PXL8_MAX_TILE_LAYERS) return 0;
|
||||
if (x >= tilemap->width || y >= tilemap->height) return 0;
|
||||
|
||||
const pxl8_tilemap_layer* l = &tilemap->layers[layer];
|
||||
u32 chunk_idx = pxl8_chunk_index(x, y, l->chunks_wide);
|
||||
|
||||
if (chunk_idx >= l->chunk_count || !l->chunks[chunk_idx]) return 0;
|
||||
|
||||
const pxl8_tile_chunk* chunk = l->chunks[chunk_idx];
|
||||
u32 local_x = x & PXL8_CHUNK_MASK;
|
||||
u32 local_y = y & PXL8_CHUNK_MASK;
|
||||
u32 idx = local_y * PXL8_CHUNK_SIZE + local_x;
|
||||
|
||||
return chunk->tiles[idx];
|
||||
}
|
||||
|
||||
void pxl8_tilemap_set_camera(pxl8_tilemap* tilemap, i32 x, i32 y) {
|
||||
if (!tilemap) return;
|
||||
tilemap->camera_x = x;
|
||||
tilemap->camera_y = y;
|
||||
}
|
||||
|
||||
void pxl8_tilemap_get_view(const pxl8_tilemap* tilemap, const pxl8_gfx* gfx, pxl8_tilemap_view* view) {
|
||||
if (!tilemap || !gfx || !view) return;
|
||||
|
||||
view->x = -tilemap->camera_x;
|
||||
view->y = -tilemap->camera_y;
|
||||
view->width = pxl8_gfx_get_width(gfx);
|
||||
view->height = pxl8_gfx_get_height(gfx);
|
||||
|
||||
view->tile_start_x = pxl8_max(0, tilemap->camera_x / (i32)tilemap->tile_size);
|
||||
view->tile_start_y = pxl8_max(0, tilemap->camera_y / (i32)tilemap->tile_size);
|
||||
view->tile_end_x = pxl8_min((i32)tilemap->width,
|
||||
(tilemap->camera_x + view->width) / (i32)tilemap->tile_size + 1);
|
||||
view->tile_end_y = pxl8_min((i32)tilemap->height,
|
||||
(tilemap->camera_y + view->height) / (i32)tilemap->tile_size + 1);
|
||||
}
|
||||
|
||||
void pxl8_tilemap_render_tile(const pxl8_tilemap* tilemap, pxl8_gfx* gfx, u16 tile_id, i32 x, i32 y, u8 flags) {
|
||||
if (!tilemap || !gfx || !tilemap->tilesheet) return;
|
||||
pxl8_tilesheet_render_tile(tilemap->tilesheet, gfx, tile_id, x, y, flags);
|
||||
}
|
||||
|
||||
void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx* gfx, u32 layer) {
|
||||
if (!tilemap || !gfx || layer >= tilemap->active_layers) return;
|
||||
if (!tilemap->tilesheet) {
|
||||
pxl8_warn("No tilesheet set for tilemap");
|
||||
return;
|
||||
}
|
||||
|
||||
const pxl8_tilemap_layer* l = &tilemap->layers[layer];
|
||||
if (!l->visible) return;
|
||||
|
||||
i32 view_left = tilemap->camera_x / tilemap->tile_size;
|
||||
i32 view_top = tilemap->camera_y / tilemap->tile_size;
|
||||
i32 view_right = (tilemap->camera_x + pxl8_gfx_get_width(gfx)) / tilemap->tile_size + 1;
|
||||
i32 view_bottom = (tilemap->camera_y + pxl8_gfx_get_height(gfx)) / tilemap->tile_size + 1;
|
||||
|
||||
u32 chunk_left = pxl8_max(0, view_left >> 4);
|
||||
u32 chunk_top = pxl8_max(0, view_top >> 4);
|
||||
u32 chunk_right = pxl8_min((tilemap->width + 15) >> 4, (u32)((view_right >> 4) + 1));
|
||||
u32 chunk_bottom = pxl8_min((tilemap->height + 15) >> 4, (u32)((view_bottom >> 4) + 1));
|
||||
|
||||
for (u32 cy = chunk_top; cy < chunk_bottom; cy++) {
|
||||
for (u32 cx = chunk_left; cx < chunk_right; cx++) {
|
||||
u32 chunk_idx = cy * l->chunks_wide + cx;
|
||||
if (chunk_idx >= l->chunk_count || !l->chunks[chunk_idx]) continue;
|
||||
|
||||
const pxl8_tile_chunk* chunk = l->chunks[chunk_idx];
|
||||
if (chunk->empty) continue;
|
||||
|
||||
u32 tile_start_x = pxl8_max(0, view_left - (i32)(cx << 4));
|
||||
u32 tile_end_x = pxl8_min(PXL8_CHUNK_SIZE, view_right - (i32)(cx << 4));
|
||||
u32 tile_start_y = pxl8_max(0, view_top - (i32)(cy << 4));
|
||||
u32 tile_end_y = pxl8_min(PXL8_CHUNK_SIZE, view_bottom - (i32)(cy << 4));
|
||||
|
||||
for (u32 ty = tile_start_y; ty < tile_end_y; ty++) {
|
||||
for (u32 tx = tile_start_x; tx < tile_end_x; tx++) {
|
||||
u32 idx = ty * PXL8_CHUNK_SIZE + tx;
|
||||
pxl8_tile tile = chunk->tiles[idx];
|
||||
u16 tile_id = pxl8_tile_get_id(tile);
|
||||
if (tile_id == 0) continue;
|
||||
|
||||
i32 world_x = (cx << 4) + tx;
|
||||
i32 world_y = (cy << 4) + ty;
|
||||
i32 screen_x = world_x * tilemap->tile_size - tilemap->camera_x;
|
||||
i32 screen_y = world_y * tilemap->tile_size - tilemap->camera_y;
|
||||
|
||||
u8 flags = pxl8_tile_get_flags(tile);
|
||||
if (flags & PXL8_TILE_ANIMATED && tilemap->tilesheet) {
|
||||
tile_id = pxl8_tilesheet_get_animated_frame(tilemap->tilesheet, tile_id);
|
||||
}
|
||||
|
||||
pxl8_tilesheet_render_tile(tilemap->tilesheet, gfx, tile_id, screen_x, screen_y, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_tilemap_render(const pxl8_tilemap* tilemap, pxl8_gfx* gfx) {
|
||||
if (!tilemap || !gfx) return;
|
||||
|
||||
for (u32 layer = 0; layer < tilemap->active_layers; layer++) {
|
||||
pxl8_tilemap_render_layer(tilemap, gfx, layer);
|
||||
}
|
||||
}
|
||||
|
||||
bool pxl8_tilemap_is_solid(const pxl8_tilemap* tilemap, u32 x, u32 y) {
|
||||
if (!tilemap) return true;
|
||||
|
||||
u32 tile_x = x / tilemap->tile_size;
|
||||
u32 tile_y = y / tilemap->tile_size;
|
||||
|
||||
for (u32 layer = 0; layer < tilemap->active_layers; layer++) {
|
||||
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, tile_x, tile_y);
|
||||
if (pxl8_tile_get_flags(tile) & PXL8_TILE_SOLID) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool pxl8_tilemap_check_collision(const pxl8_tilemap* tilemap, i32 x, i32 y, i32 w, i32 h) {
|
||||
if (!tilemap) return true;
|
||||
|
||||
i32 left = x / tilemap->tile_size;
|
||||
i32 top = y / tilemap->tile_size;
|
||||
i32 right = (x + w - 1) / tilemap->tile_size;
|
||||
i32 bottom = (y + h - 1) / tilemap->tile_size;
|
||||
|
||||
for (i32 ty = top; ty <= bottom; ty++) {
|
||||
for (i32 tx = left; tx <= right; tx++) {
|
||||
if (tx < 0 || tx >= (i32)tilemap->width ||
|
||||
ty < 0 || ty >= (i32)tilemap->height) return true;
|
||||
|
||||
for (u32 layer = 0; layer < tilemap->active_layers; layer++) {
|
||||
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, tx, ty);
|
||||
if (pxl8_tile_get_flags(tile) & PXL8_TILE_SOLID) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y) {
|
||||
if (!tilemap) return 0;
|
||||
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, x, y);
|
||||
return pxl8_tile_get_id(tile);
|
||||
}
|
||||
|
||||
void pxl8_tilemap_update(pxl8_tilemap* tilemap, f32 delta_time) {
|
||||
if (!tilemap || !tilemap->tilesheet) return;
|
||||
pxl8_tilesheet_update_animations(tilemap->tilesheet, delta_time);
|
||||
}
|
||||
|
||||
static u8 pxl8_tilemap_get_neighbors(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 match_id) {
|
||||
u8 neighbors = 0;
|
||||
|
||||
if (y > 0 && pxl8_tilemap_get_tile_id(tilemap, layer, x, y - 1) == match_id)
|
||||
neighbors |= 1 << 0;
|
||||
if (x < tilemap->width - 1 && pxl8_tilemap_get_tile_id(tilemap, layer, x + 1, y) == match_id)
|
||||
neighbors |= 1 << 1;
|
||||
if (y < tilemap->height - 1 && pxl8_tilemap_get_tile_id(tilemap, layer, x, y + 1) == match_id)
|
||||
neighbors |= 1 << 2;
|
||||
if (x > 0 && pxl8_tilemap_get_tile_id(tilemap, layer, x - 1, y) == match_id)
|
||||
neighbors |= 1 << 3;
|
||||
|
||||
if (y > 0 && x < tilemap->width - 1 && pxl8_tilemap_get_tile_id(tilemap, layer, x + 1, y - 1) == match_id)
|
||||
neighbors |= 1 << 4;
|
||||
if (y < tilemap->height - 1 && x < tilemap->width - 1 && pxl8_tilemap_get_tile_id(tilemap, layer, x + 1, y + 1) == match_id)
|
||||
neighbors |= 1 << 5;
|
||||
if (y < tilemap->height - 1 && x > 0 && pxl8_tilemap_get_tile_id(tilemap, layer, x - 1, y + 1) == match_id)
|
||||
neighbors |= 1 << 6;
|
||||
if (y > 0 && x > 0 && pxl8_tilemap_get_tile_id(tilemap, layer, x - 1, y - 1) == match_id)
|
||||
neighbors |= 1 << 7;
|
||||
|
||||
return neighbors;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_tilemap_set_tile_auto(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y,
|
||||
u16 base_tile_id, u8 flags) {
|
||||
if (!tilemap || !tilemap->tilesheet) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
pxl8_result result = pxl8_tilemap_set_tile(tilemap, layer, x, y, base_tile_id, flags | PXL8_TILE_AUTOTILE);
|
||||
if (result != PXL8_OK) return result;
|
||||
|
||||
pxl8_tilemap_update_autotiles(tilemap, layer,
|
||||
x > 0 ? x - 1 : x, y > 0 ? y - 1 : y,
|
||||
x < tilemap->width - 1 ? 3 : 2,
|
||||
y < tilemap->height - 1 ? 3 : 2);
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_tilemap_update_autotiles(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u32 w, u32 h) {
|
||||
if (!tilemap || !tilemap->tilesheet) return;
|
||||
|
||||
for (u32 ty = y; ty < y + h && ty < tilemap->height; ty++) {
|
||||
for (u32 tx = x; tx < x + w && tx < tilemap->width; tx++) {
|
||||
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, tx, ty);
|
||||
u8 flags = pxl8_tile_get_flags(tile);
|
||||
|
||||
if (flags & PXL8_TILE_AUTOTILE) {
|
||||
u16 base_id = pxl8_tile_get_id(tile);
|
||||
u8 neighbors = pxl8_tilemap_get_neighbors(tilemap, layer, tx, ty, base_id);
|
||||
u16 new_id = pxl8_tilesheet_apply_autotile(tilemap->tilesheet, base_id, neighbors);
|
||||
|
||||
if (new_id != base_id) {
|
||||
pxl8_tilemap_layer* l = &tilemap->layers[layer];
|
||||
pxl8_tile_chunk* chunk = pxl8_get_or_create_chunk(l, tx, ty);
|
||||
if (chunk) {
|
||||
u32 local_x = tx & PXL8_CHUNK_MASK;
|
||||
u32 local_y = ty & PXL8_CHUNK_MASK;
|
||||
u32 idx = local_y * PXL8_CHUNK_SIZE + local_x;
|
||||
chunk->tiles[idx] = pxl8_tile_pack(new_id, flags, pxl8_tile_get_palette(tile));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 pxl8_tilemap_get_memory_usage(const pxl8_tilemap* tilemap) {
|
||||
if (!tilemap) return 0;
|
||||
|
||||
u32 total = sizeof(pxl8_tilemap);
|
||||
|
||||
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
|
||||
const pxl8_tilemap_layer* layer = &tilemap->layers[i];
|
||||
total += layer->chunk_count * sizeof(pxl8_tile_chunk*);
|
||||
total += layer->allocated_chunks * sizeof(pxl8_tile_chunk);
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
void pxl8_tilemap_compress(pxl8_tilemap* tilemap) {
|
||||
if (!tilemap) return;
|
||||
|
||||
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
|
||||
pxl8_tilemap_layer* layer = &tilemap->layers[i];
|
||||
|
||||
for (u32 j = 0; j < layer->chunk_count; j++) {
|
||||
pxl8_tile_chunk* chunk = layer->chunks[j];
|
||||
if (!chunk) continue;
|
||||
|
||||
bool has_tiles = false;
|
||||
for (u32 k = 0; k < PXL8_CHUNK_SIZE * PXL8_CHUNK_SIZE; k++) {
|
||||
if (pxl8_tile_get_id(chunk->tiles[k]) != 0) {
|
||||
has_tiles = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_tiles) {
|
||||
free(chunk);
|
||||
layer->chunks[j] = NULL;
|
||||
layer->allocated_chunks--;
|
||||
} else {
|
||||
chunk->empty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_result pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u32 layer) {
|
||||
if (!tilemap || !filepath) return PXL8_ERROR_NULL_POINTER;
|
||||
if (!tilemap->tilesheet) return PXL8_ERROR_NULL_POINTER;
|
||||
if (layer >= PXL8_MAX_TILE_LAYERS) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
pxl8_ase_file ase_file = {0};
|
||||
pxl8_result result = pxl8_ase_load(filepath, &ase_file);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to load ASE file: %s", filepath);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (ase_file.tileset_count == 0) {
|
||||
pxl8_error("ASE file has no tileset - must be created as Tilemap in Aseprite: %s", filepath);
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
pxl8_ase_tileset* tileset = &ase_file.tilesets[0];
|
||||
|
||||
if (tileset->tile_width != tilemap->tile_size || tileset->tile_height != tilemap->tile_size) {
|
||||
pxl8_error("Tileset tile size (%ux%u) doesn't match tilemap tile size (%u)",
|
||||
tileset->tile_width, tileset->tile_height, tilemap->tile_size);
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
pxl8_info("Loading tilemap from %s: %u tiles, %ux%u each",
|
||||
filepath, tileset->tile_count, tileset->tile_width, tileset->tile_height);
|
||||
|
||||
if (!(tileset->flags & 2)) {
|
||||
pxl8_error("Tileset has no embedded tiles - external tilesets not yet supported");
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!tileset->pixels) {
|
||||
pxl8_error("Tileset has no pixel data");
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
u32 tiles_per_row = 16;
|
||||
u32 tilesheet_rows = (tileset->tile_count + tiles_per_row - 1) / tiles_per_row;
|
||||
u32 tilesheet_width = tiles_per_row * tilemap->tile_size;
|
||||
u32 tilesheet_height = tilesheet_rows * tilemap->tile_size;
|
||||
|
||||
if (tilemap->tilesheet->data) free(tilemap->tilesheet->data);
|
||||
tilemap->tilesheet->data = calloc(tilesheet_width * tilesheet_height, 1);
|
||||
if (!tilemap->tilesheet->data) {
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
tilemap->tilesheet->width = tilesheet_width;
|
||||
tilemap->tilesheet->height = tilesheet_height;
|
||||
tilemap->tilesheet->tiles_per_row = tiles_per_row;
|
||||
tilemap->tilesheet->total_tiles = tileset->tile_count;
|
||||
tilemap->tilesheet->pixel_mode = PXL8_PIXEL_INDEXED;
|
||||
|
||||
if (tilemap->tilesheet->tile_valid) free(tilemap->tilesheet->tile_valid);
|
||||
tilemap->tilesheet->tile_valid = calloc(tileset->tile_count + 1, sizeof(bool));
|
||||
|
||||
for (u32 i = 0; i < tileset->tile_count; i++) {
|
||||
u32 sheet_row = i / tiles_per_row;
|
||||
u32 sheet_col = i % tiles_per_row;
|
||||
u32 src_offset = i * tilemap->tile_size * tilemap->tile_size;
|
||||
|
||||
for (u32 y = 0; y < tilemap->tile_size; y++) {
|
||||
for (u32 x = 0; x < tilemap->tile_size; x++) {
|
||||
u32 dst_x = sheet_col * tilemap->tile_size + x;
|
||||
u32 dst_y = sheet_row * tilemap->tile_size + y;
|
||||
u32 dst_idx = dst_y * tilesheet_width + dst_x;
|
||||
u32 src_idx = src_offset + y * tilemap->tile_size + x;
|
||||
|
||||
if (src_idx < tileset->pixels_size && dst_idx < tilesheet_width * tilesheet_height) {
|
||||
tilemap->tilesheet->data[dst_idx] = tileset->pixels[src_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
tilemap->tilesheet->tile_valid[i] = true;
|
||||
}
|
||||
|
||||
pxl8_info("Loaded %u tiles into tilesheet (%ux%u)", tileset->tile_count, tilesheet_width, tilesheet_height);
|
||||
|
||||
if (ase_file.frame_count == 0 || !ase_file.frames) {
|
||||
pxl8_error("ASE file has no frames");
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
pxl8_ase_frame* frame = &ase_file.frames[0];
|
||||
pxl8_ase_cel* tilemap_cel = NULL;
|
||||
|
||||
for (u32 i = 0; i < frame->cel_count; i++) {
|
||||
if (frame->cels[i].cel_type == 3) {
|
||||
tilemap_cel = &frame->cels[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tilemap_cel || !tilemap_cel->tilemap.tiles) {
|
||||
pxl8_error("No tilemap cel found in frame 0");
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
pxl8_info("Found tilemap cel: %ux%u tiles", tilemap_cel->tilemap.width, tilemap_cel->tilemap.height);
|
||||
|
||||
for (u32 ty = 0; ty < tilemap_cel->tilemap.height && ty < tilemap->height; ty++) {
|
||||
for (u32 tx = 0; tx < tilemap_cel->tilemap.width && tx < tilemap->width; tx++) {
|
||||
u32 tile_idx = ty * tilemap_cel->tilemap.width + tx;
|
||||
u32 tile_value = tilemap_cel->tilemap.tiles[tile_idx];
|
||||
u16 tile_id = (u16)(tile_value & tilemap_cel->tilemap.tile_id_mask);
|
||||
u8 flags = 0;
|
||||
if (tile_value & tilemap_cel->tilemap.x_flip_mask) flags |= PXL8_TILE_FLIP_X;
|
||||
if (tile_value & tilemap_cel->tilemap.y_flip_mask) flags |= PXL8_TILE_FLIP_Y;
|
||||
pxl8_tilemap_set_tile(tilemap, layer, tx, ty, tile_id, flags);
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_tilesheet.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define PXL8_TILE_SIZE 8
|
||||
#define PXL8_MAX_TILEMAP_WIDTH 256
|
||||
#define PXL8_MAX_TILEMAP_HEIGHT 256
|
||||
#define PXL8_MAX_TILE_LAYERS 4
|
||||
#define PXL8_CHUNK_SIZE 16
|
||||
#define PXL8_CHUNK_MASK 15
|
||||
|
||||
typedef enum pxl8_tile_flags {
|
||||
PXL8_TILE_FLIP_X = 1 << 0,
|
||||
PXL8_TILE_FLIP_Y = 1 << 1,
|
||||
PXL8_TILE_SOLID = 1 << 2,
|
||||
PXL8_TILE_TRIGGER = 1 << 3,
|
||||
PXL8_TILE_ANIMATED = 1 << 4,
|
||||
PXL8_TILE_AUTOTILE = 1 << 5,
|
||||
} pxl8_tile_flags;
|
||||
|
||||
#define PXL8_TILE_ID_MASK 0x0000FFFF
|
||||
#define PXL8_TILE_FLAGS_MASK 0x00FF0000
|
||||
#define PXL8_TILE_PAL_MASK 0xFF000000
|
||||
#define PXL8_TILE_ID_SHIFT 0
|
||||
#define PXL8_TILE_FLAGS_SHIFT 16
|
||||
#define PXL8_TILE_PAL_SHIFT 24
|
||||
|
||||
typedef u32 pxl8_tile;
|
||||
|
||||
static inline pxl8_tile pxl8_tile_pack(u16 id, u8 flags, u8 palette_offset) {
|
||||
return (u32)id | ((u32)flags << 16) | ((u32)palette_offset << 24);
|
||||
}
|
||||
|
||||
static inline u16 pxl8_tile_get_id(pxl8_tile tile) {
|
||||
return tile & PXL8_TILE_ID_MASK;
|
||||
}
|
||||
|
||||
static inline u8 pxl8_tile_get_flags(pxl8_tile tile) {
|
||||
return (tile & PXL8_TILE_FLAGS_MASK) >> PXL8_TILE_FLAGS_SHIFT;
|
||||
}
|
||||
|
||||
static inline u8 pxl8_tile_get_palette(pxl8_tile tile) {
|
||||
return (tile & PXL8_TILE_PAL_MASK) >> PXL8_TILE_PAL_SHIFT;
|
||||
}
|
||||
|
||||
typedef struct pxl8_tile_animation {
|
||||
u16 current_frame;
|
||||
f32 frame_duration;
|
||||
u16 frame_count;
|
||||
u16* frames;
|
||||
f32 time_accumulator;
|
||||
} pxl8_tile_animation;
|
||||
|
||||
typedef struct pxl8_tile_properties {
|
||||
i16 collision_offset_x;
|
||||
i16 collision_offset_y;
|
||||
u16 collision_height;
|
||||
u16 collision_width;
|
||||
u32 property_flags;
|
||||
void* user_data;
|
||||
} pxl8_tile_properties;
|
||||
|
||||
typedef struct pxl8_autotile_rule {
|
||||
u8 neighbor_mask;
|
||||
u16 tile_id;
|
||||
} pxl8_autotile_rule;
|
||||
|
||||
typedef struct pxl8_tile_chunk {
|
||||
u32 chunk_x;
|
||||
u32 chunk_y;
|
||||
bool empty;
|
||||
pxl8_tile tiles[PXL8_CHUNK_SIZE * PXL8_CHUNK_SIZE];
|
||||
} pxl8_tile_chunk;
|
||||
|
||||
typedef struct pxl8_tilemap_layer pxl8_tilemap_layer;
|
||||
typedef struct pxl8_tilemap pxl8_tilemap;
|
||||
|
||||
typedef struct pxl8_tilemap_view {
|
||||
i32 height;
|
||||
i32 tile_end_x, tile_end_y;
|
||||
i32 tile_start_x, tile_start_y;
|
||||
i32 width;
|
||||
i32 x, y;
|
||||
} pxl8_tilemap_view;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size);
|
||||
void pxl8_tilemap_destroy(pxl8_tilemap* tilemap);
|
||||
|
||||
u32 pxl8_tilemap_get_height(const pxl8_tilemap* tilemap);
|
||||
u32 pxl8_tilemap_get_memory_usage(const pxl8_tilemap* tilemap);
|
||||
pxl8_tile pxl8_tilemap_get_tile(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);
|
||||
u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);
|
||||
u32 pxl8_tilemap_get_tile_size(const pxl8_tilemap* tilemap);
|
||||
void* pxl8_tilemap_get_tile_user_data(const pxl8_tilemap* tilemap, u16 tile_id);
|
||||
void pxl8_tilemap_get_view(const pxl8_tilemap* tilemap, const pxl8_gfx* gfx, pxl8_tilemap_view* view);
|
||||
u32 pxl8_tilemap_get_width(const pxl8_tilemap* tilemap);
|
||||
|
||||
void pxl8_tilemap_set_camera(pxl8_tilemap* tilemap, i32 x, i32 y);
|
||||
pxl8_result pxl8_tilemap_set_tile(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 tile_id, u8 flags);
|
||||
pxl8_result pxl8_tilemap_set_tile_auto(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 base_tile_id, u8 flags);
|
||||
void pxl8_tilemap_set_tile_user_data(pxl8_tilemap* tilemap, u16 tile_id, void* user_data);
|
||||
pxl8_result pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet);
|
||||
|
||||
pxl8_result pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u32 layer);
|
||||
|
||||
bool pxl8_tilemap_check_collision(const pxl8_tilemap* tilemap, i32 x, i32 y, i32 w, i32 h);
|
||||
void pxl8_tilemap_compress(pxl8_tilemap* tilemap);
|
||||
bool pxl8_tilemap_is_solid(const pxl8_tilemap* tilemap, u32 x, u32 y);
|
||||
void pxl8_tilemap_render(const pxl8_tilemap* tilemap, pxl8_gfx* gfx);
|
||||
void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx* gfx, u32 layer);
|
||||
void pxl8_tilemap_render_tile(const pxl8_tilemap* tilemap, pxl8_gfx* gfx, u16 tile_id, i32 x, i32 y, u8 flags);
|
||||
void pxl8_tilemap_update(pxl8_tilemap* tilemap, f32 delta_time);
|
||||
void pxl8_tilemap_update_autotiles(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u32 w, u32 h);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,404 +0,0 @@
|
|||
#include "pxl8_tilesheet.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_ase.h"
|
||||
#include "pxl8_color.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_tilemap.h"
|
||||
|
||||
struct pxl8_tilesheet {
|
||||
u8* data;
|
||||
bool* tile_valid;
|
||||
|
||||
u32 height;
|
||||
u32 tile_size;
|
||||
u32 tiles_per_row;
|
||||
u32 total_tiles;
|
||||
u32 width;
|
||||
|
||||
pxl8_pixel_mode pixel_mode;
|
||||
u32 ref_count;
|
||||
|
||||
pxl8_tile_animation* animations;
|
||||
u32 animation_count;
|
||||
|
||||
pxl8_tile_properties* properties;
|
||||
|
||||
pxl8_autotile_rule** autotile_rules;
|
||||
u32* autotile_rule_counts;
|
||||
};
|
||||
|
||||
pxl8_tilesheet* pxl8_tilesheet_create(u32 tile_size) {
|
||||
pxl8_tilesheet* tilesheet = calloc(1, sizeof(pxl8_tilesheet));
|
||||
if (!tilesheet) return NULL;
|
||||
|
||||
tilesheet->tile_size = tile_size;
|
||||
tilesheet->ref_count = 1;
|
||||
|
||||
return tilesheet;
|
||||
}
|
||||
|
||||
void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet) {
|
||||
if (!tilesheet) return;
|
||||
|
||||
if (tilesheet->data) {
|
||||
free(tilesheet->data);
|
||||
}
|
||||
|
||||
if (tilesheet->tile_valid) {
|
||||
free(tilesheet->tile_valid);
|
||||
}
|
||||
|
||||
if (tilesheet->animations) {
|
||||
for (u32 i = 0; i < tilesheet->animation_count; i++) {
|
||||
if (tilesheet->animations[i].frames) {
|
||||
free(tilesheet->animations[i].frames);
|
||||
}
|
||||
}
|
||||
free(tilesheet->animations);
|
||||
}
|
||||
|
||||
if (tilesheet->properties) {
|
||||
free(tilesheet->properties);
|
||||
}
|
||||
|
||||
if (tilesheet->autotile_rules) {
|
||||
for (u32 i = 0; i <= tilesheet->total_tiles; i++) {
|
||||
if (tilesheet->autotile_rules[i]) {
|
||||
free(tilesheet->autotile_rules[i]);
|
||||
}
|
||||
}
|
||||
free(tilesheet->autotile_rules);
|
||||
free(tilesheet->autotile_rule_counts);
|
||||
}
|
||||
|
||||
free(tilesheet);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx* gfx) {
|
||||
if (!tilesheet || !filepath || !gfx) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
pxl8_ase_file ase_file;
|
||||
pxl8_result result = pxl8_ase_load(filepath, &ase_file);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to load tilesheet: %s", filepath);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (tilesheet->data) {
|
||||
free(tilesheet->data);
|
||||
}
|
||||
|
||||
u32 width = ase_file.header.width;
|
||||
u32 height = ase_file.header.height;
|
||||
|
||||
if (ase_file.header.grid_width > 0 && ase_file.header.grid_height > 0) {
|
||||
tilesheet->tile_size = ase_file.header.grid_width;
|
||||
pxl8_info("Using Aseprite grid size: %dx%d",
|
||||
ase_file.header.grid_width, ase_file.header.grid_height);
|
||||
}
|
||||
|
||||
tilesheet->width = width;
|
||||
tilesheet->height = height;
|
||||
tilesheet->tiles_per_row = width / tilesheet->tile_size;
|
||||
tilesheet->total_tiles = (width / tilesheet->tile_size) * (height / tilesheet->tile_size);
|
||||
tilesheet->pixel_mode = pxl8_gfx_get_pixel_mode(gfx);
|
||||
|
||||
u32 pixel_count = width * height;
|
||||
u16 ase_depth = ase_file.header.color_depth;
|
||||
bool gfx_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_HICOLOR);
|
||||
|
||||
size_t data_size = pixel_count * pxl8_bytes_per_pixel(tilesheet->pixel_mode);
|
||||
tilesheet->data = malloc(data_size);
|
||||
if (!tilesheet->data) {
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if (ase_file.frame_count > 0 && ase_file.frames[0].pixels) {
|
||||
const u8* src = ase_file.frames[0].pixels;
|
||||
|
||||
if (ase_depth == 8 && !gfx_hicolor) {
|
||||
memcpy(tilesheet->data, src, pixel_count);
|
||||
} else if (ase_depth == 32 && gfx_hicolor) {
|
||||
u16* dst = (u16*)tilesheet->data;
|
||||
const u32* rgba = (const u32*)src;
|
||||
for (u32 i = 0; i < pixel_count; i++) {
|
||||
u32 c = rgba[i];
|
||||
u8 a = (c >> 24) & 0xFF;
|
||||
if (a == 0) {
|
||||
dst[i] = 0;
|
||||
} else {
|
||||
dst[i] = pxl8_rgba32_to_rgb565(c);
|
||||
}
|
||||
}
|
||||
} else if (ase_depth == 8 && gfx_hicolor) {
|
||||
pxl8_warn("Indexed ASE with hicolor gfx - storing as indexed");
|
||||
tilesheet->pixel_mode = PXL8_PIXEL_INDEXED;
|
||||
u8* new_data = realloc(tilesheet->data, pixel_count);
|
||||
if (!new_data) {
|
||||
free(tilesheet->data);
|
||||
tilesheet->data = NULL;
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
tilesheet->data = new_data;
|
||||
memcpy(tilesheet->data, src, pixel_count);
|
||||
} else {
|
||||
pxl8_error("Unsupported ASE color depth %d for gfx mode", ase_depth);
|
||||
free(tilesheet->data);
|
||||
tilesheet->data = NULL;
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
tilesheet->tile_valid = calloc(tilesheet->total_tiles + 1, sizeof(bool));
|
||||
if (!tilesheet->tile_valid) {
|
||||
free(tilesheet->data);
|
||||
tilesheet->data = NULL;
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
u32 valid_tiles = 0;
|
||||
bool is_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_HICOLOR);
|
||||
|
||||
for (u32 tile_id = 1; tile_id <= tilesheet->total_tiles; tile_id++) {
|
||||
u32 tile_x = ((tile_id - 1) % tilesheet->tiles_per_row) * tilesheet->tile_size;
|
||||
u32 tile_y = ((tile_id - 1) / tilesheet->tiles_per_row) * tilesheet->tile_size;
|
||||
|
||||
bool has_content = false;
|
||||
for (u32 py = 0; py < tilesheet->tile_size && !has_content; py++) {
|
||||
for (u32 px = 0; px < tilesheet->tile_size; px++) {
|
||||
u32 idx = (tile_y + py) * width + (tile_x + px);
|
||||
if (is_hicolor) {
|
||||
if (((u16*)tilesheet->data)[idx] != 0) {
|
||||
has_content = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (tilesheet->data[idx] != 0) {
|
||||
has_content = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (has_content) {
|
||||
tilesheet->tile_valid[tile_id] = true;
|
||||
valid_tiles++;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_info("Loaded tilesheet %s: %dx%d, %d valid tiles out of %d slots (%dx%d each)",
|
||||
filepath, width, height, valid_tiles, tilesheet->total_tiles,
|
||||
tilesheet->tile_size, tilesheet->tile_size);
|
||||
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_tilesheet_render_tile(const pxl8_tilesheet* tilesheet, pxl8_gfx* gfx,
|
||||
u16 tile_id, i32 x, i32 y, u8 flags) {
|
||||
if (!tilesheet || !gfx || !tilesheet->data) return;
|
||||
if (tile_id == 0 || tile_id > tilesheet->total_tiles) return;
|
||||
if (tilesheet->tile_valid && !tilesheet->tile_valid[tile_id]) return;
|
||||
|
||||
u32 tile_x = ((tile_id - 1) % tilesheet->tiles_per_row) * tilesheet->tile_size;
|
||||
u32 tile_y = ((tile_id - 1) / tilesheet->tiles_per_row) * tilesheet->tile_size;
|
||||
|
||||
for (u32 py = 0; py < tilesheet->tile_size; py++) {
|
||||
for (u32 px = 0; px < tilesheet->tile_size; px++) {
|
||||
u32 src_x = tile_x + px;
|
||||
u32 src_y = tile_y + py;
|
||||
|
||||
if (flags & PXL8_TILE_FLIP_X) src_x = tile_x + (tilesheet->tile_size - 1 - px);
|
||||
if (flags & PXL8_TILE_FLIP_Y) src_y = tile_y + (tilesheet->tile_size - 1 - py);
|
||||
|
||||
u32 src_idx = src_y * tilesheet->width + src_x;
|
||||
u8 color_idx = tilesheet->data[src_idx];
|
||||
|
||||
if (color_idx != 0) {
|
||||
i32 screen_x = x + px;
|
||||
i32 screen_y = y + py;
|
||||
|
||||
if (screen_x >= 0 && screen_x < pxl8_gfx_get_width(gfx) &&
|
||||
screen_y >= 0 && screen_y < pxl8_gfx_get_height(gfx)) {
|
||||
pxl8_pixel(gfx, screen_x, screen_y, color_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool pxl8_tilesheet_is_tile_valid(const pxl8_tilesheet* tilesheet, u16 tile_id) {
|
||||
if (!tilesheet || tile_id == 0 || tile_id > tilesheet->total_tiles) return false;
|
||||
return tilesheet->tile_valid ? tilesheet->tile_valid[tile_id] : true;
|
||||
}
|
||||
|
||||
void pxl8_tilesheet_ref(pxl8_tilesheet* tilesheet) {
|
||||
if (!tilesheet) return;
|
||||
tilesheet->ref_count++;
|
||||
}
|
||||
|
||||
void pxl8_tilesheet_unref(pxl8_tilesheet* tilesheet) {
|
||||
if (!tilesheet) return;
|
||||
|
||||
if (--tilesheet->ref_count == 0) {
|
||||
pxl8_tilesheet_destroy(tilesheet);
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_result pxl8_tilesheet_add_animation(pxl8_tilesheet* tilesheet, u16 base_tile_id,
|
||||
const u16* frames, u16 frame_count, f32 frame_duration) {
|
||||
if (!tilesheet || !frames || frame_count == 0) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
if (base_tile_id == 0 || base_tile_id > tilesheet->total_tiles) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
if (!tilesheet->animations) {
|
||||
tilesheet->animations = calloc(tilesheet->total_tiles + 1, sizeof(pxl8_tile_animation));
|
||||
if (!tilesheet->animations) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
pxl8_tile_animation* anim = &tilesheet->animations[base_tile_id];
|
||||
if (anim->frames) free(anim->frames);
|
||||
|
||||
anim->frames = malloc(frame_count * sizeof(u16));
|
||||
if (!anim->frames) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
memcpy(anim->frames, frames, frame_count * sizeof(u16));
|
||||
anim->frame_count = frame_count;
|
||||
anim->current_frame = 0;
|
||||
anim->frame_duration = frame_duration;
|
||||
anim->time_accumulator = 0;
|
||||
|
||||
tilesheet->animation_count++;
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_tilesheet_update_animations(pxl8_tilesheet* tilesheet, f32 delta_time) {
|
||||
if (!tilesheet || !tilesheet->animations) return;
|
||||
|
||||
for (u32 i = 1; i <= tilesheet->total_tiles; i++) {
|
||||
pxl8_tile_animation* anim = &tilesheet->animations[i];
|
||||
if (!anim->frames || anim->frame_count == 0) continue;
|
||||
|
||||
anim->time_accumulator += delta_time;
|
||||
while (anim->time_accumulator >= anim->frame_duration) {
|
||||
anim->time_accumulator -= anim->frame_duration;
|
||||
anim->current_frame = (anim->current_frame + 1) % anim->frame_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u16 pxl8_tilesheet_get_animated_frame(const pxl8_tilesheet* tilesheet, u16 tile_id) {
|
||||
if (!tilesheet || !tilesheet->animations || tile_id == 0 || tile_id > tilesheet->total_tiles) {
|
||||
return tile_id;
|
||||
}
|
||||
|
||||
const pxl8_tile_animation* anim = &tilesheet->animations[tile_id];
|
||||
if (!anim->frames || anim->frame_count == 0) return tile_id;
|
||||
|
||||
return anim->frames[anim->current_frame];
|
||||
}
|
||||
|
||||
pxl8_result pxl8_tilesheet_set_tile_pixels(pxl8_tilesheet* tilesheet, u16 tile_id, const u8* pixels) {
|
||||
if (!tilesheet || !pixels || tile_id == 0 || tile_id > tilesheet->total_tiles) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
if (!tilesheet->data) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
u32 tile_x = (tile_id - 1) % tilesheet->tiles_per_row;
|
||||
u32 tile_y = (tile_id - 1) / tilesheet->tiles_per_row;
|
||||
u32 bytes_per_pixel = pxl8_bytes_per_pixel(tilesheet->pixel_mode);
|
||||
|
||||
for (u32 py = 0; py < tilesheet->tile_size; py++) {
|
||||
for (u32 px = 0; px < tilesheet->tile_size; px++) {
|
||||
u32 src_idx = py * tilesheet->tile_size + px;
|
||||
u32 dst_x = tile_x * tilesheet->tile_size + px;
|
||||
u32 dst_y = tile_y * tilesheet->tile_size + py;
|
||||
u32 dst_idx = dst_y * tilesheet->width + dst_x;
|
||||
|
||||
if (bytes_per_pixel == 2) {
|
||||
((u16*)tilesheet->data)[dst_idx] = ((const u16*)pixels)[src_idx];
|
||||
} else {
|
||||
tilesheet->data[dst_idx] = pixels[src_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tilesheet->tile_valid) {
|
||||
tilesheet->tile_valid[tile_id] = true;
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_tilesheet_set_tile_property(pxl8_tilesheet* tilesheet, u16 tile_id,
|
||||
const pxl8_tile_properties* props) {
|
||||
if (!tilesheet || !props || tile_id == 0 || tile_id > tilesheet->total_tiles) return;
|
||||
|
||||
if (!tilesheet->properties) {
|
||||
tilesheet->properties = calloc(tilesheet->total_tiles + 1, sizeof(pxl8_tile_properties));
|
||||
if (!tilesheet->properties) return;
|
||||
}
|
||||
|
||||
tilesheet->properties[tile_id] = *props;
|
||||
}
|
||||
|
||||
const pxl8_tile_properties* pxl8_tilesheet_get_tile_property(const pxl8_tilesheet* tilesheet, u16 tile_id) {
|
||||
if (!tilesheet || !tilesheet->properties || tile_id == 0 || tile_id > tilesheet->total_tiles) {
|
||||
return NULL;
|
||||
}
|
||||
return &tilesheet->properties[tile_id];
|
||||
}
|
||||
|
||||
u32 pxl8_tilesheet_get_tile_size(const pxl8_tilesheet* tilesheet) {
|
||||
return tilesheet ? tilesheet->tile_size : 0;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_tilesheet_add_autotile_rule(pxl8_tilesheet* tilesheet, u16 base_tile_id,
|
||||
u8 neighbor_mask, u16 result_tile_id) {
|
||||
if (!tilesheet || base_tile_id == 0 || base_tile_id > tilesheet->total_tiles) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!tilesheet->autotile_rules) {
|
||||
tilesheet->autotile_rules = calloc(tilesheet->total_tiles + 1, sizeof(pxl8_autotile_rule*));
|
||||
tilesheet->autotile_rule_counts = calloc(tilesheet->total_tiles + 1, sizeof(u32));
|
||||
if (!tilesheet->autotile_rules || !tilesheet->autotile_rule_counts) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
u32 count = tilesheet->autotile_rule_counts[base_tile_id];
|
||||
pxl8_autotile_rule* new_rules = realloc(tilesheet->autotile_rules[base_tile_id],
|
||||
(count + 1) * sizeof(pxl8_autotile_rule));
|
||||
if (!new_rules) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
new_rules[count].neighbor_mask = neighbor_mask;
|
||||
new_rules[count].tile_id = result_tile_id;
|
||||
|
||||
tilesheet->autotile_rules[base_tile_id] = new_rules;
|
||||
tilesheet->autotile_rule_counts[base_tile_id]++;
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
u16 pxl8_tilesheet_apply_autotile(const pxl8_tilesheet* tilesheet, u16 base_tile_id, u8 neighbors) {
|
||||
if (!tilesheet || !tilesheet->autotile_rules || base_tile_id == 0 ||
|
||||
base_tile_id > tilesheet->total_tiles) {
|
||||
return base_tile_id;
|
||||
}
|
||||
|
||||
pxl8_autotile_rule* rules = tilesheet->autotile_rules[base_tile_id];
|
||||
u32 rule_count = tilesheet->autotile_rule_counts[base_tile_id];
|
||||
|
||||
for (u32 i = 0; i < rule_count; i++) {
|
||||
if (rules[i].neighbor_mask == neighbors) {
|
||||
return rules[i].tile_id;
|
||||
}
|
||||
}
|
||||
|
||||
return base_tile_id;
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_autotile_rule pxl8_autotile_rule;
|
||||
typedef struct pxl8_tile_animation pxl8_tile_animation;
|
||||
typedef struct pxl8_tile_properties pxl8_tile_properties;
|
||||
typedef struct pxl8_tilesheet pxl8_tilesheet;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_tilesheet* pxl8_tilesheet_create(u32 tile_size);
|
||||
void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet);
|
||||
|
||||
u16 pxl8_tilesheet_get_animated_frame(const pxl8_tilesheet* tilesheet, u16 tile_id);
|
||||
const pxl8_tile_properties* pxl8_tilesheet_get_tile_property(const pxl8_tilesheet* tilesheet, u16 tile_id);
|
||||
u32 pxl8_tilesheet_get_tile_size(const pxl8_tilesheet* tilesheet);
|
||||
bool pxl8_tilesheet_is_tile_valid(const pxl8_tilesheet* tilesheet, u16 tile_id);
|
||||
|
||||
pxl8_result pxl8_tilesheet_set_tile_pixels(pxl8_tilesheet* tilesheet, u16 tile_id, const u8* pixels);
|
||||
void pxl8_tilesheet_set_tile_property(pxl8_tilesheet* tilesheet, u16 tile_id, const pxl8_tile_properties* props);
|
||||
|
||||
pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx* gfx);
|
||||
void pxl8_tilesheet_ref(pxl8_tilesheet* tilesheet);
|
||||
void pxl8_tilesheet_unref(pxl8_tilesheet* tilesheet);
|
||||
|
||||
pxl8_result pxl8_tilesheet_add_animation(pxl8_tilesheet* tilesheet, u16 base_tile_id, const u16* frames, u16 frame_count, f32 frame_duration);
|
||||
pxl8_result pxl8_tilesheet_add_autotile_rule(pxl8_tilesheet* tilesheet, u16 base_tile_id, u8 neighbor_mask, u16 result_tile_id);
|
||||
u16 pxl8_tilesheet_apply_autotile(const pxl8_tilesheet* tilesheet, u16 base_tile_id, u8 neighbors);
|
||||
void pxl8_tilesheet_render_tile(const pxl8_tilesheet* tilesheet, pxl8_gfx* gfx, u16 tile_id, i32 x, i32 y, u8 flags);
|
||||
void pxl8_tilesheet_update_animations(pxl8_tilesheet* tilesheet, f32 delta_time);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,251 +0,0 @@
|
|||
#include "pxl8_transition.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_math.h"
|
||||
|
||||
pxl8_transition* pxl8_transition_create(pxl8_transition_type type, f32 duration) {
|
||||
if (duration <= 0.0f) {
|
||||
pxl8_error("Invalid transition duration: %f", duration);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_transition* transition = (pxl8_transition*)calloc(1, sizeof(pxl8_transition));
|
||||
if (!transition) {
|
||||
pxl8_error("Failed to allocate transition");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
transition->type = type;
|
||||
transition->duration = duration;
|
||||
transition->time = 0.0f;
|
||||
transition->active = false;
|
||||
transition->reverse = false;
|
||||
transition->color = 0xFF000000;
|
||||
transition->on_complete = NULL;
|
||||
transition->userdata = NULL;
|
||||
|
||||
return transition;
|
||||
}
|
||||
|
||||
void pxl8_transition_destroy(pxl8_transition* transition) {
|
||||
if (!transition) return;
|
||||
free(transition);
|
||||
}
|
||||
|
||||
f32 pxl8_transition_get_progress(const pxl8_transition* transition) {
|
||||
if (!transition) return 0.0f;
|
||||
|
||||
f32 t = transition->time / transition->duration;
|
||||
if (t < 0.0f) t = 0.0f;
|
||||
if (t > 1.0f) t = 1.0f;
|
||||
|
||||
return transition->reverse ? 1.0f - t : t;
|
||||
}
|
||||
|
||||
bool pxl8_transition_is_active(const pxl8_transition* transition) {
|
||||
return transition && transition->active;
|
||||
}
|
||||
|
||||
bool pxl8_transition_is_complete(const pxl8_transition* transition) {
|
||||
if (!transition) return true;
|
||||
return transition->time >= transition->duration;
|
||||
}
|
||||
|
||||
void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) {
|
||||
if (!transition || !gfx || !transition->active) return;
|
||||
|
||||
f32 progress = pxl8_transition_get_progress(transition);
|
||||
i32 width = pxl8_gfx_get_width(gfx);
|
||||
i32 height = pxl8_gfx_get_height(gfx);
|
||||
|
||||
switch (transition->type) {
|
||||
case PXL8_TRANSITION_FADE: {
|
||||
u8 alpha = (u8)(progress * 255.0f);
|
||||
u32 fade_color = (transition->color & 0x00FFFFFF) | (alpha << 24);
|
||||
|
||||
for (i32 y = 0; y < height; y++) {
|
||||
for (i32 x = 0; x < width; x++) {
|
||||
u32 bg = pxl8_get_pixel(gfx, x, y);
|
||||
u32 r_bg = (bg >> 16) & 0xFF;
|
||||
u32 g_bg = (bg >> 8) & 0xFF;
|
||||
u32 b_bg = bg & 0xFF;
|
||||
|
||||
u32 r_fg = (fade_color >> 16) & 0xFF;
|
||||
u32 g_fg = (fade_color >> 8) & 0xFF;
|
||||
u32 b_fg = fade_color & 0xFF;
|
||||
|
||||
u32 r = (r_bg * (255 - alpha) + r_fg * alpha) / 255;
|
||||
u32 g = (g_bg * (255 - alpha) + g_fg * alpha) / 255;
|
||||
u32 b = (b_bg * (255 - alpha) + b_fg * alpha) / 255;
|
||||
|
||||
pxl8_pixel(gfx, x, y, 0xFF000000 | (r << 16) | (g << 8) | b);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_TRANSITION_WIPE_LEFT: {
|
||||
i32 wipe_x = (i32)(width * progress);
|
||||
pxl8_rect_fill(gfx, 0, 0, wipe_x, height, transition->color);
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_TRANSITION_WIPE_RIGHT: {
|
||||
i32 wipe_x = (i32)(width * (1.0f - progress));
|
||||
pxl8_rect_fill(gfx, wipe_x, 0, width - wipe_x, height, transition->color);
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_TRANSITION_WIPE_UP: {
|
||||
i32 wipe_y = (i32)(height * progress);
|
||||
pxl8_rect_fill(gfx, 0, 0, width, wipe_y, transition->color);
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_TRANSITION_WIPE_DOWN: {
|
||||
i32 wipe_y = (i32)(height * (1.0f - progress));
|
||||
pxl8_rect_fill(gfx, 0, wipe_y, width, height - wipe_y, transition->color);
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_TRANSITION_CIRCLE_CLOSE: {
|
||||
i32 center_x = width / 2;
|
||||
i32 center_y = height / 2;
|
||||
i32 max_radius = (i32)sqrtf((f32)(center_x * center_x + center_y * center_y));
|
||||
i32 radius = (i32)(max_radius * (1.0f - progress));
|
||||
|
||||
for (i32 y = 0; y < height; y++) {
|
||||
for (i32 x = 0; x < width; x++) {
|
||||
i32 dx = x - center_x;
|
||||
i32 dy = y - center_y;
|
||||
i32 dist = (i32)sqrtf((f32)(dx * dx + dy * dy));
|
||||
if (dist > radius) {
|
||||
pxl8_pixel(gfx, x, y, transition->color);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_TRANSITION_CIRCLE_OPEN: {
|
||||
i32 center_x = width / 2;
|
||||
i32 center_y = height / 2;
|
||||
i32 max_radius = (i32)sqrtf((f32)(center_x * center_x + center_y * center_y));
|
||||
i32 radius = (i32)(max_radius * progress);
|
||||
|
||||
for (i32 y = 0; y < height; y++) {
|
||||
for (i32 x = 0; x < width; x++) {
|
||||
i32 dx = x - center_x;
|
||||
i32 dy = y - center_y;
|
||||
i32 dist = (i32)sqrtf((f32)(dx * dx + dy * dy));
|
||||
if (dist < radius) {
|
||||
pxl8_pixel(gfx, x, y, transition->color);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_TRANSITION_DISSOLVE: {
|
||||
u32 seed = 12345;
|
||||
for (i32 y = 0; y < height; y++) {
|
||||
for (i32 x = 0; x < width; x++) {
|
||||
seed = seed * 1103515245 + 12345;
|
||||
f32 noise = (f32)((seed / 65536) % 1000) / 1000.0f;
|
||||
if (noise < progress) {
|
||||
pxl8_pixel(gfx, x, y, transition->color);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_TRANSITION_PIXELATE: {
|
||||
i32 max_block_size = 32;
|
||||
i32 block_size = (i32)(max_block_size * progress);
|
||||
if (block_size < 1) block_size = 1;
|
||||
|
||||
pxl8_pixel_mode mode = pxl8_gfx_get_pixel_mode(gfx);
|
||||
bool has_fb = (mode == PXL8_PIXEL_HICOLOR)
|
||||
? (pxl8_gfx_get_framebuffer_hicolor(gfx) != NULL)
|
||||
: (pxl8_gfx_get_framebuffer_indexed(gfx) != NULL);
|
||||
if (!has_fb) break;
|
||||
|
||||
for (i32 y = 0; y < height; y += block_size) {
|
||||
for (i32 x = 0; x < width; x += block_size) {
|
||||
u32 color_sum_r = 0, color_sum_g = 0, color_sum_b = 0;
|
||||
i32 count = 0;
|
||||
|
||||
for (i32 by = 0; by < block_size && y + by < height; by++) {
|
||||
for (i32 bx = 0; bx < block_size && x + bx < width; bx++) {
|
||||
u32 color = pxl8_get_pixel(gfx, x + bx, y + by);
|
||||
color_sum_r += (color >> 16) & 0xFF;
|
||||
color_sum_g += (color >> 8) & 0xFF;
|
||||
color_sum_b += color & 0xFF;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
u32 avg_color = 0xFF000000 |
|
||||
((color_sum_r / count) << 16) |
|
||||
((color_sum_g / count) << 8) |
|
||||
(color_sum_b / count);
|
||||
|
||||
for (i32 by = 0; by < block_size && y + by < height; by++) {
|
||||
for (i32 bx = 0; bx < block_size && x + bx < width; bx++) {
|
||||
pxl8_pixel(gfx, x + bx, y + by, avg_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_transition_reset(pxl8_transition* transition) {
|
||||
if (!transition) return;
|
||||
transition->time = 0.0f;
|
||||
transition->active = false;
|
||||
}
|
||||
|
||||
void pxl8_transition_set_color(pxl8_transition* transition, u32 color) {
|
||||
if (!transition) return;
|
||||
transition->color = color;
|
||||
}
|
||||
|
||||
void pxl8_transition_set_reverse(pxl8_transition* transition, bool reverse) {
|
||||
if (!transition) return;
|
||||
transition->reverse = reverse;
|
||||
}
|
||||
|
||||
void pxl8_transition_start(pxl8_transition* transition) {
|
||||
if (!transition) return;
|
||||
transition->active = true;
|
||||
transition->time = 0.0f;
|
||||
}
|
||||
|
||||
void pxl8_transition_stop(pxl8_transition* transition) {
|
||||
if (!transition) return;
|
||||
transition->active = false;
|
||||
}
|
||||
|
||||
void pxl8_transition_update(pxl8_transition* transition, f32 dt) {
|
||||
if (!transition || !transition->active) return;
|
||||
|
||||
transition->time += dt;
|
||||
|
||||
if (transition->time >= transition->duration) {
|
||||
transition->time = transition->duration;
|
||||
transition->active = false;
|
||||
|
||||
if (transition->on_complete) {
|
||||
transition->on_complete(transition->userdata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef enum pxl8_transition_type {
|
||||
PXL8_TRANSITION_FADE,
|
||||
PXL8_TRANSITION_WIPE_LEFT,
|
||||
PXL8_TRANSITION_WIPE_RIGHT,
|
||||
PXL8_TRANSITION_WIPE_UP,
|
||||
PXL8_TRANSITION_WIPE_DOWN,
|
||||
PXL8_TRANSITION_CIRCLE_OPEN,
|
||||
PXL8_TRANSITION_CIRCLE_CLOSE,
|
||||
PXL8_TRANSITION_DISSOLVE,
|
||||
PXL8_TRANSITION_PIXELATE
|
||||
} pxl8_transition_type;
|
||||
|
||||
typedef struct pxl8_transition {
|
||||
pxl8_transition_type type;
|
||||
f32 duration;
|
||||
f32 time;
|
||||
bool active;
|
||||
bool reverse;
|
||||
|
||||
u32 color;
|
||||
|
||||
void (*on_complete)(void* userdata);
|
||||
void* userdata;
|
||||
} pxl8_transition;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_transition* pxl8_transition_create(pxl8_transition_type type, f32 duration);
|
||||
void pxl8_transition_destroy(pxl8_transition* transition);
|
||||
|
||||
f32 pxl8_transition_get_progress(const pxl8_transition* transition);
|
||||
bool pxl8_transition_is_active(const pxl8_transition* transition);
|
||||
bool pxl8_transition_is_complete(const pxl8_transition* transition);
|
||||
|
||||
void pxl8_transition_set_color(pxl8_transition* transition, u32 color);
|
||||
void pxl8_transition_set_reverse(pxl8_transition* transition, bool reverse);
|
||||
|
||||
void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx);
|
||||
void pxl8_transition_reset(pxl8_transition* transition);
|
||||
void pxl8_transition_start(pxl8_transition* transition);
|
||||
void pxl8_transition_stop(pxl8_transition* transition);
|
||||
void pxl8_transition_update(pxl8_transition* transition, f32 dt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
105
src/pxl8_types.h
105
src/pxl8_types.h
|
|
@ -1,105 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define PXL8_DEFAULT_ATLAS_ENTRY_CAPACITY 64
|
||||
#define PXL8_DEFAULT_ATLAS_SIZE 1024
|
||||
#define PXL8_DEFAULT_SPRITE_CACHE_CAPACITY 64
|
||||
#define PXL8_MAX_ERROR_SIZE 2048
|
||||
#define PXL8_MAX_KEYS 256
|
||||
#define PXL8_MAX_PALETTE_SIZE 256
|
||||
#define PXL8_MAX_PATH 256
|
||||
|
||||
typedef float f32;
|
||||
typedef double f64;
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
#if defined(__SIZEOF_INT128__)
|
||||
typedef __int128_t i128;
|
||||
typedef __uint128_t u128;
|
||||
#endif
|
||||
|
||||
typedef enum pxl8_pixel_mode {
|
||||
PXL8_PIXEL_INDEXED,
|
||||
PXL8_PIXEL_HICOLOR
|
||||
} pxl8_pixel_mode;
|
||||
|
||||
typedef enum pxl8_cursor {
|
||||
PXL8_CURSOR_ARROW,
|
||||
PXL8_CURSOR_HAND
|
||||
} pxl8_cursor;
|
||||
|
||||
typedef enum pxl8_resolution {
|
||||
PXL8_RESOLUTION_240x160,
|
||||
PXL8_RESOLUTION_320x180,
|
||||
PXL8_RESOLUTION_320x240,
|
||||
PXL8_RESOLUTION_640x360,
|
||||
PXL8_RESOLUTION_640x480,
|
||||
PXL8_RESOLUTION_800x600,
|
||||
PXL8_RESOLUTION_960x540
|
||||
} pxl8_resolution;
|
||||
|
||||
typedef enum pxl8_result {
|
||||
PXL8_OK = 0,
|
||||
PXL8_ERROR_ASE_INVALID_FRAME_MAGIC,
|
||||
PXL8_ERROR_ASE_INVALID_MAGIC,
|
||||
PXL8_ERROR_ASE_MALFORMED_CHUNK,
|
||||
PXL8_ERROR_ASE_TRUNCATED_FILE,
|
||||
PXL8_ERROR_FILE_NOT_FOUND,
|
||||
PXL8_ERROR_INITIALIZATION_FAILED,
|
||||
PXL8_ERROR_INVALID_ARGUMENT,
|
||||
PXL8_ERROR_INVALID_COORDINATE,
|
||||
PXL8_ERROR_INVALID_FORMAT,
|
||||
PXL8_ERROR_INVALID_SIZE,
|
||||
PXL8_ERROR_NOT_INITIALIZED,
|
||||
PXL8_ERROR_NULL_POINTER,
|
||||
PXL8_ERROR_OUT_OF_MEMORY,
|
||||
PXL8_ERROR_SCRIPT_ERROR,
|
||||
PXL8_ERROR_SYSTEM_FAILURE
|
||||
} pxl8_result;
|
||||
|
||||
typedef struct pxl8_bounds {
|
||||
i32 x, y;
|
||||
i32 w;
|
||||
i32 h;
|
||||
} pxl8_bounds;
|
||||
|
||||
typedef struct pxl8_input_state {
|
||||
bool keys_down[PXL8_MAX_KEYS];
|
||||
bool keys_pressed[PXL8_MAX_KEYS];
|
||||
bool keys_released[PXL8_MAX_KEYS];
|
||||
|
||||
bool mouse_buttons_down[3];
|
||||
bool mouse_buttons_pressed[3];
|
||||
bool mouse_buttons_released[3];
|
||||
i32 mouse_wheel_x;
|
||||
i32 mouse_wheel_y;
|
||||
i32 mouse_x;
|
||||
i32 mouse_y;
|
||||
i32 mouse_dx;
|
||||
i32 mouse_dy;
|
||||
bool mouse_relative_mode;
|
||||
} pxl8_input_state;
|
||||
|
||||
typedef struct pxl8_point {
|
||||
i32 x, y;
|
||||
} pxl8_point;
|
||||
|
||||
typedef struct pxl8_size {
|
||||
i32 w, h;
|
||||
} pxl8_size;
|
||||
|
||||
typedef struct pxl8_viewport {
|
||||
i32 offset_x, offset_y;
|
||||
i32 scaled_width, scaled_height;
|
||||
f32 scale;
|
||||
} pxl8_viewport;
|
||||
504
src/pxl8_vfx.c
504
src/pxl8_vfx.c
|
|
@ -1,504 +0,0 @@
|
|||
#include "pxl8_vfx.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_math.h"
|
||||
|
||||
struct pxl8_particles {
|
||||
pxl8_particle* particles;
|
||||
pxl8_rng* rng;
|
||||
u32 alive_count;
|
||||
u32 count;
|
||||
u32 max_count;
|
||||
|
||||
f32 x, y;
|
||||
f32 spread_x, spread_y;
|
||||
|
||||
f32 drag;
|
||||
f32 gravity_x, gravity_y;
|
||||
f32 turbulence;
|
||||
|
||||
f32 spawn_rate;
|
||||
f32 spawn_timer;
|
||||
|
||||
void (*render_fn)(pxl8_gfx* gfx, pxl8_particle* p, void* userdata);
|
||||
void (*spawn_fn)(pxl8_particles* particles, pxl8_particle* p);
|
||||
void (*update_fn)(pxl8_particle* p, f32 dt, void* userdata);
|
||||
void* userdata;
|
||||
};
|
||||
|
||||
void pxl8_vfx_plasma(pxl8_gfx* gfx, f32 time, f32 scale1, f32 scale2, u8 palette_offset) {
|
||||
if (!gfx || !pxl8_gfx_get_framebuffer_indexed(gfx)) return;
|
||||
|
||||
for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) {
|
||||
for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) {
|
||||
f32 v1 = sinf(x * scale1 + time);
|
||||
f32 v2 = sinf(y * scale1 + time * 0.7f);
|
||||
f32 v3 = sinf((x + y) * scale2 + time * 1.3f);
|
||||
f32 v4 = sinf(sqrtf(x * x + y * y) * 0.05f + time * 0.5f);
|
||||
|
||||
f32 v = v1 + v2 + v3 + v4;
|
||||
f32 normalized = (1.0f + v / 4.0f) * 0.5f;
|
||||
if (normalized < 0.0f) normalized = 0.0f;
|
||||
if (normalized > 1.0f) normalized = 1.0f;
|
||||
u8 color = palette_offset + (u8)(15.0f * normalized);
|
||||
|
||||
pxl8_pixel(gfx, x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_vfx_raster_bars(pxl8_gfx* gfx, pxl8_raster_bar* bars, u32 bar_count, f32 time) {
|
||||
if (!gfx || !bars) return;
|
||||
|
||||
for (u32 i = 0; i < bar_count; i++) {
|
||||
pxl8_raster_bar* bar = &bars[i];
|
||||
if (bar->height <= 1) continue;
|
||||
|
||||
f32 y = bar->base_y + bar->amplitude * sinf(time * bar->speed + bar->phase);
|
||||
i32 y_int = (i32)y;
|
||||
|
||||
for (i32 dy = 0; dy < bar->height; dy++) {
|
||||
f32 position = (f32)dy / (f32)(bar->height - 1);
|
||||
f32 distance_from_center = fabsf(position - 0.5f) * 2.0f;
|
||||
|
||||
u8 color_idx;
|
||||
if (distance_from_center < 0.3f) {
|
||||
color_idx = bar->fade_color;
|
||||
} else if (distance_from_center < 0.6f) {
|
||||
color_idx = bar->fade_color - 1;
|
||||
} else if (distance_from_center < 0.8f) {
|
||||
color_idx = bar->color;
|
||||
} else {
|
||||
color_idx = bar->color - 1;
|
||||
}
|
||||
|
||||
pxl8_rect_fill(gfx, 0, y_int + dy, pxl8_gfx_get_width(gfx), 1, color_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_vfx_rotozoom(pxl8_gfx* gfx, f32 angle, f32 zoom, i32 cx, i32 cy) {
|
||||
if (!gfx || !pxl8_gfx_get_framebuffer_indexed(gfx)) return;
|
||||
|
||||
f32 cos_a = cosf(angle);
|
||||
f32 sin_a = sinf(angle);
|
||||
|
||||
u8* temp_buffer = (u8*)malloc(pxl8_gfx_get_width(gfx) * pxl8_gfx_get_height(gfx));
|
||||
if (!temp_buffer) return;
|
||||
|
||||
memcpy(temp_buffer, pxl8_gfx_get_framebuffer_indexed(gfx), pxl8_gfx_get_width(gfx) * pxl8_gfx_get_height(gfx));
|
||||
|
||||
for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) {
|
||||
for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) {
|
||||
f32 dx = x - cx;
|
||||
f32 dy = y - cy;
|
||||
|
||||
f32 src_x = cx + (dx * cos_a - dy * sin_a) / zoom;
|
||||
f32 src_y = cy + (dx * sin_a + dy * cos_a) / zoom;
|
||||
|
||||
i32 sx = (i32)src_x;
|
||||
i32 sy = (i32)src_y;
|
||||
|
||||
if (sx >= 0 && sx < pxl8_gfx_get_width(gfx) && sy >= 0 && sy < pxl8_gfx_get_height(gfx)) {
|
||||
pxl8_gfx_get_framebuffer_indexed(gfx)[y * pxl8_gfx_get_width(gfx) + x] = temp_buffer[sy * pxl8_gfx_get_width(gfx) + sx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(temp_buffer);
|
||||
}
|
||||
|
||||
void pxl8_vfx_tunnel(pxl8_gfx* gfx, f32 time, f32 speed, f32 twist) {
|
||||
if (!gfx || !pxl8_gfx_get_framebuffer_indexed(gfx)) return;
|
||||
|
||||
f32 cx = pxl8_gfx_get_width(gfx) / 2.0f;
|
||||
f32 cy = pxl8_gfx_get_height(gfx) / 2.0f;
|
||||
u32 palette_size = pxl8_gfx_get_palette_size(gfx);
|
||||
if (palette_size == 0) palette_size = 256;
|
||||
|
||||
for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) {
|
||||
for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) {
|
||||
f32 dx = x - cx;
|
||||
f32 dy = y - cy;
|
||||
f32 dist = sqrtf(dx * dx + dy * dy);
|
||||
|
||||
if (dist > 1.0f) {
|
||||
f32 angle = atan2f(dy, dx);
|
||||
f32 u = angle * 0.159f + twist * sinf(time * 0.5f);
|
||||
f32 v = 256.0f / dist + time * speed;
|
||||
|
||||
u8 tx = (u8)((i32)(u * 256.0f) & 0xFF);
|
||||
u8 ty = (u8)((i32)(v * 256.0f) & 0xFF);
|
||||
u8 pattern = tx ^ ty;
|
||||
u8 color = (pattern / 8) % palette_size;
|
||||
|
||||
pxl8_pixel(gfx, x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_vfx_water_ripple(pxl8_gfx* gfx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping) {
|
||||
if (!gfx || !height_map) return;
|
||||
|
||||
i32 w = pxl8_gfx_get_width(gfx);
|
||||
i32 h = pxl8_gfx_get_height(gfx);
|
||||
|
||||
static f32* prev_height = NULL;
|
||||
if (!prev_height) {
|
||||
prev_height = (f32*)calloc(w * h, sizeof(f32));
|
||||
if (!prev_height) return;
|
||||
}
|
||||
|
||||
if (drop_x >= 0 && drop_x < w && drop_y >= 0 && drop_y < h) {
|
||||
height_map[drop_y * w + drop_x] = 255.0f;
|
||||
}
|
||||
|
||||
for (i32 y = 1; y < h - 1; y++) {
|
||||
for (i32 x = 1; x < w - 1; x++) {
|
||||
i32 idx = y * w + x;
|
||||
f32 sum = height_map[idx - w] + height_map[idx + w] +
|
||||
height_map[idx - 1] + height_map[idx + 1];
|
||||
f32 avg = sum / 2.0f;
|
||||
prev_height[idx] = (avg - prev_height[idx]) * damping;
|
||||
}
|
||||
}
|
||||
|
||||
f32* temp = height_map;
|
||||
height_map = prev_height;
|
||||
prev_height = temp;
|
||||
}
|
||||
|
||||
pxl8_particles* pxl8_particles_create(u32 max_count, pxl8_rng* rng) {
|
||||
pxl8_particles* particles = calloc(1, sizeof(pxl8_particles));
|
||||
if (!particles) return NULL;
|
||||
|
||||
particles->particles = calloc(max_count, sizeof(pxl8_particle));
|
||||
if (!particles->particles) {
|
||||
free(particles);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
particles->rng = rng;
|
||||
particles->max_count = max_count;
|
||||
particles->drag = 0.98f;
|
||||
particles->gravity_y = 100.0f;
|
||||
particles->spawn_rate = 10.0f;
|
||||
|
||||
return particles;
|
||||
}
|
||||
|
||||
void pxl8_particles_destroy(pxl8_particles* particles) {
|
||||
if (!particles) return;
|
||||
free(particles->particles);
|
||||
free(particles);
|
||||
}
|
||||
|
||||
void pxl8_particles_clear(pxl8_particles* particles) {
|
||||
if (!particles || !particles->particles) return;
|
||||
|
||||
for (u32 i = 0; i < particles->max_count; i++) {
|
||||
particles->particles[i].life = 0;
|
||||
particles->particles[i].flags = 0;
|
||||
}
|
||||
particles->alive_count = 0;
|
||||
particles->spawn_timer = 0;
|
||||
}
|
||||
|
||||
void pxl8_particles_emit(pxl8_particles* particles, u32 count) {
|
||||
if (!particles || !particles->particles) return;
|
||||
|
||||
for (u32 i = 0; i < count && particles->alive_count < particles->max_count; i++) {
|
||||
for (u32 j = 0; j < particles->max_count; j++) {
|
||||
if (particles->particles[j].life <= 0) {
|
||||
pxl8_particle* p = &particles->particles[j];
|
||||
p->life = 1.0f;
|
||||
p->max_life = 1.0f;
|
||||
p->x = particles->x + (pxl8_rng_f32(particles->rng) - 0.5f) * particles->spread_x;
|
||||
p->y = particles->y + (pxl8_rng_f32(particles->rng) - 0.5f) * particles->spread_y;
|
||||
p->z = 0;
|
||||
p->vx = p->vy = p->vz = 0;
|
||||
p->ax = particles->gravity_x;
|
||||
p->ay = particles->gravity_y;
|
||||
p->az = 0;
|
||||
p->color = p->start_color = p->end_color = 15;
|
||||
p->size = 1.0f;
|
||||
p->angle = 0;
|
||||
p->spin = 0;
|
||||
p->flags = 1;
|
||||
|
||||
if (particles->spawn_fn) {
|
||||
particles->spawn_fn(particles, p);
|
||||
}
|
||||
|
||||
particles->alive_count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_particles_render(pxl8_particles* particles, pxl8_gfx* gfx) {
|
||||
if (!particles || !particles->particles || !gfx) return;
|
||||
|
||||
for (u32 i = 0; i < particles->max_count; i++) {
|
||||
pxl8_particle* p = &particles->particles[i];
|
||||
if (p->life > 0 && p->flags) {
|
||||
if (particles->render_fn) {
|
||||
particles->render_fn(gfx, p, particles->userdata);
|
||||
} else {
|
||||
i32 x = (i32)p->x;
|
||||
i32 y = (i32)p->y;
|
||||
if (x >= 0 && x < pxl8_gfx_get_width(gfx) && y >= 0 && y < pxl8_gfx_get_height(gfx)) {
|
||||
pxl8_pixel(gfx, x, y, p->color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_particles_update(pxl8_particles* particles, f32 dt) {
|
||||
if (!particles || !particles->particles) return;
|
||||
|
||||
if (particles->spawn_rate > 0.0f) {
|
||||
particles->spawn_timer += dt;
|
||||
f32 spawn_interval = 1.0f / particles->spawn_rate;
|
||||
u32 max_spawns_per_frame = particles->max_count / 10;
|
||||
if (max_spawns_per_frame < 1) max_spawns_per_frame = 1;
|
||||
u32 spawn_count = 0;
|
||||
while (particles->spawn_timer >= spawn_interval && spawn_count < max_spawns_per_frame) {
|
||||
pxl8_particles_emit(particles, 1);
|
||||
particles->spawn_timer -= spawn_interval;
|
||||
spawn_count++;
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < particles->max_count; i++) {
|
||||
pxl8_particle* p = &particles->particles[i];
|
||||
if (p->life > 0) {
|
||||
if (particles->update_fn) {
|
||||
particles->update_fn(p, dt, particles->userdata);
|
||||
} else {
|
||||
p->vx += p->ax * dt;
|
||||
p->vy += p->ay * dt;
|
||||
p->vz += p->az * dt;
|
||||
|
||||
p->vx *= particles->drag;
|
||||
p->vy *= particles->drag;
|
||||
p->vz *= particles->drag;
|
||||
|
||||
p->x += p->vx * dt;
|
||||
p->y += p->vy * dt;
|
||||
p->z += p->vz * dt;
|
||||
|
||||
p->angle += p->spin * dt;
|
||||
}
|
||||
|
||||
p->life -= dt / p->max_life;
|
||||
if (p->life <= 0) {
|
||||
p->flags = 0;
|
||||
particles->alive_count--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_vfx_explosion(pxl8_particles* particles, i32 x, i32 y, u32 color, f32 force) {
|
||||
if (!particles) return;
|
||||
|
||||
particles->x = x;
|
||||
particles->y = y;
|
||||
particles->spread_x = particles->spread_y = 2.0f;
|
||||
particles->gravity_x = 0;
|
||||
particles->gravity_y = 200.0f;
|
||||
particles->drag = 0.95f;
|
||||
particles->update_fn = NULL;
|
||||
|
||||
for (u32 i = 0; i < 50 && i < particles->max_count; i++) {
|
||||
pxl8_particle* p = &particles->particles[i];
|
||||
f32 angle = pxl8_rng_f32(particles->rng) * PXL8_TAU;
|
||||
f32 speed = force * (0.5f + pxl8_rng_f32(particles->rng) * 0.5f);
|
||||
|
||||
p->x = x;
|
||||
p->y = y;
|
||||
p->vx = cosf(angle) * speed;
|
||||
p->vy = sinf(angle) * speed;
|
||||
p->life = 1.0f;
|
||||
p->max_life = 1.0f + pxl8_rng_f32(particles->rng);
|
||||
p->color = color;
|
||||
p->flags = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void fire_spawn(pxl8_particles* particles, pxl8_particle* p) {
|
||||
uintptr_t palette_start = (uintptr_t)particles->userdata;
|
||||
p->start_color = palette_start + 6 + pxl8_rng_range(particles->rng, 0, 3);
|
||||
p->end_color = palette_start;
|
||||
p->color = p->start_color;
|
||||
p->max_life = 1.5f + pxl8_rng_f32(particles->rng) * 1.5f;
|
||||
p->vy = -80.0f - pxl8_rng_f32(particles->rng) * 120.0f;
|
||||
p->vx = (pxl8_rng_f32(particles->rng) - 0.5f) * 40.0f;
|
||||
}
|
||||
|
||||
static void fire_update(pxl8_particle* p, f32 dt, void* userdata) {
|
||||
(void)userdata;
|
||||
p->vx += p->ax * dt;
|
||||
p->vy += p->ay * dt;
|
||||
p->vz += p->az * dt;
|
||||
|
||||
p->x += p->vx * dt;
|
||||
p->y += p->vy * dt;
|
||||
p->z += p->vz * dt;
|
||||
|
||||
f32 life_ratio = 1.0f - (p->life / p->max_life);
|
||||
if (p->start_color >= p->end_color) {
|
||||
u8 color_range = p->start_color - p->end_color;
|
||||
p->color = p->start_color - (u8)(life_ratio * color_range);
|
||||
} else {
|
||||
u8 color_range = p->end_color - p->start_color;
|
||||
p->color = p->start_color + (u8)(life_ratio * color_range);
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_vfx_fire(pxl8_particles* particles, i32 x, i32 y, i32 width, u8 palette_start) {
|
||||
if (!particles) return;
|
||||
|
||||
particles->x = x;
|
||||
particles->y = y;
|
||||
particles->spread_x = width;
|
||||
particles->spread_y = 4.0f;
|
||||
particles->gravity_x = 0;
|
||||
particles->gravity_y = -100.0f;
|
||||
particles->drag = 0.97f;
|
||||
particles->spawn_rate = 120.0f;
|
||||
particles->spawn_fn = fire_spawn;
|
||||
particles->update_fn = fire_update;
|
||||
particles->userdata = (void*)(uintptr_t)palette_start;
|
||||
}
|
||||
|
||||
static void rain_spawn(pxl8_particles* particles, pxl8_particle* p) {
|
||||
p->start_color = 27 + pxl8_rng_range(particles->rng, 0, 3);
|
||||
p->end_color = 29;
|
||||
p->color = p->start_color;
|
||||
p->max_life = 2.0f;
|
||||
p->vy = 200.0f + pxl8_rng_f32(particles->rng) * 100.0f;
|
||||
}
|
||||
|
||||
void pxl8_vfx_rain(pxl8_particles* particles, i32 width, f32 wind) {
|
||||
if (!particles) return;
|
||||
|
||||
particles->x = width / 2.0f;
|
||||
particles->y = -10;
|
||||
particles->spread_x = width;
|
||||
particles->spread_y = 0;
|
||||
particles->gravity_x = wind;
|
||||
particles->gravity_y = 300.0f;
|
||||
particles->drag = 1.0f;
|
||||
particles->spawn_rate = 100.0f;
|
||||
particles->spawn_fn = rain_spawn;
|
||||
particles->update_fn = NULL;
|
||||
}
|
||||
|
||||
static void smoke_spawn(pxl8_particles* particles, pxl8_particle* p) {
|
||||
uintptr_t base_color = (uintptr_t)particles->userdata;
|
||||
p->start_color = base_color;
|
||||
p->end_color = base_color + 4;
|
||||
p->color = p->start_color;
|
||||
p->max_life = 3.0f + pxl8_rng_f32(particles->rng) * 2.0f;
|
||||
p->vy = -20.0f - pxl8_rng_f32(particles->rng) * 30.0f;
|
||||
p->vx = (pxl8_rng_f32(particles->rng) - 0.5f) * 20.0f;
|
||||
p->size = 1.0f + pxl8_rng_f32(particles->rng) * 2.0f;
|
||||
}
|
||||
|
||||
void pxl8_vfx_smoke(pxl8_particles* particles, i32 x, i32 y, u8 color) {
|
||||
if (!particles) return;
|
||||
|
||||
particles->x = x;
|
||||
particles->y = y;
|
||||
particles->spread_x = 5.0f;
|
||||
particles->spread_y = 5.0f;
|
||||
particles->gravity_x = 0;
|
||||
particles->gravity_y = -50.0f;
|
||||
particles->drag = 0.96f;
|
||||
particles->spawn_rate = 20.0f;
|
||||
particles->spawn_fn = smoke_spawn;
|
||||
particles->update_fn = NULL;
|
||||
particles->userdata = (void*)(uintptr_t)color;
|
||||
}
|
||||
|
||||
static void snow_spawn(pxl8_particles* particles, pxl8_particle* p) {
|
||||
p->start_color = 8 + pxl8_rng_range(particles->rng, 0, 3);
|
||||
p->end_color = 10;
|
||||
p->color = p->start_color;
|
||||
p->max_life = 4.0f;
|
||||
p->vx = (pxl8_rng_f32(particles->rng) - 0.5f) * 20.0f;
|
||||
p->vy = 30.0f + pxl8_rng_f32(particles->rng) * 20.0f;
|
||||
}
|
||||
|
||||
void pxl8_vfx_snow(pxl8_particles* particles, i32 width, f32 wind) {
|
||||
if (!particles) return;
|
||||
|
||||
particles->x = width / 2.0f;
|
||||
particles->y = -10;
|
||||
particles->spread_x = width;
|
||||
particles->spread_y = 0;
|
||||
particles->gravity_x = wind;
|
||||
particles->gravity_y = 30.0f;
|
||||
particles->drag = 1.0f;
|
||||
particles->spawn_rate = 30.0f;
|
||||
particles->spawn_fn = snow_spawn;
|
||||
particles->update_fn = NULL;
|
||||
}
|
||||
|
||||
static void sparks_spawn(pxl8_particles* particles, pxl8_particle* p) {
|
||||
uintptr_t base_color = (uintptr_t)particles->userdata;
|
||||
p->start_color = base_color;
|
||||
p->end_color = base_color > 2 ? base_color - 2 : 0;
|
||||
p->color = p->start_color;
|
||||
p->max_life = 0.5f + pxl8_rng_f32(particles->rng) * 1.0f;
|
||||
|
||||
f32 angle = pxl8_rng_f32(particles->rng) * PXL8_TAU;
|
||||
f32 speed = 100.0f + pxl8_rng_f32(particles->rng) * 200.0f;
|
||||
p->vx = cosf(angle) * speed;
|
||||
p->vy = sinf(angle) * speed - 50.0f;
|
||||
}
|
||||
|
||||
void pxl8_vfx_sparks(pxl8_particles* particles, i32 x, i32 y, u32 color) {
|
||||
if (!particles) return;
|
||||
|
||||
particles->x = x;
|
||||
particles->y = y;
|
||||
particles->spread_x = 2.0f;
|
||||
particles->spread_y = 2.0f;
|
||||
particles->gravity_x = 0;
|
||||
particles->gravity_y = 100.0f;
|
||||
particles->drag = 0.97f;
|
||||
particles->spawn_rate = 40.0f;
|
||||
particles->spawn_fn = sparks_spawn;
|
||||
particles->update_fn = NULL;
|
||||
particles->userdata = (void*)(uintptr_t)color;
|
||||
}
|
||||
|
||||
void pxl8_vfx_starfield(pxl8_particles* particles, f32 speed, f32 spread) {
|
||||
if (!particles) return;
|
||||
|
||||
particles->spread_x = particles->spread_y = spread;
|
||||
particles->gravity_x = particles->gravity_y = 0;
|
||||
particles->drag = 1.0f;
|
||||
particles->spawn_rate = 0;
|
||||
particles->update_fn = NULL;
|
||||
|
||||
for (u32 i = 0; i < particles->max_count; i++) {
|
||||
pxl8_particle* p = &particles->particles[i];
|
||||
p->x = pxl8_rng_f32(particles->rng) * spread * 2.0f - spread;
|
||||
p->y = pxl8_rng_f32(particles->rng) * spread * 2.0f - spread;
|
||||
p->z = pxl8_rng_f32(particles->rng) * spread;
|
||||
p->vz = -speed;
|
||||
p->life = 1000.0f;
|
||||
p->max_life = 1000.0f;
|
||||
p->color = 8 + pxl8_rng_range(particles->rng, 0, 8);
|
||||
p->flags = 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_rng.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_particles pxl8_particles;
|
||||
|
||||
typedef struct pxl8_particle {
|
||||
f32 angle;
|
||||
f32 ax, ay, az;
|
||||
u32 color;
|
||||
u32 end_color;
|
||||
u8 flags;
|
||||
f32 life;
|
||||
f32 max_life;
|
||||
f32 size;
|
||||
f32 spin;
|
||||
u32 start_color;
|
||||
f32 vx, vy, vz;
|
||||
f32 x, y, z;
|
||||
} pxl8_particle;
|
||||
|
||||
typedef struct pxl8_raster_bar {
|
||||
f32 amplitude;
|
||||
f32 base_y;
|
||||
u32 color;
|
||||
u32 fade_color;
|
||||
i32 height;
|
||||
f32 phase;
|
||||
f32 speed;
|
||||
} pxl8_raster_bar;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_particles* pxl8_particles_create(u32 max_count, pxl8_rng* rng);
|
||||
void pxl8_particles_destroy(pxl8_particles* particles);
|
||||
|
||||
void pxl8_particles_clear(pxl8_particles* particles);
|
||||
void pxl8_particles_emit(pxl8_particles* particles, u32 count);
|
||||
void pxl8_particles_render(pxl8_particles* particles, pxl8_gfx* gfx);
|
||||
void pxl8_particles_update(pxl8_particles* particles, f32 dt);
|
||||
|
||||
void pxl8_vfx_explosion(pxl8_particles* particles, i32 x, i32 y, u32 color, f32 force);
|
||||
void pxl8_vfx_fire(pxl8_particles* particles, i32 x, i32 y, i32 width, u8 palette_start);
|
||||
void pxl8_vfx_rain(pxl8_particles* particles, i32 width, f32 wind);
|
||||
void pxl8_vfx_smoke(pxl8_particles* particles, i32 x, i32 y, u8 color);
|
||||
void pxl8_vfx_snow(pxl8_particles* particles, i32 width, f32 wind);
|
||||
void pxl8_vfx_sparks(pxl8_particles* particles, i32 x, i32 y, u32 color);
|
||||
void pxl8_vfx_starfield(pxl8_particles* particles, f32 speed, f32 spread);
|
||||
|
||||
void pxl8_vfx_plasma(pxl8_gfx* gfx, f32 time, f32 scale1, f32 scale2, u8 palette_offset);
|
||||
void pxl8_vfx_raster_bars(pxl8_gfx* gfx, pxl8_raster_bar* bars, u32 bar_count, f32 time);
|
||||
void pxl8_vfx_rotozoom(pxl8_gfx* gfx, f32 angle, f32 zoom, i32 cx, i32 cy);
|
||||
void pxl8_vfx_tunnel(pxl8_gfx* gfx, f32 time, f32 speed, f32 twist);
|
||||
void pxl8_vfx_water_ripple(pxl8_gfx* gfx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
399
src/pxl8_world.c
399
src/pxl8_world.c
|
|
@ -1,399 +0,0 @@
|
|||
#include "pxl8_world.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_bsp.h"
|
||||
#include "pxl8_gen.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_math.h"
|
||||
|
||||
struct pxl8_world {
|
||||
pxl8_bsp bsp;
|
||||
bool loaded;
|
||||
bool wireframe;
|
||||
u32 wireframe_color;
|
||||
};
|
||||
|
||||
pxl8_world* pxl8_world_create(void) {
|
||||
pxl8_world* world = (pxl8_world*)calloc(1, sizeof(pxl8_world));
|
||||
if (!world) {
|
||||
pxl8_error("Failed to allocate world");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
world->loaded = false;
|
||||
world->wireframe_color = 15;
|
||||
|
||||
return world;
|
||||
}
|
||||
|
||||
void pxl8_world_destroy(pxl8_world* world) {
|
||||
if (!world) return;
|
||||
|
||||
if (world->loaded) {
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
}
|
||||
|
||||
free(world);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params) {
|
||||
if (!world || !gfx || !params) {
|
||||
pxl8_error("Invalid arguments to pxl8_world_generate");
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (world->loaded) {
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
world->loaded = false;
|
||||
}
|
||||
|
||||
memset(&world->bsp, 0, sizeof(pxl8_bsp));
|
||||
|
||||
pxl8_result result = pxl8_procgen(&world->bsp, params);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to generate world: %d", result);
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
return result;
|
||||
}
|
||||
|
||||
world->loaded = true;
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_world_load(pxl8_world* world, const char* path) {
|
||||
if (!world || !path) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
if (world->loaded) {
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
world->loaded = false;
|
||||
}
|
||||
|
||||
memset(&world->bsp, 0, sizeof(pxl8_bsp));
|
||||
|
||||
pxl8_result result = pxl8_bsp_load(path, &world->bsp);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to load world: %s", path);
|
||||
return result;
|
||||
}
|
||||
|
||||
world->loaded = true;
|
||||
pxl8_info("Loaded world: %s", path);
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_world_unload(pxl8_world* world) {
|
||||
if (!world || !world->loaded) return;
|
||||
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
world->loaded = false;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, u32 count) {
|
||||
if (!world || !world->loaded || !textures || count == 0) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
pxl8_bsp* bsp = &world->bsp;
|
||||
|
||||
u32 max_texinfo = count * 6;
|
||||
bsp->texinfo = calloc(max_texinfo, sizeof(pxl8_bsp_texinfo));
|
||||
if (!bsp->texinfo) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
bsp->num_texinfo = 0;
|
||||
|
||||
for (u32 face_idx = 0; face_idx < bsp->num_faces; face_idx++) {
|
||||
pxl8_bsp_face* face = &bsp->faces[face_idx];
|
||||
pxl8_vec3 normal = bsp->planes[face->plane_id].normal;
|
||||
|
||||
u32 matched_texture_idx = count;
|
||||
for (u32 tex_idx = 0; tex_idx < count; tex_idx++) {
|
||||
if (textures[tex_idx].rule && textures[tex_idx].rule(&normal, face, bsp)) {
|
||||
matched_texture_idx = tex_idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched_texture_idx >= count) {
|
||||
pxl8_warn("No texture rule matched for face %u", face_idx);
|
||||
continue;
|
||||
}
|
||||
|
||||
const pxl8_world_texture* matched = &textures[matched_texture_idx];
|
||||
|
||||
pxl8_vec3 u_axis, v_axis;
|
||||
if (fabsf(normal.y) > 0.9f) {
|
||||
u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f};
|
||||
v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f};
|
||||
} else if (fabsf(normal.x) > 0.7f) {
|
||||
u_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f};
|
||||
v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f};
|
||||
} else {
|
||||
u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f};
|
||||
v_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f};
|
||||
}
|
||||
|
||||
u32 texinfo_idx = bsp->num_texinfo;
|
||||
bool found_existing = false;
|
||||
for (u32 i = 0; i < bsp->num_texinfo; i++) {
|
||||
if (strcmp(bsp->texinfo[i].name, matched->name) == 0 &&
|
||||
bsp->texinfo[i].miptex == matched->texture_id &&
|
||||
bsp->texinfo[i].u_axis.x == u_axis.x &&
|
||||
bsp->texinfo[i].u_axis.y == u_axis.y &&
|
||||
bsp->texinfo[i].u_axis.z == u_axis.z &&
|
||||
bsp->texinfo[i].v_axis.x == v_axis.x &&
|
||||
bsp->texinfo[i].v_axis.y == v_axis.y &&
|
||||
bsp->texinfo[i].v_axis.z == v_axis.z) {
|
||||
texinfo_idx = i;
|
||||
found_existing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_existing) {
|
||||
if (bsp->num_texinfo >= max_texinfo) {
|
||||
pxl8_error("Too many unique texinfo entries");
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
memcpy(bsp->texinfo[texinfo_idx].name, matched->name, sizeof(bsp->texinfo[texinfo_idx].name));
|
||||
bsp->texinfo[texinfo_idx].name[sizeof(bsp->texinfo[texinfo_idx].name) - 1] = '\0';
|
||||
bsp->texinfo[texinfo_idx].miptex = matched->texture_id;
|
||||
bsp->texinfo[texinfo_idx].u_offset = 0.0f;
|
||||
bsp->texinfo[texinfo_idx].v_offset = 0.0f;
|
||||
bsp->texinfo[texinfo_idx].u_axis = u_axis;
|
||||
bsp->texinfo[texinfo_idx].v_axis = v_axis;
|
||||
|
||||
bsp->num_texinfo++;
|
||||
}
|
||||
|
||||
face->texinfo_id = texinfo_idx;
|
||||
}
|
||||
|
||||
pxl8_info("Applied %u textures to %u faces, created %u texinfo entries",
|
||||
count, bsp->num_faces, bsp->num_texinfo);
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radius) {
|
||||
if (!world || !world->loaded) return false;
|
||||
|
||||
const pxl8_bsp* bsp = &world->bsp;
|
||||
|
||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||
const pxl8_bsp_face* face = &bsp->faces[i];
|
||||
const pxl8_bsp_plane* plane = &bsp->planes[face->plane_id];
|
||||
|
||||
if (fabsf(plane->normal.y) > 0.7f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
f32 dist = plane->normal.x * pos.x +
|
||||
plane->normal.y * pos.y +
|
||||
plane->normal.z * pos.z - plane->dist;
|
||||
|
||||
if (fabsf(dist) > radius) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxl8_vec3 closest_point = {
|
||||
pos.x - plane->normal.x * dist,
|
||||
pos.y - plane->normal.y * dist,
|
||||
pos.z - plane->normal.z * dist
|
||||
};
|
||||
|
||||
if (closest_point.x < face->aabb_min.x - radius || closest_point.x > face->aabb_max.x + radius ||
|
||||
closest_point.y < face->aabb_min.y - radius || closest_point.y > face->aabb_max.y + radius ||
|
||||
closest_point.z < face->aabb_min.z - radius || closest_point.z > face->aabb_max.z + radius) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool pxl8_world_is_loaded(const pxl8_world* world) {
|
||||
return world && world->loaded;
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
|
||||
if (!world || !world->loaded) return to;
|
||||
|
||||
const pxl8_bsp* bsp = &world->bsp;
|
||||
pxl8_vec3 pos = to;
|
||||
pxl8_vec3 original_velocity = {to.x - from.x, to.y - from.y, to.z - from.z};
|
||||
|
||||
pxl8_vec3 clip_planes[5];
|
||||
u32 num_planes = 0;
|
||||
|
||||
const f32 edge_epsilon = 1.2f;
|
||||
const f32 radius_min = -radius + edge_epsilon;
|
||||
const f32 radius_max = radius - edge_epsilon;
|
||||
|
||||
for (i32 iteration = 0; iteration < 4; iteration++) {
|
||||
bool collided = false;
|
||||
|
||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||
const pxl8_bsp_face* face = &bsp->faces[i];
|
||||
const pxl8_bsp_plane* plane = &bsp->planes[face->plane_id];
|
||||
|
||||
if (fabsf(plane->normal.y) > 0.7f) continue;
|
||||
|
||||
f32 dist = plane->normal.x * pos.x +
|
||||
plane->normal.y * pos.y +
|
||||
plane->normal.z * pos.z - plane->dist;
|
||||
|
||||
f32 abs_dist = fabsf(dist);
|
||||
if (abs_dist > radius) continue;
|
||||
|
||||
pxl8_vec3 closest_point = {
|
||||
pos.x - plane->normal.x * dist,
|
||||
pos.y - plane->normal.y * dist,
|
||||
pos.z - plane->normal.z * dist
|
||||
};
|
||||
|
||||
if (closest_point.x < face->aabb_min.x + radius_min || closest_point.x > face->aabb_max.x + radius_max ||
|
||||
closest_point.y < face->aabb_min.y + radius_min || closest_point.y > face->aabb_max.y + radius_max ||
|
||||
closest_point.z < face->aabb_min.z + radius_min || closest_point.z > face->aabb_max.z + radius_max) {
|
||||
continue;
|
||||
}
|
||||
|
||||
f32 penetration = radius - abs_dist;
|
||||
if (penetration > 0.01f) {
|
||||
pxl8_vec3 push_dir;
|
||||
if (dist < 0) {
|
||||
push_dir.x = -plane->normal.x;
|
||||
push_dir.y = -plane->normal.y;
|
||||
push_dir.z = -plane->normal.z;
|
||||
} else {
|
||||
push_dir.x = plane->normal.x;
|
||||
push_dir.y = plane->normal.y;
|
||||
push_dir.z = plane->normal.z;
|
||||
}
|
||||
|
||||
bool is_new_plane = true;
|
||||
for (u32 p = 0; p < num_planes; p++) {
|
||||
if (pxl8_vec3_dot(push_dir, clip_planes[p]) > 0.99f) {
|
||||
is_new_plane = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_new_plane && num_planes < 5) {
|
||||
clip_planes[num_planes++] = push_dir;
|
||||
}
|
||||
|
||||
pos.x += push_dir.x * penetration;
|
||||
pos.y += push_dir.y * penetration;
|
||||
pos.z += push_dir.z * penetration;
|
||||
|
||||
collided = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!collided) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (num_planes >= 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_planes == 2) {
|
||||
f32 orig_vel_len_sq = original_velocity.x * original_velocity.x +
|
||||
original_velocity.y * original_velocity.y +
|
||||
original_velocity.z * original_velocity.z;
|
||||
|
||||
if (orig_vel_len_sq > 0.000001f) {
|
||||
f32 vdot0 = pxl8_vec3_dot(original_velocity, clip_planes[0]);
|
||||
f32 vdot1 = pxl8_vec3_dot(original_velocity, clip_planes[1]);
|
||||
f32 dot0 = fabsf(vdot0);
|
||||
f32 dot1 = fabsf(vdot1);
|
||||
|
||||
pxl8_vec3 slide_vel;
|
||||
if (dot0 < dot1) {
|
||||
slide_vel.x = original_velocity.x - clip_planes[0].x * vdot0;
|
||||
slide_vel.y = original_velocity.y - clip_planes[0].y * vdot0;
|
||||
slide_vel.z = original_velocity.z - clip_planes[0].z * vdot0;
|
||||
} else {
|
||||
slide_vel.x = original_velocity.x - clip_planes[1].x * vdot1;
|
||||
slide_vel.y = original_velocity.y - clip_planes[1].y * vdot1;
|
||||
slide_vel.z = original_velocity.z - clip_planes[1].z * vdot1;
|
||||
}
|
||||
|
||||
f32 slide_len = sqrtf(slide_vel.x * slide_vel.x + slide_vel.y * slide_vel.y + slide_vel.z * slide_vel.z);
|
||||
|
||||
if (slide_len > 0.01f) {
|
||||
pos.x += slide_vel.x;
|
||||
pos.y += slide_vel.y;
|
||||
pos.z += slide_vel.z;
|
||||
|
||||
pxl8_vec3 crease_dir = pxl8_vec3_cross(clip_planes[0], clip_planes[1]);
|
||||
f32 crease_len = pxl8_vec3_length(crease_dir);
|
||||
if (crease_len > 0.01f && fabsf(crease_dir.y / crease_len) > 0.9f) {
|
||||
f32 bias0 = pxl8_vec3_dot(original_velocity, clip_planes[0]);
|
||||
f32 bias1 = pxl8_vec3_dot(original_velocity, clip_planes[1]);
|
||||
|
||||
if (bias0 < 0 && bias1 < 0) {
|
||||
const f32 corner_push = 0.1f;
|
||||
pxl8_vec3 push_away = {
|
||||
clip_planes[0].x + clip_planes[1].x,
|
||||
clip_planes[0].y + clip_planes[1].y,
|
||||
clip_planes[0].z + clip_planes[1].z
|
||||
};
|
||||
f32 push_len_sq = push_away.x * push_away.x + push_away.y * push_away.y + push_away.z * push_away.z;
|
||||
if (push_len_sq > 0.000001f) {
|
||||
f32 inv_push_len = corner_push / sqrtf(push_len_sq);
|
||||
pos.x += push_away.x * inv_push_len;
|
||||
pos.y += push_away.y * inv_push_len;
|
||||
pos.z += push_away.z * inv_push_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f32 horizontal_movement_sq = (pos.x - from.x) * (pos.x - from.x) +
|
||||
(pos.z - from.z) * (pos.z - from.z);
|
||||
f32 desired_horizontal_sq = (to.x - from.x) * (to.x - from.x) +
|
||||
(to.z - from.z) * (to.z - from.z);
|
||||
|
||||
if (desired_horizontal_sq > 0.01f && horizontal_movement_sq < desired_horizontal_sq * 0.25f) {
|
||||
const f32 max_step_height = 0.4f;
|
||||
|
||||
pxl8_vec3 step_up = pos;
|
||||
step_up.y += max_step_height;
|
||||
|
||||
if (!pxl8_world_check_collision(world, step_up, radius)) {
|
||||
pxl8_vec3 step_forward = {
|
||||
step_up.x + (to.x - pos.x),
|
||||
step_up.y,
|
||||
step_up.z + (to.z - pos.z)
|
||||
};
|
||||
|
||||
if (!pxl8_world_check_collision(world, step_forward, radius)) {
|
||||
pos = step_forward;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
|
||||
if (!world || !gfx || !world->loaded) return;
|
||||
|
||||
if (world->wireframe) {
|
||||
pxl8_bsp_render_wireframe(gfx, &world->bsp, camera_pos, world->wireframe_color);
|
||||
} else {
|
||||
pxl8_bsp_render_textured(gfx, &world->bsp, camera_pos);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_bsp.h"
|
||||
#include "pxl8_gen.h"
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_world pxl8_world;
|
||||
|
||||
typedef bool (*pxl8_texture_rule)(const pxl8_vec3* normal, const pxl8_bsp_face* face, const pxl8_bsp* bsp);
|
||||
|
||||
typedef struct pxl8_world_texture {
|
||||
char name[16];
|
||||
u32 texture_id;
|
||||
pxl8_texture_rule rule;
|
||||
} pxl8_world_texture;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_world* pxl8_world_create(void);
|
||||
void pxl8_world_destroy(pxl8_world* world);
|
||||
|
||||
pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params);
|
||||
pxl8_result pxl8_world_load(pxl8_world* world, const char* path);
|
||||
void pxl8_world_unload(pxl8_world* world);
|
||||
|
||||
pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, u32 count);
|
||||
bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radius);
|
||||
bool pxl8_world_is_loaded(const pxl8_world* world);
|
||||
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);
|
||||
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue