Как это работает. Под капотом у крутяшек (часть 1, flash)

?
в блоге Блог им. V747
Несколько людей спросили, как вообще собираются картинки с понями, которые можно крутить, попытаюсь сжато рассказать.
Разбирать будем на реальном примере
Сразу говорю, код который приведен ниже отвратительный и приводится исключительно для объяснения, как это работает.
Итак, у нас есть задача, сделать Луну в подарок одному человеку и где то 4 дня на моделирование, сборку и размещение крутяшек. Поехали.


Идея тюрнейблов не новая от слова совсем. На сайтах автомобилей, ювелирных украшений, бытовой техники есть превью картинки с несколькими фреймами, которые можно вращать. Некоторые сайты используют полноценные 3д вьюверы для своей продукции. Это и хорошо и плохо. У каждого типа есть свои ограничения и преимущества.
СпойлерЕсли говорить простым языком это управляемая секвенция-набор кадров, снимающие объект в моем случае с шагом в 3.6 градуса.
3.6 градуса, 100 картинок и скрипт который управляет их сменой и вешает некоторые плюшки, вот и все, спасибо.
Шучу. При таких входных данных надо решать следущие проблемы.
1) Вес.
Кэш современных браузеров ограничен, где то 30мб, и надо в этот вес уложится. С постепенным переходом девиантарта на html5 надо учитывать, что мобильные устройства имеют свои особенности в распределении памяти. Иными словами-нам нужен контейнер.
2)Хранение.
Если в случае с флеш крутяшкой контейнером выступает сам ролик swf, то в случае html, нужно искать сервер, который поддерживает прямые пути и папочную иерархию с прямыми именами файлов.
3)Отклик (до сих пор полностью не решено)
100 картинок должны одинаково быстро меняться при движении курсора и свайпа пальцем, как на экране монитора, так и на мобильных девайсах. Во втором случае обеспечить быстродействие сложнее, устройства все разные и если где то будет летать на планшете, то не факт, что будет хотя бы 3 фпс, на каком нибудь старом телефоне.
4)Ссылки. Неочивидный момент, но для html5 крутяшек недостаточно поднять свой сервер. Девиантарт не дает вставлять ссылки на непроверенные сайты. Значит нужно воспользоваться тем сайтом который входит в списки доверенных на DA

Эта часть посвящена только флеш ролику, иначе объем текста получится громадным, в html 5 сделать тоже самое сложнее.

Изначально секвенция собирается в блендере, там же редактируется и отправляется во флеш.
В блендре ставится фигурка, вокруг нее кривая-кольцо сплайн, на него вешаем камеру с типом следования follow path, в центр кольца добавляем пустышку и к ней трачим камеру.
Тип трака:



После настройки сцены идем на sheepit-renderfarm.com/ и загружаем проект.
Идем пить чай.
После пары (десятков) часов получаем готовую секвенцию.
Идем во флеш.
Решения для флеш крайне просты.
Я использую Adobe Flash Professional CS 5.5
Создаем новый проект, в свойствах сцены выставляем размеры и цвет фона как у сайта Devianart

Созадем два ключевых кадра-на одном прелоадер, на втором наша сцена.

Переходим на первый кадр и пишем такой код
/*
устанавливаем значение переменных
*/
var w:uint = 200; // ширина прелоадера
var h:uint = 10; // высота прелоадера
var colorFon:uint = 0xcccccc; // цвет фона прелоадера
var color:uint = 0x333333; // цвет индикатора загрузки
/*
останавливаем воспроизведение флеш-ролика в первом кадре
*/
stop();
/*
рисуем контейнер прелоадера
*/
var preloaderContainer:Sprite = new Sprite();
preloaderContainer.graphics.lineStyle(1,color,1);
preloaderContainer.graphics.beginFill(colorFon,1);
preloaderContainer.graphics.drawRect(0,0,w,h);
preloaderContainer.graphics.endFill();
addChild(preloaderContainer);
/*
помещаем наш прелоадер на середину флеш-ролика
*/
preloaderContainer.x = (stage.stageWidth - w) / 2;
preloaderContainer.y = (stage.stageHeight - h) / 2;
/*
рисуем индикатор загрузки
*/
var band:Sprite = new Sprite();
band.graphics.beginFill(color,1);
band.graphics.drawRect(0,0,w,h);
band.graphics.endFill();
preloaderContainer.addChild(band);
/*
в каждом новом кадре проверяем объём загруженной информации и на основе этой информации изменяем индикатор загрузки
*/
addEventListener(Event.ENTER_FRAME, onEnterFrames);
function onEnterFrames(event:Event)
{
	var bLoaded:uint = loaderInfo.bytesLoaded;
	var bTotal:uint = loaderInfo.bytesTotal;
	band.scaleX = bLoaded / bTotal;
	/*
	если флеш-ролик загружен полностью, то удаляем сам прелоадер и переходим на следующий кадр
	*/
	if (bLoaded >= bTotal)
	{
		removeChild(preloaderContainer);
		removeEventListener(Event.ENTER_FRAME, onEnterFrames);
		nextFrame(); // или gotoAndPlay(номер_кадра);
	}
}

