Javascript-форум (https://javascript.ru/forum/)
-   Оффтопик (https://javascript.ru/forum/offtopic/)
-   -   Падающий снежок на канвасе :) (https://javascript.ru/forum/offtopic/34397-padayushhijj-snezhok-na-kanvase.html)

Дзен-трансгуманист 03.01.2013 10:30

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

Не то чтобы круто, да я и не выпендривался, но может кому-нибудь понравится. :)
<head>
<style>
img, canvas { position: absolute; left: 0px; top: 0px; margin: 0px; padding: 0px; border: none; }
</style>
<script>

function byId ( id ) {
  return document.getElementById( id );
}

function rand ( min, max ) {
  return Math.random() * ( max - min ) + min;
}

function now () {
  return ( +new Date() ) / 1000;
}

function Sprite ( image, sx, sy, sw, sh, originX, originY ) {

  this.image = image;
  this.sx = sx;
  this.sy = sy;
  this.sw = sw;
  this.sh = sh;
  this.originX = originX ? originX : 0;
  this.originY = originY ? originY : 0;
}

Sprite.prototype.draw = function ( context, x, y, angle, scaleX, scaleY ) {

  context.save();
  context.translate( x, y );
  context.rotate( angle );
  context.scale( scaleX, scaleY );
  context.drawImage(
    this.image,
    this.sx, this.sy,
    this.sw, this.sh,
    -this.originX,
    -this.originY,
    this.sw, this.sh
  );
  context.restore();
}

function Flakes ( canvas, flake, zmin, zmax, count, speed, loopTime ) {

  var viewW = canvas.width;
  var viewH = canvas.height;

  zmax = Math.min( zmax, ( loopTime * speed - flake.height ) / viewH );
  zmin = Math.min( zmin, zmax );

  var particles = [];
  var flakeHalfW = flake.width * 0.5;
  var flakeHalfH = flake.height * 0.5;

  for ( var i = 0; i < count; i++ ) {

    var spawnZ = rand( zmin, zmax );

    particles.push({
      spawnX    : rand( -flakeHalfW / spawnZ, viewW + flakeHalfW / spawnZ ),
      spawnZ    : spawnZ,
      spawnTime : rand( 0, loopTime ),
      speed     : speed / spawnZ,
      halfH     : flakeHalfH / spawnZ
    });
  }

  particles.sort( function ( a, b ) {
    return b.spawnZ - a.spawnZ;
  });

  this.particles = particles;
  this.viewW     = viewW;
  this.viewH     = viewH;
  this.loopTime  = loopTime;
  this.ctx       = canvas.getContext( '2d' );
  this.sprite    = new Sprite(
    flake,
    0, 0,
    flake.width, flake.height,
    flake.width * 0.5, flake.height * 0.5
  );
}

Flakes.prototype.draw = function ( time ) {

  var ctx       = this.ctx;
  var sprite    = this.sprite;
  var particles = this.particles;
  var viewH     = this.viewH;
  var loopTime  = this.loopTime;

  for ( var i = 0; i < particles.length; i++ ) {

    var particle = particles[ i ];
    var y = -particle.halfH + ( ( time + particle.spawnTime ) % loopTime ) * particle.speed;

    if ( y < viewH + particle.halfH ) {
      sprite.draw( ctx, particle.spawnX, y, 0, 1 / particle.spawnZ, 1 / particle.spawnZ );
    }
  }
}

window.onload = function () {

  var imgBg    = byId( 'image-bg' );
  var imgFlake = byId( 'image-flake' );
  var canvas   = byId( 'canvas' );

  document.body.removeChild( imgFlake );

  canvas.width = imgBg.width;
  canvas.height = imgBg.height;

  var ctx = canvas.getContext( '2d' );
  var flakes = new Flakes( canvas, imgFlake, 2, 25, 1500, 400, 30 );

  function render () {

    ctx.clearRect( 0, 0, canvas.width, canvas.height );
    flakes.draw( now() - timeOffset );
  }

  var animate = true;
  var timeStop;
  var timeOffset = 0;
  var intervalId = window.setInterval( render, 40 );

  canvas.onclick = function () {

    if ( animate = !animate ) {
      timeOffset += now() - timeStop;
      intervalId = window.setInterval( render, 40 );
    }
    else {
      timeStop = now();
      window.clearInterval( intervalId );
    }
  }
}

</script>
</head>
<body>
<img id="image-flake" src="http://dl.dropbox.com/u/19390559/share/snowflake.png">
<img id="image-bg" src="http://dl.dropbox.com/u/19390559/share/christmas-tree.png">
<canvas id="canvas"></canvas>
</body>

Можно кликнуть по картинке чтобы остановить/возобновить анимацию.

ps: фон, конечно же, не мой - нашел в гугле.)

