суббота, 17 декабря 2011 г.

Отзывчивый интерфейс на Android

Если вы делате что-то сверх тривиального Hello World, то обеспечить "моментальную", без всяких тормозов реакцию интерфейса на действия пользователя дело не такое тривиальное, как это может показаться. Рассмотрим это на примере из простого графического редактора, который я сейчас разрабатываю.

 

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

Проблема лишь в том, что "быстренько" не выйдет. Уже после нескольких мазков нашей виртуальной кистью отрисовка начинает заметно притормаживать.

Шаг второй, не менее логичный. Заводим виртуальный холст (Bitmap+Canvas), на котором и будем выполнять само рисование, а затем просто выводить его поверх заранее подготовленного фона. А список операций будем вести параллельно, он нам таки пригодится для undo.

Замечу (но разъяснять не стану, поверьте  - он понадобится), что в действительности придется завести еще один виртуальный холст, для текущей операции. По завершении они объединяются, а в процессе накладываются по очереди.

Все замечательно, но все равно притормаживает, а undo выполняется вообще неимоверно долго.

Шаг третий, вместо списка операций храним список "снимков" экрана. Сжатие в PNG позволяет нам хранить довольно большое их количество, делаем это во вспомогательном потоке с меньшим приоритетом, запускаем его когда текущая операция завершается и возникает естественная пауза, пока пользователь не ведет стилусом (пальцем, языком или чем там еще) по экрану, а переставляет его на новое место.

Undo вписалось в приемлемые рамки, теперь мы просто достаем предпоследний снимок, а последний удаляем. Ну а чего же оно тормозит-то ТЕПЕРЬ?

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

Шаг четвертый. Делаем статический класс Trash, который будет у нас содержать коллекцию объектов, которые надо бы удалить, но не хочется вызвать этим появление на сцене сборщика мусора. И конечно у него будет метод освобождающий весь этот хлам - в тот момент, когда мы надеемся на лучшее ничем важным не заняты. Это не гарантирует, что сборщик вызовется именно в этот момент, но скорей всего так и произойдет, особенно если подсказать системе вызовом System.gc()

Шаг пятый, шерстим весь код и избавляемся от лишнего создания/удаления объектов. Т.е. если объект можно использовать повторно, то лучше его повторно использовать. В частности не зачем для каждой операции создавать новый Bitmap+Canvas, лучше просто почистить тот, что остался от предыдущей.

 

И конечно лучше об этом думать сразу.

среда, 7 декабря 2011 г.

Ошибка резидента MotionEvent & SurfaceView

После пары дней пыхтения и потения над отладкой тривиального графического редактора, обкусав ногти до локтей и выщипав все волосы в разных местах выяснил два момента. Может кому пригодится если сумеет найти данный текст.

Первая проблема известна в англоязычных энторнетах как SurfaceView flickering. Так вот если у вас картинка моргает, то почти сто процентов уверенности, что вы накосячили с синхронизацией потоков. Т.е. рисуете вы в одном потоке, а готовите картинку в другом и где-то лопухнулись. Вдумчиво изучите как у вас происходит синхронизация, прокол может быть совершенно неочевидным. Второй вариант - вынести все действия по подготовке картинки в один поток, тот же, что занимается рисованием. Тут нас может подстерегать другая проблема, названия ей я не нашел, но крови она мне попортила изрядно.

Итак, есть у нас обработчик кликов:

boolean onTouchEvent(MotionEvent e)

и есть желание эту обработку перенести в тот же поток, где у нас происходит формирование картинки. К слову сказать, SurfaceView был разработан в частности для того, чтобы эту обработку разнести по разным потокам :), но иногда это все таки может быть нужно. Так вот, делаем мы это элементарно, вариантов много, но все они сводятся к тому чтобы поместить событие в очередь, а в другом потоке эту очередь опросить и обработать. Так вот внимание, сюрприз:

 

Объект MotionEvent e, который получает наш обработчик передавать в другой поток для отложенной обработки НЕЛЬЗЯ. Потому что он существует в единственном экземпляре и в наш обработчик передается лишь ссылка на этот экземпляр. И как только вы выйдете из обработчика (положив эту ссылку в очередь), система с чистой совестью может поместить туда новое значение. В результате, если обработка у нас запаздывает, и мы успели  положить к себе в очередь три события MotionEvent, то все три будут указывать на одно и то же событие.

