понедельник, 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 по своему

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

Проверка на (о)кошках

Сложно найти черную кошку в темной комнате, 

особенно если ее там нет

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

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

 

Запускаем virtualbox, уже настроенный и готовый к применению и получаем фейсом об тейбл. А поставьте, говорит, virtualbox-dkms и запустите из под рута modprobe vboxdrv. Все бы ничего, только вот пакет этот уже стоит, а modprobe говорит: FATAL: Module vboxdrv not found. Что в переводе  на понятный язык звучит как идите нах....

 

Ну да не в первый раз замужем, известно где тут собака закопана. Ubunta обновила ядро, а новые заголовки не подтянула. Идем в synaptic и грузим все что надо, после чего наступает счастье. Винда запущена. Но радоваться рано.

Приложение выложено у нас на рабочей машине, там запущен LAMPP, который отлично работает "из коробки" при минимальной правке конфигов. Поставить его много проще чем поднять и настроить все что нужно для тестового вебсервера по отдельности. Мы даже присвоили адресу 127.0.0.2 имя game.my, вот только как теперь достучаться до этого адреса из под Virualbox'a?

Не мудрствуя лукаво спрашиваем у ifconfig наш IP адрес и быстренько меняем все так чтобы обращаться к нему. Ура!, наш немудреный сайтик с кнопочкой "запустить приложение" доступен и там и сям.

 

Результат:

в Ubuntu (Java 1.6, Chrome, FireFox) не ставится иконка на рабочий стол. При этом ругается нехорошими словами. Опера jnlp загружает, но джаву не запускает.

В WinXP (а другой у меня нету, Java 1.7, IE 6, Chrome) иконка ставится, в меню добавляется, но с кракозябрами вместо нормальных буков. Видимо не понимает кодировку UTF-8

В Ubuntu программа запускается и работает,

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

Update: размечтался... это просто кусок нижележащего окна выглядывает.

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

Обе твари напрочь игнорируют атрибут "os" у тега resources - пришлось делать два отдельных jnlp. Я знал! Т.е. os="windows" и os="linux" не работают. А если там нужно более конкретно указывать ось, то нафига тогда козе баян?

Update: они работают! Только надо писать с Большой Буквы. Linux... Windows... это вам не хухры мухры. Это звучит гордо!

Задавать codebase - обязательно полный адрес сайта, потому как все поголовно норовят загрузить jnlp как файл, а потом только скормить его джаве. Естественно после этого она пытается искать наши либы в папке загрузки - с понятно каким результатом.

Возможно сервер отдает неправильный mime, хотя я его прописал... Чертов firebug не показывает подобные сетевые запросы...

Такое чувство что яву ненавязчиво выдавливают из браузеров, а там глядишь, обойдутся как обкусанные с флэшем - не поддерживаем и все тут!

На том пока эксперименты с различными платформами я завершаю - ставить невиртуальную винду пока нет желания

Проблемы новичков

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

 

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

чтобы сделать из приложения апплет, или сделать возможным Web Start - ресурсы должны хранится в jar. Т.е. можно конечно обойтись и без этого, даже в таких случаях, но некошерными методами. Главное засунуть их туда проблемы нет вообще - jar, это тот же zip, и положить что-то внутрь можно даже силами файлового менеджера, многие из них давно умеют обращаться с такими файлами как собычными папками. Но как потом из программы обратится к положенному?

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

предполагается что вы пользуетесь Eclipse, в прочих IDE не должно сильно отличаться. 

  • создаем новый package: File->New->Package и даем ему какое-то разумное имя. Например resources. Или images
  • ложим туда все наши картинки. Если вы уже сделали простую папку и натолкали их туда - перетаскиваем мышой, если они лежат где-то отдельно, не подключенные к проекту - можно сделать импорт.
  • InputStream is=getClass().getResourceAsStream("/images/example.png") - получаем InputStream для файла example.png лежащего в пакете images
  • Вуаля! 

Если вы привыкли работать с File - отвыкайте. В общем-то он нужен вам чтобы получить все тот же InputStream. Если какие-то методы требуют File, то поищите - и обнаружите что они имеют вариант с InputStream.

Если вы сделали пакет resources.images, то обращение к файлу будет выглядеть так: 

InputStream is=getClass().getResourceAsStream("/resources/images/example.png");

