diff --git a/.gitignore b/.gitignore
index 5d947ca..90762f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,5 @@ bin-release/
# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
# should NOT be excluded as they contain compiler settings and other important
# information for Eclipse / Flash Builder.
+
+.svn/
\ No newline at end of file
diff --git a/libs/Alternativa3D.swc b/libs/Alternativa3D.swc
new file mode 100644
index 0000000..61378b1
Binary files /dev/null and b/libs/Alternativa3D.swc differ
diff --git a/libs/AlternativaTypes.swc b/libs/AlternativaTypes.swc
new file mode 100644
index 0000000..bcac74d
Binary files /dev/null and b/libs/AlternativaTypes.swc differ
diff --git a/libs/AlternativaUtils.swc b/libs/AlternativaUtils.swc
new file mode 100644
index 0000000..c9e8809
Binary files /dev/null and b/libs/AlternativaUtils.swc differ
diff --git a/libs/PropsLib.swc b/libs/PropsLib.swc
new file mode 100644
index 0000000..b266eeb
Binary files /dev/null and b/libs/PropsLib.swc differ
diff --git a/src/AlternativaEditor-app.xml b/src/AlternativaEditor-app.xml
new file mode 100644
index 0000000..16f6cbe
--- /dev/null
+++ b/src/AlternativaEditor-app.xml
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+ AlternativaEditor
+
+
+ AlternativaEditor
+
+
+ AlternativaEditor
+
+
+ v1
+
+
+
+
+
+
+
+
+
+
+
+ [This value will be overwritten by Flex Builder in the output app.xml]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AlternativaEditor.mxml b/src/AlternativaEditor.mxml
new file mode 100644
index 0000000..444908e
--- /dev/null
+++ b/src/AlternativaEditor.mxml
@@ -0,0 +1,784 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ All
+ Tile
+ Sprite
+ Bonus + Spawn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ImageItemRenderer.as b/src/ImageItemRenderer.as
new file mode 100644
index 0000000..3df0dba
--- /dev/null
+++ b/src/ImageItemRenderer.as
@@ -0,0 +1,45 @@
+package {
+ import flash.display.Bitmap;
+
+ import mx.containers.VBox;
+ import mx.controls.Image;
+ import mx.controls.Label;
+ import mx.core.ScrollPolicy;
+ import mx.events.FlexEvent;
+
+ public class ImageItemRenderer extends VBox {
+ private var img:Image = new Image();
+ private var lbl:Label = new Label();
+
+
+ public function ImageItemRenderer() {
+ super();
+
+ this.width=52;
+ this.height=82;
+
+ setStyle("horizontalAlign","center");
+ setStyle("verticalGap","0");
+
+ addChild(img);
+ addChild(lbl);
+
+ img.width = img.height = 50;
+
+ verticalScrollPolicy = ScrollPolicy.OFF;
+ horizontalScrollPolicy = ScrollPolicy.OFF;
+
+ updateDisplayList(52,82);
+
+ addEventListener(FlexEvent.DATA_CHANGE, dataChangeHandler);
+ }
+
+ private function dataChangeHandler(event:FlexEvent):void {
+
+ img.source = data["image"];
+ lbl.text = data["label"];
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/ModeButton.mxml b/src/ModeButton.mxml
new file mode 100644
index 0000000..7549e4a
--- /dev/null
+++ b/src/ModeButton.mxml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
diff --git a/src/PropListPanel.mxml b/src/PropListPanel.mxml
new file mode 100644
index 0000000..e2f6c11
--- /dev/null
+++ b/src/PropListPanel.mxml
@@ -0,0 +1,97 @@
+
+
+
+ =0){
+ list = _group[groupIndex] as TileList;
+ dp = dataProviders[groupIndex] as ArrayCollection;
+ } else {
+ panel = new Canvas()
+ panel.label = group;
+ panel.autoLayout=true;
+ panel.percentHeight = panel.percentWidth = 100;
+ panel.horizontalScrollPolicy = ScrollPolicy.OFF;
+
+ accordion.addChild(panel);
+
+ list = new TileList();
+ list.percentHeight = list.percentWidth = 100;
+ list.focusEnabled = false;
+
+
+
+ dp = new ArrayCollection();
+ dataProviders.push(dp);
+
+ list.dataProvider = dp;
+
+ list.itemRenderer = new ClassFactory(ImageItemRenderer);
+ list.addEventListener(ListEvent.ITEM_CLICK, changeSelected);
+
+ _group.push(list);
+ _groupName.push(group);
+
+ panel.addChild(list);
+
+ }
+
+ var picture:Sprite = new Sprite();
+ picture.addChild(img);
+ dp.addItem({label:name, image:picture, obj:object});
+
+
+ }
+ public function deleteAllProps():void {
+ var list:TileList;
+ for (var i:int = 0; i<_group.length; i++) {
+ list = _group[i] as TileList;
+ list.removeEventListener(ListEvent.ITEM_CLICK, changeSelected);
+ }
+
+ while (accordion.numChildren>0){
+ accordion.removeChildAt(0);
+ }
+
+ dataProviders = new Array();
+ _group = new Array();
+ _groupName = new Array();
+
+ }
+
+ private function changeSelected(e:ListEvent):void {
+ dispatchEvent(new PropListEvent(0, e.itemRenderer.data.obj));
+ selectedItem = e.itemRenderer.data.obj;
+ }
+ ]]>
+
+
+
+
+
+
diff --git a/src/TexturePanel.as b/src/TexturePanel.as
new file mode 100644
index 0000000..a0d2f10
--- /dev/null
+++ b/src/TexturePanel.as
@@ -0,0 +1,87 @@
+package {
+ import flash.display.Bitmap;
+ import flash.display.Sprite;
+
+ import gui.events.PropListEvent;
+
+ import mx.collections.ArrayCollection;
+ import mx.containers.HBox;
+ import mx.containers.Panel;
+ import mx.controls.Image;
+ import mx.controls.TileList;
+ import mx.core.ClassFactory;
+ import mx.events.ListEvent;
+
+ public class TexturePanel extends Panel {
+
+ private var list:TileList = new TileList();
+ private var thumb:Image = new Image();
+ private static var dp:Array;
+ public var selectedItem:* = null;
+ public var empty:Boolean = true;
+
+ public function TexturePanel() {
+ var hbox:HBox = new HBox();
+
+ super();
+ dp = new Array();
+ this.title = "Textures";
+// this.minimizable = this.maximizable = false;
+// this.type = NativeWindowType.UTILITY;
+// this.alwaysInFront = true;
+
+ addChild(hbox);
+
+ hbox.addChild(list);
+ hbox.addChild(thumb);
+
+ this.percentWidth = 100;
+ this.height = 140;
+
+ hbox.percentHeight = hbox.percentWidth = 100;
+ hbox.setStyle("verticalAlign","middle");
+
+ thumb.width = thumb.height = 100;
+ list.percentWidth = 100;
+ list.height = 80;
+ list.setStyle("verticalAlign","middle");
+
+
+ list.dataProvider = dp;
+
+ list.rowHeight=82;
+ list.columnWidth=52;
+ list.itemRenderer = new ClassFactory(ImageItemRenderer);
+ list.addEventListener(ListEvent.ITEM_CLICK, onSelect);
+
+ }
+
+ private function onSelect(e:ListEvent):void {
+ thumb.source = e.itemRenderer.data.pr;// new Bitmap(bmp);
+ selectedItem = e.itemRenderer.data.id;
+ dispatchEvent(new PropListEvent(0, e.itemRenderer.data.id));
+ }
+
+ public function addItem(id:Object, picture:Bitmap=null, label:String=''):void {
+ var img:Sprite = new Sprite();
+ var pr:Sprite = new Sprite();
+ img.addChild(picture);
+ pr.addChild(new Bitmap(picture.bitmapData));
+ var item:Object = {id:id, image:img, label:label, pr:pr};
+// dp.addItem(item);
+ dp.push(item);
+ dp.sortOn("label");
+ empty = false;
+
+ }
+
+ public function deleteAllProps():void {
+ dp = new Array();
+ list.dataProvider = dp;
+ thumb.source=null;
+ empty = true;
+
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/KeyMapper.as b/src/alternativa/editor/KeyMapper.as
new file mode 100644
index 0000000..9655e32
--- /dev/null
+++ b/src/alternativa/editor/KeyMapper.as
@@ -0,0 +1,116 @@
+package alternativa.editor {
+ import __AS3__.vec.Vector;
+
+ import flash.events.IEventDispatcher;
+ import flash.events.KeyboardEvent;
+
+ /**
+ *
+ */
+ public class KeyMapper {
+
+ private const MAX_KEYS:int = 31;
+ private var keys:int;
+ private var map:Vector. = new Vector.(MAX_KEYS);
+ private var _dispatcher:IEventDispatcher;
+
+ /**
+ *
+ */
+ public function KeyMapper(dispatcher:IEventDispatcher = null) {
+ if (dispatcher != null) startListening(dispatcher);
+ }
+
+ /**
+ * @param keyNum
+ */
+ private function checkKey(keyNum:int):void {
+ if (keyNum < 0 || keyNum > MAX_KEYS - 1) throw new ArgumentError("keyNum is out of range");
+ }
+
+ /**
+ * @param keyNum
+ * @param keyCode
+ */
+ public function mapKey(keyNum:int, keyCode:int):void {
+ checkKey(keyNum);
+ map[keyNum] = keyCode;
+ }
+
+ /**
+ * @param keyNum
+ */
+ public function unmapKey(keyNum:int):void {
+ checkKey(keyNum);
+ map[keyNum] = 0;
+ keys &= ~(1 << keyNum);
+ }
+
+ /**
+ * @param e
+ */
+ public function checkEvent(e:KeyboardEvent):void {
+ var idx:int = map.indexOf(e.keyCode);
+ if (idx > -1) e.type == KeyboardEvent.KEY_DOWN ? keys |= (0x1 << idx): keys &= ~(0x1 << idx);
+ }
+
+ /**
+ * @param keyNum
+ * @return
+ */
+ public function getKeyState(keyNum:int):int {
+ return (keys >> keyNum) & 0x1;
+ }
+
+ /**
+ * @param keyNum
+ * @return
+ */
+ public function keyPressed(keyNum:int):Boolean {
+ return getKeyState(keyNum) == 1;
+ }
+
+ /**
+ * @param dispatcher
+ */
+ public function startListening(dispatcher:IEventDispatcher):void {
+ if (_dispatcher == dispatcher) return;
+ if (_dispatcher != null) unregisterListeners();
+ _dispatcher = dispatcher;
+ if (_dispatcher != null) registerListeners();
+ }
+
+ /**
+ *
+ */
+ public function stopListening():void {
+ if (_dispatcher != null) unregisterListeners();
+ _dispatcher = null;
+ keys = 0;
+ }
+
+ /**
+ *
+ */
+ private function registerListeners():void {
+ _dispatcher.addEventListener(KeyboardEvent.KEY_DOWN, onKey);
+ _dispatcher.addEventListener(KeyboardEvent.KEY_UP, onKey);
+ }
+
+ /**
+ *
+ */
+ private function unregisterListeners():void {
+ _dispatcher.removeEventListener(KeyboardEvent.KEY_DOWN, onKey);
+ _dispatcher.removeEventListener(KeyboardEvent.KEY_UP, onKey);
+ }
+
+ /**
+ *
+ */
+ private function onKey(e:KeyboardEvent):void {
+ checkEvent(e);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/LibraryManager.as b/src/alternativa/editor/LibraryManager.as
new file mode 100644
index 0000000..bf33dd0
--- /dev/null
+++ b/src/alternativa/editor/LibraryManager.as
@@ -0,0 +1,203 @@
+package alternativa.editor {
+ import __AS3__.vec.Vector;
+
+ import alternativa.editor.prop.Bonus;
+ import alternativa.editor.prop.Flag;
+ import alternativa.editor.prop.Prop;
+ import alternativa.editor.prop.Spawn;
+ import alternativa.editor.prop.Tile;
+ import alternativa.editor.prop.TileSprite3D;
+ import alternativa.editor.propslib.PropData;
+ import alternativa.editor.propslib.PropGroup;
+ import alternativa.editor.propslib.PropMesh;
+ import alternativa.editor.propslib.PropObject;
+ import alternativa.editor.propslib.PropsLibrary;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Sprite3D;
+ import alternativa.types.Map;
+ import alternativa.types.Point3D;
+
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.filesystem.File;
+
+ import mx.controls.Alert;
+ import mx.controls.ProgressBar;
+
+ /**
+ * @author danilova
+ */
+ public class LibraryManager extends EventDispatcher {
+
+ private static const GRP_SPAWN_POINTS:String = "Spawn Points";
+ private static const GRP_BONUS_REGIONS:String = "Bonus Regions";
+ private static const GRP_FLAGS:String = "Flags";
+
+ // Файл библиотеки
+ private var fileLoadLibrary:File = new File();
+ // Индикатор загрузки библиотеки с очисткой текущих
+ private var clearLibrary:Boolean = false;
+ //
+ public var libraryProgressBar:ProgressBar;
+ //
+ public var libraries:Array = [];
+ //
+ private var propsLibraries:Map = new Map();
+ //
+ private var nextFunction:Function;
+ // Имя пропа (библа + группа + имя меша) -> проп
+ public var nameProp:Map = new Map();
+
+ private var libraryCount:int;
+ private var index:int = 0;
+
+ public function LibraryManager() {
+
+ fileLoadLibrary.addEventListener(Event.SELECT, onSelect);
+ libraryProgressBar = new ProgressBar();
+ libraryProgressBar.labelPlacement = "left";
+ libraryProgressBar.indeterminate = true;
+ libraryProgressBar.label = "Loading library...";
+ libraryProgressBar.direction = "right";
+ libraryProgressBar.width = 200;
+ libraryProgressBar.visible = false;
+ }
+
+ public function loadLibrary(nextFunction:Function = null):void {
+ this.nextFunction = nextFunction;
+ fileLoadLibrary.browseForDirectory("Load library");
+
+ }
+
+ public function clearAndLoadLibrary():void {
+ clearLibrary = true;
+ loadLibrary();
+ }
+
+ private function onSelect(e:Event):void {
+
+ libraryProgressBar.visible = true;
+
+ var propsLibrary:PropsLibrary;
+ var list:Array = fileLoadLibrary.getDirectoryListing();
+ index = 0;
+
+
+ if ((list[0] as File).isDirectory) {
+ libraryCount = list.length;
+ for (var i:int = 0; i < libraryCount; i++) {
+ var file:File = list[i];
+ if (file.isDirectory) {
+ propsLibrary = new PropsLibrary();
+ propsLibrary.addEventListener(Event.COMPLETE, onLoadingComplete);
+ propsLibrary.load(file.url);
+ }
+ }
+ } else {
+ libraryCount = 1;
+ propsLibrary = new PropsLibrary();
+ propsLibrary.addEventListener(Event.COMPLETE, onLoadingComplete);
+ propsLibrary.load(fileLoadLibrary.url);
+ }
+
+ if (clearLibrary) {
+ clearLibrary = false;
+// cursorScene.clear();
+ libraries.length = 0;
+ propsLibraries.clear();
+ }
+
+ }
+
+
+
+ private function onLoadingComplete(e:Event):void {
+ try {
+ index++;
+ var propslibrary:PropsLibrary = e.target as PropsLibrary;
+ var library:String = propslibrary.name;
+ libraries.push(library);
+ var libraryProps:Array = [];
+ var groups:Vector. = propslibrary.rootGroup.groups;
+ var len:int = groups.length;
+ for (var i:int = 0; i < len; i++) {
+ var group:PropGroup = groups[i];
+ // Получаем имя группы
+ var groupName:String = group.name;
+ var props:Vector. = group.props;
+ var propsLen:int = props.length;
+ for (var j:int = 0; j < propsLen; j++) {
+ var propData:PropData = props[j];
+ var propObject:PropObject = propData.statelessData.object;
+ var name:String = propData.name;
+ if (propObject) {
+ var object:Object3D = propObject.object3d;
+ object.coords = new Point3D();
+ var prop:Prop;
+ if (object is Mesh) {
+ switch (groupName) {
+ case GRP_SPAWN_POINTS:
+ prop = new Spawn(object, library, groupName);
+ break;
+ case GRP_BONUS_REGIONS:
+ prop = new Bonus(object, library, groupName);
+ break;
+ case GRP_FLAGS:
+ prop = new Flag(object, library, groupName);
+ break;
+ default:
+ var tile:Tile = new Tile(object, library, groupName);
+ prop = tile;
+ tile.bitmaps = (propObject as PropMesh).bitmaps;
+ // Установка текстуры по умолчанию
+ // TODO: Желательно, чтобы устанавливалась первая указанная в XML текстура
+ if (tile.bitmaps != null) {
+ for (var tName:String in tile.bitmaps) {
+ tile.textureName = tName;
+ break;
+ }
+ }
+ break;
+ }
+
+ } else if (object is Sprite3D) {
+ prop = new TileSprite3D(object as Sprite3D, library, groupName);
+ }
+
+ prop.name = name;
+
+ // Получаем иконку пропа
+ prop.icon = AlternativaEditor.preview.getPropIcon(prop);
+ libraryProps.push(prop);
+ nameProp.add(library + groupName + name, prop);
+
+ }
+
+ }
+ }
+
+ propsLibraries.add(library, libraryProps);
+ libraryProgressBar.visible = false;
+ if (index == libraryCount) {
+ dispatchEvent(new Event(Event.CHANGE));
+ if (nextFunction != null) {
+ // Если загрузили библиотеку в процессе загрузки уровня, продолжаем загружать уровень
+ nextFunction();
+ nextFunction = null;
+ }
+ }
+
+
+ } catch (err:Error) {
+ Alert.show(err.message);
+ }
+
+ }
+
+ public function getLibrary(libraryName:String):Array {
+ return propsLibraries[libraryName];
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/Preview.as b/src/alternativa/editor/Preview.as
new file mode 100644
index 0000000..4a2c7d2
--- /dev/null
+++ b/src/alternativa/editor/Preview.as
@@ -0,0 +1,212 @@
+package alternativa.editor {
+ import alternativa.editor.prop.Prop;
+ import alternativa.editor.prop.TileSprite3D;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Scene3D;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.engine3d.display.View;
+ import alternativa.types.Map;
+ import alternativa.types.Point3D;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.Bitmap;
+ import flash.display.BitmapData;
+ import flash.events.Event;
+ import flash.geom.Matrix;
+ import flash.geom.Point;
+
+ import mx.core.UIComponent;
+
+
+ public class Preview extends UIComponent {
+
+ private var sceneProp:Scene3D;
+ private var cameraProp:Camera3D;
+ private var cameraPropContainer:Object3D;
+ private var viewProp:View;
+ private var matrix:Matrix = new Matrix();
+ // Мап проп -> оптимальное расстояние от камеры до пропа
+ private var propDistance:Map = new Map();
+ private var halfFov:Number;
+ private const iconWidth:Number = 50;
+ private const sqrt2:Number = Math.sqrt(2);
+ private const sqrt3:Number = Math.sqrt(3);
+
+ public function Preview() {
+ super();
+ addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
+
+ }
+
+ private function onAddedToStage(e:Event):void {
+ removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
+ initScene();
+ addEventListener(Event.ENTER_FRAME, onEnterFrame);
+ addEventListener(Event.RESIZE, onResize);
+ onResize();
+ }
+
+ /**
+ * Инициализация сцены предосмотра пропа.
+ */
+ private function initScene(): void {
+
+ // Создание сцены
+ sceneProp = new Scene3D();
+ sceneProp.root = new Object3D();
+
+ // Добавление камеры и области вывода
+ cameraProp = new Camera3D();
+ cameraProp.rotationX = -MathUtils.DEG90 - MathUtils.DEG30;
+ cameraPropContainer = new Object3D();
+ cameraPropContainer.addChild(cameraProp);
+ cameraProp.coords = new Point3D(0, -100, 40);
+ sceneProp.root.addChild(cameraPropContainer);
+
+ viewProp = new View(cameraProp);
+ addChild(viewProp);
+ viewProp.graphics.beginFill(0xFFFFFF);
+ viewProp.graphics.drawRect(0, 0, 1, 1);
+ viewProp.graphics.endFill();
+
+ halfFov = cameraProp.fov/2;
+
+ }
+
+ private function onEnterFrame(e:Event):void {
+
+ cameraPropContainer.rotationZ += MathUtils.DEG1;
+ sceneProp.calculate();
+ }
+
+ /**
+ * Находит расстояние, на котором должна быть камера, чтобы проп было хорошо видно.
+ * @param prop проп
+ * @return расстояние
+ */
+ private function analyzeProp(prop:Prop):Point {
+
+ var point:Point;
+ var maxSqrDistance:Number = 0;
+ var maxZ:Number = 0;
+ var h:Number;
+
+ var tileSprite3D:TileSprite3D = prop as TileSprite3D;
+ if (tileSprite3D) {
+
+ var bitmapData:BitmapData = prop.bitmapData;
+ maxSqrDistance = tileSprite3D.scale*1.5*Math.max(bitmapData.width, bitmapData.height);
+ maxZ = bitmapData.height;
+
+ } else {
+ var vertices:Array = prop.vertices.toArray(true);
+ var len:int = vertices.length;
+ var deltaZ:Number;
+ var deltaY:Number;
+ var deltaX:Number;
+ var sqrDistance:Number;
+ for (var i:int = 0; i < len; i++) {
+ var vertex1:Point3D = (vertices[i] as Vertex).coords;
+ deltaX = vertex1.x - prop.x;
+ deltaY = vertex1.y - prop.y;
+ deltaZ = vertex1.z - prop.z;
+ sqrDistance = deltaX*deltaX + deltaY*deltaY + deltaZ*deltaZ;
+ if (sqrDistance > maxSqrDistance) {
+ maxSqrDistance = sqrDistance;
+ }
+
+ for (var j:int = i + 1; j < len; j++) {
+ var vertex2:Point3D = (vertices[j] as Vertex).coords;
+ deltaZ = vertex1.z - vertex2.z;
+ deltaZ = deltaZ < 0 ? -deltaZ : deltaZ;
+
+ if (deltaZ > maxZ) {
+ maxZ = deltaZ;
+ }
+
+ }
+ }
+
+ maxSqrDistance = 2*Math.sqrt(maxSqrDistance);
+
+
+ }
+
+ h = sqrt2*maxSqrDistance/(2*Math.tan(halfFov)) + maxSqrDistance/2;
+ point = new Point(h, maxZ/2);
+
+ propDistance.add(prop, point);
+ return point;
+ }
+
+ /**
+ * Получение иконки для пропа
+ * @param prop
+ * @return иконка
+ */
+ public function getPropIcon(prop:Prop):Bitmap {
+
+ clearPropScene();
+ analyzeProp(prop);
+ setCameraCoords(prop);
+ sceneProp.root.addChild(prop);
+ sceneProp.calculate();
+ var bitmapData:BitmapData = new BitmapData(iconWidth, iconWidth, false, 0x0);
+ matrix.a = iconWidth/viewProp.width;
+ matrix.d = matrix.a;
+ bitmapData.draw(viewProp, matrix);
+ var result:Bitmap = new Bitmap(bitmapData);
+ return result;
+
+ }
+
+ /**
+ * Установка координат камеры.
+ * @param prop
+ *
+ */
+ private function setCameraCoords(prop:Object3D):void {
+
+ var yzDistance:Point = propDistance[prop];
+ if (yzDistance) {
+ cameraProp.y = -yzDistance.x;
+ cameraProp.z = yzDistance.y/2 + yzDistance.x/sqrt3;
+
+ }
+
+ }
+
+ /**
+ * Очистка сцены предосмотра.
+ */
+ private function clearPropScene():void {
+
+ for (var child:* in sceneProp.root.children) {
+ var prop:Prop = child as Prop;
+ if (prop) {
+ sceneProp.root.removeChild(prop);
+ }
+ }
+
+ }
+
+ public function showProp(prop:Prop):void {
+
+ clearPropScene();
+ setCameraCoords(prop);
+ sceneProp.root.addChild(prop);
+ }
+
+ /**
+ * Корректировка размеров и положения объектов при изменении окна плеера.
+ */
+ public function onResize(e:Event = null):void {
+
+ viewProp.width = parent.width;
+ viewProp.height = parent.height;
+ sceneProp.calculate();
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/SceneContainer.as b/src/alternativa/editor/SceneContainer.as
new file mode 100644
index 0000000..dd6479f
--- /dev/null
+++ b/src/alternativa/editor/SceneContainer.as
@@ -0,0 +1,696 @@
+package alternativa.editor {
+ import alternativa.editor.eventjournal.EventJournal;
+ import alternativa.editor.eventjournal.EventJournalItem;
+ import alternativa.editor.prop.Prop;
+ import alternativa.editor.scene.CursorScene;
+ import alternativa.editor.scene.EditorScene;
+ import alternativa.editor.scene.MainScene;
+ import alternativa.editor.scene.OccupyMap;
+ import alternativa.engine3d.events.MouseEvent3D;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+ import alternativa.utils.KeyboardUtils;
+
+ import flash.display.Graphics;
+ import flash.display.Shape;
+ import flash.events.Event;
+ import flash.events.KeyboardEvent;
+ import flash.events.MouseEvent;
+ import flash.geom.Point;
+ import flash.ui.Keyboard;
+ import flash.utils.setTimeout;
+
+ import mx.controls.Alert;
+ import mx.core.UIComponent;
+ import mx.events.CloseEvent;
+
+ public class SceneContainer extends UIComponent {
+ // Сцена с курсором
+ public var cursorScene:CursorScene;
+ // Сцена уровня
+ public var mainScene:MainScene;
+ // Индикатор вертикального движения
+ private var verticalMoving:Boolean = false;
+ //
+ private var copy:Boolean = false;
+ //
+ private var mouseDown:Boolean;
+ // Журнал событий
+ private var eventJournal:EventJournal;
+ // Индикатор режима вставки пропов
+ public var multiplePropMode:int = 1;
+ //
+ private var cameraTransformation:Matrix3D;
+ //
+ private var _snapMode:Boolean = true;
+
+ private var cameraDistance:Number;
+
+ private var shape:Shape;
+
+ private var keyMapper:KeyMapper;
+
+ public function SceneContainer() {
+ super();
+ addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
+ initKeyMapper();
+ }
+
+ private function initKeyMapper():void {
+ keyMapper = new KeyMapper();
+ keyMapper.mapKey(0, KeyboardUtils.N);
+ keyMapper.mapKey(1, KeyboardUtils.M);
+ keyMapper.mapKey(2, Keyboard.NUMPAD_4);
+ keyMapper.mapKey(3, Keyboard.NUMPAD_6);
+ keyMapper.mapKey(4, Keyboard.NUMPAD_8);
+ keyMapper.mapKey(5, Keyboard.NUMPAD_2);
+ keyMapper.mapKey(6, Keyboard.NUMPAD_9);
+ keyMapper.mapKey(7, Keyboard.NUMPAD_3);
+ }
+
+ private function onAddedToStage(e:Event):void {
+
+ removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
+
+ keyMapper.startListening(stage);
+
+ cursorScene = new CursorScene(stage);
+ mainScene = new MainScene();
+ cursorScene.occupyMap = mainScene.occupyMap;
+
+ addChild(mainScene.view);
+ addChild(cursorScene.view);
+ shape = new Shape();
+ addChild(shape);
+
+ initListeners();
+ eventJournal = new EventJournal();
+
+ var cameraCoords:Point3D = cursorScene.camera.coords;
+ cameraDistance = Math.sqrt(cameraCoords.x*cameraCoords.x + cameraCoords.y*cameraCoords.y + cameraCoords.z*cameraCoords.z);
+ }
+
+ /**
+ * Установка обработчиков.
+ */
+ protected function initListeners():void {
+
+ addEventListener(Event.ENTER_FRAME, onEnterFrame);
+ stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
+ stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
+ parent.addEventListener(Event.RESIZE, onResize);
+ parent.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
+ parent.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ parent.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
+ parent.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
+ parent.addEventListener(MouseEvent.MIDDLE_MOUSE_DOWN, onMiddleMouseDown);
+ parent.addEventListener(MouseEvent.MIDDLE_MOUSE_UP, onMiddleMouseUp);
+ parent.addEventListener(MouseEvent.ROLL_OUT, onMouseOut);
+ onResize();
+ }
+
+
+ // Точка клика
+ private var mouseDownPoint:Point = new Point();
+ // Координаты выделенного пропа на момент выделения
+ private var startSelCoords:Point3D;
+
+ private function onMouseDown(e:MouseEvent):void {
+ if (mainScene.propMouseDown) {
+
+ var selProp:Prop = mainScene.selectedProp;
+ if (selProp) {
+ // Запоминаем исходные координаты
+ startSelCoords = selProp.coords;
+ cursorScene.visible = false;
+ }
+ }
+
+ mouseDown = true;
+ mouseDownPoint.x = mainScene.view.mouseX;
+ mouseDownPoint.y = mainScene.view.mouseY;
+ }
+
+
+
+ private function onMouseUp(e:MouseEvent):void {
+ var selProp:Prop = mainScene.selectedProp;
+ var selectedProps:Set = mainScene.selectedProps;
+
+ if (mainScene.propMouseDown) {
+ if (selProp) {
+ var move:Boolean = false;
+ if (copy) {
+ // Заносим в журнал
+ eventJournal.addEvent(EventJournal.COPY, selectedProps.clone());
+ } else {
+ // Проверка на перемещение
+ if (!startSelCoords.equals(selProp.coords)) {
+ var delta:Point3D = selProp.coords;
+ delta.difference(delta, startSelCoords);
+ // Заносим в журнал
+ eventJournal.addEvent(EventJournal.MOVE, selectedProps.clone(), delta);
+ move = true;
+ }
+ }
+
+ if (_snapMode && (copy || move)) {
+ checkConflict();
+ }
+
+ copy = false;
+
+ }
+ mainScene.propMouseDown = false;
+
+ } else {
+ // Проверка на клик
+ var deltaX:Number = mouseDownPoint.x - mainScene.view.mouseX;
+ var deltaY:Number = mouseDownPoint.y - mainScene.view.mouseY;
+ deltaX = deltaX < 0 ? -deltaX : deltaX;
+ deltaY = deltaY < 0 ? -deltaY : deltaY;
+ if ((deltaX < 3) && (deltaY < 3)) {
+ // Перемещаем курсор туда, куда кликнули мышью
+ if (propDown) {
+ if (cursorScene.object) {
+ cursorScene.object.z = clickZ;
+ }
+ propDown = false;
+ }
+ cursorScene.moveCursorByMouse();
+
+ if (!cursorScene.visible) {
+ mainScene.deselectProps();
+ cursorScene.visible = true;
+
+ }
+ } else {
+ // выделяем/снимаем выделение с пропов под прямоугольником
+ if (e.shiftKey) {
+ for (var p:* in rectProps) {
+ var prop:Prop = p;
+ if (e.altKey) {
+ if (selectedProps.has(prop)) {
+ mainScene.deselectProp(prop);
+ }
+ } else {
+ if (!selectedProps.has(prop)) {
+ mainScene.selectProp(prop);
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ mouseDown = false;
+ shape.graphics.clear();
+ }
+
+ private function alertConflict(e:CloseEvent):void {
+ if (e.detail == Alert.NO) {
+ // Отменяем
+ mainScene.undo(eventJournal.undo(true));
+ }
+ setFocus();
+ }
+
+ private function checkConflict():void {
+
+ if (multiplePropMode != 0) {
+ // Ищем пересекающие пропы
+ var selectedProps:Set = mainScene.selectedProps;
+ var occupyMap:OccupyMap = mainScene.occupyMap;
+ for (var p:* in selectedProps) {
+ var prop:Prop = p;
+ if ((multiplePropMode == 2 && occupyMap.isConflict(prop)) || (multiplePropMode == 1 && occupyMap.isConflictGroup(prop))) {
+ Alert.show("This location is occupied. Continue?", "", Alert.YES|Alert.NO, this, alertConflict, null, Alert.YES);
+ break;
+ }
+ }
+
+ }
+ }
+
+ private var prevMoveX:Number;
+ private var prevMoveY:Number;
+ private var rectProps:Set = new Set();
+
+ private function onMouseMove(e:MouseEvent):void {
+ var p:*;
+ var prop:Prop;
+ var selProp:Prop = mainScene.selectedProp;
+ var selectedProps:Set = mainScene.selectedProps;
+
+ if (mainScene.propMouseDown && selProp) {
+ // Проверка на необходимость копирования
+ if (e.shiftKey && !copy) {
+ // Проверка на перемещение
+ if (!startSelCoords.equals(selProp.coords)) {
+ var delta:Point3D = selProp.coords;
+ delta.difference(delta, startSelCoords);
+ // Заносим в журнал
+ eventJournal.addEvent(EventJournal.MOVE, selectedProps.clone(), delta);
+ }
+ // Копируем пропы
+ var copyProps:Set = new Set();
+ for (p in selectedProps) {
+ prop = p as Prop;
+ var copyProp:Prop = mainScene.addProp(prop, prop.coords, prop.rotationZ);
+ if (prop == selProp) {
+ selProp = copyProp;
+ }
+ copyProps.add(copyProp);
+ }
+ // Выделяем копии
+ mainScene.selectProps(copyProps);
+ mainScene.selectedProp = selProp;
+ // Запоминаем исходные координаты
+ startSelCoords = selProp.coords;
+ copy = true;
+ }
+
+ // Перемещаем проп
+ mainScene.moveSelectedProps(verticalMoving);
+
+ } else {
+ // Проверка на паннинг
+ if (middleDown) {
+ var matrix:Matrix3D = cursorScene.camera.transformation;
+ var axisX:Point3D = new Point3D(matrix.a, matrix.e, matrix.i);
+ var axisY:Point3D = new Point3D(matrix.b, matrix.f, matrix.j);
+ axisX.multiply(10*(prevMoveX - e.stageX));
+ axisY.multiply(10*(prevMoveY - e.stageY));
+
+ var coords:Point3D = new Point3D(matrix.d, matrix.h, matrix.l);
+ coords.add(axisX);
+ coords.add(axisY);
+ cursorScene.cameraController.coords = cursorScene.container.globalToLocal(coords);
+
+ } else if (mouseDown) {
+ var dx:Number = mouseDownPoint.x - mainScene.view.mouseX;
+ dx = dx > 0 ? dx : -dx;
+ var dy:Number = mouseDownPoint.y - mainScene.view.mouseY;
+ dy = dy > 0 ? dy : -dy;
+ if (dx > 3 && dy > 3) {
+ // Отрисовка прямоугольника выделения
+ var point:Point = new Point(Math.min(mainScene.view.mouseX, mouseDownPoint.x), Math.min(mainScene.view.mouseY, mouseDownPoint.y));
+ var gfx:Graphics = shape.graphics;
+ gfx.clear();
+ gfx.lineStyle(0, 0x000000);
+ gfx.moveTo(point.x, point.y);
+
+ gfx.lineTo(point.x + dx, point.y);
+ gfx.lineTo(point.x + dx, point.y + dy);
+ gfx.lineTo(point.x, point.y + dy);
+ gfx.lineTo(point.x, point.y);
+
+ // Выделяем пропы, попавшие под прямоугольник
+ if (e.shiftKey) {
+ var prevRectProps:Set = rectProps.clone();
+ if (e.altKey) {
+ // Снимаем выделение
+ rectProps = mainScene.getPropsUnderRect(point, dx, dy, false);
+ for (p in prevRectProps) {
+ prop = p;
+ if (!rectProps.has(prop) && selectedProps.has(prop)) {
+ prop.select();
+ }
+ }
+ } else {
+ // Выделяем
+ rectProps = mainScene.getPropsUnderRect(point, dx, dy, true);
+ for (p in prevRectProps) {
+ prop = p;
+ if (!rectProps.has(prop) && !selectedProps.has(prop)) {
+ prop.deselect();
+ }
+ }
+
+ }
+
+ } else {
+ mainScene.selectProps(mainScene.getPropsUnderRect(point, dx, dy, true));
+ }
+
+ cursorScene.visible = false;
+ }
+ }
+ }
+ prevMoveX = e.stageX;
+ prevMoveY = e.stageY;
+ }
+
+ private var cameraPoint:Point3D = new Point3D(0, 0, 1000);
+ /**
+ * Зум.
+ */
+ private function onMouseWheel(e:MouseEvent):void {
+ zoom(e.delta);
+ }
+
+ /**
+ * @param delta
+ */
+ private function zoom(delta:int):void {
+ var point:Point3D = mainScene.selectedProp ? mainScene.selectedProp.coords : cursorScene.camera.localToGlobal(cameraPoint);
+ var coords:Point3D = cursorScene.container.localToGlobal(cursorScene.cameraController.coords);
+ var old:Point3D = coords.clone();
+ coords.x = (point.x + delta*coords.x)/(1 + delta);
+ coords.y = (point.y + delta*coords.y)/(1 + delta);
+ coords.z = (point.z + delta*coords.z)/(1 + delta);
+ cursorScene.cameraController.coords = cursorScene.container.globalToLocal(coords);
+ coords.difference(coords, old);
+ if (delta > 0) cameraDistance -= Math.sqrt(coords.x*coords.x + coords.y*coords.y + coords.z*coords.z);
+ else cameraDistance += Math.sqrt(coords.x*coords.x + coords.y*coords.y + coords.z*coords.z);
+ }
+
+ private var outDown:Boolean = false;
+ private function onMouseOut(e:MouseEvent):void {
+ if (e.buttonDown) {
+ parent.addEventListener(MouseEvent.ROLL_OVER, onMouseOver);
+ cursorScene.containerController.setMouseLook(false);
+ }
+
+ }
+
+ private function onMouseOver(e:MouseEvent):void {
+ parent.removeEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
+ if (!e.buttonDown) {
+ onMouseUp(e);
+ } else {
+ onMouseDown(e);
+ }
+ }
+
+ private var middleDown:Boolean = false;
+ private function onMiddleMouseDown(e:MouseEvent):void {
+ var coords:Point3D;
+
+ if (e.altKey) {
+ var selProp:Prop = mainScene.selectedProp;
+ if (selProp) {
+ var centre:Point = mainScene.getCentrePropsGroup();
+ coords = new Point3D(centre.x, centre.y, selProp.z);
+ } else {
+ coords = cursorScene.camera.localToGlobal(new Point3D(0, 0, cameraDistance));
+ }
+ var offset:Point3D = cursorScene.containerController.coords.clone();
+ offset.subtract(coords);
+ var cameraCoords:Point3D = cursorScene.container.localToGlobal(cursorScene.cameraController.coords);
+ cameraCoords.add(offset);
+ cursorScene.cameraController.coords = cursorScene.container.globalToLocal(cameraCoords);
+ cursorScene.containerController.coords = coords;
+ cursorScene.containerController.setMouseLook(true);
+ } else {
+ middleDown = true;
+ }
+
+ }
+
+ private function onMiddleMouseUp(e:MouseEvent):void {
+ middleDown = false;
+ cursorScene.containerController.setMouseLook(false);
+ }
+
+
+ private var cameraOffset:Point3D = new Point3D;
+
+ /**
+ * Покадровая обработка.
+ */
+ private function onEnterFrame(e:Event):void {
+
+ cursorScene.containerController.yawLeft(keyMapper.keyPressed(0));
+ cursorScene.containerController.yawRight(keyMapper.keyPressed(1));
+ cursorScene.containerController.pitchDown(keyMapper.keyPressed(6));
+ cursorScene.containerController.pitchUp(keyMapper.keyPressed(7));
+
+ cursorScene.containerController.speed = 2000;
+ cursorScene.containerController.moveLeft(keyMapper.keyPressed(2));
+ cursorScene.containerController.moveRight(keyMapper.keyPressed(3));
+ cursorScene.containerController.moveForward(keyMapper.keyPressed(4));
+ cursorScene.containerController.moveBack(keyMapper.keyPressed(5));
+
+ cursorScene.cameraController.processInput();
+ cursorScene.containerController.processInput();
+
+ cursorScene.calculate();
+
+ cameraTransformation = cursorScene.camera.transformation;
+ cameraOffset.x = cameraTransformation.d;
+ cameraOffset.y = cameraTransformation.h;
+ cameraOffset.z = cameraTransformation.l;
+ // Рисуем оси
+ cursorScene.drawAxis(cameraTransformation);
+ var rotation:Point3D = cameraTransformation.getRotations();
+ // Синхронизируем камеру главной сцены
+ mainScene.synchronize(cameraOffset, rotation.x, rotation.y, rotation.z);
+ mainScene.calculate();
+ }
+
+ /**
+ * Корректировка размеров и положения объектов при изменении окна плеера.
+ */
+ private function onResize(e:Event = null):void {
+
+ cursorScene.viewResize(parent.width - 20, parent.height - 40);
+ mainScene.viewResize(parent.width - 20, parent.height - 40);
+
+ }
+
+// override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
+// if (cursorScene == null) return;
+//
+// cursorScene.viewResize(unscaledWidth, unscaledHeight);
+// mainScene.viewResize(unscaledWidth, unscaledHeight);
+//
+// }
+
+
+ /**
+ * Обработка нажатия клавиш.
+ */
+ private function onKeyDown(event:KeyboardEvent):void {
+
+ var selProp:Prop;
+ var p:*;
+ var delta:Point3D;
+ var oldCoords:Point3D;
+
+ switch (event.keyCode) {
+ case KeyboardUtils.UP:
+ case KeyboardUtils.DOWN:
+ case KeyboardUtils.LEFT:
+ case KeyboardUtils.RIGHT:
+ var sector:int = mainScene.getCameraSector();
+ if (cursorScene.visible) {
+ cursorScene.moveByArrows(event.keyCode, sector);
+ } else {
+ selProp = mainScene.selectedProp;
+ oldCoords = selProp.coords;
+ // Перемещаем
+ mainScene.moveByArrows(event.keyCode, sector);
+ // Вычисляем перемещение
+ delta = selProp.coords;
+ delta.difference(delta, oldCoords);
+ // Заносим в журнал
+ eventJournal.addEvent(EventJournal.MOVE, mainScene.selectedProps, delta);
+ }
+
+ break;
+ case KeyboardUtils.V:
+ verticalMoving = true;
+ break;
+ case KeyboardUtils.W:
+ if (cursorScene.visible) {
+ cursorScene.object.z += EditorScene.vBase;
+ cursorScene.updateMaterial();
+ } else {
+ selProp = mainScene.selectedProp;
+ if (selProp) {
+ oldCoords = selProp.coords;
+ mainScene.verticalMove(false);
+ delta = selProp.coords;
+ delta.difference(delta, oldCoords);
+ // Заносим в журнал
+ eventJournal.addEvent(EventJournal.MOVE, mainScene.selectedProps, delta);
+ }
+
+ }
+ break;
+ case KeyboardUtils.S:
+ if (!event.ctrlKey) {
+ if (cursorScene.visible) {
+ cursorScene.object.z -= EditorScene.vBase;
+ cursorScene.updateMaterial();
+ } else {
+ selProp = mainScene.selectedProp;
+ if (selProp) {
+ oldCoords = selProp.coords;
+ mainScene.verticalMove(true);
+ delta = selProp.coords;
+ delta.difference(delta, oldCoords);
+ // Заносим в журнал
+ eventJournal.addEvent(EventJournal.MOVE, mainScene.selectedProps, delta);
+ }
+ }
+
+ }
+ break;
+
+ case KeyboardUtils.DELETE:
+ case KeyboardUtils.C:
+ selProp = mainScene.selectedProp;
+ if (selProp) {
+ var cursor:Prop = cursorScene.object;
+ if (cursor) {
+ cursor.coords = selProp.coords;
+ if (snapMode) {
+ cursor.snapCoords();
+ }
+ }
+ eventJournal.addEvent(EventJournal.DELETE, mainScene.deleteProps());
+ cursorScene.visible = true;
+
+ }
+ break;
+ case KeyboardUtils.Z:
+ if (cursorScene.visible) {
+ cursorScene.rotateProps(true);
+ cursorScene.updateMaterial();
+ } else {
+ eventJournal.addEvent(EventJournal.ROTATE, mainScene.selectedProps.clone(), false);
+ mainScene.rotateProps(true);
+ }
+ break;
+ case KeyboardUtils.X:
+ if (cursorScene.visible) {
+ cursorScene.rotateProps(false);
+ cursorScene.updateMaterial();
+ } else {
+ eventJournal.addEvent(EventJournal.ROTATE, mainScene.selectedProps.clone(), true);
+ mainScene.rotateProps(false);
+ }
+ break;
+ case KeyboardUtils.ESCAPE:
+ selProp = mainScene.selectedProp;
+ if (selProp) {
+ if (cursorScene.object) {
+ cursorScene.object.coords = selProp.coords;
+ cursorScene.object.snapCoords();
+ }
+ mainScene.deselectProps();
+ cursorScene.visible = true;
+ }
+ break;
+ case Keyboard.NUMPAD_ADD:
+ zoom(3);
+ break;
+ case Keyboard.NUMPAD_SUBTRACT:
+ zoom(-3);
+ break;
+ case Keyboard.F:
+ mainScene.mirrorTextures();
+ break;
+ case Keyboard.Q:
+ mainScene.selectConflictProps();
+ break;
+ }
+ }
+
+ /**
+ *
+ */
+ private function onKeyUp(e:KeyboardEvent):void {
+ switch (e.keyCode) {
+ case KeyboardUtils.V:
+ verticalMoving = false;
+ break;
+ }
+ }
+
+ /**
+ * Очистка сцен.
+ */
+ public function clear():void {
+
+ mainScene.clear();
+ cursorScene.clear();
+ }
+
+ /**
+ *
+ */
+ public function set snapMode(value:Boolean):void {
+ _snapMode = value;
+ mainScene.snapMode = value;
+ cursorScene.snapMode = value;
+ }
+
+ /**
+ *
+ */
+ public function get snapMode():Boolean {
+ return _snapMode;
+ }
+
+ /**
+ * Добавление пропа.
+ * @param sourceProp исходный проп
+ */
+ public function addProp(sourceProp:Prop):void {
+
+ var prop:Prop = mainScene.addProp(sourceProp, cursorScene.object.coords, cursorScene.object.rotationZ);
+ var props:Set = new Set();
+ props.add(prop);
+ eventJournal.addEvent(EventJournal.ADD, props);
+ setTimeout(cursorScene.updateMaterial, 200);
+ prop.addEventListener(MouseEvent3D.MOUSE_DOWN, onPropMouseDown);
+
+// if (_snapMode && !cursorScene.freeState && ((multiplePropMode == 2) || (multiplePropMode == 1 && cursorScene.occupyMap.isConflictGroup(cursorScene.object)))) {
+
+ if (_snapMode && !cursorScene.freeState && ((multiplePropMode == 2 && cursorScene.occupyMap.isConflict(prop)) || (multiplePropMode == 1 && cursorScene.occupyMap.isConflictGroup(prop)))) {
+ Alert.show("This location is occupied. Continue?", "", Alert.YES|Alert.NO, this, alertConflict, null, Alert.YES);
+
+ }
+
+
+ }
+
+ private var clickZ:Number;
+ private var propDown:Boolean = false;
+ private function onPropMouseDown(e:MouseEvent3D):void {
+ clickZ = e.object.z;
+ propDown = true;
+
+ }
+
+
+ public function undo():void {
+ var e:EventJournalItem = eventJournal.undo();
+ if (e) {
+ mainScene.undo(e);
+ if (cursorScene.visible) {
+ cursorScene.updateMaterial();
+ }
+ }
+ }
+
+ public function redo():void {
+ var e:EventJournalItem = eventJournal.redo();
+ if (e) {
+ mainScene.redo(e);
+ if (cursorScene.visible) {
+ cursorScene.updateMaterial();
+ }
+ }
+ }
+
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/eventjournal/EventJournal.as b/src/alternativa/editor/eventjournal/EventJournal.as
new file mode 100644
index 0000000..3a4f76f
--- /dev/null
+++ b/src/alternativa/editor/eventjournal/EventJournal.as
@@ -0,0 +1,56 @@
+package alternativa.editor.eventjournal {
+ import alternativa.types.Set;
+
+
+ public class EventJournal {
+
+ public static const ADD:int = 0;
+ public static const DELETE:int = 1;
+ public static const ROTATE:int = 2;
+ public static const MOVE:int = 3;
+ public static const COPY:int = 4;
+ public static const CHANGE_TEXTURE:int = 5;
+
+ private var events:Array;
+ private var cancelEvents:Array;
+
+ public function EventJournal() {
+ events = [];
+ cancelEvents = [];
+ }
+
+ public function addEvent(operation:int, props:Set, oldState:* = null):void {
+
+ events.push(new EventJournalItem(operation, props, oldState));
+ cancelEvents.length = 0;
+ }
+
+
+ public function undo(deleteEvent:Boolean = false):EventJournalItem {
+ var len:int = events.length;
+ if (len > 0) {
+ var e:EventJournalItem = events[len - 1];
+ events.pop();
+ if (!deleteEvent) {
+ cancelEvents.push(e);
+ }
+ return e;
+ }
+ return null;
+ }
+
+ public function redo():EventJournalItem {
+ var len:int = cancelEvents.length;
+ if (len > 0) {
+ var e:EventJournalItem = cancelEvents[len - 1];
+ cancelEvents.pop();
+ events.push(e);
+ return e;
+ }
+ return null;
+ }
+ }
+}
+
+
+
diff --git a/src/alternativa/editor/eventjournal/EventJournalItem.as b/src/alternativa/editor/eventjournal/EventJournalItem.as
new file mode 100644
index 0000000..1cca256
--- /dev/null
+++ b/src/alternativa/editor/eventjournal/EventJournalItem.as
@@ -0,0 +1,18 @@
+package alternativa.editor.eventjournal {
+ import alternativa.types.Set;
+
+
+ public class EventJournalItem {
+
+ public var operation:int;
+ public var props:Set;
+ public var oldState:*;
+
+ public function EventJournalItem(operation:int, props:Set, oldState:*) {
+
+ this.operation = operation;
+ this.props = props;
+ this.oldState = oldState;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/events/LevelLoaded.as b/src/alternativa/editor/events/LevelLoaded.as
new file mode 100644
index 0000000..922183f
--- /dev/null
+++ b/src/alternativa/editor/events/LevelLoaded.as
@@ -0,0 +1,12 @@
+package alternativa.editor.events {
+ import flash.events.Event;
+
+ public class LevelLoaded extends Event {
+ public static const LEVEL_LOADED:String = "level_loaded";
+
+ public function LevelLoaded(bubbles:Boolean=false, cancelable:Boolean=false) {
+ super(LEVEL_LOADED, bubbles, cancelable);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/export/BinaryExporter.as b/src/alternativa/editor/export/BinaryExporter.as
new file mode 100644
index 0000000..9921434
--- /dev/null
+++ b/src/alternativa/editor/export/BinaryExporter.as
@@ -0,0 +1,47 @@
+package alternativa.editor.export {
+ import alternativa.editor.prop.Prop;
+ import alternativa.editor.prop.Tile;
+ import alternativa.engine3d.core.Object3D;
+
+ import flash.filesystem.FileStream;
+
+ /**
+ * Сохраняет уровень в бинарном формате.
+ */
+ public class BinaryExporter extends FileExporter {
+
+ /**
+ *
+ */
+ public function BinaryExporter(root:Object3D) {
+ super(root);
+ }
+
+ /**
+ * @param stream
+ */
+ override public function exportToFileStream(stream:FileStream):void {
+ for (var child:* in root.children) {
+ var prop:Prop = child as Prop;
+ if (prop) {
+ stream.writeUTF(prop.library);
+ stream.writeUTF(prop.group);
+ stream.writeUTF(prop.name);
+ stream.writeFloat(prop.x);
+ stream.writeFloat(prop.y);
+ stream.writeFloat(prop.z);
+ stream.writeFloat(prop.rotationZ);
+ stream.writeBoolean(prop.free);
+ var tile:Tile = prop as Tile;
+ if (tile) {
+ stream.writeUTF(tile.textureName);
+// stream.writeBoolean(tile.isMirror);
+ } else {
+ stream.writeUTF("");
+// stream.writeBoolean(false);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/export/CollisionBox.as b/src/alternativa/editor/export/CollisionBox.as
new file mode 100644
index 0000000..8cf4380
--- /dev/null
+++ b/src/alternativa/editor/export/CollisionBox.as
@@ -0,0 +1,99 @@
+package alternativa.editor.export {
+
+ import alternativa.engine3d.alternativa3d;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+
+ import flash.geom.Vector3D;
+
+ use namespace alternativa3d;
+
+ /**
+ * Физический примитив, представляющий ориентированный бокс.
+ */
+ public class CollisionBox extends CollisionPrimitive {
+
+ // Размеры бокса вдоль локальных осей (x-ширина, y-длина, z-высота)
+ public var size:Point3D = new Point3D();
+
+ /**
+ *
+ * @param mesh
+ */
+ public function CollisionBox(mesh:Mesh = null) {
+ super(mesh);
+ }
+
+ /**
+ *
+ * @param mesh
+ */
+ override public function parse(mesh:Mesh):void {
+ // Поиск максимальных положительных координат по каждой из осей
+ var minX:Number = Number.MAX_VALUE;
+ var maxX:Number = -Number.MAX_VALUE;
+ var minY:Number = Number.MAX_VALUE;
+ var maxY:Number = -Number.MAX_VALUE;
+ var minZ:Number = Number.MAX_VALUE;
+ var maxZ:Number = -Number.MAX_VALUE;
+ for each (var v:Vertex in mesh._vertices) {
+ var p:Point3D = v._coords;
+ if (p.x < minX) minX = p.x;
+ if (p.x > maxX) maxX = p.x;
+
+ if (p.y < minY) minY = p.y;
+ if (p.y > maxY) maxY = p.y;
+
+ if (p.z < minZ) minZ = p.z;
+ if (p.z > maxZ) maxZ = p.z;
+ }
+ size.x = maxX - minX;
+ size.y = maxY - minY;
+ size.z = maxZ - minZ;
+
+ var midPoint:Point3D = new Point3D(0.5*(maxX + minX), 0.5*(maxY + minY), 0.5*(maxZ + minZ));
+
+ transform.toIdentity();
+ transform.rotate(mesh._rotationX, mesh._rotationY, mesh._rotationZ);
+ transform.translate(mesh._coords.x, mesh._coords.y, mesh._coords.z);
+ midPoint.transform(transform);
+
+ transform.d = midPoint.x;
+ transform.h = midPoint.y;
+ transform.l = midPoint.z;
+ }
+
+ /**
+ *
+ * @param parentTransform
+ * @return
+ */
+ override public function getXml(parentTransform:Matrix3D):XML {
+ var globalTransfrom:Matrix3D = transform.clone();
+ globalTransfrom.combine(parentTransform);
+ var angles:Point3D = globalTransfrom.getRotations();
+ var xml:XML =
+
+
+ {size.x}
+ {size.y}
+ {size.z}
+
+
+ {globalTransfrom.d}
+ {globalTransfrom.h}
+ {globalTransfrom.l}
+
+
+ {angles.x}
+ {angles.y}
+ {angles.z}
+
+ ;
+ return xml;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/export/CollisionPlane.as b/src/alternativa/editor/export/CollisionPlane.as
new file mode 100644
index 0000000..552ba46
--- /dev/null
+++ b/src/alternativa/editor/export/CollisionPlane.as
@@ -0,0 +1,102 @@
+package alternativa.editor.export {
+
+ import __AS3__.vec.Vector;
+
+ import alternativa.engine3d.alternativa3d;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+
+ use namespace alternativa3d;
+
+ /**
+ * Физический примитив, представляющий собой прямоугольник, лежащий в плоскости XY локальной системы координат.
+ * Нормаль прямоугольника направлена вдоль локальной оси Z.
+ */
+ public class CollisionPlane extends CollisionPrimitive {
+ // Ширина, размер по оси X
+ public var width:Number = 0;
+ // Длина, размер по оси Y
+ public var length:Number = 0;
+
+ /**
+ * @param mesh
+ */
+ public function CollisionPlane(mesh:Mesh = null) {
+ super(mesh);
+ }
+
+ /**
+ * Конструирует примитив из полигонального объекта. Объект должен содержать в себе ориентированный прямоугольник, состоящий из двух треугольников.
+ * @param mesh
+ */
+ override public function parse(mesh:Mesh):void {
+ var i:int;
+ // Подразумевается, что прямоугольник состоит из двух треугольников. Для определения параметров воспользуемся первым из них.
+ var face:Face = mesh._faces.peek() as Face;
+ // Найдём длины рёбер треугольника и индекс гипотенузы
+ var max:Number = -1;
+ var imax:int = 0;
+ var edges:Vector. = Vector.([new Point3D(), new Point3D(), new Point3D()]);
+ var lengths:Vector. = new Vector.(3);
+ for (i = 0; i < 3; i++) {
+ var edge:Point3D = edges[i];
+ edge.difference(face.vertices[(i + 1)%3]._coords, face.vertices[i]._coords);
+ var len:Number = lengths[i] = edge.length;
+ if (len > max) {
+ max = len;
+ imax = i;
+ }
+ }
+ // Выберем оси X и Y
+ var ix:int = (imax + 2)%3;
+ var iy:int = (imax + 1)%3;
+ var xAxis:Point3D = edges[ix];
+ var yAxis:Point3D = edges[iy];
+ yAxis.invert();
+ width = lengths[ix];
+ length = lengths[iy];
+ // Смещение локального начала координат
+ var trans:Point3D = face.vertices[(imax + 2)%3]._coords.clone();
+ trans.x += 0.5*(xAxis.x + yAxis.x);
+ trans.y += 0.5*(xAxis.y + yAxis.y);
+ trans.z += 0.5*(xAxis.z + yAxis.z);
+ // Оси локального базиса в родительской системе координат
+ xAxis.normalize();
+ yAxis.normalize();
+ var zAxis:Point3D = Point3D.cross(xAxis, yAxis);
+ // Матрица трансформации примитива в родительской системе координат
+ transform.setVectors(xAxis, yAxis, zAxis, trans);
+ transform.rotate(mesh._rotationX, mesh._rotationY, mesh._rotationZ);
+ transform.translate(mesh._coords.x, mesh._coords.y, mesh._coords.z);
+ }
+
+ /**
+ * @param parentTransform
+ * @return
+ */
+ override public function getXml(parentTransform:Matrix3D):XML {
+ var globalTransfrom:Matrix3D = transform.clone();
+ globalTransfrom.combine(parentTransform);
+ var angles:Point3D = globalTransfrom.getRotations();
+ var xml:XML =
+
+ {width}
+ {length}
+
+ {globalTransfrom.d}
+ {globalTransfrom.h}
+ {globalTransfrom.l}
+
+
+ {angles.x}
+ {angles.y}
+ {angles.z}
+
+ ;
+ return xml;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/export/CollisionPrimitive.as b/src/alternativa/editor/export/CollisionPrimitive.as
new file mode 100644
index 0000000..4e84f8c
--- /dev/null
+++ b/src/alternativa/editor/export/CollisionPrimitive.as
@@ -0,0 +1,37 @@
+package alternativa.editor.export {
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.types.Matrix3D;
+
+ /**
+ * Базовый класс для примитивов физической геометрии.
+ */
+ public class CollisionPrimitive {
+ // Трансформация примитива в системе координат пропа. Трансформация не должна содержать масштабирования.
+ public var transform:Matrix3D = new Matrix3D();
+
+ /**
+ * @param mesh
+ */
+ public function CollisionPrimitive(mesh:Mesh = null) {
+ if (mesh != null) parse(mesh);
+ }
+
+ /**
+ * Строит примитив на основе полигонального объекта.
+ * @param mesh
+ */
+ public function parse(mesh:Mesh):void {
+ }
+
+ /**
+ * Формирует представление примитива в формате XML с учётом трансформации родительского пропа.
+ *
+ * @param parentTransform трансформация родительского пропа
+ * @return представление примитива в виде XML
+ */
+ public function getXml(parentTransform:Matrix3D):XML {
+ return new XML();
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/export/CollisionPrimitivesCache.as b/src/alternativa/editor/export/CollisionPrimitivesCache.as
new file mode 100644
index 0000000..881c3ea
--- /dev/null
+++ b/src/alternativa/editor/export/CollisionPrimitivesCache.as
@@ -0,0 +1,56 @@
+package alternativa.editor.export {
+ import __AS3__.vec.Vector;
+
+ /**
+ * Кэш физических примитивов. Хранит наборы физических примтивов, индексированных значениями "имя библиотеки"-"имя группы"-"имя пропа".
+ */
+ public class CollisionPrimitivesCache {
+
+ private var cache:Object = {};
+
+ /**
+ *
+ */
+ public function CollisionPrimitivesCache() {
+ }
+
+ /**
+ * Добавляет список примитивов пропа в кэш.
+ *
+ * @param libName
+ * @param grpName
+ * @param propName
+ * @param prim
+ */
+ public function addPrimitives(libName:String, grpName:String, propName:String, primitives:Vector.):void {
+ var libCache:Object = cache[libName];
+ if (libCache == null) cache[libName] = libCache = {};
+ var grpCache:Object = libCache[grpName];
+ if (grpCache == null) libCache[grpName] = grpCache = {};
+ grpCache[propName] = primitives;
+ }
+
+ /**
+ * Возвращает кэшированный список примитив пропа или null в случае отсутствия списка.
+ *
+ * @param libName
+ * @param grpName
+ * @param propName
+ * @return
+ */
+ public function getPrimitives(libName:String, grpName:String, propName:String):Vector. {
+ var currCache:Object = cache[libName];
+ if (currCache == null) return null;
+ currCache = currCache[grpName];
+ return currCache != null ? currCache[propName] : null;
+ }
+
+ /**
+ * Очищает кэш.
+ */
+ public function clear():void {
+ cache = {};
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/export/FileExporter.as b/src/alternativa/editor/export/FileExporter.as
new file mode 100644
index 0000000..1de846d
--- /dev/null
+++ b/src/alternativa/editor/export/FileExporter.as
@@ -0,0 +1,30 @@
+package alternativa.editor.export {
+ import alternativa.engine3d.core.Object3D;
+
+ import flash.filesystem.FileStream;
+
+ /**
+ * Базовый класс для экспортёров данных.
+ */
+ public class FileExporter {
+
+ // Корневой объект сцены, в котором находятся пропы экспортируемого уровня
+ public var root:Object3D;
+
+ /**
+ *
+ * @param root
+ */
+ public function FileExporter(root:Object3D) {
+ this.root = root;
+ }
+
+ /**
+ * Метод сохраняет данные уровня в заданный файловый поток.
+ * @param stream
+ */
+ public function exportToFileStream(stream:FileStream):void {
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/export/TanksXmlExporter.as b/src/alternativa/editor/export/TanksXmlExporter.as
new file mode 100644
index 0000000..6c60c90
--- /dev/null
+++ b/src/alternativa/editor/export/TanksXmlExporter.as
@@ -0,0 +1,224 @@
+package alternativa.editor.export {
+ import __AS3__.vec.Vector;
+
+ import alternativa.editor.prop.Bonus;
+ import alternativa.editor.prop.Flag;
+ import alternativa.editor.prop.Prop;
+ import alternativa.editor.prop.Spawn;
+ import alternativa.editor.prop.Tile;
+ import alternativa.engine3d.alternativa3d;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+
+ import flash.filesystem.FileStream;
+
+ use namespace alternativa3d;
+
+ /**
+ * Экспортёр, сохраняющий уровень в формате XML, который используется для представления танкового уровня на сервере.
+ */
+ public class TanksXmlExporter extends FileExporter {
+
+ private var collPrimCache:CollisionPrimitivesCache;
+
+ /**
+ *
+ */
+ public function TanksXmlExporter(root:Object3D) {
+ super(root);
+ collPrimCache = new CollisionPrimitivesCache();
+ }
+
+ /**
+ *
+ * @param tile
+ * @return
+ */
+ private function getTileXml(tile:Tile):XML {
+// trace("export ", tile.free);
+ var xml:XML =
+
+
+ {tile.rotationZ}
+
+
+ {tile.x}
+ {tile.y}
+ {tile.z}
+
+ {tile.textureName}
+ ;
+ return xml;
+ }
+
+ /**
+ *
+ * @param stream
+ */
+ override public function exportToFileStream(stream:FileStream):void {
+ var xml:XML =
+ ;
+
+ var staticGeometry:XML = xml.child("static-geometry")[0];
+ var collisionGeometry:XML = xml.child("collision-geometry")[0];
+ var bonusRegions:XML = xml.child("bonus-regions")[0];
+ var spawnPoints:XML = xml.child("spawn-points")[0];
+ for (var child:* in root.children) {
+ var prop:Prop = child as Prop;
+ if (prop) {
+ switch (prop.type) {
+ case Prop.BONUS:
+ bonusRegions.appendChild(getBonusXml(prop as Bonus));
+ break;
+ case Prop.SPAWN:
+ spawnPoints.appendChild(getSpawnXml(prop as Spawn));
+ break;
+ case Prop.FLAG:
+ addCtfFlag(xml, prop as Flag);
+ break;
+ case Prop.TILE:
+ var tile:Tile = prop as Tile;
+ staticGeometry.appendChild(getTileXml(tile));
+ createTileCollisionXml(tile, collisionGeometry);
+ break;
+ }
+ }
+ }
+
+ stream.writeUTFBytes(xml.toXMLString());
+ }
+
+ /**
+ *
+ * @param mapXml
+ */
+ private function addCtfFlag(mapXml:XML, flag:Flag):void {
+ var flags:XMLList = mapXml.elements("ctf-flags");
+ var flagsElement:XML;
+ if (flags.length() == 0) {
+ flagsElement = ;
+ mapXml.appendChild(flagsElement);
+ } else {
+ flagsElement = flags[0];
+ }
+ var flagElement:XML;
+ switch (flag.name) {
+ case "red_flag":
+ flagElement =
+
+ {flag.x}
+ {flag.y}
+ {flag.z}
+ ;
+ break;
+ case "blue_flag":
+ flagElement =
+
+ {flag.x}
+ {flag.y}
+ {flag.z}
+ ;
+ break;
+ }
+ flagsElement.appendChild(flagElement);
+ }
+
+ /**
+ *
+ * @param tile
+ * @return
+ */
+ private function createTileCollisionXml(tile:Tile, parentElement:XML):void {
+ // Пробуем достать примитивы из кэша, если не удаётся, создаём набор для пропа и добавляем его в кэш
+ var primitives:Vector. = collPrimCache.getPrimitives(tile.library, tile.group, tile.name);
+ if (primitives == null) {
+ primitives = createPropCollisionPrimitives(tile);
+ collPrimCache.addPrimitives(tile.library, tile.group, tile.name, primitives);
+ }
+ // Записываем XML представления примитивов
+ for each (var p:CollisionPrimitive in primitives) parentElement.appendChild(p.getXml(tile._transformation));
+ }
+
+ /**
+ * Создаёт набор физической геометрии для пропа.
+ * @param tile
+ */
+ private function createPropCollisionPrimitives(tile:Tile):Vector. {
+ var primitives:Vector. = new Vector.();
+ for (var key:* in tile.collisionGeometry) {
+ var mesh:Mesh = key;
+ var meshName:String = mesh.name.toLowerCase();
+ if (meshName.indexOf("plane") == 0) primitives.push(new CollisionPlane(mesh));
+ else if (meshName.indexOf("box") == 0) primitives.push(new CollisionBox(mesh));
+ }
+ return primitives;
+ }
+
+ /**
+ *
+ * @param spawn
+ * @return
+ */
+ private function getSpawnXml(spawn:Spawn):XML {
+ var xml:XML =
+
+
+ {spawn.x}
+ {spawn.y}
+ {spawn.z}
+
+
+ {spawn.rotationZ}
+
+
+ return xml;
+ }
+
+ /**
+ *
+ * @param prop
+ * @return
+ */
+ private function getBonusXml(prop:Bonus):XML {
+
+ var xml:XML =
+
+
+ {prop.x}
+ {prop.y}
+ {prop.z}
+
+
+ {prop.rotationZ}
+
+
+ {prop.x + prop.distancesX.x}
+ {prop.y + prop.distancesY.x}
+ {prop.z + prop.distancesZ.x}
+
+
+ {prop.x + prop.distancesX.y}
+ {prop.y + prop.distancesY.y}
+ {prop.z + prop.distancesZ.y}
+
+ ;
+
+
+ for (var type:* in prop.types) {
+ xml.appendChild({type});
+ }
+
+ return xml;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/importLevel/BinaryImporter.as b/src/alternativa/editor/importLevel/BinaryImporter.as
new file mode 100644
index 0000000..36a935c
--- /dev/null
+++ b/src/alternativa/editor/importLevel/BinaryImporter.as
@@ -0,0 +1,69 @@
+package alternativa.editor.importLevel {
+ import alternativa.editor.LibraryManager;
+ import alternativa.editor.prop.Prop;
+ import alternativa.editor.prop.Tile;
+ import alternativa.editor.scene.MainScene;
+ import alternativa.types.Point3D;
+
+ import flash.filesystem.FileStream;
+
+ import mx.controls.Alert;
+
+ public class BinaryImporter extends FileImporter {
+
+ public function BinaryImporter(scene:MainScene, libraryManager:LibraryManager) {
+ super(scene, libraryManager);
+ }
+
+ override public function importFromFileStream(stream:FileStream):void {
+ try {
+ while (stream.bytesAvailable) {
+ // Проверка на то, что в предыдущей итерации не загружали библиотеку
+ if (libname == "") {
+ var lib:String = stream.readUTF();
+ // Составляем ключ: имя библиотеки + имя группы + имя меша
+ libname = lib + stream.readUTF() + stream.readUTF();
+ }
+ // Ищем проп по ключу
+ var prop:Prop = libraryManager.nameProp[libname];
+ if (prop) {
+ // Добавляем проп на сцену
+ prop = scene.addProp(prop, new Point3D(stream.readFloat(), stream.readFloat(), stream.readFloat()), stream.readFloat(), true, false);
+ var free:Boolean = stream.readBoolean();
+ if (!free) {
+ // Заполняем карту
+ scene.occupyMap.occupy(prop);
+ }
+ var textureName:String = stream.readUTF();
+// var isMirror:Boolean = stream.readBoolean();
+ var tile:Tile = prop as Tile;
+ if (tile) {
+ try {
+ if (textureName != "") {
+ tile.textureName = textureName;
+ }
+ } catch (err:Error) {
+ Alert.show("Tile " + tile.name + ": texture " + textureName + " is not found");
+ }
+
+// if (isMirror) {
+// tile.mirrorTexture();
+// }
+ }
+ libname = "";
+ scene.calculate();
+ } else {
+ Alert.show("Library '"+ lib + "' is used by the level. Load?", "", Alert.YES|Alert.NO, null, libAlertListener);
+ return;
+ }
+ }
+ } catch (err:Error) {
+ Alert.show(err.message);
+ }
+
+ endLoadLevel();
+
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/importLevel/FileImporter.as b/src/alternativa/editor/importLevel/FileImporter.as
new file mode 100644
index 0000000..92c73b9
--- /dev/null
+++ b/src/alternativa/editor/importLevel/FileImporter.as
@@ -0,0 +1,61 @@
+package alternativa.editor.importLevel {
+ import alternativa.editor.LibraryManager;
+ import alternativa.editor.events.LevelLoaded;
+ import alternativa.editor.scene.MainScene;
+
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.filesystem.FileStream;
+
+ import mx.controls.Alert;
+ import mx.events.CloseEvent;
+
+
+ public class FileImporter extends EventDispatcher {
+ protected var scene:MainScene;
+ protected var libraryManager:LibraryManager;
+ protected var libname:String = "";
+
+ public function FileImporter(scene:MainScene, libraryManager:LibraryManager) {
+ this.scene = scene;
+ this.libraryManager = libraryManager;
+ }
+
+ public function importFromFileStream(stream:FileStream):void {
+
+ }
+
+ /**
+ * Обработка алерта загрузки библиотеки.
+ */
+ protected function libAlertListener(e:CloseEvent):void {
+
+ switch (e.detail) {
+ case Alert.YES:
+// libraryManager.loadLibrary(loadingLevel);
+ break;
+ case Alert.NO:
+ scene.clear();
+ endLoadLevel();
+ break;
+ }
+
+ }
+
+ /**
+ * Конец загрузки уровня.
+ */
+ protected function endLoadLevel():void {
+// fileStream.close();
+ scene.changed = false;
+// emptyPath = false;
+// fileForSave = file.clone();
+// fileForSave.addEventListener(Event.SELECT, onSaveFileSelect);
+ libname = "";
+// progressBar.visible = false;
+// cursorScene.visible = true;
+// dispatchEvent(new LevelLoaded());
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/importLevel/TanksXmlImporter.as b/src/alternativa/editor/importLevel/TanksXmlImporter.as
new file mode 100644
index 0000000..d0641c8
--- /dev/null
+++ b/src/alternativa/editor/importLevel/TanksXmlImporter.as
@@ -0,0 +1,184 @@
+package alternativa.editor.importLevel {
+
+ import alternativa.editor.LibraryManager;
+ import alternativa.editor.prop.Bonus;
+ import alternativa.editor.prop.Prop;
+ import alternativa.editor.prop.Tile;
+ import alternativa.editor.prop.TileSprite3D;
+ import alternativa.editor.scene.MainScene;
+ import alternativa.types.Point3D;
+
+ import flash.filesystem.FileStream;
+
+ import mx.controls.Alert;
+
+ public class TanksXmlImporter extends FileImporter {
+
+ public function TanksXmlImporter(scene:MainScene, libraryManager:LibraryManager) {
+
+ super(scene, libraryManager);
+ }
+
+ override public function importFromFileStream(stream:FileStream):void {
+
+ var xml:XML = new XML(stream.readUTFBytes(stream.bytesAvailable));
+ stream.close();
+ try {
+ var staticGeometry:XML = xml.child("static-geometry")[0];
+ var bonusRegions:XML = xml.child("bonus-regions")[0];
+ var spawnPoints:XML = xml.child("spawn-points")[0];
+
+ if (staticGeometry) {
+ loadTiles(staticGeometry);
+ }
+ if (bonusRegions) {
+ loadBonuses(bonusRegions);
+ }
+ if (spawnPoints) {
+ loadSpawns(spawnPoints);
+ }
+
+ var flagList:XMLList = xml.child("ctf-flags");
+ if (flagList.length() > 0) loadFlags(flagList[0]);
+
+ } catch (err:Error) {
+ Alert.show(err.message);
+ }
+ endLoadLevel();
+
+
+ }
+
+ /**
+ *
+ * @param flags
+ */
+ private function loadFlags(flags:XML):void {
+ addFlag(flags.child("flag-red")[0], "red_flag");
+ addFlag(flags.child("flag-blue")[0], "blue_flag");
+ }
+
+ /**
+ *
+ * @param flagXml
+ * @param flagPropName
+ */
+ private function addFlag(flagXml:XML, flagPropName:String):void {
+ libname = "FunctionalFlags" + flagPropName;
+ var prop:Prop = libraryManager.nameProp[libname];
+ if (prop != null) {
+ // Добавляем проп на сцену
+ prop = scene.addProp(prop, new Point3D(Number(flagXml.x),Number(flagXml.y), Number(flagXml.z)), 0, true, false);
+ libname = "";
+ scene.calculate();
+ }
+ }
+
+ /**
+ *
+ * @param staticGeometry
+ */
+ private function loadTiles(staticGeometry:XML):void {
+ var tiles:XMLList = staticGeometry.child("prop");
+ for (var i:int = 0; i < tiles.length(); i++) {
+ var propXML:XML = tiles[i];
+ libname = propXML.attribute("library-name").toString() + propXML.attribute("group-name").toString() + propXML.attribute("name").toString();
+ var prop:Prop = libraryManager.nameProp[libname];
+ if (prop) {
+ var position:XML = propXML.child("position")[0];
+ var rotation:XML = propXML.child("rotation")[0];
+ // Добавляем проп на сцену
+ prop = scene.addProp(prop, new Point3D(Number(position.child("x")[0]),Number(position.child("y")[0]), Number(position.child("z")[0])), Number(rotation.child("z")[0]), true, false);
+ var free:Boolean = propXML.attribute("free").toString() == "true";
+// trace("import free", free);
+ if (!(free && prop is TileSprite3D)) {
+ // Заполняем карту
+ scene.occupyMap.occupy(prop);
+ }
+
+ var textureName:String = propXML.child("texture-name")[0];
+// var isMirror:Boolean = fileStream.readBoolean();
+ var tile:Tile = prop as Tile;
+ if (tile) {
+ try {
+ if (textureName != "") {
+ tile.textureName = textureName;
+ }
+ } catch (err:Error) {
+ Alert.show("Tile " + tile.name + ": texture " + textureName + " is not found");
+ }
+
+// if (isMirror) {
+// tile.mirrorTexture();
+// }
+ }
+ libname = "";
+ scene.calculate();
+// } else {
+// Alert.show("Library '"+ lib + "' is used by the level. Load?", "", Alert.YES|Alert.NO, this, libAlertListener);
+// return;
+// }
+// }
+
+
+ }
+ }
+ }
+
+ private function loadBonuses(bonusRegions:XML):void {
+ var bonuses:XMLList = bonusRegions.child("bonus-region");
+ for (var i:int = 0; i < bonuses.length(); i++) {
+ var bonusXML:XML = bonuses[i];
+ libname = "FunctionalBonus Regions" + bonusXML.attribute("name").toString();
+ var prop:Prop = libraryManager.nameProp[libname];
+ if (prop) {
+ var position:XML = bonusXML.child("position")[0];
+ var rotation:XML = bonusXML.child("rotation")[0];
+ // Добавляем проп на сцену
+ prop = scene.addProp(prop, new Point3D(Number(position.child("x")[0]),Number(position.child("y")[0]), Number(position.child("z")[0])), Number(rotation.child("z")[0]), true, false);
+ var free:Boolean = bonusXML.attribute("free");
+ if (!free) {
+ // Заполняем карту
+ scene.occupyMap.occupy(prop);
+ }
+ var bonusType:XMLList = bonusXML.child("bonus-type");
+
+ (prop as Bonus).types.clear();
+ for (var j:int = 0; j < bonusType.length(); j++) {
+// trace("j", bonusType[j].toString());
+ (prop as Bonus).types.add(bonusType[j].toString());
+ }
+ libname = "";
+ scene.calculate();
+ }
+ }
+ }
+
+ private function loadSpawns(spawnPoints:XML):void {
+ var spawns:XMLList = spawnPoints.child("spawn-point");
+ for (var i:int = 0; i < spawns.length(); i++) {
+ var spawnXML:XML = spawns[i];
+ libname = "FunctionalSpawn Points" + spawnXML.attribute("type").toString();
+ var prop:Prop = libraryManager.nameProp[libname];
+ if (prop) {
+ var position:XML = spawnXML.child("position")[0];
+ var rotation:XML = spawnXML.child("rotation")[0];
+ // Добавляем проп на сцену
+ prop = scene.addProp(prop, new Point3D(Number(position.child("x")[0]),Number(position.child("y")[0]), Number(position.child("z")[0])), Number(rotation.child("z")[0]), true, false);
+ var free:Boolean = spawnXML.attribute("free");;
+ if (!free) {
+ // Заполняем карту
+ scene.occupyMap.occupy(prop);
+ }
+
+ libname = "";
+ scene.calculate();
+ }
+ }
+ }
+
+
+
+
+ }
+}
diff --git a/src/alternativa/editor/prop/Bonus.as b/src/alternativa/editor/prop/Bonus.as
new file mode 100644
index 0000000..9fdfa13
--- /dev/null
+++ b/src/alternativa/editor/prop/Bonus.as
@@ -0,0 +1,38 @@
+package alternativa.editor.prop {
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.types.Set;
+
+ /**
+ * @author danilova
+ */
+ public class Bonus extends Prop {
+
+ public var types:Set;
+ public function Bonus(object:Object3D, library:String, group:String, needCalculate:Boolean=true) {
+ super(object, library, group, needCalculate);
+ types = new Set();
+ types.add("damageup");
+ }
+
+
+
+ override public function clone():Object3D {
+
+ var copyObject:Mesh = _object.clone() as Mesh;
+ copyObject.cloneMaterialToAllSurfaces(_material as TextureMaterial);
+ // Создаем проп
+ var copy:Bonus = new Bonus(copyObject, _library, _group, false);
+ // Копируем свойства
+ copy.distancesX = distancesX.clone();
+ copy.distancesY = distancesY.clone();
+ copy.distancesZ = distancesZ.clone();
+ copy._multi = _multi;
+ copy.name = name;
+ copy.types = types.clone();
+ return copy;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/prop/CustomFillMaterial.as b/src/alternativa/editor/prop/CustomFillMaterial.as
new file mode 100644
index 0000000..239bbbf
--- /dev/null
+++ b/src/alternativa/editor/prop/CustomFillMaterial.as
@@ -0,0 +1,45 @@
+package alternativa.editor.prop {
+ import alternativa.engine3d.alternativa3d;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.display.Skin;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.Material;
+ import alternativa.types.Point3D;
+ import alternativa.utils.ColorUtils;
+
+ use namespace alternativa3d;
+
+ public class CustomFillMaterial extends FillMaterial {
+
+ private var center:Point3D = new Point3D();
+ private var lightPoint:Point3D = new Point3D();
+ private var normal:Point3D = new Point3D();
+
+ public function CustomFillMaterial(lightPoint:Point3D, color:uint, alpha:Number=1, blendMode:String="normal", wireThickness:Number=-1, wireColor:uint=0) {
+ super(color, alpha, blendMode, wireThickness, wireColor);
+ this.lightPoint.copy(lightPoint);
+ }
+
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ var poly:PolyPrimitive = skin.primitive;
+ center.reset();
+ for (var i:int = 0; i < poly.num; i++) {
+ center.add(poly.points[i]);
+ }
+ center.multiply(1/poly.num);
+ normal.difference(lightPoint, center);
+ normal.normalize();
+ var c:uint = _color;
+ var k:Number = 0.5*(1 + normal.dot(poly.face.globalNormal));
+ _color = ColorUtils.multiply(_color, k);
+ super.draw(camera, skin, length, points);
+ _color = c;
+ }
+
+ override public function clone():Material {
+ return new CustomFillMaterial(lightPoint, color, alpha, blendMode, wireThickness, wireColor);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/prop/Flag.as b/src/alternativa/editor/prop/Flag.as
new file mode 100644
index 0000000..aeee25e
--- /dev/null
+++ b/src/alternativa/editor/prop/Flag.as
@@ -0,0 +1,52 @@
+package alternativa.editor.prop {
+ import alternativa.editor.scene.EditorScene;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.materials.TextureMaterial;
+
+ import flash.geom.Point;
+
+ /**
+ *
+ */
+ public class Flag extends Prop {
+
+ /**
+ * @param object
+ * @param library
+ * @param group
+ * @param needCalculate
+ */
+ public function Flag(object:Object3D, library:String, group:String, needCalculate:Boolean=true) {
+ super(object, library, group, needCalculate);
+ type = Prop.FLAG;
+ }
+
+ /**
+ *
+ */
+ override public function calculate():void {
+ distancesX = new Point(-EditorScene.hBase, EditorScene.hBase);
+ distancesY = new Point(-EditorScene.hBase, EditorScene.hBase);
+ distancesZ = new Point(0, EditorScene.vBase);
+ }
+
+ /**
+ *
+ */
+ override public function clone():Object3D {
+ var copyObject:Mesh = _object.clone() as Mesh;
+ copyObject.cloneMaterialToAllSurfaces(_material as TextureMaterial);
+ // Создаем проп
+ var copy:Flag = new Flag(copyObject, _library, _group, false);
+ // Копируем свойства
+ copy.distancesX = distancesX.clone();
+ copy.distancesY = distancesY.clone();
+ copy.distancesZ = distancesZ.clone();
+ copy._multi = _multi;
+ copy.name = name;
+ return copy;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/prop/Prop.as b/src/alternativa/editor/prop/Prop.as
new file mode 100644
index 0000000..e131e8b
--- /dev/null
+++ b/src/alternativa/editor/prop/Prop.as
@@ -0,0 +1,373 @@
+package alternativa.editor.prop {
+
+ import alternativa.editor.scene.EditorScene;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.events.MouseEvent3D;
+ import alternativa.engine3d.materials.Material;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.types.Map;
+ import alternativa.types.Point3D;
+ import alternativa.types.Texture;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.Bitmap;
+ import flash.display.BitmapData;
+ import flash.display.BlendMode;
+ import flash.geom.Matrix;
+ import flash.geom.Point;
+
+ /**
+ *
+ * @author danilova
+ */
+ public class Prop extends Object3D {
+
+ public static const TILE:int = 1;
+ public static const SPAWN:int = 2;
+ public static const BONUS:int = 3;
+ public static const FLAG:int = 4;
+
+ public var type:int = BONUS;
+
+ // Объект на сцене
+ protected var _object:Object3D;
+ // Группа
+ protected var _group:String;
+ // Библиотека
+ protected var _library:String;
+ // Расстояния от центра до крайних вершин по оси X
+ public var distancesX:Point;
+ // Расстояния от центра до крайних вершин по оси Y
+ public var distancesY:Point;
+ // Расстояния от центра до крайних вершин по оси Z
+ public var distancesZ:Point;
+ // Индикатор многоячеечности
+ protected var _multi:Boolean = false;
+ // Индикатор занесенности на карту
+ public var free:Boolean = true;
+ // Исходный материал
+ protected var _material:Material;
+ // Битмапдата текстуры исходного материала
+ public var bitmapData:BitmapData;
+ // Битмапдата выделенного пропа
+ protected var _selectBitmapData:BitmapData;
+ //
+ public var icon:Bitmap;
+ //
+ protected var _selected:Boolean = false;
+ //
+ private var matrix:Matrix = new Matrix();
+
+
+ [Embed (source = "red_cursor.jpg")] private static var redClass:Class;
+ private static const redBmp:BitmapData = new redClass().bitmapData;
+
+ public function Prop(object:Object3D, library:String, group:String, needCalculate:Boolean=true) {
+
+ super(object.name);
+ addChild(object);
+
+ _object = object;
+ _object.addEventListener(MouseEvent3D.MOUSE_DOWN, onMouseDown);
+ _library = library;
+ _group = group;
+
+ initBitmapData();
+
+ if (needCalculate) {
+ calculate();
+ }
+
+ }
+
+ /**
+ *
+ */
+ private function onMouseDown(e:MouseEvent3D):void {
+ e.object = this;
+ }
+
+ /**
+ *
+ */
+ protected function initBitmapData():void {
+ _material = (_object as Mesh).surfaces.peek().material;
+ bitmapData = (_material as TextureMaterial).texture.bitmapData;
+
+ }
+
+ /**
+ * Расчет расстояний от центра по всем осям.
+ */
+ public function calculate():void {
+
+ var vertices:Array = (_object as Mesh).vertices.toArray(true);
+ var maxZ:Number = 0;
+ var maxY:Number = 0;
+ var maxX:Number = 0;
+
+ var z1:Number = 0;
+ var z2:Number = 0;
+ var y1:Number = 0;
+ var y2:Number = 0;
+ var x1:Number = 0;
+ var x2:Number = 0;
+ var len:int = vertices.length;
+ for (var i:int = 0; i < len; i++) {
+ var vertex1:Point3D = vertices[i].coords;
+ if (scene) {
+ vertex1 = localToGlobal(vertex1);
+ }
+ for (var j:int = i+1; j < len; j++) {
+ var vertex2:Point3D = vertices[j].coords;
+ if (scene) {
+ vertex2 = localToGlobal(vertex2);
+ }
+ var dx:Number = (vertex1.x - vertex2.x);
+ var dy:Number = (vertex1.y - vertex2.y);
+ var dz:Number = (vertex1.z - vertex2.z);
+ var distanceX:Number = dx*dx;
+ var distanceY:Number = dy*dy;
+ var distanceZ:Number = dz*dz;
+
+ if (distanceX > maxX) {
+ maxX = distanceX;
+ x1 = vertex1.x;
+ x2 = vertex2.x;
+ }
+ if (distanceY > maxY) {
+ maxY = distanceY;
+ y1 = vertex1.y;
+ y2 = vertex2.y;
+ }
+ if (distanceZ > maxZ) {
+ maxZ = distanceZ;
+ z1 = vertex1.z;
+ z2 = vertex2.z;
+ }
+ }
+ }
+
+ distancesX = calcDistance(x, x1, x2, EditorScene.hBase);
+ distancesY = calcDistance(y, y1, y2, EditorScene.hBase);
+ distancesZ = calcDistance(z, z1, z2, EditorScene.vBase);
+
+ if (Math.abs(int(x2) - int(x1))/EditorScene.hBase2 > 1 ||
+ Math.abs(int(y1) - int(y2))/EditorScene.hBase2 > 1) {
+ _multi = true;
+ }
+
+ }
+
+ /**
+ * Расчет расстояния от точки центра до 1й и 2й точек, приведённых к сетке.
+ * @param centre точка центра
+ * @param value1 1я точка
+ * @param value2 2я точка
+ * @param base шаг сетки
+ * return расстояние от центра до меньшей точки со знаком "-", расстояние от центра до большей точки
+ */
+ private function calcDistance(centre:Number, value1:Number, value2:Number, base:Number):Point {
+
+ var distances:Point = new Point();
+
+ value2 = floorTo(value2, base);
+ value1 = floorTo(value1, base);
+
+ if (value2 == 0 && value1 == 0) {
+ distances.x = 0;
+ distances.y = base;
+ } else {
+ if (value2 > value1) {
+ if (value1 == 0) {
+ distances.x = 0;
+ distances.y = value2 - centre;
+ } else {
+ distances.x = value1 - centre;
+ distances.y = value2 - centre;
+ }
+ } else {
+ if (value2 == 0) {
+ distances.x = 0;
+ distances.y = value1 - centre;
+ } else {
+ distances.x = value2 - centre;
+ distances.y = value1 - centre;
+ }
+ }
+ }
+
+ return distances;
+ }
+
+ /**
+ * Привязка к узлу сетки.
+ * @param value округляемое значение
+ * @param base шаг сетки
+ * @return округленное значение
+ */
+ public static function floorTo(value:Number, base:Number):Number {
+
+ return Math.floor((value + base/2)/base)*base;
+
+ }
+
+ /**
+ * Привязка к центру ячейки.
+ * @param value округляемое значение
+ * @param base шаг сетки
+ * @return округленное значение
+ */
+ public static function roundTo(value:Number, base:Number):Number {
+
+ return Math.round((value + base/2)/base)*base - base/2;
+ }
+ /**
+ * Выделить проп.
+ */
+ public function select():void {
+
+ _selectBitmapData = bitmapData.clone();
+ matrix.a = bitmapData.width/redBmp.width;
+ matrix.d = matrix.a;
+ _selectBitmapData.draw(redBmp, matrix, null, BlendMode.MULTIPLY);
+ setMaterial(newSelectedMaterial);
+ _selected = true;
+ }
+
+ /**
+ * Снять выделение.
+ */
+ public function deselect():void {
+
+ _selectBitmapData.dispose();
+ setMaterial(_material);
+ _selected = false;
+ }
+
+ /**
+ * Создает материал для выделения пропа.
+ * @return новый материал
+ */
+ protected function get newSelectedMaterial():Material {
+ return new TextureMaterial(new Texture(_selectBitmapData));
+ }
+
+ /**
+ * Назначает пропу материал.
+ * @param material материал
+ */
+ public function setMaterial(material:Material):void {
+ var sm:SurfaceMaterial = material as SurfaceMaterial;
+ (_object as Mesh).cloneMaterialToAllSurfaces(sm);
+ }
+
+ /**
+ *
+ * @return
+ */
+ public function get multi():Boolean {
+ return _multi;
+ }
+
+ /**
+ *
+ * @return
+ */
+ public function get library():String {
+ return _library;
+ }
+
+ public function get selected():Boolean {
+ return _selected;
+ }
+ /**
+ *
+ * @return
+ */
+ public function get group():String {
+ return _group;
+ }
+
+ /**
+ *
+ * @return
+ */
+ public function get vertices():Map {
+ return (_object as Mesh).vertices;
+ }
+
+ /**
+ *
+ * @return
+ */
+ public function get material():Material {
+ return _material;
+ }
+
+ /**
+ * Поворот.
+ * @param plus флаг положительного поворота
+ */
+ public function rotate(plus:Boolean):void {
+
+ var point:Point;
+
+ if (plus) {
+ point = new Point(distancesX.x, distancesX.y);
+ distancesX.x = distancesY.x;
+ distancesX.y = distancesY.y;
+ distancesY.x = -point.y;
+ distancesY.y = -point.x;
+ rotationZ -= MathUtils.DEG90;
+ } else {
+ point = new Point(distancesY.x, distancesY.y);
+ distancesY.x = distancesX.x;
+ distancesY.y = distancesX.y;
+ distancesX.x = -point.y;
+ distancesX.y = -point.x;
+ rotationZ += MathUtils.DEG90;
+ }
+
+ }
+
+
+ override public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void {
+
+ _object.addEventListener(type, listener);
+ }
+
+ override public function clone():Object3D {
+ var copyObject:Mesh = _object.clone() as Mesh;
+ copyObject.cloneMaterialToAllSurfaces(_material as TextureMaterial);
+ var prop:Prop = new Prop(copyObject, _library, _group, false);
+ prop.distancesX = distancesX.clone();
+ prop.distancesY = distancesY.clone();
+ prop.distancesZ = distancesZ.clone();
+ prop._multi = _multi;
+ prop.name = name;
+ return prop;
+ }
+
+ /**
+ * Выравнивание по сетке.
+ */
+ public function snapCoords():void {
+
+ if (_multi) {
+ x = floorTo(x, EditorScene.hBase2);
+ y = floorTo(y, EditorScene.hBase2);
+ } else {
+ x = roundTo(x, EditorScene.hBase2);
+ y = roundTo(y, EditorScene.hBase2);
+ }
+
+ z = floorTo(z, EditorScene.vBase);
+ }
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/prop/Spawn.as b/src/alternativa/editor/prop/Spawn.as
new file mode 100644
index 0000000..129e31d
--- /dev/null
+++ b/src/alternativa/editor/prop/Spawn.as
@@ -0,0 +1,48 @@
+package alternativa.editor.prop {
+ import alternativa.editor.scene.EditorScene;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.utils.MathUtils;
+
+ import flash.geom.Point;
+
+ public class Spawn extends Prop {
+
+ public function Spawn(object:Object3D, library:String, group:String, needCalculate:Boolean=true) {
+ super(object, library, group, needCalculate);
+ type = Prop.SPAWN;
+ }
+
+ override public function calculate():void {
+
+ distancesX = new Point(-EditorScene.hBase, EditorScene.hBase);
+ distancesY = new Point(-EditorScene.hBase, EditorScene.hBase);
+ distancesZ = new Point(0, EditorScene.vBase);
+ }
+
+ override public function rotate(plus:Boolean):void {
+ if (plus) {
+ rotationZ += MathUtils.DEG5;
+ } else {
+ rotationZ -= MathUtils.DEG5;
+ }
+ }
+
+ override public function clone():Object3D {
+
+ var copyObject:Mesh = _object.clone() as Mesh;
+ copyObject.cloneMaterialToAllSurfaces(_material as TextureMaterial);
+ // Создаем проп
+ var copy:Spawn = new Spawn(copyObject, _library, _group, false);
+ // Копируем свойства
+ copy.distancesX = distancesX.clone();
+ copy.distancesY = distancesY.clone();
+ copy.distancesZ = distancesZ.clone();
+ copy._multi = _multi;
+ copy.name = name;
+ return copy;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/prop/Tile.as b/src/alternativa/editor/prop/Tile.as
new file mode 100644
index 0000000..d94151f
--- /dev/null
+++ b/src/alternativa/editor/prop/Tile.as
@@ -0,0 +1,142 @@
+package alternativa.editor.prop {
+
+ import alternativa.engine3d.alternativa3d;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.types.Map;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+ import alternativa.types.Texture;
+
+ import flash.display.BitmapData;
+ import flash.geom.Matrix;
+
+ use namespace alternativa3d;
+
+ /**
+ * @author danilova
+ */
+ public class Tile extends Prop {
+
+ private var collisionMaterial:CustomFillMaterial;
+
+ private var _isMirror:Boolean = false;
+
+ private var collisionBoxes:Set;
+ //
+ public var bitmaps:Map;
+ //
+ protected var _textureName:String = "";
+
+ public function Tile(object:Object3D, library:String, group:String, needCalculate:Boolean = true) {
+ super(object, library, group, needCalculate);
+ type = Prop.TILE;
+ collisionBoxes = new Set();
+ // Collision boxes
+ for (var child:* in object.children) {
+ var box:Mesh = child as Mesh;
+ box.cloneMaterialToAllSurfaces(null);
+ if (box.name.substr(0, 3) != "occ") {
+ collisionBoxes.add(box);
+ }
+ }
+ collisionMaterial = new CustomFillMaterial(new Point3D(-1e10, -0.7e10, 0.4e10), 0xFF7F7F);
+ }
+
+
+ /**
+ * Показать коллижн-боксы.
+ */
+ public function showCollisionBoxes():void {
+
+ for (var child:* in collisionBoxes) {
+ var box:Mesh = child as Mesh;
+ box.cloneMaterialToAllSurfaces(collisionMaterial);
+ }
+
+ setMaterial(null);
+
+ }
+
+ /**
+ * Скрыть коллижн-боксы.
+ */
+ public function hideCollisionBoxes():void {
+
+ for (var child:* in collisionBoxes) {
+ var box:Mesh = child as Mesh;
+ box.cloneMaterialToAllSurfaces(null);
+ }
+
+ setMaterial(_material);
+
+ }
+
+ public function get collisionGeometry():Set {
+ return collisionBoxes;
+ }
+
+ public function get textureName():String {
+ return _textureName;
+ }
+
+ public function set textureName(value:String):void {
+ _textureName = value;
+
+ bitmapData = _isMirror ? getMirrorBitmapData(bitmaps[value]) : bitmaps[value];
+ _material = new TextureMaterial(new Texture(bitmapData));
+ if (_selected) {
+ _selectBitmapData.dispose();
+ select();
+ } else {
+ setMaterial(_material);
+ }
+ }
+
+
+ private function getMirrorBitmapData(bmd:BitmapData):BitmapData {
+
+ var mirrorBmd:BitmapData = new BitmapData(bmd.width, bmd.height);
+ mirrorBmd.draw(bmd, new Matrix(-1, 0, 0, 1, bmd.width, 0 ));
+ return mirrorBmd;
+ }
+
+ public function mirrorTexture():void {
+
+ _isMirror = !_isMirror;
+
+ bitmapData = getMirrorBitmapData(bitmapData);
+ (_material as TextureMaterial).texture = new Texture(bitmapData);
+ if (selected) {
+ _selectBitmapData.dispose();
+ select();
+ } else {
+ setMaterial(_material);
+ }
+ }
+
+ public function get isMirror():Boolean {
+ return _isMirror;
+ }
+
+ override public function clone():Object3D {
+
+ var copyObject:Mesh = _object.clone() as Mesh;
+ copyObject.cloneMaterialToAllSurfaces(_material as TextureMaterial);
+ // Создаем проп
+ var copy:Tile = new Tile(copyObject, _library, _group, false);
+ // Копируем свойства
+ copy.distancesX = distancesX.clone();
+ copy.distancesY = distancesY.clone();
+ copy.distancesZ = distancesZ.clone();
+ copy._multi = _multi;
+ copy.name = name;
+ copy.bitmaps = bitmaps;
+ copy._textureName = _textureName;
+ return copy;
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/prop/TileSprite3D.as b/src/alternativa/editor/prop/TileSprite3D.as
new file mode 100644
index 0000000..6cd5a0d
--- /dev/null
+++ b/src/alternativa/editor/prop/TileSprite3D.as
@@ -0,0 +1,74 @@
+package alternativa.editor.prop {
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Sprite3D;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.engine3d.materials.Material;
+ import alternativa.engine3d.materials.SpriteTextureMaterial;
+ import alternativa.types.Map;
+ import alternativa.types.Texture;
+
+ import flash.geom.Point;
+
+ public class TileSprite3D extends Tile {
+ private var spriteTextureMaterial:SpriteTextureMaterial;
+
+ public function TileSprite3D(object:Sprite3D, library:String, group:String, needCalculate:Boolean=true) {
+ super(object, library, group, needCalculate);
+ }
+
+ public function get scale():Number {
+ return (_object as Sprite3D).scaleX;
+ }
+
+ override public function calculate():void {
+ distancesX = new Point();
+ distancesY = new Point();
+ distancesZ = new Point();
+ _multi = false;
+ }
+
+ override public function setMaterial(material:Material):void {
+ var spriteMaterial:SpriteTextureMaterial = material as SpriteTextureMaterial;
+ if (spriteMaterial) {
+ spriteMaterial.originX = spriteTextureMaterial.originX;
+ spriteMaterial.originY = spriteTextureMaterial.originY;
+ }
+ (_object as Sprite3D).material = spriteMaterial;
+
+ }
+
+ override protected function initBitmapData():void {
+ _material = (_object as Sprite3D).material;
+ spriteTextureMaterial = _material as SpriteTextureMaterial;
+ bitmapData = spriteTextureMaterial.texture.bitmapData;
+ }
+
+ override public function get vertices():Map {
+ var vertex:Vertex = new Vertex(0, 0, 0);
+ var map:Map = new Map();
+ map.add("1", vertex);
+ return map;
+ }
+
+ override protected function get newSelectedMaterial():Material {
+ var material:SpriteTextureMaterial = new SpriteTextureMaterial(new Texture(_selectBitmapData));
+ return material;
+ }
+
+ override public function clone():Object3D {
+
+ var copyObject:Sprite3D = _object.clone() as Sprite3D;
+ copyObject.material = _material.clone() as SpriteTextureMaterial;
+ // Создаем проп
+ var copy:TileSprite3D = new TileSprite3D(copyObject, _library, _group, false);
+ // Копируем свойства
+ copy.distancesX = distancesX.clone();
+ copy.distancesY = distancesY.clone();
+ copy.distancesZ = distancesZ.clone();
+ copy._multi = _multi;
+ copy.name = name;
+ copy.bitmaps = bitmaps;
+ return copy;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/scene/CursorScene.as b/src/alternativa/editor/scene/CursorScene.as
new file mode 100644
index 0000000..7366295
--- /dev/null
+++ b/src/alternativa/editor/scene/CursorScene.as
@@ -0,0 +1,321 @@
+package alternativa.editor.scene {
+
+ import alternativa.editor.prop.CustomFillMaterial;
+ import alternativa.editor.prop.Prop;
+ import alternativa.editor.prop.TileSprite3D;
+ import alternativa.engine3d.controllers.WalkController;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.display.View;
+ import alternativa.engine3d.materials.Material;
+ import alternativa.engine3d.materials.SpriteTextureMaterial;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+ import alternativa.types.Texture;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.BitmapData;
+ import flash.display.BlendMode;
+ import flash.display.DisplayObject;
+ import flash.display.Graphics;
+ import flash.display.Shape;
+ import flash.geom.Matrix;
+ import flash.geom.Point;
+
+ public class CursorScene extends EditorScene {
+ // Курсорный проп
+ protected var _object:Prop;
+ private var redMaterial:Material;
+ private var greenMaterial:Material;
+ private var material:Material;
+ // Индикатор свободного состояния ячейки, в которой находится курсор
+ private var _freeState:Boolean = true;
+ // Контроллер камеры
+ public var cameraController:WalkController;
+ // Контроллер для контейнера камеры
+ public var containerController:WalkController;
+ // Контейнер для камеры
+ public var container:Object3D;
+ // Индикатор вертикального перемещения
+ protected var verticalMoving:Boolean = false;
+ // stage
+ private var eventSourceObject:DisplayObject;
+ // Индикатор режима snap
+ protected var _snapMode:Boolean = true;
+ //
+ private var matrix:Matrix = new Matrix();
+
+ private var axisIndicatorOverlay:Shape;
+ private var axisIndicatorSize:Number = 30;
+
+ [Embed (source = "red_cursor.jpg")] private static var redClass:Class;
+ private static const redBmp:BitmapData = new redClass().bitmapData;
+ [Embed (source = "green_cursor.jpg")] private static var greenClass:Class;
+ private static const greenBmp:BitmapData = new greenClass().bitmapData;
+
+ public function CursorScene(eventSourceObject:DisplayObject) {
+ super();
+ this.eventSourceObject = eventSourceObject;
+ initControllers();
+ view.addChild(axisIndicatorOverlay = new Shape());
+ }
+
+ /**
+ *
+ */
+ private function initControllers():void {
+ // Подключение контроллера камеры
+ cameraController = new WalkController(eventSourceObject);
+ cameraController.object = camera;
+ cameraController.speedMultiplier = 4;
+ cameraController.speedThreshold = 1;
+ cameraController.mouseEnabled = false;
+
+ cameraController.coords = new Point3D(250, -7800, 4670);
+
+ // Контейнер
+ container = new Object3D();
+ root.addChild(container);
+ // Контроллер контейнера
+ containerController = new WalkController(eventSourceObject);
+ containerController.object = container;
+ containerController.mouseEnabled = false;
+ container.addChild(camera);
+ }
+
+ /**
+ * Установка курсора.
+ * @param value
+ *
+ */
+ public function set object(value:Prop):void {
+ var point3D:Point3D;
+ if (_object) {
+ point3D = _object.coords;
+ if (_visible) {
+ root.removeChild(_object);
+ }
+ }
+ _object = value;
+ material = _object.material.clone();
+ material.alpha = 0.5;
+
+ if (point3D) {
+ _object.coords = point3D;
+ }
+ if (_visible) {
+ root.addChild(_object);
+ }
+ if (_snapMode) {
+ snapObject();
+ }
+
+ }
+
+ /**
+ *
+ * @return
+ */
+ public function get object():Prop {
+ return _object;
+ }
+
+ /**
+ *
+ * @param value
+ */
+ public function set snapMode(value:Boolean):void {
+ if (_snapMode != value && _object) {
+
+ _snapMode = value;
+ if (value) {
+ snapObject();
+ } else {
+ _object.setMaterial(material);
+ }
+
+ }
+ }
+ /**
+ *
+ * @return
+ */
+ public function get snapMode():Boolean {
+ return _snapMode;
+ }
+
+ /**
+ *
+ */
+ private function snapObject():void {
+ createMaterials();
+ _object.snapCoords();
+
+ }
+
+ /**
+ * Cоздание зеленого и красного материалов.
+ */
+ private function createMaterials():void {
+
+ var redBmd:BitmapData = _object.bitmapData.clone();
+ var greenBmd:BitmapData = redBmd.clone();
+ matrix.a = redBmd.width/redBmp.width;
+ matrix.d = matrix.a;
+ redBmd.draw(redBmp, matrix, null, BlendMode.HARDLIGHT);
+ greenBmd.draw(greenBmp, matrix, null, BlendMode.HARDLIGHT);
+
+ if (_object is TileSprite3D) {
+ greenMaterial = new SpriteTextureMaterial(new Texture(greenBmd));
+ redMaterial = new SpriteTextureMaterial(new Texture(redBmd));
+
+ } else {
+// greenMaterial = new TextureMaterial(new Texture(greenBmd));
+// redMaterial = new TextureMaterial(new Texture(redBmd));
+ greenMaterial = new CustomFillMaterial(new Point3D(-1e10, -0.7e10, 0.4e10), 0x00FF00);
+ redMaterial = new CustomFillMaterial(new Point3D(-1e10, -0.7e10, 0.4e10), 0xFF0000);
+ }
+
+ redMaterial.alpha = greenMaterial.alpha = 0.8;
+
+ updateMaterial();
+ }
+
+
+ /**
+ * Перемещение курсора мышью.
+ */
+ public function moveCursorByMouse():void {
+ if (_object) {
+ var point:Point3D = view.projectViewPointToPlane(new Point(view.mouseX, view.mouseY), znormal, _object.z);
+ _object.x = point.x;
+ _object.y = point.y;
+ if (_snapMode) {
+ _object.snapCoords();
+ }
+ updateMaterial();
+
+ }
+ }
+
+
+ /**
+ *
+ * @return
+ */
+ public function get freeState():Boolean {
+ return _freeState;
+ }
+
+
+ /**
+ * Инициализация основной сцены.
+ */
+ override protected function initScene():void {
+ root = new Object3D();
+
+ // Добавление камеры и области вывода
+ camera = new Camera3D();
+ camera.rotationX = -MathUtils.DEG90 - MathUtils.DEG30;
+
+ view = new View(camera);
+ view.interactive = false;
+ view.mouseEnabled = false;
+ view.mouseChildren = false;
+
+ view.graphics.beginFill(0xFFFFFF);
+ view.graphics.drawRect(0, 0, 1, 1);
+ view.graphics.endFill();
+ }
+
+ /**
+ *
+ */
+ public function updateMaterial():void {
+ if (_object && _snapMode) {
+ if (occupyMap.isConflict(_object)) {
+ _freeState = false;
+ _object.setMaterial(redMaterial);
+ } else {
+ _freeState = true;
+ _object.setMaterial(greenMaterial);
+ }
+ }
+ }
+
+ public function clear():void {
+ if (_object) {
+ if (root.getChildByName(_object.name)) {
+ root.removeChild(_object);
+ }
+ _object = null;
+ _visible = false;
+
+ }
+ }
+
+ /**
+ * Рисование координатных осей.
+ * @param matrix
+ *
+ */
+ public function drawAxis(matrix:Matrix3D):void {
+ var gfx:Graphics = axisIndicatorOverlay.graphics;
+ var centreX:Number = axisIndicatorSize;
+ var centreY:Number = 0;
+ gfx.clear();
+ gfx.lineStyle(2, 0xFF0000);
+ gfx.moveTo(centreX, centreY);
+ gfx.lineTo(matrix.a*axisIndicatorSize + centreX, matrix.b*axisIndicatorSize + centreY);
+ gfx.lineStyle(2, 0x00FF00);
+ gfx.moveTo(centreX, centreY);
+ gfx.lineTo(matrix.e*axisIndicatorSize + centreX, matrix.f*axisIndicatorSize + centreY);
+ gfx.lineStyle(2, 0x0000FF);
+ gfx.moveTo(centreX, centreY);
+ gfx.lineTo(matrix.i*axisIndicatorSize + centreX, matrix.j*axisIndicatorSize + centreY);
+ }
+
+
+ private var _visible:Boolean = false;
+
+ public function set visible(value:Boolean):void {
+
+ if (value != _visible) {
+ _visible = value;
+ if (_object) {
+ if (_visible) {
+ root.addChild(_object);
+ updateMaterial();
+ } else {
+ root.removeChild(_object);
+ }
+ }
+ }
+ }
+
+ public function get visible():Boolean {
+ return _visible;
+
+ }
+
+ override public function moveByArrows(keyCode:uint, sector:int):void {
+ move(_object, keyCode, sector);
+ updateMaterial();
+ }
+
+ override public function viewResize(viewWidth:Number, viewHeight:Number):void {
+ super.viewResize(viewWidth, viewHeight);
+ axisIndicatorOverlay.y = view.height - axisIndicatorSize;
+ }
+
+ override public function rotateProps(plus:Boolean, props:Set = null):void {
+
+ props = new Set();
+ props.add(_object);
+ super.rotateProps(plus, props);
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/scene/EditorScene.as b/src/alternativa/editor/scene/EditorScene.as
new file mode 100644
index 0000000..70b17df
--- /dev/null
+++ b/src/alternativa/editor/scene/EditorScene.as
@@ -0,0 +1,248 @@
+package alternativa.editor.scene {
+
+ import alternativa.editor.prop.Prop;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Scene3D;
+ import alternativa.engine3d.display.View;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+ import alternativa.utils.KeyboardUtils;
+ import alternativa.utils.MathUtils;
+
+ import flash.geom.Point;
+
+ public class EditorScene extends Scene3D {
+ public var camera:Camera3D;
+ public var view:View;
+ // Карта занятых ячеек
+ public var occupyMap:OccupyMap;
+
+ public static const hBase:Number = 250;
+ public static const hBase2:Number = 2*hBase;
+ public static const vBase:Number = 300;
+
+ protected var znormal:Point3D = new Point3D(0, 0, 1);
+ protected var ynormal:Point3D = new Point3D(0, 1, 0);
+ protected var xnormal:Point3D = new Point3D(1, 0, 0);
+
+ /**
+ *
+ */
+ public function EditorScene() {
+ super();
+ // Инициализация основной сцены
+ initScene();
+ }
+
+ /**
+ * Корректировка размеров view.
+ * @param viewWidth ширина
+ * @param viewHeight высота
+ *
+ */
+ public function viewResize(viewWidth:Number, viewHeight:Number):void {
+
+ view.width = viewWidth;
+ view.height = viewHeight;
+ calculate();
+
+ }
+
+ /**
+ * Инициализация основной сцены.
+ */
+ protected function initScene():void {
+ root = new Object3D();
+
+ // Добавление камеры и области вывода
+ camera = new Camera3D();
+ camera.rotationX = -MathUtils.DEG90 - MathUtils.DEG30;
+ camera.coords = new Point3D(250, -7800, 4670);
+ root.addChild(camera);
+
+ view = new View(camera);
+ view.interactive = true;
+ view.buttonMode = true;
+ view.useHandCursor = false;
+
+ view.graphics.beginFill(0xFFFFFF);
+ view.graphics.drawRect(0, 0, 1, 1);
+ view.graphics.endFill();
+ }
+
+ /**
+ * Переопределяется наследниками.
+ * @param keyCode
+ * @param sector
+ */
+ public function moveByArrows(keyCode:uint, sector:int):void {
+
+ }
+
+ protected function move(prop:Prop, keyCode:uint, sector:int):void {
+
+ if (prop) {
+ switch (keyCode) {
+ case KeyboardUtils.UP:
+
+ switch (sector) {
+ case 1:
+ prop.x -= hBase2;
+ break;
+ case 4:
+ prop.y += hBase2;
+ break;
+ case 3:
+ prop.x += hBase2;
+ break;
+ case 2:
+ prop.y -= hBase2;
+ break;
+ }
+ break;
+ case KeyboardUtils.DOWN:
+ switch (sector) {
+ case 1:
+ prop.x += hBase2;
+ break;
+ case 4:
+ prop.y -= hBase2;
+ break;
+ case 3:
+ prop.x -= hBase2;
+ break;
+ case 2:
+ prop.y += hBase2;
+ break;
+ }
+
+ break;
+ case KeyboardUtils.LEFT:
+
+ switch (sector) {
+ case 1:
+ prop.y -= hBase2;
+ break;
+ case 4:
+ prop.x -= hBase2;
+ break;
+ case 3:
+ prop.y += hBase2;
+ break;
+ case 2:
+ prop.x += hBase2;
+ break;
+ }
+ break;
+ case KeyboardUtils.RIGHT:
+ switch (sector) {
+ case 1:
+ prop.y += hBase2;
+ break;
+ case 4:
+ prop.x += hBase2;
+ break;
+ case 3:
+ prop.y -= hBase2;
+ break;
+ case 2:
+ prop.x -= hBase2;
+ break;
+ }
+ break;
+
+ }
+
+ }
+ }
+
+ /**
+ * Вычисляет центр группы пропов.
+ * @param props
+ * @return
+ */
+ public function getCentrePropsGroup(props:Set = null):Point {
+
+ var minX:Number = Number.POSITIVE_INFINITY;
+ var maxX:Number = Number.NEGATIVE_INFINITY;
+ var minY:Number = Number.POSITIVE_INFINITY;
+ var maxY:Number = Number.NEGATIVE_INFINITY;
+ // Среднее арифметическое центров
+ var averageX:Number = 0;
+ var averageY:Number = 0;
+
+ for (var p:* in props) {
+ var prop:Prop = p;
+ var left:Number = prop.distancesX.x + prop.x;
+ var right:Number = prop.distancesX.y + prop.x;
+ if (left < minX) {
+ minX = left;
+ }
+ if (right > maxX) {
+ maxX = right;
+ }
+
+ left = prop.distancesY.x + prop.y;
+ right = prop.distancesY.y + prop.y;
+ if (left < minY) {
+ minY = left;
+ }
+ if (right > maxY) {
+ maxY = right;
+ }
+ averageX += prop.x;
+ averageY += prop.y;
+ }
+
+ // Проверяем протяженность по x и по y на четность
+ var modX:Number = (maxX - minX)/EditorScene.hBase2 % 2;
+ if ( modX != ((maxY - minY)/EditorScene.hBase2 % 2)) {
+
+ if (modX != 0) {
+ // Если протяженность по x нечетная, прибавим ширину ячейки со стороны, ближайшей к центру
+ averageX = averageX/props.length;
+ if (Math.abs(averageX - maxX) < Math.abs(averageX - minX)) {
+ maxX += EditorScene.hBase2;
+ } else {
+ minX -= EditorScene.hBase2;
+ }
+ } else {
+ averageY = averageY/props.length;
+ if (Math.abs(averageY - maxY) < Math.abs(averageY - minY)) {
+ maxY += EditorScene.hBase2;
+ } else {
+ minY -= EditorScene.hBase2;
+ }
+ }
+ }
+ return new Point((maxX + minX)/2, (maxY + minY)/2);
+ }
+ /**
+ * Поворот пропа.
+ * @param plus флаг положительного поворота
+ * @param prop
+ *
+ */
+ public function rotateProps(plus:Boolean, props:Set = null):void {
+
+ var centre:Point = getCentrePropsGroup(props);
+ for (var p:* in props) {
+ var prop:Prop = p;
+ var x:Number = prop.x;
+ var y:Number = prop.y;
+ if (plus) {
+ prop.x = y + centre.x - centre.y;
+ prop.y = -x + centre.y + centre.x;
+ } else {
+ prop.x = -y + centre.x + centre.y;
+ prop.y = x + centre.y - centre.x;
+ }
+ prop.rotate(plus);
+ }
+
+
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/scene/MainScene.as b/src/alternativa/editor/scene/MainScene.as
new file mode 100644
index 0000000..94bd80f
--- /dev/null
+++ b/src/alternativa/editor/scene/MainScene.as
@@ -0,0 +1,219 @@
+package alternativa.editor.scene {
+ import alternativa.editor.eventjournal.EventJournal;
+ import alternativa.editor.eventjournal.EventJournalItem;
+ import alternativa.editor.export.BinaryExporter;
+ import alternativa.editor.export.FileExporter;
+ import alternativa.editor.export.TanksXmlExporter;
+ import alternativa.editor.prop.Prop;
+ import alternativa.editor.prop.Tile;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.materials.WireMaterial;
+ import alternativa.engine3d.primitives.Plane;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ import flash.filesystem.FileStream;
+
+ /**
+ * Главная сцена.
+ * @author danilova
+ */
+ public class MainScene extends PropsScene {
+
+ public static const EXPORT_BINARY:int = 1;
+ public static const EXPORT_XML:int = 2;
+
+ // Сетка
+ private var grid:Plane;
+
+ private var exporters:Object = {};
+
+ /**
+ *
+ */
+ public function MainScene() {
+ super();
+
+ // Сетка
+ var count:int = 15;
+ var width:Number = count*hBase2;
+ grid = new Plane(width, width, count, count);
+ grid.cloneMaterialToAllSurfaces(new WireMaterial());
+ root.addChild(grid);
+ grid.x = hBase;
+ grid.y = hBase;
+ grid.mouseEnabled = false;
+
+ exporters[EXPORT_BINARY] = new BinaryExporter(root);
+ exporters[EXPORT_XML] = new TanksXmlExporter(root);
+ }
+
+ /**
+ *
+ * @param value
+ */
+ override public function set root(value:Object3D):void {
+ super.root = value;
+ for each (var exp:FileExporter in exporters) exp.root = value;
+ }
+
+ /**
+ *
+ * @param type
+ * @param stream
+ */
+ public function export(type:int, stream:FileStream):void {
+ (exporters[type] as FileExporter).exportToFileStream(stream);
+ _changed = false;
+ }
+
+ /**
+ * Перемещение пропов.
+ * @param props перемещаемые пропы
+ * @param delta смещение
+ */
+ public function moveProps(props:Set, delta:Point3D):void {
+
+ for (var p:* in props) {
+ var prop:Prop = p;
+ occupyMap.free(prop);
+ prop.x -= delta.x;
+ prop.y -= delta.y;
+ prop.z -= delta.z;
+ if (snapMode) {
+ prop.snapCoords();
+ occupyMap.occupy(prop);
+ }
+ }
+ }
+
+ /**
+ * Отмена действия.
+ * @param e отменяемое событие
+ */
+ public function undo(e:EventJournalItem):void {
+ var props:Set = e.props;
+ var p:*;
+
+ switch (e.operation) {
+ case EventJournal.ADD:
+ deleteProps(props);
+ break;
+ case EventJournal.COPY:
+ deleteProps(props);
+ break;
+ case EventJournal.DELETE:
+ for (p in props) {
+ var prop:Prop = p;
+ prop.deselect();
+ addProp(prop, prop.coords, prop.rotationZ, false);
+ }
+ break;
+ case EventJournal.MOVE:
+ moveProps(props, e.oldState);
+ (e.oldState as Point3D).multiply(-1);
+ break;
+ case EventJournal.ROTATE:
+ rotateProps(e.oldState, props);
+ e.oldState = !e.oldState;
+ break;
+ case EventJournal.CHANGE_TEXTURE:
+ break;
+ }
+ }
+
+ /**
+ * Возврат действия.
+ * @param e
+ */
+ public function redo(e:EventJournalItem):void {
+ var props:Set = e.props;
+ var prop:Prop;
+ var p:*;
+ switch (e.operation) {
+ case EventJournal.ADD:
+ prop = props.peek();
+ addProp(prop, prop.coords, prop.rotationZ, false);
+ break;
+ case EventJournal.COPY:
+ for (p in props) {
+ prop = p;
+ addProp(prop, prop.coords, prop.rotationZ, false);
+ }
+ break;
+ case EventJournal.DELETE:
+ deleteProps(props);
+ break;
+ case EventJournal.MOVE:
+ moveProps(props, e.oldState);
+ (e.oldState as Point3D).multiply(-1);
+ break;
+ case EventJournal.ROTATE:
+ rotateProps(e.oldState, props);
+ e.oldState = !e.oldState;
+ break;
+ case EventJournal.CHANGE_TEXTURE:
+ break;
+ }
+ }
+
+
+ /**
+ * Синхронизация с камерой главной сцены
+ * @param cameraCoords
+ * @param rotationX
+ * @param rotationY
+ * @param rotationZ
+ *
+ */
+ public function synchronize(cameraCoords:Point3D, rotationX:Number, rotationY:Number, rotationZ:Number):void {
+
+ camera.coords = cameraCoords;
+ camera.rotationX = rotationX;
+ camera.rotationY = rotationY;
+ camera.rotationZ = rotationZ;
+ }
+
+ /**
+ *
+ */
+ public function showCollisionBoxes():void {
+
+ for (var child:* in root.children) {
+ var tile:Tile = child as Tile;
+ if (tile) {
+ tile.showCollisionBoxes();
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ public function hideCollisionBoxes():void {
+
+ for (var child:* in root.children) {
+ var tile:Tile = child as Tile;
+ if (tile) {
+ tile.hideCollisionBoxes();
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ public function showGrid():void {
+ root.addChild(grid);
+ }
+
+ /**
+ *
+ */
+ public function hideGrid():void {
+ root.removeChild(grid);
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/scene/OccupyMap.as b/src/alternativa/editor/scene/OccupyMap.as
new file mode 100644
index 0000000..00d83b1
--- /dev/null
+++ b/src/alternativa/editor/scene/OccupyMap.as
@@ -0,0 +1,222 @@
+package alternativa.editor.scene {
+
+ import alternativa.editor.prop.Prop;
+ import alternativa.types.Map;
+ import alternativa.types.Set;
+
+ public class OccupyMap {
+ private var map:Map;
+
+ public function OccupyMap() {
+
+ map = new Map();
+ }
+
+ public function occupy(prop:Prop):void {
+ if (prop.free) {
+ var z1:Number = prop.distancesZ.x + prop.z;
+ var z2:Number = prop.distancesZ.y + prop.z;
+
+ var mapZ:Map = new Map();
+ for (var i:Number = z1; i < z2; i += EditorScene.vBase) {
+ mapZ.add(i, [prop]);
+ }
+
+ var y1:Number = prop.distancesY.x + prop.y;
+ var y2:Number = prop.distancesY.y + prop.y;
+
+ var setY:Set = new Set();
+ for (i = y1; i < y2; i += EditorScene.hBase) {
+ setY.add(i);
+ }
+
+ var x1:Number = prop.distancesX.x + prop.x;
+ var x2:Number = prop.distancesX.y + prop.x;
+
+ for (var x:Number = x1; x < x2; x += EditorScene.hBase) {
+ for (var y:Number = y1; y < y2; y += EditorScene.hBase) {
+ for (var z:Number = z1; z < z2; z += EditorScene.vBase) {
+ addElement(x, y, z, prop);
+ }
+ }
+
+ }
+ prop.free = false;
+ }
+
+ }
+
+ public function addElement(x:Number, y:Number, z:Number, prop:Prop):void {
+
+ var mapY:Map = map[x];
+ if (!mapY) {
+ mapY = new Map();
+ map[x] = mapY;
+ }
+ var mapZ:Map = mapY[y];
+ if (!mapZ) {
+ mapZ = new Map();
+ mapY[y] = mapZ;
+ }
+ var props:Array = mapZ[z];
+ if (!props) {
+ mapZ.add(z, [prop]);
+ } else {
+ props.push(prop);
+ }
+
+
+ }
+
+ public function free(prop:Prop):void {
+ if (!prop.free) {
+ var z1:Number = prop.distancesZ.x + prop.z;
+ var z2:Number = prop.distancesZ.y + prop.z;
+
+ var setZ:Set = new Set();
+ for (var i:Number = z1; i < z2; i += EditorScene.vBase) {
+ setZ.add(i);
+ }
+
+ var y1:Number = prop.distancesY.x + prop.y;
+ var y2:Number = prop.distancesY.y + prop.y;
+
+ var setY:Set = new Set();
+ for (i = y1; i < y2; i += EditorScene.hBase) {
+ setY.add(i);
+ }
+
+ var x1:Number = prop.distancesX.x + prop.x;
+ var x2:Number = prop.distancesX.y + prop.x;
+
+ for (i = x1; i < x2; i += EditorScene.hBase) {
+ var mapY:Map = map[i];
+ if (mapY) {
+ for (var cy:* in setY) {
+ var y:Number = cy;
+ var mapZ:Map = mapY[y];
+ if (mapZ) {
+ for (var cz:* in setZ) {
+ var z:Number = cz;
+ var arr:Array = mapZ[z];
+ if (arr) {
+ var index:int = arr.indexOf(prop);
+ if (index > -1) {
+ arr.splice(index, 1);
+ if (arr.length == 0) {
+ mapZ.remove(z);
+ }
+ }
+ }
+ }
+ if (mapZ.length == 0) {
+ mapY.remove(y);
+ }
+ }
+
+ }
+
+ if (mapY.length == 0) {
+ map.remove(i);
+ }
+ }
+ }
+ prop.free = true;
+ }
+
+ }
+
+ public function isOccupy(x:Number, y:Number, z:Number):Array {
+
+ var mapY:Map = map[x];
+ if (mapY) {
+ var mapZ:Map = mapY[y];
+ if (mapZ) {
+ if (mapZ.hasKey(z)) {
+ return mapZ[z];
+ }
+ }
+ }
+
+ return null;
+ }
+
+
+ public function clear():void {
+ map.clear();
+ }
+
+ public function isConflict(prop:Prop):Boolean {
+ var x1:Number = prop.distancesX.x + prop.x;
+ var x2:Number = prop.distancesX.y + prop.x;
+ for (var i:Number = x1; i < x2; i += EditorScene.hBase) {
+ var y1:Number = prop.distancesY.x + prop.y;
+ var y2:Number = prop.distancesY.y + prop.y;
+ for (var j:Number = y1; j < y2; j += EditorScene.hBase) {
+ var z1:Number = prop.distancesZ.x + prop.z;
+ var z2:Number = prop.distancesZ.y + prop.z;
+ for (var k:Number = z1; k < z2; k += EditorScene.vBase) {
+ var props:Array = isOccupy(i, j, k);
+
+ if (props && (props.indexOf(prop) == -1 || props.length > 1)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public function isConflictGroup(prop:Prop):Boolean {
+
+ var x1:Number = prop.distancesX.x + prop.x;
+ var x2:Number = prop.distancesX.y + prop.x;
+ for (var i:Number = x1; i < x2; i += EditorScene.hBase) {
+ var y1:Number = prop.distancesY.x + prop.y;
+ var y2:Number = prop.distancesY.y + prop.y;
+ for (var j:Number = y1; j < y2; j += EditorScene.hBase) {
+ var z1:Number = prop.distancesZ.x + prop.z;
+ var z2:Number = prop.distancesZ.y + prop.z;
+ for (var k:Number = z1; k < z2; k += EditorScene.vBase) {
+ var props:Array = isOccupy(i, j, k);
+ if (props) {
+ var len:int = props.length;
+ for (var p:int = 0; p < len; p++) {
+ var conflictProp:Prop = props[p];
+ if (conflictProp != prop && conflictProp.group == prop.group) {
+// trace('name', conflictProp.name, i, j, k);
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public function getConflictProps():Set {
+
+ var conflictProps:Set = new Set();
+ for (var x:* in map) {
+ var mapY:Map = map[x];
+ for (var y:* in mapY) {
+ var mapZ:Map = mapY[y];
+ for (var z:* in mapZ) {
+ var props:Array = mapZ[z];
+ if (props && props.length > 1) {
+ for (var i:int = 0; i < props.length; i++) {
+ conflictProps.add(props[i]);
+ }
+ }
+ }
+ }
+ }
+ return conflictProps;
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/alternativa/editor/scene/PropsScene.as b/src/alternativa/editor/scene/PropsScene.as
new file mode 100644
index 0000000..946c3cf
--- /dev/null
+++ b/src/alternativa/editor/scene/PropsScene.as
@@ -0,0 +1,700 @@
+package alternativa.editor.scene {
+ import alternativa.editor.prop.Bonus;
+ import alternativa.editor.prop.Prop;
+ import alternativa.editor.prop.Tile;
+ import alternativa.editor.prop.TileSprite3D;
+ import alternativa.engine3d.events.MouseEvent3D;
+ import alternativa.types.Map;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.Bitmap;
+ import flash.display.BitmapData;
+ import flash.events.Event;
+ import flash.geom.Point;
+ import flash.utils.getQualifiedClassName;
+
+ import gui.events.PropListEvent;
+
+ import mx.containers.HBox;
+ import mx.containers.Panel;
+ import mx.controls.CheckBox;
+
+ /**
+ * Главная сцена.
+ * @author danilova
+ */
+ public class PropsScene extends EditorScene {
+
+ public var selectedProp:Prop;
+ // Выделенные пропы
+ public var selectedProps:Set;
+ // Индикатор нажатия на проп
+ public var propMouseDown:Boolean = false;
+ // Индикатор изменений на сцене
+ protected var _changed:Boolean = false;
+ // Индикатор режима выравнивания по сетке
+ public var snapMode:Boolean = true;
+
+ private var _texturePanel:TexturePanel;
+ private var _propertyPanel:Panel;
+ private var bonusPanel:HBox;
+ private var checkTypeMap:Map;
+
+ private var currentBitmaps:Map;
+
+ protected var hideProps:Array = [];
+
+ public var allowSelectingTypes:Set = new Set();
+
+ public function PropsScene() {
+ super();
+ occupyMap = new OccupyMap();
+ selectedProps = new Set();
+ allowSelectingTypes.add("Tile");
+ allowSelectingTypes.add("TileSprite3D");
+ allowSelectingTypes.add("Spawn");
+ allowSelectingTypes.add("Prop");
+ allowSelectingTypes.add("Bonus");
+ allowSelectingTypes.add("Flag");
+
+
+ }
+
+ public function get isTexturePanel():Boolean {
+ return (_texturePanel.visible && _texturePanel.selectedItem);
+ }
+
+ /**
+ * @param value
+ */
+ public function set propertyPanel(value:Panel):void {
+ _propertyPanel = value;
+ createBonusTypePanel();
+ _texturePanel = new TexturePanel();
+ _texturePanel.addEventListener(PropListEvent.SELECT, onTexturePanelSelect);
+ _propertyPanel.addChild(_texturePanel);
+ _texturePanel.visible = false;
+ _propertyPanel.addChild(bonusPanel);
+ }
+
+
+ /**
+ *
+ */
+ public function createBonusTypePanel():void {
+ bonusPanel = new HBox();
+ bonusPanel.percentWidth = 100;
+ bonusPanel.visible = false;
+ checkTypeMap = new Map();
+
+ var damage:CheckBox = new CheckBox();
+ damage.label = "damageup";
+ damage.addEventListener(Event.CHANGE, onBonusTypeChange);
+ bonusPanel.addChild(damage);
+ checkTypeMap.add(damage.label, damage);
+
+ var armor:CheckBox = new CheckBox();
+ armor.label = "armorup";
+ armor.addEventListener(Event.CHANGE, onBonusTypeChange);
+ bonusPanel.addChild(armor);
+ checkTypeMap.add(armor.label, armor);
+
+ var nitro:CheckBox = new CheckBox();
+ nitro.label = "nitro";
+ nitro.addEventListener(Event.CHANGE, onBonusTypeChange);
+ bonusPanel.addChild(nitro);
+ checkTypeMap.add(nitro.label, nitro);
+
+ var repkit:CheckBox = new CheckBox();
+ repkit.label = "repkit";
+ repkit.addEventListener(Event.CHANGE, onBonusTypeChange);
+ bonusPanel.addChild(repkit);
+ checkTypeMap.add(repkit.label, repkit);
+
+ var check:CheckBox = new CheckBox();
+ check.label = "medkit";
+ check.addEventListener(Event.CHANGE, onBonusTypeChange);
+ bonusPanel.addChild(check);
+ checkTypeMap.add(check.label, check);
+
+ check = new CheckBox();
+ check.label = "money";
+ check.addEventListener(Event.CHANGE, onBonusTypeChange);
+ bonusPanel.addChild(check);
+ checkTypeMap.add(check.label, check);
+
+ check = new CheckBox();
+ check.label = "crystal";
+ check.addEventListener(Event.CHANGE, onBonusTypeChange);
+ bonusPanel.addChild(check);
+ checkTypeMap.add(check.label, check);
+
+ }
+
+ public function get changed():Boolean {
+ return _changed;
+ }
+
+ public function set changed(value:Boolean):void {
+ _changed = value;
+ }
+
+ private function getClassName(qualifiedClassName:String):String {
+
+ //alternativa.editor.prop::
+ return qualifiedClassName.substr(25);
+ }
+
+ private function isAllowClassName(prop:Prop):Boolean {
+
+ return allowSelectingTypes.has(getClassName(getQualifiedClassName(prop)));
+ }
+
+ /**
+ *
+ */
+ public function getCameraSector():int {
+
+ var sector:Number = camera.rotationZ/MathUtils.DEG90 % 4;
+ if ((sector >= -0.5 && sector <= 0.5) || (sector <= -3.5)) {
+ return 4;
+ } else if ((sector >= 0.5 && sector <= 1.5)
+ || (sector >= -3.5 && sector <= -2.5) ) {
+ return 1;
+ } else if ((sector >= 1.5 && sector <= 2.5) ||
+ (sector >= -2.5 && sector <= -1.5)) {
+ return 2;
+ } else {
+ return 3;
+ }
+
+ }
+
+ /**
+ * Перемещение выделенных пропов.
+ * @param verticalMoving индикатор вертикального движения
+ */
+ public function moveSelectedProps(verticalMoving:Boolean):void {
+
+ if (selectedProp) {
+ var viewPoint:Point = new Point(view.mouseX, view.mouseY);
+ var point:Point3D;
+ var p:*;
+
+ // Стираем с карты
+ for (p in selectedProps) {
+ occupyMap.free(p as Prop);
+ }
+
+ var deltaX:Number = 0;
+ var deltaY:Number = 0;
+ var deltaZ:Number = 0;
+
+ if (verticalMoving) {
+ var sector:Number = getCameraSector();
+
+ if (sector == 2 || sector == 4) {
+ point = view.projectViewPointToPlane(viewPoint, ynormal, selectedProp.y);
+ deltaX = point.x - selectedProp.x;
+ selectedProp.x = point.x;
+
+ } else {
+ point = view.projectViewPointToPlane(viewPoint, xnormal, selectedProp.x);
+ deltaY = point.y - selectedProp.y;
+ selectedProp.y = point.y;
+
+ }
+ deltaZ = point.z - selectedProp.z;
+ selectedProp.z = point.z;
+
+ } else {
+ point = view.projectViewPointToPlane(viewPoint, znormal, selectedProp.z);
+
+ deltaX = point.x - selectedProp.x;
+ deltaY = point.y - selectedProp.y;
+
+ selectedProp.x = point.x;
+ selectedProp.y = point.y;
+
+ }
+ // Смещаем все выделенные пропы
+ for (p in selectedProps) {
+ var prop:Prop = p;
+ if (prop != selectedProp) {
+ prop.x += deltaX;
+ prop.y += deltaY;
+ prop.z += deltaZ;
+ }
+ if (snapMode || (prop is Tile && !(prop is TileSprite3D))) {
+ prop.snapCoords();
+ occupyMap.occupy(prop);
+ }
+ }
+
+ }
+ }
+
+
+
+ /**
+ * Перемещение пропа стрелками.
+ * @param prop проп
+ * @param keyCode код стрелки
+ * @param sector сектор, на который смотрит камера
+ */
+ override public function moveByArrows(keyCode:uint, sector:int):void {
+
+ for (var p:* in selectedProps) {
+ var prop:Prop = p;
+ occupyMap.free(prop);
+ move(prop, keyCode, sector);
+ if (snapMode) {
+ prop.snapCoords();
+ occupyMap.occupy(prop);
+ }
+ }
+
+ }
+
+ /**
+ * Вертикальное перемещение.
+ * @param down
+ */
+ public function verticalMove(down:Boolean):void {
+ var delta:Number = vBase;
+ if (down) {
+ delta = -delta;
+ }
+
+ for (var p:* in selectedProps) {
+ var prop:Prop = p;
+ occupyMap.free(prop);
+ prop.z += delta;
+ if (snapMode) {
+ occupyMap.occupy(prop);
+ }
+ }
+
+ }
+
+
+ /**
+ * Клик на проп.
+ */
+ public function onPropMouseDown(e:MouseEvent3D):void {
+ if (!e.ctrlKey) {
+ var downProp:Prop = e.object as Prop;
+ if (isAllowClassName(downProp)) {
+ var selected:Boolean = downProp.selected;
+ if (e.shiftKey) {
+ if (e.altKey) {
+ if (selected) {
+ deselectProp(downProp);
+ }
+ } else {
+ if (!selected) {
+ selectProp(downProp);
+ }
+ }
+ } else {
+
+ if (!selected) {
+ deselectProps();
+ selectProp(downProp);
+ } else {
+ selectedProp = downProp;
+ }
+ }
+
+ propMouseDown = true;
+ }
+
+ }
+
+ }
+
+ private function onPropMouseOut(e:MouseEvent3D):void {
+
+ view.useHandCursor = false;
+ }
+
+
+ private function onPropMouseOver(e:MouseEvent3D):void {
+
+ view.useHandCursor = true;
+ }
+
+
+ /**
+ * Отменяет выделение пропов.
+ */
+ public function deselectProps():void {
+ for (var p:* in selectedProps) {
+ (p as Prop).deselect();
+ }
+ selectedProps.clear();
+ selectedProp = null;
+
+ bonusPanel.visible = false;
+ _texturePanel.visible = false;
+
+ }
+
+ public function deselectProp(prop:Prop):void {
+
+ prop.deselect();
+ selectedProps.remove(prop);
+ if (prop == selectedProp) {
+ selectedProp = null;
+ }
+
+// if (_texturePanel.visible && !noConflictBitmaps()) {
+// _texturePanel.visible = false;
+// } else
+ bonusPanel.visible = oneBonusSelected();
+ _texturePanel.visible = !bonusPanel.visible && noConflictBitmaps();
+ }
+
+ /**
+ * Выделение пропа.
+ * @param prop
+ */
+ public function selectProps(props:Set):void {
+
+ deselectProps();
+
+ for (var p:* in props) {
+ var prop:Prop = p;
+ if (isAllowClassName(prop)) {
+ prop.select();
+ selectedProps.add(prop);
+ selectedProp = prop;
+ }
+
+ }
+
+ showPropertyPanel();
+
+ }
+
+ /**
+ * Выделить конфликтующие пропы.
+ */
+ public function selectConflictProps():void {
+ selectProps(occupyMap.getConflictProps());
+ }
+
+ public function selectProp(prop:Prop):void {
+ if (isAllowClassName(prop)) {
+ prop.select();
+ selectedProps.add(prop);
+ selectedProp = prop;
+ showPropertyPanel();
+ }
+ }
+
+ public function getPropsUnderRect(point:Point, dx:Number, dy:Number, select:Boolean):Set {
+ var result:Set = new Set();
+ for (var child:* in root.children) {
+ var prop:Prop = child as Prop;
+ if (prop && isAllowClassName(prop)) {
+ var view_coords:Point3D = view.projectPoint(prop.coords);
+ if (view_coords.x >= point.x && view_coords.x <= point.x + dx
+ && view_coords.y >= point.y && view_coords.y <= point.y + dy) {
+ if (select) {
+ if (!prop.selected) {
+ prop.select();
+ }
+ } else {
+
+ if (prop.selected) {
+ prop.deselect();
+ }
+ }
+ result.add(prop);
+ }
+ }
+ }
+ return result;
+
+ }
+
+
+ /**
+ * Создание пропа.
+ * @param sourceProp прототип
+ */
+ public function addProp(sourceProp:Prop, coords:Point3D, rotation:Number, copy:Boolean = true, addToMap:Boolean = true):Prop {
+ var prop:Prop;
+ if (copy) {
+ prop = sourceProp.clone() as Prop;
+ prop.rotationZ = rotation;
+ } else {
+ prop = sourceProp;
+ }
+
+ root.addChild(prop);
+
+ if (rotation != 0 && copy) {
+ // Расчитывать надо после добавления на сцену
+ prop.calculate();
+ }
+
+ // Определяем координаты
+ prop.x = coords.x;
+ prop.y = coords.y;
+ prop.z = coords.z;
+
+
+ prop.addEventListener(MouseEvent3D.MOUSE_DOWN, onPropMouseDown);
+ prop.addEventListener(MouseEvent3D.MOUSE_OUT, onPropMouseOut);
+ prop.addEventListener(MouseEvent3D.MOUSE_OVER, onPropMouseOver);
+ _changed = true;
+
+ if (snapMode && addToMap) {
+ occupyMap.occupy(prop);
+ }
+ return prop;
+
+ }
+
+ /**
+ * Удаление пропа.
+ * @param prop
+ * @return проп
+ */
+ public function deleteProps(props:Set = null):Set {
+
+ if (!props) {
+ props = selectedProps;
+ selectedProp = null;
+ }
+
+ if (props) {
+ var result:Set = props.clone();
+ for (var p:* in props) {
+ var prop:Prop = p;
+ root.removeChild(prop);
+ occupyMap.free(prop);
+ if (selectedProps.has(prop)) {
+ deselectProp(prop);
+ }
+ }
+
+// _propertyPanel.enabled = false;
+ bonusPanel.visible = false;
+ _texturePanel.visible = false;
+ propMouseDown = false;
+ _changed = true;
+ }
+
+ return result;
+ }
+
+
+
+ /**
+ * Очистка сцены.
+ */
+ public function clear():void {
+
+ for (var child:* in root.children) {
+ var prop:Prop = child as Prop;
+ if (prop) {
+ root.removeChild(prop);
+ }
+ }
+ selectedProp = null;
+ selectedProps.clear();
+ occupyMap.clear();
+ view.interactive = true;
+
+ }
+
+
+ /**
+ * Смена текстуры.
+ */
+ public function onTexturePanelSelect(e:PropListEvent = null):void {
+
+ for (var p:* in selectedProps) {
+ var tile:Tile = p;
+ if (tile && tile.bitmaps) {
+ tile.textureName = _texturePanel.selectedItem;
+ }
+ }
+
+ }
+
+ private function onBonusTypeChange(e:Event):void {
+
+ var check:CheckBox = e.target as CheckBox;
+ for (var p:* in selectedProps) {
+ var bonus:Bonus = p;
+ if (check.selected) {
+ bonus.types.add(check.label);
+ } else {
+ bonus.types.remove(check.label);
+ }
+
+ }
+
+
+ }
+
+ private function noConflictBitmaps():Map {
+
+ var bitmaps:Map;
+ for (var p:* in selectedProps) {
+ var tile:Tile = p as Tile;
+ if (tile && tile.bitmaps) {
+ if (!bitmaps) {
+ bitmaps = tile.bitmaps;
+ } else {
+ if (bitmaps != tile.bitmaps) {
+ return null;
+ }
+
+ }
+ }
+ }
+
+ return bitmaps;
+ }
+
+ private function oneBonusSelected():Boolean {
+
+ if (selectedProps.length > 1) {
+ return false;
+ }
+
+ var bonus:Bonus = selectedProps.peek() as Bonus;
+ if (!bonus) {
+ return false;
+ }
+
+ return true;
+
+ }
+ public function showPropertyPanel():void {
+
+ bonusPanel.visible = oneBonusSelected();
+ if (bonusPanel.visible) {
+ var types:Set = (selectedProps.peek() as Bonus).types;
+
+ for (var type:* in checkTypeMap) {
+ (checkTypeMap[type] as CheckBox).selected = types.has(type);
+ }
+
+ } else {
+ var bitmaps:Map = noConflictBitmaps();
+ if (bitmaps) {
+ _texturePanel.visible = true;
+
+ if (bitmaps != currentBitmaps) {
+ _texturePanel.deleteAllProps();
+ _texturePanel.selectedItem = null;
+ for (var key:* in bitmaps) {
+ var bitmapData:BitmapData = bitmaps[key];
+ var bitmap:Bitmap = new Bitmap(bitmapData);
+ _texturePanel.addItem(key, bitmap, key);
+ }
+ currentBitmaps = bitmaps;
+ }
+ }
+
+// _texturePanel.visible = true;
+ }
+
+ }
+
+ public function mirrorTextures():void {
+ for (var p:* in selectedProps) {
+ var tile:Tile = p as Tile;
+ if (tile != null) tile.mirrorTexture();
+ }
+ }
+
+ /**
+ *
+ */
+ public function hideSelectedProps():void {
+
+ var props:Set = selectedProps.clone();
+ deselectProps();
+ for (var p:* in props) {
+ var prop:Prop = p;
+ hideProps.push(prop);
+ if (!prop.free) {
+ occupyMap.free(prop);
+ prop.free = true;
+ }
+ root.removeChild(prop);
+ calculate();
+ }
+ }
+
+ /**
+ *
+ */
+ public function showAll():void {
+ var len:int = hideProps.length;
+ for (var i:int = 0; i < len; i++) {
+ var prop:Prop = hideProps[i];
+ root.addChild(prop);
+ if (prop.free) {
+ occupyMap.occupy(prop);
+ }
+ calculate();
+ }
+
+ hideProps.length = 0;
+ }
+
+
+
+ override public function getCentrePropsGroup(props:Set = null):Point {
+ if (!props) {
+ props = selectedProps;
+ }
+
+ return super.getCentrePropsGroup(props);
+
+ }
+
+ override public function rotateProps(plus:Boolean, props:Set = null):void {
+ if (!props) {
+ props = selectedProps;
+ }
+
+ var centre:Point = getCentrePropsGroup(props);
+
+ for (var p:* in props) {
+ var prop:Prop = p;
+ occupyMap.free(prop);
+
+ var x:Number = prop.x;
+ var y:Number = prop.y;
+ if (plus) {
+ prop.x = y + centre.x - centre.y;
+ prop.y = -x + centre.y + centre.x;
+ } else {
+ prop.x = -y + centre.x + centre.y;
+ prop.y = x + centre.y - centre.x;
+ }
+
+ prop.rotate(plus);
+ if (snapMode) {
+ prop.snapCoords();
+ occupyMap.occupy(prop);
+ }
+ }
+
+ _changed = true;
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/green_cursor.jpg b/src/green_cursor.jpg
new file mode 100644
index 0000000..6089636
Binary files /dev/null and b/src/green_cursor.jpg differ
diff --git a/src/gui/events/PropListEvent.as b/src/gui/events/PropListEvent.as
new file mode 100644
index 0000000..3546519
--- /dev/null
+++ b/src/gui/events/PropListEvent.as
@@ -0,0 +1,36 @@
+package gui.events {
+
+ import flash.events.Event;
+
+ /**
+ * @author Michael
+ */
+ public class PropListEvent extends Event {
+ public static const SELECT:String = "select";
+ private var _selectedIndex:int;
+ private var _selectedItem:*;
+
+ public function PropListEvent(index:int, item:*) {
+ super(PropListEvent.SELECT, false, false);
+ _selectedIndex = index;
+ _selectedItem = item;
+ }
+
+ public function get selectedIndex():int {
+ return _selectedIndex;
+ }
+
+ public function get selectedItem():* {
+ return _selectedItem;
+ }
+
+ override public function toString():String {
+ return formatToString("ListEvents", "type", "bubbles", "cancelable", "selectedIndex", "selectedItem");
+ }
+
+ override public function clone():Event {
+ return new PropListEvent(_selectedIndex, _selectedItem);
+ }
+
+ }
+}
diff --git a/src/icons/editor_boxes_icon.png b/src/icons/editor_boxes_icon.png
new file mode 100644
index 0000000..4ea6f6b
Binary files /dev/null and b/src/icons/editor_boxes_icon.png differ
diff --git a/src/icons/editor_boxes_icon_on.png b/src/icons/editor_boxes_icon_on.png
new file mode 100644
index 0000000..d0b0967
Binary files /dev/null and b/src/icons/editor_boxes_icon_on.png differ
diff --git a/src/icons/editor_grid_icon.png b/src/icons/editor_grid_icon.png
new file mode 100644
index 0000000..bd4e138
Binary files /dev/null and b/src/icons/editor_grid_icon.png differ
diff --git a/src/icons/editor_grid_icon_on.png b/src/icons/editor_grid_icon_on.png
new file mode 100644
index 0000000..772ed95
Binary files /dev/null and b/src/icons/editor_grid_icon_on.png differ
diff --git a/src/icons/editor_snap_icon.png b/src/icons/editor_snap_icon.png
new file mode 100644
index 0000000..6b6477f
Binary files /dev/null and b/src/icons/editor_snap_icon.png differ
diff --git a/src/icons/editor_snap_icon_on.png b/src/icons/editor_snap_icon_on.png
new file mode 100644
index 0000000..b0c52ee
Binary files /dev/null and b/src/icons/editor_snap_icon_on.png differ
diff --git a/src/icons/editor_textures_icon.png b/src/icons/editor_textures_icon.png
new file mode 100644
index 0000000..ec34600
Binary files /dev/null and b/src/icons/editor_textures_icon.png differ
diff --git a/src/icons/editor_textures_icon_on.png b/src/icons/editor_textures_icon_on.png
new file mode 100644
index 0000000..9268ebd
Binary files /dev/null and b/src/icons/editor_textures_icon_on.png differ
diff --git a/src/icons/icon_hide_selected.png b/src/icons/icon_hide_selected.png
new file mode 100644
index 0000000..218257e
Binary files /dev/null and b/src/icons/icon_hide_selected.png differ
diff --git a/src/icons/icon_show_all.png b/src/icons/icon_show_all.png
new file mode 100644
index 0000000..8d3c243
Binary files /dev/null and b/src/icons/icon_show_all.png differ
diff --git a/src/red_cursor.jpg b/src/red_cursor.jpg
new file mode 100644
index 0000000..87c61e5
Binary files /dev/null and b/src/red_cursor.jpg differ