В результате мы одновременно а) теряем события и б) они у нас дублируются. Я плакаль...

Хрен вы это где найдете в документации. Это наглядный пример результатов телепатической связи между разработчиками Android SDK (которые полагают подобное поведение очевидным) и конечными его пользователями.

Вывод: верить можно только себе, и то, с осторожностью. Т.е. любой системный объект (в данном случае MotionEvent) должен быть обработан там, где он получен  и не следует рассчитывать что он сохранит своё состояние после выхода из обработчика. Если нам нужно сделать "ленивую обработку", то нужно получить "твердую копию", т.е. объект состояние которого полностью контролируется нашей программой, а не системой.

понедельник, 5 декабря 2011 г.

Андроид. Вести с полей

Наконец-то я плотно занялся этим девайсом. Делаю несложный графический редактор, узнаю много нового и интересного.

 

Моя первая попытка сделать его с использованием OpenGL ES1.0 потерпела полнейшее фиаско. Для редактора пиксельной графики OpenGL (по крайней мере ES1.0) подходит как квадратные колеса для велосипеда. Т.е. как-то приспособить может быть и можно, и даже едет, но совершенно не тривиально и с жутким скрыпом.

 

Вторая попытка была более удачной. Обычный Canvas заточен как раз под пиксельную графику, без всяких заморочек с 3D. Осложняет задачу то что я с ним не работал никогда (в отличие от OpenGL) и многие проблемы и их решение для меня неочевидны. А от некоторых спецэффектов, появившихся в процессе работы у меня волосы встали дыбом во всех местах, я так и не понял как такое возможно. Скажем появился у меня объект класса Picture, который по разному рисуется при четном и нечетном вызове процедуры рисования. 

 

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

 

Если кому интересно - нужно создать Bitmap, обернуть его в Canvas и спокойно рисовать туда. А когда дело доходит до обновления экрана - рисовать этот Bitmap в Canvas экрана. В этом случае время отрисовки стабильно и достаточно невелико.

 

P.S. Всплыл интересный факт - размер сенсорной области у меня меньше видимой области экрана. Если разместить какой-то мелкий элемент на краю экрана, то для нажатия он будет недоступен. Практический пример: ползунок (SeekBar) шириной во весь экран невозможно довести до крайних положений.

пятница, 2 декабря 2011 г.

Ограничения OpenGLES 1.0

Оказывается мой девайс не поддерживает OpenGL ES 2.0. Что более неприятно - эмулятор тоже его не поддерживет, так что возможности отлаживать приложения под 'эту версию я лишен начисто. Придется осваивать 1.0. Сразу понятно, что некоторые вещи ориентированные на удобство программиста отсутствуют в принципе, но есть и другие нюансы. Этот пост будет постоянно пополнятся по мере вникания в тему

glPointSize не работает. Размер точки задаваемый этой функцией игнорируется напрочь. Точка выводится как точка, вне зависимости от заданного размера.

Поправка: не работает на моем устройстве, а на эмуляторе  - отрабатывает как положено. Милый сюрприз...

 

 

Бардак на корабле:

есть glClearColor(float,float,float,float) и glClearColorx(int,int,int,int);

и есть glClearDepthf(float) и glClearDepthх(int);

прозевали? 

четверг, 1 декабря 2011 г.

Multi-touch которого нет

Как ведет себя Android без поддержки MultiTouch технологии, когда вы касаетесь экрана в нескольких местах сразу? Прямо скажем - неважно. Может проскочить событие с координатами  одной точки, может проскочить с координатами другой - поскольку ничего одновременного в этом мире не бывает. А в основном идет нечто среднее  - результирующая точка где-то посередине между двумя настоящими.

 

Интерфейс программы должен это учитывать и не провоцировать пользователя хвататься за экран всеми лапами сразу. А если интерфейс как бы намекает на возможность multi-touch, то должна быть фильрация ошибочных нажатий. Подозреваю что немалая часть нареканий к управлению играми связана с тем что разработчики об этом не подумали. Пользователь в азарте вцепляется в девайс и зажимает какие-то области на экране -  программа получает левые координаты нажатий и либо ведет себя некорректно, либо просто игнорирует. Отсюда вывод второй - хорошим тоном является показать пользователю куда он нажал. В том числе если он нажал "в никуда".

 

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

