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

Вычеркиваем


После непродолжительного подбора различных способов реализации, сразу были исключены варианты:
  • с gif-анимацией (из-за низкого качества изображения);
  • с Flash (по договоренности, Flash-технологии решили в проекте не использовать);
  • с анимацией с помощью JQuery посредством функции $().animate (по причине ее прожорливости).


CANVAS в помощь!


Итак, остановились мы на использовании Canvas и JavaScript, посчитав этот вариант оптимальным для реализации нашей задачи.

Пофантазируем...

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

image

Нам необходимо половину изображения скопировать и добавить с правой стороны, т.к. наша карта будет двигаться по поверхности canvas-блока, и при крайнем положении начинать движение заново. Результат получается таким:

image

image

Создаем планету

В качестве космического пространства у нас выступит блок div с любым подходящим для этого бэкграундом, а внутри разместим элемент с id=«planet»:

<div style="background-image:url(space.jpg); width:1000px; height: 1000px;">
     <canvas id="planet" width="300" height="300" style="position: absolute; left:200px; top: 200px;">
     </canvas>
</div>

Далее заставляем нашу карту двигаться внутри созданного элемента canvas, который скоро превратится в планету:

 $(function(){

        var pl_id = 'planet';

	var image = new Image();
	image.src = 'images/planets/1/1/map.jpg';

        // определяем длину и высоту элемента canvas
	var width = $('#'+pl_id).width();
	var height = $('#'+pl_id).height();

	var canvas = document.getElementById(pl_id);
	var id = canvas.getContext("2d");

	var newMoveWidth = 0;
	var newMoveHeight = 0;

	var drawPl = function(){

	     id.clearRect(0, 0, width, height);
             // рисуем карту с новыми координатами внутри элемента
	     id.drawImage(image, newMoveWidth, newMoveHeight, width, height, 0, 0, width, height); 

	     if (newMoveWidth >= 899.5) newMoveWidth = 0; // если смещение достигло предела, начинаем сначала
	     else newMoveWidth = newMoveWidth+0.5; // иначе двигаем карту дальше

	}

        setInterval(drawPl, 50); // запускаем анимацию со скоростью 50 мс.

 });

В результате произведенных действий, получаем примерно такую картину:

image

Закругляем...


Квадратных планет еще не открыли, поэтому придадим нашему небесному телу более привычный вид, прописав в style нашего элемента canvas border-radius на 50 процентов. Получаем:

image

Уже лучше, однако по-прежнему нет ощущения, что перед нами сферический объект.

Теперь подготовим в графическом редакторе круг с тенью по краям и бликами. Он должен быть обязательно полупрозрачным, т.к. мы будем накладывать его на нашу планетарную карту. В оригинале он будет выглядеть так:

image

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

Финальный код нашей анимированной планеты получается таким:

<div style="background-image:url(space.jpg); width:1000px; height: 1000px;">
     <canvas id="planet" width="300" height="300" style="position: absolute; left:200px; top: 200px; border-radius:50%">   
     </canvas>
</div>

И код самой анимации:

$(function(){

        var pl_id = 'planet';

	var image = new Image();
	image.src = 'map2.jpg';

        // загружаем изображение тени и бликов планеты
	var fxShadow = new Image();
	fxShadow.src = 'planet_shadow.png';

        // определяем длину и высоту элемента canvas
	var width = $('#'+pl_id).width();
	var height = $('#'+pl_id).height();

	// рассчитываем угол вращения планеты
	var beta = 360/900;
	var beta = (beta*Math.PI)/360;
	var l = (Math.sqrt(width*width+width*width))/2;
	var gam = Math.PI - ((Math.PI - (beta * Math.PI)/360)/2) - (Math.PI/4);
	var b = 2*l*Math.sin(beta/2);
	var x = b*Math.sin(gam);
	var y = b*Math.cos(gam);
	var p1 = Math.cos(beta);
	var p2 = Math.sin(beta);

	var canvas = document.getElementById(pl_id);
	var id = canvas.getContext("2d");

	var newMoveWidth = 0;
	var newMoveHeight = 0;

	var drawPl = function(){

	        id.clearRect(0, 0, width, height);
		// применяем к нашей планете вращение
		id.transform(p1, p2, -p2, p1, x, -y);
                // рисуем карту с новыми координатами внутри элемента
		id.drawImage(image, newMoveWidth, newMoveHeight, width, height, 0, 0, width, height); 
                //добавляем тень и блики
                id.drawImage(fxShadow, 0, 0, width, height);
                // если смещение достигло предела, начинаем сначала
		if (newMoveWidth >= 899.5) newMoveWidth = 0; 
		else newMoveWidth = newMoveWidth+0.5; // иначе двигаем карту дальше

	}

        setInterval(drawPl, 50); // запускаем анимацию со скоростью 50 мс.

 });

