forum.boolean.name

forum.boolean.name (http://forum.boolean.name/index.php)
-   FAQ и уроки (http://forum.boolean.name/forumdisplay.php?f=110)
-   -   Разрывание рисунка на куски и растворение (http://forum.boolean.name/showthread.php?t=9542)

Жека 19.10.2009 12:48

Разрывание рисунка на куски и растворение
 
Вложений: 5
Здравствуйте!:)

Здесь пример того, как можно спецэффектно рвать рисунки на куски или растворять их не просто альфой.

Реализовано два типа "деформации" рисунка - взрыв и растворение.
Создавать куски можно как заблаговременно, например, в момент загрузки уровня, так и "на лету". Но создание на лету тормозит систему на время своей работы.
Куски получаются посредством попиксельного копирования цветов из исходного рисунка в рисунок куска. Медленно? Ещё бы! (для ускорения и есть предварительная загрузка в кеш)

Управление:
-левая кнопка мыши - создать взрыв
-правая кнопка мыши - создать растворение
-Esc - выход

Псевдокод использования:
1. создаем заранее:
взрыв_растворение.СоздатьВКеш(исходныйРисунок, ширина куска, высота куска, тип{Взрыв,Растворение})
а затем там где нужно создать эффект:
взрыв_растворение.Запуск(Х, У)

2. создаем в реальном времени:
взрыв_растворение.СоздатьПрямЩас(исходныйРисунок, ширина куска, высота куска, тип{Взрыв,Растворение}, Х, У)

Для примера возьмем такой рисунок:
Вложение 8000

Что есть в примере:
-класс TParticle - для хранения информации о кусках и их рисования
-класс TExplosion - для создания и управления кусками
-код, показывающий как этим воспользоваться

Скрины из данного примера:
исходный рисунок:
Вложение 8001

взрыв:
Вложение 8002

растворение:
Вложение 8003

Теперь - непосредственно к исходнику.
В коде имеются комментарии для увеличения степени понятности; их вполне может хватить. Итак,

Код:

Strict

AppTitle = "Разрывание и растворение рисунка"

'загружаем наш подопытный рисунок
Local img:TImage = LoadImage("pointer.png")


'создаем окно
Graphics(800, 600, 0, 60)

HideMouse()

'создаем экземпляр объекта "взрыв"
Local expl:TExplosion = New TExplosion

'заранее создаем будущие осколки из исходного рисунка
'параметры: рисунок, ширина осколков, высота осколков, тип осколков
expl.fnPreCreate(img, Rand(5, 15), Rand(5, 15), TParticle.KIND_EXPLOSION)


SetClsColor 100, 100, 200

SetBlend ALPHABLEND

'главный цикл
While Not KeyHit(KEY_ESCAPE)
       
        'сбрасываем в исходное состояние альфу, поворот и масштаб
        SetAlpha 1
        SetTransform
       
        'по щелчку левой кнопки мыши запускаем наш ранее кешированный взрыв
        If(MouseHit(1) > 0)
                'указываем координаты левого верхнего угла
                expl.fnStart(MouseX(), MouseY())
        EndIf
       
        'по щелчку правой кнопки - "на лету" создаем "растворение" рисунка
        'на время разбиения на куски прога подвисает, поэтому рекомендуется заранее (на этапе загрузки) создавать нарезку
        If(MouseHit(2) > 0)
                'параметры: исходный рисунок, ширина кусков, высота кусков, тип "растворение", координаты левого верхнего угла
                expl.fnCreate(img, Rand(5, 15), Rand(5, 15), TParticle.KIND_RASTVORENIE, MouseX(), MouseY())
        EndIf
       
        'обновление взрыва/растворения
        'если идет анимация, то функция вернет true - исходный рисунок не рисуем
        'если анимации нет, то вернет false - и будет нарисован исходный рисунок
        If(expl.fnUpdate() = False) DrawImage (img, MouseX(), MouseY())
       
        Flip
        Cls
Wend

End


'класс КУСОК, содержит картинки и параметры рисования этих картинок
Type TParticle
        Field img:TImage 'картинка
        Field x:Float, y:Float 'координаты
        Field dx:Float, dy:Float 'смещение по X и Y (скорость)
        Field alpha:Float, scale:Float, angle:Float 'альфа, масштаб и поворот
        Field dAlpha:Float, dScale:Float, dAngle:Float 'их приращение
        Field dir 'направление вращения картинки
        Field kind 'тип куска
       
        Global KIND_EXPLOSION = 1 'для взрыва
        Global KIND_RASTVORENIE = 2 'для растворения
       
       
        'создание экземпляра
        'параметры: указатель на рисунок, координаты х-у, тип, скорость по х-у,начальные значения альфы, масштаба и поворота
        Function fnCreate:TParticle(img:TImage, x:Float, y:Float, knd, dx:Float = 0, dy:Float = 0, alpha:Float = 1.0, scale:Float = 1.0, angle:Float = 0)
                Local p:TParticle = New TParticle
                p.img = img
                p.x = x
                p.y = y
                p.dx = dx
                p.dy = dy
                p.fnSetKind(knd) 'эта функция ставит параметры в зависимости от типа
                p.alpha = alpha
                p.scale = scale
                p.angle = angle
                p.dir = 1 - 2 * (Rand(100) < 50) 'случайное направление вращения (dir = -1 или 1)
                Return p
        End Function
       
        'устанавливаем параметры в зависимости от типа       
        Method fnSetKind(knd)
               
                kind = knd
               
                'для взрава значения побольше, для растворения - поменьше
                If(kind = KIND_EXPLOSION)
                        dx = Rnd(- 2.0, 2.0)
                        dy = Rnd(- 2.0, 2.0)
                        dAlpha = -0.015
                        dScale = 0.03
                        dAngle = 3.5
                       
                Else 'If(kind = KIND_RASTVORENIE)
                        dx = Rnd(- 0.02, 0.02)
                        dy = Rnd(- 0.02, 0.02)
                        dAlpha = -0.007
                        dScale = 0.01
                        dAngle = 0.3
                       
                EndIf
               
        End Method
       
        'обновления куска
        'здесь все значения нужно умножать на коэффициент,
        'равный koef#=fpsEtalon#/fpsReal#
        'чтобы на разномощных компах одинаково выглядело
        Method fnUpdate()
                x:+dx
                y:+dy
                alpha:+dAlpha
                scale:+dScale
                angle:+dAngle * dir
        End Method
       
        'рисование картинки с ее альфой, масштабом и поворотом
        'а также с внешним смещением по х-у
        Method fnDraw(x0:Float = 0, y0:Float = 0)
                SetAlpha alpha
                SetTransform angle, scale, scale
                DrawImage img, x0 + x, y0 + y
        End Method
               
End Type


'класс ВЗРЫВ (он же и растворение)
Type TExplosion
        Field list:TList = New TList 'список кусков рисунка
        Field listCashed:TList = New TList 'список кешированных кусков рисунка
        Field x:Float, y:Float 'координаты левого верхнего угла, относительно которого рисовать
        Field image:TImage 'указатель на исходный рисунок
        Field cashed 'флаг - кешировали или нет
        Field animate 'флаг - идет анимация или нет
       
        '"врЕменная" переменная для циклов for ... eachin ...
        Global part:TParticle
       
       
        'кеширование кусков
        Method fnPreCreate(pimage:TImage, sizeX:Float, sizeY:Float, kind)
                image = pimage
                'создаем куски из указанного рисунка, указанного размера и типа
                listCashed = fnCreateParticles(image, sizeX, sizeY, kind)
                cashed = True 'устанавливаем флаг, ибо сделали дело
        End Method
       
       
        'запуск анамации - используется для старта из кешированного списка
        Method fnStart(px:Float, py:Float)
               
                'если не кешировали или уже идет анимация - выходим
                If(cashed = False Or animate = True) Return
               
                Local p:TParticle 'это для кусков-копий кешированных
               
                'позицию запоминаем
                x = px
                y = py
               
                'проходим по списку кешированных кусков
                'создаем их копиии и добавляем в список
                For part = EachIn listCashed
                        p = New TParticle
                        p.x = part.x
                        p.y = part.y
                        p.dir = part.dir
                        p.dx = part.dx
                        p.dy = part.dy
                        p.alpha = part.alpha
                        p.angle = part.angle
                        p.scale = part.scale
                        p.dAlpha = part.dAlpha
                        p.dAngle = part.dAngle
                        p.dScale = part.dScale
                        p.img = part.img
                        list.AddLast(p)
                Next
               
                animate = True '"включаем" анимацию
               
        End Method
       
       
        'создание кусков "на лету"
        'даем функции рисунок, ширину, высоту и тип кусков, и координаты откуда все рисовать
        Method fnCreate(pimage:TImage, sizeX:Float, sizeY:Float, kind, px:Float, py:Float)
               
                'если уже идет анимация - выходим
                If(animate = True) Return
               
                image = pimage
                x = px
                y = py
               
                'создаем куски
                list = fnCreateParticles(image, sizeX, sizeY, kind)
                animate = True 'поехали!
               
        End Method
       
       
        'создание кусков - возвращает список с кусками
        'даем функции исходный рисунок + ширину, высоту и тип кусков
        Function fnCreateParticles:TList(img1:TImage, sizeX:Float, sizeY:Float, kind)
                Local lst:TList = New TList
                                                       
                Local imgW, imgH, ix, iy, px, py
                Local pix1:TPixmap, pix2:TPixmap 'через пиксмапы будем попиксельно копировать точки из исходного в куски
                Local img2:TImage 'это для указывания на очередной кусок
               
                'реальная ширина и высота создаваемого куска
                'нужна для того, чтобы правильного размера крайние куски сделать
                Local wd, hg
               
                'запомним размеры исходного рисунка в переменные, для удобства
                imgW = img1.width
                imgH = img1.height
               
                'блокируем исходный рисунок для работы с его точками
                'и получаем пиксмап
                pix1 = LockImage(img1)
               
                'пробегание по координате Y
                'iy - хранит номер строки
                While(iy * sizeY < imgH)
                       
                        'проверка - выйдем ли мы следующим проходим за границы исходного рисунка
                        'если да, то значит кусок нужно делать не на всю высоту sizeY, а поменьше
                        'чтоб он как раз до края получился
                        If((iy + 1) * sizeY > imgH)
                                hg = imgH - iy * sizeY
                        Else
                                hg = sizeY
                        EndIf
                       
                        'обнуляем номер столбца
                        ix = 0
               
                        'пробегание по координате Х
                        'iх - хранит номер столбца
                        While(ix * sizeX < imgW)
                               
                                'проверка - выйдем ли мы следующим проходим за границы исходного рисунка
                                'если да, то значит кусок нужно делать не на всю ширину sizeХ, а поменьше
                                'чтоб он как раз до края получился
                                If((ix + 1) * sizeX > imgW)
                                        wd = imgW - ix * sizeX
                                Else
                                        wd = sizeX
                                EndIf
                                       
                                'создаем рисунок для очередного куска размером (wd x hg) точек
                                img2 = CreateImage(wd, hg, 1, DYNAMICIMAGE | FILTEREDIMAGE)
                                'блокируем рисунок и получаем его пиксмап
                                pix2 = LockImage(img2)
                               
                                '(столько циклов, аж дух захватывает!)
                                'здесь мы попиксельно копируем инфу о цветах из исходного рисунка в рисунок куска
                                For py = 0 Until hg
                                        For px = 0 Until wd
                                                'в пиксмап куска пишем в позицию px,py
                                                'а из исходного читаем со смещением ix*sizeX,iy*sizeY
                                                WritePixel(pix2, px, py, ReadPixel(pix1, ix * sizeX + px, iy * sizeY + py))
                                        Next
                                Next
                                'разблокируем рисунок куска
                                UnlockImage(img2)
                                'ставим ему смещения рисования в центр
                                MidHandleImage(img2)
                                'добавляем кусок в список
                                'отнимать от х координаты (sizeX - wd) * 0.5
                                'и от у координаты (sizeY - hg) * 0.5
                                'нужно для того, чтобы крайние куски не съехали в сторону
                                'когда их размер меньше чем sizeX*sizeY - из-за midhandl'а
                                'и еще 0.5 от размера прибавляем, опять же компенсируя сдвиг midhandl'a
                                lst.AddLast(TParticle.fnCreate(img2, ix * sizeX - (sizeX - wd) * 0.5 + sizeX * 0.5, iy * sizeY - (sizeY - hg) * 0.5 + sizeY * 0.5, kind))
                               
                                ix:+1
                        Wend
                        iy:+1
                Wend
               
                'разблокируем исходный рисунок
                UnlockImage(img1)
               
                'возвращаем список
                Return lst
               
        End Function

       
        'обновление
        Method fnUpdate()
               
                'если анимация не включена, то выходим, возвращая false
                If(animate = False) Return False
               
                'пробегаем по всем кускам из списка
                For part = EachIn list
                        'обновляем и рисуем каждый
                        part.fnUpdate()
                        part.fnDraw(x, y)
                        'проверяем - не пора ли удалить кусок?
                        If(part.alpha <= 0)
                                list.Remove(part)
                                part = Null
                        EndIf
                Next
               
                'если кусков больше нет, то останавливаем анимацию
                If(list.IsEmpty())
                        animate = False
                EndIf
               
                'возвращает true, а после удаления последнего - false
                'это не столь важно, здесь можно просто return true поставить
                Return animate
               
        End Method
       
End Type

Во вложении исходник и подопытный рисунок.
Вот и всё! ;)

ABTOMAT 20.10.2009 00:33

Ответ: Разрывание рисунка на куски и растворение
 
Нельзя ли выложить скомпиленное? Так заинтересовало, что даже посмотреть захотелось, а БМакса нет-с :( За урок спасибо. Идея оригинальная, использует преимущества аппаратного рендера БМакса и не сложна в реализации.

moka 20.10.2009 04:45

Ответ: Разрывание рисунка на куски и растворение
 
Очень хороший пример, и интересная реализация.
Поддерживаю предыдущего оратора - скомпиленную ехе'шку хочется.
Если указать фрэймворк, и заинклудить что нада, будет очень лёгкая ехе.

Жека 20.10.2009 07:04

Ответ: Разрывание рисунка на куски и растворение
 
Вложений: 1
Прицепил архив с exe-шником и новым исходником.

В новом коде небольшие изменения: в класс TParticle добавил поле deep, эта переменная аналогична переменной dir, только та для направления вращения, а эта для направления масштабирования, т.е. задает - увеличивать картинку или уменьшать при анимации.
Так же принимает случайное значение - или 1, или -1.
deep = 1 - 2 * (Rand(100) < 50)

Для получения результата в функции обновления куска приращение масштаба умножается на значение этой переменной:
scale:+dScale * deep

Теперь часть осколков летит к смотрящему, приближаясь, а часть - отдаляется, тем самым объёмность прослеживается чётче.
Отдаление только для взрыва, для растворения некрасиво с ним.

Спасибо за беседу :)


Часовой пояс GMT +4, время: 20:32.

vBulletin® Version 3.6.5.
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Перевод: zCarot