понедельник, 28 ноября 2011 г.

Блокировка при записи в ObjectOutputStream

Получил сегодня такой интересный эффект. Разбирательство показало, что добавился класс, которому не задан интерфейс Serializable. При этом сам он в явном виде в поток не пишется, а может оказаться в значении поля внутри другого класса, который имеет Serializable и пишется в поток.

 

Но самая главная пакость заключается в другом. На тестовых примерах такая ошибка вызывает java.io.NotSerializableException, но в данном конкретном случае этого почему-то не происходит. Оладка показала, что ObjectOutputStream обнаруживает проблемы, но вместо того чтобы тихо мирно вывалить эксепшн, пытается записать его в выходной поток (writeFatalException)! Немножко поразмыслив понял что это правильно - на другой стороне в процессе приема объекта вместо очередного поля обнаружится эксепшен и вывалится с ним, а иначе мы рискуем получить блокировку уже там (поскольку объект целиком никогда не прийдет.) В моем случа это не важно, но универсальность должна быть универсальной, хуже от этого быть не должно... Однако стало и по прежнему неясно, где же приключился затык.

 

Смотрим дальше... затык происходит при вызове slotDesc.invokeWriteObject(obj, this) ,  где obj это экземпляр NotSerializableException. Теоретически должен вызваться метод writeObject этого класса.

NotSerializableException и его родитель, ObjectStreamException, IOException, Exception  нужного метода не содержат, обнаруживается он только в Throwable и тут мы снова возвращаемся к ObjectOutputStream, метод defaultWriteObject(). Все что я могу тут сказать - мы туда приходим и впадаем в ступор где-то на выводе состояния стека. Что там такого криминального обнаружилось я право не знаю. Отладка становится трудоемкой и слабоосмысленной без каких-нибудь специальных инструментов, которыми я не владею. 

воскресенье, 27 ноября 2011 г.

Кто тут еще мне расскажет про супердостоинства Open Source?

Скачал, поставил и запустил LAMPP. Точно также как я это делал десятки раз за последние лет 5. И все всегда работало и не требовало никаких мысленных усилий (поправил конфиги и вперед). А тут на тебе - MySQL не запустился. Хорошо он мне сейчас не нужен и разбираться кто чего и где поломал мне не интересно.  Просто это типичная ситуация в этой среде. Все работало, потом бац! - перестало. Иногда это бац происходит при очередном обновлении и как откатится назад для простого пользователя не очевидно.

 

Еще до кучи:

у видеокарты ATI Radeon X1600PRO - проблемы с драйверами. Регулярно падает blender, странно себя ведет jogl

у встроенной видеокарты - тоже проблемы с драйверами. Другие. Иногда ее клинит и на экране появляются разные странные артефакты при выделении.

И чаво? Еще одну видюху покупать в надежде что с ней все будет ОК?

суббота, 26 ноября 2011 г.

Berkeley DB Java Edition, ч.3

Импортировал в БД 7 записей, исходный файл занимал 2Кб, при том что большая часть это вспомогательный мусор, который в базу никоим образом попасть не может. Реальный объем данных - байт 100, не больше.

 

База после импорта заняла 100Кб. Проверил программу - ничего лишнего я туда не толкаю. Буду надеяться, что это просто резервируется место и следующие 7777 записей займут столько же сколько заняли 7. Иначе придется плотно разбираться что это за ерунда такая .

Berkeley DB Java Edition, ч.2

Для любого объекта, который хранится в BDB опосредованно (как часть другого объекта) его класс должен быть объявлен как @Persistent и иметь конструктор по умолчанию. Это не требуется только базовым типам вроде String, Long, Integer а также коллекциям (по крайней мере для ArrayList, до прочих у меня руки не дорасли).

 

И как я уже писал, если мы хотим хранить в БД потомков некоторогоо класса, то они все должны быть помечены как @Persistent (@Entity для них мы использовать уже не можем). И если мы хотим иметь к ним доступ не только через базовый класс, но и индивидуально, то они должны иметь поле помеченное как @SecondaryKey, причем имя поля должно быть уникальным среди всех потомков базового класса. Пример:

 