Финальный результат анимации планеты в игре:

image

image

Все вопросы и предложения по улучшению этого варианта реализации буду рад увидеть в комментариях.

Спасибо за внимание!

Комментарии (14)


  1. Magistr_AVSH
    16.10.2015 20:16

    Угу, есть такой же эффект, но написанный на three.js/webgl
    image
    image
    image
    Вот здесь всякое.
    Там и есть кастомизируемые тени и вообще рандомайзер.
    Спасибо Darthman за идею и большую часть реализации =)
    twitter.com/magistr_avsh


    1. Magistr_AVSH
      16.10.2015 20:19

      Во еще красивая планетка:
      image
      Все вращается естественно.


    1. AstrumOnline
      17.10.2015 09:36

      Да, реализовано красиво. Признаться, вариант реализации на Three.js тоже рассматривали, но нас остановило то, что сейчас не все браузеры поддерживают WebGL (в особенности, старые версии браузеров). Поэтому, вариант отпал.


    1. Darthman
      19.10.2015 11:53
      +1

      Всегда пожалуйста.


  1. vintage
    16.10.2015 22:43
    -6

    http://liveweave.com/xZtwj5


  1. ginkage
    16.10.2015 23:13
    +3

    Или вот, тоже WebGL (можно крутить, таская мышкой и зумить колёсиком), реализовано двумя треугольниками, с освещением, bump-mapping'ом и атмосферой. :)


  1. superalesha
    17.10.2015 08:55
    +5

    Хм. Вам и canvas-то не обязательно использовать, да и jQuery — разве что у вас остальная логика на нем написана.

    Плавное движение фона легко реализуется через CSS3 animations и background-position.

    @keyframes pl {
    100% {
    background-position: -1000px 0;
    }
    }

    animation: pl 5s infinite;

    А направление вращения через transform: rotateY :)

    Эти вещи уже даже мобильные браузеры свободно поддерживают, по-моему.

    А WEBGl тут использовать, мне кажется, чересчур, он все-таки для более комплексных задач предназначен.


    1. AstrumOnline
      17.10.2015 09:41
      -3

      Вы все верно поняли — использовали jQuery, поскольку, большая часть функционала реализована на нем. А предложенный Вами вариант на CSS3, и правда, рациональнее, да и целесообразнее при использовании в простых эффектах анимации, поскольку является куда более производительным, чем анимация на javascript. Осталось только протестировать кроссбраузерность этого способа.


      1. YChebotaev
        17.10.2015 17:43
        +3

        Все уже протестировано до вас: caniuse.com/#feat=css-animation


  1. aalebedev
    17.10.2015 12:03

    Где можно карту сгенерировать для планеты?


    1. AstrumOnline
      17.10.2015 13:45

      к примеру, фильтр LunarCell для Adobe Photoshop оказался наиболее удобным.


  1. cjfynjy
    17.10.2015 13:34
    +6

    Извините, но это не вращение. Это передвижение текстуры вбок и поворот вокруг центра круга. Математически неправильно — это ладно, но оно и невооруженным глазом заметно, что «что-то не так».

    Можно сделать точно (правда, не уверен, насколько это просто будет реализовать имеющимися средствами). Проекция ортогональная, смотрим на планету мы с плоскости экватора, так что математика довольно простая. Для каждой точки на карте косинус широты — координата по вертикали, косинус [долготы минус угол поворота] — координата по горизонтали. Само собой, все координаты (в т.ч. широта / долгота) отсчитываются от центра в данном примере.

    Если вот так не получится, то можно учесть хотя бы, что при взгляде на планету экватор вращается «быстро», а полюса — «медленно», и наложить на текстуру что-то вроде линзирования в центре.


    1. AstrumOnline
      17.10.2015 13:40

      Фактически — да, Вы правы. Подобным способом создается лишь эффект вращения, весьма далекий от реалистичного, однако, пока более «правильного» способа реализации данными методами мы, к сожалению, не нашли.


      1. barkalov
        17.10.2015 19:20
        +2

        Более правильный способ есть в three.js — эмуляция 3d на canvas'е (без WebGL). Но, поскольку канвас умеет только аффинные преобразования сферу приходится разбивать на (слишком) большое количество треугольников (чтобы аффинные преобразования выглядели не так криво), что напрочь убивает производительность.