Все, прелоадер готов.
Идем на второй фрейм и создаем новый символ

Во всплывшем окне нажимаем OK, теперь мы сидим в символе (символ во флеше-это картинка или секвенция, которая имеет свои собственные слои, линию воспроизведения и управление по обращению к имени инстанса).Идем в файл-> импортировать секвенцию в сцену

Секвенция должна иметь вид: 0001.png,0002.png (именно png-это позволит впоследствии настроить качество финальной флешки при сохранении,jpg не дает такого сделать)и так далее, двойной клик на первом фрейме, программа предлагает загрузить все файлы.
Идем попить чаю

После того как секвенция загрузилась, выходим из символа и видим в билиотеке все наши фреймы и наш символ

Захватываем его мышкой и тащим на сцену, прасполагаем так чтобы был подогнан по всем углам. Дабл клик на символе, мы внутри него, идем на таймлайн и выбрав все фреймы символа нажимаем пкм и выбираем reverse frames, это нужно для того, чтобы крутяшка не вращалась в противодействие курсору, выходим из символа кликнув на слово сцена

Идем на вкладку actions, при выбранном втором кадре и пишем следующий код
import com.adobe.images.JPGEncoder;
import flash.net.FileReference;
import flash.net.FileFilter;



maincontent.stop();

maincontent.gotoAndStop(1);

maincontent.alpha=0;

stage.addEventListener(MouseEvent.MOUSE_DOWN, mDown); 
stage.addEventListener(MouseEvent.MOUSE_UP, mUp); 

maincontent.addEventListener(Event.ENTER_FRAME, fadein);



function fadein(e:Event){
    if(maincontent.alpha <1){
        maincontent.alpha +=0.05;
    }
	
}

var prevMouseX:int; 
var playingStatus:int = 0;

function mDown(event:MouseEvent):void { 
prevMouseX = stage.mouseX; 
maincontent.addEventListener(Event.ENTER_FRAME,mMove); 
} 

function mUp(event:MouseEvent):void { 
maincontent.removeEventListener(Event.ENTER_FRAME,mMove); 
} 

function mMove(event:Event):void { 

var activeMouseX = stage.mouseX; 

if (activeMouseX > prevMouseX){ 
if(maincontent.currentFrame == 1){ 
maincontent.gotoAndStop(maincontent.totalFrames); 

} else { 
maincontent.prevFrame();

} 
} 
if (activeMouseX < prevMouseX){ 
if(maincontent.currentFrame == maincontent.totalFrames) { 
maincontent.gotoAndStop(1); 

} else { 
maincontent.nextFrame();

} 
} 
prevMouseX = stage.mouseX; 
}

//----управление с клавиатуры

stage.addEventListener(KeyboardEvent.KEY_DOWN, KeyPressed); 

function KeyPressed(event:KeyboardEvent)
{
    if(event.keyCode==39)
	{
		if(maincontent.currentFrame == 1){ 
           maincontent.gotoAndStop(maincontent.totalFrames); 
		   
      	} else { 
		maincontent.prevFrame();
		
		} 
	}

    if(event.keyCode==37)
	{		
		if(maincontent.currentFrame == maincontent.totalFrames) { 
		maincontent.gotoAndStop(1); 
		
		} else { 
		maincontent.nextFrame();
		
		} 
	}
	
	
    if(event.keyCode==83)
	{		maincontent.stop();
		playingStatus=0;
		
		 var bitmapData:BitmapData=new BitmapData(maincontent.width, maincontent.height);
		
    		bitmapData.draw(maincontent);
			
    		
    //
    var jpgEncoder:JPGEncoder = new JPGEncoder(100);
    var byteArray:ByteArray = jpgEncoder.encode(bitmapData);
    var imagesFilter:FileFilter = new FileFilter("Images", "*.jpg;*.gif;*.png");
    var fileReference:FileReference=new FileReference();
    fileReference.save(byteArray,'frame №'+maincontent.currentFrame.toString()+'.jpg');
	}
 
    
  	if(event.keyCode==32)
	{ playingStatus++;
	if(playingStatus==2)
	{
		playingStatus=0;
	}
	switch (playingStatus){
		case 1:
		maincontent.play();
		break;
		
		case 0:
		maincontent.stop();
		break;
	}
	}
	
	
	
}