@Entity
class Base {
@PrimaryKey
String id;
Data data;
public Base() {}
}

@Persistent
class Data {
String somedata;
public Data() {}
}

@Persistent
class Child1 extends Base{
@SecondaryKey(relate=Relationship.ONE_TO_ONE)
String id1;
public Child1() {}
}

@Persistent
class Child2 extends Base{
@SecondaryKey(relate=Relationship.ONE_TO_ONE)
String id2;

public Child2() {}
}

Если вы фактически не используете SecondaryKey. то использовать

Relationship.ONE_TO_ONE

не обязательно, можно и

Relationship.MANY_TO_ONE

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

среда, 23 ноября 2011 г.

Berkeley DB Java Edition, ч.1

Осознал что пора задуматься о хранении данных и нахожусь в творческом поиске БД. Хочется:

  • нечто заточенное под хранение произвольных объектов с заранее неизвестной структурой. Т.е. в идеале - объектно-ориентированная БД, или просто key-value хранилище
  • нечто компактное, но потенциально расширяемое. Поднимать на недорогом (на дорогой нет денег) хостинге серьезную БД задача не для слабонервных. Это в любом случае не просто "с нуля", в том числен и с нуля знаний. MySQL в минимальном варианте я конечно подниму, но как раз MySQL мне не очень подойдет по п.1. Да и он достаточно прожорлив в плане ресурсов

По всей видимости мне нужна компактная "embedded" БД и после некоторых поисков я остановился на Berkeley DB. Она не совсем проходит по первому пункту, БД не объектно-ориентированная. Но собственно мне не так много этого объектно-ориентированного функционала и нужно. Т.е. не помешает, но ставить ради этого отдельный сервер БД я не готов.

Посмотрел еще всякие разные объектные БД, в том числе и встраиваемые в приложения, но либо откровенное не то, либо нехорошие отзывы, либо лицензия не подходящая. Попробую познакомиться поближе с BDB JE и обойти ее ограничения, которые начали всплывать практически сразу.

java.lang.IllegalArgumentException: An entity class may not be derived from another entity class

У меня сложная иерархия объектов, но хранить их нужно единообразно. Т.е. получив некоторый ключ, я не знаю заранее, объекту какого типа он соответствует. Для примера:

class Ship {
 ArrayList<Equipment> equipment;
}

Equipment это может быть и Gun и PowerGenerator и бог знает что еще. Если у меня есть необходимость хранить их как отдельные Entity (а такая необходимость в общем случае у меня есть), а не просто как часть Ship, то возникают некоторые сложности. А именно - Gun, PowerUnit и GoodOnlyKnowsWhatElse - это отдельные Entity и записать их вместе и просто получить потом по значению ключа одним запросом нельзя. Т.е:

PrimaryIndex<Key,Equipment> gunindex = store.getPrimaryIndex(Key.class,Equipment.class)

Gun gun=new Gun();
PowerUnit pu=new PowerUnit();
index.put(gun); // вылетит Exception

если же я сделаю

PrimaryIndex<Key,Gun> gunindex = store.getPrimaryIndex(Key.class,Gun.class)

PrimaryIndex<Key,PowerUnit> puindex = store.getPrimaryIndex(Key.class,PowerUnit.class)