Все точки в названии пакета заменяются на / и не забывайте про ведущий слэш.

Если обнаружите что можно сделать так

InputStream is=getClass().getResource("/resources/images/example.png").getFile();

то вдумчиво читайте описание всех упомянутых методов, ничего хорошего у вас так не выйдет :). 

Когда придет время явить свое творение миру, вы сформируете единый jar со всем что нужно для счастья при помощи maven, или ручками. Если второй вариант, то не забывайте: во время отладки эклипса сама формирует переменные типа classpath и прочую фигню так чтобы ваша программа видела свои ресурсы - но в bin, как откомпилированные классы, их не копирует. Так что когда будете делать jar, кроме классов не забывайте затолкать лежащие отдельно ресурсы - не нарушая структуры packages.

Т.е. если у вас класс yourpackage.YourApp и ресурс resources.images.example.png, то структура jar-файла:

  • yourpackage
    • YourApp.java
  • resources
    • images
      • example.png

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

Если вы не собираетесь делать апплет или webstart - просто положите в отдельную папку и в скрипте запуска вашего приложения укажите путь:

 

#!/bin/sh

java -Djava.library.path=lib -jar myapp-1.0.0-jar-with-dependencies.jar

 

в папке где у меня лежит приложение в виде одного jar со всеми прибамбасами и ресурсами myapp-1.0.0-jar-with-dependencies.jar есть еще папочка lib, где валяются нативные либы. Можно сделать несколько папочек, но тогда придется указывать их все через ":".

Пример файла JNLP на русском языке

Толи оно никому не надо, толи все тривиально и только я непонятливый, однако ж найти хорошее описание JNLP и как с ним работать на родном языке сложно. Значит придется делать самому.


Стандартный заголовок XML файла

<?xml version="1.0" encoding="utf-8"?>


дальше идет тэг <jnlp>, все остальное находится внутри него. Вот его структура, дальше рассмотрим все элементы подробнее

<jnlp>

  <information>...</information>

  <security>...</security>

  <update>...</update>

  <resources>...</resources>


  <application-desc>...</application-desc> или <applet-desc>...</applet-desc>

  или <component-desc>...</component-desc> или <installer-desc>...</installer-desc>

</jnlp>

 

Элемент jnlp может иметь следующие аттрибуты:

  • spec="6.0+" (1.0+,1.5.0 и т.п.) версия спецификации JNLP. + означает - и выше. Самое правильное решение поставить 6.0 или 6.0+ (или уточнить какая версия наиболее распространена на данный момент).
  • codebase="" базовый URL, от которого будут плясать все относительные ссылки в этом файле.
  • href="" URL этой самой страницы. Следует указать если вы задаете codebase для  общей части относительных ссылок на jar-файлы, а jnlp лежит в другом месте. 
  • version номер версии вашей программы. Если обновился - пользователю автоматически загрузится новая версия. Иначе хоть за обновляйтесь - он об этом не узнает.

Простой вариант - jnlp-файл лежит там куда указывает codebase, все jar'ы - там же, если их мало, или ниже, раскиданные по каталогам если их много. Тогда href можно не задавать вообще или указать "". Если у вас много jnlp, они раскиданы по разным местам и ссылаются на кучу библиотек, которые лежат в одном месте - тогда придется явно указывать и codebase и href.

<information>

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

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


<information>

 ... а здесь для всех ОС

</information>

<information os="linux">

 ... указываем что-то специфически линуксовое

</information>


атрибуты:

os, arch, platform, locale - думаю разъяснений не надо. Но если вы их используете, то точные значения придется поискать и у меня есть сомнения что оно будет работать одинаково. Например, все ли линуксы будут сообщать о себе как os="linux", или придется писать отдельные блоки information для centoos, redhat, ubuntu и т.п.?

Рассмотрим information подробнее:

 

<information>

<title>Название вашей программы</title> 

 

<vendor>Название фирмы или имя разработчика</vendor>

<homepage href=""/> указание на домашнюю страницу

<description kind=""> Описание. kind может принимать значения "one-line", "short", и "tooltip". Описание должно содержать только чистый текст. Никаких html тэгов.</description>

