Эффект "стекания" на JS (canvas)

Давно хотел переписать этот старый эффект, знакомый многим еще по такой игре как DOOM (там таким образом менялось меню и заставки между уровнями).

Эффект выражается в том, что изображение начинает "течь" вниз, как вода стекает с вертикальной поверхности.

Демо

Canvas does't support by this browser...
текст
апельсин
всплеск

Как работает данный эффект?

поведение

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

моделирование

При каждом рендеринге кадра делается следующее:
  1. Массив сдвигов, количество элементов в котором равно количеству пикселей по горизонтали - это наши "части объекта" - заполняется случайными значениями от 0 - maxStep, на эти значения будет сдвигаться соответствующие столбцы вниз - т.е. если например в массиве steps[4] = 3, это значит что столбец с координатой X = 4 будет сдвинут вниз на 3 пикселя.
  2. Все столбцы изображения сдвигаются на число пикселей из массива steps: освободившееся сверху место заполняется черным цветом, а на все перенесенные вниз пиксели накладывается эффект затухания (просто для красоты) - это имитирует различную скорость стекания.
    maxStep не должен быть слишком большой - иначе наш объект "развалится".

  3. Поверх полученного изображения накладывается исходное изображение с учетом прозрачности - иначе картинка просто стечет и останется черный экран, а так она будет течь (ну или капать) бесконечно.

По скольку массив заполняется случайными значениями при каждом кадре - это имитирует изменения скорости течения частей - т.е то однни быстрее то другие.
Вот и все!


Код эффекта

Для отрисовки используется библиотека canvas.js

function drip( id ) {
	var bb = new DBUF( id );
	bb.clear();
 
	var ibb = null;
 
	var play = false;
	var rrID = null;
 
	drip.isPlaying = function() {
		return play;
	};
 
	drip.start = function() {
		if ( play ) return;
 
		play = true;
		renderer();
	};
 
	drip.stop = function() {
		if ( !play ) return;
 
		play = false;
		if ( rrID ) {
			bb.cancelRedraw( rrID );
			rrID = null;
		}
	};
 
	var xa = 0, ya = 0;
	drip.setImage = function( url, noAni ) {
		var img = new Image();
		img.onload = function() {
			ibb = new DBUF( "imgbb", this.width, this.height );
			ibb.drawImage( img, 0, 0 );
			xa = Math.floor( ( bb.w - ibb.w ) / 2 );
			ya = Math.floor( ( bb.h - ibb.h ) / 2 );
			if ( xa < 0 ) xa = 0;
			if ( ya < 0 ) ya = 0;
 
			drip.stop();
			if ( noAni ) doDrip();
			else drip.start();
		};
		img.src = url;
	};
 
	var steps = new Array( bb.w );
	var maxStep = 25;
	var minStep = 1;
	var fade = 0.8;
 
	var maxStepH = maxStep * 0.5;
	function countSteps() {
		for ( var i = 0; i < bb.w; i++ ) {
 
			steps[i] = Math.floor( maxStepH - Math.random() * maxStep );
			if ( i > 0 && i % 2 == 0 ) steps[i] = steps[i-1];
 
			if ( steps[i] < minStep ) steps[i] = minStep;
			if ( steps[i] > maxStep ) steps[i] = maxStep;
		}
	}
 
	function doDrip() {
		var x, y, p;
 
		if ( !ibb ) return;
 
		countSteps();
 
		for ( x = 0; x < bb.w; x++ ) {
			var s = steps[x];
			for ( y = bb.h - s - 1; y >= 0; y-- ) {
				p = bb.getPixel( x, y );
				p.r = ( p.r * fade ) & 255;
				p.g = ( p.g * fade ) & 255;
				p.b = ( p.b * fade ) & 255;
				bb.setPixel( x, y + s, p.r, p.g, p.b, 255 );
			}
 
			for ( y = 0; y < s; y++ ) bb.setPixel( x, y, 0, 0, 0, 255 );
		}
 
		for( y = 0; y < ibb.h && y < bb.h; y++ ) {
			for ( x = 0; x < ibb.w && x < bb.w; x++ ) {
				p = ibb.getPixel( x, y );
				if ( p.a > 0 ) {
					bb.mergePixel( x + xa, y + ya, p.r, p.g, p.b, p.a );
				}
			}
		}
 
 
		bb.put();
	}
 
	var frameLength = 150;
	var lastFrame = 0;
	function renderer() {
		rrID = null;
 
		if ( !play ) return;
 
		var now = Date.now();
		if ( now  - lastFrame > frameLength ) {
			lastFrame = now;
			doDrip();
		}
 
		rrID = bb.requestRedraw( renderer );
	}
}

Скачать drip.zip

Все файлы демки одним архивом
скачать

12.03.2013, Protocoder
Написать комментарий