package com.alternativagame.engine3d { import com.alternativagame.engine3d.object.Object3D; import com.alternativagame.engine3d.skin.Skin; import com.alternativagame.type.RGB; import com.alternativagame.type.Set; import com.alternativagame.type.Vector; import flash.display.DisplayObject; import flash.display.DisplayObjectContainer; import flash.display.Shape; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.ColorTransform; import flash.geom.Point; import flash.geom.Rectangle; import flash.utils.clearTimeout; import flash.utils.setTimeout; use namespace engine3d; public class View3D extends Sprite { use namespace engine3d; // Корневой объект private var _object:Object3D = null; // Область отрисовки спрайтов private var canvas:Sprite; private var canvasCoords:Vector; // Список скинов на изменение глубины private var skinsToDepth:Array; // Список скинов на перепозиционирование private var skinsToPosition:Set; // Список скинов на отрисовку private var skinsToDraw:Set; // Список скинов на освещение private var skinsToLight:Set; // Размеры окна камеры private var _width:uint; private var _height:uint; // Флаг ограничения окна камеры private var _crop:Boolean = false; // Координаты камеры относительно начала координат private var _targetX:Number = 0; private var _targetY:Number = 0; private var _targetZ:Number = 0; // Повороты камеры private var _pitch:Number = 0; private var _roll:Number = 0; private var _yaw:Number = 0; // Степень увеличения объектов private var _zoom:Number = 1; // Трансформация камеры engine3d var transformation:Matrix3D; engine3d var inverseTransformation:Matrix3D; // Изменилась точка обзора камеры engine3d var positionChanged:Boolean = true; // Изменился угол обзора или масштаб engine3d var geometryChanged:Boolean = true; // Флаг заморозки камеры private var _hold:Boolean = false; // Текущий нажатый объект private var pressedObject:Object3D; public function View3D(width:uint, height:uint) { hitArea = new Sprite(); hitArea.mouseEnabled = false; hitArea.visible = false; with (hitArea.graphics) { beginFill(0); drawRect(0, 0, 100, 100); } addChild(hitArea); canvas = new Sprite(); canvas.mouseEnabled = false; canvas.mouseChildren = false; addChild(canvas); canvasCoords = new Vector(); this.width = width; this.height = height; skinsToDepth = new Array(); skinsToPosition = new Set(); skinsToDraw = new Set(); skinsToLight = new Set(); addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); addEventListener(MouseEvent.MOUSE_UP, onMouseUp); } private function onMouseDown(e:MouseEvent):void { dispatchEvent3D(Event3D.DOWN, e.ctrlKey, e.altKey, e.shiftKey); } private function onMouseUp(e:MouseEvent):void { dispatchEvent3D(Event3D.UP, e.ctrlKey, e.altKey, e.shiftKey); } private function dispatchEvent3D(type:String, ctrlKey:Boolean, altKey:Boolean, shiftKey:Boolean):void { var mouse:Point = new Point(stage.mouseX, stage.mouseY); var skin:Skin = getSkinFromPoint(mouse); // Если нажали на интерактивный скин if (skin != null && skin.interactive) { // При нажатии сохраняем нажатый объект if (type == Event3D.DOWN) { pressedObject = skin.object; } var click:Boolean = (type == Event3D.UP && pressedObject == skin.object); // Получаем пересечение вектора мыши со скином var canvasCoords:Vector = skin.getIntersectionCoords(canvas.globalToLocal(mouse)); // Формируем ветку объектов var objectList:Array = skin.object.getBranch(); // Перевести точку в мировые координаты var worldCoords:Vector = Math3D.vectorTransform(canvasCoords, inverseTransformation); // Рассчитываем точку в координатах каждого из родительских объектах и формируем список var coordsList:Array = new Array(); var objectMatrix:Matrix3D; var objectCoords:Vector = worldCoords.clone(); var currentObject:Object3D; // Перебираем список объектов с конца (с корневого объекта) var i:int; for (i = objectList.length - 1; i >= 0; i--) { currentObject = objectList[i]; // Трансформируем точку через матрицу в локальные координаты текущего объекта objectCoords = Math3D.vectorTransform(objectCoords, currentObject.inverseTransform); coordsList[i] = objectCoords.clone(); } // Рассылаем события от объектов for (i = 0; i < objectList.length; i++) { currentObject = objectList[i]; currentObject.dispatchEvent(new Event3D(type, ctrlKey, altKey, shiftKey, skin.object, skin.polygon, skin.material, canvasCoords, objectCoords, coordsList[i])); // Если отжали на нажатом объекте, то отправить ещё и клик if (click) { currentObject.dispatchEvent(new Event3D(Event3D.CLICK, ctrlKey, altKey, shiftKey, skin.object, skin.polygon, skin.material, canvasCoords, objectCoords, coordsList[i])); } } // Отослать событие от камеры dispatchEvent(new Event3D(type, ctrlKey, altKey, shiftKey, skin.object, skin.polygon, skin.material, canvasCoords, objectCoords, worldCoords)); // Если отжали на нажатом объекте, то отправить ещё и клик if (click) { dispatchEvent(new Event3D(Event3D.CLICK, ctrlKey, altKey, shiftKey, skin.object, skin.polygon, skin.material, canvasCoords, objectCoords, worldCoords)); } } else { // При нажатии на пустое место сбрасываем нажатый объект if (type == Event3D.DOWN) { pressedObject = null; } // Рассылаем пустое событие dispatchEvent(new Event3D(type, ctrlKey, altKey, shiftKey)); // Если нажатый объект также был пуст, то отправить клик на пустое место if (type == Event3D.UP && pressedObject == null) { dispatchEvent(new Event3D(Event3D.CLICK, ctrlKey, altKey, shiftKey)); } } } // Получить скин по заданным координатам public function getSkinFromPoint(point:Point):Skin { // Получаем список объектов под координатой var objectList:Array = getObjectsUnderPoint(point); // Оставить в списке только скины var skinList:Array = new Array(); var len:uint = objectList.length; for (var i:uint = 0; i < len; i++) { if (objectList[i] is Skin) { skinList.push(objectList[i]); } } // Сортируем их по глубине skinList.sortOn("sortDepth", Array.NUMERIC); // Возвращаем самый близкий return skinList[0]; } // Заморозить изображение камеры public function hold():void { _hold = true; canvas.cacheAsBitmap = true; } // Заморозить изображение камеры public function unhold():void { _hold = false; canvas.cacheAsBitmap = false; } // Перерисовать объекты в камере public function draw():void { if (object != null) { // Если изменилась геометрия if (geometryChanged) { // Пересчитать трансформацию transformation = new Matrix3D(); Math3D.rotateZMatrix(transformation, -_yaw); Math3D.rotateYMatrix(transformation, -_roll); Math3D.rotateXMatrix(transformation, -_pitch); Math3D.scaleMatrix(transformation, _zoom, _zoom, _zoom); } // Если изменилась позиция if (geometryChanged || positionChanged) { // Передвигаем всю область скинов canvasCoords = Math3D.vectorTransform(new Vector(-_targetX, -_targetY, -_targetZ), transformation); // Пересчитать инверсную трансформацию var inv:Matrix3D = new Matrix3D(_targetX, _targetY, _targetZ, _pitch, _roll, _yaw, 1/_zoom, 1/_zoom, 1/_zoom); inv.d += inv.a*canvasCoords.x + inv.b*canvasCoords.y + inv.c*canvasCoords.z; inv.h += inv.e*canvasCoords.x + inv.f*canvasCoords.y + inv.g*canvasCoords.z; inv.l += inv.i*canvasCoords.x + inv.j*canvasCoords.y + inv.k*canvasCoords.z; inverseTransformation = inv; canvas.x = width/2 + canvasCoords.x; canvas.y = height/2 - canvasCoords.z; } geometryChanged = false; positionChanged = false; // Если камера не заморожена if (!_hold) { // Расчитываем трансформацию дерева объектов object.calculateTransform(); // Расчитать освещение дерева объектов object.calculateLight(); if (skinsToDepth.length > 0 || skinsToPosition.length > 0 || skinsToDraw.length > 0 || skinsToLight.length > 0) { trace(skinsToDepth.length, skinsToDraw.length, skinsToPosition.length, skinsToLight.length); } // Сортируем глубины sortDepths(); var skin:Skin; // Позиционируем скины for each (skin in skinsToPosition) { skin.position(); } skinsToPosition = new Set(); // Отрисовываем скины for each (skin in skinsToDraw) { skin.draw(); } skinsToDraw = new Set(); // Освещаем скины for each (skin in skinsToLight) { skin.light(); } skinsToLight = new Set(); } } } // Запуск стирания скина private function clearSkin(skin:Skin, index:int, arr:Array):void { canvas.removeChild(DisplayObject(skin)); } // Сортировка глубин скинов private function sortDepths():void { // Убираем скины из списка skinsToDepth.forEach(clearSkin); // Сортируем скины по глубине skinsToDepth.sortOn("sortDepth", Array.NUMERIC | Array.DESCENDING); // Вставляем скины на нужные глубины var ma:int = -1; var mb:int = (canvas.numChildren > 0) ? canvas.numChildren : 0; var side:Boolean = false; var skin:Skin; var a:int; var b:int; var c:int; var len:uint = skinsToDepth.length; for (var i:uint = 0; i < len; i++) { skin = (side) ? skinsToDepth.pop() : skinsToDepth.shift(); a = ma; b = mb; while (a < b - 1) { c = (a + b) >>> 1; (skin.sortDepth >= (canvas.getChildAt(c) as Skin).sortDepth) ? (b = c) : (a = c); } canvas.addChildAt(DisplayObject(skin), b); (side) ? (mb = b) : (ma = b); mb++; side = !side; } } // Добавить скин в список изменения глубин engine3d function addToDepth(skin:Skin):void { if (skinsToDepth.indexOf(skin) < 0) skinsToDepth.push(skin); } // Добавить скин в список репозиционированных в следующий раз engine3d function addToPosition(skin:Skin):void { skinsToPosition.add(skin); } // Добавить скин в список отрисовываемых в следующий раз engine3d function addToDraw(skin:Skin):void { skinsToDraw.add(skin); } // Добавить скин в список освещаемых в следующий раз engine3d function addToLight(skin:Skin):void { skinsToLight.add(skin); } // Добавить скин engine3d function addSkin(skin:Skin):void { canvas.addChild(DisplayObject(skin)); } // Убрать скин engine3d function removeSkin(skin:Skin):void { // Удаляем скин из камеры canvas.removeChild(DisplayObject(skin)); // Удаляем из списка на сортировку var i:int = skinsToDepth.indexOf(skin); if (i>=0) skinsToDepth.splice(i,1); // Удаляем из список на отрисовку, позиционирование и освещение skinsToPosition.remove(skin); skinsToDraw.remove(skin); skinsToLight.remove(skin); } // Указать корневой объект public function set object(value:Object3D):void { // Если есть текущий объект if (object != null) { // Снимаем у него камеру object.setView(null); } // Если устанавливаем не пустой объект if (value != null) { // Если объект был в другой камере и был там корневым if (value.view != null && value === value.view.object) { // Снимаем у той камеры объект value.view.object = null; } else { // Если объект был в другом объекте if (value.parent != null) { // Удалить его оттуда value.parent.detach(value); } } // Указываем объектам камеру value.setView(this); } _object = value; } public function get object():Object3D { return _object; } public function get targetX():Number { return _targetX; } public function get targetY():Number { return _targetY; } public function get targetZ():Number { return _targetZ; } public function get zoom():Number { return _zoom; } public function get pitch():Number { return _pitch; } public function get roll():Number { return _roll; } public function get yaw():Number { return _yaw; } public function set targetX(value:Number):void { _targetX = value; positionChanged = true; } public function set targetY(value:Number):void { _targetY = value; positionChanged = true; } public function set targetZ(value:Number):void { _targetZ = value; positionChanged = true; } public function set pitch(value:Number):void { _pitch = value; setGeometryChanged(); } public function set roll(value:Number):void { _roll = value; setGeometryChanged(); } public function set yaw(value:Number):void { _yaw = value; setGeometryChanged(); } public function set zoom(value:Number):void { _zoom = value; setGeometryChanged(); } engine3d function setGeometryChanged():void { // Изменить геометрию у объекта и его потомков if (!geometryChanged && object != null) { object.setGeometryChanged(); geometryChanged = true; } } override public function set width(value:Number):void { _width = value; hitArea.width = _width; canvas.x = _width/2 + canvasCoords.x; if (crop) { scrollRect = new Rectangle(0, 0, _width, height); } } override public function get width():Number { return _width; } override public function set height(value:Number):void { _height = value; hitArea.height = _height; canvas.y = _height/2 - canvasCoords.z; if (crop) { scrollRect = new Rectangle(0, 0, width, _height); } } override public function get height():Number { return _height; } public function set crop(value:Boolean):void { _crop = value; if (value) { scrollRect = new Rectangle(0, 0, width, height); } else { scrollRect = null; } } public function get crop():Boolean { return _crop; } public function get mouseCanvasCoords():Point { var res:Point = null; if (stage != null) { res = canvas.globalToLocal(new Point(stage.mouseX, stage.mouseY)); } return res; } public function canvasToView(coords:Vector):Vector { return Math3D.vectorAdd(coords, canvasCoords); } public function viewToCanvas(coords:Vector):Vector { return Math3D.vectorSub(coords, canvasCoords); } } }