<icon/> может иметь атрибуты width,height - ну это понятно. kind="splash" - если вы хотите окно заставку, вроде того что выводит эклипсе при запуске. Естественно должен быть указан href. Во время загрузки будет показываться иконка 64х64, на рабочем столе 32х32, другие размеры автоматически смасштабируются. Произвольных размеров видимо может быть только заставка.

<offline-allowed/> указываем этот тэг если хотим чтобы приложение работало в оффлайне. Кроме того он влияет на то, как приложение проверяет обновления. Если не задан, то сначала проверяется наличие обновлений, если надо обновляется и только потом запускается - это гарантирует что всегда запущена свежая версия. Если задан, то приложение запускается, и только потом неспешно проверяет, нет ли более свежих версий. В результате запуск быстрее, но не факт, что запущена последняя версия. Необходимо отметить, что если приложение уже запустилось, а вы на сервере поменяли версию, то у вас таки может возникнуть ситуация, что работает несоответствующая версия. Если это важно - передавайте номер версии при общении клиента с сервером и контролируйте соответствие.

<shortcut> указывает, надо ли что-то делать с рабочим столом. Атрибут online - смысл не ясен.

<desktop/> нужна ли иконка на рабочем столе

<menu/> нужно ли добавлять себя в меню. Атрибут submenu="" указывает в какое именно.

</shortcut>
<association/> указывает что ваша программа будет запускаться ОС по умолчанию для указанных типов данных из инета и расширений файлов. Атрибуты

 

  • extensions="mydata" Т.е. при запуске файла "чтоугодно.mydata" будет запускаться ваша прога
  • mime-type="application-x/mydata-file"  . Если сервер передаст такой mime-type, то опять же запустится ваша прога.

 

 

 

<related-content href="" title=""> может содержать ссылки на дополнительную информацию (readme-файл, страницу помощи, страницу регистрации и т.п). Соответственно href задает ссылку, title - название. Может содержать элементы icon и description. Но я ума не приложу - где пользователь все это увидит.

 

</information>

 

C information покончили. Следующий элемент security.

<security>

<all-permissions/>

</security>

Если вы его не задаете - приложение работает в песочнице. Если задаете - выше указано как. Может быть есть еще варианты, но смысла в них ковыряться нет. Либо все, либо ничего. Если всё, то все JARы должны быть подписаны, причем одним ключом. Т.е. если вы пользуете стороние библиотеки, то подписываете и их тоже. Так кажется, но есть нюансы. Ранее все jar'ы можно было тянуть только из одного места, теперь вы вроде как можете указывать и публичные репозитории для сторонних библиотек. Но подписать вы их естественно в таком случае не сумеете.


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

<update check="" policy=""/> 

check указывает когда и как проверять обновления. может принимать значения 

  • background приложение запускается, проверка стартует параллельно
  • timeout (по умолчанию) проверка выполняется перед тем как запустить приложение. Если соединения с инетом нет, или сервер загружен, упал и не отвечает то о истечении некоторого таймаута приложение запустится.
  • always  проверка всегда выполняется перед тем как запустить приложение. Если соединения нет - ваша программа будет жутко пользователя раздражать. На мой взгляд лучше ее запустить и сообщить об отсутствии связи (если она вам нужна). Так что мой выбор - background

policy указывает что делать с обновлением

  • always (по умолчанию) - загружать без всяких вопросов
  • promt-update - спросить пользователя, хочет ли он обновить или запустить установленную версию
  • prompt-run - спросить пользователя, хочет ли он обновить и запустить, или отказаться от запуска программы.


Что выйдет если задать background+prompt-run - не ясно. Т.е. программа уже запустилась, и тут выясняется что запускать ее было нельзя, м-да... По идее такая комбинация использоваться не должна.


Следующий, очень важный и полезный элемент resources.

<resources os="" arch="" locale="">

<jar href="myjar.jar"/> задает ссылку на библиотеку, которая требуется приложению.

<nativelib href="lib/windows/corelib.jar"/> задает ссылку на нативные библиотеки. Все ваши *.dll, lib*.so и т.п. файлы должны быть запакованы в jar (видимо потому что jar можно, и нужно, подписать). и должны быть в корне этого jar, без всяких вложенных папок.

