From 8f0362f3d4cb8d105e033bee5d15ac49a5f0fcc8 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Mon, 10 Aug 2026 22:09:45 +0300 Subject: [PATCH] =?UTF-8?q?L4-MIRROR=20=D1=88=D0=B0=D0=B3=D0=B8=203=20?= =?UTF-8?q?=D0=B8=205=20+=20=D0=BB=D0=B5=D0=B2=D1=8B=D0=B9=20=D0=BA=D0=BB?= =?UTF-8?q?=D0=B8=D0=BF=20=D0=BA=D0=BE=D0=BB=D0=BE=D0=BD=D0=BE=D0=BA=20?= =?UTF-8?q?=D0=B2=20libbgi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libbgi: новая gfx_blit_cols_part_wx — обрезка СЛЕВА (skipw) в дополнение к верхней (skip/rows) и правой (maxw). По подсказке пользователя сделано примитивом, а не обходным путём «нарисовать и вернуть фон поверх лишнего»: для column-major левая обрезка стоит ровно столько же, сколько правая — колонка это непрерывный кусок ОЗУ, меняется стартовая колонка источника и экранная X. Внутри это уже было (так клипается левый край экрана), наружу не было выведено. Полное тело блита колонками переехало туда, gfx_blit_cols_part_w стала обёрткой (skipw=0). size-check: OK, роста нет (62 программы, -22..-33 Б на пользователей блита). Шаг 5 — клип ТЕНИ (seg008:1699): на уровне зеркала она показывается только СПРАВА от него, obj_clip_left = 137 + (mirror_column-4)*32. Шаг 3 — ОТРАЖЕНИЕ (check_mirror, seg003:0798): пока Кид стоит на тайле зеркала, каждый кадр рисуется его зеркальная копия с клипом left = (curr_col<<5)+9 и top = y_clip[curr_row+1]. Отдельной функцией pop_mirror_draw, а НЕ третьим слотом Char — это структура самого оригинала: отражение идёт сокращённым путём load_frame_to_obj + add_objtable(4), без клинка, брызг, fore-прохода и пропуска кадра; гейтить всё это в общем теле значило бы добавить ветки в самый горячий путь. Свой heal (pop_mirror_heal) рядом с pop_char_heal, в skip-маске не участвует. tests-host: заглушки pop_mirror_draw/pop_mirror_heal (pop_map теперь на них ссылается), все 5 наборов прошли. Цена: _CODE 24501 -> 24629, банк 3 10551, банк 4 8267 -> 9590. НЕ ПРОВЕРЕНО В MAME. Co-Authored-By: Claude Opus 5 --- applications/PoP/roomtest/pop_cdraw.c | 121 ++++++++++++++++++- applications/PoP/roomtest/pop_cdraw.h | 9 ++ applications/PoP/roomtest/pop_map.c | 29 ++++- applications/PoP/roomtest/roomtest.c | 4 + applications/PoP/roomtest/tests-host/stubs.c | 5 + libbgi/common/gfx_blit_cols_part_w.c | 62 +--------- libbgi/common/gfx_blit_cols_part_wx.c | 82 +++++++++++++ libbgi/include/gfx.h | 14 +++ 8 files changed, 259 insertions(+), 67 deletions(-) create mode 100644 libbgi/common/gfx_blit_cols_part_wx.c diff --git a/applications/PoP/roomtest/pop_cdraw.c b/applications/PoP/roomtest/pop_cdraw.c index c4b2728..7aaba37 100644 --- a/applications/PoP/roomtest/pop_cdraw.c +++ b/applications/PoP/roomtest/pop_cdraw.c @@ -25,6 +25,7 @@ #include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */ #include "pop_map.h" /* hitp_curr/hitp_max — HP Кида; clip_char */ #include "pop_level.h" /* тип соперника уровня (набор спрайтов) */ +#include "pop_tile.h" /* pop_heal_off — клип тени/отражения через фон */ #include "kid_data.h" #include "pop_guard_pal.h" /* таблица палитр стражей (сгенерена упаковщиком) */ @@ -456,7 +457,7 @@ void pop_char_draw(uint8_t who) __banked const kframe *fr; const atlas_t *pages; const uint8_t *img; - uint8_t npages, page, idx, dp, flip, hurt; + uint8_t npages, page, idx, dp, flip, hurt, lskip; uint16_t w, h, vis_w; int obj_x, obj_y, fp_x, fwd, top, bx, skip, rows, ct, cr; @@ -533,6 +534,7 @@ void pop_char_draw(uint8_t who) __banked if (flip) bx -= (int)w; top = obj_y - (int)h + 1; vis_w = w; + lskip = 0; skip = 0; /* clip_char (seg006:1231/1749): тайл над головой — стена/пол → всё * выше линии полосы своего ряда НЕ рисуется, иначе персонаж @@ -552,6 +554,18 @@ void pop_char_draw(uint8_t who) __banked if (avail <= 0) vis_w = 0; /* весь спрайт за косяком */ else if (avail < (int)w) vis_w = (uint16_t)avail; } + /* КЛИП ТЕНИ СЛЕВА (seg008:1699): на уровне зеркала она может + * показываться ТОЛЬКО СПРАВА от него — + * obj_clip_left = 137 + (mirror_column-4)*32. */ + if (ch->charid == CHARID_1_SHADOW && + pop_current_level == MIRROR_LEVEL && ch->room == MIRROR_ROOM) { + int cl = 137 + (MIRROR_COL - 4) * 32; + if (bx < cl) { + int d = cl - bx; + if (d >= (int)vis_w) vis_w = 0; /* вся тень левее линии */ + else lskip = (uint8_t)d; + } + } /* Поле комнаты — 192 строки (reset_obj_clip, seg006:0507: * obj_clip_top = 0, obj_clip_bottom = 192). Персонаж, вышедший за * него (скелет, падающий в пропасть), обязан обрезаться: ниже поля @@ -564,7 +578,10 @@ void pop_char_draw(uint8_t who) __banked /* noclip-ядро ширину не ограничивает, поэтому при обрезке справа * идём общим путём: это редкие кадры (подъём в двери уровня раз * за уровень), цена подготовки там не видна. */ - if (vis_w == w && pop_onscreen_cols(bx, top + POP_YOFF, w, (uint16_t)rows)) + if (lskip) /* тень у зеркала: срез слева */ + gfx_blit_cols_part_wx(bx, top + POP_YOFF, img, flip, skip, rows, + lskip, (uint8_t)(vis_w - lskip)); + else if (vis_w == w && pop_onscreen_cols(bx, top + POP_YOFF, w, (uint16_t)rows)) gfx_blit_cols_part_noclip(bx, top + POP_YOFF, img, flip, (uint8_t)skip, (uint8_t)rows); else @@ -574,8 +591,8 @@ void pop_char_draw(uint8_t who) __banked gfx_set_bank(GFX_BANK_NORMAL); /* heal чистит ТОЛЬКО нарисованное: иначе стирается кромка пола над * срезом (мусор/дыра в кладке на второй странице дабл-буфера). */ - s->x[dp] = bx; s->y[dp] = top; - s->w[dp] = vis_w; s->h[dp] = (uint16_t)rows; + s->x[dp] = bx + lskip; s->y[dp] = top; + s->w[dp] = (uint16_t)(vis_w - lskip); s->h[dp] = (uint16_t)rows; s->valid[dp] = (uint8_t)(rows != 0); if (rows) cd_clip_add(s, bx, top, vis_w, (uint16_t)rows); s->fpw = w; s->fph = h; /* габарит КАДРА (не обрезанный): */ @@ -614,6 +631,102 @@ void pop_char_fore(uint8_t who) __banked s->fpx, s->fpy, s->fpw, s->fph); } +/* ---- ОТРАЖЕНИЕ В ЗЕРКАЛЕ (check_mirror, seg003:0798) ---------------- * + * + * Пока Кид стоит на тайле зеркала, оригинал каждый кадр рисует его + * зеркальную копию. ОТДЕЛЬНОЙ функцией, а не третьим слотом pop_cd — и это + * не экономия, а структура САМОГО оригинала: отражение идёт сокращённым + * путём `load_frame_to_obj(); reset_obj_clip(); add_objtable(4)`, а НЕ через + * add_kid_to_objtable. У него нет ни клинка, ни брызг, ни fore-прохода, ни + * пропуска неизменившегося кадра — гейтить всё это в общем теле значило бы + * добавить ветки в самый горячий путь ради двух секунд за игру. + * + * Char к моменту вызова уже ОТРАЖЁН вызывающим (pop_check_mirror -> + * mirror_image), поэтому здесь никакого «зеркалим» нет — обычная математика + * кадра, та же, что в pop_char_draw (см. там load_frame_to_obj, seg008:1728; + * шесть строк намеренно продублированы, а не вынесены: трогать горячее тело + * ради холодного вызова дороже, чем держать их синхронными по комментарию). + */ +static int mi_x[2], mi_y[2]; +static uint16_t mi_w[2], mi_h[2]; +static uint8_t mi_valid[2]; + +void pop_mirror_heal(void) __banked +{ + uint8_t p = gfx_get_draw_page() & 1; + if (!mi_valid[p]) return; + pop_heal_fast(mi_x[p], mi_y[p] + POP_YOFF, mi_w[p], mi_h[p]); + mi_valid[p] = 0; +} + +void pop_mirror_draw(int clip_top) __banked +{ + uint8_t dp = gfx_get_draw_page() & 1; + const kframe *fr = &kid_frame; + const uint8_t *img; + uint8_t page, idx, flip; + uint16_t w, h, vis_w; + int fwd, obj_x, obj_y, bx, top, skip = 0, rows, cl; + uint8_t lskip = 0; + + if (fr->image == 255) return; + page = (uint8_t)(fr->image >> 3); + idx = (uint8_t)(fr->image & 7); + if (page >= kid_npages) return; + + fwd = (int)Char.x + ((Char.direction < 0) ? -fr->dx : fr->dx); + obj_x = (fwd << 1) - 116; + obj_y = (int)Char.y + fr->dy; + if ((int8_t)(fr->flags ^ Char.direction) >= 0) obj_x++; + obj_x = scr_x(obj_x); + + img = (const uint8_t *)atlas_image(&kidp[page], idx); + gfx_w0_map(kidp[page].page); + w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8)); + h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8)); + if (!w || !h) { gfx_w0_unmap(); return; } + flip = (uint8_t)(Char.direction >= 0); + bx = flip ? obj_x - (int)w : obj_x; + top = obj_y - (int)h + 1; + + /* Клип СВЕРХУ — y_clip[curr_row+1] оригинала: отражение не должно + * вылезать из проёма зеркала вверх. Значение считает вызывающий + * (таблица y_clip живёт в pop_map). */ + if (clip_top > top) { skip = clip_top - top; top = clip_top; } + rows = (int)h - skip; + if (top < 0) { skip += -top; rows += top; top = 0; } + if (top + rows > 192) rows = 192 - top; + vis_w = w; + if (rows <= 0) { gfx_w0_unmap(); return; } + + /* Клип СЛЕВА — obj_clip_left = (curr_col<<5)+9: отражение видно только + * внутри проёма зеркала. Настоящей обрезкой колонок + * (gfx_blit_cols_part_wx), а не «нарисовать и вернуть фон»: для + * column-major левая обрезка стоит столько же, сколько правая. */ + cl = ((int)Char.curr_col << 5) + 9; + if (bx < cl) { + int d = cl - bx; + if (d >= (int)vis_w) { gfx_w0_unmap(); return; } /* целиком за краем */ + lskip = (uint8_t)d; + } + + gfx_set_bank(GFX_BANK_SPRITE); + if (lskip) + gfx_blit_cols_part_wx(bx, top + POP_YOFF, img, flip, skip, rows, + lskip, (uint8_t)(vis_w - lskip)); + else if (pop_onscreen_cols(bx, top + POP_YOFF, vis_w, (uint16_t)rows)) + gfx_blit_cols_part_noclip(bx, top + POP_YOFF, img, flip, + (uint8_t)skip, (uint8_t)rows); + else + gfx_blit_cols_part_w(bx, top + POP_YOFF, img, flip, skip, rows, 0); + gfx_set_bank(GFX_BANK_NORMAL); + gfx_w0_unmap(); + + mi_x[dp] = bx + lskip; mi_y[dp] = top; + mi_w[dp] = (uint16_t)(vis_w - lskip); mi_h[dp] = (uint16_t)rows; + mi_valid[dp] = 1; +} + /* ---- Индикаторы HP (порт draw_kid_hp / draw_guard_hp, seg000:1416) --- * * Оригинал рисует их в статус-полосе на y = 194 (ниже поля тайлов, внутри * тех же 200 строк экрана PoP): HP Кида слева направо шагом 7, HP стража diff --git a/applications/PoP/roomtest/pop_cdraw.h b/applications/PoP/roomtest/pop_cdraw.h index 19d57ec..f48db99 100644 --- a/applications/PoP/roomtest/pop_cdraw.h +++ b/applications/PoP/roomtest/pop_cdraw.h @@ -135,4 +135,13 @@ void pop_cd_touch(int x, int y, int w, int h); * сотрёт прошлый кадр за нас). */ uint8_t pop_char_skip_mask(void) __banked; +/* ОТРАЖЕНИЕ В ЗЕРКАЛЕ (уровень 4, seg003:0798). Char к моменту вызова уже + * отражён (pop_check_mirror); clip_top — линия y_clip[curr_row+1], выше + * которой отражение не рисуется. Отдельно от pop_char_draw, потому что и в + * оригинале это сокращённый путь add_objtable(4): без клинка, брызг, + * fore-прохода и пропуска кадра. heal — парный вызов, рядом с + * pop_char_heal. */ +void pop_mirror_draw(int clip_top) __banked; +void pop_mirror_heal(void) __banked; + #endif diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index d07a293..3743278 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -2613,11 +2613,30 @@ static void jump_through_mirror(void) hitp_curr = 1; } +/* Линия клипа отражения СВЕРХУ — y_clip[curr_row+1] оригинала. Таблица та + * же, что в pop_clip_char_top; держим её здесь копией из четырёх значений, + * а не тащим наружу: ряд у отражения всегда 0..2. */ +static int mirror_clip_top(int8_t row) +{ + static const int16_t y_clip[5] = { -60, 3, 66, 129, 192 }; + int8_t r = (int8_t)(row + 1); + if (r < 0 || r > 4) return 0; + return (int)y_clip[r]; +} + void pop_check_mirror(void) __banked { - if (pop_jumped_mirror == -1) jump_through_mirror(); - /* Ветка ОТРИСОВКИ ОТРАЖЕНИЯ (шаг 3 задачи L4-MIRROR) сюда добавится - * позже: пока Кид стоит на тайле зеркала, оригинал каждый кадр рисует - * его зеркальную копию типом 4 objtable с клипом left = (curr_col<<5)+9 - * и top = y_clip[curr_row+1]. */ + if (pop_jumped_mirror == -1) { jump_through_mirror(); return; } + /* Отражение: пока Кид СТОИТ на тайле зеркала, рисуем его зеркальную + * копию (в оригинале — add_objtable(4)). Условия оригинала: тайл под + * персонажем — зеркало, distance_mirror >= 0 (вошёл в тайл достаточно + * глубоко) и его комната = отрисованная. */ + if (Kid.room != g_room) return; + pop_loadkid(); + if (get_tile_at_char() != TILE_MIRROR) return; + pop_load_frame(); + if (mirror_image() >= 0) + pop_mirror_draw(mirror_clip_top(Char.curr_row)); + /* Char остался ОТРАЖЁННЫМ — как в оригинале: обратно он не сохраняется, + * а следующий тик перезагружает окно из Kid. */ } diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index 41a499b..3188aed 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -697,6 +697,10 @@ int main(void) skip = pop_char_skip_mask(); if (!(skip & 1)) pop_char_heal(POP_CH_KID); /* стереть прошлый кадр ЭТОЙ страницы */ if (!(skip & 2)) pop_char_heal(POP_CH_OPP); /* и прошлый кадр соперника */ + /* Отражение в зеркале — свой heal: у него нет слота pop_cd и в + * skip-маске он не участвует (рисуется/стирается безусловно, живёт + * только пока Кид стоит на тайле зеркала). */ + pop_mirror_heal(); pop_fore_heal(); /* + следы оверлея (шире прямоугольника Kid) */ PROF(2); /* логика (тот же синий) */ kid_tick(); /* play_seq: следующий кадр */ diff --git a/applications/PoP/roomtest/tests-host/stubs.c b/applications/PoP/roomtest/tests-host/stubs.c index aaca387..ad57e55 100644 --- a/applications/PoP/roomtest/tests-host/stubs.c +++ b/applications/PoP/roomtest/tests-host/stubs.c @@ -318,3 +318,8 @@ const uint8_t POP_TILE_MOD[256] = { * тесты гоняют физику подземелья, поэтому 0. Не заглушка-функция, а та же * переменная, что и в резиденте: карта читает её напрямую. */ uint8_t pop_palace; + +/* Отражение в зеркале (уровень 4) — чистая графика, в тестах физики её нет. + * pop_map зовёт pop_mirror_draw из pop_check_mirror, поэтому символ нужен. */ +void pop_mirror_draw(int clip_top) __banked { (void)clip_top; } +void pop_mirror_heal(void) __banked { } diff --git a/libbgi/common/gfx_blit_cols_part_w.c b/libbgi/common/gfx_blit_cols_part_w.c index 901fc9a..1dcb823 100644 --- a/libbgi/common/gfx_blit_cols_part_w.c +++ b/libbgi/common/gfx_blit_cols_part_w.c @@ -1,23 +1,10 @@ /* * gfx_blit_cols_part_w — блит спрайта column-major с обрезкой СВЕРХУ - * (skip/rows) И ОГРАНИЧЕНИЕМ ШИРИНЫ (maxw): рисуются только первые maxw + * (skip/rows) и ОГРАНИЧЕНИЕМ ШИРИНЫ (maxw): рисуются только первые maxw * колонок футпринта, считая от x. maxw = 0 — «вся ширина кадра». * - * Здесь живёт ПОЛНОЕ тело блита колонками; gfx_blit_cols_part (maxw=0) и - * gfx_blit_cols (ещё и skip=rows=0) — тонкие обёртки, чтобы клип-логика не - * дублировалась. Подробности зеркала/клипа — см. шапку gfx_blit_cols.c. - * - * Зачем ограничение ширины. Обрезка СПРАВА для column-major — это ровно - * уменьшение числа колонок, то есть внутри уже есть (так же клипается край - * экрана: `w = _bgi_maxx + 1 - x`), наружу просто не было выведено. - * Понадобилось для порта clip_char в PoP: персонаж, поднимающийся по - * лестнице за дверью уровня, обязан уходить за правый косяк проёма - * (obj_clip_right), а дверь рисуется ПОД ним и перекрыть его не может. - * - * Важно: ограничение работает и при flip. При зеркале источник читается с - * последней колонки к первой (sstride < 0), но первые w нарисованных - * колонок в любом случае ложатся в ЛЕВУЮ часть футпринта [x, x+w) — то - * есть уменьшение w всегда срезает справа, как и требуется. + * Тонкая обёртка над gfx_blit_cols_part_wx (skipw = 0) — полное тело блита + * колонками живёт там; здесь оно было до появления обрезки СЛЕВА. */ #include #include "../_bgi.h" @@ -25,46 +12,5 @@ void gfx_blit_cols_part_w(int x, int y, const void *img, uint8_t flip, int skip, int rows, uint8_t maxw) { - const uint8_t *p = (const uint8_t *)img; - int full_w = p[0] | (p[1] << 8); - int full_h = p[2] | (p[3] << 8); - const uint8_t *px = p + 4; - int w = full_w, h; - int sstride = flip ? -full_h : full_h; /* шаг между колонками в ОЗУ */ - int sy; /* пропуск строк сверху колонки */ - int cx = 0; /* пропуск колонок слева экрана */ - const uint8_t *src; - uint8_t *dst; - - if (full_w <= 0 || full_h <= 0) - return; - if (maxw && (int)maxw < w) - w = (int)maxw; /* обрезка справа (см. шапку) */ - if (skip < 0) skip = 0; - if (skip >= full_h) - return; /* обрезано целиком */ - sy = skip; - h = full_h - skip; - if (rows > 0 && rows < h) - h = rows; - - /* Клип по экрану — только если хоть один край вылезает. */ - if (x < 0 || y < 0 || x + w > _bgi_maxx + 1 || y + h > _bgi_maxy + 1) { - if (x < 0) { cx = -x; w += x; x = 0; } - if (y < 0) { sy -= y; h += y; y = 0; } - if (x + w > _bgi_maxx + 1) w = _bgi_maxx + 1 - x; - if (y + h > _bgi_maxy + 1) h = _bgi_maxy + 1 - y; - if (w <= 0 || h <= 0) return; - } - - /* Первая ВИДИМАЯ колонка (с учётом флипа) + первая видимая строка. */ - src = flip ? (px + (uint16_t)(full_w - 1) * (uint16_t)full_h) : px; - if (cx) - src += cx * sstride; - src += sy; - - _bgi_begin(); - dst = (uint8_t *)(_gfx_addr_base + (uint16_t)x); - _bgi_blit_cols_raw(src, dst, (uint8_t)w, (uint8_t)h, sstride, (uint8_t)y); - _bgi_end(); + gfx_blit_cols_part_wx(x, y, img, flip, skip, rows, 0, maxw); } diff --git a/libbgi/common/gfx_blit_cols_part_wx.c b/libbgi/common/gfx_blit_cols_part_wx.c new file mode 100644 index 0000000..cc0040c --- /dev/null +++ b/libbgi/common/gfx_blit_cols_part_wx.c @@ -0,0 +1,82 @@ +/* + * gfx_blit_cols_part_wx — блит спрайта column-major с обрезкой СВЕРХУ + * (skip/rows) и ОКНОМ ПО КОЛОНКАМ: рисуются только колонки + * [skipw, skipw + maxw) футпринта, считая от x. skipw = 0 — от левого края, + * maxw = 0 — «до правого края кадра». + * + * Здесь живёт ПОЛНОЕ тело блита колонками; gfx_blit_cols_part_w (skipw=0), + * gfx_blit_cols_part (ещё и maxw=0) и gfx_blit_cols (плюс skip=rows=0) — + * тонкие обёртки, чтобы клип-логика не дублировалась. Подробности + * зеркала/клипа — см. шапку gfx_blit_cols.c. + * + * Зачем обрезка СЛЕВА (skipw). Для column-major она стоит ровно столько же, + * сколько обрезка справа: колонка — непрерывный кусок ОЗУ, меняется только + * стартовая колонка источника и экранная X. Внутри это уже было — + * так клипается левый край экрана (`if (x < 0) { cx = -x; ... }`), наружу + * просто не было выведено. + * + * Понадобилось для зеркала в PoP (obj_clip_left, seg003:0798 и seg008:1699): + * отражение Кида видно только внутри проёма зеркала, а тень уровня 4 — только + * СПРАВА от него. Обходной путь «нарисовать и вернуть фон поверх лишнего» + * работал, но стоил лишний heal в кадре и рисовал невидимые колонки. + * + * Важно: окно работает и при flip. При зеркале источник читается с + * последней колонки к первой (sstride < 0), но НАРИСОВАННЫЕ колонки всегда + * идут слева направо по футпринту [x, x+w) — то есть skipw срезает слева, а + * maxw справа, независимо от направления чтения источника. + */ +#include +#include "../_bgi.h" + +void gfx_blit_cols_part_wx(int x, int y, const void *img, uint8_t flip, + int skip, int rows, uint8_t skipw, uint8_t maxw) +{ + const uint8_t *p = (const uint8_t *)img; + int full_w = p[0] | (p[1] << 8); + int full_h = p[2] | (p[3] << 8); + const uint8_t *px = p + 4; + int w = full_w, h; + int sstride = flip ? -full_h : full_h; /* шаг между колонками в ОЗУ */ + int sy; /* пропуск строк сверху колонки */ + int cx = 0; /* пропуск колонок слева */ + const uint8_t *src; + uint8_t *dst; + + if (full_w <= 0 || full_h <= 0) + return; + if (skipw) { /* обрезка СЛЕВА (см. шапку) */ + if ((int)skipw >= w) return; /* срезано целиком */ + cx = (int)skipw; + x += (int)skipw; + w -= (int)skipw; + } + if (maxw && (int)maxw < w) + w = (int)maxw; /* обрезка справа (см. шапку) */ + if (skip < 0) skip = 0; + if (skip >= full_h) + return; /* обрезано целиком */ + sy = skip; + h = full_h - skip; + if (rows > 0 && rows < h) + h = rows; + + /* Клип по экрану — только если хоть один край вылезает. */ + if (x < 0 || y < 0 || x + w > _bgi_maxx + 1 || y + h > _bgi_maxy + 1) { + if (x < 0) { cx -= x; w += x; x = 0; } + if (y < 0) { sy -= y; h += y; y = 0; } + if (x + w > _bgi_maxx + 1) w = _bgi_maxx + 1 - x; + if (y + h > _bgi_maxy + 1) h = _bgi_maxy + 1 - y; + if (w <= 0 || h <= 0) return; + } + + /* Первая ВИДИМАЯ колонка (с учётом флипа) + первая видимая строка. */ + src = flip ? (px + (uint16_t)(full_w - 1) * (uint16_t)full_h) : px; + if (cx) + src += cx * sstride; + src += sy; + + _bgi_begin(); + dst = (uint8_t *)(_gfx_addr_base + (uint16_t)x); + _bgi_blit_cols_raw(src, dst, (uint8_t)w, (uint8_t)h, sstride, (uint8_t)y); + _bgi_end(); +} diff --git a/libbgi/include/gfx.h b/libbgi/include/gfx.h index d2a0e0e..8485f6b 100644 --- a/libbgi/include/gfx.h +++ b/libbgi/include/gfx.h @@ -142,6 +142,20 @@ void gfx_blit_cols_part(int x, int y, const void *img, uint8_t flip, void gfx_blit_cols_part_w(int x, int y, const void *img, uint8_t flip, int skip, int rows, uint8_t maxw); +/* То же + ОБРЕЗКА СЛЕВА: рисуются только колонки [skipw, skipw+maxw) + * футпринта, считая от x (skipw = 0 — от левого края, maxw = 0 — до правого + * края кадра). Для column-major левая обрезка стоит столько же, сколько + * правая: колонка — непрерывный кусок ОЗУ, меняется стартовая колонка + * источника и экранная X. Работает и при flip: НАРИСОВАННЫЕ колонки всегда + * идут слева направо по футпринту, независимо от направления чтения. + * + * Мотивация — зеркало в PoP (obj_clip_left, seg003:0798 / seg008:1699): + * отражение Кида видно только внутри проёма, тень уровня 4 — только справа + * от зеркала. Здесь живёт ПОЛНОЕ тело блита колонками, остальные три + * варианта — обёртки. */ +void gfx_blit_cols_part_wx(int x, int y, const void *img, uint8_t flip, + int skip, int rows, uint8_t skipw, uint8_t maxw); + /* То же БЕЗ клипа по экрану — пара к gfx_blit_cols_part так же, как * gfx_blit_part_noclip к gfx_blit_part. Вызывающий гарантирует: спрайт * целиком на экране, ширина/высота кадра и y <= 255, skip < высоты.