Расковыриваем библиотеки Proscenium'a. Работа с мышью при помощи метода SceneGraph.pi
Запись от crazyone размещена 20.10.2011 в 22:36
Это статья не столько о просцениуме (его привью версии), сколько о дебаге библиотек вобще. Я расскажу, как расковырять и исправить что-либо в библиотеках swc на примере еще недописанной библиотеки от адоба proscenium.swc. Это продолжение статьи "Proscenium во FlashDevelop" (http://www.flasher.ru/forum/blog.php?b=460), поэтому рассказывать, что такое просцениум и где взять сабжевую swc я не буду.
Исходники, которые будут исполльзоваться в статье: http://doctorstal.itx.com.ua/flash/r...bixCube0.1.zip - проект под FD, вам прийдется подправить в нем пути к SWC Libraries в настройках проекта (см. предыдущую статью)
Говорят - ковыряться где ни попадя - плохая привычка, но я просто не могу с собой ничего поделать. Залезть в байткод сторонней библиотеки и исправить в ней багу - что может быть приятнее? Еда, деньги, секс? Не смешите.
Начнем с задачи:
У нас есть кубик рубика (http://doctorstal.itx.com.ua/flash/rubixcube/html/), который нужно заставить реагировать на мышь. Лезем в "документацию", которая идет вместе с просцениумом и методично ищем что-нибудь, что могло бы нам помочь. Выясняем, что никаких событий мыши в библиотеке на данный момент нет.
Зато! У SceneGraph есть метод pick(x,y), а у SceneNode есть метод pickNode(rayOrigin,rayDirrection). Испытав невероятный приступ радости, находим в ProsceniumSamples/src/ файлик, в котором используется один из этих методов: TestPicking.as. Используется там SceneGraph.pick().
Смотрим, как это делается:
var x:Number = (event.stageX - width *.5) / width *2; var y:Number = -(event.stageY - height*.5) / height*2; var node:SceneNode = scene.pick( x, y ); // (x и y - это не координаты на экране, а координаты // относительно центра вьювера, причем принимают значения // в диапазоне [-1,1], где -1 - лево/низ, 0 - центр, а 1 - право/верх).
private var currNode:SceneMesh; private function stage_clickHandler(event:MouseEvent):void { if (!scene) return; var x:Number = (event.stageX - width * .5) / width * 2; var y:Number = -(event.stageY - height * .5) / height * 2; var node:SceneMesh = scene.pick( x, y ) as SceneMesh; // выбираем кубик под мышкой // меняем его цвет, чтобы видеть, какой кубик выбран var key:MeshElement; if (node){ if (currNode){ if (currNode.elements.length > 0){ key = currNode.elements[0]; currNode.materialBindings[key] = null; } } currNode = node; if (currNode.elements.length > 0){ key = currNode.elements[0]; var mtrl:MaterialStandard = new MaterialStandard("test"); mtrl.diffuseColor.setFromUInt(0xff0000); currNode.materialBindings[key] = mtrl; } } }
Ура, маленкий кубик выделяется! Но радость проходит быстро. Если мы развернем кубик синей гранью к себе - получится, что выделяется не тот кубик, на который мы нажали, а кубик с другой стороны. Это очень обидно. Это неправильно. Вопиющая несправедливость.
Замечаем, что при вызове метода pick() трейсится чтото такое:
Цитата:
node (cube11) is at 23.765269211529183
node (cube14) is at 18.93605061830806
node (cube17) is at 14.10683202508694
node (cube14) is at 18.93605061830806
node (cube17) is at 14.10683202508694
Значит, вопиющая несправедливость - это не злобное решение адоба подсунуть нам каку, а просто попытка адоба уложиться с просцениумом до Adobe MAX. Ну, не успели ребята. Прийдется отдебажить все за них.
Итак. Нам нужна идея - в чем баг и как его исправить. Исходного кода у нас пока нет, мы могли бы полезть байтшкодить (привет тиграм), но делать это вслепую - глупо.
Путем непередаваемых на словах потоков мыслей приходим к выводу, что метод pick() почему-то игнорирует глобальное положение объекта в пространстве, и ведет себя, как будто куб не разворачивался.
Теперь самое время задуматься - как бы вы написали этот метод, если бы были в команде адоб.лабс. Лично я бы проходил по всем чилдренам сцены, смотрел бы - попадает ли луч из камеры, проведенный через точку x,y в этот чайлд и как далеко от начала луча это происходит. Т.е. - в любом случае нужно перебирать все объекты и как-нибудь их тестить.
И в каком-то месте этих тестов, наш метод берет неправильное глобальное положение чайлдов. Глобальное положение задается матрицей трансформации worldTransform в SceneNode.
Нам нужно переопределить геттер SceneNode.worldTransform, чтобы посмотреть - что же неправильного он возвращает.
Для этого мы создаем класс, экземпляры которого будут нашими кубиками в большом кубе:
package rubix.view { import com.adobe.scenegraph.Material; import com.adobe.scenegraph.MeshUtils; import com.adobe.scenegraph.SceneMesh; import flash.geom.Matrix3D; /** * ... * @author DoctorSTaL */ public class SmallCube extends SceneMesh { public function SmallCube(r:Number, n1:Number, n2:Number, material:Material) { super(); var child:SceneMesh = MeshUtils.createSuperSphere(r, n1, n2, material); addElement(child.elements[0]); } override public function get worldTransform():Matrix3D { trace("get SmallCube worldTransform! "+name); return super.worldTransform; } } }
var cube:SceneMesh = new SmallCube(2.5, 0.2, 0.2, blackMaterial); // MeshUtils.createSuperSphere(2.5, 0.2, 0.2, blackMaterial);
Цитата:
node (cube10) is at 21.68095095471583
node (cube4) is at 24.81038792162723
node (cube9) is at 20.63268745003665
node (cube4) is at 24.81038792162723
node (cube9) is at 20.63268745003665
В SmallCube.as:
override public function get transform():Matrix3D { trace("get SmallCube transform! "+name); return super.transform; }
get SmallCube transform! cube11 get SmallCube transform! cube11 get SmallCube transform! cube11 node (cube11) is at 21.06597190491122 get SmallCube transform! cube20 get SmallCube transform! cube20 get SmallCube transform! cube20 node (cube20) is at 16.341109329711564
В SmallCube.as:
Запускаем. Ура, все работает так, как надо! Кубики выделяются под курсором, не зависимо от того - какой стороной повернут к нам куб.
Мы нашли, где собака зарыта.
Теперь нужно эту собаку откопать. Для этого нам нужно будет поправить байткод в библиотеке. Это делается достаточно просто, если знать - как.
Итак, мы берем наш proscenium.swc и открываем его архиватором (почему архиватором? потому что любой swc - это всего лишь зазипованные swf-ка и xml-ка с ее описанием). Распаковываем содержимое в отдельную папку proscenium_unpack.
Теперь нам нужен инструмент для редактирования байткода. Лучший, который я нашел - это RABCDAsm (https://github.com/CyberShadow/RABCDAsm). Он работает из коммандной строки, но зато быстро и качественно. Он абсолютно бесплатный и опенсорсный - качаем и распаковываем в любую папку.
Чтобы не настраивать переменные окружения и не возиться с длинными путями, создаем в этой папке папку src и копируем туда library.swf из proscenium_unpack. Открываем командную строку в папке RABCDAsm'а. Следуем инструкциям из README.md :
Код:
h:\programs\RABCDAsm_v1.8>abcexport src/library.swf
Какой же надо поправить? Чтобы это узнать - запускаем поиск по файлам (я юзаю встроенный в ТоталКоммандер) и ищем словосочетание "is at", которое трэйсится у нас как раз в нужном нам методе. Текстовые константы в байткоде хранятся в первозданном виде.
Поиск по файлам находит нам library-13.abc и library.swf. Нам нужно library-13.abc (ну логично ведь, что исходный файл library.swf нам не нужен). Дизассемблируем его:
[code]
h:\programs\RABCDAsm_v1.8>rabcdasm src/library-13.abc
[code]
В результате у нас появилась папка src/library-13/. О ее устройстве можно почитать в README.mb, но я вам сразу скажу, что нам нужен файл src\library-13\com\adobe\scenegraph\SceneNode.class.asasm. Открываем его в любом текстовом редакторе. Ищем "is at". Находим. Вот так выглядит обычный trace("node (" + this.name + ") is at " + localvar);
Код:
findpropstrict QName(PackageNamespace(""), "trace") pushstring "node (" getlocal0 getproperty QName(PackageNamespace(""), "name") add pushstring ") is at " add getlocal 6 add callproperty QName(PackageNamespace(""), "trace"), 1 pop
Код:
findpropstrict QName(PrivateNamespace("com.adobe.scenegraph:SceneNode", "com.adobe.scenegraph:SceneNode#0"), "_tmpRN_") getproperty QName(PrivateNamespace("com.adobe.scenegraph:SceneNode", "com.adobe.scenegraph:SceneNode#0"), "_tmpRN_") getlocal0 getproperty QName(PackageNamespace(""), "transform") getproperty QName(PackageNamespace(""), "position") getproperty QName(PackageNamespace(""), "x") getlocal1 getproperty QName(PackageNamespace(""), "x") subtract setproperty QName(PackageNamespace(""), "x")
Меняем transform на worldTransform (во всех трех местах) и сохраняем файл.
Теперь, чтобы собрать этот файл в abc-тег (abc - это ActionscriptByteCode) и впихнуть этот тег обратно в swf, нужно в командной строке выполнить следующее:
Код:
h:\programs\RABCDAsm_v1.8>rabcasm src/library-13/library-13.main.asasm h:\programs\RABCDAsm_v1.8>abcreplace src/library.swf 13 src/library-13/library-13.main.abc
На всякий случай переименовываем оригинальный proscenium.swc в proscenium.swc.bak и копируем на его место наш новенький swc-файлик.
Открываем наш проект кубика-рубика, удаляем в SmallCube переопределенные методы и компилим - вуаля, все работает как надо! (http://doctorstal.itx.com.ua/flash/rubixcube/html0.12/)
И да, вот получившийся файл proscenium.swc: http://doctorstal.itx.com.ua/flash/r...proscenium.swc
На этом эта статья подходит к концу. А в следующий раз я расскажу вам о том, как использовать этот метод, чтобы реализовать поддержку событий мыши для наших кубиков.
Всего комментариев 6
Комментарии
21.10.2011 00:40 | |
Прямо детектив
|
21.10.2011 10:49 | |
Ну да, с дебагом всегда так.
|
21.10.2011 13:24 | |
Маньяк!
Уважуха за труды! |
21.10.2011 20:49 | |
Спасибо. Надеюсь - кому-нибудь пригодится. Не каждый день приходится ковырять байткод, но навык полезный.
|
19.12.2011 09:23 | |
спасибо за статью... ждём статью о поддержке мышинных событий
|
19.12.2011 13:11 | |
Ох, я как-то забегался, у самого кураж прошел и я подумал, что это больше никому не интересно. Хорошо, напишу на выходных (может чуток раньше).
|
Последние записи от crazyone