mirror of
https://github.com/MapMakersAndProgrammers/alternativa3d-archive.git
synced 2025-10-26 01:49:05 -07:00
541 lines
17 KiB
Plaintext
541 lines
17 KiB
Plaintext
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);
|
||
}
|
||
|
||
|
||
}
|
||
}
|