Форум Flasher.ru
Ближайшие курсы в Школе RealTime
Список интенсивных курсов: [см.]  
  
Специальные предложения: [см.]  
  
 
Блоги Правила Справка Пользователи Календарь Поиск рулит! Сообщения за день Все разделы прочитаны
 

Вернуться   Форум Flasher.ru > Блоги > crazyone

Рейтинг: 5.00. Голосов: 2.

Расковыриваем библиотеки 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().
Смотрим, как это делается:
Код AS3:
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 - право/верх).
Добавляем на stage.click в RubixCube.as такой же слушатель:
Код AS3:
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;
				}
			}
		}
Результат: http://doctorstal.itx.com.ua/flash/rubixcube/html0.11/
Ура, маленкий кубик выделяется! Но радость проходит быстро. Если мы развернем кубик синей гранью к себе - получится, что выделяется не тот кубик, на который мы нажали, а кубик с другой стороны. Это очень обидно. Это неправильно. Вопиющая несправедливость.

Замечаем, что при вызове метода pick() трейсится чтото такое:
Цитата:
node (cube11) is at 23.765269211529183
node (cube14) is at 18.93605061830806
node (cube17) is at 14.10683202508694
Это трейсы не из нашего кода, а прямо из proscenium.swc. Видимо, адоб сам еще не отдебажил эти методы, раз даже трейсы забыл убрать.

Значит, вопиющая несправедливость - это не злобное решение адоба подсунуть нам каку, а просто попытка адоба уложиться с просцениумом до Adobe MAX. Ну, не успели ребята. Прийдется отдебажить все за них.
Итак. Нам нужна идея - в чем баг и как его исправить. Исходного кода у нас пока нет, мы могли бы полезть байтшкодить (привет тиграм), но делать это вслепую - глупо.
Путем непередаваемых на словах потоков мыслей приходим к выводу, что метод pick() почему-то игнорирует глобальное положение объекта в пространстве, и ведет себя, как будто куб не разворачивался.
Теперь самое время задуматься - как бы вы написали этот метод, если бы были в команде адоб.лабс. Лично я бы проходил по всем чилдренам сцены, смотрел бы - попадает ли луч из камеры, проведенный через точку x,y в этот чайлд и как далеко от начала луча это происходит. Т.е. - в любом случае нужно перебирать все объекты и как-нибудь их тестить.
И в каком-то месте этих тестов, наш метод берет неправильное глобальное положение чайлдов. Глобальное положение задается матрицей трансформации worldTransform в SceneNode.

Нам нужно переопределить геттер SceneNode.worldTransform, чтобы посмотреть - что же неправильного он возвращает.
Для этого мы создаем класс, экземпляры которого будут нашими кубиками в большом кубе:
Код AS3:
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;
		}
	}
 
}
В RubixView.as в initModels меняем создание кубика на следующее:
Код AS3:
var cube:SceneMesh = new SmallCube(2.5, 0.2, 0.2, blackMaterial); // MeshUtils.createSuperSphere(2.5, 0.2, 0.2, blackMaterial);
Теперь, когда в методе pick() будут перебираться наши кубики - мы увидим, что у них дергается для проверки. Запускаем, проверяем.
Цитата:
node (cube10) is at 21.68095095471583
node (cube4) is at 24.81038792162723
node (cube9) is at 20.63268745003665
Никаких тебе "get SmallCube worldTransform!". Но почему? Думаем дальше. Если метод не берет worldTransform и ведет себя так, как будто ему пофиг положение родительского объекта, значит он может брать transform. Проверяем догадку:
В SmallCube.as:
Код AS3:
override public function get transform():Matrix3D {
			trace("get SmallCube transform! "+name);
			return super.transform;
		}
Проверяем:
Код AS3:
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:
Код AS3:
override public function get transform():Matrix3D {
			return super.worldTransform;
		}
Запускаем. Ура, все работает так, как надо! Кубики выделяются под курсором, не зависимо от того - какой стороной повернут к нам куб.
Мы нашли, где собака зарыта.

Теперь нужно эту собаку откопать. Для этого нам нужно будет поправить байткод в библиотеке. Это делается достаточно просто, если знать - как.
Итак, мы берем наш 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
RABCDAsm у нас молчаливый (для скорости работы сделано), поэтому идем в src и смотрим, что получилось. Получилось, что в src теперь лежит не много не мало - 553 файла с именем library-n.abc

Какой же надо поправить? Чтобы это узнать - запускаем поиск по файлам (я юзаю встроенный в ТоталКоммандер) и ищем словосочетание "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
Где-то чуть раньше в коде у нас должно быть обращение к положению объекта - transform. Но раньше - не обязательно означает - выше, т.к. код прыгает по блокам с метками. Наша строка "pushstring ) is at" находится в блоке с меткой "L96:". И в этом блоке к SceneNode.transform обращения нет. Ищем, откуда код переходит на наш блок. Т.е. - ищем "L96". К счастью - находим, причем только в одном месте - на пару строчек выше нашего блока - в блоке L27. И в этом блоке есть обращение к SceneNode.transform:
Код:
      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")
Причем - в трех местах. Этот байткод стоит воспринимать примерно так:
Код AS3:
_tmpRN_.x=this.transform.position.x-local1.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
Теперь нужно спаковать swc заново. Для этого мы заменяем в папке proscenium_unpack library.swf и запаковываем library.swf и catalog.xml в зип-архив proscenium.swc
На всякий случай переименовываем оригинальный 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 Astraport вне форума
Astraport
 
Аватар для Astraport
Прямо детектив
Старый 21.10.2011 10:49 crazyone вне форума
crazyone
 
Аватар для crazyone
Ну да, с дебагом всегда так.
Старый 21.10.2011 13:24 2Sun вне форума
2Sun
 
Аватар для 2Sun
Маньяк!
Уважуха за труды!
Старый 21.10.2011 20:49 crazyone вне форума
crazyone
 
Аватар для crazyone
Спасибо. Надеюсь - кому-нибудь пригодится. Не каждый день приходится ковырять байткод, но навык полезный.
Старый 19.12.2011 09:23 bpla вне форума
bpla
спасибо за статью... ждём статью о поддержке мышинных событий
Старый 19.12.2011 13:11 crazyone вне форума
crazyone
 
Аватар для crazyone
Ох, я как-то забегался, у самого кураж прошел и я подумал, что это больше никому не интересно. Хорошо, напишу на выходных (может чуток раньше).
 

 


Часовой пояс GMT +4, время: 13:58.


Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2024, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.