Deff 03.01.2013 12:02

Классно!
Цитата:

Сообщение от Дзен-трансгуманист
Можно кликнуть по картинке чтобы остановить/возобновить анимацию.

по идее, чтобы поставить на сайт во всю страницу, не останавливая возможности копирования и ввода текста нун наверно играть с z-index при запуске - остановке.

melky 03.01.2013 12:19

Цитата:

Сообщение от Дзен-трансгуманист
Не то чтобы круто, да я и не выпендривался, но может кому-нибудь понравится.

классная вещь) осталось добавить сюда рандом в распределение звёздочек, и будет настоящий снегопад :)

DjDiablo 03.01.2013 12:48

Спасибо порадовал, очень празднично получилось)))

помню когда javascript только только появился все писали телетайп, бегущий строчки, и снегопад. Снежинки правда gif картинками в dom были, тобишь без канваса.

Теперь вот шедевр на канвасе, в следующем году ждём снежинок на webGL )))))

Gozar 03.01.2013 13:07

На этой картинке выглядит хорошо, хотя местами подтормаживает.

А варианты будут, чтобы снежинки полетели вправо, влево, вверх и вбок, вниз и вбок?

PashPP 03.01.2013 15:05

Красиво, но двадцатню процентов ЦП жрет. Хотя, если останавливать анимацию при открытии другой вкладки - норм. А то задолбал этот снежок, который в фоне на 3 вкладках сжирает все ресурсы.

Deff 03.01.2013 15:10

Цитата:

Сообщение от PashPP
Красиво, но двадцатню процентов ЦП жрет

Хм , в опере 12.12 практически не зависит от числа вкладок, жрет 15% не увеличивая

Дзен-трансгуманист 03.01.2013 15:28

Цитата:

Сообщение от Deff
поставить на сайт во всю страницу

Не нужно во всю страницу - это будет не анимация, а слайдшоу.)

Цитата:

Сообщение от DjDiablo
в следующем году ждём снежинок на webGL )))))

Ага, если у меня дотянуться руки взяться за спеку, что также влечет за собой изучение OpenGL ES 2.0, а в паузах осваивать соответствующую матчасть да и сами основы 3d, на что уйдет минимум месяц в ускоренном темпе - тогда может быть...))

Цитата:

Сообщение от Gozar
местами подтормаживает

Ну что ж. Спецификация не гарантирует аппаратное ускорение 2d-контекста, но даже если оно и ускорено в некоторых движках, все равно невозможно имеющимися средствами распараллелить отрисовку отдельных спрайтов, мощь железа тут недоступна. Только что померил, каждый кадр содержит 630-670 видимых снежинок, а это уже серьезная цифра с учетом нецелочисленных трансформаций отображения - больше нагрузки на графическую подсистему. К сожалению, тут не разгонишься особенно.

Цитата:

Сообщение от Gozar
А варианты будут, чтобы снежинки полетели вправо, влево, вверх и вбок, вниз и вбок?

А нужно? Ну, я в том смысле, что это тема наподобие "посмотри и порадуйся", я как-то не планировал что-то делать с этим дальше. Поэтому оно в оффтопике.
Оно-то можно сделать и спиральное падение с осевым вращением, и нелинейный ветер с интерполяцией по динамическому векторному полю и зонами турбулентности. Много чего можно сделать. :) Данное было сделано просто из любопытства - и любопытство я удовлетворил.

Gozar 03.01.2013 16:40

Цитата:

Сообщение от Дзен-трансгуманист
А нужно?

Тебе, не знаю. Наверное нет.

Прикольно, когда пишешь то, с чем потом можно играться.

Наверное предложение неуместно, раз это в оффтопе?!

godofjavascript 03.01.2013 17:04

годный бенчмарк, как крайсиз 2 прямо

9xakep 03.01.2013 21:07

godofjavascript,
комп гавно


А фон кстати у dmitrymar, или как там его ник пишется, не стоял? :)

godofjavascript 03.01.2013 21:10

Цитата:

Сообщение от 9xakep
комп гавно

пень четвертый, крайсиз первый на ультра-максимальных идет на 27 фпс

9xakep 03.01.2013 21:24

godofjavascript,
27 - это маловато как бы

Gozar 03.01.2013 21:38

Цитата:

Сообщение от 9xakep
комп гавно