то и записывать и получать я их потом буду отдельно:
gunindex.put(gun);
puindex.put(pu);
gun=gunindex.get(key);
pu=puindex.get(key);
а как я уже сказал - я не знаю заранее к чему относится key, т.е. придется делать обращение ко всем индексам чтобы выяснить к какому относится данный key. Либо закладывать в key информацию о типе объекта и выбирать нужный index.
Есть другой путь
Даже 2. Первый - использовать Base API вместо Direct Persistent Layer. Не вдаваясь в подробности - это возможность закопаться глубоко в недра БД и делать с ней все что захочется. Никаких надстроек, чистая работа с key-value и не важно что именно в них находится. По сути это ядро БД, на которое можно накрутить свою надстройку которая будет делать то что нужно и так как нужно именно вам. Но возни будет много.
Второй путь проще. Делаем обертку:
@Entity
class Item {
  @PrimaryKey
  Key key;
  Equipment data;
  public Item() {}
  public Item(Equipment dt) {
     data=dt;
     key=dt.getKey();
     }
}
а классы Gun, PowerUnit и GoodOnlyKnowsWhatElse помечаем как @Persistent и реализуем в них метод getKey. В результате мы имеем избыточность в данных, но зато все они вставляются и извлекаются единообразно как Item, чтобы у него там внутри не было. Можно добавить еще вторичный ключ который задает тип хранимого объекта. - избыточность возрастет еще, но можно будет выбирать объекты по их типу
Update: Все это бред
Ларчик открылся просто.
Есди у меня есть иерархия объектов, но я хочу хранить их вместе, то я должен пометить базовый класс как @Entity, а его потомки  - как @Persistent.
Тогда не нужны никакие левые обертки.

Некоторые замечания

Долго колебался, делать ли значения типа (корабля, оборудования, сообщения) цифровыми или строкой. В конце концов во многих случаях склонился к строке. Пусть это медленнее, и отнимает больше памяти но зато проще делать экспорт/импорт данных в/из человекочитаемый формат. Да и при отладке проще догадаться что означает "user.login" в поле msgtype, чем 42.

 

Жалко switch (строка) появился только в Java 1.7

вторник, 22 ноября 2011 г.

IOException: Read end dead

Сегодня затеял небольшой рефакторинг и очень удивился получив вот такой вот эксепшн и еще ряд интересных глюков. Выяснил интересную вещь:

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

Так вот, родительский поток 1 среди прочего инициализирует парочку PipedInput/OutputStream, а использоваться эта связка будет исключительно в потоке 2. Тут и ожидает сюрприз. Эти Piped как-то привязаны к потоку в котором были созданы, и если он завершается, то при попытке их использовать получаем тот самый эксепшн. Открытие неприятное ввиду того что

а) совершенно не очевидно

б) непонятно, касается ли это чего-то еще? Какие еще ресурсы не являются в этом отношении thread-safe?

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

д) непонятно, на что еще может повлиять такая зависимость? А не окажется ли, что используя некоторые объекты (те же PipedInput/OutputStream) я неявным образом могу вызывать блокировку потока 2, из-за необходимости  синхронизации его с потоком 1 (который в это время занят чем-нибудь)?

 

суббота, 19 ноября 2011 г.

Blender, цирк с конями часть вторая

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

Попробовал пересобрать блендер самостоятельно из исходников, результатов не воспоследовало. Я почти решился откатиться на 2.4х, но тут выяснилось что эта версия страшно тормозит.

Остался еще один радикальный вариант. Сбросив все данные на резервный диск и морально приготовившись ставить систему заново, я выключил комп и изъял из него видеокарту ATI Rageon X1600PRO. Осталась интегрированная в процессор (да, именно в процессор, даже не в мать, вот до чего дошла буржуйская техника) и отключенная до сих пор. Название проца и что именно там в него встроено я сейчас уже не вспомню, однако замечу - Ubuntu в свое время категорически отказалась с этой встроенной видеокартой работать, из-за чего и пришлось вскрыть покоившийся с миром старый комп и извлечь из трупа атишку.

Надо отметить, что в отличие от винды, которая обычно переносит подобные манипуляции достаточно безболезненно, линуксы крайне не любят когда им меняют видеокарту. Так что я в лучшем случае готовился запустить консоль и применить волшебное заклинание "Xorg -configure", в более вероятном - поставить систему заново, ну и в наихудшем вернуть все как было, еcли федора разделит нелюбовь убунты к встроенным видюхам.

 

Поэтому сильно удивился, когда Федора молча проигнорировала замену. Причем падения блендера, тьфу-тьфу-тьфу, прекратились. И версия 2.49 не тормозит, так что запасной аэродром у меня имеется.

З.Ы. Надо будет проверить, раньше у меня были некоторые странности в JOGL, может тоже видюха виновата? 

пятница, 18 ноября 2011 г.

Java и NPAPI

Низзя.

 