<j2se version="1.6+"/>указывает какая версия Java нам нужна. Я задаю больше или равно той, на которой разрабатываю. С одной стороны - не надо заставлять пользователя загружать устаревшую версию, если у него более новая. С другой стороны меньше шансов, что программа использует какую-то новую фичу, в то время как у пользователя стоит старье.

</resources>


Атрибуты os, arch, locale позволяют например подгрузить разные нативные библиотеки в соответствии с ОС пользователя и языковые данные.

jar и nativelib могут иметь атрибут download. Значения 

  • lazy - приложение может запускаться не дожидаясь их загрузки
  • eager - должны загружаться перед загрузкой приложения
  • progress - тоже самое что eager + может использоваться для указания прогресса загрузки. Чтобы это только значило?

дополнительные атрибуты для j2se:

  • href="http://java.sun.com/products/autodl/j2se" - указывает где можно взять джаву
  • initial-heap-size="64m"
  • max-heap-size="128m"
  • java-vm-args="" доп. ключики с которыми нужно запускать JVM для ввашего приложения

если вы не знаете зачем все это, то не задавайте.

элемент j2se может содержать еще элементы property

<property name="key" value="value"/> 

если ваша программа использует  System.getProperty и System.setProperties то вы знаете что это и зачем, если нет - не заморачивайтесь. Почитать о них (на англицком) можно тут:  Properties That Affect the Behavior of Rich Internet Applications

Элемент application-desc

<application-desc main-class="Main">

<argument>arg1</argument>

<argument>arg2</argument>

</application-desc>

атрибут main-class должен задавать полное имя класса(включая имя пакета), который содержит метод main. 

Если у нас апплет, то вместо application-desc задается applet-desc

<applet-desc  documentBase="http://..."

      name="имя вашей программы" main-class="класс апплета"

      width="527" height="428">

<param name="key1" value="value1"/>

<param name="key2" value="value2"/>

</applet-desc>


Еще у нас может быть компонент и инсталлятор, <component-desc> и <installer-desc> соответственно. Их описывать не стану, когда вам понадобится их использовать, JNLP файлы уже не будут вызывать у вас священный ужас и в этом вы сможете разобраться самостоятельно.

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

Создание исполнимых файлов для разных платформ ч.1

 

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

 

Первый подход к снаряду окончился не слишком удачно. Выяснилось - просто затолкать все необходимое в "исполнимый" jar - не достаточно. Мешают яйца нативные библиотеки. Их затолкать туда тоже можно, а вот извлечь - только ручками, сами не извлекутся. Т.е. начинающему разработчику, который написал простую программку придется писать свой загрузчик библиотек, который выцарапает из jar'а (или тырнета) нужные либы (в соответствии с ОС и железом), где-то их разместит и потом загрузит. При том что не факт, что это вообще возможно - это первое что приходит в голову, задача не тривиальная и возможны различные грабли.

 

Разумеется кто-то и как-то эти проблемы решал и существуют готовые инструменты (со своими граблями и глюками конечно). На заметку:

Начнем с последнего, но не факт, что на нем остановимся - просто первым под руку попался.

 

Итак... предполагается что Eclipse у нас уже стоит. Тем не менее, если мы хотим получить приложение для нескольких платформ сразу, нужно немного пошевелиться.

  • идем страницу загрузки Eclipse http://www.eclipse.org/downloads/ и ищем Eclipse Classic.
  • там рядышком есть ссылка Other downloads, идем туда и выбираем свою версию эклипсы - у меня 3.х - значит идем еще дальше и выбираем там
  • находим среди большой кучи всякой всячины ссылочку на загрузку DeltaPack и загружаем.
  • Этот самый дельта-пак распакуем и положим аккуратненько в сторону - он нам понадобится.
  • Запускаем Eclipse (дельта-пак пока не трогаем) и создаем новый workspace.
  • Копируем туда наш проект, а также (в данном случае) JOGL под разные платформы И импортируем.
  • Идем в Window->Preferences->Plug-in Development->Target platform. Выбираем Running Platform, жмем Edit.
  • Жмем Add, Выбираем Directory и жмем Next.
  • Выбираем папку в которой у нас лежит Delta Pack, находится масса плагинов. Finish. Хм... не так уж и много. Еще раз Finish и OK.

 

