(***************************************************************************** Юнит: u_TileIteratorWideSpiralByRect Программа: SASPlaneta Назначение: Итератор по спирали от центра Автор: Dima2000 Версия: v5 Дата: 11.05.2012 Модуль реализует класс итератора для Планеты, обходящего прямоугольную область по спирали начиная от центра. Предназначен для прямой замены предыдущего u_TileIteratorSpiralByRect в дистрибутиве Планеты. По этой причине все форматы используемых Планетой методов предыдущего класса (.Create и .Next) сохранены. В отличии от предыдущего итератора, спираль данного немного сплюснута по вертикали и на "широких" мониторах заполняет экранную область визуально более логично. Собственно, это его основное достоинство. На обычных мониторах (4:3) разница непринципиальна. Итератор работоспособен при любых размерах региона (TRect) от 1*1 и больше. Но для сильно вытянутых регионов (скажем 100*2) может замедляться поиск следующей подходящей точки, хотя для размеров до сотен тысяч тайлов это видимо непринцпиально. В предыдущем итераторе сделано похоже. ****************************************************************************** История версий: v5 11.05.2012 [*] Сменил родителя на ITileIterator. v4 11.05.2012 [*] Привёл ограничивающий box к стандарту Планеты - правый нижний угол уже ЗА областью построения спирали. [*] Изменил поведение на квадратном box - спираль будет всегда квадратной. v3 06.05.2012 [+] Сделал несколько одновременных итераторов одной спирали. В конструкторе задаётся их общее число и номер каждого. v2 06.05.2012 [+] Сделал выбор типа спирали. По умолчанию - сплюснутая разумеется. [*] Для размеров спирали до 5 по меньшей оси спираль всегда будет квадратной. v1 03.05.2012 [+] Первая версия, работает лишь сплюснутая спираль. ****************************************************************************** ToDo: +1. Сделать выбор типа спирали, сплюснутую или квадратную. +2. Сделать выбор начального смещения и шага прохода по спирали. Чтобы можно было натравить несколько итераторов на одну спираль. +3. Наследовать не TTileIteratorByRectBase, а сразу ITileIterator. PS. В качестве примера приведу карту заполнения региона 11*9 тайлов: 81_82_83_84_85_86_87_88_89_90_91 80_49_50_51_52_53_54_55_56_57_92 79_48_25_26_27_28_29_30_31_58_93 78_47_24_09_10_11_12_13_32_59_94 77_46_23_08_02_01_03_14_33_60_95 76_45_22_07_06_05_04_15_34_61_96 75_44_21_20_19_18_17_16_35_62_97 74_43_42_41_40_39_38_37_36_63_98 73_72_71_70_69_68_67_66_65_64_99 Основное отличие в первых 3-х шагах в центре, дальше классическая раскрутка спирали вниз-влево-вверх-вправо и т.д. *****************************************************************************) unit u_TileIteratorWideSpiralByRect; interface uses Types, i_TileIterator; type TTileIteratorWideSpiralByRect = class(TInterfacedObject, ITileIterator) private {Ограничивающий прямоугольник} fBox: TRect; {Координаты центра} fCenter: TPoint; {Текущее положение} fCurrent: TPoint; {Приращения по осям} fDelta: TPoint; {Максимальный размер раскрутки спирали} fMax: integer; {Использовать квадратную спираль} fSquare: boolean; fCount, fSize: integer; fNc, fN, fN0: integer; {Сделать шаг без проверки координат} function Step: TPoint; {Проверить точку на попадание в fBox} function CheckPoint(const t: TPoint): integer; {Получить общее количество тайлов в fBox} function GetTilesTotal: int64; {Получить исходный box} function GetTilesRect: TRect; public {Создать итератор в заданном прямоугольнике, координаты целые (со знаком). Флаг задания квадратной спирали, для небольших размеров по любой оси спираль всегда будет квадратной - красивее и глюков меньше. N - выдавать лишь каждую N-ю точку, N0 - первый раз выдать N0-ю точку. Или: N - количество итераторов, N0 - номер данного итератора [1..N]. Параметры нужны для запуска нескольких итераторов по одной спирали} constructor Create(const rect: TRect; const Square: boolean = false; const N: integer=1; const N0: integer=1); {Начать спираль заново, от центра} procedure Reset; {Выдать следующую точку и флаг что она есть} function Next(out Tile: TPoint): boolean; {Общее количество тайлов в прямоугольнике} property TilesTotal: int64 read GetTilesTotal; {Ограничивающий прямоугольник} property TilesRect: TRect read GetTilesRect; {Координаты центра} property Center: TPoint read fCenter; property Square: boolean read fSquare; end; implementation {Получить общее количество тайлов в fBox} function TTileIteratorWideSpiralByRect.GetTilesTotal: int64; begin Result := int64(fBox.Right - fBox.Left) * int64(fBox.Bottom - fBox.Top); end; {Получить исходный box} function TTileIteratorWideSpiralByRect.GetTilesRect: TRect; begin Result := fBox; end; {Проверить точку на попадание в fBox} function TTileIteratorWideSpiralByRect.CheckPoint(const t: TPoint): integer; {Вернёт 1 если точка внутри fBox} {Вернёт -1 если точки в fBox закончились} {Вернёт 0 если данная точка не в fBox, но не гарантированно больше нет других точек в fBox, можно попробовать поискать ещё} begin Result := 1; if t.X < fBox.Left then Result := 0; if t.X >= fBox.Right then Result := 0; if t.Y < fBox.Top then Result := 0; if t.Y >= fBox.Bottom then Result := 0; if fSize > fMax + 2 then Result := -1; {Ушли достаточно далеко от fBox} end; {Сделать шаг без проверки координат} function TTileIteratorWideSpiralByRect.Step: TPoint; begin case fSize of -2: fCurrent := Point(fCenter.X + 0, fCenter.Y + 0); -1: fCurrent := Point(fCenter.X - 1, fCenter.Y + 0); 0: fCurrent := Point(fCenter.X + 1, fCenter.Y + 0); else {Сделать шаг в нужном направлении} Inc(fCurrent.X, fDelta.X); Inc(fCurrent.Y, fDelta.Y); {И уменьшить оставшуюся длину прямого хода} Dec(fCount); {Если прямой ход закончен, то сменить приращения координат и длину хода} if fCount = 0 then begin fCount := fSize; if fDelta.Y = 0 then begin fDelta.Y := fDelta.X; fDelta.X := 0; if fSquare then Inc(fSize); end else begin fDelta.X := -fDelta.Y; fDelta.Y := 0; if not fSquare then begin Inc(fSize); Inc(fCount, 2); end; end; end; end; {Это делается здесь лишь для сокращения кода в первых трёх case выше} if fSize < 1 then Inc(fSize); Result := fCurrent; end; {Начать спираль заново, от центра} procedure TTileIteratorWideSpiralByRect.Reset; begin fNc := fN0; if fSquare then begin fSize := 1; fCount := 2; fDelta := Point(+1, 0); fCurrent := Point(fCenter.X - 1, fCenter.Y); end else begin fSize := -2; fCount := 1; fDelta := Point(0, +1); end; end; {Создать итератор в заданном прямоугольнике, координаты целые (со знаком) флаг задания квадратной спирали, для небольших размеров по любой оси спираль всегда будет квадратной - красивее и глюков меньше N - выдавать лишь каждую N-ю точку, N0 - первый раз выдать N0-ю точку. Или: N - количество итераторов, N0 - номер данного итератора [1..N]. Параметры нужны для запуска нескольких итераторов по одной спирали} constructor TTileIteratorWideSpiralByRect.Create(const rect: TRect; const Square: boolean = false; const N: integer=1; const N0: integer=1); var {Размеры ограничивающего прямоугольника} sh, sv: integer; begin fSquare := Square; fBox := rect; {Первый шаг будет другим, равный номеру итератора} fN0 := N0; {Остальные шаги все одинаковы, размером в количество итераторов} fN := N; sh := fBox.Right - fBox.Left; sv := fBox.Bottom - fBox.Top; {Для предела раскрутки выберем бОльший размер} if sv > sh then fMax := sv else fMax := sh; {Если размер по любой оси меньше 5-ти единиц используем квадратную спираль. Если регион передан квадратный, то и спираль используем квадратную.} if (sh < 5) or (sv < 5) or (sh = sv) then fSquare := true; fCenter.X := (fBox.Left + fBox.Right - 2) div 2; fCenter.Y := (fBox.Top + fBox.Bottom - 2) div 2; {Не будем дублировать код, вызовем метод} Reset; end; {Выдать следующую точку и флаг что она есть} function TTileIteratorWideSpiralByRect.Next(out Tile: TPoint): boolean; var res, i: integer; begin Result := false; {Если в длине шага ошибка, то к чёрту всю спираль} if fNc < 1 then Exit; {Выдавать на выход лишь одну-fNc-ую точку} for i := 1 to fNc do begin repeat {Сделать шаг без проверки} Tile := Step; {Проверить полученную точку} res := CheckPoint(Tile); if res < 0 then Exit; {В прямоугольнике точки кончились} {Цикл до нахождения подходящей точки} until res <> 0; end; {Начиная со второго вызова выдавать точки с нормальным шагом} fNc := fN; Result := true; end; end.