Не гунди. У меня комп хороший. Четыре проца и картинка такая:
ЦП1 52% 3400МГц
ЦП2 34% 1600МГц
ЦП3 27% 1600МГц
ЦП4 25% 1600МГц

Если без скрипта то 2% на 1600 держится.

За такие вещи кастрировать нужно. Но так как это не продакшн, а just for fun, то это простительно.

godofjavascript 03.01.2013 22:32

Цитата:

Сообщение от 9xakep
27 - это маловато как бы

ты вообще блять понял к чему я сказал что у меня кризис на максимальных идет? или ты вообще дно?

l-liava-l 03.01.2013 22:59

классно! даже натурально.:)
гозар и максимус - вредины!

dmitriymar 04.01.2013 00:15

Цитата:

Сообщение от 9xakep
А фон кстати у dmitrymar, или как там его ник пишется, не стоял?

та да, завтра надо будет зайти к адвокату))
Дзен-трансгуманист, зайдём к тебе с приставом в гости, посмотрим что у тебя личное , а что лишнее))

dmitry111 04.01.2013 07:39

Интересно было бы сравнить нагрузку на проц двух одинаковых работ сделанных на канвас и на флеш.. Мне кажется флеш нагружает не меньше.. взять тот же рутрекер - напичкан флешом

godofjavascript 04.01.2013 16:12

правильно говорить "на канве", а не "на канвасе"

Цитата:

Сообщение от l-liava-l
гозар и максимус - вредины!

тока узнал?

Цитата:

Сообщение от dmitry111
Мне кажется флеш нагружает не меньше..

а погуглить сравнения в сети, не?

Дзен-трансгуманист 04.01.2013 20:21

Цитата:

Сообщение от godofjavascript
как крайсиз 2 прямо

Цитата:

Сообщение от Gozar
ЦП1 52% 3400МГц
ЦП2 34% 1600МГц
ЦП3 27% 1600МГц
ЦП4 25% 1600МГц

Достал с антресоли старый ноут, 2006 год выпуска, проц Celeron M 1,73 GHz (одноядерный само собой), WinXP.
Нагрузка в хроме - 40-50%. Вот так-то. :D

Опять же, повторюсь, что производительность напрямую зависит от того, ускорен ли 2d-контекст на конкретном браузере под конкретную систему аппаратно, или нет. А если нет, то от того, насколько эффективны программные алгоритмы.

И предполагаю, что на WebGL показатели в среднем были бы лучше - но поскольку я его еще не изучал, то и проверить пока ничего не могу, могу только строить догадки. :)

godofjavascript 04.01.2013 22:45

а я думал это на нем сделано, это просто 2д контекст?

nerv_ 04.01.2013 22:59

Дзен-трансгуманист, 3-и страницы треда в очередной раз говорят о том, что инициатива наказуема :D
Вроде ничего никому не обещаешь, а они все требуют и требуют )

Дзен-трансгуманист 04.01.2013 23:52

Цитата:

Сообщение от godofjavascript
это просто 2д контекст?

Ну да. :)

Цитата:

Сообщение от nerv_
а они все требуют и требуют

me.on( 'request', function ( request, response ) {
  response.write( 'ПНХ' );
  response.close();
});

Таков им будет мой ответ.)))

DjDiablo 05.01.2013 00:14

Цитата:

Таков будет мой ответ.)))
:)

ну если говорить о производительности то часть нагрузки должно давать масштабирование на particle.spawnZ
sprite.draw( ctx, particle.spawnX, y, 0, 1/ particle.spawnZ, 1/ particle.spawnZ );

(маштабирование горизонталь + вертикаль )*каждую снежинку * каждый кадр = замедление.

без масштабирования у меня прирост производительности вдвое, нагрузка упала c 25% до 12-10%


Попробуйте замерить производительность (без масштабирования) у себя сами.
<head>
<style>
img, canvas { position: absolute; left: 0px; top: 0px; margin: 0px; padding: 0px; border: none; }
</style>
<script>

function byId ( id ) {
  return document.getElementById( id );
}

function rand ( min, max ) {
  return Math.random() * ( max - min ) + min;
}

function now () {
  return ( +new Date() ) / 1000;
}

function Sprite ( image, sx, sy, sw, sh, originX, originY ) {

  this.image = image;
  this.sx = sx;
  this.sy = sy;
  this.sw = sw;
  this.sh = sh;
  this.originX = originX ? originX : 0;
  this.originY = originY ? originY : 0;
}