То мы все развлекались, подготавливались, теперь займемся делом. Выбираем наш проект, New->Product configuration... Вот тут автор исходного мануала сжульничал. У него эта конфигурация уже была и на ее основе он сделал новую. У нас ее нет и как делать не совсем понятно. Смотрим его проект... мммать его так и об косяк, тудыть его и перетудыть... вовремя спохватились.

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

Ладно, пройдемся по его проекту до конца... и получим на выходе программу, которая практически ничего не делает, но занимает 19Мб в архиве! При этом она работает в специфическом окружении и большой вопрос какие ограничения это окружение накладывает. И сразу встает ребром вопрос переносимости этого чуда на андроид.

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

З.Ы. И так происходит сплошь и рядом. Берешь некий продукт, кувыркаешься с ним (хорошо если пару часов, а иной раз - пару месяцев)... и со вздохом сожаления откладываешь в сторону. Не то. Категорически не подходит.

 

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

Мышиная возня

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

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

Так что вместо MOUSECLICKED - MOUSEPRESSED, а если же нам все таки нужен двойной щелчок, то реализуем его логику сами - ловим MOUSE_PRESSED, отслеживаем координаты и время между кликами, а дальше - если то что одинарный клик уже прошел для нас несущественно, то все легко и просто, а если существенно... Нужна некоторая задержка в обработке кликов. Проще всего сделать подсчет времени при рендеринге (все равно обработка мыши у нас там) и брать в дальнейшую обработку клики которые отлежались заданное время, в течение которого они могут превратиться в двойные клики... Уфф...

 

Обработка мышиных событий при рендеринге

 Как меня угораздило? А жизнь заставила. Собственно сейчас (при работе в 2D) в этом большой необходимости нет, но этот кусок унаследован от предыдущего (3D) варианта... А там чтобы выяснить куда мы мышью попали, нужно было произвести некий танец с бубном вокруг gluUnProject. И танец этот нужно проводить там, где мы имеем доступ к текущим матрицам - уже полностью подготовленнным. Что возможно только при рендеринге.

Более того, если мы по событиям мыши передвигаем камеру и т.д. и т.п., то матрицы меняются и картинку желательно тут же перерисовать. Так что алгоритм выглядит примерно так

  1. Формируем матрицы
  2. Проверяем очередь событий мыши (а в стандартном обработчике мы их туда заталкиваем)
  3. Обрабатываем мышь, если есть что обрабатывать
  4. Если надо заново формируем матрицы
  5. теперь только рисуем

В 2D координаты отслеживаются тривиально, можно обойтись без этого, ну а если вдруг потом приспичит перейти на 3D?

 

P.S. Как же иногда задалбывают слишком "умные" программы. Ну зачем, скажите, ScribeFire упорно заменяет символ _ на выделение курсивом? Причем не везде, а выборочно? 

P.P.S. Ну где бы найти хороший блог-клиент, чтобы все было удобно, без глюков и работало под Ubuntu?

Texture vs Bitmap

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

 

И тут выяснилось, что все мои танцы с бубном вокруг glDrawPixels - идиотизм чистой воды. То же самое делается через текстуры НАМНОГО проще. Видимо потому что текстуры - мэйнстрим, а вывод пикселей - рудиментарный придаток. Итак, сравните два куска кода. Первый загружает графический файл и переводит его в подходящую для использования в glDrawPixels форму:


image = ImageIO.read(new File(fname));

imgHeight = image.getHeight(null);
imgWidth = image.getWidth(null);
WritableRaster raster =
Raster.createInterleavedRaster( DataBuffer.TYPE_BYTE,
imgWidth,
imgHeight,
4,
null );

ColorModel colorModel =
new ComponentColorModel( ColorSpace.getInstance(ColorSpace.CS_sRGB ),//CS_sRGB
new int[] {8,8,8,8},
true,
false,
ComponentColorModel.TRANSLUCENT,//,
DataBuffer.TYPE_BYTE );
bufImg = new BufferedImage (colorModel, // color model
raster,
false, // isRasterPremultiplied
null); // properties

g = bufImg.createGraphics();
AffineTransform gt = new AffineTransform();
gt.translate (0, imgHeight);
gt.scale (1, -1d);
g.transform ( gt );
g.drawImage ( image, null, null );
raster=bufImg.getRaster();//!!
DataBufferByte imgBuf = (DataBufferByte)raster.getDataBuffer();
imgRGBA=ByteBuffer.wrap(imgBuf.getData());
g.dispose();

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