Выделяем символ и нарекаем его именем maincontent

В коде мы останавливаем воспроизведение символа, делаем альфу в ноль, вешаем событие fade in, подцепляемся к мышке и просто закольцовываем наши фреймы, при достижении 100 го фрейма он сменяется на 1ый.
Далее идут функции управления с клавиатуры и сохранения изображения.
Теперь необходимо настроить качество выходной флешки


Нам нужно уложиться в ограничение на 30 мб от дивана, ставим поочередно от 98 процентов и ниже, сохраняемся и смотрим сколько весит ролик. При достижении нижней границы порога, сохраняем, пьем чай и грузим на да флеш ролик.
Получаем такое
В данном случае проблемы описанные в начале
1)Контейнер-сам ролик флеш
2)Сайт DA предоставляет возможность грузить флеш ролики напрямую
3)Отклик, в данном случае код написанный на ac3 не особо шустрый, можно цепляться непосредственно за событие перемещения курсора, а не событие смены кадра
4)Проблемы со ссылками будут решаться в случае с html 5 роликом, здесь все собрано в самом проекте.
Позже, если будет интересно, допишу про html 5 сборку.

Теги:

  • В избранное
    17

34 комментария

gotoAndStop

Ах как давно я не видел этой фразы)

Крутой пост, спасибо!
Было интересно.
Ты, наверно, уже отвечал, но какими мощностями ты располагаешь, или сейчас всё у «овечки» рендерится? Сотня картинок, это не мало. Плюс, у тебя всякие эффекты, типо шершавости были. Вопрос может глупый, но я до рендера сам не доходил.
Наивно полагал, что индикатор загрузки, это стандартная флешная штука.
Ну и Луна класс, да.
q9300 +4gb ram+gtx660
Комп подарен Bra1n_Eater ом
Рендерю сейчас почти все на овце, там помогает один парень, за что ему низкий душевный поклон.
Если бы не эти двое ребят, то мне жилось очень туго
Гордому обладателю пентиума чипсет в моноблоке лучше не соваться в такое дело, да?
Ну почему, могу помочь с рендером
О, спасибо! Было интересно.
Теперь одной тайной меньше)))
Шикарный пост.
Забрал в бибилотеку.
<в ожидании 3D порнухи от V747>
Жиза)))
Какой охреневший и моральный урод посмел изобразить Вечную в таком низком виде? Она что, портовая шлюха? Чернушка из деревни? Что это за мерзкая ересь?
Это тебя еще на порнреакторе не было.
Я был даже там, где ты вообразить себе не можешь. Но от притонов я этого и ожидаю.
А эта помойка не притон штоле? Тут только обложка цивильная, чтоб роскомговнодзор до детской порнухи не доебался.
А где на табуне можно достать цп? Это же монастырь в этом плане. Не сомневаюсь, что тут сидит хотя бы парочка педофилов, но в остальном это омежки всех мастей. Половину пользователей табуна среднестатистическая ЦАшка может научить пестикам и тычинкам. Говорю по своему опыту. Кобылки нынче совсем испорченные пошли, аж обидно. Ведь это означает обилие в будущем уже взрослых без царя в голове и каких-либо ценностей и понятий о том что прилично, а что нет.
Серьезно, я этим обеспокоен.
Встретились как то лунафаг и клопер…
Хорошо хоть не два клопера. Эта тема настолько меня окружает, что я боюсь не заметить, когда ересь поглотит меня. Но пока что я тверд в своей вере и своих принципах.
Попросил бы, я не клопер, я — дрочер. Я не ограничиваюсь лошадьми.
АХАХААХ вот это бомбануло)
На самом деле ты можешь абсолютно спозиционировать те же 100 кадров в одной области и переключать их видимость скриптом.
Более того, раз уж мы изголяемся, то, при непрозрачном фоне, можно вообразить себя видеокодеком и каждый кадр после первого сделать difference-кадром. Не знаю, какой от этого получится выигрыш и не подохнет ли браузер, силясь это отрендерить, зато будет проще переключать кадры.
наговнокодил пример
примечаниятам код нужно будет красиво в класс упаковать, опции в объект скинуть и отдавать в конструктор, ну и что-то решить с позиционированием контейнера и сайзингом изображения
есть ещё подозрение, что можно это сделать на чистом css, но с ходу не соображу
анимация — уж какая была
работает на мобилке и десктопе
Спасибо огромное, на html у меня сейчас 2 вариата
1 с канвасом-там просто отрисовываются картинки по очереди
2 Див при движении мышки удаляем первого потомка дива-элемент img, и делаем append child, но это перестраивает dom
Буду ковырять=)
Надо еще попробовать свойство visibility, через чистый css можно создав два класса
.show и .hide которые просто будут переключать свойство у img, а в js коде менять названия классов
— Перестраивать дом лишний раз — так себе идея, потому что это тянет перестройку дерева рендеринга
— Можно переключать классы, это удобнее в смысле отделения стиля от кода, но опять же ведёт к пересчёту конечного стиля элемента
— Канва — хороший вариант, но я с ходу не помню, что у неё с отрисовкой. В том смысле, можно ли ей просто сказать, «эй, канва, нарисуй-ка мне вот этот имадж». Вроде бы можно.
— Мне нравится мой вариант (кто бы сомневался =), потому что у него не пертряхивается дом, все элементы уже там, где они будут при выводе, всё позиционирование отдано на откуп элементу-контейнеру. У меня серьёзные подозрения, что на больших суммарных объёмах графики это будет жрать память как не в себя, и это будет огромный минус, но это нужно тестить. С другой стороны, вряд ли это будет тяжелее ютуба, и в любом случае, не загрузив все кадры плавности анимации не добьёшься, а загрузив их всё равно придётся все держать в памяти.
Спойлер