Вчера уже спать ложился, когда задумался - а нельзя ли сделать игру в виде плагина к браузеру? Сегодня с утра выяснил - в принципе можно, но не на java. Увы. Причина - интерфейс плагинов у популярных браузеров ориентирован на нативные плагины, написанные на языке Си. Либо кроссплатформенные плагины, написанные на JavaScript. Поддержки Java нет и не планируется, более того, отчетливо просматривается тенденция по выжиманию java из браузеров. Java - продукт проприетарный, и потому нелюбимый разработчиками открытого софта. Как по религиозным соображениям, так и в связи с лицензионными ограничениями. Ну а гугл со своим chrome видимо не желает плевать проив ветра и поддерживать не то чтобы конкурента... но с какой стати им вообще поддерживать постороннюю фирму?

 

Так что - апплет, вебстарт или обычное приложение. Ну и ладно, и этого хватит.

X3D vs Collada

Попробовал экспорт модели из блендера. X3D и Collada. До сих пор в думках, что лучше. И то и другое нормально стало работать с версии 2.59 (в 2.58 Collada отсутствует, X3D экспортируется с ошибками).

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

Вот такая модель корабля при экспорте в X3D занимает 3.7Мб против 530 Кб в виде blend файла

среда, 16 ноября 2011 г.

Blender

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

Или говоря проще - логика должна быть реализована программой, но для создания картинок существует графический редактор. А для создания 3D моделей - программа моделирования. Забегая вперед - нужно еще суметь скрестить ежа с ужом, то бишь нарисованную модель как-то подцепить к своей программе, но это еще впереди, некоторые наметки в этом плане имеются.

Начнем с другого - почему, собственно, блендер? Я далек от фанатизма в отношении open source софта и блендер мне не нравится, просто другой бесплатной альтернативы с сопоставимыми возможностями в природе нет.

Несколько слов для фанатиков, готовых с пеной у рта отстаивать преимущества open source вообще и blender в частности.

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