gl.glRasterPos2d(x,y);
gl.glDrawPixels(imgWidth, imgHeight, GL2.GL_RGBA, GL2.GL_UNSIGNED_BYTE,imgRGBA);

А вот вариант с текстурой. Загрузка:


texture = TextureIO.newTexture(new File(fname), false);
h = texture.getHeight();
w = texture.getWidth();

Рисование, причем уже сразу с поворотом:


gl.glTranslated(x,y,0);
gl.glRotated(angle, 0, 0, 1);
gl.glEnable(GL2.GL_TEXTURE_2D);
gl.glTexEnvf(GL2.GL_TEXTURE_ENV,GL2.GL_TEXTURE_ENV_MODE,GL2.GL_REPLACE);
texture.bind(gl);

gl.glBegin(GL2.GL_QUADS);
gl.glTexCoord2d(0.0,1.0); gl.glVertex2d(-design.w/2,-design.h/2);
gl.glTexCoord2d(0.0,0.0); gl.glVertex2d(-design.w/2,design.h/2);
gl.glTexCoord2d(1.0,0.0); gl.glVertex2d(design.w/2,design.h/2);
gl.glTexCoord2d(1.0,1.0); gl.glVertex2d(design.w/2,-design.h/2);
gl.glEnd();
gl.glDisable(GL2.GL_TEXTURE_2D);

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

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

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

Оптинческий обман

Обнаружил интересный феномен. 

 

Если нарисовать медленно движущуюся точку (смещать ее совсем понемногу, со скоростью пиксель в секунду или меньше) - глаз воспринимает это как дерганье. Раз в секунду точка рывком смещается на пиксель.

 

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

 

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

P.S. Вести с полей:

  • Максимум что может выжать моя прога на полном экране 1440х900 - 45 FPS. Это при том что она практически ничего не рисует (время рендеринга <1мс).
  • сделал пресловутые метеоры

четверг, 3 ноября 2011 г.

9 байт которые едва не свели меня с ума

Подня убил на решение простейшей задачки - вывести в OpenGL фоновую картинку. Картинка выводилась, но имела странные цвета.

 

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

 

В конце концов, перепробовав все и вся я взялся за GIMP и нарисовал простую картинку с тремя базовыми цветами - черный, белый, красный, синий и зеленый, чтобы точно понять - что же происходит с этими цветами. Результат меня шокировал - черный остался черным, а вот белый - тоже стал черным! остальные превратились в непонятно что. То есть ни инверсия цвета, ни перестановка компонент (RGB или BGR) не могут привести к такому эффекту. При этом все элементы картинки остаются на своих местах, т.е. вариант что я подсовываю 32битное представление цвета вместо 24битного и т.п. тоже отпадает.

 

Тогда я попробовал сам сформировать массив пикселей и обнаружил что максимальные значения цветовых компонент - 127,127,127 - старший бит игнорируется. Преобразовав значения исходной картинки из диапазона 0-255 в 0-127 я впервый раз за день получил нормальные цвета и задумался. С одной стороны задача решилась, но как-тот убого, что-то тут не так.... И тут наконец забрезжил свет в конце тоннеля.

Найдите отличие:

gl.glDrawPixels(imgWidth, imgHeight, 
GL2.GL_RGBA, GL2.GL_UNSIGNED_BYTE,imgRGBA);

gl.glDrawPixels(imgWidth, imgHeight,
GL2.GL_RGBA, GL2.GL_BYTE,imgRGBA);

Черт меня дери...  Непонятно, кому и зачем это вообще может понадобится - представлять цвет 7ми битными компонентами, однако OpenGL любезно предоставляет такую возможность. Я ожидал что GL2.GL_RGBA задает цветовую модель, а единственное назначение следующей константы задавать размер элементов массива. Для данной модели  это либо 1 байт - каждый элемент представляет отдельный цвет , либо 4 байта (int) - каждый элемент представляет цвет целиком, включая прозрачность. То что учитывается знак - полный сюрприз.

 

Разница - 9 байт. Ровно столько занимает добавка "_UNSIGNED". Потрачено полдня, перерыта масса документации и примеров. Как выяснилось - рыл совсем не там и не то.

JOGL и FPS

