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