Причем в последнем случае речь идет не о каком-то экзотическом, никому не нужном продукте. Речь идет о популярных, массово применяющихся дистрибутивах Linux'а. Достаточно сказать что на моем компе (и это уже второй такой комп) "из коробки" нормально не ставится практически ни один. Из тех что ставятся - не каждый работает. Или работает, но с глюками. Свежий пример, за последние пару дней я попробовал последние Debian, Fedora, Mandriva:

  1. Собственно началось с того что на Ubuntu блендер работал очень нестабильно - падал без малейшего повода по несколько раз в час. Поначалу я грешил на странный выбор разработчиков Ubuntы, положивших в репозиторий не релиз, а не разбери поймешь что. Однако несколько релизов, взятых непосредственно с blender.org вели себя точно также. После чего икалось уже разработчикам блендера. Но запустив его в виртуалбоксе на дебиане усомнился. Там все работало как часы. Это стало последней каплей и я решил пересесть на дебиан, как более стабильный (ха-ха).
  2. Выбрал архитектуру х86, как наиболее отработанную (в свое время ставил 64ех битную Ubunt'у и она попортила мне немало крови)
  3. Каково же было мое удивление, когда после установки у меня оказалась x86_64 , причем chrome и skype отказались воспринимать ее как 64 и пришлось ставить их 32ух разрядные варианты.
  4. Поставил еще раз, на этот раз сразу х86_64. На этот раз получилась "честная" x86_64, вот только отчего-то у нее постоянно падала сеть, что лечилось только перезапуском. После запрета IPv6 (нашел такой рецепт в инете) стало легче, но не намного. 15-20 минут, потом сеть клинит и привет. Кроме того иногда начинали сыпаться сообщения от ядра.
  5. Такими вот сессиями по 15-20 минут скачал Fedora и Mandriva, сразу варианты 32 и 64
  6. Ни один из 4ех установочных дисков не запустился, хоть контрольную сумму дистрибутивов я проверил  - все ок.
  7. Применил Unetbootin чтобы загрузиться с флэшки - аналогично.
  8. После рытья инета родилась идея, в явном виде с описанием "по шагам" мне нигде не попавшаяся, хотя пользователи с аналогичными проблемами имелись в ассортименте. По крайней мере мне попадались страдальцы для Fedora 12-13 (сейчас уже 16)
  9. Задал метку тома для флешки и поправил лежащие на ней конфиги. Чудо свершилось - Fedora стала загружаться в обоих вариантах. 
  10. Правда Mandriva так и не заработала - она грузится, разбивает диск и вылетает с экраном полным мусора. Похоже у нее проблемы с моей видеокартой. Собственно и 2-3 года назад она с ней не очень дружила, но сейчас ситуация ухудшилась, хотя карта осталась прежней. По крайней мере раньше проблемы начинались после установки, а не во время.
  11. Поставил федору x86_64. Обнаружил что скайпа под нее нет, а скайп для федроры x86 требует чтоб ему руками подключили подходящие либы. Не то чтобы это сложно (доводилось делать), но я использую много разного софта и если с каждой софтиной возиться... работать то когда? Снес нафиг.
  12. Поставил Федору x86. Пока осваиваю. Все непривычно и неудобно. 

 

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

 

В целом продукт хороший (по возможностям), но интерфейс ужасен и есть немало суровой правды в словах тех, кто задает вопрос "А что, у блендера есть интерфейс?". Тем не менее освоить его можно, можно к нему привыкнуть и пользоваться.

Update: За день возни с блендером на федоре он не упал ни разу. И звук заработал сразу. Нарисовал кривое уродливое страшилище, но результатом страшно доводен. Завтра постараюсь нарисовать первый корабль и начну трахаться с его импортом в свою программу.

Update 2: Таки падает. Но не так часто.

воскресенье, 13 ноября 2011 г.

Идея №1

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

 

Так вот, наигравшись в AngryBird, и нарисовав кучу картинок, она начала запускать все подряд, иногда спрашивая, иногда нет... но так или иначе теперь она вовсю пользуются и камерой, и диктофоном, и мультики смотрит на ютубе... вот только с последним есть некоторая закавыка. Грамоте она не обучена и в строке поиска набирает просто бессмысленную чепуху. Так что сначала приходится просить старших, чтобы ввели нечто осмысленное, а дальше она сама листает результаты. Что собственно и подводит к моей идее.

 

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

Т.е.:

  • нужен сайт для родитетелей, где они могут составить программу передач (не обязательно мультиков) для своего ребенка на основе роликов из YouTube
  • нужно клиентское приложение для андроид, которое загружает с сайта программу и позволяет выбирать передачи в произвольном порядке, или просматривать подряд, отмечать понравилось/не понравилось, добавлять в избранное и т.п.
  • естественно на сайте должна быть аналитика для родителей - что их ребенок смотрел/не смотрел, что понравилось/не понравилось и т.п.
  • естественно также родители должны иметь возможность обмениваться опытом, своими удачными находками и т.п. Кто-то захочет вести свой детский канал, а другому нужно что-то готовое, чтобы включить и все.

суббота, 12 ноября 2011 г.

Софт для Android: Графические редакторы

Мне понадобился графический редактор для андроида, но с наскока ничего подходящего не нашлось. Решил упорядочить поиски и зафиксировать результаты, чтобы не ходить по тем же программам по второму кругу. По хорошему бы нужно сделать в андроидмаркете возможность помечать уже просмотренные результаты, но что ж делать...

ClearDraw Простой и непритязательный графический редактор.

  • Кисть и пульверизатор с выбором размера точки, плюс несколько примитивов (прямоугольники круги и т.п.)
  • ограниченный набор цветов
  • возможность отправить рисунок в дропбокс, по почте и т.д.
  • отсутствует ластик! 
  • отсутствует нормальный выход - нажати кнопки назад приводит к отмене последнего действия и только.

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

EasyPaint вообще ниочем

  • 9 цветов
  • единственная толщина и вид кисти.
  • никаких настроек
  • стереть можно только все сразу

PaintCommander

  • выбор произвольного цвета
  • набор кистей, но нетрадицонный. Кому-то может понравится, но я бы предпочел более привычные
  • нет отправки на почту/dropbox, только сохранение
  • ластик традицонно отсутствует. Они все издеваются...

Pain HD

  • 15 цветов
  • 1 тип кисти + 4 предустановленных размера
  • возможность отправки
  • выход только через Home
  • опять отсутствует ластик. Точно издеваются

Если эти ребята рассчитывают, что я куплю их платные версии, то они сильно просчитались. Уродские поделки, часто с корявым описанием не вызывают никакого желания поддержать разработчика. А когда программа рисования хочет иметь доступ к звонкам и смс... да ну их в баню.

Paint Pro первая программа пригодная для использования.

  • выбор цвета ограниченный, но достаточно широкий. Реализован в виде круговой палитры - т.е. некоторые оттенки отсутствуют, но все же  это не 7-15 цветов
  • размер кисти задается. Но тип только один
  • ластик - есть
  • отправка на почту/dropbox есть
  • нормальный выход

Из минусов - реклама. И меню - не сразу догадаешься что оно прокручивается.

IPaintEasyFree это вообще раскраска для детей

Буду искать дальше.

пятница, 11 ноября 2011 г.

Выбор объектов в OpenGL

Выбор объекта мышкой кажется в режиме 2D делом достаточно тривиальным. Вот есть список объектов, вот координаты мышиного указателя. Пробегаем по списку, сравниваем и выбираем тот объект который ближе.

 

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

    FloatBuffer fb=FloatBuffer.allocate(1);

    gl.glReadPixels(x,h-y-1, 1, 1, GL2.GL_DEPTH_COMPONENT, GL.GL_FLOAT, fb);

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

 

А вот и не все... Вокруг каждого объекта есть квадратная прозрачная область (мы берем текстуру и натягиваем ее на квадрат). И как выяснилось - прозрачная она лишь для меня, но не для glReadPixels. В результате мы попадаем в объект просто ткнув рядом. Хуже того - мы тыкаем в один объект, а попадаем в другой, который этой прозрачной областью его перекрывает. Однако решение этой проблемы тривиально до безобразия, и видимо из-за этого его хрен найдешь.

 

Ставим в начало программы:

     gl.glEnable(GL2.GL_ALPHA_TEST);

    gl.glAlphaFunc(GL2.GL_GREATER, 0.01f);

В результате пиксели со значением alpha<0.01 (а у нас ровно 0) в буфер не попадают и не портят нам малину. Теперь объект можно выбрать хоть через дырку в другом объекте.

Пришлось еще переставить обработку мышиных событий - теперь мышь обрабатывается после отрисовки.

А еще надо помнить, что машина может представить десятичное значение совсем не так как мы ожидаем (например 0.003999 вместо 0.004), так что формируя индекс нужно его аккуратненько округлить и не полагаться лишний раз на автоматическое приведение типов.

Ну и не стоит забывать что в буфере глубины у нас значение 0 : 1.0, а объекты мы рисуем в диапазоне -1.0 : 1.0, а то я все не мог понять, что за левые координаты получаются

float z=fb.get(0);

z=1 - (z*2);

З.Ы. Завтра буду учить свои кораблики стрелять

Подземный стук

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

 

Тем временем переделал серверную часть. В место программного поллинга сокетов (который грузил сервер на 100% просто так, ничего не делая) использовал SocketChannel. Серверу сразу полегчало. Вполне возможно что и мышиные проблемы росли оттудова же. Поскольку сервер и клиент на одной машине.

 

Отдельного доброго слова заслуживают разработчики ObjectInputStream. Конструктор, блокирующий исполнение - это что-то новое в моей небогатой практике. Они не могли потерпеть и выполнить первое чтение из InputStream при первом обращении к функции чтения? Пока писал, понял почему не захотели. Функций чтения у ObjectInputStream много, не хотелось им в каждую вставлять лишнюю строчку вида if (!checkInputHeader()) throw new BadDataException().

 

А вот авторам этих двух туториалов настоящее спасибо:

Действительно помогли разобраться

 

Вообще с этими объектными потоками проблем было больше чем с каналами. Очевидно им не хватает простой, очевидной вещи - буфферизации и функции isObjectReady(). Т.е. при вызове этой функции из потока должен считываться очередной объект, и если он полностью считан, то возвращается true. Если нет - естественно false. А готовый объект, или некие промежуточные недочитанные данные ложатся  в буфер для дальнейшей обработки.

 

Короче - нужен неблокирующий аналог ObjectInputStream.readObject, иначе весь поллинг идет к чертям собачьим. Сейчас я выкрутился через ж, но по хорошему нужно переписать ObjectInputStream по своему