Поигравшись с darkorbit и столкнувшись с тормозами на почве уменьшения FPS решил посмотреть сколько FPS дает моя заготовка.

Для начала написал небольшой тестовый примерчик с 2D, без света и всего прочего.... и содрогнулся. ~15-20 FPS и периодически (раз в секунд 10-15) все замирает на секунду.

 

Я был в шоке. Это никуда не годится, надо срочно что-то менять. Как бы я был в курсе, что мной выбран наиболее медленный способ работы с JOGL, но не знал что все настолько плохо.

 

Немножко покурил мануал и пошел по пути наименьшего сопротивления - отказался от GLJPanel в пользу другого варианта иcпользования JOGL cо Swing (совсем отказаться от Swing пока не решился. Нашел нелестные отзыва о ближайшей альтернативе - SWT). И случилось чудо - FPS разом подпрыгнул до 75, остановки прекратились.

 

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

 

Собственно саму игру можно делать с использованием хоть AWT, но тогда всякие менюшки, окошки придется сразу делать средствами JOGL, а я пока не хочу тратить на это время.

 

З.Ы. А еще настоятельно порекомендовал бы всем использовать обертку следующего вида для обращений к функциям Swing:

EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
// Устанавливаем заголовок окна
Main.mainWindow.setTitle(count+"FPS "+fpsCount1+" Render "+renderTime+"ms");
}});

Иначе такое простейшее действие выполненное в таймере - блокирует его и подсчет времени сбивается

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

Я рисую легко ли применение пальцем картины для детей

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

 

Те кто бахвалятся обилием программ под андроид - сильно лукавят. Барахла обилие, глюков навалом. А программ нехватает - простейших! В конце концов я нашел то, что меня устроит - на некоторое время. Но разочарован сильно.

 

Почему мне приспичило рисовать на телефоне? Оказывается это удобно. Недавно поставил программу для рисования напоминалок. Запустил, написал, поставил таймер - через заданное время она тебе напомнит. Или написал список покупок, пришел в магазин - посмотрел. Для полного счастья не хватает только возможности сохранить результат в файл, а лучше кинуть в dropbox - и эта программулина была б мне милее любого самого навороченного графического редактора. Что ж поделать - взять в руки палочку и возить ей по экранчику намного, НАМНОГО удобнее чем делать это мышью.

 

З.Ы.

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

 

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

Счастье в простоте и увлекательности

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

В социальных играх люди в большей степени, чем в традиционных, ищут быстрого, доступного и «легкоусвояемого» развлечения. Это одна из ключевых характеристик социальной игры, которая должна быть достаточно простой, быстро загружаться и позволять с лёгкостью ориентироваться в ней. Лишь небольшое количество людей согласится перед началом игры изучить 50-страничное руководство пользователя, поэтому о социальных играх можно думать как о веб-странице, которую нужно сделать быстрой, понятной и которую можно закрыть в любой момент, продолжив игру позже.

 

Можно написать игру для "интеллектуалов", которые будут корпеть над докуметацией, проводить сложные рассчеты и строить хитроумные многоходовые стратегии. Но если в эту игру можно играть только так - вы потеряете 99% аудитории. Но не нужно впадать и в другую крайность. Для любой игровой задачи желательно иметь как минимум 2 решения - в лоб (тупо навалиться массой) и путем шевеления мозгами. Это добавит в игру изюминку и стимулирует общение между игроками.

Социальные игры можно сравнить с ёмкостью, полной дырок, через которые утекает налитая в неё вода (игроки). Задача разработчика — затыкать эти дыры всеми возможными методами, чтобы сохранить воду в ёмкости (игре). Иными словами, не имеет значения, как много новых пользователей войдут в вашу игру, если они сразу же её покинут из-за наличия бесчисленного количества «дыр» в дизайне или геймплее. Важно не только привлечь, но и удержать.

Не совсем согласен на счет "всех возможных методов". Приведу пример - в battlespace игрокам запрещается обсуждать и обмениваться ссылками на другие игры. Даже в приватном общении - для чего личная переписка(!) просматривается модератором. Вот последнее меня откровенно взбесило и сразу добавило игре минусов. Я могу понять на запрет делать это на форуме или в общем чате, но частная переписка - это священная корова, которую резать нельзя. 

 

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

 

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