Sprite.prototype.draw = function ( context, x, y, angle, scaleX, scaleY ) {

  //context.save();
  //context.translate( x, y );
  //context.rotate( angle );
  //context.scale( scaleX, scaleY );
  /*context.drawImage(
    this.image,
    this.sx, this.sy,
    this.sw, this.sh,
    -this.originX,
    -this.originY,
    this.sw, this.sh
  );*/

  context.drawImage(this.image, x,y);
  //context.restore();
}

function Flakes ( canvas, flake, zmin, zmax, count, speed, loopTime ) {

  var viewW = canvas.width;
  var viewH = canvas.height;

  zmax = Math.min( zmax, ( loopTime * speed - flake.height ) / viewH );
  zmin = Math.min( zmin, zmax );

  var particles = [];
  var flakeHalfW = flake.width * 0.5;
  var flakeHalfH = flake.height * 0.5;

  for ( var i = 0; i < count; i++ ) {

    var spawnZ = rand( zmin, zmax );

    particles.push({
      spawnX    : rand( -flakeHalfW / spawnZ, viewW + flakeHalfW / spawnZ ),
      spawnZ    : spawnZ,
      spawnTime : rand( 0, loopTime ),
      speed     : speed / spawnZ,
      halfH     : flakeHalfH / spawnZ
    });
  }

  particles.sort( function ( a, b ) {
    return b.spawnZ - a.spawnZ;
  });

  this.particles = particles;
  this.viewW     = viewW;
  this.viewH     = viewH;
  this.loopTime  = loopTime;
  this.ctx       = canvas.getContext( '2d' );
  this.sprite    = new Sprite(
    flake,
    0, 0,
    flake.width, flake.height,
    flake.width * 0.5, flake.height * 0.5
  );
}





Flakes.prototype.draw = function ( time ) {


  var ctx       = this.ctx;
  var sprite    = this.sprite;
  var particles = this.particles;
  var viewH     = this.viewH;
  var loopTime  = this.loopTime;

  //временный контекст
  //var pCanvas = document.createElement('canvas');
  //var pCtx = pCanvas.getContext('2d');
  //pCanvas.width = 480;
  //pCanvas.height = 384;  

  for ( var i = 0; i < particles.length; i++ ) {

    var particle = particles[ i ];
    var y = -particle.halfH + ( ( time + particle.spawnTime ) % loopTime ) * particle.speed;

    if ( y < viewH + particle.halfH ) {
      //sprite.draw( pCtx, particle.spawnX, y, 0, 1, 1 );
      sprite.draw( ctx, particle.spawnX, y, 0, 1, 1 );
    }
  }
  //ctx.drawImage(pCanvas, 0,0);
}

window.onload = function () {

  var imgBg    = byId( 'image-bg' );
  var imgFlake = byId( 'image-flake' );
  var canvas   = byId( 'canvas' );

  document.body.removeChild( imgFlake );

  canvas.width = imgBg.width;
  canvas.height = imgBg.height;

  var ctx = canvas.getContext( '2d' );
  var flakes = new Flakes( canvas, imgFlake, 2, 25, 1500, 400, 30 );

  function render () {

    ctx.clearRect( 0, 0, canvas.width, canvas.height );
    flakes.draw( now() - timeOffset );
  }

  var animate = true;
  var timeStop;
  var timeOffset = 0;
  var intervalId = window.setInterval( render, 40 );

  canvas.onclick = function () {

    if ( animate = !animate ) {
      timeOffset += now() - timeStop;
      intervalId = window.setInterval( render, 40 );
    }
    else {
      timeStop = now();
      window.clearInterval( intervalId );
    }
  }
}

</script>
</head>
<body>
<img id="image-flake" src="http://dl.dropbox.com/u/19390559/share/snowflake.png">
<img id="image-bg" src="http://dl.dropbox.com/u/19390559/share/christmas-tree.png">
<canvas id="canvas"></canvas>
</body>


В качестве решения
можно спрайты готовить заранее а не ресайзить в процессе рендеринга.

это конечно всё теории а пробывать лень. Мне кажется что большие снежинки отьедают выигрыш обратно, так как маленькие снежинки прорисовать быстрее, предварительно подготовленные спрайты могут дать представление истинном приросте)

UPD - из решения выпилено ещё часть манипуляций с канвасом

Gozar 05.01.2013 00:59

Сейчас перечитал название темы и вспомнил "Брат 2". Там был один сутенер, "ганста-нига" (3:15 сек) на его сленге русские были "снежок". В свете этого, падающий снежок воспринимается по другому. :)

Дзен-трансгуманист 05.01.2013 01:18