Попробовал по быстрому поднять у себя на тумбе, увы вариант с опасити ест аж 900 метров на вкладку, css значит трогать дорого =\
Там 100 картинок и они при каждом движении мыши идут циклом переключая опасити у всех 100


Да, канвас очень проста, чисуем через 2д контекст

Вариант с заменой doomа
А если так?
function setFrame(n) {
  let lim = n
  while (lim < 0) {
    lim += ANIM_LEN
  }
  lim = (lim % ANIM_LEN) << 0

  for (let i = 0; i < FRAME_REF.length; i++) {
    FRAME_REF[i].style.display = (i === lim)
      ? 'initial'
      : 'none'
  }
}

плюс в ините убрать строку FRAME_REF[i].style.opacity = 0

Вот что бывает, когда тестируешь на данных далёких от реальных =)
Хотя это вряд ли поможет, потому что если ты загрузил много полновесных кадров, то они все лягут в память (а больше некуда). Тут разве что и вправду на difference-кадры переходить =)
Либо юзать html5 тэг video, прокручивая его скриптом =/
video пробовал, там есть переменная скорости, которая позволяет плавно менять ее на лету, попробовал использовать в проекте и сразу столкнулся с подводными камнями. Скриптом снимал скорость мышки и ее направление и передавал в переменную скорости. Оказалось что она не ест отрицательные значения, старый баг, но никто не фиксит, идея с видео отвалилась. Была даже идея сделать два видео, одно прямое второе реверс и подменять их z или опасити при изменении направления движения мыши. Но не справился с синхронизацией да и вес странички возрастал в 2 раза и такая конструкция оч неохотно работала на мобильных девайсах, да и картинки были мыльнее чем сейчас из за сжатия видео, использовал формат webm. Тут целый квест на самом деле)
Вариант выше попробую чуть попоже, сейчас с планша)
сейчас работает на канвасе так getturnable.tumblr.com/cyclone
на перестройке дума так getturnable.tumblr.com/testFull
У видео есть свойство currentTime, можно менять его =)
А вот и еще один камушек, это свойство имеет задержку смены кадра около 0,4 секунды, реалтайм отклика не будет) Даже когда на ютубе возишь таймлайн мы видим токо мыльное превью на экране, а после отпускания мыши идет задержка пол секунды на перемотку
Я пробовал через свойство playbackRate
Можно сделать через ключевые кадры, но опять же на перескок будет тратиться время
Посовещался с фронтами, и в среднем сошлись мы на том, что раз у канвы нет очевидных недостатков и бизнес её использует, то, при прочих равных, — это оптимальный вариант. У видео, кстати, мозилла обещала повысить точность позиционирования до 2мс, плюс, если оно закешировано, то лаг должен быть минимальный (посчитать от ближайшего опорного кадра до выбранного).
О, эт хорошие новости, в моем случае канва имеет один небольшой недостаток. Если долго не отпуская тащить мышкой то первые 30 20 кадров идут плавно, а потом начинается легкое подтормаживание, как будто заполняется какой то внутренний буфер. Если опять же не отпуская сменить направление вращения, то та же история 20 кадров в обратку плавно, потом подлагивание, особенно заметно на планшете
У меня в ФФ всё норм. Была пара просадок до 23фпс во время сборки мусора, но куда ж от него денешься. На телефонном хроме я лагов не заметил. А вот вариант с ДОМом почему-то крашится в какой-то момент =/ Но что-то меня обламывает удалённый отладчик настраивать =)
спасибо за помощь все равно)
Было интересно прочитать, пасибки за пост. =)
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.