DjDiablo,
particle.spawnX и y тоже с плавающей запятой, попробуй Math.round их - будет еще быстрее.
А подресайзить заранее конечно можно, но это на каждый спрайт отдельный слой придется готовить, потому что масштаб тут у всех разный. То есть, в данном случае это будет 1500 картинок размером вплоть до 20x20.

Еще я, наверно, неправильно поступил, что подготовил все частицы заранее и зациклил их по модулю от времени. Логичнее было бы генерировать их на лету, но это мой первый опыт создания систем частиц, так что бить ногами не надо.)

Цитата:

Сообщение от DjDiablo
нагрузка упала c 25% до 12-10%

Хм, а у меня без изменений.

DjDiablo 05.01.2013 02:01

Да мне тоже неудаётся воспроизвести результат хз, почему в первый разы так много выдало.

прирост есть но слишком мальнький (в хроме), интересно как в других браузерах.

в качестве альтернативы пробую предварителный рендеринг, но здесь он прироста отчего то тоже недаёт толи контекст слишком большой, толи частиц слишком мало чтобы профит ощутить.

upd.
Выпилил ещё лишние манипуляции с контекстом, кому нелень потестите пример сверху, я его обновил.
сейчас у меня 9-10% стабильно VS 13-15% у оригинала

Tim 05.01.2013 03:01

Дзен-трансгуманист,
Крутая штука. Тоже была идея такое сделать, но до НГ не успел. Потом так и забыл.
Спасибо, порадовал. Я даже гозару плюсик поставил чтобы тебя плюсануть. :D
Кст, о ограничениях. Я немо опять наверно могу минус поставить. Ща проверим :)

Deff 05.01.2013 03:05

Цитата:

Сообщение от DjDiablo
Выпилил ещё лишние манипуляции с контекстом, кому нелень потестите пример сверху, я его обновил.

У мну под Оперой у TS первом примере -13 процентов - твое - 52% от полной загрузки проца
Опера 12.12 XP двух ядерка 3Гига частота проца

Gozar 05.01.2013 03:06

DjDiablo,
У [html height=600 ] задай.

Tim 05.01.2013 03:12

http://processingjs.org/ - нашёл недавно.

Потихоньку колупаюсь в своём недавнем приобретении (Arduino). Выяснил что язык для IDE от этой игрушки основан на языке processing, который заточен под работу с графикой. processingjs это порт этого языка на JS. Либа использует canvas.

Deff 05.01.2013 03:19

Проверил в Лисе - тож самое что и в опере
Ошущение:... что движения в канвас походят на топот солдат по мосту, т.е при определенном соотношении тактовой частоты и периода обновлений картинок 0 все это приходит в резонанс, очевидно при многопроцессорах еще хитрее
=============
Зы Проверил на ноуте - эффект обратный - Первопост жрёт порядка 40% и 25% пост от DjDiablo...

DjDiablo 05.01.2013 03:25

Да тут как раз всё логично.
Опера похоже медленно копирует изображения в канвас.
у меня снежинки больше, поэтому и тормозит опера сильнее
заремарь context.scale( scaleX, scaleY ); в оригинале, получишь точно такиеже тормоза
похоже что опере проще отмасштабировать картинку, чем отрисовать её.

у меня в опере мой 25, оригинал 12%
после отключения scale в оригинале, оригинал также начинает жрать 25%

opera.
Версия: 12.00 Сборка:1467
Платформа: Win32 Система: Windows 7

хром v23.0.1271.97
разница 9-10 vs 12-15-17 я уже писал выше. (однако это на ноуте, на PC гляну завтра, хотя непонимаю почему должно отличаться)

лису нетестил, вероятно таже болезнь что и у оперы

PC I3-2330m 2.2 гц, ноут

Deff 05.01.2013 03:27

DjDiablo,
В лисе тож самое - я писал
==========================
В Хроме - твой пост и Первопост - почти идентично 23%

cyber 05.01.2013 14:55

Цитата:

Сообщение от Deff (Сообщение 225343)
Проверил в Лисе - тож самое что и в опере
Ошущение:... что движения в канвас походят на топот солдат по мосту, т.е при определенном соотношении тактовой частоты и периода обновлений картинок 0 все это приходит в резонанс, очевидно при многопроцессорах еще хитрее
=============
Зы Проверил на ноуте - эффект обратный - Первопост жрёт порядка 40% и 25% пост от DjDiablo...

хм, у меня в опере так же как и в хроме работает , да и в лисе норм .


Часовой пояс GMT +3, время: 21:05.