From c58621fb99aeb7ae050dac22727ae42912c73b61 Mon Sep 17 00:00:00 2001
From: Pyogenics Направление камеры совпадает с её локальной осью Z, поэтому только что созданная камера смотрит вверх в системе
+ * координат родителя.
+ *
+ * Для отображения видимой через камеру части сцены на экран, к камере должна быть подключёна область вывода —
+ * экземпляр класса Масштабирование, ориентация и положение объекта задаются в родительской системе координат. Результирующая
+ * локальная трансформация является композицией операций масштабирования, поворотов объекта относительно осей
+ * Глобальная трансформация (в системе координат корневого объекта сцены) является композицией трансформаций
+ * самого объекта и всех его предков по иерархии объектов сцены.
+ */
+ public class Object3D {
+ // Операции
+ /**
+ * @private
+ * Поворот или масштабирование
+ */
+ alternativa3d var changeRotationOrScaleOperation:Operation = new Operation("changeRotationOrScale", this);
+ /**
+ * @private
+ * Перемещение
+ */
+ alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this);
+ /**
+ * @private
+ * Расчёт матрицы трансформации
+ */
+ alternativa3d var calculateTransformationOperation:Operation = new Operation("calculateTransformation", this, calculateTransformation, Operation.OBJECT_CALCULATE_TRANSFORMATION);
+ /**
+ * @private
+ * Изменение уровеня мобильности
+ */
+ alternativa3d var calculateMobilityOperation:Operation = new Operation("calculateMobility", this, calculateMobility, Operation.OBJECT_CALCULATE_MOBILITY);
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ /**
+ * @private
+ * Наименование
+ */
+ alternativa3d var _name:String;
+ /**
+ * @private
+ * Сцена
+ */
+ alternativa3d var _scene:Scene3D;
+ /**
+ * @private
+ * Родительский объект
+ */
+ alternativa3d var _parent:Object3D;
+ /**
+ * @private
+ * Дочерние объекты
+ */
+ alternativa3d var _children:Set = new Set();
+ /**
+ * @private
+ * Уровень мобильности
+ */
+ alternativa3d var _mobility:int = 0;
+ /**
+ * @private
+ */
+ alternativa3d var inheritedMobility:int;
+ /**
+ * @private
+ * Координаты объекта относительно родителя
+ */
+ alternativa3d var _coords:Point3D = new Point3D();
+ /**
+ * @private
+ * Поворот объекта по оси X относительно родителя. Угол измеряется в радианах.
+ */
+ alternativa3d var _rotationX:Number = 0;
+ /**
+ * @private
+ * Поворот объекта по оси Y относительно родителя. Угол измеряется в радианах.
+ */
+ alternativa3d var _rotationY:Number = 0;
+ /**
+ * @private
+ * Поворот объекта по оси Z относительно родителя. Угол измеряется в радианах.
+ */
+ alternativa3d var _rotationZ:Number = 0;
+ /**
+ * @private
+ * Мастшаб объекта по оси X относительно родителя
+ */
+ alternativa3d var _scaleX:Number = 1;
+ /**
+ * @private
+ * Мастшаб объекта по оси Y относительно родителя
+ */
+ alternativa3d var _scaleY:Number = 1;
+ /**
+ * @private
+ * Мастшаб объекта по оси Z относительно родителя
+ */
+ alternativa3d var _scaleZ:Number = 1;
+ /**
+ * @private
+ * Полная матрица трансформации, переводящая координаты из локальной системы координат объекта в систему координат сцены
+ */
+ alternativa3d var transformation:Matrix3D = new Matrix3D();
+ /**
+ * @private
+ * Координаты в сцене
+ */
+ alternativa3d var globalCoords:Point3D = new Point3D();
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param name имя экземпляра
+ */
+ public function Object3D(name:String = null) {
+ // Имя по-умолчанию
+ _name = (name != null) ? name : defaultName();
+
+ // Последствия операций
+ changeRotationOrScaleOperation.addSequel(calculateTransformationOperation);
+ changeCoordsOperation.addSequel(calculateTransformationOperation);
+ }
+
+ /**
+ * @private
+ * Расчёт трансформации
+ */
+ private function calculateTransformation():void {
+ if (changeRotationOrScaleOperation.queued) {
+ // Если полная трансформация
+ transformation.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ);
+ if (_parent != null) {
+ transformation.combine(_parent.transformation);
+ }
+ // Сохраняем глобальные координаты объекта
+ globalCoords.x = transformation.d;
+ globalCoords.y = transformation.h;
+ globalCoords.z = transformation.l;
+ } else {
+ // Если только перемещение
+ globalCoords.copy(_coords);
+ if (_parent != null) {
+ globalCoords.transform(_parent.transformation);
+ }
+ transformation.offset(globalCoords.x, globalCoords.y, globalCoords.z);
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт общей мобильности
+ */
+ private function calculateMobility():void {
+ inheritedMobility = ((_parent != null) ? _parent.inheritedMobility : 0) + _mobility;
+ }
+
+ /**
+ * Добавление дочернего объекта. Добавляемый объект удаляется из списка детей предыдущего родителя.
+ * Новой сценой дочернего объекта становится сцена родителя.
+ *
+ * @param child добавляемый объект
+ *
+ * @throws alternativa.engine3d.errors.Object3DHierarchyError нарушение иерархии объектов сцены
+ */
+ public function addChild(child:Object3D):void {
+
+ // Проверка на null
+ if (child == null) {
+ throw new Object3DHierarchyError(null, this);
+ }
+
+ // Проверка на наличие
+ if (child._parent == this) {
+ return;
+ }
+
+ // Проверка на добавление к самому себе
+ if (child == this) {
+ throw new Object3DHierarchyError(this, this);
+ }
+
+ // Проверка на добавление родительского объекта
+ if (child._scene == _scene) {
+ // Если объект был в той же сцене, либо оба не были в сцене
+ var parentObject:Object3D = _parent;
+ while (parentObject != null) {
+ if (child == parentObject) {
+ throw new Object3DHierarchyError(child, this);
+ return;
+ }
+ parentObject = parentObject._parent;
+ }
+ }
+
+ // Если объект был в другом объекте
+ if (child._parent != null) {
+ // Удалить его оттуда
+ child._parent._children.remove(child);
+ } else {
+ // Если объект был корневым в сцене
+ if (child._scene != null && child._scene._root == child) {
+ child._scene.root = null;
+ }
+ }
+
+ // Добавляем в список
+ _children.add(child);
+ // Указываем себя как родителя
+ child.setParent(this);
+ // Устанавливаем уровни
+ child.setLevel((calculateTransformationOperation.priority & 0xFFFFFF) + 1);
+ // Указываем сцену
+ child.setScene(_scene);
+ }
+
+ /**
+ * Удаление дочернего объекта.
+ *
+ * @param child удаляемый дочерний объект
+ *
+ * @throws alternativa.engine3d.errors.Object3DNotFoundError указанный объект не содержится в списке детей текущего объекта
+ */
+ public function removeChild(child:Object3D):void {
+ // Проверка на null
+ if (child == null) {
+ throw new Object3DNotFoundError(null, this);
+ }
+ // Проверка на наличие
+ if (child._parent != this) {
+ throw new Object3DNotFoundError(child, this);
+ }
+ // Убираем из списка
+ _children.remove(child);
+ // Удаляем ссылку на родителя
+ child.setParent(null);
+ // Удаляем ссылку на сцену
+ child.setScene(null);
+ }
+
+ /**
+ * @private
+ * Установка родительского объекта.
+ *
+ * @param value родительский объект
+ */
+ alternativa3d function setParent(value:Object3D):void {
+ // Отписываемся от сигналов старого родителя
+ if (_parent != null) {
+ removeParentSequels();
+ }
+ // Сохранить родителя
+ _parent = value;
+ // Если устанавливаем родителя
+ if (value != null) {
+ // Подписка на сигналы родителя
+ addParentSequels();
+ }
+ }
+
+ /**
+ * @private
+ * Установка новой сцены для объекта.
+ *
+ * @param value сцена
+ */
+ alternativa3d function setScene(value:Scene3D):void {
+ if (_scene != value) {
+ // Если была сцена
+ if (_scene != null) {
+ // Удалиться из сцены
+ removeFromScene(_scene);
+ }
+ // Если новая сцена
+ if (value != null) {
+ // Добавиться на сцену
+ addToScene(value);
+ }
+ // Сохранить сцену
+ _scene = value;
+ } else {
+ // Посылаем операцию трансформации
+ addOperationToScene(changeRotationOrScaleOperation);
+ // Посылаем операцию пересчёта мобильности
+ addOperationToScene(calculateMobilityOperation);
+ }
+ // Установить эту сцену у дочерних объектов
+ for (var key:* in _children) {
+ var object:Object3D = key;
+ object.setScene(_scene);
+ }
+ }
+
+ /**
+ * @private
+ * Установка уровня операции трансформации.
+ *
+ * @param value уровень операции трансформации
+ */
+ alternativa3d function setLevel(value:uint):void {
+ // Установить уровень операции трансформации и расчёта мобильности
+ calculateTransformationOperation.priority = (calculateTransformationOperation.priority & 0xFF000000) | value;
+ calculateMobilityOperation.priority = (calculateMobilityOperation.priority & 0xFF000000) | value;
+ // Установить уровни у дочерних объектов
+ for (var key:* in _children) {
+ var object:Object3D = key;
+ object.setLevel(value + 1);
+ }
+ }
+
+ /**
+ * @private
+ * Подписка на сигналы родителя.
+ */
+ private function addParentSequels():void {
+ _parent.changeCoordsOperation.addSequel(changeCoordsOperation);
+ _parent.changeRotationOrScaleOperation.addSequel(changeRotationOrScaleOperation);
+ _parent.calculateMobilityOperation.addSequel(calculateMobilityOperation);
+ }
+
+ /**
+ * @private
+ * Удаление подписки на сигналы родителя.
+ */
+ private function removeParentSequels():void {
+ _parent.changeCoordsOperation.removeSequel(changeCoordsOperation);
+ _parent.changeRotationOrScaleOperation.removeSequel(changeRotationOrScaleOperation);
+ _parent.calculateMobilityOperation.removeSequel(calculateMobilityOperation);
+ }
+
+ /**
+ * Метод вызывается при добавлении объекта на сцену. Наследники могут переопределять метод для выполнения
+ * специфических действий.
+ *
+ * @param scene сцена, в которую добавляется объект
+ */
+ protected function addToScene(scene:Scene3D):void {
+ // При добавлении на сцену полная трансформация и расчёт мобильности
+ scene.addOperation(changeRotationOrScaleOperation);
+ scene.addOperation(calculateMobilityOperation);
+ }
+
+ /**
+ * Метод вызывается при удалении объекта со сцены. Наследники могут переопределять метод для выполнения
+ * специфических действий.
+ *
+ * @param scene сцена, из которой удаляется объект
+ */
+ protected function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(changeRotationOrScaleOperation);
+ scene.removeOperation(changeCoordsOperation);
+ scene.removeOperation(calculateMobilityOperation);
+ }
+
+ /**
+ * Имя объекта.
+ */
+ public function get name():String {
+ return _name;
+ }
+
+ /**
+ * @private
+ */
+ public function set name(value:String):void {
+ _name = value;
+ }
+
+ /**
+ * Сцена, которой принадлежит объект.
+ */
+ public function get scene():Scene3D {
+ return _scene;
+ }
+
+ /**
+ * Родительский объект.
+ */
+ public function get parent():Object3D {
+ return _parent;
+ }
+
+ /**
+ * Набор дочерних объектов.
+ */
+ public function get children():Set {
+ return _children.clone();
+ }
+
+ /**
+ * Уровень мобильности. Результирующая мобильность объекта является суммой мобильностей объекта и всех его предков
+ * по иерархии объектов в сцене. Результирующая мобильность влияет на положение объекта в BSP-дереве. Менее мобильные
+ * объекты находятся ближе к корню дерева, чем более мобильные.
+ */
+ public function get mobility():int {
+ return _mobility;
+ }
+
+ /**
+ * @private
+ */
+ public function set mobility(value:int):void {
+ if (_mobility != value) {
+ _mobility = value;
+ addOperationToScene(calculateMobilityOperation);
+ }
+ }
+
+ /**
+ * Координата X.
+ */
+ public function get x():Number {
+ return _coords.x;
+ }
+
+ /**
+ * Координата Y.
+ */
+ public function get y():Number {
+ return _coords.y;
+ }
+
+ /**
+ * Координата Z.
+ */
+ public function get z():Number {
+ return _coords.z;
+ }
+
+ /**
+ * @private
+ */
+ public function set x(value:Number):void {
+ if (_coords.x != value) {
+ _coords.x = value;
+ addOperationToScene(changeCoordsOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set y(value:Number):void {
+ if (_coords.y != value) {
+ _coords.y = value;
+ addOperationToScene(changeCoordsOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set z(value:Number):void {
+ if (_coords.z != value) {
+ _coords.z = value;
+ addOperationToScene(changeCoordsOperation);
+ }
+ }
+
+ /**
+ * Координаты объекта.
+ */
+ public function get coords():Point3D {
+ return _coords.clone();
+ }
+
+ /**
+ * @private
+ */
+ public function set coords(value:Point3D):void {
+ if (!_coords.equals(value)) {
+ _coords.copy(value);
+ addOperationToScene(changeCoordsOperation);
+ }
+ }
+
+ /**
+ * Угол поворота вокруг оси X, заданный в радианах.
+ */
+ public function get rotationX():Number {
+ return _rotationX;
+ }
+
+ /**
+ * Угол поворота вокруг оси Y, заданный в радианах.
+ */
+ public function get rotationY():Number {
+ return _rotationY;
+ }
+
+ /**
+ * Угол поворота вокруг оси Z, заданный в радианах.
+ */
+ public function get rotationZ():Number {
+ return _rotationZ;
+ }
+
+ /**
+ * @private
+ */
+ public function set rotationX(value:Number):void {
+ if (_rotationX != value) {
+ _rotationX = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set rotationY(value:Number):void {
+ if (_rotationY != value) {
+ _rotationY = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set rotationZ(value:Number):void {
+ if (_rotationZ != value) {
+ _rotationZ = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * Коэффициент масштабирования вдоль оси X.
+ */
+ public function get scaleX():Number {
+ return _scaleX;
+ }
+
+ /**
+ * Коэффициент масштабирования вдоль оси Y.
+ */
+ public function get scaleY():Number {
+ return _scaleY;
+ }
+
+ /**
+ * Коэффициент масштабирования вдоль оси Z.
+ */
+ public function get scaleZ():Number {
+ return _scaleZ;
+ }
+
+ /**
+ * @private
+ */
+ public function set scaleX(value:Number):void {
+ if (_scaleX != value) {
+ _scaleX = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set scaleY(value:Number):void {
+ if (_scaleY != value) {
+ _scaleY = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set scaleZ(value:Number):void {
+ if (_scaleZ != value) {
+ _scaleZ = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * Строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ return "[" + ObjectUtils.getClassName(this) + " " + _name + "]";
+ }
+
+ /**
+ * Имя объекта по умолчанию.
+ *
+ * @return имя объекта по умолчанию
+ */
+ protected function defaultName():String {
+ return "object" + ++counter;
+ }
+
+ /**
+ * @private
+ * Добавление операции в очередь.
+ *
+ * @param operation добавляемая операция
+ */
+ alternativa3d function addOperationToScene(operation:Operation):void {
+ if (_scene != null) {
+ _scene.addOperation(operation);
+ }
+ }
+
+ /**
+ * @private
+ * Удаление операции из очереди.
+ *
+ * @param operation удаляемая операция
+ */
+ alternativa3d function removeOperationFromScene(operation:Operation):void {
+ if (_scene != null) {
+ _scene.removeOperation(operation);
+ }
+ }
+
+ /**
+ * Создание пустого объекта без какой-либо внутренней структуры. Например, если некоторый геометрический примитив при
+ * своём создании формирует набор вершин, граней и поверхностей, то этот метод не должен создавать вершины, грани и
+ * поверхности. Данный метод используется в методе clone() и должен быть переопределён в потомках для получения
+ * правильного объекта.
+ *
+ * @return новый пустой объект
+ */
+ protected function createEmptyObject():Object3D {
+ return new Object3D();
+ }
+
+ /**
+ * Копирование свойств объекта-источника. Данный метод используется в методе clone() и должен быть переопределён в
+ * потомках для получения правильного объекта. Каждый потомок должен в переопределённом методе копировать только те
+ * свойства, которые добавлены к базовому классу именно в нём. Копирование унаследованных свойств выполняется
+ * вызовом super.clonePropertiesFrom(source).
+ *
+ * @param source объект, свойства которого копируются
+ */
+ protected function clonePropertiesFrom(source:Object3D):void {
+ _name = source._name;
+ _mobility = source._mobility;
+ _coords.x = source._coords.x;
+ _coords.y = source._coords.y;
+ _coords.z = source._coords.z;
+ _rotationX = source._rotationX;
+ _rotationY = source._rotationY;
+ _rotationZ = source._rotationZ;
+ _scaleX = source._scaleX;
+ _scaleY = source._scaleY;
+ _scaleZ = source._scaleZ;
+ }
+
+ /**
+ * Клонирование объекта. Для реализации собственного клонирования наследники должны переопределять методы
+ * Изменением свойства Класс предоставляет средства загрузки объектов из 3DS-файла с заданным URL. Если
+ * в модели используются текстуры, то все файлы текстур должны находиться рядом с файлом модели.
+ *
+ * Из файла модели загружаются:
+ *
+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения
+ * @param url URL 3DS-файла
+ * @param context LoaderContext для загрузки файлов текстур
+ */
+ public function load(url:String, context:LoaderContext = null):void {
+ path = url.substring(0, url.lastIndexOf("/") + 1);
+ this.context = context;
+
+ // Очистка
+ _content = null;
+ version = 0;
+ objectDatas = null;
+ animationDatas = null;
+ materialDatas = null;
+ bitmaps = null;
+ sequence = null;
+
+ if (urlLoader == null) {
+ urlLoader = new URLLoader();
+ urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
+ urlLoader.addEventListener(Event.COMPLETE, on3DSLoad);
+ urlLoader.addEventListener(IOErrorEvent.IO_ERROR, on3DSError);
+ urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, on3DSError);
+ } else {
+ close();
+ }
+
+ urlLoader.load(new URLRequest(url));
+ }
+
+ private function on3DSLoad(e:Event):void {
+ data = urlLoader.data;
+ data.endian = Endian.LITTLE_ENDIAN;
+ parse3DSChunk(0, data.bytesAvailable);
+ }
+
+ private function on3DSError(e:Event):void {
+ dispatchEvent(e);
+ }
+
+ private function loadBitmaps():void {
+ if (bitmaps != null) {
+ counter = 0;
+ sequence = new Array();
+ for (var filename:String in bitmaps) {
+ sequence.push(filename);
+ }
+ if (loader == null) {
+ loader = new Loader();
+ loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadNextBitmap);
+ loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, loadNextBitmap);
+ loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loadNextBitmap);
+ }
+ loader.load(new URLRequest(path + sequence[counter]), context);
+ } else {
+ buildContent();
+ }
+ }
+
+ private function loadNextBitmap(e:Event = null):void {
+ if (!(e is IOErrorEvent)) {
+ bitmaps[sequence[counter]] = Bitmap(loader.content).bitmapData;
+ } else {
+ if (stubBitmapData == null) {
+ var size:uint = 20;
+ stubBitmapData = new BitmapData(size, size, false, 0);
+ for (var i:uint = 0; i < size; i++) {
+ for (var j:uint = 0; j < size; j+=2) {
+ stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF);
+ }
+ }
+ }
+ bitmaps[sequence[counter]] = stubBitmapData;
+ }
+ counter++;
+ if (counter < sequence.length) {
+ loader.load(new URLRequest(path + sequence[counter]), context);
+ } else {
+ buildContent();
+ }
+ }
+
+ private function buildContent():void {
+ var i:uint;
+ var length:uint;
+
+ // Формируем связи объектов
+ _content = new Object3D();
+
+ // Создаём материалы
+ var materialData:MaterialData;
+ for (var materialName:String in materialDatas) {
+ materialData = materialDatas[materialName];
+ var mapData:MapData = materialData.diffuseMap;
+ var materialMatrix:Matrix = new Matrix();
+ if (mapData != null) {
+ var rot:Number = MathUtils.toRadian(mapData.rotation);
+ var rotSin:Number = Math.sin(rot);
+ var rotCos:Number = Math.cos(rot);
+ materialMatrix.translate(-mapData.offsetU, mapData.offsetV);
+ materialMatrix.translate(-0.5, -0.5);
+ materialMatrix.rotate(-rot);
+ materialMatrix.scale(mapData.scaleU, mapData.scaleV);
+ materialMatrix.translate(0.5, 0.5);
+ }
+ materialData.matrix = materialMatrix;
+ }
+
+ // Если есть данные об анимации и иерархии объектов
+ var objectName:String;
+ var objectData:ObjectData;
+ var mesh:Mesh;
+ if (animationDatas != null) {
+ if (objectDatas != null) {
+
+ length = animationDatas.length;
+ for (i = 0; i < length; i++) {
+ var animationData:AnimationData = animationDatas[i];
+ objectName = animationData.objectName;
+ objectData = objectDatas[objectName];
+
+ // Если на один объект приходится несколько данных об анимации
+ if (objectData != null) {
+ var nameCounter:uint = 2;
+ for (var j:uint = i + 1; j < length; j++) {
+ var animationData2:AnimationData = animationDatas[j];
+ if (objectName == animationData2.objectName) {
+ var newName:String = objectName + nameCounter;
+ var newObjectData:ObjectData = new ObjectData();
+ animationData2.objectName = newName;
+ newObjectData.name = newName;
+ if (objectData.vertices != null) {
+ newObjectData.vertices = new Array().concat(objectData.vertices);
+ }
+ if (objectData.uvs != null) {
+ newObjectData.uvs = new Array().concat(objectData.uvs);
+ }
+ if (objectData.matrix != null) {
+ newObjectData.matrix = objectData.matrix.clone();
+ }
+ if (objectData.faces != null) {
+ newObjectData.faces = new Array().concat(objectData.faces);
+ }
+ if (objectData.surfaces != null) {
+ newObjectData.surfaces = objectData.surfaces.clone();
+ }
+ objectDatas[newName] = newObjectData;
+ nameCounter++;
+ }
+ }
+ }
+
+ if (objectData != null && objectData.vertices != null) {
+ // Меш
+ mesh = new Mesh(objectName);
+ animationData.object = mesh;
+ buildObject(animationData);
+ buildMesh(mesh, objectData, animationData);
+ } else {
+ var object:Object3D = new Object3D(objectName);
+ animationData.object = object;
+ buildObject(animationData);
+ }
+ }
+
+ buildHierarchy(_content, 0, length - 1);
+ }
+ } else {
+ for (objectName in objectDatas) {
+ objectData = objectDatas[objectName];
+ if (objectData.vertices != null) {
+ // Меш
+ mesh = new Mesh(objectName);
+ buildMesh(mesh, objectData, null);
+ _content.addChild(mesh);
+ }
+ }
+ }
+
+ // Рассылаем событие о завершении
+ dispatchEvent(new Event(Event.COMPLETE));
+ }
+
+ private function buildObject(animationData:AnimationData):void {
+ var object:Object3D = animationData.object;
+ if (animationData.position != null) {
+ object.x = animationData.position.x * units;
+ object.y = animationData.position.y * units;
+ object.z = animationData.position.z * units;
+ }
+ if (animationData.rotation != null) {
+ object.rotationX = animationData.rotation.x;
+ object.rotationY = animationData.rotation.y;
+ object.rotationZ = animationData.rotation.z;
+ }
+ if (animationData.scale != null) {
+ object.scaleX = animationData.scale.x;
+ object.scaleY = animationData.scale.y;
+ object.scaleZ = animationData.scale.z;
+ }
+ object.mobility = mobility;
+ }
+
+ private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData):void {
+ // Добавляем вершины
+ var i:uint;
+ var key:*;
+ var vertex:Vertex;
+ var face:Face;
+ var length:uint = objectData.vertices.length;
+ for (i = 0; i < length; i++) {
+ var vertexData:Point3D = objectData.vertices[i];
+ objectData.vertices[i] = mesh.createVertex(vertexData.x, vertexData.y, vertexData.z, i);
+ }
+
+ // Коррекция вершин
+ if (animationData != null) {
+ // Инвертируем матрицу
+ objectData.matrix.invert();
+
+ // Вычитаем точку привязки из смещения матрицы
+ objectData.matrix.d -= animationData.pivot.x;
+ objectData.matrix.h -= animationData.pivot.y;
+ objectData.matrix.l -= animationData.pivot.z;
+
+ // Трансформируем вершины
+ for (key in mesh._vertices) {
+ vertex = mesh._vertices[key];
+ vertex._coords.transform(objectData.matrix);
+ }
+ }
+ for (key in mesh._vertices) {
+ vertex = mesh._vertices[key];
+ vertex._coords.multiply(units);
+ }
+
+ // Добавляем грани
+ length = objectData.faces.length;
+ for (i = 0; i < length; i++) {
+ var faceData:FaceData = objectData.faces[i];
+ face = mesh.createFace([objectData.vertices[faceData.a], objectData.vertices[faceData.b], objectData.vertices[faceData.c]], i);
+ if (objectData.uvs != null) {
+ face.aUV = objectData.uvs[faceData.a];
+ face.bUV = objectData.uvs[faceData.b];
+ face.cUV = objectData.uvs[faceData.c];
+ }
+ }
+
+ // Добавляем поверхности
+ if (objectData.surfaces != null) {
+ for (var surfaceId:String in objectData.surfaces) {
+ var materialData:MaterialData = materialDatas[surfaceId];
+ var surfaceData:SurfaceData = objectData.surfaces[surfaceId];
+ var surface:Surface = mesh.createSurface(surfaceData.faces, surfaceId);
+ if (materialData.diffuseMap != null) {
+ var bmp:BitmapData = bitmaps[materialData.diffuseMap.filename];
+ surface.material = new TextureMaterial(new Texture(bmp, materialData.diffuseMap.filename), 1 - materialData.transparency/100, repeat, (bmp != stubBitmapData) ? smooth : false, blendMode, -1, 0, precision);
+ length = surfaceData.faces.length;
+ for (i = 0; i < length; i++) {
+ face = mesh.getFaceById(surfaceData.faces[i]);
+ if (face._aUV != null && face._bUV != null && face._cUV != null) {
+ var m:Matrix = materialData.matrix;
+ var x:Number = face.aUV.x;
+ var y:Number = face.aUV.y;
+ face._aUV.x = m.a*x + m.b*y + m.tx;
+ face._aUV.y = m.c*x + m.d*y + m.ty;
+ x = face._bUV.x;
+ y = face._bUV.y;
+ face._bUV.x = m.a*x + m.b*y + m.tx;
+ face._bUV.y = m.c*x + m.d*y + m.ty;
+ x = face._cUV.x;
+ y = face._cUV.y;
+ face._cUV.x = m.a*x + m.b*y + m.tx;
+ face._cUV.y = m.c*x + m.d*y + m.ty;
+ }
+ }
+ } else {
+ surface.material = new FillMaterial(materialDatas[surfaceId].color, 1 - materialData.transparency/100);
+ }
+ }
+ } else {
+ // Поверхность по умолчанию
+ var defaultSurface:Surface = mesh.createSurface();
+ // Добавляем грани
+ for (var faceId:String in mesh._faces) {
+ defaultSurface.addFace(mesh._faces[faceId]);
+ }
+ defaultSurface.material = new WireMaterial(0, 0x7F7F7F);
+ }
+ }
+
+ private function buildHierarchy(parent:Object3D, begin:uint, end:uint):void {
+ if (begin <= end) {
+ var animation:AnimationData = animationDatas[begin];
+ var object:Object3D = animation.object;
+ parent.addChild(object);
+
+ var parentIndex:uint = animation.parentIndex;
+ for (var i:uint = begin + 1; i <= end; i++) {
+ animation = animationDatas[i];
+ if (parentIndex == animation.parentIndex) {
+ buildHierarchy(object, begin + 1, i - 1);
+ buildHierarchy(parent, i, end);
+ return;
+ }
+ }
+ buildHierarchy(object, begin + 1, end);
+ }
+ }
+
+
+ private function parse3DSChunk(index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Главный
+ case 0x4D4D:
+ parseMainChunk(dataIndex, dataLength);
+ break;
+ }
+
+ parse3DSChunk(index + chunkLength, length - chunkLength);
+ } else {
+ // Загрузка битмап
+ loadBitmaps();
+ }
+ }
+
+ private function parseMainChunk(index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Версия
+ case 0x0002:
+ parseVersion(dataIndex);
+ break;
+ // 3D-сцена
+ case 0x3D3D:
+ parse3DChunk(dataIndex, dataLength);
+ break;
+ // Анимация
+ case 0xB000:
+ parseAnimationChunk(dataIndex, dataLength);
+ break;
+ }
+
+ parseMainChunk(index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseVersion(index:uint):void {
+ data.position = index;
+ version = data.readUnsignedInt();
+ }
+
+ private function parse3DChunk(index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Материал
+ case 0xAFFF:
+ // Парсим материал
+ var material:MaterialData = new MaterialData();
+ parseMaterialChunk(material, dataIndex, dataLength);
+ break;
+ // Объект
+ case 0x4000:
+ // Создаём данные объекта
+ var object:ObjectData = new ObjectData();
+ var objectLength:uint = parseObject(object, dataIndex);
+ // Парсим объект
+ parseObjectChunk(object, dataIndex + objectLength, dataLength - objectLength);
+ break;
+ }
+
+ parse3DChunk(index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseMaterialChunk(material:MaterialData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+ switch (chunkId) {
+ // Имя материала
+ case 0xA000:
+ parseMaterialName(material, dataIndex);
+ break;
+ // Ambient color
+ case 0xA010:
+ break;
+ // Diffuse color
+ case 0xA020:
+ data.position = dataIndex + 6;
+ material.color = ColorUtils.rgb(data.readUnsignedByte(), data.readUnsignedByte(), data.readUnsignedByte());
+ break;
+ // Specular color
+ case 0xA030:
+ break;
+ // Shininess percent
+ case 0xA040:
+ data.position = dataIndex + 6;
+ material.glossiness = data.readUnsignedShort();
+ break;
+ // Shininess strength percent
+ case 0xA041:
+ data.position = dataIndex + 6;
+ material.specular = data.readUnsignedShort();
+ break;
+ // Transperensy
+ case 0xA050:
+ data.position = dataIndex + 6;
+ material.transparency = data.readUnsignedShort();
+ break;
+ // Texture map 1
+ case 0xA200:
+ material.diffuseMap = new MapData();
+ parseMapChunk(material.diffuseMap, dataIndex, dataLength);
+ break;
+ // Texture map 2
+ case 0xA33A:
+ break;
+ // Opacity map
+ case 0xA210:
+ break;
+ // Bump map
+ case 0xA230:
+ //material.normalMap = new MapData();
+ //parseMapChunk(material.normalMap, dataIndex, dataLength);
+ break;
+ // Shininess map
+ case 0xA33C:
+ break;
+ // Specular map
+ case 0xA204:
+ break;
+ // Self-illumination map
+ case 0xA33D:
+ break;
+ // Reflection map
+ case 0xA220:
+ break;
+ }
+
+ parseMaterialChunk(material, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseMaterialName(material:MaterialData, index:uint):void {
+ // Создаём список материалов, если надо
+ if (materialDatas == null) {
+ materialDatas = new Array();
+ }
+ // Получаем название материала
+ material.name = getString(index);
+ // Помещаем данные материала в список
+ materialDatas[material.name] = material;
+ }
+
+ private function parseMapChunk(map:MapData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Имя файла
+ case 0xA300:
+ map.filename = getString(dataIndex).toLowerCase();
+ if (bitmaps == null) {
+ bitmaps = new Array();
+ }
+ bitmaps[map.filename] = null;
+ break;
+ // Масштаб по U
+ case 0xA354:
+ data.position = dataIndex;
+ map.scaleU = data.readFloat();
+ break;
+ // Масштаб по V
+ case 0xA356:
+ data.position = dataIndex;
+ map.scaleV = data.readFloat();
+ break;
+ // Смещение по U
+ case 0xA358:
+ data.position = dataIndex;
+ map.offsetU = data.readFloat();
+ break;
+ // Смещение по V
+ case 0xA35A:
+ data.position = dataIndex;
+ map.offsetV = data.readFloat();
+ break;
+ // Угол поворота
+ case 0xA35C:
+ data.position = dataIndex;
+ map.rotation = data.readFloat();
+ break;
+ }
+
+ parseMapChunk(map, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseObject(object:ObjectData, index:uint):uint {
+ // Создаём список объектов, если надо
+ if (objectDatas == null) {
+ objectDatas = new Map();
+ }
+ // Получаем название объекта
+ object.name = getString(index);
+ // Помещаем данные объекта в список
+ objectDatas[object.name] = object;
+ return object.name.length + 1;
+ }
+
+ private function parseObjectChunk(object:ObjectData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Меш
+ case 0x4100:
+ parseMeshChunk(object, dataIndex, dataLength);
+ break;
+ // Источник света
+ case 0x4600:
+ break;
+ // Камера
+ case 0x4700:
+ break;
+ }
+
+ parseObjectChunk(object, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseMeshChunk(object:ObjectData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Вершины
+ case 0x4110:
+ parseVertices(object, dataIndex);
+ break;
+ // UV
+ case 0x4140:
+ parseUVs(object, dataIndex);
+ break;
+ // Трансформация
+ case 0x4160:
+ parseMatrix(object, dataIndex);
+ break;
+ // Грани
+ case 0x4120:
+ var facesLength:uint = parseFaces(object, dataIndex);
+ parseFacesChunk(object, dataIndex + facesLength, dataLength - facesLength);
+ break;
+ }
+
+ parseMeshChunk(object, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseVertices(object:ObjectData, index:uint):void {
+ data.position = index;
+ var num:uint = data.readUnsignedShort();
+ object.vertices = new Array();
+ for (var i:uint = 0; i < num; i++) {
+ object.vertices.push(new Point3D(data.readFloat(), data.readFloat(), data.readFloat()));
+ }
+ }
+
+ private function parseUVs(object:ObjectData, index:uint):void {
+ data.position = index;
+ var num:uint = data.readUnsignedShort();
+ object.uvs = new Array();
+ for (var i:uint = 0; i < num; i++) {
+ object.uvs.push(new Point(data.readFloat(), data.readFloat()));
+ }
+ }
+
+ private function parseMatrix(object:ObjectData, index:uint):void {
+ data.position = index;
+ object.matrix = new Matrix3D();
+ object.matrix.a = data.readFloat();
+ object.matrix.e = data.readFloat();
+ object.matrix.i = data.readFloat();
+ object.matrix.b = data.readFloat();
+ object.matrix.f = data.readFloat();
+ object.matrix.j = data.readFloat();
+ object.matrix.c = data.readFloat();
+ object.matrix.g = data.readFloat();
+ object.matrix.k = data.readFloat();
+ object.matrix.d = data.readFloat();
+ object.matrix.h = data.readFloat();
+ object.matrix.l = data.readFloat();
+ }
+
+ private function parseFaces(object:ObjectData, index:uint):uint {
+ data.position = index;
+ var num:uint = data.readUnsignedShort();
+ object.faces = new Array();
+ for (var i:uint = 0; i < num; i++) {
+ var face:FaceData = new FaceData();
+ face.a = data.readUnsignedShort();
+ face.b = data.readUnsignedShort();
+ face.c = data.readUnsignedShort();
+ object.faces.push(face);
+ data.position += 2; // Пропускаем флаг
+ }
+ return 2 + num*8;
+ }
+
+ private function parseFacesChunk(object:ObjectData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Поверхности
+ case 0x4130:
+ parseSurface(object, dataIndex);
+ break;
+ // Группы сглаживания
+ case 0x4150:
+ break;
+ }
+
+ parseFacesChunk(object, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseSurface(object:ObjectData, index:uint):void {
+ // Создаём данные поверхности
+ var surface:SurfaceData = new SurfaceData();
+ // Создаём список поверхностей, если надо
+ if (object.surfaces == null) {
+ object.surfaces = new Map();
+ }
+ // Получаем название материала
+ surface.materialName = getString(index);
+ // Помещаем данные поверхности в список
+ object.surfaces[surface.materialName] = surface;
+
+ // Получаем грани поверхности
+ data.position = index + surface.materialName.length + 1;
+ var num:uint = data.readUnsignedShort();
+ surface.faces = new Array();
+ for (var i:uint = 0; i < num; i++) {
+ surface.faces.push(data.readUnsignedShort());
+ }
+ }
+
+ private function parseAnimationChunk(index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+ switch (chunkId) {
+ // Анимация объекта
+ case 0xB001:
+ case 0xB002:
+ case 0xB003:
+ case 0xB004:
+ case 0xB005:
+ case 0xB006:
+ case 0xB007:
+ var animation:AnimationData = new AnimationData();
+ if (animationDatas == null) {
+ animationDatas = new Array();
+ }
+ animationDatas.push(animation);
+ parseObjectAnimationChunk(animation, dataIndex, dataLength);
+ break;
+
+ // Таймлайн
+ case 0xB008:
+ break;
+ }
+
+ parseAnimationChunk(index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseObjectAnimationChunk(animation:AnimationData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+ switch (chunkId) {
+ // Идентификация объекта и его связь
+ case 0xB010:
+ parseObjectAnimationInfo(animation, dataIndex);
+ break;
+ // Точка привязки объекта (pivot)
+ case 0xB013:
+ parseObjectAnimationPivot(animation, dataIndex);
+ break;
+ // Смещение объекта относительно родителя
+ case 0xB020:
+ parseObjectAnimationPosition(animation, dataIndex);
+ break;
+ // Поворот объекта относительно родителя (angle-axis)
+ case 0xB021:
+ parseObjectAnimationRotation(animation, dataIndex);
+ break;
+ // Масштабирование объекта относительно родителя
+ case 0xB022:
+ parseObjectAnimationScale(animation, dataIndex);
+ break;
+ }
+
+ parseObjectAnimationChunk(animation, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseObjectAnimationInfo(animation:AnimationData, index:uint):void {
+ var name:String = getString(index);
+ data.position = index + name.length + 1 + 4;
+ animation.objectName = name;
+ animation.parentIndex = data.readUnsignedShort();
+ }
+
+ private function parseObjectAnimationPivot(animation:AnimationData, index:uint):void {
+ data.position = index;
+ animation.pivot = new Point3D(data.readFloat(), data.readFloat(), data.readFloat());
+ }
+
+ private function parseObjectAnimationPosition(animation:AnimationData, index:uint):void {
+ data.position = index + 20;
+ animation.position = new Point3D(data.readFloat(), data.readFloat(), data.readFloat());
+ }
+
+ private function parseObjectAnimationRotation(animation:AnimationData, index:uint):void {
+ data.position = index + 20;
+ animation.rotation = getRotationFrom3DSAngleAxis(data.readFloat(), data.readFloat(), data.readFloat(), data.readFloat());
+ }
+
+ private function parseObjectAnimationScale(animation:AnimationData, index:uint):void {
+ data.position = index + 20;
+ animation.scale = new Point3D(data.readFloat(), data.readFloat(), data.readFloat());
+ }
+
+ /**
+ * Объект-контейнер, содержащий все загруженные объекты.
+ */
+ public function get content():Object3D {
+ return _content;
+ }
+
+ // Считываем строку заканчивающуюся на нулевой байт
+ private function getString(index:uint):String {
+ data.position = index;
+ var charCode:uint = data.readByte();
+ var res:String = "";
+ while (charCode != 0) {
+ res += String.fromCharCode(charCode);
+ charCode = data.readByte();
+ }
+ return res;
+ }
+
+ private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Point3D {
+ var res:Point3D = new Point3D();
+ var s:Number = Math.sin(angle);
+ var c:Number = Math.cos(angle);
+ var t:Number = 1 - c;
+ var k:Number = x*y*t + z*s;
+ var half:Number;
+ if (k > 0.998) {
+ half = angle/2;
+ res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half));
+ res.y = -Math.PI/2;
+ res.x = 0;
+ return res;
+ }
+ if (k < -0.998) {
+ half = angle/2;
+ res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half));
+ res.y = Math.PI/2;
+ res.x = 0;
+ return res;
+ }
+ res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t);
+ res.y = -Math.asin(x*y*t + z*s);
+ res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t);
+ return res;
+ }
+
+ }
+}
+
+import alternativa.engine3d.core.Object3D;
+import alternativa.types.Matrix3D;
+import alternativa.types.Point3D;
+
+import flash.geom.Matrix;
+import alternativa.types.Map;
+
+class MaterialData {
+ public var name:String;
+ public var color:uint;
+ public var specular:uint;
+ public var glossiness:uint;
+ public var transparency:uint;
+ public var diffuseMap:MapData;
+ //public var normalMap:MapData;
+ public var matrix:Matrix;
+}
+
+class MapData {
+ public var filename:String;
+ public var scaleU:Number = 1;
+ public var scaleV:Number = 1;
+ public var offsetU:Number = 0;
+ public var offsetV:Number = 0;
+ public var rotation:Number = 0;
+}
+
+class ObjectData {
+ public var name:String;
+ public var vertices:Array;
+ public var uvs:Array;
+ public var matrix:Matrix3D;
+ public var faces:Array;
+ public var surfaces:Map;
+}
+
+class FaceData {
+ public var a:uint;
+ public var b:uint;
+ public var c:uint;
+}
+
+class SurfaceData {
+ public var materialName:String;
+ public var faces:Array;
+}
+
+class AnimationData {
+ public var objectName:String;
+ public var object:Object3D;
+ public var parentIndex:uint;
+ public var pivot:Point3D;
+ public var position:Point3D;
+ public var rotation:Point3D;
+ public var scale:Point3D;
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/loaders/LoaderMTL.as b/Alternativa3D5/5.3/alternativa/engine3d/loaders/LoaderMTL.as
new file mode 100644
index 0000000..622eedb
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/loaders/LoaderMTL.as
@@ -0,0 +1,285 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.*;
+ import alternativa.types.Map;
+ import alternativa.utils.ColorUtils;
+
+ import flash.display.Bitmap;
+ import flash.display.BitmapData;
+ import flash.display.Loader;
+ import flash.events.ErrorEvent;
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IOErrorEvent;
+ import flash.events.SecurityErrorEvent;
+ import flash.geom.Point;
+ import flash.net.URLLoader;
+ import flash.net.URLRequest;
+ import flash.system.LoaderContext;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ * Загрузчик библиотеки материалов из файлов в формате MTL material format (Lightwave, OBJ).
+ *
+ * На данный момент обеспечивается загрузка цвета, прозрачности и диффузной текстуры материала.
+ */
+ internal class LoaderMTL extends EventDispatcher {
+
+ private static const COMMENT_CHAR:String = "#";
+ private static const CMD_NEW_MATERIAL:String = "newmtl";
+ private static const CMD_DIFFUSE_REFLECTIVITY:String = "Kd";
+ private static const CMD_DISSOLVE:String = "d";
+ private static const CMD_MAP_DIFFUSE:String = "map_Kd";
+
+ private static const REGEXP_TRIM:RegExp = /^\s*(.*)\s*$/;
+ private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/;
+ private static const REGEXP_SPLIT_LINE:RegExp = /\s+/;
+
+ // Загрузчик файла MTL
+ private var fileLoader:URLLoader;
+ // Загрузчик файлов текстур
+ private var bitmapLoader:Loader;
+ // Контекст загрузки для bitmapLoader
+ private var loaderContext:LoaderContext;
+ // Базовый URL файла MTL
+ private var baseUrl:String;
+
+ // Библиотека загруженных материалов
+ private var _library:Map;
+ // Список материалов, имеющих диффузные текстуры
+ private var diffuseMaps:Map;
+ // Имя текущего материала
+ private var materialName:String;
+ // параметры текущего материала
+ private var currentMaterialInfo:MaterialInfo = new MaterialInfo();
+
+ alternativa3d static var stubBitmapData:BitmapData;
+
+ /**
+ * Создаёт новый экземпляр класса.
+ */
+ public function LoaderMTL() {
+ }
+
+ /**
+ * Прекращение текущей загрузки.
+ */
+ public function close():void {
+ try {
+ fileLoader.close();
+ } catch (e:Error) {
+ }
+ }
+
+ /**
+ * Библиотека материалов. Ключами являются наименования материалов, значениями -- объекты, наследники класса
+ *
+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения
+ * Если происходит ошибка при загрузке файла текстуры, то соответствующая текстура заменяется на текстуру-заглушку.
+ *
+ * @param url URL MTL-файла
+ * @param loaderContext LoaderContext для загрузки файлов текстур
+ *
+ * @see #library
+ */
+ public function load(url:String, loaderContext:LoaderContext = null):void {
+ this.loaderContext = loaderContext;
+ baseUrl = url.substring(0, url.lastIndexOf("/") + 1);
+
+ if (fileLoader == null) {
+ fileLoader = new URLLoader();
+ fileLoader.addEventListener(Event.COMPLETE, parseMTLFile);
+ fileLoader.addEventListener(IOErrorEvent.IO_ERROR, onError);
+ fileLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
+
+ bitmapLoader = new Loader();
+ bitmapLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onBitmapLoadComplete);
+ bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onBitmapLoadComplete);
+ bitmapLoader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onBitmapLoadComplete);
+ }
+
+ try {
+ fileLoader.close();
+ bitmapLoader.close();
+ } catch (e:Error) {
+ // Пропуск ошибки при попытке закрытия неактивных загрузчиков
+ }
+
+ fileLoader.load(new URLRequest(url));
+ }
+
+ /**
+ * Разбор содержимого загруженного файла материалов.
+ */
+ private function parseMTLFile(e:Event = null):void {
+ var lines:Array = fileLoader.data.split(REGEXP_SPLIT_FILE);
+ _library = new Map();
+ diffuseMaps = new Map();
+ for each (var line:String in lines) {
+ parseLine(line);
+ }
+ defineMaterial();
+
+ if (diffuseMaps.isEmpty()) {
+ // Текстур нет, загрузка окончена
+ dispatchEvent(new Event(Event.COMPLETE));
+ } else {
+ // Загрузка файлов текстур
+ loadNextBitmap();
+ }
+ }
+
+ /**
+ * Разбор строки файла.
+ *
+ * @param line строка файла
+ */
+ private function parseLine(line:String):void {
+ line = line.replace(REGEXP_TRIM,"$1")
+ if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) {
+ return;
+ }
+ var parts:Array = line.split(REGEXP_SPLIT_LINE);
+ switch (parts[0]) {
+ case CMD_NEW_MATERIAL:
+ defineMaterial(parts);
+ break;
+ case CMD_DIFFUSE_REFLECTIVITY:
+ readDiffuseReflectivity(parts);
+ break;
+ case CMD_DISSOLVE:
+ readAlpha(parts);
+ break;
+ case CMD_MAP_DIFFUSE:
+ parseDiffuseMapLine(parts);
+ break;
+ }
+ }
+
+ /**
+ * Определение нового материала.
+ */
+ private function defineMaterial(parts:Array = null):void {
+ if (materialName != null) {
+ _library[materialName] = currentMaterialInfo;
+ }
+ if (parts != null) {
+ materialName = parts[1];
+ currentMaterialInfo = new MaterialInfo();
+ }
+ }
+
+ /**
+ * Чтение коэффициентов диффузного отражения. Считываются только коэффициенты, заданные в формате r g b. Для текущей
+ * версии движка данные коэффициенты преобразуются в цвет материала.
+ */
+ private function readDiffuseReflectivity(parts:Array):void {
+ var r:Number = Number(parts[1]);
+ // Проверка, заданы ли коэффициенты в виде r g b
+ if (!isNaN(r)) {
+ var g:Number = Number(parts[2]);
+ var b:Number = Number(parts[3]);
+ currentMaterialInfo.color = ColorUtils.rgb(255 * r, 255 * g, 255 * b);
+ }
+ }
+
+ /**
+ * Чтение коэффициента непрозрачности. Считывается только коэффициент, заданный числом
+ * (не поддерживается параметр -halo).
+ */
+ private function readAlpha(parts:Array):void {
+ var alpha:Number = Number(parts[1]);
+ if (!isNaN(alpha)) {
+ currentMaterialInfo.alpha = alpha;
+ }
+ }
+
+ /**
+ * Разбор строки, задающей текстурную карту для диффузного отражения.
+ */
+ private function parseDiffuseMapLine(parts:Array):void {
+ var info:MTLTextureMapInfo = MTLTextureMapInfo.parse(parts);
+ diffuseMaps[materialName] = info;
+ }
+
+ /**
+ * Загрузка файла следующей текстуры.
+ */
+ private function loadNextBitmap():void {
+ // Установка имени текущего текстурного материала, для которого выполняется загрузка текстуры
+ for (materialName in diffuseMaps) {
+ break;
+ }
+ bitmapLoader.load(new URLRequest(baseUrl + diffuseMaps[materialName].fileName), loaderContext);
+ }
+
+ /**
+ *
+ */
+ private function createStubBitmap():void {
+ if (stubBitmapData == null) {
+ var size:uint = 10;
+ stubBitmapData = new BitmapData(size, size, false, 0);
+ for (var i:uint = 0; i < size; i++) {
+ for (var j:uint = 0; j < size; j+=2) {
+ stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF);
+ }
+ }
+ }
+ }
+
+ /**
+ * Обработка результата загрузки файла текстуры.
+ */
+ private function onBitmapLoadComplete(e:Event):void {
+ var bmd:BitmapData;
+
+ if (e is ErrorEvent) {
+ if (stubBitmapData == null) {
+ createStubBitmap();
+ }
+ bmd = stubBitmapData;
+ } else {
+ bmd = Bitmap(bitmapLoader.content).bitmapData;
+ }
+
+ var mtlInfo:MTLTextureMapInfo = diffuseMaps[materialName];
+ delete diffuseMaps[materialName];
+ var info:MaterialInfo = _library[materialName];
+
+ info.bitmapData = bmd;
+ info.repeat = mtlInfo.repeat;
+ info.mapOffset = new Point(mtlInfo.offsetU, mtlInfo.offsetV);
+ info.mapSize = new Point(mtlInfo.sizeU, mtlInfo.sizeV);
+ info.textureFileName = mtlInfo.fileName;
+
+ if (diffuseMaps.isEmpty()) {
+ dispatchEvent(new Event(Event.COMPLETE));
+ } else {
+ loadNextBitmap();
+ }
+ }
+
+ /**
+ *
+ * @param e
+ */
+ private function onError(e:IOErrorEvent):void {
+ dispatchEvent(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/loaders/LoaderOBJ.as b/Alternativa3D5/5.3/alternativa/engine3d/loaders/LoaderOBJ.as
new file mode 100644
index 0000000..a70b200
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/loaders/LoaderOBJ.as
@@ -0,0 +1,506 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.engine3d.materials.TextureMaterialPrecision;
+ import alternativa.types.Map;
+ import alternativa.types.Point3D;
+ import alternativa.types.Texture;
+
+ import flash.display.BlendMode;
+ import flash.events.ErrorEvent;
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IOErrorEvent;
+ import flash.events.SecurityErrorEvent;
+ import flash.geom.Point;
+ import flash.net.URLLoader;
+ import flash.net.URLRequest;
+ import flash.system.LoaderContext;
+
+ use namespace alternativa3d;
+
+ /**
+ * Загрузчик моделей из файла в формате OBJ. Так как OBJ не поддерживает иерархию объектов, все загруженные
+ * модели помещаются в один контейнер
+ * Поддерживаюся следующие команды формата OBJ:
+ *
+ *
+ * Пример использования:
+ *
+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения
+ * @param url URL OBJ-файла
+ * @param loadMaterials флаг загрузки материалов. Если указано значение Параллелепипед после создания будет содержать в себе шесть поверхностей.
+ * Различные значения параметров позволяют создавать различные примитивы.
+ * Так при установленном параметре По умолчанию параметр После создания примитив всегда содержит в себе поверхность Геоплоскость это плоскость с сетчатой структурой граней. Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров.
+ * При значении Геосфера после создания содержит в себе одну поверхность с идентификатором по умолчанию. Текстурные координаты у геосферы не находятся в промежутке Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров.
+ * При значении После создания примитив содержит в себе одну поверхность с идентификатором по умолчанию. По умолчанию параметр Соответствия локальных осей для объектов, не являющихся камерой:
+ * Соответствия локальных осей для объектов, являющихся камерой:
+ *
+ * Алгоритм работы метода следующий:
+ * Контроллер предоставляет два режима движения: режим ходьбы с учётом силы тяжести и режим полёта, в котором сила
+ * тяжести не учитывается. В обоих режимах может быть включена проверка столкновений с объектами сцены. Если проверка
+ * столкновений отключена, то в режиме ходьбы сила тяжести также игнорируется и дополнительно появляется возможность
+ * движения по вертикали.
+ *
+ * Для всех объектов, за исключением Вне зависимости от того, включена проверка столкновений или нет, координаты при перемещении расчитываются для
+ * эллипсоида, параметры которого устанавливаются через свойство Команда Направление камеры совпадает с её локальной осью Z, поэтому только что созданная камера смотрит вверх в системе
+ * координат родителя.
+ *
+ * Для отображения видимой через камеру части сцены на экран, к камере должна быть подключёна область вывода —
+ * экземпляр класса Масштабирование, ориентация и положение объекта задаются в родительской системе координат. Результирующая
+ * локальная трансформация является композицией операций масштабирования, поворотов объекта относительно осей
+ * Глобальная трансформация (в системе координат корневого объекта сцены) является композицией трансформаций
+ * самого объекта и всех его предков по иерархии объектов сцены.
+ */
+ public class Object3D {
+ // Операции
+ /**
+ * @private
+ * Поворот или масштабирование
+ */
+ alternativa3d var changeRotationOrScaleOperation:Operation = new Operation("changeRotationOrScale", this);
+ /**
+ * @private
+ * Перемещение
+ */
+ alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this);
+ /**
+ * @private
+ * Расчёт матрицы трансформации
+ */
+ alternativa3d var calculateTransformationOperation:Operation = new Operation("calculateTransformation", this, calculateTransformation, Operation.OBJECT_CALCULATE_TRANSFORMATION);
+ /**
+ * @private
+ * Изменение уровеня мобильности
+ */
+ alternativa3d var calculateMobilityOperation:Operation = new Operation("calculateMobility", this, calculateMobility, Operation.OBJECT_CALCULATE_MOBILITY);
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ /**
+ * @private
+ * Наименование
+ */
+ alternativa3d var _name:String;
+ /**
+ * @private
+ * Сцена
+ */
+ alternativa3d var _scene:Scene3D;
+ /**
+ * @private
+ * Родительский объект
+ */
+ alternativa3d var _parent:Object3D;
+ /**
+ * @private
+ * Дочерние объекты
+ */
+ alternativa3d var _children:Set = new Set();
+ /**
+ * @private
+ * Уровень мобильности
+ */
+ alternativa3d var _mobility:int = 0;
+ /**
+ * @private
+ */
+ alternativa3d var inheritedMobility:int;
+ /**
+ * @private
+ * Координаты объекта относительно родителя
+ */
+ alternativa3d var _coords:Point3D = new Point3D();
+ /**
+ * @private
+ * Поворот объекта по оси X относительно родителя. Угол измеряется в радианах.
+ */
+ alternativa3d var _rotationX:Number = 0;
+ /**
+ * @private
+ * Поворот объекта по оси Y относительно родителя. Угол измеряется в радианах.
+ */
+ alternativa3d var _rotationY:Number = 0;
+ /**
+ * @private
+ * Поворот объекта по оси Z относительно родителя. Угол измеряется в радианах.
+ */
+ alternativa3d var _rotationZ:Number = 0;
+ /**
+ * @private
+ * Мастшаб объекта по оси X относительно родителя
+ */
+ alternativa3d var _scaleX:Number = 1;
+ /**
+ * @private
+ * Мастшаб объекта по оси Y относительно родителя
+ */
+ alternativa3d var _scaleY:Number = 1;
+ /**
+ * @private
+ * Мастшаб объекта по оси Z относительно родителя
+ */
+ alternativa3d var _scaleZ:Number = 1;
+ /**
+ * @private
+ * Полная матрица трансформации, переводящая координаты из локальной системы координат объекта в систему координат сцены
+ */
+ alternativa3d var transformation:Matrix3D = new Matrix3D();
+ /**
+ * @private
+ * Координаты в сцене
+ */
+ alternativa3d var globalCoords:Point3D = new Point3D();
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param name имя экземпляра
+ */
+ public function Object3D(name:String = null) {
+ // Имя по-умолчанию
+ _name = (name != null) ? name : defaultName();
+
+ // Последствия операций
+ changeRotationOrScaleOperation.addSequel(calculateTransformationOperation);
+ changeCoordsOperation.addSequel(calculateTransformationOperation);
+ }
+
+ /**
+ * @private
+ * Расчёт трансформации
+ */
+ private function calculateTransformation():void {
+ if (changeRotationOrScaleOperation.queued) {
+ // Если полная трансформация
+ transformation.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ);
+ if (_parent != null) {
+ transformation.combine(_parent.transformation);
+ }
+ // Сохраняем глобальные координаты объекта
+ globalCoords.x = transformation.d;
+ globalCoords.y = transformation.h;
+ globalCoords.z = transformation.l;
+ } else {
+ // Если только перемещение
+ globalCoords.copy(_coords);
+ if (_parent != null) {
+ globalCoords.transform(_parent.transformation);
+ }
+ transformation.offset(globalCoords.x, globalCoords.y, globalCoords.z);
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт общей мобильности
+ */
+ private function calculateMobility():void {
+ inheritedMobility = ((_parent != null) ? _parent.inheritedMobility : 0) + _mobility;
+ }
+
+ /**
+ * Добавление дочернего объекта. Добавляемый объект удаляется из списка детей предыдущего родителя.
+ * Новой сценой дочернего объекта становится сцена родителя.
+ *
+ * @param child добавляемый объект
+ *
+ * @throws alternativa.engine3d.errors.Object3DHierarchyError нарушение иерархии объектов сцены
+ */
+ public function addChild(child:Object3D):void {
+
+ // Проверка на null
+ if (child == null) {
+ throw new Object3DHierarchyError(null, this);
+ }
+
+ // Проверка на наличие
+ if (child._parent == this) {
+ return;
+ }
+
+ // Проверка на добавление к самому себе
+ if (child == this) {
+ throw new Object3DHierarchyError(this, this);
+ }
+
+ // Проверка на добавление родительского объекта
+ if (child._scene == _scene) {
+ // Если объект был в той же сцене, либо оба не были в сцене
+ var parentObject:Object3D = _parent;
+ while (parentObject != null) {
+ if (child == parentObject) {
+ throw new Object3DHierarchyError(child, this);
+ return;
+ }
+ parentObject = parentObject._parent;
+ }
+ }
+
+ // Если объект был в другом объекте
+ if (child._parent != null) {
+ // Удалить его оттуда
+ child._parent._children.remove(child);
+ } else {
+ // Если объект был корневым в сцене
+ if (child._scene != null && child._scene._root == child) {
+ child._scene.root = null;
+ }
+ }
+
+ // Добавляем в список
+ _children.add(child);
+ // Указываем себя как родителя
+ child.setParent(this);
+ // Устанавливаем уровни
+ child.setLevel((calculateTransformationOperation.priority & 0xFFFFFF) + 1);
+ // Указываем сцену
+ child.setScene(_scene);
+ }
+
+ /**
+ * Удаление дочернего объекта.
+ *
+ * @param child удаляемый дочерний объект
+ *
+ * @throws alternativa.engine3d.errors.Object3DNotFoundError указанный объект не содержится в списке детей текущего объекта
+ */
+ public function removeChild(child:Object3D):void {
+ // Проверка на null
+ if (child == null) {
+ throw new Object3DNotFoundError(null, this);
+ }
+ // Проверка на наличие
+ if (child._parent != this) {
+ throw new Object3DNotFoundError(child, this);
+ }
+ // Убираем из списка
+ _children.remove(child);
+ // Удаляем ссылку на родителя
+ child.setParent(null);
+ // Удаляем ссылку на сцену
+ child.setScene(null);
+ }
+
+ /**
+ * @private
+ * Установка родительского объекта.
+ *
+ * @param value родительский объект
+ */
+ alternativa3d function setParent(value:Object3D):void {
+ // Отписываемся от сигналов старого родителя
+ if (_parent != null) {
+ removeParentSequels();
+ }
+ // Сохранить родителя
+ _parent = value;
+ // Если устанавливаем родителя
+ if (value != null) {
+ // Подписка на сигналы родителя
+ addParentSequels();
+ }
+ }
+
+ /**
+ * @private
+ * Установка новой сцены для объекта.
+ *
+ * @param value сцена
+ */
+ alternativa3d function setScene(value:Scene3D):void {
+ if (_scene != value) {
+ // Если была сцена
+ if (_scene != null) {
+ // Удалиться из сцены
+ removeFromScene(_scene);
+ }
+ // Если новая сцена
+ if (value != null) {
+ // Добавиться на сцену
+ addToScene(value);
+ }
+ // Сохранить сцену
+ _scene = value;
+ } else {
+ // Посылаем операцию трансформации
+ addOperationToScene(changeRotationOrScaleOperation);
+ // Посылаем операцию пересчёта мобильности
+ addOperationToScene(calculateMobilityOperation);
+ }
+ // Установить эту сцену у дочерних объектов
+ for (var key:* in _children) {
+ var object:Object3D = key;
+ object.setScene(_scene);
+ }
+ }
+
+ /**
+ * @private
+ * Установка уровня операции трансформации.
+ *
+ * @param value уровень операции трансформации
+ */
+ alternativa3d function setLevel(value:uint):void {
+ // Установить уровень операции трансформации и расчёта мобильности
+ calculateTransformationOperation.priority = (calculateTransformationOperation.priority & 0xFF000000) | value;
+ calculateMobilityOperation.priority = (calculateMobilityOperation.priority & 0xFF000000) | value;
+ // Установить уровни у дочерних объектов
+ for (var key:* in _children) {
+ var object:Object3D = key;
+ object.setLevel(value + 1);
+ }
+ }
+
+ /**
+ * @private
+ * Подписка на сигналы родителя.
+ */
+ private function addParentSequels():void {
+ _parent.changeCoordsOperation.addSequel(changeCoordsOperation);
+ _parent.changeRotationOrScaleOperation.addSequel(changeRotationOrScaleOperation);
+ _parent.calculateMobilityOperation.addSequel(calculateMobilityOperation);
+ }
+
+ /**
+ * @private
+ * Удаление подписки на сигналы родителя.
+ */
+ private function removeParentSequels():void {
+ _parent.changeCoordsOperation.removeSequel(changeCoordsOperation);
+ _parent.changeRotationOrScaleOperation.removeSequel(changeRotationOrScaleOperation);
+ _parent.calculateMobilityOperation.removeSequel(calculateMobilityOperation);
+ }
+
+ /**
+ * Метод вызывается при добавлении объекта на сцену. Наследники могут переопределять метод для выполнения
+ * специфических действий.
+ *
+ * @param scene сцена, в которую добавляется объект
+ */
+ protected function addToScene(scene:Scene3D):void {
+ // При добавлении на сцену полная трансформация и расчёт мобильности
+ scene.addOperation(changeRotationOrScaleOperation);
+ scene.addOperation(calculateMobilityOperation);
+ }
+
+ /**
+ * Метод вызывается при удалении объекта со сцены. Наследники могут переопределять метод для выполнения
+ * специфических действий.
+ *
+ * @param scene сцена, из которой удаляется объект
+ */
+ protected function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(changeRotationOrScaleOperation);
+ scene.removeOperation(changeCoordsOperation);
+ scene.removeOperation(calculateMobilityOperation);
+ }
+
+ /**
+ * Имя объекта.
+ */
+ public function get name():String {
+ return _name;
+ }
+
+ /**
+ * @private
+ */
+ public function set name(value:String):void {
+ _name = value;
+ }
+
+ /**
+ * Сцена, которой принадлежит объект.
+ */
+ public function get scene():Scene3D {
+ return _scene;
+ }
+
+ /**
+ * Родительский объект.
+ */
+ public function get parent():Object3D {
+ return _parent;
+ }
+
+ /**
+ * Набор дочерних объектов.
+ */
+ public function get children():Set {
+ return _children.clone();
+ }
+
+ /**
+ * Уровень мобильности. Результирующая мобильность объекта является суммой мобильностей объекта и всех его предков
+ * по иерархии объектов в сцене. Результирующая мобильность влияет на положение объекта в BSP-дереве. Менее мобильные
+ * объекты находятся ближе к корню дерева, чем более мобильные.
+ */
+ public function get mobility():int {
+ return _mobility;
+ }
+
+ /**
+ * @private
+ */
+ public function set mobility(value:int):void {
+ if (_mobility != value) {
+ _mobility = value;
+ addOperationToScene(calculateMobilityOperation);
+ }
+ }
+
+ /**
+ * Координата X.
+ */
+ public function get x():Number {
+ return _coords.x;
+ }
+
+ /**
+ * Координата Y.
+ */
+ public function get y():Number {
+ return _coords.y;
+ }
+
+ /**
+ * Координата Z.
+ */
+ public function get z():Number {
+ return _coords.z;
+ }
+
+ /**
+ * @private
+ */
+ public function set x(value:Number):void {
+ if (_coords.x != value) {
+ _coords.x = value;
+ addOperationToScene(changeCoordsOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set y(value:Number):void {
+ if (_coords.y != value) {
+ _coords.y = value;
+ addOperationToScene(changeCoordsOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set z(value:Number):void {
+ if (_coords.z != value) {
+ _coords.z = value;
+ addOperationToScene(changeCoordsOperation);
+ }
+ }
+
+ /**
+ * Координаты объекта.
+ */
+ public function get coords():Point3D {
+ return _coords.clone();
+ }
+
+ /**
+ * @private
+ */
+ public function set coords(value:Point3D):void {
+ if (!_coords.equals(value)) {
+ _coords.copy(value);
+ addOperationToScene(changeCoordsOperation);
+ }
+ }
+
+ /**
+ * Угол поворота вокруг оси X, заданный в радианах.
+ */
+ public function get rotationX():Number {
+ return _rotationX;
+ }
+
+ /**
+ * Угол поворота вокруг оси Y, заданный в радианах.
+ */
+ public function get rotationY():Number {
+ return _rotationY;
+ }
+
+ /**
+ * Угол поворота вокруг оси Z, заданный в радианах.
+ */
+ public function get rotationZ():Number {
+ return _rotationZ;
+ }
+
+ /**
+ * @private
+ */
+ public function set rotationX(value:Number):void {
+ if (_rotationX != value) {
+ _rotationX = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set rotationY(value:Number):void {
+ if (_rotationY != value) {
+ _rotationY = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set rotationZ(value:Number):void {
+ if (_rotationZ != value) {
+ _rotationZ = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * Коэффициент масштабирования вдоль оси X.
+ */
+ public function get scaleX():Number {
+ return _scaleX;
+ }
+
+ /**
+ * Коэффициент масштабирования вдоль оси Y.
+ */
+ public function get scaleY():Number {
+ return _scaleY;
+ }
+
+ /**
+ * Коэффициент масштабирования вдоль оси Z.
+ */
+ public function get scaleZ():Number {
+ return _scaleZ;
+ }
+
+ /**
+ * @private
+ */
+ public function set scaleX(value:Number):void {
+ if (_scaleX != value) {
+ _scaleX = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set scaleY(value:Number):void {
+ if (_scaleY != value) {
+ _scaleY = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set scaleZ(value:Number):void {
+ if (_scaleZ != value) {
+ _scaleZ = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * Строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ return "[" + ObjectUtils.getClassName(this) + " " + _name + "]";
+ }
+
+ /**
+ * Имя объекта по умолчанию.
+ *
+ * @return имя объекта по умолчанию
+ */
+ protected function defaultName():String {
+ return "object" + ++counter;
+ }
+
+ /**
+ * @private
+ * Добавление операции в очередь.
+ *
+ * @param operation добавляемая операция
+ */
+ alternativa3d function addOperationToScene(operation:Operation):void {
+ if (_scene != null) {
+ _scene.addOperation(operation);
+ }
+ }
+
+ /**
+ * @private
+ * Удаление операции из очереди.
+ *
+ * @param operation удаляемая операция
+ */
+ alternativa3d function removeOperationFromScene(operation:Operation):void {
+ if (_scene != null) {
+ _scene.removeOperation(operation);
+ }
+ }
+
+ /**
+ * Создание пустого объекта без какой-либо внутренней структуры. Например, если некоторый геометрический примитив при
+ * своём создании формирует набор вершин, граней и поверхностей, то этот метод не должен создавать вершины, грани и
+ * поверхности. Данный метод используется в методе clone() и должен быть переопределён в потомках для получения
+ * правильного объекта.
+ *
+ * @return новый пустой объект
+ */
+ protected function createEmptyObject():Object3D {
+ return new Object3D();
+ }
+
+ /**
+ * Копирование свойств объекта-источника. Данный метод используется в методе clone() и должен быть переопределён в
+ * потомках для получения правильного объекта. Каждый потомок должен в переопределённом методе копировать только те
+ * свойства, которые добавлены к базовому классу именно в нём. Копирование унаследованных свойств выполняется
+ * вызовом super.clonePropertiesFrom(source).
+ *
+ * @param source объект, свойства которого копируются
+ */
+ protected function clonePropertiesFrom(source:Object3D):void {
+ _name = source._name;
+ _mobility = source._mobility;
+ _coords.x = source._coords.x;
+ _coords.y = source._coords.y;
+ _coords.z = source._coords.z;
+ _rotationX = source._rotationX;
+ _rotationY = source._rotationY;
+ _rotationZ = source._rotationZ;
+ _scaleX = source._scaleX;
+ _scaleY = source._scaleY;
+ _scaleZ = source._scaleZ;
+ }
+
+ /**
+ * Клонирование объекта. Для реализации собственного клонирования наследники должны переопределять методы
+ * Изменением свойства Класс предоставляет средства загрузки объектов из 3DS-файла с заданным URL. Если
+ * в модели используются текстуры, то все файлы текстур должны находиться рядом с файлом модели.
+ *
+ * Из файла модели загружаются:
+ *
+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения
+ * @param url URL 3DS-файла
+ * @param context LoaderContext для загрузки файлов текстур
+ */
+ public function load(url:String, context:LoaderContext = null):void {
+ path = url.substring(0, url.lastIndexOf("/") + 1);
+ this.context = context;
+
+ // Очистка
+ _content = null;
+ version = 0;
+ objectDatas = null;
+ animationDatas = null;
+ materialDatas = null;
+ bitmaps = null;
+ sequence = null;
+
+ if (urlLoader == null) {
+ urlLoader = new URLLoader();
+ urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
+ urlLoader.addEventListener(Event.COMPLETE, on3DSLoad);
+ urlLoader.addEventListener(IOErrorEvent.IO_ERROR, on3DSError);
+ urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, on3DSError);
+ } else {
+ close();
+ }
+
+ urlLoader.load(new URLRequest(url));
+ }
+
+ private function on3DSLoad(e:Event):void {
+ data = urlLoader.data;
+ data.endian = Endian.LITTLE_ENDIAN;
+ parse3DSChunk(0, data.bytesAvailable);
+ }
+
+ private function on3DSError(e:Event):void {
+ dispatchEvent(e);
+ }
+
+ private function loadBitmaps():void {
+ if (bitmaps != null) {
+ counter = 0;
+ sequence = new Array();
+ for (var filename:String in bitmaps) {
+ sequence.push(filename);
+ }
+ if (loader == null) {
+ loader = new Loader();
+ loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadNextBitmap);
+ loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, loadNextBitmap);
+ loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loadNextBitmap);
+ }
+ loader.load(new URLRequest(path + sequence[counter]), context);
+ } else {
+ buildContent();
+ }
+ }
+
+ private function loadNextBitmap(e:Event = null):void {
+ if (!(e is IOErrorEvent)) {
+ bitmaps[sequence[counter]] = Bitmap(loader.content).bitmapData;
+ } else {
+ if (stubBitmapData == null) {
+ var size:uint = 20;
+ stubBitmapData = new BitmapData(size, size, false, 0);
+ for (var i:uint = 0; i < size; i++) {
+ for (var j:uint = 0; j < size; j+=2) {
+ stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF);
+ }
+ }
+ }
+ bitmaps[sequence[counter]] = stubBitmapData;
+ }
+ counter++;
+ if (counter < sequence.length) {
+ loader.load(new URLRequest(path + sequence[counter]), context);
+ } else {
+ buildContent();
+ }
+ }
+
+ private function buildContent():void {
+ var i:uint;
+ var length:uint;
+
+ // Формируем связи объектов
+ _content = new Object3D();
+
+ // Создаём материалы
+ var materialData:MaterialData;
+ for (var materialName:String in materialDatas) {
+ materialData = materialDatas[materialName];
+ var mapData:MapData = materialData.diffuseMap;
+ var materialMatrix:Matrix = new Matrix();
+ if (mapData != null) {
+ var rot:Number = MathUtils.toRadian(mapData.rotation);
+ var rotSin:Number = Math.sin(rot);
+ var rotCos:Number = Math.cos(rot);
+ materialMatrix.translate(-mapData.offsetU, mapData.offsetV);
+ materialMatrix.translate(-0.5, -0.5);
+ materialMatrix.rotate(-rot);
+ materialMatrix.scale(mapData.scaleU, mapData.scaleV);
+ materialMatrix.translate(0.5, 0.5);
+ }
+ materialData.matrix = materialMatrix;
+ }
+
+ // Если есть данные об анимации и иерархии объектов
+ var objectName:String;
+ var objectData:ObjectData;
+ var mesh:Mesh;
+ if (animationDatas != null) {
+ if (objectDatas != null) {
+
+ length = animationDatas.length;
+ for (i = 0; i < length; i++) {
+ var animationData:AnimationData = animationDatas[i];
+ objectName = animationData.objectName;
+ objectData = objectDatas[objectName];
+
+ // Если на один объект приходится несколько данных об анимации
+ if (objectData != null) {
+ var nameCounter:uint = 2;
+ for (var j:uint = i + 1; j < length; j++) {
+ var animationData2:AnimationData = animationDatas[j];
+ if (objectName == animationData2.objectName) {
+ var newName:String = objectName + nameCounter;
+ var newObjectData:ObjectData = new ObjectData();
+ animationData2.objectName = newName;
+ newObjectData.name = newName;
+ if (objectData.vertices != null) {
+ newObjectData.vertices = new Array().concat(objectData.vertices);
+ }
+ if (objectData.uvs != null) {
+ newObjectData.uvs = new Array().concat(objectData.uvs);
+ }
+ if (objectData.matrix != null) {
+ newObjectData.matrix = objectData.matrix.clone();
+ }
+ if (objectData.faces != null) {
+ newObjectData.faces = new Array().concat(objectData.faces);
+ }
+ if (objectData.surfaces != null) {
+ newObjectData.surfaces = objectData.surfaces.clone();
+ }
+ objectDatas[newName] = newObjectData;
+ nameCounter++;
+ }
+ }
+ }
+
+ if (objectData != null && objectData.vertices != null) {
+ // Меш
+ mesh = new Mesh(objectName);
+ animationData.object = mesh;
+ buildObject(animationData);
+ buildMesh(mesh, objectData, animationData);
+ } else {
+ var object:Object3D = new Object3D(objectName);
+ animationData.object = object;
+ buildObject(animationData);
+ }
+ }
+
+ buildHierarchy(_content, 0, length - 1);
+ }
+ } else {
+ for (objectName in objectDatas) {
+ objectData = objectDatas[objectName];
+ if (objectData.vertices != null) {
+ // Меш
+ mesh = new Mesh(objectName);
+ buildMesh(mesh, objectData, null);
+ _content.addChild(mesh);
+ }
+ }
+ }
+
+ // Рассылаем событие о завершении
+ dispatchEvent(new Event(Event.COMPLETE));
+ }
+
+ private function buildObject(animationData:AnimationData):void {
+ var object:Object3D = animationData.object;
+ if (animationData.position != null) {
+ object.x = animationData.position.x * units;
+ object.y = animationData.position.y * units;
+ object.z = animationData.position.z * units;
+ }
+ if (animationData.rotation != null) {
+ object.rotationX = animationData.rotation.x;
+ object.rotationY = animationData.rotation.y;
+ object.rotationZ = animationData.rotation.z;
+ }
+ if (animationData.scale != null) {
+ object.scaleX = animationData.scale.x;
+ object.scaleY = animationData.scale.y;
+ object.scaleZ = animationData.scale.z;
+ }
+ object.mobility = mobility;
+ }
+
+ private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData):void {
+ // Добавляем вершины
+ var i:uint;
+ var key:*;
+ var vertex:Vertex;
+ var face:Face;
+ var length:uint = objectData.vertices.length;
+ for (i = 0; i < length; i++) {
+ var vertexData:Point3D = objectData.vertices[i];
+ objectData.vertices[i] = mesh.createVertex(vertexData.x, vertexData.y, vertexData.z, i);
+ }
+
+ // Коррекция вершин
+ if (animationData != null) {
+ // Инвертируем матрицу
+ objectData.matrix.invert();
+
+ // Вычитаем точку привязки из смещения матрицы
+ if (animationData.pivot != null) {
+ objectData.matrix.d -= animationData.pivot.x;
+ objectData.matrix.h -= animationData.pivot.y;
+ objectData.matrix.l -= animationData.pivot.z;
+ }
+
+ // Трансформируем вершины
+ for (key in mesh._vertices) {
+ vertex = mesh._vertices[key];
+ vertex._coords.transform(objectData.matrix);
+ }
+ }
+ for (key in mesh._vertices) {
+ vertex = mesh._vertices[key];
+ vertex._coords.multiply(units);
+ }
+
+ // Добавляем грани
+ length = objectData.faces.length;
+ for (i = 0; i < length; i++) {
+ var faceData:FaceData = objectData.faces[i];
+ face = mesh.createFace([objectData.vertices[faceData.a], objectData.vertices[faceData.b], objectData.vertices[faceData.c]], i);
+ if (objectData.uvs != null) {
+ face.aUV = objectData.uvs[faceData.a];
+ face.bUV = objectData.uvs[faceData.b];
+ face.cUV = objectData.uvs[faceData.c];
+ }
+ }
+
+ // Добавляем поверхности
+ if (objectData.surfaces != null) {
+ for (var surfaceId:String in objectData.surfaces) {
+ var materialData:MaterialData = materialDatas[surfaceId];
+ var surfaceData:SurfaceData = objectData.surfaces[surfaceId];
+ var surface:Surface = mesh.createSurface(surfaceData.faces, surfaceId);
+ if (materialData.diffuseMap != null) {
+ var bmp:BitmapData = bitmaps[materialData.diffuseMap.filename];
+ surface.material = new TextureMaterial(new Texture(bmp, materialData.diffuseMap.filename), 1 - materialData.transparency/100, repeat, (bmp != stubBitmapData) ? smooth : false, blendMode, -1, 0, precision);
+ length = surfaceData.faces.length;
+ for (i = 0; i < length; i++) {
+ face = mesh.getFaceById(surfaceData.faces[i]);
+ if (face._aUV != null && face._bUV != null && face._cUV != null) {
+ var m:Matrix = materialData.matrix;
+ var x:Number = face.aUV.x;
+ var y:Number = face.aUV.y;
+ face._aUV.x = m.a*x + m.b*y + m.tx;
+ face._aUV.y = m.c*x + m.d*y + m.ty;
+ x = face._bUV.x;
+ y = face._bUV.y;
+ face._bUV.x = m.a*x + m.b*y + m.tx;
+ face._bUV.y = m.c*x + m.d*y + m.ty;
+ x = face._cUV.x;
+ y = face._cUV.y;
+ face._cUV.x = m.a*x + m.b*y + m.tx;
+ face._cUV.y = m.c*x + m.d*y + m.ty;
+ }
+ }
+ } else {
+ surface.material = new FillMaterial(materialDatas[surfaceId].color, 1 - materialData.transparency/100);
+ }
+ }
+ } else {
+ // Поверхность по умолчанию
+ var defaultSurface:Surface = mesh.createSurface();
+ // Добавляем грани
+ for (var faceId:String in mesh._faces) {
+ defaultSurface.addFace(mesh._faces[faceId]);
+ }
+ defaultSurface.material = new WireMaterial(0, 0x7F7F7F);
+ }
+ }
+
+ private function buildHierarchy(parent:Object3D, begin:uint, end:uint):void {
+ if (begin <= end) {
+ var animation:AnimationData = animationDatas[begin];
+ var object:Object3D = animation.object;
+ parent.addChild(object);
+
+ var parentIndex:uint = animation.parentIndex;
+ for (var i:uint = begin + 1; i <= end; i++) {
+ animation = animationDatas[i];
+ if (parentIndex == animation.parentIndex) {
+ buildHierarchy(object, begin + 1, i - 1);
+ buildHierarchy(parent, i, end);
+ return;
+ }
+ }
+ buildHierarchy(object, begin + 1, end);
+ }
+ }
+
+
+ private function parse3DSChunk(index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Главный
+ case 0x4D4D:
+ parseMainChunk(dataIndex, dataLength);
+ break;
+ }
+
+ parse3DSChunk(index + chunkLength, length - chunkLength);
+ } else {
+ // Загрузка битмап
+ loadBitmaps();
+ }
+ }
+
+ private function parseMainChunk(index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Версия
+ case 0x0002:
+ parseVersion(dataIndex);
+ break;
+ // 3D-сцена
+ case 0x3D3D:
+ parse3DChunk(dataIndex, dataLength);
+ break;
+ // Анимация
+ case 0xB000:
+ parseAnimationChunk(dataIndex, dataLength);
+ break;
+ }
+
+ parseMainChunk(index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseVersion(index:uint):void {
+ data.position = index;
+ version = data.readUnsignedInt();
+ }
+
+ private function parse3DChunk(index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Материал
+ case 0xAFFF:
+ // Парсим материал
+ var material:MaterialData = new MaterialData();
+ parseMaterialChunk(material, dataIndex, dataLength);
+ break;
+ // Объект
+ case 0x4000:
+ // Создаём данные объекта
+ var object:ObjectData = new ObjectData();
+ var objectLength:uint = parseObject(object, dataIndex);
+ // Парсим объект
+ parseObjectChunk(object, dataIndex + objectLength, dataLength - objectLength);
+ break;
+ }
+
+ parse3DChunk(index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseMaterialChunk(material:MaterialData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+ switch (chunkId) {
+ // Имя материала
+ case 0xA000:
+ parseMaterialName(material, dataIndex);
+ break;
+ // Ambient color
+ case 0xA010:
+ break;
+ // Diffuse color
+ case 0xA020:
+ data.position = dataIndex + 6;
+ material.color = ColorUtils.rgb(data.readUnsignedByte(), data.readUnsignedByte(), data.readUnsignedByte());
+ break;
+ // Specular color
+ case 0xA030:
+ break;
+ // Shininess percent
+ case 0xA040:
+ data.position = dataIndex + 6;
+ material.glossiness = data.readUnsignedShort();
+ break;
+ // Shininess strength percent
+ case 0xA041:
+ data.position = dataIndex + 6;
+ material.specular = data.readUnsignedShort();
+ break;
+ // Transperensy
+ case 0xA050:
+ data.position = dataIndex + 6;
+ material.transparency = data.readUnsignedShort();
+ break;
+ // Texture map 1
+ case 0xA200:
+ material.diffuseMap = new MapData();
+ parseMapChunk(material.diffuseMap, dataIndex, dataLength);
+ break;
+ // Texture map 2
+ case 0xA33A:
+ break;
+ // Opacity map
+ case 0xA210:
+ break;
+ // Bump map
+ case 0xA230:
+ //material.normalMap = new MapData();
+ //parseMapChunk(material.normalMap, dataIndex, dataLength);
+ break;
+ // Shininess map
+ case 0xA33C:
+ break;
+ // Specular map
+ case 0xA204:
+ break;
+ // Self-illumination map
+ case 0xA33D:
+ break;
+ // Reflection map
+ case 0xA220:
+ break;
+ }
+
+ parseMaterialChunk(material, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseMaterialName(material:MaterialData, index:uint):void {
+ // Создаём список материалов, если надо
+ if (materialDatas == null) {
+ materialDatas = new Array();
+ }
+ // Получаем название материала
+ material.name = getString(index);
+ // Помещаем данные материала в список
+ materialDatas[material.name] = material;
+ }
+
+ private function parseMapChunk(map:MapData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Имя файла
+ case 0xA300:
+ map.filename = getString(dataIndex).toLowerCase();
+ if (bitmaps == null) {
+ bitmaps = new Array();
+ }
+ bitmaps[map.filename] = null;
+ break;
+ // Масштаб по U
+ case 0xA354:
+ data.position = dataIndex;
+ map.scaleU = data.readFloat();
+ break;
+ // Масштаб по V
+ case 0xA356:
+ data.position = dataIndex;
+ map.scaleV = data.readFloat();
+ break;
+ // Смещение по U
+ case 0xA358:
+ data.position = dataIndex;
+ map.offsetU = data.readFloat();
+ break;
+ // Смещение по V
+ case 0xA35A:
+ data.position = dataIndex;
+ map.offsetV = data.readFloat();
+ break;
+ // Угол поворота
+ case 0xA35C:
+ data.position = dataIndex;
+ map.rotation = data.readFloat();
+ break;
+ }
+
+ parseMapChunk(map, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseObject(object:ObjectData, index:uint):uint {
+ // Создаём список объектов, если надо
+ if (objectDatas == null) {
+ objectDatas = new Map();
+ }
+ // Получаем название объекта
+ object.name = getString(index);
+ // Помещаем данные объекта в список
+ objectDatas[object.name] = object;
+ return object.name.length + 1;
+ }
+
+ private function parseObjectChunk(object:ObjectData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Меш
+ case 0x4100:
+ parseMeshChunk(object, dataIndex, dataLength);
+ break;
+ // Источник света
+ case 0x4600:
+ break;
+ // Камера
+ case 0x4700:
+ break;
+ }
+
+ parseObjectChunk(object, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseMeshChunk(object:ObjectData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Вершины
+ case 0x4110:
+ parseVertices(object, dataIndex);
+ break;
+ // UV
+ case 0x4140:
+ parseUVs(object, dataIndex);
+ break;
+ // Трансформация
+ case 0x4160:
+ parseMatrix(object, dataIndex);
+ break;
+ // Грани
+ case 0x4120:
+ var facesLength:uint = parseFaces(object, dataIndex);
+ parseFacesChunk(object, dataIndex + facesLength, dataLength - facesLength);
+ break;
+ }
+
+ parseMeshChunk(object, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseVertices(object:ObjectData, index:uint):void {
+ data.position = index;
+ var num:uint = data.readUnsignedShort();
+ object.vertices = new Array();
+ for (var i:uint = 0; i < num; i++) {
+ object.vertices.push(new Point3D(data.readFloat(), data.readFloat(), data.readFloat()));
+ }
+ }
+
+ private function parseUVs(object:ObjectData, index:uint):void {
+ data.position = index;
+ var num:uint = data.readUnsignedShort();
+ object.uvs = new Array();
+ for (var i:uint = 0; i < num; i++) {
+ object.uvs.push(new Point(data.readFloat(), data.readFloat()));
+ }
+ }
+
+ private function parseMatrix(object:ObjectData, index:uint):void {
+ data.position = index;
+ object.matrix = new Matrix3D();
+ object.matrix.a = data.readFloat();
+ object.matrix.e = data.readFloat();
+ object.matrix.i = data.readFloat();
+ object.matrix.b = data.readFloat();
+ object.matrix.f = data.readFloat();
+ object.matrix.j = data.readFloat();
+ object.matrix.c = data.readFloat();
+ object.matrix.g = data.readFloat();
+ object.matrix.k = data.readFloat();
+ object.matrix.d = data.readFloat();
+ object.matrix.h = data.readFloat();
+ object.matrix.l = data.readFloat();
+ }
+
+ private function parseFaces(object:ObjectData, index:uint):uint {
+ data.position = index;
+ var num:uint = data.readUnsignedShort();
+ object.faces = new Array();
+ for (var i:uint = 0; i < num; i++) {
+ var face:FaceData = new FaceData();
+ face.a = data.readUnsignedShort();
+ face.b = data.readUnsignedShort();
+ face.c = data.readUnsignedShort();
+ object.faces.push(face);
+ data.position += 2; // Пропускаем флаг
+ }
+ return 2 + num*8;
+ }
+
+ private function parseFacesChunk(object:ObjectData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Поверхности
+ case 0x4130:
+ parseSurface(object, dataIndex);
+ break;
+ // Группы сглаживания
+ case 0x4150:
+ break;
+ }
+
+ parseFacesChunk(object, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseSurface(object:ObjectData, index:uint):void {
+ // Создаём данные поверхности
+ var surface:SurfaceData = new SurfaceData();
+ // Создаём список поверхностей, если надо
+ if (object.surfaces == null) {
+ object.surfaces = new Map();
+ }
+ // Получаем название материала
+ surface.materialName = getString(index);
+ // Помещаем данные поверхности в список
+ object.surfaces[surface.materialName] = surface;
+
+ // Получаем грани поверхности
+ data.position = index + surface.materialName.length + 1;
+ var num:uint = data.readUnsignedShort();
+ surface.faces = new Array();
+ for (var i:uint = 0; i < num; i++) {
+ surface.faces.push(data.readUnsignedShort());
+ }
+ }
+
+ private function parseAnimationChunk(index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+ switch (chunkId) {
+ // Анимация объекта
+ case 0xB001:
+ case 0xB002:
+ case 0xB003:
+ case 0xB004:
+ case 0xB005:
+ case 0xB006:
+ case 0xB007:
+ var animation:AnimationData = new AnimationData();
+ if (animationDatas == null) {
+ animationDatas = new Array();
+ }
+ animationDatas.push(animation);
+ parseObjectAnimationChunk(animation, dataIndex, dataLength);
+ break;
+
+ // Таймлайн
+ case 0xB008:
+ break;
+ }
+
+ parseAnimationChunk(index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseObjectAnimationChunk(animation:AnimationData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+ switch (chunkId) {
+ // Идентификация объекта и его связь
+ case 0xB010:
+ parseObjectAnimationInfo(animation, dataIndex);
+ break;
+ // Точка привязки объекта (pivot)
+ case 0xB013:
+ parseObjectAnimationPivot(animation, dataIndex);
+ break;
+ // Смещение объекта относительно родителя
+ case 0xB020:
+ parseObjectAnimationPosition(animation, dataIndex);
+ break;
+ // Поворот объекта относительно родителя (angle-axis)
+ case 0xB021:
+ parseObjectAnimationRotation(animation, dataIndex);
+ break;
+ // Масштабирование объекта относительно родителя
+ case 0xB022:
+ parseObjectAnimationScale(animation, dataIndex);
+ break;
+ }
+
+ parseObjectAnimationChunk(animation, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseObjectAnimationInfo(animation:AnimationData, index:uint):void {
+ var name:String = getString(index);
+ data.position = index + name.length + 1 + 4;
+ animation.objectName = name;
+ animation.parentIndex = data.readUnsignedShort();
+ }
+
+ private function parseObjectAnimationPivot(animation:AnimationData, index:uint):void {
+ data.position = index;
+ animation.pivot = new Point3D(data.readFloat(), data.readFloat(), data.readFloat());
+ }
+
+ private function parseObjectAnimationPosition(animation:AnimationData, index:uint):void {
+ data.position = index + 20;
+ animation.position = new Point3D(data.readFloat(), data.readFloat(), data.readFloat());
+ }
+
+ private function parseObjectAnimationRotation(animation:AnimationData, index:uint):void {
+ data.position = index + 20;
+ animation.rotation = getRotationFrom3DSAngleAxis(data.readFloat(), data.readFloat(), data.readFloat(), data.readFloat());
+ }
+
+ private function parseObjectAnimationScale(animation:AnimationData, index:uint):void {
+ data.position = index + 20;
+ animation.scale = new Point3D(data.readFloat(), data.readFloat(), data.readFloat());
+ }
+
+ /**
+ * Объект-контейнер, содержащий все загруженные объекты.
+ */
+ public function get content():Object3D {
+ return _content;
+ }
+
+ // Считываем строку заканчивающуюся на нулевой байт
+ private function getString(index:uint):String {
+ data.position = index;
+ var charCode:uint = data.readByte();
+ var res:String = "";
+ while (charCode != 0) {
+ res += String.fromCharCode(charCode);
+ charCode = data.readByte();
+ }
+ return res;
+ }
+
+ private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Point3D {
+ var res:Point3D = new Point3D();
+ var s:Number = Math.sin(angle);
+ var c:Number = Math.cos(angle);
+ var t:Number = 1 - c;
+ var k:Number = x*y*t + z*s;
+ var half:Number;
+ if (k > 0.998) {
+ half = angle/2;
+ res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half));
+ res.y = -Math.PI/2;
+ res.x = 0;
+ return res;
+ }
+ if (k < -0.998) {
+ half = angle/2;
+ res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half));
+ res.y = Math.PI/2;
+ res.x = 0;
+ return res;
+ }
+ res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t);
+ res.y = -Math.asin(x*y*t + z*s);
+ res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t);
+ return res;
+ }
+
+ }
+}
+
+import alternativa.engine3d.core.Object3D;
+import alternativa.types.Matrix3D;
+import alternativa.types.Point3D;
+
+import flash.geom.Matrix;
+import alternativa.types.Map;
+
+class MaterialData {
+ public var name:String;
+ public var color:uint;
+ public var specular:uint;
+ public var glossiness:uint;
+ public var transparency:uint;
+ public var diffuseMap:MapData;
+ //public var normalMap:MapData;
+ public var matrix:Matrix;
+}
+
+class MapData {
+ public var filename:String;
+ public var scaleU:Number = 1;
+ public var scaleV:Number = 1;
+ public var offsetU:Number = 0;
+ public var offsetV:Number = 0;
+ public var rotation:Number = 0;
+}
+
+class ObjectData {
+ public var name:String;
+ public var vertices:Array;
+ public var uvs:Array;
+ public var matrix:Matrix3D;
+ public var faces:Array;
+ public var surfaces:Map;
+}
+
+class FaceData {
+ public var a:uint;
+ public var b:uint;
+ public var c:uint;
+}
+
+class SurfaceData {
+ public var materialName:String;
+ public var faces:Array;
+}
+
+class AnimationData {
+ public var objectName:String;
+ public var object:Object3D;
+ public var parentIndex:uint;
+ public var pivot:Point3D;
+ public var position:Point3D;
+ public var rotation:Point3D;
+ public var scale:Point3D;
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/loaders/LoaderMTL.as b/Alternativa3D5/5.4/alternativa/engine3d/loaders/LoaderMTL.as
new file mode 100644
index 0000000..2a7d6d5
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/loaders/LoaderMTL.as
@@ -0,0 +1,285 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.*;
+ import alternativa.types.Map;
+ import alternativa.utils.ColorUtils;
+
+ import flash.display.Bitmap;
+ import flash.display.BitmapData;
+ import flash.display.Loader;
+ import flash.events.ErrorEvent;
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IOErrorEvent;
+ import flash.events.SecurityErrorEvent;
+ import flash.geom.Point;
+ import flash.net.URLLoader;
+ import flash.net.URLRequest;
+ import flash.system.LoaderContext;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ * Загрузчик библиотеки материалов из файлов в формате MTL material format (Lightwave, OBJ).
+ *
+ * На данный момент обеспечивается загрузка цвета, прозрачности и диффузной текстуры материала.
+ */
+ internal class LoaderMTL extends EventDispatcher {
+
+ private static const COMMENT_CHAR:String = "#";
+ private static const CMD_NEW_MATERIAL:String = "newmtl";
+ private static const CMD_DIFFUSE_REFLECTIVITY:String = "Kd";
+ private static const CMD_DISSOLVE:String = "d";
+ private static const CMD_MAP_DIFFUSE:String = "map_Kd";
+
+ private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/;
+ private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/;
+ private static const REGEXP_SPLIT_LINE:RegExp = /\s+/;
+
+ // Загрузчик файла MTL
+ private var fileLoader:URLLoader;
+ // Загрузчик файлов текстур
+ private var bitmapLoader:Loader;
+ // Контекст загрузки для bitmapLoader
+ private var loaderContext:LoaderContext;
+ // Базовый URL файла MTL
+ private var baseUrl:String;
+
+ // Библиотека загруженных материалов
+ private var _library:Map;
+ // Список материалов, имеющих диффузные текстуры
+ private var diffuseMaps:Map;
+ // Имя текущего материала
+ private var materialName:String;
+ // параметры текущего материала
+ private var currentMaterialInfo:MaterialInfo = new MaterialInfo();
+
+ alternativa3d static var stubBitmapData:BitmapData;
+
+ /**
+ * Создаёт новый экземпляр класса.
+ */
+ public function LoaderMTL() {
+ }
+
+ /**
+ * Прекращение текущей загрузки.
+ */
+ public function close():void {
+ try {
+ fileLoader.close();
+ } catch (e:Error) {
+ }
+ }
+
+ /**
+ * Библиотека материалов. Ключами являются наименования материалов, значениями -- объекты, наследники класса
+ *
+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения
+ * Если происходит ошибка при загрузке файла текстуры, то соответствующая текстура заменяется на текстуру-заглушку.
+ *
+ * @param url URL MTL-файла
+ * @param loaderContext LoaderContext для загрузки файлов текстур
+ *
+ * @see #library
+ */
+ public function load(url:String, loaderContext:LoaderContext = null):void {
+ this.loaderContext = loaderContext;
+ baseUrl = url.substring(0, url.lastIndexOf("/") + 1);
+
+ if (fileLoader == null) {
+ fileLoader = new URLLoader();
+ fileLoader.addEventListener(Event.COMPLETE, parseMTLFile);
+ fileLoader.addEventListener(IOErrorEvent.IO_ERROR, onError);
+ fileLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
+
+ bitmapLoader = new Loader();
+ bitmapLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onBitmapLoadComplete);
+ bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onBitmapLoadComplete);
+ bitmapLoader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onBitmapLoadComplete);
+ }
+
+ try {
+ fileLoader.close();
+ bitmapLoader.close();
+ } catch (e:Error) {
+ // Пропуск ошибки при попытке закрытия неактивных загрузчиков
+ }
+
+ fileLoader.load(new URLRequest(url));
+ }
+
+ /**
+ * Разбор содержимого загруженного файла материалов.
+ */
+ private function parseMTLFile(e:Event = null):void {
+ var lines:Array = fileLoader.data.split(REGEXP_SPLIT_FILE);
+ _library = new Map();
+ diffuseMaps = new Map();
+ for each (var line:String in lines) {
+ parseLine(line);
+ }
+ defineMaterial();
+
+ if (diffuseMaps.isEmpty()) {
+ // Текстур нет, загрузка окончена
+ dispatchEvent(new Event(Event.COMPLETE));
+ } else {
+ // Загрузка файлов текстур
+ loadNextBitmap();
+ }
+ }
+
+ /**
+ * Разбор строки файла.
+ *
+ * @param line строка файла
+ */
+ private function parseLine(line:String):void {
+ line = line.replace(REGEXP_TRIM,"$1")
+ if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) {
+ return;
+ }
+ var parts:Array = line.split(REGEXP_SPLIT_LINE);
+ switch (parts[0]) {
+ case CMD_NEW_MATERIAL:
+ defineMaterial(parts);
+ break;
+ case CMD_DIFFUSE_REFLECTIVITY:
+ readDiffuseReflectivity(parts);
+ break;
+ case CMD_DISSOLVE:
+ readAlpha(parts);
+ break;
+ case CMD_MAP_DIFFUSE:
+ parseDiffuseMapLine(parts);
+ break;
+ }
+ }
+
+ /**
+ * Определение нового материала.
+ */
+ private function defineMaterial(parts:Array = null):void {
+ if (materialName != null) {
+ _library[materialName] = currentMaterialInfo;
+ }
+ if (parts != null) {
+ materialName = parts[1];
+ currentMaterialInfo = new MaterialInfo();
+ }
+ }
+
+ /**
+ * Чтение коэффициентов диффузного отражения. Считываются только коэффициенты, заданные в формате r g b. Для текущей
+ * версии движка данные коэффициенты преобразуются в цвет материала.
+ */
+ private function readDiffuseReflectivity(parts:Array):void {
+ var r:Number = Number(parts[1]);
+ // Проверка, заданы ли коэффициенты в виде r g b
+ if (!isNaN(r)) {
+ var g:Number = Number(parts[2]);
+ var b:Number = Number(parts[3]);
+ currentMaterialInfo.color = ColorUtils.rgb(255 * r, 255 * g, 255 * b);
+ }
+ }
+
+ /**
+ * Чтение коэффициента непрозрачности. Считывается только коэффициент, заданный числом
+ * (не поддерживается параметр -halo).
+ */
+ private function readAlpha(parts:Array):void {
+ var alpha:Number = Number(parts[1]);
+ if (!isNaN(alpha)) {
+ currentMaterialInfo.alpha = alpha;
+ }
+ }
+
+ /**
+ * Разбор строки, задающей текстурную карту для диффузного отражения.
+ */
+ private function parseDiffuseMapLine(parts:Array):void {
+ var info:MTLTextureMapInfo = MTLTextureMapInfo.parse(parts);
+ diffuseMaps[materialName] = info;
+ }
+
+ /**
+ * Загрузка файла следующей текстуры.
+ */
+ private function loadNextBitmap():void {
+ // Установка имени текущего текстурного материала, для которого выполняется загрузка текстуры
+ for (materialName in diffuseMaps) {
+ break;
+ }
+ bitmapLoader.load(new URLRequest(baseUrl + diffuseMaps[materialName].fileName), loaderContext);
+ }
+
+ /**
+ *
+ */
+ private function createStubBitmap():void {
+ if (stubBitmapData == null) {
+ var size:uint = 10;
+ stubBitmapData = new BitmapData(size, size, false, 0);
+ for (var i:uint = 0; i < size; i++) {
+ for (var j:uint = 0; j < size; j+=2) {
+ stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF);
+ }
+ }
+ }
+ }
+
+ /**
+ * Обработка результата загрузки файла текстуры.
+ */
+ private function onBitmapLoadComplete(e:Event):void {
+ var bmd:BitmapData;
+
+ if (e is ErrorEvent) {
+ if (stubBitmapData == null) {
+ createStubBitmap();
+ }
+ bmd = stubBitmapData;
+ } else {
+ bmd = Bitmap(bitmapLoader.content).bitmapData;
+ }
+
+ var mtlInfo:MTLTextureMapInfo = diffuseMaps[materialName];
+ delete diffuseMaps[materialName];
+ var info:MaterialInfo = _library[materialName];
+
+ info.bitmapData = bmd;
+ info.repeat = mtlInfo.repeat;
+ info.mapOffset = new Point(mtlInfo.offsetU, mtlInfo.offsetV);
+ info.mapSize = new Point(mtlInfo.sizeU, mtlInfo.sizeV);
+ info.textureFileName = mtlInfo.fileName;
+
+ if (diffuseMaps.isEmpty()) {
+ dispatchEvent(new Event(Event.COMPLETE));
+ } else {
+ loadNextBitmap();
+ }
+ }
+
+ /**
+ *
+ * @param e
+ */
+ private function onError(e:IOErrorEvent):void {
+ dispatchEvent(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/loaders/LoaderOBJ.as b/Alternativa3D5/5.4/alternativa/engine3d/loaders/LoaderOBJ.as
new file mode 100644
index 0000000..5c05896
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/loaders/LoaderOBJ.as
@@ -0,0 +1,509 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.engine3d.materials.TextureMaterialPrecision;
+ import alternativa.types.Map;
+ import alternativa.types.Point3D;
+ import alternativa.types.Texture;
+
+ import flash.display.BlendMode;
+ import flash.events.ErrorEvent;
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IOErrorEvent;
+ import flash.events.SecurityErrorEvent;
+ import flash.geom.Point;
+ import flash.net.URLLoader;
+ import flash.net.URLRequest;
+ import flash.system.LoaderContext;
+
+ use namespace alternativa3d;
+
+ /**
+ * Загрузчик моделей из файла в формате OBJ. Так как OBJ не поддерживает иерархию объектов, все загруженные
+ * модели помещаются в один контейнер
+ * Поддерживаюся следующие команды формата OBJ:
+ *
+ *
+ * Пример использования:
+ *
+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения
+ * @param url URL OBJ-файла
+ * @param loadMaterials флаг загрузки материалов. Если указано значение Параллелепипед после создания будет содержать в себе шесть поверхностей.
+ * Различные значения параметров позволяют создавать различные примитивы.
+ * Так при установленном параметре По умолчанию параметр После создания примитив всегда содержит в себе поверхность Геоплоскость это плоскость с сетчатой структурой граней. Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров.
+ * При значении Геосфера после создания содержит в себе одну поверхность с идентификатором по умолчанию. Текстурные координаты у геосферы не находятся в промежутке Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров.
+ * При значении После создания примитив содержит в себе одну поверхность с идентификатором по умолчанию. По умолчанию параметр Соответствия локальных осей для объектов, не являющихся камерой:
+ * Соответствия локальных осей для объектов, являющихся камерой:
+ *
+ * Поворот мышью реализован следующим образом: в момент активации режима поворота (нажата левая кнопка мыши или
+ * соответствующая кнопка на клавиатуре) текущее положение курсора становится точкой, относительно которой определяются
+ * дальнейшие отклонения. Отклонение курсора по вертикали в пикселях, умноженное на коэффициент чувствительности мыши
+ * по вертикали даёт угловую скорость по тангажу. Отклонение курсора по горизонтали в пикселях, умноженное на коэффициент
+ * чувствительности мыши по горизонтали даёт угловую скорость по крену.
+ */
+ public class FlyController extends ObjectController {
+ /**
+ * Имя действия для привязки клавиш поворота по крену влево.
+ */
+ public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT";
+ /**
+ * Имя действия для привязки клавиш поворота по крену вправо.
+ */
+ public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT";
+
+ private var _rollLeft:Boolean;
+ private var _rollRight:Boolean;
+
+ private var _rollSpeed:Number = 1;
+
+ private var rotations:Point3D;
+ private var rollMatrix:Matrix3D = new Matrix3D();
+ private var transformation:Matrix3D = new Matrix3D();
+ private var axis:Point3D = new Point3D();
+
+ private var velocity:Point3D = new Point3D();
+ private var displacement:Point3D = new Point3D();
+ private var destination:Point3D = new Point3D();
+ private var deltaVelocity:Point3D = new Point3D();
+ private var accelerationVector:Point3D = new Point3D();
+ private var currentTransform:Matrix3D = new Matrix3D();
+
+ private var _currentSpeed:Number = 1;
+ /**
+ * Текущие координаты мышиного курсора в режиме mouse look.
+ */
+ private var currentMouseCoords:Point3D = new Point3D();
+
+ /**
+ * Модуль вектора ускорния, получаемого от команд движения.
+ */
+ public var acceleration:Number = 1000;
+ /**
+ * Модуль вектора замедляющего ускорения.
+ */
+ public var deceleration:Number = 50;
+ /**
+ * Погрешность определения скорости. Скорость приравнивается к нулю, если её модуль не превышает заданного значения.
+ */
+ public var speedThreshold:Number = 1;
+ /**
+ * Переключение инерционного режима. В инерционном режиме отсутствует замедляющее ускорение, в результате чего вектор
+ * скорости объекта остаётся постоянным, если нет управляющих воздействий. При выключенном инерционном режиме к объекту
+ * прикладывается замедляющее ускорение.
+ */
+ public var inertialMode:Boolean;
+
+ /**
+ * @inheritDoc
+ */
+ public function FlyController(eventsSourceObject:DisplayObject) {
+ super(eventsSourceObject);
+
+ actionBindings[ACTION_ROLL_LEFT] = rollLeft;
+ actionBindings[ACTION_ROLL_RIGHT] = rollRight;
+ }
+
+ /**
+ * Текущая скорость движения.
+ */
+ public function get currentSpeed():Number {
+ return _currentSpeed;
+ }
+
+ /**
+ * Активация вращения по крену влево.
+ */
+ public function rollLeft(value:Boolean):void {
+ _rollLeft = value;
+ }
+
+ /**
+ * Активация вращения по крену вправо.
+ */
+ public function rollRight(value:Boolean):void {
+ _rollRight = value;
+ }
+
+ /**
+ * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
+ *
+ * Алгоритм работы метода следующий:
+ * Контроллер предоставляет два режима движения: режим ходьбы с учётом силы тяжести и режим полёта, в котором сила
+ * тяжести не учитывается. В обоих режимах может быть включена проверка столкновений с объектами сцены. Если проверка
+ * столкновений отключена, то в режиме ходьбы сила тяжести также игнорируется и дополнительно появляется возможность
+ * движения по вертикали.
+ *
+ * Для всех объектов, за исключением Вне зависимости от того, включена проверка столкновений или нет, координаты при перемещении расчитываются для
+ * эллипсоида, параметры которого устанавливаются через свойство Команда Направление камеры совпадает с её локальной осью Z, поэтому только что созданная камера смотрит вверх в системе
+ * координат родителя.
+ *
+ * Для отображения видимой через камеру части сцены на экран, к камере должна быть подключёна область вывода —
+ * экземпляр класса Класс реализует интерфейс Масштабирование, ориентация и положение объекта задаются в родительской системе координат. Результирующая
+ * локальная трансформация является композицией операций масштабирования, поворотов объекта относительно осей
+ * Глобальная трансформация (в системе координат корневого объекта сцены) является композицией трансформаций
+ * самого объекта и всех его предков по иерархии объектов сцены.
+ *
+ * Класс реализует интерфейс Изменением свойства Класс реализует интерфейс Класс предоставляет средства загрузки объектов из 3DS-файла с заданным URL. Если
+ * в модели используются текстуры, то все файлы текстур должны находиться рядом с файлом модели.
+ *
+ * Из файла модели загружаются:
+ *
+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения
+ * @param url URL 3DS-файла
+ * @param context LoaderContext для загрузки файлов текстур
+ */
+ public function load(url:String, context:LoaderContext = null):void {
+ path = url.substring(0, url.lastIndexOf("/") + 1);
+ this.context = context;
+
+ // Очистка
+ version = 0;
+ clean();
+
+ if (modelLoader == null) {
+ modelLoader = new URLLoader();
+ modelLoader.dataFormat = URLLoaderDataFormat.BINARY;
+ modelLoader.addEventListener(Event.COMPLETE, on3DSLoad);
+ modelLoader.addEventListener(IOErrorEvent.IO_ERROR, on3DSError);
+ modelLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, on3DSError);
+ } else {
+ close();
+ }
+
+ loaderState = STATE_LOADING_MODEL;
+ modelLoader.load(new URLRequest(url));
+ }
+
+ /**
+ * Запуск разбора загруженного 3DS-файла.
+ */
+ private function on3DSLoad(e:Event):void {
+ loaderState = STATE_IDLE;
+ data = modelLoader.data;
+ data.endian = Endian.LITTLE_ENDIAN;
+ parse3DSChunk(0, data.bytesAvailable);
+ }
+
+ /**
+ * Обработка ошибки при загрузке 3DS-файла.
+ */
+ private function on3DSError(e:Event):void {
+ loaderState = STATE_IDLE;
+ dispatchEvent(e);
+ }
+
+ /**
+ * Запуск загрузки файлов текстур.
+ */
+ private function loadBitmaps():void {
+ if (textureLoader == null) {
+ textureLoader = new TextureMapsLoader();
+ textureLoader.addEventListener(Event.COMPLETE, loadNextBitmap);
+ textureLoader.addEventListener(IOErrorEvent.IO_ERROR, loadNextBitmap);
+ }
+
+ // Имена материалов с диффузными текстурами собираются в массив textureMaterialNames
+ bitmaps = new Array();
+ textureMaterialNames = new Array();
+ for each (var materialData:MaterialData in materialDatas) {
+ if (materialData.diffuseMap != null) {
+ textureMaterialNames.push(materialData.name);
+ }
+ }
+
+ loaderState = STATE_LOADING_TEXTURES;
+ loadNextBitmap();
+ }
+
+ /**
+ * Запуск загрузки следующей текстуры в очереди.
+ */
+ private function loadNextBitmap(e:Event = null):void {
+ if (e != null) {
+ if (!(e is IOErrorEvent)) {
+ bitmaps[textureMaterialNames[counter]] = textureLoader.bitmapData;
+ } else {
+ if (stubBitmapData == null) {
+ var size:uint = 20;
+ stubBitmapData = new BitmapData(size, size, false, 0);
+ for (var i:uint = 0; i < size; i++) {
+ for (var j:uint = 0; j < size; j+=2) {
+ stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF);
+ }
+ }
+ }
+ bitmaps[textureMaterialNames[counter]] = stubBitmapData;
+ }
+ } else {
+ counter = -1;
+ }
+ counter++;
+ if (counter < textureMaterialNames.length) {
+ var materialData:MaterialData = materialDatas[textureMaterialNames[counter]];
+ textureLoader.load(path + materialData.diffuseMap.filename, materialData.opacityMap == null ? null : path + materialData.opacityMap.filename, context);
+ } else {
+ loaderState = STATE_IDLE;
+ buildContent();
+ }
+ }
+
+ private function buildContent():void {
+ var i:uint;
+ var length:uint;
+
+ // Формируем связи объектов
+ _content = new Object3D();
+
+ // Создаём материалы
+ var materialData:MaterialData;
+ for (var materialName:String in materialDatas) {
+ materialData = materialDatas[materialName];
+ var mapData:MapData = materialData.diffuseMap;
+ var materialMatrix:Matrix = new Matrix();
+ if (mapData != null) {
+ var rot:Number = MathUtils.toRadian(mapData.rotation);
+ var rotSin:Number = Math.sin(rot);
+ var rotCos:Number = Math.cos(rot);
+ materialMatrix.translate(-mapData.offsetU, mapData.offsetV);
+ materialMatrix.translate(-0.5, -0.5);
+ materialMatrix.rotate(-rot);
+ materialMatrix.scale(mapData.scaleU, mapData.scaleV);
+ materialMatrix.translate(0.5, 0.5);
+ }
+ materialData.matrix = materialMatrix;
+ }
+
+ // Если есть данные об анимации и иерархии объектов
+ var objectName:String;
+ var objectData:ObjectData;
+ var mesh:Mesh;
+ if (animationDatas != null) {
+ if (objectDatas != null) {
+
+ length = animationDatas.length;
+ for (i = 0; i < length; i++) {
+ var animationData:AnimationData = animationDatas[i];
+ objectName = animationData.objectName;
+ objectData = objectDatas[objectName];
+
+ // Если на один объект приходится несколько данных об анимации
+ if (objectData != null) {
+ var nameCounter:uint = 2;
+ for (var j:uint = i + 1; j < length; j++) {
+ var animationData2:AnimationData = animationDatas[j];
+ if (objectName == animationData2.objectName) {
+ var newName:String = objectName + nameCounter;
+ var newObjectData:ObjectData = new ObjectData();
+ animationData2.objectName = newName;
+ newObjectData.name = newName;
+ if (objectData.vertices != null) {
+ newObjectData.vertices = new Array().concat(objectData.vertices);
+ }
+ if (objectData.uvs != null) {
+ newObjectData.uvs = new Array().concat(objectData.uvs);
+ }
+ if (objectData.matrix != null) {
+ newObjectData.matrix = objectData.matrix.clone();
+ }
+ if (objectData.faces != null) {
+ newObjectData.faces = new Array().concat(objectData.faces);
+ }
+ if (objectData.surfaces != null) {
+ newObjectData.surfaces = objectData.surfaces.clone();
+ }
+ objectDatas[newName] = newObjectData;
+ nameCounter++;
+ }
+ }
+ }
+
+ if (objectData != null && objectData.vertices != null) {
+ // Меш
+ mesh = new Mesh(objectName);
+ animationData.object = mesh;
+ buildObject(animationData);
+ buildMesh(mesh, objectData, animationData);
+ } else {
+ var object:Object3D = new Object3D(objectName);
+ animationData.object = object;
+ buildObject(animationData);
+ }
+ }
+
+ buildHierarchy(_content, 0, length - 1);
+ }
+ } else {
+ for (objectName in objectDatas) {
+ objectData = objectDatas[objectName];
+ if (objectData.vertices != null) {
+ // Меш
+ mesh = new Mesh(objectName);
+ buildMesh(mesh, objectData, null);
+ _content.addChild(mesh);
+ }
+ }
+ }
+
+ // Рассылаем событие о завершении
+ dispatchEvent(new Event(Event.COMPLETE));
+ }
+
+ private function buildObject(animationData:AnimationData):void {
+ var object:Object3D = animationData.object;
+ if (animationData.position != null) {
+ object.x = animationData.position.x * units;
+ object.y = animationData.position.y * units;
+ object.z = animationData.position.z * units;
+ }
+ if (animationData.rotation != null) {
+ object.rotationX = animationData.rotation.x;
+ object.rotationY = animationData.rotation.y;
+ object.rotationZ = animationData.rotation.z;
+ }
+ if (animationData.scale != null) {
+ object.scaleX = animationData.scale.x;
+ object.scaleY = animationData.scale.y;
+ object.scaleZ = animationData.scale.z;
+ }
+ object.mobility = mobility;
+ }
+
+ private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData):void {
+ // Добавляем вершины
+ var i:uint;
+ var key:*;
+ var vertex:Vertex;
+ var face:Face;
+ var length:uint = objectData.vertices.length;
+ for (i = 0; i < length; i++) {
+ var vertexData:Point3D = objectData.vertices[i];
+ objectData.vertices[i] = mesh.createVertex(vertexData.x, vertexData.y, vertexData.z, i);
+ }
+
+ // Коррекция вершин
+ if (animationData != null) {
+ // Инвертируем матрицу
+ objectData.matrix.invert();
+
+ // Вычитаем точку привязки из смещения матрицы
+ if (animationData.pivot != null) {
+ objectData.matrix.d -= animationData.pivot.x;
+ objectData.matrix.h -= animationData.pivot.y;
+ objectData.matrix.l -= animationData.pivot.z;
+ }
+
+ // Трансформируем вершины
+ for (key in mesh._vertices) {
+ vertex = mesh._vertices[key];
+ vertex._coords.transform(objectData.matrix);
+ }
+ }
+ for (key in mesh._vertices) {
+ vertex = mesh._vertices[key];
+ vertex._coords.multiply(units);
+ }
+
+ // Добавляем грани
+ length = objectData.faces.length;
+ for (i = 0; i < length; i++) {
+ var faceData:FaceData = objectData.faces[i];
+ face = mesh.createFace([objectData.vertices[faceData.a], objectData.vertices[faceData.b], objectData.vertices[faceData.c]], i);
+ if (objectData.uvs != null) {
+ face.aUV = objectData.uvs[faceData.a];
+ face.bUV = objectData.uvs[faceData.b];
+ face.cUV = objectData.uvs[faceData.c];
+ }
+ }
+
+ // Добавляем поверхности
+ if (objectData.surfaces != null) {
+ for (var surfaceId:String in objectData.surfaces) {
+ var materialData:MaterialData = materialDatas[surfaceId];
+ var surfaceData:SurfaceData = objectData.surfaces[surfaceId];
+ var surface:Surface = mesh.createSurface(surfaceData.faces, surfaceId);
+ if (materialData.diffuseMap != null) {
+ var bmp:BitmapData = bitmaps[materialData.name];
+ surface.material = new TextureMaterial(new Texture(bmp, materialData.diffuseMap.filename), 1 - materialData.transparency/100, repeat, (bmp != stubBitmapData) ? smooth : false, blendMode, -1, 0, precision);
+ length = surfaceData.faces.length;
+ for (i = 0; i < length; i++) {
+ face = mesh.getFaceById(surfaceData.faces[i]);
+ if (face._aUV != null && face._bUV != null && face._cUV != null) {
+ var m:Matrix = materialData.matrix;
+ var x:Number = face.aUV.x;
+ var y:Number = face.aUV.y;
+ face._aUV.x = m.a*x + m.b*y + m.tx;
+ face._aUV.y = m.c*x + m.d*y + m.ty;
+ x = face._bUV.x;
+ y = face._bUV.y;
+ face._bUV.x = m.a*x + m.b*y + m.tx;
+ face._bUV.y = m.c*x + m.d*y + m.ty;
+ x = face._cUV.x;
+ y = face._cUV.y;
+ face._cUV.x = m.a*x + m.b*y + m.tx;
+ face._cUV.y = m.c*x + m.d*y + m.ty;
+ }
+ }
+ } else {
+ surface.material = new FillMaterial(materialDatas[surfaceId].color, 1 - materialData.transparency/100);
+ }
+ }
+ } else {
+ // Поверхность по умолчанию
+ var defaultSurface:Surface = mesh.createSurface();
+ // Добавляем грани
+ for (var faceId:String in mesh._faces) {
+ defaultSurface.addFace(mesh._faces[faceId]);
+ }
+ defaultSurface.material = new WireMaterial(0, 0x7F7F7F);
+ }
+ }
+
+ private function buildHierarchy(parent:Object3D, begin:uint, end:uint):void {
+ if (begin <= end) {
+ var animation:AnimationData = animationDatas[begin];
+ var object:Object3D = animation.object;
+ parent.addChild(object);
+
+ var parentIndex:uint = animation.parentIndex;
+ for (var i:uint = begin + 1; i <= end; i++) {
+ animation = animationDatas[i];
+ if (parentIndex == animation.parentIndex) {
+ buildHierarchy(object, begin + 1, i - 1);
+ buildHierarchy(parent, i, end);
+ return;
+ }
+ }
+ buildHierarchy(object, begin + 1, end);
+ }
+ }
+
+ private function parse3DSChunk(index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Главный
+ case 0x4D4D:
+ parseMainChunk(dataIndex, dataLength);
+ break;
+ }
+
+ parse3DSChunk(index + chunkLength, length - chunkLength);
+ } else {
+ // Загрузка битмап
+ loadBitmaps();
+ }
+ }
+
+ private function parseMainChunk(index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Версия
+ case 0x0002:
+ parseVersion(dataIndex);
+ break;
+ // 3D-сцена
+ case 0x3D3D:
+ parse3DChunk(dataIndex, dataLength);
+ break;
+ // Анимация
+ case 0xB000:
+ parseAnimationChunk(dataIndex, dataLength);
+ break;
+ }
+
+ parseMainChunk(index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseVersion(index:uint):void {
+ data.position = index;
+ version = data.readUnsignedInt();
+ }
+
+ private function parse3DChunk(index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Материал
+ case 0xAFFF:
+ // Парсим материал
+ var material:MaterialData = new MaterialData();
+ parseMaterialChunk(material, dataIndex, dataLength);
+ break;
+ // Объект
+ case 0x4000:
+ // Создаём данные объекта
+ var object:ObjectData = new ObjectData();
+ var objectLength:uint = parseObject(object, dataIndex);
+ // Парсим объект
+ parseObjectChunk(object, dataIndex + objectLength, dataLength - objectLength);
+ break;
+ }
+
+ parse3DChunk(index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseMaterialChunk(material:MaterialData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+ switch (chunkId) {
+ // Имя материала
+ case 0xA000:
+ parseMaterialName(material, dataIndex);
+ break;
+ // Ambient color
+ case 0xA010:
+ break;
+ // Diffuse color
+ case 0xA020:
+ data.position = dataIndex + 6;
+ material.color = ColorUtils.rgb(data.readUnsignedByte(), data.readUnsignedByte(), data.readUnsignedByte());
+ break;
+ // Specular color
+ case 0xA030:
+ break;
+ // Shininess percent
+ case 0xA040:
+ data.position = dataIndex + 6;
+ material.glossiness = data.readUnsignedShort();
+ break;
+ // Shininess strength percent
+ case 0xA041:
+ data.position = dataIndex + 6;
+ material.specular = data.readUnsignedShort();
+ break;
+ // Transperensy
+ case 0xA050:
+ data.position = dataIndex + 6;
+ material.transparency = data.readUnsignedShort();
+ break;
+ // Texture map 1
+ case 0xA200:
+ material.diffuseMap = new MapData();
+ parseMapChunk(material.name, material.diffuseMap, dataIndex, dataLength);
+ break;
+ // Texture map 2
+ case 0xA33A:
+ break;
+ // Opacity map
+ case 0xA210:
+ material.opacityMap = new MapData();
+ parseMapChunk(material.name, material.opacityMap, dataIndex, dataLength);
+ break;
+ // Bump map
+ case 0xA230:
+ //material.normalMap = new MapData();
+ //parseMapChunk(material.normalMap, dataIndex, dataLength);
+ break;
+ // Shininess map
+ case 0xA33C:
+ break;
+ // Specular map
+ case 0xA204:
+ break;
+ // Self-illumination map
+ case 0xA33D:
+ break;
+ // Reflection map
+ case 0xA220:
+ break;
+ }
+
+ parseMaterialChunk(material, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseMaterialName(material:MaterialData, index:uint):void {
+ // Создаём список материалов, если надо
+ if (materialDatas == null) {
+ materialDatas = new Array();
+ }
+ // Получаем название материала
+ material.name = getString(index);
+ // Помещаем данные материала в список
+ materialDatas[material.name] = material;
+ }
+
+ private function parseMapChunk(materialName:String, map:MapData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Имя файла
+ case 0xA300:
+ map.filename = getString(dataIndex).toLowerCase();
+ break;
+ // Масштаб по U
+ case 0xA354:
+ data.position = dataIndex;
+ map.scaleU = data.readFloat();
+ break;
+ // Масштаб по V
+ case 0xA356:
+ data.position = dataIndex;
+ map.scaleV = data.readFloat();
+ break;
+ // Смещение по U
+ case 0xA358:
+ data.position = dataIndex;
+ map.offsetU = data.readFloat();
+ break;
+ // Смещение по V
+ case 0xA35A:
+ data.position = dataIndex;
+ map.offsetV = data.readFloat();
+ break;
+ // Угол поворота
+ case 0xA35C:
+ data.position = dataIndex;
+ map.rotation = data.readFloat();
+ break;
+ }
+
+ parseMapChunk(materialName, map, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseObject(object:ObjectData, index:uint):uint {
+ // Создаём список объектов, если надо
+ if (objectDatas == null) {
+ objectDatas = new Map();
+ }
+ // Получаем название объекта
+ object.name = getString(index);
+ // Помещаем данные объекта в список
+ objectDatas[object.name] = object;
+ return object.name.length + 1;
+ }
+
+ private function parseObjectChunk(object:ObjectData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Меш
+ case 0x4100:
+ parseMeshChunk(object, dataIndex, dataLength);
+ break;
+ // Источник света
+ case 0x4600:
+ break;
+ // Камера
+ case 0x4700:
+ break;
+ }
+
+ parseObjectChunk(object, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseMeshChunk(object:ObjectData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Вершины
+ case 0x4110:
+ parseVertices(object, dataIndex);
+ break;
+ // UV
+ case 0x4140:
+ parseUVs(object, dataIndex);
+ break;
+ // Трансформация
+ case 0x4160:
+ parseMatrix(object, dataIndex);
+ break;
+ // Грани
+ case 0x4120:
+ var facesLength:uint = parseFaces(object, dataIndex);
+ parseFacesChunk(object, dataIndex + facesLength, dataLength - facesLength);
+ break;
+ }
+
+ parseMeshChunk(object, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseVertices(object:ObjectData, index:uint):void {
+ data.position = index;
+ var num:uint = data.readUnsignedShort();
+ object.vertices = new Array();
+ for (var i:uint = 0; i < num; i++) {
+ object.vertices.push(new Point3D(data.readFloat(), data.readFloat(), data.readFloat()));
+ }
+ }
+
+ private function parseUVs(object:ObjectData, index:uint):void {
+ data.position = index;
+ var num:uint = data.readUnsignedShort();
+ object.uvs = new Array();
+ for (var i:uint = 0; i < num; i++) {
+ object.uvs.push(new Point(data.readFloat(), data.readFloat()));
+ }
+ }
+
+ private function parseMatrix(object:ObjectData, index:uint):void {
+ data.position = index;
+ object.matrix = new Matrix3D();
+ object.matrix.a = data.readFloat();
+ object.matrix.e = data.readFloat();
+ object.matrix.i = data.readFloat();
+ object.matrix.b = data.readFloat();
+ object.matrix.f = data.readFloat();
+ object.matrix.j = data.readFloat();
+ object.matrix.c = data.readFloat();
+ object.matrix.g = data.readFloat();
+ object.matrix.k = data.readFloat();
+ object.matrix.d = data.readFloat();
+ object.matrix.h = data.readFloat();
+ object.matrix.l = data.readFloat();
+ }
+
+ private function parseFaces(object:ObjectData, index:uint):uint {
+ data.position = index;
+ var num:uint = data.readUnsignedShort();
+ object.faces = new Array();
+ for (var i:uint = 0; i < num; i++) {
+ var face:FaceData = new FaceData();
+ face.a = data.readUnsignedShort();
+ face.b = data.readUnsignedShort();
+ face.c = data.readUnsignedShort();
+ object.faces.push(face);
+ data.position += 2; // Пропускаем флаг
+ }
+ return 2 + num*8;
+ }
+
+ private function parseFacesChunk(object:ObjectData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+
+ switch (chunkId) {
+ // Поверхности
+ case 0x4130:
+ parseSurface(object, dataIndex);
+ break;
+ // Группы сглаживания
+ case 0x4150:
+ break;
+ }
+
+ parseFacesChunk(object, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseSurface(object:ObjectData, index:uint):void {
+ // Создаём данные поверхности
+ var surface:SurfaceData = new SurfaceData();
+ // Создаём список поверхностей, если надо
+ if (object.surfaces == null) {
+ object.surfaces = new Map();
+ }
+ // Получаем название материала
+ surface.materialName = getString(index);
+ // Помещаем данные поверхности в список
+ object.surfaces[surface.materialName] = surface;
+
+ // Получаем грани поверхности
+ data.position = index + surface.materialName.length + 1;
+ var num:uint = data.readUnsignedShort();
+ surface.faces = new Array();
+ for (var i:uint = 0; i < num; i++) {
+ surface.faces.push(data.readUnsignedShort());
+ }
+ }
+
+ private function parseAnimationChunk(index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+ switch (chunkId) {
+ // Анимация объекта
+ case 0xB001:
+ case 0xB002:
+ case 0xB003:
+ case 0xB004:
+ case 0xB005:
+ case 0xB006:
+ case 0xB007:
+ var animation:AnimationData = new AnimationData();
+ if (animationDatas == null) {
+ animationDatas = new Array();
+ }
+ animationDatas.push(animation);
+ parseObjectAnimationChunk(animation, dataIndex, dataLength);
+ break;
+
+ // Таймлайн
+ case 0xB008:
+ break;
+ }
+
+ parseAnimationChunk(index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseObjectAnimationChunk(animation:AnimationData, index:uint, length:uint):void {
+ if (length > 6) {
+ data.position = index;
+ var chunkId:uint = data.readUnsignedShort();
+ var chunkLength:uint = data.readUnsignedInt();
+ var dataIndex:uint = index + 6;
+ var dataLength:uint = chunkLength - 6;
+ switch (chunkId) {
+ // Идентификация объекта и его связь
+ case 0xB010:
+ parseObjectAnimationInfo(animation, dataIndex);
+ break;
+ // Точка привязки объекта (pivot)
+ case 0xB013:
+ parseObjectAnimationPivot(animation, dataIndex);
+ break;
+ // Смещение объекта относительно родителя
+ case 0xB020:
+ parseObjectAnimationPosition(animation, dataIndex);
+ break;
+ // Поворот объекта относительно родителя (angle-axis)
+ case 0xB021:
+ parseObjectAnimationRotation(animation, dataIndex);
+ break;
+ // Масштабирование объекта относительно родителя
+ case 0xB022:
+ parseObjectAnimationScale(animation, dataIndex);
+ break;
+ }
+
+ parseObjectAnimationChunk(animation, index + chunkLength, length - chunkLength);
+ }
+ }
+
+ private function parseObjectAnimationInfo(animation:AnimationData, index:uint):void {
+ var name:String = getString(index);
+ data.position = index + name.length + 1 + 4;
+ animation.objectName = name;
+ animation.parentIndex = data.readUnsignedShort();
+ }
+
+ private function parseObjectAnimationPivot(animation:AnimationData, index:uint):void {
+ data.position = index;
+ animation.pivot = new Point3D(data.readFloat(), data.readFloat(), data.readFloat());
+ }
+
+ private function parseObjectAnimationPosition(animation:AnimationData, index:uint):void {
+ data.position = index + 20;
+ animation.position = new Point3D(data.readFloat(), data.readFloat(), data.readFloat());
+ }
+
+ private function parseObjectAnimationRotation(animation:AnimationData, index:uint):void {
+ data.position = index + 20;
+ animation.rotation = getRotationFrom3DSAngleAxis(data.readFloat(), data.readFloat(), data.readFloat(), data.readFloat());
+ }
+
+ private function parseObjectAnimationScale(animation:AnimationData, index:uint):void {
+ data.position = index + 20;
+ animation.scale = new Point3D(data.readFloat(), data.readFloat(), data.readFloat());
+ }
+
+ /**
+ * Объект-контейнер, содержащий все загруженные объекты.
+ */
+ public function get content():Object3D {
+ return _content;
+ }
+
+ // Считываем строку заканчивающуюся на нулевой байт
+ private function getString(index:uint):String {
+ data.position = index;
+ var charCode:uint = data.readByte();
+ var res:String = "";
+ while (charCode != 0) {
+ res += String.fromCharCode(charCode);
+ charCode = data.readByte();
+ }
+ return res;
+ }
+
+ private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Point3D {
+ var res:Point3D = new Point3D();
+ var s:Number = Math.sin(angle);
+ var c:Number = Math.cos(angle);
+ var t:Number = 1 - c;
+ var k:Number = x*y*t + z*s;
+ var half:Number;
+ if (k > 0.998) {
+ half = angle/2;
+ res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half));
+ res.y = -Math.PI/2;
+ res.x = 0;
+ return res;
+ }
+ if (k < -0.998) {
+ half = angle/2;
+ res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half));
+ res.y = Math.PI/2;
+ res.x = 0;
+ return res;
+ }
+ res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t);
+ res.y = -Math.asin(x*y*t + z*s);
+ res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t);
+ return res;
+ }
+
+ }
+}
+
+import alternativa.engine3d.core.Object3D;
+import alternativa.types.Matrix3D;
+import alternativa.types.Point3D;
+
+import flash.geom.Matrix;
+import alternativa.types.Map;
+
+class MaterialData {
+ public var name:String;
+ public var color:uint;
+ public var specular:uint;
+ public var glossiness:uint;
+ public var transparency:uint;
+ public var diffuseMap:MapData;
+ public var opacityMap:MapData;
+ //public var normalMap:MapData;
+ public var matrix:Matrix;
+}
+
+class MapData {
+ public var filename:String;
+ public var scaleU:Number = 1;
+ public var scaleV:Number = 1;
+ public var offsetU:Number = 0;
+ public var offsetV:Number = 0;
+ public var rotation:Number = 0;
+}
+
+class ObjectData {
+ public var name:String;
+ public var vertices:Array;
+ public var uvs:Array;
+ public var matrix:Matrix3D;
+ public var faces:Array;
+ public var surfaces:Map;
+}
+
+class FaceData {
+ public var a:uint;
+ public var b:uint;
+ public var c:uint;
+}
+
+class SurfaceData {
+ public var materialName:String;
+ public var faces:Array;
+}
+
+class AnimationData {
+ public var objectName:String;
+ public var object:Object3D;
+ public var parentIndex:uint;
+ public var pivot:Point3D;
+ public var position:Point3D;
+ public var rotation:Point3D;
+ public var scale:Point3D;
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/loaders/LoaderMTL.as b/Alternativa3D5/5.5/alternativa/engine3d/loaders/LoaderMTL.as
new file mode 100644
index 0000000..74922a7
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/loaders/LoaderMTL.as
@@ -0,0 +1,346 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.*;
+ import alternativa.types.Map;
+ import alternativa.utils.ColorUtils;
+
+ import flash.display.BitmapData;
+ import flash.events.ErrorEvent;
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IOErrorEvent;
+ import flash.events.SecurityErrorEvent;
+ import flash.geom.Point;
+ import flash.net.URLLoader;
+ import flash.net.URLRequest;
+ import flash.system.LoaderContext;
+
+ use namespace alternativa3d;
+
+ /**
+ * Тип события, возникающего при завершении загрузки модели.
+ */
+ [Event (name="complete", type="flash.events.Event")]
+ /**
+ * Тип события, возникающего при ошибке загрузки, связанной с вводом-выводом.
+ */
+ [Event (name="ioError", type="flash.events.IOErrorEvent")]
+ /**
+ * Тип события, возникающего при нарушении безопасности.
+ */
+ [Event (name="securityError", type="flash.events.SecurityErrorEvent")]
+ /**
+ * @private
+ * Загрузчик библиотеки материалов из файлов в формате MTL material format (Lightwave, OBJ).
+ *
+ * На данный момент обеспечивается загрузка цвета, прозрачности и диффузной текстуры материала.
+ */
+ internal class LoaderMTL extends EventDispatcher {
+
+ private static const COMMENT_CHAR:String = "#";
+ private static const CMD_NEW_MATERIAL:String = "newmtl";
+ private static const CMD_DIFFUSE_REFLECTIVITY:String = "Kd";
+ private static const CMD_DISSOLVE:String = "d";
+ private static const CMD_MAP_DIFFUSE:String = "map_Kd";
+ private static const CMD_MAP_DISSOLVE:String = "map_d";
+
+ private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/;
+ private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/;
+ private static const REGEXP_SPLIT_LINE:RegExp = /\s+/;
+
+ private static const STATE_IDLE:int = -1;
+ private static const STATE_LOADING_LIBRARY:int = 0;
+ private static const STATE_LOADING_TEXTURES:int = 1;
+
+ // Загрузчик файла MTL
+ private var mtlFileLoader:URLLoader;
+ // Загрузчик файлов текстур
+ private var textureLoader:TextureMapsLoader;
+ // Контекст загрузки для bitmapLoader
+ private var loaderContext:LoaderContext;
+ // Базовый URL файла MTL
+ private var baseUrl:String;
+
+ // Библиотека загруженных материалов
+ private var _library:Map;
+ // Список диффузные текстур. Ключи -- имена материалов, значения -- информация о текстурах.
+ private var diffuseMaps:Map;
+ // Список карт прозрачности. Ключи -- имена материалов, значения -- информация о картах прозрачности.
+ private var dissolveMaps:Map;
+ // Имя текущего материала
+ private var materialName:String;
+ // параметры текущего материала
+ private var currentMaterialInfo:MaterialInfo = new MaterialInfo();
+
+ private var loaderState:int = STATE_IDLE;
+
+ alternativa3d static var stubBitmapData:BitmapData;
+
+ /**
+ * Создаёт новый экземпляр класса.
+ */
+ public function LoaderMTL() {
+ }
+
+ /**
+ * Прекращение текущей загрузки.
+ */
+ public function close():void {
+ if (loaderState == STATE_LOADING_LIBRARY) {
+ mtlFileLoader.close();
+ }
+ if (loaderState == STATE_LOADING_TEXTURES) {
+ textureLoader.close();
+ }
+ loaderState = STATE_IDLE;
+ }
+
+ /**
+ * Метод очищает внутренние ссылки на загруженные данные чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает
+ * во время загрузки.
+ */
+ public function unload():void {
+ if (loaderState == STATE_IDLE) {
+ clean();
+ _library = null;
+ }
+ }
+
+ /**
+ *
+ */
+ private function clean():void {
+ diffuseMaps = null;
+ dissolveMaps = null;
+ loaderContext = null;
+ }
+
+ /**
+ * Библиотека материалов. Ключами являются наименования материалов, значениями -- объекты, наследники класса
+ *
+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения
+ * Если происходит ошибка при загрузке файла текстуры, то соответствующая текстура заменяется на текстуру-заглушку.
+ *
+ * @param url URL MTL-файла
+ * @param loaderContext LoaderContext для загрузки файлов текстур
+ *
+ * @see #library
+ */
+ public function load(url:String, loaderContext:LoaderContext = null):void {
+ this.loaderContext = loaderContext;
+ baseUrl = url.substring(0, url.lastIndexOf("/") + 1);
+
+ if (mtlFileLoader == null) {
+ mtlFileLoader = new URLLoader();
+ mtlFileLoader.addEventListener(Event.COMPLETE, parseMTLFile);
+ mtlFileLoader.addEventListener(IOErrorEvent.IO_ERROR, onError);
+ mtlFileLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
+ }
+ close();
+ loaderState = STATE_LOADING_LIBRARY;
+ mtlFileLoader.load(new URLRequest(url));
+ }
+
+ /**
+ * Разбор содержимого загруженного файла материалов.
+ */
+ private function parseMTLFile(e:Event = null):void {
+ loaderState = STATE_IDLE;
+ var lines:Array = mtlFileLoader.data.split(REGEXP_SPLIT_FILE);
+ _library = new Map();
+ diffuseMaps = new Map();
+ dissolveMaps = new Map();
+ lines.forEach(parseLine);
+ defineMaterial();
+
+ if (diffuseMaps.isEmpty()) {
+ // Текстур нет, загрузка окончена
+ complete();
+ } else {
+ // Загрузка файлов текстур
+ loaderState = STATE_LOADING_TEXTURES;
+ loadNextBitmap();
+ }
+ }
+
+ /**
+ * Разбор строки файла.
+ *
+ * @param line строка файла
+ */
+ private function parseLine(line:String, index:int, lines:Array):void {
+ line = line.replace(REGEXP_TRIM,"$1")
+ if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) {
+ return;
+ }
+ var parts:Array = line.split(REGEXP_SPLIT_LINE);
+ switch (parts[0]) {
+ case CMD_NEW_MATERIAL:
+ defineMaterial(parts);
+ break;
+ case CMD_DIFFUSE_REFLECTIVITY:
+ readDiffuseReflectivity(parts);
+ break;
+ case CMD_DISSOLVE:
+ readAlpha(parts);
+ break;
+ case CMD_MAP_DIFFUSE:
+ parseTextureMapLine(diffuseMaps, parts);
+ break;
+ case CMD_MAP_DISSOLVE:
+ parseTextureMapLine(dissolveMaps, parts);
+ break;
+ }
+ }
+
+ /**
+ * Определение нового материала.
+ */
+ private function defineMaterial(parts:Array = null):void {
+ if (materialName != null) {
+ _library[materialName] = currentMaterialInfo;
+ }
+ if (parts != null) {
+ materialName = parts[1];
+ currentMaterialInfo = new MaterialInfo();
+ }
+ }
+
+ /**
+ * Чтение коэффициентов диффузного отражения. Считываются только коэффициенты, заданные в формате r g b. Для текущей
+ * версии движка данные коэффициенты преобразуются в цвет материала.
+ */
+ private function readDiffuseReflectivity(parts:Array):void {
+ var r:Number = Number(parts[1]);
+ // Проверка, заданы ли коэффициенты в виде r g b
+ if (!isNaN(r)) {
+ var g:Number = Number(parts[2]);
+ var b:Number = Number(parts[3]);
+ currentMaterialInfo.color = ColorUtils.rgb(255 * r, 255 * g, 255 * b);
+ }
+ }
+
+ /**
+ * Чтение коэффициента непрозрачности. Считывается только коэффициент, заданный числом
+ * (не поддерживается параметр -halo).
+ */
+ private function readAlpha(parts:Array):void {
+ var alpha:Number = Number(parts[1]);
+ if (!isNaN(alpha)) {
+ currentMaterialInfo.alpha = alpha;
+ }
+ }
+
+ /**
+ * Разбор строки, задающей текстурную карту.
+ */
+ private function parseTextureMapLine(map:Map, parts:Array):void {
+ var info:MTLTextureMapInfo = MTLTextureMapInfo.parse(parts);
+ map[materialName] = info;
+ }
+
+ /**
+ * Загрузка файла следующей текстуры.
+ */
+ private function loadNextBitmap():void {
+ if (textureLoader == null) {
+ textureLoader = new TextureMapsLoader();
+ textureLoader.addEventListener(Event.COMPLETE, onBitmapLoadComplete);
+ textureLoader.addEventListener(IOErrorEvent.IO_ERROR, onBitmapLoadComplete);
+ }
+
+ // Установка имени текущего текстурного материала, для которого выполняется загрузка текстуры
+ for (materialName in diffuseMaps) {
+ break;
+ }
+
+ var diffuseName:String = baseUrl + diffuseMaps[materialName].fileName;
+ var dissolveName:String;
+ var dissolveTextureInfo:MTLTextureMapInfo = dissolveMaps[materialName];
+ if (dissolveTextureInfo != null) {
+ dissolveName = dissolveTextureInfo.fileName;
+ }
+
+ textureLoader.load(diffuseName, dissolveName, loaderContext);
+ }
+
+ /**
+ *
+ */
+ private function createStubBitmap():void {
+ if (stubBitmapData == null) {
+ var size:uint = 10;
+ stubBitmapData = new BitmapData(size, size, false, 0);
+ for (var i:uint = 0; i < size; i++) {
+ for (var j:uint = 0; j < size; j+=2) {
+ stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF);
+ }
+ }
+ }
+ }
+
+ /**
+ * Обработка результата загрузки файла текстуры.
+ */
+ private function onBitmapLoadComplete(e:Event):void {
+ var bmd:BitmapData;
+
+ if (e is ErrorEvent) {
+ if (stubBitmapData == null) {
+ createStubBitmap();
+ }
+ bmd = stubBitmapData;
+ } else {
+ bmd = textureLoader.bitmapData;
+ }
+
+ var mtlInfo:MTLTextureMapInfo = diffuseMaps[materialName];
+ delete diffuseMaps[materialName];
+ var materialInfo:MaterialInfo = _library[materialName];
+
+ materialInfo.bitmapData = bmd;
+ materialInfo.repeat = mtlInfo.repeat;
+ materialInfo.mapOffset = new Point(mtlInfo.offsetU, mtlInfo.offsetV);
+ materialInfo.mapSize = new Point(mtlInfo.sizeU, mtlInfo.sizeV);
+ materialInfo.textureFileName = mtlInfo.fileName;
+
+ if (diffuseMaps.isEmpty()) {
+ complete();
+ } else {
+ loadNextBitmap();
+ }
+ }
+
+ /**
+ * Обработка успешного завершения загрузки.
+ */
+ private function complete():void {
+ loaderState = STATE_IDLE;
+ if (textureLoader != null) {
+ textureLoader.unload();
+ }
+ clean();
+ dispatchEvent(new Event(Event.COMPLETE));
+ }
+
+ /**
+ * Обработка ошибки загрузки MTL-файла.
+ */
+ private function onError(e:Event):void {
+ loaderState = STATE_IDLE;
+ dispatchEvent(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/loaders/LoaderOBJ.as b/Alternativa3D5/5.5/alternativa/engine3d/loaders/LoaderOBJ.as
new file mode 100644
index 0000000..70710f5
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/loaders/LoaderOBJ.as
@@ -0,0 +1,567 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.engine3d.materials.TextureMaterialPrecision;
+ import alternativa.types.Map;
+ import alternativa.types.Point3D;
+ import alternativa.types.Texture;
+
+ import flash.display.BlendMode;
+ import flash.events.ErrorEvent;
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IOErrorEvent;
+ import flash.events.SecurityErrorEvent;
+ import flash.geom.Point;
+ import flash.net.URLLoader;
+ import flash.net.URLRequest;
+ import flash.system.LoaderContext;
+
+ use namespace alternativa3d;
+
+ /**
+ * Событие посылается после завершения загрузки модели.
+ *
+ * @eventType flash.events.Event.COMPLETE
+ */
+ [Event (name="complete", type="flash.events.Event")]
+ /**
+ * Событие посылается при возникновении ошибки загрузки, связанной с вводом-выводом.
+ *
+ * @eventType flash.events.IOErrorEvent.IO_ERROR
+ */
+ [Event (name="ioError", type="flash.events.IOErrorEvent")]
+ /**
+ * Событие посылается при нарушении правил безопасности при загрузке файла модели.
+ *
+ * @eventType flash.events.SecurityErrorEvent.SECURITY_ERROR
+ */
+ [Event (name="securityError", type="flash.events.SecurityErrorEvent")]
+ /**
+ * Загрузчик моделей из файла в формате OBJ. Так как OBJ не поддерживает иерархию объектов, все загруженные
+ * модели помещаются в один контейнер
+ * Распознаются следующие ключевые слова формата OBJ:
+ *
+ *
+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения
+ * @param url URL OBJ-файла
+ * @param loadMaterials флаг загрузки материалов. Если указано значение Параллелепипед содержит шесть поверхностей с идентификаторами Различные значения параметров позволяют создавать различные примитивы.
+ * При установленном параметре По умолчанию параметр После создания примитив всегда содержит в себе поверхность Геоплоскость это плоскость с сетчатой структурой граней. Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров.
+ * При значении Геосфера после создания содержит в себе одну поверхность с идентификатором по умолчанию. Текстурные координаты у геосферы не находятся в промежутке Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров.
+ * При значении После создания примитив содержит в себе одну поверхность с идентификатором по умолчанию. По умолчанию параметр Соответствия локальных осей для объектов, не являющихся камерой:
+ *
+ *
+ */
+ public function setDefaultBindings():void {
+ unbindAll();
+ bindKey(KeyboardUtils.W, ACTION_FORWARD);
+ bindKey(KeyboardUtils.S, ACTION_BACK);
+ bindKey(KeyboardUtils.A, ACTION_LEFT);
+ bindKey(KeyboardUtils.D, ACTION_RIGHT);
+ bindKey(KeyboardUtils.SPACE, ACTION_UP);
+ bindKey(KeyboardUtils.CONTROL, ACTION_DOWN);
+ bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
+ bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
+ bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT);
+ bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT);
+ bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE);
+ }
+
+ /**
+ * Направление камеры на точку.
+ *
+ * @param point координаты точки направления камеры
+ */
+ public function lookAt(point:Point3D):void {
+ if (_camera == null) {
+ return;
+ }
+ var dx:Number = point.x - _camera.x;
+ var dy:Number = point.y - _camera.y;
+ var dz:Number = point.z - _camera.z;
+ _camera.rotationZ = -Math.atan2(dx, dy);
+ _camera.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - MathUtils.DEG90;
+ }
+
+ /**
+ * Callback-функция, вызываемая при начале движения камеры.
+ */
+ public function get onStartMoving():Function {
+ return _onStartMoving;
+ }
+
+ /**
+ * @private
+ */
+ public function set onStartMoving(value:Function):void {
+ _onStartMoving = value;
+ }
+
+ /**
+ * Callback-функция, вызываемая при прекращении движения камеры.
+ */
+ public function get onStopMoving():Function {
+ return _onStopMoving;
+ }
+
+ /**
+ * @private
+ */
+ public function set onStopMoving(value:Function):void {
+ _onStopMoving = value;
+ }
+
+ /**
+ * Набор объектов, исключаемых из проверки столкновений.
+ */
+ public function get collisionIgnoreSet():Set {
+ return _collisionIgnoreSet;
+ }
+
+ /**
+ * Источник событий клавиатуры и мыши.
+ */
+ public function get eventsSource():DisplayObject {
+ return _eventsSource;
+ }
+
+ /**
+ * @private
+ */
+ public function set eventsSource(value:DisplayObject):void {
+ if (_eventsSource != value) {
+ if (value == null) {
+ throw new ArgumentError("CameraController: eventsSource is null");
+ }
+ if (_controlsEnabled) {
+ unregisterEventsListeners();
+ }
+ _eventsSource = value;
+ if (_controlsEnabled) {
+ registerEventListeners();
+ }
+ }
+ }
+
+ /**
+ * Ассоциированная камера.
+ */
+ public function get camera():Camera3D {
+ return _camera;
+ }
+
+ /**
+ * @private
+ */
+ public function set camera(value:Camera3D):void {
+ if (_camera != value) {
+ _camera = value;
+ if (value == null) {
+ controlsEnabled = false;
+ } else {
+ createCollider();
+ }
+ }
+ }
+
+ /**
+ * Режим движения камеры. Если значение равно
+ * Клавиша Действие
+ * W ACTION_FORWARD
+ * S ACTION_BACK
+ * A ACTION_LEFT
+ * D ACTION_RIGHT
+ * SPACE ACTION_UP
+ * CONTROL ACTION_DOWN
+ * SHIFT ACTION_ACCELERATE
+ * UP ACTION_PITCH_UP
+ * DOWN ACTION_PITCH_DOWN
+ * LEFT ACTION_YAW_LEFT
+ * RIGHT ACTION_YAW_RIGHT true, то перемещения камеры происходят относительно
+ * локальной системы координат, иначе относительно глобальной.
+ *
+ * @default true
+ */
+ public function get moveLocal():Boolean {
+ return _moveLocal;
+ }
+
+ /**
+ * @private
+ */
+ public function set moveLocal(value:Boolean):void {
+ _moveLocal = value;
+ }
+
+ /**
+ * Включение режима проверки столкновений.
+ */
+ public function get checkCollisions():Boolean {
+ return _checkCollisions;
+ }
+
+ /**
+ * @private
+ */
+ public function set checkCollisions(value:Boolean):void {
+ _checkCollisions = value;
+ }
+
+ /**
+ * Радиус сферы для определения столкновений.
+ *
+ * @default 0
+ */
+ public function get collisionRadius():Number {
+ return _collisionRadius;
+ }
+
+ /**
+ * @private
+ */
+ public function set collisionRadius(value:Number):void {
+ _collisionRadius = value;
+ if (_collider != null) {
+ _collider.sphereRadius = _collisionRadius;
+ }
+ }
+
+ /**
+ * Привязка клавиши к действию.
+ *
+ * @param keyCode код клавиши
+ * @param action наименование действия
+ */
+ public function bindKey(keyCode:uint, action:String):void {
+ var method:Function = actionBindings[action];
+ if (method != null) {
+ keyBindings[keyCode] = method;
+ }
+ }
+
+ /**
+ * Очистка привязки клавиши.
+ *
+ * @param keyCode код клавиши
+ */
+ public function unbindKey(keyCode:uint):void {
+ keyBindings.remove(keyCode);
+ }
+
+ /**
+ * Очистка привязки всех клавиш.
+ */
+ public function unbindAll():void {
+ keyBindings.clear();
+ }
+
+ /**
+ * Активация движения камеры вперёд.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function forward(value:Boolean):void {
+ _forward = value;
+ }
+
+ /**
+ * Активация движения камеры назад.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function back(value:Boolean):void {
+ _back = value;
+ }
+
+ /**
+ * Активация движения камеры влево.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function left(value:Boolean):void {
+ _left = value;
+ }
+
+ /**
+ * Активация движения камеры вправо.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function right(value:Boolean):void {
+ _right = value;
+ }
+
+ /**
+ * Активация движения камеры вверх.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function up(value:Boolean):void {
+ _up = value;
+ }
+
+ /**
+ * Активация движения камеры вниз.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function down(value:Boolean):void {
+ _down = value;
+ }
+
+ /**
+ * Активация поворота камеры вверх.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function pitchUp(value:Boolean):void {
+ _pitchUp = value;
+ }
+
+ /**
+ * Активация поворота камеры вниз.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function pitchDown(value:Boolean):void {
+ _pitchDown = value;
+ }
+
+ /**
+ * Активация поворота камеры влево.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function yawLeft(value:Boolean):void {
+ _yawLeft = value;
+ }
+
+ /**
+ * Активация поворота камеры вправо.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function yawRight(value:Boolean):void {
+ _yawRight = value;
+ }
+
+
+ /**
+ * Активация режима увеличенной скорости.
+ *
+ * @param value true для включения ускорения, false для выключения
+ */
+ public function accelerate(value:Boolean):void {
+ _accelerate = value;
+ }
+
+ /**
+ * @private
+ */
+ private function createCollider():void {
+ _collider = new SphereCollider(_camera.scene, _collisionRadius);
+ _collider.offsetThreshold = 0.01;
+ _collider.ignoreSet = _collisionIgnoreSet;
+ }
+
+ /**
+ * Чувствительность мыши — коэффициент умножения mousePitch и mouseYaw.
+ *
+ * @default 1
+ *
+ * @see #mousePitch()
+ * @see #mouseYaw()
+ */
+ public function get mouseSensitivity():Number {
+ return _mouseSensitivity;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseSensitivity(sensitivity:Number):void {
+ _mouseSensitivity = sensitivity;
+ _mousePitchCoeff = _mouseSensitivity * _mousePitch;
+ _mouseYawCoeff = _mouseSensitivity * _mouseYaw;
+ }
+
+ /**
+ * Скорость изменения угла тангажа при управлении мышью (радианы на пиксель).
+ *
+ * @default Math.PI / 360
+ */
+ public function get mousePitch():Number {
+ return _mousePitch;
+ }
+
+ /**
+ * @private
+ */
+ public function set mousePitch(pitch:Number):void {
+ _mousePitch = pitch;
+ _mousePitchCoeff = _mouseSensitivity * _mousePitch;
+ }
+
+ /**
+ * Скорость изменения угла рысканья при управлении мышью (радианы на пиксель).
+ *
+ * @default Math.PI / 360
+ */
+ public function get mouseYaw():Number {
+ return _mouseYaw;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseYaw(yaw:Number):void {
+ _mouseYaw = yaw;
+ _mouseYawCoeff = _mouseSensitivity * _mouseYaw;
+ }
+
+ /**
+ * Угловая скорость по тангажу при управлении с клавиатуры (радианы в секунду).
+ *
+ * @default 1
+ */
+ public function get pitchSpeed():Number {
+ return _pitchSpeed;
+ }
+
+ /**
+ * @private
+ */
+ public function set pitchSpeed(spd:Number):void {
+ _pitchSpeed = spd;
+ }
+
+ /**
+ * Угловая скорость по рысканью при управлении с клавиатуры (радианы в секунду).
+ *
+ * @default 1
+ */
+ public function get yawSpeed():Number {
+ return _yawSpeed;
+ }
+
+ /**
+ * @private
+ */
+ public function set yawSpeed(spd:Number):void {
+ _yawSpeed = spd;
+ }
+
+ /**
+ * Скорость поступательного движения (единицы в секунду).
+ */
+ public function get speed():Number {
+ return _speed;
+ }
+
+ /**
+ * @private
+ */
+ public function set speed(spd:Number):void {
+ _speed = spd;
+ }
+
+ /**
+ * Коэффициент увеличения скорости при активном действии ACTION_ACCELERATE.
+ *
+ * @default 2
+ */
+ public function get speedMultiplier():Number {
+ return _speedMultiplier;
+ }
+
+ /**
+ * @private
+ */
+ public function set speedMultiplier(value:Number):void {
+ _speedMultiplier = value;
+ }
+
+ /**
+ * Активность управления камеры.
+ *
+ * @default false
+ */
+ public function get controlsEnabled():Boolean {
+ return _controlsEnabled;
+ }
+
+ /**
+ * @private
+ */
+ public function set controlsEnabled(value:Boolean):void {
+ if (_camera == null || _controlsEnabled == value) return;
+ if (value) {
+ lastFrameTime = getTimer();
+ registerEventListeners();
+ }
+ else {
+ unregisterEventsListeners();
+ }
+ _controlsEnabled = value;
+ }
+
+ /**
+ * Базовый шаг изменения угла зрения в радианах. Реальный шаг получаеся умножением этого значения на величину
+ * MouseEvent.delta.
+ *
+ * @default Math.PI / 180
+ */
+ public function get fovStep():Number {
+ return _fovStep;
+ }
+
+ /**
+ * @private
+ */
+ public function set fovStep(value:Number):void {
+ _fovStep = value;
+ }
+
+ /**
+ * Множитель при изменении коэффициента увеличения. Закон изменения коэффициента увеличения описывается формулой:
+ * zoom (1 + MouseEvent.delta zoomMultiplier).
+ *
+ * @default 0.1
+ */
+ public function get zoomMultiplier():Number {
+ return _zoomMultiplier;
+ }
+
+ /**
+ * @private
+ */
+ public function set zoomMultiplier(value:Number):void {
+ _zoomMultiplier = value;
+ }
+
+ /**
+ * @private
+ */
+ private function registerEventListeners():void {
+ _eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey);
+ _eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKey);
+ _eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
+ _eventsSource.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
+ }
+
+ /**
+ * @private
+ */
+ private function unregisterEventsListeners():void {
+ _eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey);
+ _eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKey);
+ _eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
+ _eventsSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ _eventsSource.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
+ }
+
+ /**
+ * @private
+ * @param e
+ */
+ private function onKey(e:KeyboardEvent):void {
+ var method:Function = keyBindings[e.keyCode];
+ if (method != null) {
+ method.call(this, e.type == KeyboardEvent.KEY_DOWN);
+ }
+ }
+
+ /**
+ * @private
+ * @param e
+ */
+ private function onMouseDown(e:MouseEvent):void {
+ mouseLookActive = true;
+ currentDragCoords.x = startDragCoords.x = _eventsSource.stage.mouseX;
+ currentDragCoords.y = startDragCoords.y = _eventsSource.stage.mouseY;
+ startRotX = _camera.rotationX;
+ startRotZ = _camera.rotationZ;
+ _eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * @private
+ * @param e
+ */
+ private function onMouseUp(e:MouseEvent):void {
+ mouseLookActive = false;
+ _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * @private
+ * @param e
+ */
+ private function onMouseWheel(e:MouseEvent):void {
+ if (_camera.orthographic) {
+ _camera.zoom = _camera.zoom * (1 + e.delta * _zoomMultiplier);
+ } else {
+ _camera.fov -= _fovStep * e.delta;
+ }
+ }
+
+ /**
+ * Обработка управляющих воздействий.
+ * Метод должен вызываться каждый кадр перед вызовом Scene3D.calculate().
+ *
+ * @see alternativa.engine3d.core.Scene3D#calculate()
+ */
+ public function processInput(): void {
+ if (!_controlsEnabled || _camera == null) return;
+
+ // Время в секундах от начала предыдущего кадра
+ var frameTime:Number = getTimer() - lastFrameTime;
+ lastFrameTime += frameTime;
+ frameTime /= 1000;
+
+ // Обработка mouselook
+ if (mouseLookActive) {
+ prevDragCoords.x = currentDragCoords.x;
+ prevDragCoords.y = currentDragCoords.y;
+ currentDragCoords.x = _eventsSource.stage.mouseX;
+ currentDragCoords.y = _eventsSource.stage.mouseY;
+ if (!prevDragCoords.equals(currentDragCoords)) {
+ _camera.rotationZ = startRotZ + (startDragCoords.x - currentDragCoords.x) * _mouseYawCoeff;
+ var rotX:Number = startRotX + (startDragCoords.y - currentDragCoords.y) * _mousePitchCoeff;
+ _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
+ }
+ }
+
+ // Поворот относительно вертикальной оси (рысканье, yaw)
+ if (_yawLeft) {
+ _camera.rotationZ += _yawSpeed * frameTime;
+ } else if (_yawRight) {
+ _camera.rotationZ -= _yawSpeed * frameTime;
+ }
+
+ // Поворот относительно поперечной оси (тангаж, pitch)
+ if (_pitchUp) {
+ rotX = _camera.rotationX + _pitchSpeed * frameTime;
+ _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
+ } else if (_pitchDown) {
+ rotX = _camera.rotationX - _pitchSpeed * frameTime;
+ _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
+ }
+
+ // TODO: Поворот относительно продольной оси (крен, roll)
+
+ var frameDistance:Number = _speed * frameTime;
+ if (_accelerate) {
+ frameDistance *= _speedMultiplier;
+ }
+ velocity.x = 0;
+ velocity.y = 0;
+ velocity.z = 0;
+ var transformation:Matrix3D = _camera.transformation;
+
+ if (_moveLocal) {
+ // Режим относительных пермещений
+ // Движение вперед-назад
+ if (_forward) {
+ velocity.x += frameDistance * transformation.c;
+ velocity.y += frameDistance * transformation.g;
+ velocity.z += frameDistance * transformation.k;
+ } else if (_back) {
+ velocity.x -= frameDistance * transformation.c;
+ velocity.y -= frameDistance * transformation.g;
+ velocity.z -= frameDistance * transformation.k;
+ }
+ // Движение влево-вправо
+ if (_left) {
+ velocity.x -= frameDistance * transformation.a;
+ velocity.y -= frameDistance * transformation.e;
+ velocity.z -= frameDistance * transformation.i;
+ } else if (_right) {
+ velocity.x += frameDistance * transformation.a;
+ velocity.y += frameDistance * transformation.e;
+ velocity.z += frameDistance * transformation.i;
+ }
+ // Движение вверх-вниз
+ if (_up) {
+ velocity.x -= frameDistance * transformation.b;
+ velocity.y -= frameDistance * transformation.f;
+ velocity.z -= frameDistance * transformation.j;
+ } else if (_down) {
+ velocity.x += frameDistance * transformation.b;
+ velocity.y += frameDistance * transformation.f;
+ velocity.z += frameDistance * transformation.j;
+ }
+ }
+ else {
+ // Режим глобальных перемещений
+ var cosZ:Number = Math.cos(_camera.rotationZ);
+ var sinZ:Number = Math.sin(_camera.rotationZ);
+ // Движение вперед-назад
+ if (_forward) {
+ velocity.x -= frameDistance * sinZ;
+ velocity.y += frameDistance * cosZ;
+ } else if (_back) {
+ velocity.x += frameDistance * sinZ;
+ velocity.y -= frameDistance * cosZ;
+ }
+ // Движение влево-вправо
+ if (_left) {
+ velocity.x -= frameDistance * cosZ;
+ velocity.y -= frameDistance * sinZ;
+ } else if (_right) {
+ velocity.x += frameDistance * cosZ;
+ velocity.y += frameDistance * sinZ;
+ }
+ // Движение вверх-вниз
+ if (_up) {
+ velocity.z += frameDistance;
+ } else if (_down) {
+ velocity.z -= frameDistance;
+ }
+ }
+
+ // Коррекция модуля вектора скорости
+ if (velocity.x != 0 || velocity.y != 0 || velocity.z != 0) {
+ velocity.length = frameDistance;
+ }
+
+ // Проверка столкновений
+ if (_checkCollisions) {
+ _collider.calculateDestination(_camera.coords, velocity, destination);
+ _camera.x = destination.x;
+ _camera.y = destination.y;
+ _camera.z = destination.z;
+ } else {
+ _camera.x += velocity.x;
+ _camera.y += velocity.y;
+ _camera.z += velocity.z;
+ }
+
+ // Обработка начала/окончания движения
+ if (_camera.changeRotationOrScaleOperation.queued || _camera.changeCoordsOperation.queued) {
+ if (!_isMoving) {
+ _isMoving = true;
+ if (_onStartMoving != null) {
+ _onStartMoving.call(this);
+ }
+ }
+ } else {
+ if (_isMoving) {
+ _isMoving = false;
+ if (_onStopMoving != null) {
+ _onStopMoving.call(this);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/BSPNode.as b/Alternativa3D5/5.3/alternativa/engine3d/core/BSPNode.as
new file mode 100644
index 0000000..5f3ccf3
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/core/BSPNode.as
@@ -0,0 +1,65 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ */
+ public final class BSPNode {
+
+ // Родительская нода
+ alternativa3d var parent:BSPNode;
+
+ // Дочерние ветки
+ alternativa3d var front:BSPNode;
+ alternativa3d var back:BSPNode;
+
+ // Нормаль плоскости ноды
+ alternativa3d var normal:Point3D = new Point3D();
+
+ // Смещение плоскости примитива
+ alternativa3d var offset:Number;
+
+ // Минимальная мобильность ноды
+ alternativa3d var mobility:int = int.MAX_VALUE;
+
+ // Набор примитивов в ноде
+ alternativa3d var primitive:PolyPrimitive;
+ alternativa3d var backPrimitives:Set;
+ alternativa3d var frontPrimitives:Set;
+
+ // Хранилище неиспользуемых нод
+ static private var collector:Array = new Array();
+
+ // Создать ноду на основе примитива
+ static alternativa3d function createBSPNode(primitive:PolyPrimitive):BSPNode {
+ // Достаём ноду из коллектора
+ var node:BSPNode = collector.pop();
+ // Если коллектор пуст, создаём новую ноду
+ if (node == null) {
+ node = new BSPNode();
+ }
+
+ // Добавляем примитив в ноду
+ node.primitive = primitive;
+ // Сохраняем ноду
+ primitive.node = node;
+ // Сохраняем плоскость
+ node.normal.copy(primitive.face.globalNormal);
+ node.offset = primitive.face.globalOffset;
+ // Сохраняем мобильность
+ node.mobility = primitive.mobility;
+ return node;
+ }
+
+ // Удалить ноду, все ссылки должны быть почищены
+ static alternativa3d function destroyBSPNode(node:BSPNode):void {
+ //trace(node.back, node.front, node.parent, node.primitive, node.backPrimitives, node.frontPrimitives);
+ collector.push(node);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/Camera3D.as b/Alternativa3D5/5.3/alternativa/engine3d/core/Camera3D.as
new file mode 100644
index 0000000..66ef3e7
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/core/Camera3D.as
@@ -0,0 +1,902 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.display.Skin;
+ import alternativa.engine3d.display.View;
+ import alternativa.engine3d.materials.DrawPoint;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ import flash.geom.Matrix;
+ import flash.geom.Point;
+ import alternativa.utils.MathUtils;
+
+ use namespace alternativa3d;
+
+ /**
+ * Камера для отображения 3D-сцены на экране.
+ *
+ * alternativa.engine3d.display.View.
+ *
+ * @see alternativa.engine3d.display.View
+ */
+ public class Camera3D extends Object3D {
+
+ /**
+ * @private
+ * Расчёт матрицы пространства камеры
+ */
+ alternativa3d var calculateMatrixOperation:Operation = new Operation("calculateMatrix", this, calculateMatrix, Operation.CAMERA_CALCULATE_MATRIX);
+ /**
+ * @private
+ * Расчёт плоскостей отсечения
+ */
+ alternativa3d var calculatePlanesOperation:Operation = new Operation("calculatePlanes", this, calculatePlanes, Operation.CAMERA_CALCULATE_PLANES);
+ /**
+ * @private
+ * Отрисовка
+ */
+ alternativa3d var renderOperation:Operation = new Operation("render", this, render, Operation.CAMERA_RENDER);
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ /**
+ * @private
+ * Поле зрения
+ */
+ alternativa3d var _fov:Number = Math.PI/2;
+ /**
+ * @private
+ * Фокусное расстояние
+ */
+ alternativa3d var focalLength:Number;
+ /**
+ * @private
+ * Перспективное искажение
+ */
+ alternativa3d var focalDistortion:Number;
+
+ /**
+ * @private
+ * Флаги рассчитанности UV-матриц
+ */
+ alternativa3d var uvMatricesCalculated:Set = new Set(true);
+
+ // Всмомогательные точки для расчёта UV-матриц
+ private var textureA:Point3D = new Point3D();
+ private var textureB:Point3D = new Point3D();
+ private var textureC:Point3D = new Point3D();
+
+ /**
+ * @private
+ * Вид из камеры
+ */
+ alternativa3d var _view:View;
+
+ /**
+ * @private
+ * Режим отрисовки
+ */
+ alternativa3d var _orthographic:Boolean = false;
+ private var fullDraw:Boolean;
+
+ // Масштаб
+ private var _zoom:Number = 1;
+
+ // Синус половинчатого угла обзора камеры
+ private var viewAngle:Number;
+
+ // Направление камеры
+ private var direction:Point3D = new Point3D(0, 0, 1);
+
+ // Обратная трансформация камеры
+ private var cameraMatrix:Matrix3D = new Matrix3D();
+
+ // Скины
+ private var firstSkin:Skin;
+ private var prevSkin:Skin;
+ private var currentSkin:Skin;
+
+ // Плоскости отсечения
+ private var leftPlane:Point3D = new Point3D();
+ private var rightPlane:Point3D = new Point3D();
+ private var topPlane:Point3D = new Point3D();
+ private var bottomPlane:Point3D = new Point3D();
+ private var leftOffset:Number;
+ private var rightOffset:Number;
+ private var topOffset:Number;
+ private var bottomOffset:Number;
+
+ // Вспомогательные массивы точек для отрисовки
+ private var points1:Array = new Array();
+ private var points2:Array = new Array();
+
+ /**
+ * Создание нового экземпляра камеры.
+ *
+ * @param name имя экземпляра
+ */
+ public function Camera3D(name:String = null) {
+ super(name);
+ }
+
+ /**
+ * @private
+ */
+ private function calculateMatrix():void {
+ // Расчёт матрицы пространства камеры
+ cameraMatrix.copy(transformation);
+ cameraMatrix.invert();
+ if (_orthographic) {
+ cameraMatrix.scale(_zoom, _zoom, _zoom);
+ }
+ // Направление камеры
+ direction.x = transformation.c;
+ direction.y = transformation.g;
+ direction.z = transformation.k;
+ direction.normalize();
+ }
+
+ /**
+ * @private
+ * Расчёт плоскостей отсечения
+ */
+ private function calculatePlanes():void {
+ var halfWidth:Number = _view._width*0.5;
+ var halfHeight:Number = _view._height*0.5;
+
+ var aw:Number = transformation.a*halfWidth;
+ var ew:Number = transformation.e*halfWidth;
+ var iw:Number = transformation.i*halfWidth;
+ var bh:Number = transformation.b*halfHeight;
+ var fh:Number = transformation.f*halfHeight;
+ var jh:Number = transformation.j*halfHeight;
+ if (_orthographic) {
+ // Расчёт плоскостей отсечения в изометрии
+ aw /= _zoom;
+ ew /= _zoom;
+ iw /= _zoom;
+ bh /= _zoom;
+ fh /= _zoom;
+ jh /= _zoom;
+
+ // Левая плоскость
+ leftPlane.x = transformation.f*transformation.k - transformation.j*transformation.g;
+ leftPlane.y = transformation.j*transformation.c - transformation.b*transformation.k;
+ leftPlane.z = transformation.b*transformation.g - transformation.f*transformation.c;
+ leftOffset = (transformation.d - aw)*leftPlane.x + (transformation.h - ew)*leftPlane.y + (transformation.l - iw)*leftPlane.z;
+
+ // Правая плоскость
+ rightPlane.x = -leftPlane.x;
+ rightPlane.y = -leftPlane.y;
+ rightPlane.z = -leftPlane.z;
+ rightOffset = (transformation.d + aw)*rightPlane.x + (transformation.h + ew)*rightPlane.y + (transformation.l + iw)*rightPlane.z;
+
+ // Верхняя плоскость
+ topPlane.x = transformation.g*transformation.i - transformation.k*transformation.e;
+ topPlane.y = transformation.k*transformation.a - transformation.c*transformation.i;
+ topPlane.z = transformation.c*transformation.e - transformation.g*transformation.a;
+ topOffset = (transformation.d - bh)*topPlane.x + (transformation.h - fh)*topPlane.y + (transformation.l - jh)*topPlane.z;
+
+ // Нижняя плоскость
+ bottomPlane.x = -topPlane.x;
+ bottomPlane.y = -topPlane.y;
+ bottomPlane.z = -topPlane.z;
+ bottomOffset = (transformation.d + bh)*bottomPlane.x + (transformation.h + fh)*bottomPlane.y + (transformation.l + jh)*bottomPlane.z;
+ } else {
+ // Вычисляем расстояние фокуса
+ focalLength = Math.sqrt(_view._width*_view._width + _view._height*_view._height)*0.5/Math.tan(0.5*_fov);
+ // Вычисляем минимальное (однопиксельное) искажение перспективной коррекции
+ focalDistortion = 1/(focalLength*focalLength);
+
+ // Расчёт плоскостей отсечения в перспективе
+ var cl:Number = transformation.c*focalLength;
+ var gl:Number = transformation.g*focalLength;
+ var kl:Number = transformation.k*focalLength;
+
+ // Угловые вектора пирамиды видимости
+ var leftTopX:Number = -aw - bh + cl;
+ var leftTopY:Number = -ew - fh + gl;
+ var leftTopZ:Number = -iw - jh + kl;
+ var rightTopX:Number = aw - bh + cl;
+ var rightTopY:Number = ew - fh + gl;
+ var rightTopZ:Number = iw - jh + kl;
+ var leftBottomX:Number = -aw + bh + cl;
+ var leftBottomY:Number = -ew + fh + gl;
+ var leftBottomZ:Number = -iw + jh + kl;
+ var rightBottomX:Number = aw + bh + cl;
+ var rightBottomY:Number = ew + fh + gl;
+ var rightBottomZ:Number = iw + jh + kl;
+
+ // Левая плоскость
+ leftPlane.x = leftBottomY*leftTopZ - leftBottomZ*leftTopY;
+ leftPlane.y = leftBottomZ*leftTopX - leftBottomX*leftTopZ;
+ leftPlane.z = leftBottomX*leftTopY - leftBottomY*leftTopX;
+ leftOffset = transformation.d*leftPlane.x + transformation.h*leftPlane.y + transformation.l*leftPlane.z;
+
+ // Правая плоскость
+ rightPlane.x = rightTopY*rightBottomZ - rightTopZ*rightBottomY;
+ rightPlane.y = rightTopZ*rightBottomX - rightTopX*rightBottomZ;
+ rightPlane.z = rightTopX*rightBottomY - rightTopY*rightBottomX;
+ rightOffset = transformation.d*rightPlane.x + transformation.h*rightPlane.y + transformation.l*rightPlane.z;
+
+ // Верхняя плоскость
+ topPlane.x = leftTopY*rightTopZ - leftTopZ*rightTopY;
+ topPlane.y = leftTopZ*rightTopX - leftTopX*rightTopZ;
+ topPlane.z = leftTopX*rightTopY - leftTopY*rightTopX;
+ topOffset = transformation.d*topPlane.x + transformation.h*topPlane.y + transformation.l*topPlane.z;
+
+ // Нижняя плоскость
+ bottomPlane.x = rightBottomY*leftBottomZ - rightBottomZ*leftBottomY;
+ bottomPlane.y = rightBottomZ*leftBottomX - rightBottomX*leftBottomZ;
+ bottomPlane.z = rightBottomX*leftBottomY - rightBottomY*leftBottomX;
+ bottomOffset = transformation.d*bottomPlane.x + transformation.h*bottomPlane.y + transformation.l*bottomPlane.z;
+
+
+ // Расчёт угла конуса
+ var length:Number = Math.sqrt(leftTopX*leftTopX + leftTopY*leftTopY + leftTopZ*leftTopZ);
+ leftTopX /= length;
+ leftTopY /= length;
+ leftTopZ /= length;
+ length = Math.sqrt(rightTopX*rightTopX + rightTopY*rightTopY + rightTopZ*rightTopZ);
+ rightTopX /= length;
+ rightTopY /= length;
+ rightTopZ /= length;
+ length = Math.sqrt(leftBottomX*leftBottomX + leftBottomY*leftBottomY + leftBottomZ*leftBottomZ);
+ leftBottomX /= length;
+ leftBottomY /= length;
+ leftBottomZ /= length;
+ length = Math.sqrt(rightBottomX*rightBottomX + rightBottomY*rightBottomY + rightBottomZ*rightBottomZ);
+ rightBottomX /= length;
+ rightBottomY /= length;
+ rightBottomZ /= length;
+
+ viewAngle = leftTopX*direction.x + leftTopY*direction.y + leftTopZ*direction.z;
+ var dot:Number = rightTopX*direction.x + rightTopY*direction.y + rightTopZ*direction.z;
+ viewAngle = (dot < viewAngle) ? dot : viewAngle;
+ dot = leftBottomX*direction.x + leftBottomY*direction.y + leftBottomZ*direction.z;
+ viewAngle = (dot < viewAngle) ? dot : viewAngle;
+ dot = rightBottomX*direction.x + rightBottomY*direction.y + rightBottomZ*direction.z;
+ viewAngle = (dot < viewAngle) ? dot : viewAngle;
+
+ viewAngle = Math.sin(Math.acos(viewAngle));
+ }
+ }
+
+ /**
+ * @private
+ */
+ private function render():void {
+ // Режим отрисовки
+ fullDraw = (calculateMatrixOperation.queued || calculatePlanesOperation.queued);
+
+ // Очистка рассчитанных текстурных матриц
+ uvMatricesCalculated.clear();
+
+ // Отрисовка
+ prevSkin = null;
+ currentSkin = firstSkin;
+ renderBSPNode(_scene.bsp);
+
+ // Удаление ненужных скинов
+ while (currentSkin != null) {
+ removeCurrentSkin();
+ }
+ }
+
+ /**
+ * @private
+ */
+ private function renderBSPNode(node:BSPNode):void {
+ if (node != null) {
+ var primitive:*;
+ var normal:Point3D = node.normal;
+ var cameraAngle:Number = direction.x*normal.x + direction.y*normal.y + direction.z*normal.z;
+ var cameraOffset:Number;
+ if (!_orthographic) {
+ cameraOffset = globalCoords.x*normal.x + globalCoords.y*normal.y + globalCoords.z*normal.z - node.offset;
+ }
+ if (node.primitive != null) {
+ // В ноде только базовый примитив
+ if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) {
+ // Камера спереди ноды
+ if (_orthographic || cameraAngle < viewAngle) {
+ renderBSPNode(node.back);
+ drawSkin(node.primitive);
+ }
+ renderBSPNode(node.front);
+ } else {
+ // Камера сзади ноды
+ if (_orthographic || cameraAngle > -viewAngle) {
+ renderBSPNode(node.front);
+ }
+ renderBSPNode(node.back);
+ }
+ } else {
+ // В ноде несколько примитивов
+ if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) {
+ // Камера спереди ноды
+ if (_orthographic || cameraAngle < viewAngle) {
+ renderBSPNode(node.back);
+ for (primitive in node.frontPrimitives) {
+ drawSkin(primitive);
+ }
+ }
+ renderBSPNode(node.front);
+ } else {
+ // Камера сзади ноды
+ if (_orthographic || cameraAngle > -viewAngle) {
+ renderBSPNode(node.front);
+ for (primitive in node.backPrimitives) {
+ drawSkin(primitive);
+ }
+ }
+ renderBSPNode(node.back);
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Отрисовка скина примитива
+ */
+ private function drawSkin(primitive:PolyPrimitive):void {
+ if (!fullDraw && currentSkin != null && currentSkin.primitive == primitive && !_scene.changedPrimitives[primitive]) {
+ // Пропуск скина
+ prevSkin = currentSkin;
+ currentSkin = currentSkin.nextSkin;
+ } else {
+ // Проверка поверхности
+ var surface:Surface = primitive.face._surface;
+ if (surface == null) {
+ return;
+ }
+ // Проверка материала
+ var material:SurfaceMaterial = surface._material;
+ if (material == null || !material.canDraw(primitive)) {
+ return;
+ }
+ // Отсечение выходящих за окно просмотра частей
+ var i:uint;
+ var length:uint = primitive.num;
+ var primitivePoint:Point3D;
+ var primitiveUV:Point;
+ var point:DrawPoint;
+ var useUV:Boolean = !_orthographic && material.useUV && primitive.face.uvMatrixBase;
+ if (useUV) {
+ // Формируем список точек и UV-координат полигона
+ for (i = 0; i < length; i++) {
+ primitivePoint = primitive.points[i];
+ primitiveUV = primitive.uvs[i];
+ point = points1[i];
+ if (point == null) {
+ points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z, primitiveUV.x, primitiveUV.y);
+ } else {
+ point.x = primitivePoint.x;
+ point.y = primitivePoint.y;
+ point.z = primitivePoint.z;
+ point.u = primitiveUV.x;
+ point.v = primitiveUV.y;
+ }
+ }
+ } else {
+ // Формируем список точек полигона
+ for (i = 0; i < length; i++) {
+ primitivePoint = primitive.points[i];
+ point = points1[i];
+ if (point == null) {
+ points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z);
+ } else {
+ point.x = primitivePoint.x;
+ point.y = primitivePoint.y;
+ point.z = primitivePoint.z;
+ }
+ }
+ }
+ // Отсечение по левой стороне
+ length = clip(length, points1, points2, leftPlane, leftOffset, useUV);
+ if (length < 3) {
+ return;
+ }
+ // Отсечение по правой стороне
+ length = clip(length, points2, points1, rightPlane, rightOffset, useUV);
+ if (length < 3) {
+ return;
+ }
+ // Отсечение по верхней стороне
+ length = clip(length, points1, points2, topPlane, topOffset, useUV);
+ if (length < 3) {
+ return;
+ }
+ // Отсечение по нижней стороне
+ length = clip(length, points2, points1, bottomPlane, bottomOffset, useUV);
+ if (length < 3) {
+ return;
+ }
+
+ if (fullDraw || _scene.changedPrimitives[primitive]) {
+
+ // Если конец списка скинов
+ if (currentSkin == null) {
+ // Добавляем скин в конец
+ addCurrentSkin();
+ } else {
+ if (fullDraw || _scene.changedPrimitives[currentSkin.primitive]) {
+ // Очистка скина
+ currentSkin.material.clear(currentSkin);
+ } else {
+ // Вставка скина перед текущим
+ insertCurrentSkin();
+ }
+ }
+
+ // Переводим координаты в систему камеры
+ var x:Number;
+ var y:Number;
+ var z:Number;
+ for (i = 0; i < length; i++) {
+ point = points1[i];
+ x = point.x;
+ y = point.y;
+ z = point.z;
+ point.x = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d;
+ point.y = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h;
+ point.z = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l;
+ }
+
+ // Назначаем скину примитив и материал
+ currentSkin.primitive = primitive;
+ currentSkin.material = material;
+ material.draw(this, currentSkin, length, points1);
+
+ // Переключаемся на следующий скин
+ prevSkin = currentSkin;
+ currentSkin = currentSkin.nextSkin;
+
+ } else {
+
+ // Удаление ненужных скинов
+ while (currentSkin != null && _scene.changedPrimitives[currentSkin.primitive]) {
+ removeCurrentSkin();
+ }
+
+ // Переключение на следующий скин
+ if (currentSkin != null) {
+ prevSkin = currentSkin;
+ currentSkin = currentSkin.nextSkin;
+ }
+
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Отсечение полигона плоскостью.
+ */
+ private function clip(length:uint, points1:Array, points2:Array, plane:Point3D, offset:Number, calculateUV:Boolean):uint {
+ var i:uint;
+ var k:Number;
+ var index:uint = 0;
+ var point:DrawPoint;
+ var point1:DrawPoint;
+ var point2:DrawPoint;
+ var offset1:Number;
+ var offset2:Number;
+
+ point1 = points1[length - 1];
+ offset1 = plane.x*point1.x + plane.y*point1.y + plane.z*point1.z - offset;
+
+ if (calculateUV) {
+
+ for (i = 0; i < length; i++) {
+
+ point2 = points1[i];
+ offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset;
+
+ if (offset2 > 0) {
+ if (offset1 <= 0) {
+ k = offset2/(offset2 - offset1);
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k);
+ points2[index] = point;
+ } else {
+ point.x = point2.x - (point2.x - point1.x)*k;
+ point.y = point2.y - (point2.y - point1.y)*k;
+ point.z = point2.z - (point2.z - point1.z)*k;
+ point.u = point2.u - (point2.u - point1.u)*k;
+ point.v = point2.v - (point2.v - point1.v)*k;
+ }
+ index++;
+ }
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x, point2.y, point2.z, point2.u, point2.v);
+ points2[index] = point;
+ } else {
+ point.x = point2.x;
+ point.y = point2.y;
+ point.z = point2.z;
+ point.u = point2.u;
+ point.v = point2.v;
+ }
+ index++;
+ } else {
+ if (offset1 > 0) {
+ k = offset2/(offset2 - offset1);
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k);
+ points2[index] = point;
+ } else {
+ point.x = point2.x - (point2.x - point1.x)*k;
+ point.y = point2.y - (point2.y - point1.y)*k;
+ point.z = point2.z - (point2.z - point1.z)*k;
+ point.u = point2.u - (point2.u - point1.u)*k;
+ point.v = point2.v - (point2.v - point1.v)*k;
+ }
+ index++;
+ }
+ }
+ offset1 = offset2;
+ point1 = point2;
+ }
+
+ } else {
+
+ for (i = 0; i < length; i++) {
+
+ point2 = points1[i];
+ offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset;
+
+ if (offset2 > 0) {
+ if (offset1 <= 0) {
+ k = offset2/(offset2 - offset1);
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k);
+ points2[index] = point;
+ } else {
+ point.x = point2.x - (point2.x - point1.x)*k;
+ point.y = point2.y - (point2.y - point1.y)*k;
+ point.z = point2.z - (point2.z - point1.z)*k;
+ }
+ index++;
+ }
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x, point2.y, point2.z);
+ points2[index] = point;
+ } else {
+ point.x = point2.x;
+ point.y = point2.y;
+ point.z = point2.z;
+ }
+ index++;
+ } else {
+ if (offset1 > 0) {
+ k = offset2/(offset2 - offset1);
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k);
+ points2[index] = point;
+ } else {
+ point.x = point2.x - (point2.x - point1.x)*k;
+ point.y = point2.y - (point2.y - point1.y)*k;
+ point.z = point2.z - (point2.z - point1.z)*k;
+ }
+ index++;
+ }
+ }
+ offset1 = offset2;
+ point1 = point2;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * @private
+ * Добавление текущего скина.
+ */
+ private function addCurrentSkin():void {
+ currentSkin = Skin.createSkin();
+ _view.canvas.addChild(currentSkin);
+ if (prevSkin == null) {
+ firstSkin = currentSkin;
+ } else {
+ prevSkin.nextSkin = currentSkin;
+ }
+ }
+
+ /**
+ * @private
+ * Вставляем под текущий скин.
+ */
+ private function insertCurrentSkin():void {
+ var skin:Skin = Skin.createSkin();
+ _view.canvas.addChildAt(skin, _view.canvas.getChildIndex(currentSkin));
+ skin.nextSkin = currentSkin;
+ if (prevSkin == null) {
+ firstSkin = skin;
+ } else {
+ prevSkin.nextSkin = skin;
+ }
+ currentSkin = skin;
+ }
+
+ /**
+ * @private
+ * Удаляет текущий скин.
+ */
+ private function removeCurrentSkin():void {
+ // Сохраняем следующий
+ var next:Skin = currentSkin.nextSkin;
+ // Удаляем из канваса
+ _view.canvas.removeChild(currentSkin);
+ // Очистка скина
+ if (currentSkin.material != null) {
+ currentSkin.material.clear(currentSkin);
+ }
+ // Зачищаем ссылки
+ currentSkin.nextSkin = null;
+ currentSkin.primitive = null;
+ currentSkin.material = null;
+ // Удаляем
+ Skin.destroySkin(currentSkin);
+ // Следующий устанавливаем текущим
+ currentSkin = next;
+ // Устанавливаем связь от предыдущего скина
+ if (prevSkin == null) {
+ firstSkin = currentSkin;
+ } else {
+ prevSkin.nextSkin = currentSkin;
+ }
+ }
+
+ /**
+ * @private
+ */
+ alternativa3d function calculateUVMatrix(face:Face, width:uint, height:uint):void {
+
+ // Расчёт точек базового примитива в координатах камеры
+ var point:Point3D = face.primitive.points[0];
+ textureA.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
+ textureA.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
+ point = face.primitive.points[1];
+ textureB.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
+ textureB.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
+ point = face.primitive.points[2];
+ textureC.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
+ textureC.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
+
+ // Находим AB и AC
+ var abx:Number = textureB.x - textureA.x;
+ var aby:Number = textureB.y - textureA.y;
+ var acx:Number = textureC.x - textureA.x;
+ var acy:Number = textureC.y - textureA.y;
+
+ // Расчёт текстурной матрицы
+ var uvMatrixBase:Matrix = face.uvMatrixBase;
+ var uvMatrix:Matrix = face.uvMatrix;
+ uvMatrix.a = (uvMatrixBase.a*abx + uvMatrixBase.b*acx)/width;
+ uvMatrix.b = (uvMatrixBase.a*aby + uvMatrixBase.b*acy)/width;
+ uvMatrix.c = -(uvMatrixBase.c*abx + uvMatrixBase.d*acx)/height;
+ uvMatrix.d = -(uvMatrixBase.c*aby + uvMatrixBase.d*acy)/height;
+ uvMatrix.tx = (uvMatrixBase.tx + uvMatrixBase.c)*abx + (uvMatrixBase.ty + uvMatrixBase.d)*acx + textureA.x + cameraMatrix.d;
+ uvMatrix.ty = (uvMatrixBase.tx + uvMatrixBase.c)*aby + (uvMatrixBase.ty + uvMatrixBase.d)*acy + textureA.y + cameraMatrix.h;
+
+ // Помечаем, как рассчитанную
+ uvMatricesCalculated[face] = true;
+ }
+
+ /**
+ * Поле вывода, в котором происходит отрисовка камеры.
+ */
+ public function get view():View {
+ return _view;
+ }
+
+ /**
+ * @private
+ */
+ public function set view(value:View):void {
+ if (value != _view) {
+ if (_view != null) {
+ _view.camera = null;
+ }
+ if (value != null) {
+ value.camera = this;
+ }
+ }
+ }
+
+ /**
+ * Включение режима аксонометрической проекции.
+ *
+ * @default false
+ */
+ public function get orthographic():Boolean {
+ return _orthographic;
+ }
+
+ /**
+ * @private
+ */
+ public function set orthographic(value:Boolean):void {
+ if (_orthographic != value) {
+ // Отправляем сигнал об изменении типа камеры
+ addOperationToScene(calculateMatrixOperation);
+ // Сохраняем новое значение
+ _orthographic = value;
+ }
+ }
+
+ /**
+ * Угол поля зрения в радианах в режиме перспективной проекции. При изменении FOV изменяется фокусное расстояние
+ * камеры по формуле f = d/tan(fov/2), где d является половиной диагонали поля вывода.
+ * Угол зрения ограничен диапазоном 0-180 градусов.
+ */
+ public function get fov():Number {
+ return _fov;
+ }
+
+ /**
+ * @private
+ */
+ public function set fov(value:Number):void {
+ value = (value < 0) ? 0 : ((value > (Math.PI - 0.0001)) ? (Math.PI - 0.0001) : value);
+ if (_fov != value) {
+ // Если перспектива
+ if (!_orthographic) {
+ // Отправляем сигнал об изменении плоскостей отсечения
+ addOperationToScene(calculatePlanesOperation);
+ }
+ // Сохраняем новое значение
+ _fov = value;
+ }
+ }
+
+ /**
+ * Коэффициент увеличения изображения в режиме аксонометрической проекции.
+ */
+ public function get zoom():Number {
+ return _zoom;
+ }
+
+ /**
+ * @private
+ */
+ public function set zoom(value:Number):void {
+ value = (value < 0) ? 0 : value;
+ if (_zoom != value) {
+ // Если изометрия
+ if (_orthographic) {
+ // Отправляем сигнал об изменении zoom
+ addOperationToScene(calculateMatrixOperation);
+ }
+ // Сохраняем новое значение
+ _zoom = value;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function addToScene(scene:Scene3D):void {
+ super.addToScene(scene);
+ if (_view != null) {
+ // Отправляем операцию расчёта плоскостей отсечения
+ scene.addOperation(calculatePlanesOperation);
+ // Подписываемся на сигналы сцены
+ scene.changePrimitivesOperation.addSequel(renderOperation);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function removeFromScene(scene:Scene3D):void {
+ super.removeFromScene(scene);
+
+ // Удаляем все операции из очереди
+ scene.removeOperation(calculateMatrixOperation);
+ scene.removeOperation(calculatePlanesOperation);
+ scene.removeOperation(renderOperation);
+
+ if (_view != null) {
+ // Отписываемся от сигналов сцены
+ scene.changePrimitivesOperation.removeSequel(renderOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ alternativa3d function addToView(view:View):void {
+ // Сохраняем первый скин
+ firstSkin = (view.canvas.numChildren > 0) ? Skin(view.canvas.getChildAt(0)) : null;
+
+ // Подписка на свои операции
+
+ // При изменении камеры пересчёт матрицы
+ calculateTransformationOperation.addSequel(calculateMatrixOperation);
+ // При изменении матрицы или FOV пересчёт плоскостей отсечения
+ calculateMatrixOperation.addSequel(calculatePlanesOperation);
+ // При изменении плоскостей перерисовка
+ calculatePlanesOperation.addSequel(renderOperation);
+
+ if (_scene != null) {
+ // Отправляем сигнал перерисовки
+ _scene.addOperation(calculateMatrixOperation);
+ // Подписываемся на сигналы сцены
+ _scene.changePrimitivesOperation.addSequel(renderOperation);
+ }
+
+ // Сохраняем вид
+ _view = view;
+ }
+
+ /**
+ * @private
+ */
+ alternativa3d function removeFromView(view:View):void {
+ // Сброс ссылки на первый скин
+ firstSkin = null;
+
+ // Отписка от своих операций
+
+ // При изменении камеры пересчёт матрицы
+ calculateTransformationOperation.removeSequel(calculateMatrixOperation);
+ // При изменении матрицы или FOV пересчёт плоскостей отсечения
+ calculateMatrixOperation.removeSequel(calculatePlanesOperation);
+ // При изменении плоскостей перерисовка
+ calculatePlanesOperation.removeSequel(renderOperation);
+
+ if (_scene != null) {
+ // Удаляем все операции из очереди
+ _scene.removeOperation(calculateMatrixOperation);
+ _scene.removeOperation(calculatePlanesOperation);
+ _scene.removeOperation(renderOperation);
+ // Отписываемся от сигналов сцены
+ _scene.changePrimitivesOperation.removeSequel(renderOperation);
+ }
+ // Удаляем ссылку на вид
+ _view = null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function defaultName():String {
+ return "camera" + ++counter;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Camera3D();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function clonePropertiesFrom(source:Object3D):void {
+ super.clonePropertiesFrom(source);
+
+ var src:Camera3D = Camera3D(source);
+ orthographic = src._orthographic;
+ zoom = src._zoom;
+ fov = src._fov;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/Face.as b/Alternativa3D5/5.3/alternativa/engine3d/core/Face.as
new file mode 100644
index 0000000..f5c0906
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/core/Face.as
@@ -0,0 +1,921 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ import flash.geom.Matrix;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Грань, образованная тремя или более вершинами. Грани являются составными частями полигональных объектов. Каждая грань
+ * содержит информацию об объекте и поверхности, которым она принадлежит. Для обеспечения возможности наложения
+ * текстуры на грань, первым трём её вершинам могут быть заданы UV-координаты, на основании которых расчитывается
+ * матрица трансформации текстуры.
+ */
+ final public class Face {
+ // Операции
+ /**
+ * @private
+ * Расчёт глобальной нормали плоскости грани.
+ */
+ alternativa3d var calculateNormalOperation:Operation = new Operation("calculateNormal", this, calculateNormal, Operation.FACE_CALCULATE_NORMAL);
+ /**
+ * @private
+ * Расчёт UV-координат (выполняется до трансформации, чтобы UV корректно разбились при построении BSP).
+ */
+ alternativa3d var calculateUVOperation:Operation = new Operation("calculateUV", this, calculateUV, Operation.FACE_CALCULATE_UV);
+ /**
+ * @private
+ * Обновление примитива в сцене.
+ */
+ alternativa3d var updatePrimitiveOperation:Operation = new Operation("updatePrimitive", this, updatePrimitive, Operation.FACE_UPDATE_PRIMITIVE);
+ /**
+ * @private
+ * Обновление материала.
+ */
+ alternativa3d var updateMaterialOperation:Operation = new Operation("updateMaterial", this, updateMaterial, Operation.FACE_UPDATE_MATERIAL);
+ /**
+ * @private
+ * Расчёт UV для фрагментов (выполняется после трансформации, если её не было).
+ */
+ alternativa3d var calculateFragmentsUVOperation:Operation = new Operation("calculateFragmentsUV", this, calculateFragmentsUV, Operation.FACE_CALCULATE_FRAGMENTS_UV);
+
+ /**
+ * @private
+ * Меш
+ */
+ alternativa3d var _mesh:Mesh;
+ /**
+ * @private
+ * Поверхность
+ */
+ alternativa3d var _surface:Surface;
+ /**
+ * @private
+ * Вершины грани
+ */
+ alternativa3d var _vertices:Array;
+ /**
+ * @private
+ * Количество вершин
+ */
+ alternativa3d var _verticesCount:uint;
+ /**
+ * @private
+ * Примитив
+ */
+ alternativa3d var primitive:PolyPrimitive;
+
+ // UV-координаты
+ /**
+ * @private
+ */
+ alternativa3d var _aUV:Point;
+ /**
+ * @private
+ */
+ alternativa3d var _bUV:Point;
+ /**
+ * @private
+ */
+ alternativa3d var _cUV:Point;
+
+ /**
+ * @private
+ * Коэффициенты базовой UV-матрицы
+ */
+ alternativa3d var uvMatrixBase:Matrix;
+
+ /**
+ * @private
+ * UV Матрица перевода текстурных координат в изометрическую камеру.
+ */
+ alternativa3d var uvMatrix:Matrix;
+ /**
+ * @private
+ * Нормаль плоскости
+ */
+ alternativa3d var globalNormal:Point3D = new Point3D();
+ /**
+ * @private
+ * Смещение плоскости
+ */
+ alternativa3d var globalOffset:Number;
+
+ /**
+ * Создание экземпляра грани.
+ *
+ * @param vertices массив объектов типа alternativa.engine3d.core.Vertex, задающий вершины грани в
+ * порядке обхода лицевой стороны грани против часовой стрелки.
+ *
+ * @see Vertex
+ */
+ public function Face(vertices:Array) {
+ // Сохраняем вершины
+ _vertices = vertices;
+ _verticesCount = vertices.length;
+
+ // Создаём оригинальный примитив
+ primitive = PolyPrimitive.createPolyPrimitive();
+ primitive.face = this;
+ primitive.num = _verticesCount;
+
+ // Обрабатываем вершины
+ for (var i:uint = 0; i < _verticesCount; i++) {
+ var vertex:Vertex = vertices[i];
+ // Добавляем координаты вершины в примитив
+ primitive.points.push(vertex.globalCoords);
+ // Добавляем пустые UV-координаты в примитив
+ primitive.uvs.push(null);
+ // Добавляем вершину в грань
+ vertex.addToFace(this);
+ }
+
+ // Расчёт нормали
+ calculateNormalOperation.addSequel(updatePrimitiveOperation);
+
+ // Расчёт UV грани инициирует расчёт UV фрагментов и перерисовку
+ calculateUVOperation.addSequel(calculateFragmentsUVOperation);
+ calculateUVOperation.addSequel(updateMaterialOperation);
+ }
+
+ /**
+ * @private
+ * Расчёт нормали в глобальных координатах
+ */
+ private function calculateNormal():void {
+ // Вектор AB
+ var vertex:Vertex = _vertices[0];
+ var av:Point3D = vertex.globalCoords;
+ vertex = _vertices[1];
+ var bv:Point3D = vertex.globalCoords;
+ var abx:Number = bv.x - av.x;
+ var aby:Number = bv.y - av.y;
+ var abz:Number = bv.z - av.z;
+ // Вектор AC
+ vertex = _vertices[2];
+ var cv:Point3D = vertex.globalCoords;
+ var acx:Number = cv.x - av.x;
+ var acy:Number = cv.y - av.y;
+ var acz:Number = cv.z - av.z;
+ // Перпендикуляр к плоскости
+ globalNormal.x = acz*aby - acy*abz;
+ globalNormal.y = acx*abz - acz*abx;
+ globalNormal.z = acy*abx - acx*aby;
+ // Нормализация перпендикуляра
+ globalNormal.normalize();
+ }
+
+ /**
+ * @private
+ * Расчитывает глобальное смещение плоскости грани.
+ * Помечает конечные примитивы на удаление, а базовый на добавление в сцене.
+ */
+ private function updatePrimitive():void {
+ // Расчёт смещения
+ var vertex:Vertex = _vertices[0];
+ globalOffset = vertex.globalCoords.x*globalNormal.x + vertex.globalCoords.y*globalNormal.y + vertex.globalCoords.z*globalNormal.z;
+
+ removePrimitive(primitive);
+ primitive.mobility = _mesh.inheritedMobility;
+ _mesh._scene.addPrimitives.push(primitive);
+ }
+
+ /**
+ * @private
+ * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на удаление из сцены
+ */
+ private function removePrimitive(primitive:PolyPrimitive):void {
+ if (primitive.backFragment != null) {
+ removePrimitive(primitive.backFragment);
+ removePrimitive(primitive.frontFragment);
+ primitive.backFragment = null;
+ primitive.frontFragment = null;
+ if (primitive != this.primitive) {
+ primitive.parent = null;
+ primitive.sibling = null;
+ PolyPrimitive.destroyPolyPrimitive(primitive);
+ }
+ } else {
+ // Если примитив в BSP-дереве
+ if (primitive.node != null) {
+ // Удаление примитива
+ _mesh._scene.removeBSPPrimitive(primitive);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Пометка на перерисовку фрагментов грани.
+ */
+ private function updateMaterial():void {
+ if (!updatePrimitiveOperation.queued) {
+ changePrimitive(primitive);
+ }
+ }
+
+ /**
+ * @private
+ * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на перерисовку
+ */
+ private function changePrimitive(primitive:PolyPrimitive):void {
+ if (primitive.backFragment != null) {
+ changePrimitive(primitive.backFragment);
+ changePrimitive(primitive.frontFragment);
+ } else {
+ _mesh._scene.changedPrimitives[primitive] = true;
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт UV-матрицы на основании первых трёх UV-координат.
+ * Расчёт UV-координат для оставшихся точек.
+ */
+ private function calculateUV():void {
+ var i:uint;
+ // Расчёт UV-матрицы
+ if (_aUV != null && _bUV != null && _cUV != null) {
+ var abu:Number = _bUV.x - _aUV.x;
+ var abv:Number = _bUV.y - _aUV.y;
+ var acu:Number = _cUV.x - _aUV.x;
+ var acv:Number = _cUV.y - _aUV.y;
+ var det:Number = abu*acv - abv*acu;
+ if (det != 0) {
+ if (uvMatrixBase == null) {
+ uvMatrixBase = new Matrix();
+ uvMatrix = new Matrix();
+ }
+ uvMatrixBase.a = acv/det;
+ uvMatrixBase.b = -abv/det;
+ uvMatrixBase.c = -acu/det;
+ uvMatrixBase.d = abu/det;
+ uvMatrixBase.tx = -(uvMatrixBase.a*_aUV.x + uvMatrixBase.c*_aUV.y);
+ uvMatrixBase.ty = -(uvMatrixBase.b*_aUV.x + uvMatrixBase.d*_aUV.y);
+
+ // Заполняем UV в базовом примитиве
+ primitive.uvs[0] = _aUV;
+ primitive.uvs[1] = _bUV;
+ primitive.uvs[2] = _cUV;
+
+ // Расчёт недостающих UV
+ if (_verticesCount > 3) {
+ var a:Point3D = primitive.points[0];
+ var b:Point3D = primitive.points[1];
+ var c:Point3D = primitive.points[2];
+
+ var ab1:Number;
+ var ab2:Number;
+ var ac1:Number;
+ var ac2:Number;
+ var ad1:Number;
+ var ad2:Number;
+ var abk:Number;
+ var ack:Number;
+
+ var uv:Point;
+ var point:Point3D;
+
+ // Выбор наиболее подходящих осей для расчёта
+ if (((globalNormal.x < 0) ? -globalNormal.x : globalNormal.x) > ((globalNormal.y < 0) ? -globalNormal.y : globalNormal.y)) {
+ if (((globalNormal.x < 0) ? -globalNormal.x : globalNormal.x) > ((globalNormal.z < 0) ? -globalNormal.z : globalNormal.z)) {
+ // Ось X
+ ab1 = b.y - a.y;
+ ab2 = b.z - a.z;
+ ac1 = c.y - a.y;
+ ac2 = c.z - a.z;
+ det = ab1*ac2 - ac1*ab2;
+ for (i = 3; i < _verticesCount; i++) {
+ point = primitive.points[i];
+ ad1 = point.y - a.y;
+ ad2 = point.z - a.z;
+ abk = (ad1*ac2 - ac1*ad2)/det;
+ ack = (ab1*ad2 - ad1*ab2)/det;
+ uv = primitive.uvs[i];
+ if (uv == null) {
+ uv = new Point();
+ primitive.uvs[i] = uv;
+ }
+ uv.x = _aUV.x + abu*abk + acu*ack;
+ uv.y = _aUV.y + abv*abk + acv*ack;
+ }
+ } else {
+ // Ось Z
+ ab1 = b.x - a.x;
+ ab2 = b.y - a.y;
+ ac1 = c.x - a.x;
+ ac2 = c.y - a.y;
+ det = ab1*ac2 - ac1*ab2;
+ for (i = 3; i < _verticesCount; i++) {
+ point = primitive.points[i];
+ ad1 = point.x - a.x;
+ ad2 = point.y - a.y;
+ abk = (ad1*ac2 - ac1*ad2)/det;
+ ack = (ab1*ad2 - ad1*ab2)/det;
+ uv = primitive.uvs[i];
+ if (uv == null) {
+ uv = new Point();
+ primitive.uvs[i] = uv;
+ }
+ uv.x = _aUV.x + abu*abk + acu*ack;
+ uv.y = _aUV.y + abv*abk + acv*ack;
+ }
+ }
+ } else {
+ if (((globalNormal.y < 0) ? -globalNormal.y : globalNormal.y) > ((globalNormal.z < 0) ? -globalNormal.z : globalNormal.z)) {
+ // Ось Y
+ ab1 = b.x - a.x;
+ ab2 = b.z - a.z;
+ ac1 = c.x - a.x;
+ ac2 = c.z - a.z;
+ det = ab1*ac2 - ac1*ab2;
+ for (i = 3; i < _verticesCount; i++) {
+ point = primitive.points[i];
+ ad1 = point.x - a.x;
+ ad2 = point.z - a.z;
+ abk = (ad1*ac2 - ac1*ad2)/det;
+ ack = (ab1*ad2 - ad1*ab2)/det;
+ uv = primitive.uvs[i];
+ if (uv == null) {
+ uv = new Point();
+ primitive.uvs[i] = uv;
+ }
+ uv.x = _aUV.x + abu*abk + acu*ack;
+ uv.y = _aUV.y + abv*abk + acv*ack;
+ }
+ } else {
+ // Ось Z
+ ab1 = b.x - a.x;
+ ab2 = b.y - a.y;
+ ac1 = c.x - a.x;
+ ac2 = c.y - a.y;
+ det = ab1*ac2 - ac1*ab2;
+ for (i = 3; i < _verticesCount; i++) {
+ point = primitive.points[i];
+ ad1 = point.x - a.x;
+ ad2 = point.y - a.y;
+ abk = (ad1*ac2 - ac1*ad2)/det;
+ ack = (ab1*ad2 - ad1*ab2)/det;
+ uv = primitive.uvs[i];
+ if (uv == null) {
+ uv = new Point();
+ primitive.uvs[i] = uv;
+ }
+ uv.x = _aUV.x + abu*abk + acu*ack;
+ uv.y = _aUV.y + abv*abk + acv*ack;
+ }
+ }
+ }
+ }
+ } else {
+ // Удаляем UV-матрицу
+ uvMatrixBase = null;
+ uvMatrix = null;
+ // Удаляем UV-координаты из базового примитива
+ for (i = 0; i < _verticesCount; i++) {
+ primitive.uvs[i] = null;
+ }
+ }
+ } else {
+ // Удаляем UV-матрицу
+ uvMatrixBase = null;
+ uvMatrix = null;
+ // Удаляем UV-координаты из базового примитива
+ for (i = 0; i < _verticesCount; i++) {
+ primitive.uvs[i] = null;
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт UV-координат для фрагментов примитива, если не было трансформации
+ */
+ private function calculateFragmentsUV():void {
+ // Если в этом цикле не было трансформации
+ if (!updatePrimitiveOperation.queued) {
+ if (uvMatrixBase != null) {
+ // Рассчитываем UV в примитиве
+ calculatePrimitiveUV(primitive);
+ } else {
+ // Удаляем UV в примитиве
+ removePrimitiveUV(primitive);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт UV для точек базового примитива.
+ *
+ * @param primitive
+ */
+ private function calculatePrimitiveUV(primitive:PolyPrimitive):void {
+ if (primitive.backFragment != null) {
+ var points:Array = primitive.points;
+ var backPoints:Array = primitive.backFragment.points;
+ var frontPoints:Array = primitive.frontFragment.points;
+ var uvs:Array = primitive.uvs;
+ var backUVs:Array = primitive.backFragment.uvs;
+ var frontUVs:Array = primitive.frontFragment.uvs;
+ var index1:uint = 0;
+ var index2:uint = 0;
+ var point:Point3D;
+ var uv:Point;
+ var uv1:Point;
+ var uv2:Point;
+ var t:Number;
+ var firstSplit:Boolean = true;
+ for (var i:uint = 0; i < primitive.num; i++) {
+ var split:Boolean = true;
+ point = points[i];
+ if (point == frontPoints[index2]) {
+ if (frontUVs[index2] == null) {
+ frontUVs[index2] = uvs[i];
+ }
+ split = false;
+ index2++;
+ }
+ if (point == backPoints[index1]) {
+ if (backUVs[index1] == null) {
+ backUVs[index1] = uvs[i];
+ }
+ split = false;
+ index1++;
+ }
+
+ if (split) {
+ uv1 = uvs[(i == 0) ? (primitive.num - 1) : (i - 1)];
+ uv2 = uvs[i];
+ t = (firstSplit) ? primitive.splitTime1 : primitive.splitTime2;
+ uv = frontUVs[index2];
+ if (uv == null) {
+ uv = new Point(uv1.x + (uv2.x - uv1.x)*t, uv1.y + (uv2.y - uv1.y)*t);
+ frontUVs[index2] = uv;
+ backUVs[index1] = uv;
+ } else {
+ uv.x = uv1.x + (uv2.x - uv1.x)*t;
+ uv.y = uv1.y + (uv2.y - uv1.y)*t;
+ }
+ firstSplit = false;
+ index2++;
+ index1++;
+ if (point == frontPoints[index2]) {
+ if (frontUVs[index2] == null) {
+ frontUVs[index2] = uvs[i];
+ }
+ index2++;
+ }
+ if (point == backPoints[index1]) {
+ if (backUVs[index1] == null) {
+ backUVs[index1] = uvs[i];
+ }
+ index1++;
+ }
+ }
+ }
+ // Проверяем рассечение последнего ребра
+ if (index2 < primitive.frontFragment.num) {
+ uv1 = uvs[primitive.num - 1];
+ uv2 = uvs[0];
+ t = (firstSplit) ? primitive.splitTime1 : primitive.splitTime2;
+ uv = frontUVs[index2];
+ if (uv == null) {
+ uv = new Point(uv1.x + (uv2.x - uv1.x)*t, uv1.y + (uv2.y - uv1.y)*t);
+ frontUVs[index2] = uv;
+ backUVs[index1] = uv;
+ } else {
+ uv.x = uv1.x + (uv2.x - uv1.x)*t;
+ uv.y = uv1.y + (uv2.y - uv1.y)*t;
+ }
+ }
+
+ calculatePrimitiveUV(primitive.backFragment);
+ calculatePrimitiveUV(primitive.frontFragment);
+ }
+ }
+
+ /**
+ * @private
+ * Удаление UV в примитиве и его фрагментах
+ * @param primitive
+ */
+ private function removePrimitiveUV(primitive:PolyPrimitive):void {
+ // Очищаем список UV
+ for (var i:uint = 0; i < primitive.num; i++) {
+ primitive.uvs[i] = null;
+ }
+ // Если есть фрагменты, удаляем UV в них
+ if (primitive.backFragment != null) {
+ removePrimitiveUV(primitive.backFragment);
+ removePrimitiveUV(primitive.frontFragment);
+ }
+ }
+
+ /**
+ * Массив вершин грани, представленных объектами класса alternativa.engine3d.core.Vertex.
+ *
+ * @see Vertex
+ */
+ public function get vertices():Array {
+ return new Array().concat(_vertices);
+ }
+
+ /**
+ * Количество вершин грани.
+ */
+ public function get verticesCount():uint {
+ return _verticesCount;
+ }
+
+ /**
+ * Полигональный объект, которому принадлежит грань.
+ */
+ public function get mesh():Mesh {
+ return _mesh;
+ }
+
+ /**
+ * Поверхность, которой принадлежит грань.
+ */
+ public function get surface():Surface {
+ return _surface;
+ }
+
+ /**
+ * Идентификатор грани в полигональном объекте. В случае, если грань не принадлежит ни одному объекту, идентификатор
+ * имеет значение null.
+ */
+ public function get id():Object {
+ return (_mesh != null) ? _mesh.getFaceId(this) : null;
+ }
+
+ /**
+ * UV-координаты, соответствующие первой вершине грани.
+ */
+ public function get aUV():Point {
+ return (_aUV != null) ? _aUV.clone() : null;
+ }
+
+ /**
+ * UV-координаты, соответствующие второй вершине грани.
+ */
+ public function get bUV():Point {
+ return (_bUV != null) ? _bUV.clone() : null;
+ }
+
+ /**
+ * UV-координаты, соответствующие третьей вершине грани.
+ */
+ public function get cUV():Point {
+ return (_cUV != null) ? _cUV.clone() : null;
+ }
+
+ /**
+ * @private
+ */
+ public function set aUV(value:Point):void {
+ if (_aUV != null) {
+ if (value != null) {
+ if (!_aUV.equals(value)) {
+ _aUV.x = value.x;
+ _aUV.y = value.y;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ _aUV = null;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ if (value != null) {
+ _aUV = value.clone();
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set bUV(value:Point):void {
+ if (_bUV != null) {
+ if (value != null) {
+ if (!_bUV.equals(value)) {
+ _bUV.x = value.x;
+ _bUV.y = value.y;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ _bUV = null;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ if (value != null) {
+ _bUV = value.clone();
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set cUV(value:Point):void {
+ if (_cUV != null) {
+ if (value != null) {
+ if (!_cUV.equals(value)) {
+ _cUV.x = value.x;
+ _cUV.y = value.y;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ _cUV = null;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ if (value != null) {
+ _cUV = value.clone();
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ }
+ }
+
+ /**
+ * Нормаль в локальной системе координат.
+ */
+ public function get normal():Point3D {
+ var res:Point3D = new Point3D();
+ var vertex:Vertex = _vertices[0];
+ var av:Point3D = vertex.coords;
+ vertex = _vertices[1];
+ var bv:Point3D = vertex.coords;
+ var abx:Number = bv.x - av.x;
+ var aby:Number = bv.y - av.y;
+ var abz:Number = bv.z - av.z;
+ vertex = _vertices[2];
+ var cv:Point3D = vertex.coords;
+ var acx:Number = cv.x - av.x;
+ var acy:Number = cv.y - av.y;
+ var acz:Number = cv.z - av.z;
+ res.x = acz*aby - acy*abz;
+ res.y = acx*abz - acz*abx;
+ res.z = acy*abx - acx*aby;
+ if (res.x != 0 || res.y != 0 || res.z != 0) {
+ var k:Number = Math.sqrt(res.x*res.x + res.y*res.y + res.z*res.z);
+ res.x /= k;
+ res.y /= k;
+ res.z /= k;
+ }
+ return res;
+ }
+
+ /**
+ * Расчёт UV-координат для произвольной точки в системе координат объекта, которому принадлежит грань.
+ *
+ * @param point точка в плоскости грани, для которой производится расчёт UV-координат
+ * @return UV-координаты заданной точки
+ */
+ public function getUV(point:Point3D):Point {
+ return getUVFast(point, normal);
+ }
+
+ /**
+ * @private
+ * Расчёт UV-координат для произвольной точки в локальной системе координат без расчёта
+ * локальной нормали грани. Используется для оптимизации.
+ *
+ * @param point точка в плоскости грани, для которой производится расчёт UV-координат
+ * @param normal нормаль плоскости грани в локальной системе координат
+ * @return UV-координаты заданной точки
+ */
+ alternativa3d function getUVFast(point:Point3D, normal:Point3D):Point {
+ if (_aUV == null || _bUV == null || _cUV == null) {
+ return null;
+ }
+
+ // Выбор наиболее длинной оси нормали
+ var dir:uint;
+ if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.y < 0) ? -normal.y : normal.y)) {
+ if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.z < 0) ? -normal.z : normal.z)) {
+ dir = 0;
+ } else {
+ dir = 2;
+ }
+ } else {
+ if (((normal.y < 0) ? -normal.y : normal.y) > ((normal.z < 0) ? -normal.z : normal.z)) {
+ dir = 1;
+ } else {
+ dir = 2;
+ }
+ }
+
+ // Расчёт соотношения по векторам AB и AC
+ var v:Vertex = _vertices[0];
+ var a:Point3D = v._coords;
+ v = _vertices[1];
+ var b:Point3D = v._coords;
+ v = _vertices[2];
+ var c:Point3D = v._coords;
+
+ var ab1:Number = (dir == 0) ? (b.y - a.y) : (b.x - a.x);
+ var ab2:Number = (dir == 2) ? (b.y - a.y) : (b.z - a.z);
+ var ac1:Number = (dir == 0) ? (c.y - a.y) : (c.x - a.x);
+ var ac2:Number = (dir == 2) ? (c.y - a.y) : (c.z - a.z);
+ var det:Number = ab1*ac2 - ac1*ab2;
+
+ var ad1:Number = (dir == 0) ? (point.y - a.y) : (point.x - a.x);
+ var ad2:Number = (dir == 2) ? (point.y - a.y) : (point.z - a.z);
+ var abk:Number = (ad1*ac2 - ac1*ad2)/det;
+ var ack:Number = (ab1*ad2 - ad1*ab2)/det;
+
+ // Интерполяция по UV первых точек
+ var abu:Number = _bUV.x - _aUV.x;
+ var abv:Number = _bUV.y - _aUV.y;
+ var acu:Number = _cUV.x - _aUV.x;
+ var acv:Number = _cUV.y - _aUV.y;
+
+ return new Point(_aUV.x + abu*abk + acu*ack, _aUV.y + abv*abk + acv*ack);
+ }
+
+ /**
+ * Множество граней, имеющих общие рёбра с текущей гранью.
+ */
+ public function get edgeJoinedFaces():Set {
+ var res:Set = new Set(true);
+ // Перебираем точки грани
+ for (var i:uint = 0; i < _verticesCount; i++) {
+ var a:Vertex = _vertices[i];
+ var b:Vertex = _vertices[(i < _verticesCount - 1) ? (i + 1) : 0];
+
+ // Перебираем грани текущей точки
+ for (var key:* in a._faces) {
+ var face:Face = key;
+ // Если это другая грань и у неё также есть следующая точка
+ if (face != this && face._vertices.indexOf(b) >= 0) {
+ // Значит у граней общее ребро
+ res[face] = true;
+ }
+ }
+ }
+ return res;
+ }
+
+ /**
+ * @private
+ * Удаление всех вершин из грани.
+ * Очистка базового примитива.
+ */
+ alternativa3d function removeVertices():void {
+ // Удалить вершины
+ for (var i:uint = 0; i < _verticesCount; i++) {
+ // Удаляем из списка
+ var vertex:Vertex = _vertices.pop();
+ // Удаляем координаты вершины из примитива
+ primitive.points.pop();
+ // Удаляем вершину из грани
+ vertex.removeFromFace(this);
+ }
+ // Обнуляем количество вершин
+ _verticesCount = 0;
+ }
+
+ /**
+ * @private
+ * Добавление грани на сцену
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {
+ // При добавлении на сцену рассчитываем плоскость и UV
+ scene.addOperation(calculateNormalOperation);
+ scene.addOperation(calculateUVOperation);
+
+ // Подписываем сцену на операции
+ updatePrimitiveOperation.addSequel(scene.calculateBSPOperation);
+ updateMaterialOperation.addSequel(scene.changePrimitivesOperation);
+ }
+
+ /**
+ * @private
+ * Удаление грани из сцены
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(calculateUVOperation);
+ scene.removeOperation(calculateFragmentsUVOperation);
+ scene.removeOperation(calculateNormalOperation);
+ scene.removeOperation(updatePrimitiveOperation);
+ scene.removeOperation(updateMaterialOperation);
+
+ // Удаляем примитивы из сцены
+ removePrimitive(primitive);
+
+ // Посылаем операцию сцены на расчёт BSP
+ scene.addOperation(scene.calculateBSPOperation);
+
+ // Отписываем сцену от операций
+ updatePrimitiveOperation.removeSequel(scene.calculateBSPOperation);
+ updateMaterialOperation.removeSequel(scene.changePrimitivesOperation);
+ }
+
+ /**
+ * @private
+ * Добавление грани в меш
+ * @param mesh
+ */
+ alternativa3d function addToMesh(mesh:Mesh):void {
+ // Подписка на операции меша
+ mesh.changeCoordsOperation.addSequel(updatePrimitiveOperation);
+ mesh.changeRotationOrScaleOperation.addSequel(calculateNormalOperation);
+ mesh.calculateMobilityOperation.addSequel(updatePrimitiveOperation);
+ // Сохранить меш
+ _mesh = mesh;
+ }
+
+ /**
+ * @private
+ * Удаление грани из меша
+ * @param mesh
+ */
+ alternativa3d function removeFromMesh(mesh:Mesh):void {
+ // Отписка от операций меша
+ mesh.changeCoordsOperation.removeSequel(updatePrimitiveOperation);
+ mesh.changeRotationOrScaleOperation.removeSequel(calculateNormalOperation);
+ mesh.calculateMobilityOperation.removeSequel(updatePrimitiveOperation);
+ // Удалить ссылку на меш
+ _mesh = null;
+ }
+
+ /**
+ * @private
+ * Добавление к поверхности
+ *
+ * @param surface
+ */
+ alternativa3d function addToSurface(surface:Surface):void {
+ // Подписка поверхности на операции
+ surface.changeMaterialOperation.addSequel(updateMaterialOperation);
+ // Если при смене поверхности изменился материал
+ if (_mesh != null && (_surface != null && _surface._material != surface._material || _surface == null && surface._material != null)) {
+ // Отправляем сигнал смены материала
+ _mesh.addOperationToScene(updateMaterialOperation);
+ }
+ // Сохранить поверхность
+ _surface = surface;
+ }
+
+ /**
+ * @private
+ * Удаление из поверхности
+ *
+ * @param surface
+ */
+ alternativa3d function removeFromSurface(surface:Surface):void {
+ // Отписка поверхности от операций
+ surface.changeMaterialOperation.removeSequel(updateMaterialOperation);
+ // Если был материал
+ if (surface._material != null) {
+ // Отправляем сигнал смены материала
+ _mesh.addOperationToScene(updateMaterialOperation);
+ }
+ // Удалить ссылку на поверхность
+ _surface = null;
+ }
+
+ /**
+ * Строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ var res:String = "[Face ID:" + id + ((_verticesCount > 0) ? " vertices:" : "");
+ for (var i:uint = 0; i < _verticesCount; i++) {
+ var vertex:Vertex = _vertices[i];
+ res += vertex.id + ((i < _verticesCount - 1) ? ", " : "");
+ }
+ res += "]";
+ return res;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/Mesh.as b/Alternativa3D5/5.3/alternativa/engine3d/core/Mesh.as
new file mode 100644
index 0000000..0a13b19
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/core/Mesh.as
@@ -0,0 +1,981 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.errors.FaceExistsError;
+ import alternativa.engine3d.errors.FaceNeedMoreVerticesError;
+ import alternativa.engine3d.errors.FaceNotFoundError;
+ import alternativa.engine3d.errors.InvalidIDError;
+ import alternativa.engine3d.errors.SurfaceExistsError;
+ import alternativa.engine3d.errors.SurfaceNotFoundError;
+ import alternativa.engine3d.errors.VertexExistsError;
+ import alternativa.engine3d.errors.VertexNotFoundError;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.types.Map;
+ import alternativa.utils.ObjectUtils;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Полигональный объект — базовый класс для трёхмерных объектов, состоящих из граней-полигонов. Объект
+ * содержит в себе наборы вершин, граней и поверхностей.
+ */
+ public class Mesh extends Object3D {
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ // Инкременты для идентификаторов вершин, граней и поверхностей
+ private var vertexIDCounter:uint = 0;
+ private var faceIDCounter:uint = 0;
+ private var surfaceIDCounter:uint = 0;
+
+ /**
+ * @private
+ * Список вершин
+ */
+ alternativa3d var _vertices:Map = new Map();
+ /**
+ * @private
+ * Список граней
+ */
+ alternativa3d var _faces:Map = new Map();
+ /**
+ * @private
+ * Список поверхностей
+ */
+ alternativa3d var _surfaces:Map = new Map();
+
+ /**
+ * Создание экземпляра полигонального объекта.
+ *
+ * @param name имя экземпляра
+ */
+ public function Mesh(name:String = null) {
+ super(name);
+ }
+
+ /**
+ * Добавление новой вершины к объекту.
+ *
+ * @param x координата X в локальной системе координат объекта
+ * @param y координата Y в локальной системе координат объекта
+ * @param z координата Z в локальной системе координат объекта
+ * @param id идентификатор вершины. Если указано значение null, идентификатор будет
+ * сформирован автоматически.
+ *
+ * @return экземпляр добавленной вершины
+ *
+ * @throws alternativa.engine3d.errors.VertexExistsError объект уже содержит вершину с указанным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function createVertex(x:Number = 0, y:Number = 0, z:Number = 0, id:Object = null):Vertex {
+ // Проверяем ID
+ if (id != null) {
+ // Если уже есть вершина с таким ID
+ if (_vertices[id] != undefined) {
+ if (_vertices[id] is Vertex) {
+ throw new VertexExistsError(id, this);
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ } else {
+ // Ищем первый свободный
+ while (_vertices[vertexIDCounter] != undefined) {
+ vertexIDCounter++;
+ }
+ id = vertexIDCounter;
+ }
+
+ // Создаём вершину
+ var v:Vertex = new Vertex(x, y, z);
+
+ // Добавляем вершину на сцену
+ if (_scene != null) {
+ v.addToScene(_scene);
+ }
+
+ // Добавляем вершину в меш
+ v.addToMesh(this);
+ _vertices[id] = v;
+
+ return v;
+ }
+
+ /**
+ * Удаление вершины из объекта. При удалении вершины из объекта также удаляются все грани, которым принадлежит данная вершина.
+ *
+ * @param vertex экземпляр класса alternativa.engine3d.core.Vertex или идентификатор удаляемой вершины
+ *
+ * @return экземпляр удалённой вершины
+ *
+ * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function removeVertex(vertex:Object):Vertex {
+ var byLink:Boolean = vertex is Vertex;
+
+ // Проверяем на null
+ if (vertex == null) {
+ throw new VertexNotFoundError(null, this);
+ }
+
+ // Проверяем наличие вершины в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Vertex(vertex)._mesh != this) {
+ // Если вершина не в меше
+ throw new VertexNotFoundError(vertex, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_vertices[vertex] == undefined) {
+ // Если нет вершины с таким ID
+ throw new VertexNotFoundError(vertex, this);
+ } else if (!(_vertices[vertex] is Vertex)) {
+ // По этому id не вершина
+ throw new InvalidIDError(vertex, this);
+ }
+ }
+
+ // Находим вершину и её ID
+ var v:Vertex = byLink ? Vertex(vertex) : _vertices[vertex];
+ var id:Object = byLink ? getVertexId(Vertex(vertex)) : vertex;
+
+ // Удаляем вершину из сцены
+ if (_scene != null) {
+ v.removeFromScene(_scene);
+ }
+
+ // Удаляем вершину из меша
+ v.removeFromMesh(this);
+ delete _vertices[id];
+
+ return v;
+ }
+
+ /**
+ * Добавление грани к объекту. В результате выполнения метода в объекте появляется новая грань, не привязанная
+ * ни к одной поверхности.
+ *
+ * @param vertices массив вершин грани, указанных в порядке обхода лицевой стороны грани против часовой
+ * стрелки. Каждый элемент массива может быть либо экземпляром класса alternativa.engine3d.core.Vertex,
+ * либо идентификатором в наборе вершин объекта. В обоих случаях объект должен содержать указанную вершину.
+ * @param id идентификатор грани. Если указано значение null, идентификатор будет
+ * сформирован автоматически.
+ *
+ * @return экземпляр добавленной грани
+ *
+ * @throws alternativa.engine3d.errors.FaceNeedMoreVerticesError в качестве массива вершин был передан
+ * null, либо количество вершин в массиве меньше трёх
+ * @throws alternativa.engine3d.errors.FaceExistsError объект уже содержит грань с заданным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит какую-либо вершину из входного массива
+ *
+ * @see Vertex
+ */
+ public function createFace(vertices:Array, id:Object = null):Face {
+
+ // Проверяем на null
+ if (vertices == null) {
+ throw new FaceNeedMoreVerticesError(this);
+ }
+
+ // Проверяем ID
+ if (id != null) {
+ // Если уже есть грань с таким ID
+ if (_faces[id] != undefined) {
+ if (_faces[id] is Face) {
+ throw new FaceExistsError(id, this);
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ } else {
+ // Ищем первый свободный ID
+ while (_faces[faceIDCounter] != undefined) {
+ faceIDCounter++;
+ }
+ id = faceIDCounter;
+ }
+
+ // Проверяем количество точек
+ var length:uint = vertices.length;
+ if (length < 3) {
+ throw new FaceNeedMoreVerticesError(this, length);
+ }
+
+ // Проверяем и формируем список вершин
+ var v:Array = new Array();
+ var vertex:Vertex;
+ for (var i:uint = 0; i < length; i++) {
+ if (vertices[i] is Vertex) {
+ // Если работаем со ссылками
+ vertex = vertices[i];
+ if (vertex._mesh != this) {
+ // Если вершина не в меше
+ throw new VertexNotFoundError(vertices[i], this);
+ }
+ } else {
+ // Если работаем с ID
+ if (_vertices[vertices[i]] == null) {
+ // Если нет вершины с таким ID
+ throw new VertexNotFoundError(vertices[i], this);
+ } else if (!(_vertices[vertices[i]] is Vertex)) {
+ // Если id зарезервировано
+ throw new InvalidIDError(vertices[i],this);
+ }
+ vertex = _vertices[vertices[i]];
+ }
+ v.push(vertex);
+ }
+
+ // Создаём грань
+ var f:Face = new Face(v);
+
+ // Добавляем грань на сцену
+ if (_scene != null) {
+ f.addToScene(_scene);
+ }
+
+ // Добавляем грань в меш
+ f.addToMesh(this);
+ _faces[id] = f;
+
+ return f;
+ }
+
+ /**
+ * Удаление грани из объекта. Грань также удаляется из поверхности объекта, которой она принадлежит.
+ *
+ * @param экземпляр класса alternativa.engine3d.core.Face или идентификатор удаляемой грани
+ *
+ * @return экземпляр удалённой грани
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function removeFace(face:Object):Face {
+ var byLink:Boolean = face is Face;
+
+ // Проверяем на null
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие грани в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Face(face)._mesh != this) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_faces[face] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(face, this);
+ } else if (!(_faces[face] is Face)) {
+ throw new InvalidIDError(face, this);
+ }
+ }
+
+ // Находим грань и её ID
+ var f:Face = byLink ? Face(face) : _faces[face] ;
+ var id:Object = byLink ? getFaceId(Face(face)) : face;
+
+ // Удаляем грань из сцены
+ if (_scene != null) {
+ f.removeFromScene(_scene);
+ }
+
+ // Удаляем вершины из грани
+ f.removeVertices();
+
+ // Удаляем грань из поверхности
+ if (f._surface != null) {
+ f._surface._faces.remove(f);
+ f.removeFromSurface(f._surface);
+ }
+
+ // Удаляем грань из меша
+ f.removeFromMesh(this);
+ delete _faces[id];
+
+ return f;
+ }
+
+ /**
+ * Добавление новой поверхности к объекту.
+ *
+ * @param faces набор граней, составляющих поверхность. Каждый элемент массива должен быть либо экземпляром класса
+ * alternativa.engine3d.core.Face, либо идентификатором грани. В обоих случаях объект должен содержать
+ * указанную грань. Если значение параметра равно null, то будет создана пустая поверхность. Если
+ * какая-либо грань содержится в другой поверхности, она будет перенесена в новую поверхность.
+ * @param id идентификатор новой поверхности. Если указано значение null, идентификатор будет
+ * сформирован автоматически.
+ *
+ * @return экземпляр добавленной поверхности
+ *
+ * @throws alternativa.engine3d.errors.SurfaceExistsError объект уже содержит поверхность с заданным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Face
+ */
+ public function createSurface(faces:Array = null, id:Object = null):Surface {
+ // Проверяем ID
+ if (id != null) {
+ // Если уже есть поверхность с таким ID
+ if (_surfaces[id] != undefined) {
+ if (_surfaces[id] is Surface) {
+ throw new SurfaceExistsError(id, this);
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ } else {
+ // Ищем первый свободный ID
+ while (_surfaces[surfaceIDCounter] != undefined) {
+ surfaceIDCounter++;
+ }
+ id = surfaceIDCounter;
+ }
+
+ // Создаём поверхность
+ var s:Surface = new Surface();
+
+ // Добавляем поверхность на сцену
+ if (_scene != null) {
+ s.addToScene(_scene);
+ }
+
+ // Добавляем поверхность в меш
+ s.addToMesh(this);
+ _surfaces[id] = s;
+
+ // Добавляем грани, если есть
+ if (faces != null) {
+ var length:uint = faces.length;
+ for (var i:uint = 0; i < length; i++) {
+ s.addFace(faces[i]);
+ }
+ }
+
+ return s;
+ }
+
+ /**
+ * Удаление поверхности объекта. Из удаляемой поверхности также удаляются все содержащиеся в ней грани.
+ *
+ * @param surface экземпляр класса alternativa.engine3d.core.Face или идентификатор удаляемой поверхности
+ *
+ * @return экземпляр удалённой поверхности
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function removeSurface(surface:Object):Surface {
+ var byLink:Boolean = surface is Surface;
+
+ // Проверяем на null
+ if (surface == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие поверхности в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Surface(surface)._mesh != this) {
+ // Если поверхность не в меше
+ throw new SurfaceNotFoundError(surface, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_surfaces[surface] == undefined) {
+ // Если нет поверхности с таким ID
+ throw new SurfaceNotFoundError(surface, this);
+ } else if (!(_surfaces[surface] is Surface)) {
+ throw new InvalidIDError(surface, this);
+ }
+ }
+
+ // Находим поверхность и её ID
+ var s:Surface = byLink ? Surface(surface) : _surfaces[surface];
+ var id:Object = byLink ? getSurfaceId(Surface(surface)) : surface;
+
+ // Удаляем поверхность из сцены
+ if (_scene != null) {
+ s.removeFromScene(_scene);
+ }
+
+ // Удаляем грани из поверхности
+ s.removeFaces();
+
+ // Удаляем поверхность из меша
+ s.removeFromMesh(this);
+ delete _surfaces[id];
+
+ return s;
+ }
+
+ /**
+ * Добавление всех граней объекта в указанную поверхность.
+ *
+ * @param surface экземпляр класса alternativa.engine3d.core.Surface или идентификатор поверхности, в
+ * которую добавляются грани. Если задан идентификатор, и объект не содержит поверхность с таким идентификатором,
+ * будет создана новая поверхность.
+ *
+ * @param removeSurfaces удалять или нет пустые поверхности после переноса граней
+ *
+ * @return экземпляр поверхности, в которую перенесены грани
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанный экземпляр поверхности
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function moveAllFacesToSurface(surface:Object = null, removeSurfaces:Boolean = false):Surface {
+ var returnSurface:Surface;
+ var returnSurfaceId:Object;
+ if (surface is Surface) {
+ // Работаем с экземпляром Surface
+ if (surface._mesh == this) {
+ returnSurface = Surface(surface);
+ } else {
+ throw new SurfaceNotFoundError(surface, this);
+ }
+ } else {
+ // Работаем с идентификатором
+ if (_surfaces[surface] == undefined) {
+ // Поверхности еще нет
+ returnSurface = createSurface(null, surface);
+ returnSurfaceId = surface;
+ } else {
+ if (_surfaces[surface] is Surface) {
+ returnSurface = _surfaces[surface];
+ } else {
+ // _surfaces[surface] по идентификатору возвращает не Surface
+ throw new InvalidIDError(surface, this);
+ }
+ }
+ }
+ // Перемещаем все грани
+ for each (var face:Face in _faces) {
+ if (face._surface != returnSurface) {
+ returnSurface.addFace(face);
+ }
+ }
+ if (removeSurfaces) {
+ // Удаляем старые, теперь вручную - меньше проверок, но рискованно
+ if (returnSurfaceId == null) {
+ returnSurfaceId = getSurfaceId(returnSurface);
+ }
+ var newSurfaces:Map = new Map();
+ newSurfaces[returnSurfaceId] = returnSurface;
+ delete _surfaces[returnSurfaceId];
+ // Удаляем оставшиеся
+ for (var currentSurfaceId:* in _surfaces) {
+ // Удаляем поверхность из сцены
+ var currentSurface:Surface = _surfaces[currentSurfaceId];
+ if (_scene != null) {
+ currentSurface.removeFromScene(_scene);
+ }
+ // Удаляем поверхность из меша
+ currentSurface.removeFromMesh(this);
+ delete _surfaces[currentSurfaceId];
+ }
+ // Новый список граней
+ _surfaces = newSurfaces;
+ }
+ return returnSurface;
+ }
+
+ /**
+ * Установка материала для указанной поверхности.
+ *
+ * @param material материал, назначаемый поверхности. Один экземпляр SurfaceMaterial можно назначить только одной поверхности.
+ * @param surface экземпляр класса alternativa.engine3d.core.Surface или идентификатор поверхности
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Surface
+ */
+ public function setMaterialToSurface(material:SurfaceMaterial, surface:Object):void {
+ var byLink:Boolean = surface is Surface;
+
+ // Проверяем на null
+ if (surface == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие поверхности в меше
+ if (byLink) {
+ // Если назначаем по ссылке
+ if (Surface(surface)._mesh != this) {
+ // Если поверхность не в меше
+ throw new SurfaceNotFoundError(surface, this);
+ }
+ } else {
+ // Если назначаем по ID
+ if (_surfaces[surface] == undefined) {
+ // Если нет поверхности с таким ID
+ throw new SurfaceNotFoundError(surface, this);
+ } else if (!(_surfaces[surface] is Surface)) {
+ throw new InvalidIDError(surface, this);
+ }
+ }
+
+ // Находим поверхность
+ var s:Surface = byLink ? Surface(surface) : _surfaces[surface];
+
+ // Назначаем материал
+ s.material = material;
+ }
+
+ /**
+ * Установка материала для всех поверхностей объекта. Для каждой поверхности устанавливается копия материала.
+ * При передаче null в качестве параметра происходит сброс материалов у всех поверхностей.
+ *
+ * @param material устанавливаемый материал
+ */
+ public function cloneMaterialToAllSurfaces(material:SurfaceMaterial):void {
+ for each (var surface:Surface in _surfaces) {
+ surface.material = (material != null) ? SurfaceMaterial(material.clone()) : null;
+ }
+ }
+
+ /**
+ * Установка UV-координат для указанной грани объекта. Матрица преобразования UV-координат расчитывается по
+ * UV-координатам первых трёх вершин грани, поэтому для корректного текстурирования эти вершины должны образовывать
+ * невырожденный треугольник в UV-пространстве.
+ *
+ * @param aUV UV-координаты, соответствующие первой вершине грани
+ * @param bUV UV-координаты, соответствующие второй вершине грани
+ * @param cUV UV-координаты, соответствующие третьей вершине грани
+ * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Face
+ */
+ public function setUVsToFace(aUV:Point, bUV:Point, cUV:Point, face:Object):void {
+ var byLink:Boolean = face is Face;
+
+ // Проверяем на null
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие грани в меше
+ if (byLink) {
+ // Если назначаем по ссылке
+ if (Face(face)._mesh != this) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ } else {
+ // Если назначаем по ID
+ if (_faces[face] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(face, this);
+ } else if (!(_faces[face] is Face)) {
+ throw new InvalidIDError(face, this);
+ }
+ }
+
+ // Находим грань
+ var f:Face = byLink ? Face(face) : _faces[face];
+
+ // Назначаем UV-координаты
+ f.aUV = aUV;
+ f.bUV = bUV;
+ f.cUV = cUV;
+ }
+
+ /**
+ * Набор вершин объекта. Ключами ассоциативного массива являются идентификаторы вершин, значениями - экземпляры вершин.
+ */
+ public function get vertices():Map {
+ return _vertices.clone();
+ }
+
+ /**
+ * Набор граней объекта. Ключами ассоциативного массива являются идентификаторы граней, значениями - экземпляры граней.
+ */
+ public function get faces():Map {
+ return _faces.clone();
+ }
+
+ /**
+ * Набор поверхностей объекта. Ключами ассоциативного массива являются идентификаторы поверхностей, значениями - экземпляры поверхностей.
+ */
+ public function get surfaces():Map {
+ return _surfaces.clone();
+ }
+
+ /**
+ * Получение вершины объекта по её идентификатору.
+ *
+ * @param id идентификатор вершины
+ *
+ * @return экземпляр вершины с указанным идентификатором
+ *
+ * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит вершину с указанным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function getVertexById(id:Object):Vertex {
+ if (id == null) {
+ throw new VertexNotFoundError(null, this);
+ }
+ if (_vertices[id] == undefined) {
+ // Если нет вершины с таким ID
+ throw new VertexNotFoundError(id, this);
+ } else {
+ if (_vertices[id] is Vertex) {
+ return _vertices[id];
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ }
+
+ /**
+ * Получение идентификатора вершины объекта.
+ *
+ * @param экземпляр вершины
+ *
+ * @return идентификатор указанной вершины
+ *
+ * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину
+ */
+ public function getVertexId(vertex:Vertex):Object {
+ if (vertex == null) {
+ throw new VertexNotFoundError(null, this);
+ }
+ if (vertex._mesh != this) {
+ // Если вершина не в меше
+ throw new VertexNotFoundError(vertex, this);
+ }
+ for (var i:Object in _vertices) {
+ if (_vertices[i] == vertex) {
+ return i;
+ }
+ }
+ throw new VertexNotFoundError(vertex, this);
+ }
+
+ /**
+ * Проверка наличия вершины в объекте.
+ *
+ * @param vertex экземпляр класса alternativa.engine3d.core.Vertex или идентификатор вершины
+ *
+ * @return true, если объект содержит указанную вершину, иначе false
+ *
+ * @throws alternativa.engine3d.errors.VertexNotFoundError в качестве vertex был передан null
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Vertex
+ */
+ public function hasVertex(vertex:Object):Boolean {
+ if (vertex == null) {
+ throw new VertexNotFoundError(null, this);
+ }
+ if (vertex is Vertex) {
+ // Проверка вершины
+ return vertex._mesh == this;
+ } else {
+ // Проверка ID вершины
+ if (_vertices[vertex] != undefined) {
+ // По этому ID есть объект
+ if (_vertices[vertex] is Vertex) {
+ // Объект является вершиной
+ return true;
+ } else {
+ // ID некорректный
+ throw new InvalidIDError(vertex, this);
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Получение грани объекта по ее идентификатору.
+ *
+ * @param id идентификатор грани
+ *
+ * @return экземпляр грани с указанным идентификатором
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит грань с указанным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function getFaceById(id:Object):Face {
+ if (id == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+ if (_faces[id] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(id, this);
+ } else {
+ if (_faces[id] is Face) {
+ return _faces[id];
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ }
+
+ /**
+ * Получение идентификатора грани объекта.
+ *
+ * @param face экземпляр грани
+ *
+ * @return идентификатор указанной грани
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
+ */
+ public function getFaceId(face:Face):Object {
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+ if (face._mesh != this) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ for (var i:Object in _faces) {
+ if (_faces[i] == face) {
+ return i;
+ }
+ }
+ throw new FaceNotFoundError(face, this);
+ }
+
+ /**
+ * Проверка наличия грани в объекте.
+ *
+ * @param face экземпляр класса Face или идентификатор грани
+ *
+ * @return true, если объект содержит указанную грань, иначе false
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError в качестве face был указан null
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function hasFace(face:Object):Boolean {
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+ if (face is Face) {
+ // Проверка грани
+ return face._mesh == this;
+ } else {
+ // Проверка ID грани
+ if (_faces[face] != undefined) {
+ // По этому ID есть объект
+ if (_faces[face] is Face) {
+ // Объект является гранью
+ return true;
+ } else {
+ // ID некорректный
+ throw new InvalidIDError(face, this);
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Получение поверхности объекта по ее идентификатору
+ *
+ * @param id идентификатор поверхности
+ *
+ * @return экземпляр поверхности с указанным идентификатором
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит поверхность с указанным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function getSurfaceById(id:Object):Surface {
+ if (id == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+ if (_surfaces[id] == undefined) {
+ // Если нет поверхности с таким ID
+ throw new SurfaceNotFoundError(id, this);
+ } else {
+ if (_surfaces[id] is Surface) {
+ return _surfaces[id];
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ }
+
+ /**
+ * Получение идентификатора поверхности объекта.
+ *
+ * @param surface экземпляр поверхности
+ *
+ * @return идентификатор указанной поверхности
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
+ */
+ public function getSurfaceId(surface:Surface):Object {
+ if (surface == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+ if (surface._mesh != this) {
+ // Если поверхность не в меше
+ throw new SurfaceNotFoundError(surface, this);
+ }
+ for (var i:Object in _surfaces) {
+ if (_surfaces[i] == surface) {
+ return i;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Проверка наличия поверхности в объекте.
+ *
+ * @param surface экземпляр класса Surface или идентификатор поверхности
+ *
+ * @return true, если объект содержит указанную поверхность, иначе bottomRadius = topRadius будет построен цилиндр.false
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError в качестве surface был передан null
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function hasSurface(surface:Object):Boolean {
+ if (surface == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+ if (surface is Surface) {
+ // Проверка поверхности
+ return surface._mesh == this;
+ } else {
+ // Проверка ID поверхности
+ if (_surfaces[surface] != undefined) {
+ // По этому ID есть объект
+ if (_surfaces[surface] is Surface) {
+ // Объект является поверхностью
+ return true;
+ } else {
+ // ID некорректный
+ throw new InvalidIDError(surface, this);
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function setScene(value:Scene3D):void {
+ if (_scene != value) {
+ var vertex:Vertex;
+ var face:Face;
+ var surface:Surface;
+ if (value != null) {
+ // Добавить вершины на сцену
+ for each (vertex in _vertices) {
+ vertex.addToScene(value);
+ }
+ // Добавить грани на сцену
+ for each (face in _faces) {
+ face.addToScene(value);
+ }
+ // Добавить поверхности на сцену
+ for each (surface in _surfaces) {
+ surface.addToScene(value);
+ }
+ } else {
+ // Удалить вершины из сцены
+ for each (vertex in _vertices) {
+ vertex.removeFromScene(_scene);
+ }
+ // Удалить грани из сцены
+ for each (face in _faces) {
+ face.removeFromScene(_scene);
+ }
+ // Удалить поверхности из сцены
+ for each (surface in _surfaces) {
+ surface.removeFromScene(_scene);
+ }
+ }
+ }
+ super.setScene(value);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function defaultName():String {
+ return "mesh" + ++counter;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function toString():String {
+ return "[" + ObjectUtils.getClassName(this) + " " + _name + " vertices: " + _vertices.length + " faces: " + _faces.length + "]";
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Mesh();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function clonePropertiesFrom(source:Object3D):void {
+ super.clonePropertiesFrom(source);
+
+ var src:Mesh = Mesh(source);
+
+ var id:*;
+ var len:int;
+ var i:int;
+ // Копирование вершин
+ var vertexMap:Map = new Map(true);
+ for (id in src._vertices) {
+ var sourceVertex:Vertex = src._vertices[id];
+ vertexMap[sourceVertex] = createVertex(sourceVertex.x, sourceVertex.y, sourceVertex.z, id);
+ }
+
+ // Копирование граней
+ var faceMap:Map = new Map(true);
+ for (id in src._faces) {
+ var sourceFace:Face = src._faces[id];
+ len = sourceFace._vertices.length;
+ var faceVertices:Array = new Array(len);
+ for (i = 0; i < len; i++) {
+ faceVertices[i] = vertexMap[sourceFace._vertices[i]];
+ }
+ var newFace:Face = createFace(faceVertices, id);
+ newFace.aUV = sourceFace._aUV;
+ newFace.bUV = sourceFace._bUV;
+ newFace.cUV = sourceFace._cUV;
+ faceMap[sourceFace] = newFace;
+ }
+
+ // Копирование поверхностей
+ for (id in src._surfaces) {
+ var sourceSurface:Surface = src._surfaces[id];
+ var surfaceFaces:Array = sourceSurface._faces.toArray();
+ len = surfaceFaces.length;
+ for (i = 0; i < len; i++) {
+ surfaceFaces[i] = faceMap[surfaceFaces[i]];
+ }
+ createSurface(surfaceFaces, id).material = SurfaceMaterial(sourceSurface.material.clone());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/Object3D.as b/Alternativa3D5/5.3/alternativa/engine3d/core/Object3D.as
new file mode 100644
index 0000000..d2133da
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/core/Object3D.as
@@ -0,0 +1,708 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.errors.Object3DHierarchyError;
+ import alternativa.engine3d.errors.Object3DNotFoundError;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+ import alternativa.utils.ObjectUtils;
+
+ use namespace alternativa3d;
+
+ /**
+ * Базовый класс для объектов, находящихся в сцене. Класс реализует иерархию объектов сцены, а также содержит сведения
+ * о трансформации объекта как единого целого.
+ *
+ * X, Y, Z и параллельного переноса центра объекта из начала координат.
+ * Операции применяются в порядке их перечисления.
+ *
+ * createEmptyObject() и clonePropertiesFrom().
+ *
+ * @return клонированный экземпляр объекта
+ *
+ * @see #createEmptyObject()
+ * @see #clonePropertiesFrom()
+ */
+ public function clone():Object3D {
+ var copy:Object3D = createEmptyObject();
+ copy.clonePropertiesFrom(this);
+
+ // Клонирование детей
+ for (var key:* in _children) {
+ var child:Object3D = key;
+ copy.addChild(child.clone());
+ }
+
+ return copy;
+ }
+
+ /**
+ * Получение дочернего объекта с заданным именем.
+ *
+ * @param name имя дочернего объекта
+ * @return любой дочерний объект с заданным именем или null в случае отсутствия таких объектов
+ */
+ public function getChildByName(name:String):Object3D {
+ for (var key:* in _children) {
+ var child:Object3D = key;
+ if (child._name == name) {
+ return child;
+ }
+ }
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/Operation.as b/Alternativa3D5/5.3/alternativa/engine3d/core/Operation.as
new file mode 100644
index 0000000..ca430ac
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/core/Operation.as
@@ -0,0 +1,125 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.types.Set;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ */
+ public class Operation {
+
+ alternativa3d static const OBJECT_CALCULATE_TRANSFORMATION:uint = 0x01000000;
+ alternativa3d static const OBJECT_CALCULATE_MOBILITY:uint = 0x02000000;
+ alternativa3d static const VERTEX_CALCULATE_COORDS:uint = 0x03000000;
+ alternativa3d static const FACE_CALCULATE_NORMAL:uint = 0x04000000;
+ alternativa3d static const FACE_CALCULATE_UV:uint = 0x05000000;
+ alternativa3d static const FACE_UPDATE_PRIMITIVE:uint = 0x06000000;
+ alternativa3d static const SCENE_CALCULATE_BSP:uint = 0x07000000;
+ alternativa3d static const FACE_UPDATE_MATERIAL:uint = 0x08000000;
+ alternativa3d static const FACE_CALCULATE_FRAGMENTS_UV:uint = 0x09000000;
+ alternativa3d static const CAMERA_CALCULATE_MATRIX:uint = 0x0A000000;
+ alternativa3d static const CAMERA_CALCULATE_PLANES:uint = 0x0B000000;
+ alternativa3d static const CAMERA_RENDER:uint = 0x0C000000;
+ alternativa3d static const SCENE_CLEAR_PRIMITIVES:uint = 0x0D000000;
+
+ // Объект
+ alternativa3d var object:Object;
+
+ // Метод
+ alternativa3d var method:Function;
+
+ // Название метода
+ alternativa3d var name:String;
+
+ // Последствия
+ private var sequel:Operation;
+ private var sequels:Set;
+
+ // Приоритет операции
+ alternativa3d var priority:uint;
+
+ // Находится ли операция в очереди
+ alternativa3d var queued:Boolean = false;
+
+ public function Operation(name:String, object:Object = null, method:Function = null, priority:uint = 0) {
+ this.object = object;
+ this.method = method;
+ this.name = name;
+ this.priority = priority;
+ }
+
+ // Добавить последствие
+ alternativa3d function addSequel(operation:Operation):void {
+ if (sequel == null) {
+ if (sequels == null) {
+ sequel = operation;
+ } else {
+ sequels[operation] = true;
+ }
+ } else {
+ if (sequel != operation) {
+ sequels = new Set(true);
+ sequels[sequel] = true;
+ sequels[operation] = true;
+ sequel = null;
+ }
+ }
+ }
+
+ // Удалить последствие
+ alternativa3d function removeSequel(operation:Operation):void {
+ if (sequel == null) {
+ if (sequels != null) {
+ delete sequels[operation];
+ var key:*;
+ var single:Boolean = false;
+ for (key in sequels) {
+ if (single) {
+ single = false;
+ break;
+ }
+ single = true;
+ }
+ if (single) {
+ sequel = key;
+ sequels = null;
+ }
+ }
+ } else {
+ if (sequel == operation) {
+ sequel = null;
+ }
+ }
+ }
+
+ alternativa3d function collectSequels(collector:Array):void {
+ if (sequel == null) {
+ // Проверяем последствия
+ for (var key:* in sequels) {
+ var operation:Operation = key;
+ // Если операция ещё не в очереди
+ if (!operation.queued) {
+ // Добавляем её в очередь
+ collector.push(operation);
+ // Устанавливаем флаг очереди
+ operation.queued = true;
+ // Вызываем добавление в очередь её последствий
+ operation.collectSequels(collector);
+ }
+ }
+ } else {
+ if (!sequel.queued) {
+ collector.push(sequel);
+ sequel.queued = true;
+ sequel.collectSequels(collector);
+ }
+ }
+ }
+
+ public function toString():String {
+ return "[Operation " + (priority >>> 24) + "/" + (priority & 0xFFFFFF) + " " + object + "." + name + "]";
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/PolyPrimitive.as b/Alternativa3D5/5.3/alternativa/engine3d/core/PolyPrimitive.as
new file mode 100644
index 0000000..5fa4143
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/core/PolyPrimitive.as
@@ -0,0 +1,83 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ */
+ public class PolyPrimitive {
+
+ // Количество точек
+ alternativa3d var num:uint;
+ // Точки
+ alternativa3d var points:Array = new Array();
+ // UV-координаты
+ alternativa3d var uvs:Array = new Array();
+
+ // Грань
+ alternativa3d var face:Face;
+ // Родительский примитив
+ alternativa3d var parent:PolyPrimitive;
+ // Соседний примитив (при наличии родительского)
+ alternativa3d var sibling:PolyPrimitive;
+
+ // Фрагменты
+ alternativa3d var backFragment:PolyPrimitive;
+ alternativa3d var frontFragment:PolyPrimitive;
+ // Рассечения
+ alternativa3d var splitTime1:Number;
+ alternativa3d var splitTime2:Number;
+
+ // BSP-нода, в которой находится примитив
+ alternativa3d var node:BSPNode;
+
+ // Значения для расчёта качества сплиттера
+ alternativa3d var splits:uint;
+ alternativa3d var disbalance:int;
+ // Качество примитива как сплиттера (меньше - лучше)
+ public var splitQuality:Number;
+
+ // Приоритет в BSP-дереве. Чем ниже мобильность, тем примитив выше в дереве.
+ public var mobility:int;
+
+ // Хранилище неиспользуемых примитивов
+ static private var collector:Array = new Array();
+
+ // Создать примитив
+ static alternativa3d function createPolyPrimitive():PolyPrimitive {
+ // Достаём примитив из коллектора
+ var primitive:PolyPrimitive = collector.pop();
+ // Если коллектор пуст, создаём новый примитив
+ if (primitive == null) {
+ primitive = new PolyPrimitive();
+ }
+ //trace(primitive.num, primitive.points.length, primitive.face, primitive.parent, primitive.sibling, primitive.fragment1, primitive.fragment2, primitive.node);
+ return primitive;
+ }
+
+ /**
+ * Кладёт примитив в коллектор для последующего реиспользования.
+ * Ссылка на грань и массивы точек зачищаются в этом методе.
+ * Ссылки на фрагменты (parent, sibling, back, front) должны быть зачищены перед запуском метода.
+ *
+ * Исключение:
+ * при сборке примитивов в сцене ссылки на back и front зачищаются после запуска метода.
+ *
+ * @param primitive примитив на реиспользование
+ */
+ static alternativa3d function destroyPolyPrimitive(primitive:PolyPrimitive):void {
+ primitive.face = null;
+ for (var i:uint = 0; i < primitive.num; i++) {
+ primitive.points.pop();
+ primitive.uvs.pop();
+ }
+ collector.push(primitive);
+ }
+
+ public function toString():String {
+ return "[Primitive " + face._mesh._name + "]";
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/Scene3D.as b/Alternativa3D5/5.3/alternativa/engine3d/core/Scene3D.as
new file mode 100644
index 0000000..58702ff
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/core/Scene3D.as
@@ -0,0 +1,1236 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.engine3d.materials.WireMaterial;
+ import alternativa.types.*;
+
+ import flash.display.Shape;
+ import flash.display.Sprite;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+ use namespace alternativatypes;
+
+ /**
+ * Сцена является контейнером 3D-объектов, с которыми ведётся работа. Все взаимодействия объектов
+ * происходят в пределах одной сцены. Класс обеспечивает работу системы сигналов и реализует алгоритм построения
+ * BSP-дерева для содержимого сцены.
+ */
+ public class Scene3D {
+ // Операции
+ /**
+ * @private
+ * Полное обновление BSP-дерева
+ */
+ alternativa3d var updateBSPOperation:Operation = new Operation("updateBSP", this);
+ /**
+ * @private
+ * Изменение примитивов
+ */
+ alternativa3d var changePrimitivesOperation:Operation = new Operation("changePrimitives", this);
+ /**
+ * @private
+ * Расчёт BSP-дерева
+ */
+ alternativa3d var calculateBSPOperation:Operation = new Operation("calculateBSP", this, calculateBSP, Operation.SCENE_CALCULATE_BSP);
+ /**
+ * @private
+ * Очистка списков изменений
+ */
+ alternativa3d var clearPrimitivesOperation:Operation = new Operation("clearPrimitives", this, clearPrimitives, Operation.SCENE_CLEAR_PRIMITIVES);
+
+ /**
+ * @private
+ * Корневой объект
+ */
+ alternativa3d var _root:Object3D;
+
+ /**
+ * @private
+ * Список операций на выполнение
+ */
+ alternativa3d var operations:Array = new Array();
+ //protected var operationSort:Array = ["priority"];
+ //protected var operationSortOptions:Array = [Array.NUMERIC];
+ /**
+ * @private
+ * Вспомогательная пустая операция, используется при удалении операций из списка
+ */
+ alternativa3d var dummyOperation:Operation = new Operation("removed", this);
+
+ /**
+ * @private
+ * Флаг анализа сплиттеров
+ */
+ alternativa3d var _splitAnalysis:Boolean = true;
+ /**
+ * @private
+ * Cбалансированность дерева
+ */
+ alternativa3d var _splitBalance:Number = 0;
+ /**
+ * @private
+ * Список изменённых примитивов
+ */
+ alternativa3d var changedPrimitives:Set = new Set();
+
+ // Вспомогательный список для сборки дочерних примитивов
+ private var childPrimitives:Set = new Set();
+ /**
+ * @private
+ * Список примитивов на добавление/удаление
+ */
+ alternativa3d var addPrimitives:Array = new Array();
+
+// alternativa3d var addSort:Array = ["mobility"];
+// alternativa3d var addSortOptions:Array = [Array.NUMERIC | Array.DESCENDING];
+// alternativa3d var addSplitQualitySort:Array = ["mobility", "splitQuality"];
+// alternativa3d var addSplitQualitySortOptions:Array = [Array.NUMERIC | Array.DESCENDING, Array.NUMERIC | Array.DESCENDING];
+ /**
+ * @private
+ * Погрешность при определении точек на плоскости
+ */
+ alternativa3d const planeOffsetThreshold:Number = 0.01;
+ /**
+ * @private
+ * BSP-дерево
+ */
+ alternativa3d var bsp:BSPNode;
+
+ /**
+ * @private
+ * Список нод на удаление
+ */
+ alternativa3d var removeNodes:Set = new Set();
+ /**
+ * @private
+ * Вспомогательная пустая нода, используется при удалении нод из дерева
+ */
+ alternativa3d var dummyNode:BSPNode = new BSPNode();
+
+ /**
+ * Создание экземпляра сцены.
+ */
+ public function Scene3D() {
+ // Обновление BSP-дерева требует его пересчёта
+ updateBSPOperation.addSequel(calculateBSPOperation);
+ // После пересчёта дерева необходимо очистить списки изменений
+ calculateBSPOperation.addSequel(clearPrimitivesOperation);
+ // Изменение примитивов в случае пересчёта дерева
+ calculateBSPOperation.addSequel(changePrimitivesOperation);
+ }
+
+ /**
+ * Расчёт сцены. Метод анализирует все изменения, произошедшие с момента предыдущего расчёта, формирует список
+ * команд и исполняет их в необходимой последовательности. В результате расчёта происходит перерисовка во всех
+ * областях вывода, к которым подключены находящиеся в сцене камеры.
+ */
+ public function calculate():void {
+ if (operations[0] != undefined) {
+ // Формируем последствия
+ var operation:Operation;
+ var length:uint = operations.length;
+ var i:uint;
+ for (i = 0; i < length; i++) {
+ operation = operations[i];
+ operation.collectSequels(operations);
+ }
+ // Сортируем операции
+ length = operations.length;
+ //operations.sortOn(operationSort, operationSortOptions);
+ sortOperations(0, length - 1);
+ // Запускаем операции
+ //trace("----------------------------------------");
+ for (i = 0; i < length; i++) {
+ operation = operations[i];
+ if (operation.method != null) {
+ //trace("EXECUTE:", operation);
+ operation.method();
+ } else {
+ /*if (operation == dummyOperation) {
+ trace("REMOVED");
+ } else {
+ trace(operation);
+ }*/
+ }
+ }
+ // Очищаем список операций
+ for (i = 0; i < length; i++) {
+ operation = operations.pop();
+ operation.queued = false;
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Сортировка операций, если массив operations пуст будет ошибка
+ *
+ * @param l начальный элемент
+ * @param r конечный элемент
+ */
+ alternativa3d function sortOperations(l:int, r:int):void {
+ var i:int = l;
+ var j:int = r;
+ var left:Operation;
+ var mid:uint = operations[(r + l) >>> 1].priority;
+ var right:Operation;
+ do {
+ while ((left = operations[i]).priority < mid) {i++};
+ while (mid < (right = operations[j]).priority) {j--};
+ if (i <= j) {
+ operations[i++] = right;
+ operations[j--] = left;
+ }
+ } while (i <= j)
+ if (l < j) {
+ sortOperations(l, j);
+ }
+ if (i < r) {
+ sortOperations(i, r);
+ }
+ }
+
+ /**
+ * @private
+ * Добавление операции в список
+ *
+ * @param operation добавляемая операция
+ */
+ alternativa3d function addOperation(operation:Operation):void {
+ if (!operation.queued) {
+ operations.push(operation);
+ operation.queued = true;
+ }
+ }
+
+ /**
+ * @private
+ * удаление операции из списка
+ *
+ * @param operation удаляемая операция
+ */
+ alternativa3d function removeOperation(operation:Operation):void {
+ if (operation.queued) {
+ operations[operations.indexOf(operation)] = dummyOperation;
+ operation.queued = false;
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт изменений в BSP-дереве.
+ * Обработка удалённых и добавленных примитивов.
+ */
+ protected function calculateBSP():void {
+ if (updateBSPOperation.queued) {
+
+ // Удаление списка нод, помеченных на удаление
+ removeNodes.clear();
+
+ // Удаление BSP-дерева, перенос примитивов в список дочерних
+ childBSP(bsp);
+ bsp = null;
+
+ // Собираем дочерние примитивы в список нижних
+ assembleChildPrimitives();
+
+ } else {
+
+ var key:*;
+ var primitive:PolyPrimitive;
+
+ // Удаляем ноды из дерева
+ if (!removeNodes.isEmpty()) {
+ var node:BSPNode;
+ while ((node = removeNodes.peek()) != null) {
+
+ // Ищем верхнюю удаляемую ноду
+ var removeNode:BSPNode = node;
+ while ((node = node.parent) != null) {
+ if (removeNodes[node]) {
+ removeNode = node;
+ }
+ }
+
+ // Удаляем ветку
+ var parent:BSPNode = removeNode.parent;
+ var replace:BSPNode = removeBSPNode(removeNode);
+
+ // Если вернулась вспомогательная нода, игнорируем её
+ if (replace == dummyNode) {
+ replace = null;
+ }
+
+ // Если есть родительская нода
+ if (parent != null) {
+ // Заменяем себя на указанную ноду
+ if (parent.front == removeNode) {
+ parent.front = replace;
+ } else {
+ parent.back = replace;
+ }
+ } else {
+ // Если нет родительской ноды, значит заменяем корень на указанную ноду
+ bsp = replace;
+ }
+
+ // Устанавливаем связь с родителем для заменённой ноды
+ if (replace != null) {
+ replace.parent = parent;
+ }
+ }
+
+ // Собираем дочерние примитивы в список на добавление
+ assembleChildPrimitives();
+ }
+ }
+
+ // Если есть примитивы на добавление
+ if (addPrimitives[0] != undefined) {
+
+ // Если включен анализ сплиттеров
+ if (_splitAnalysis) {
+ // Рассчитываем качество рассечения примитивов
+ splitQualityAnalise();
+ // Сортируем массив примитивов c учётом качества
+ //addPrimitives.sortOn(addSplitQualitySort, addSplitQualitySortOptions);
+ sortPrimitives(0, addPrimitives.length - 1);
+ } else {
+ // Сортируем массив по мобильности
+ ///addPrimitives.sortOn(addSort, addSortOptions);
+ sortPrimitivesByMobility(0, addPrimitives.length - 1);
+ }
+
+ // Если корневого нода ещё нет, создаём
+ if (bsp == null) {
+ primitive = addPrimitives.pop();
+ bsp = BSPNode.createBSPNode(primitive);
+ changedPrimitives[primitive] = true;
+ }
+
+ // Встраиваем примитивы в дерево
+ while ((primitive = addPrimitives.pop()) != null) {
+ addBSP(bsp, primitive);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Сортировка граней, если массив addPrimitives пуст будет ошибка
+ *
+ * @param l начальный элемент
+ * @param r конечный элемент
+ */
+ alternativa3d function sortPrimitives(l:int, r:int):void {
+ var i:int = l;
+ var j:int = r;
+ var left:PolyPrimitive;
+ var mid:PolyPrimitive = addPrimitives[(r + l)>>>1];
+ var midMobility:Number = mid.mobility;
+ var midSplitQuality:Number = mid.splitQuality;
+ var right:PolyPrimitive;
+ do {
+ while (((left = addPrimitives[i]).mobility > midMobility) || ((left.mobility == midMobility) && (left.splitQuality > midSplitQuality))) {i++};
+ while ((midMobility > (right = addPrimitives[j]).mobility) || ((midMobility == right.mobility) && (midSplitQuality > right.splitQuality))) {j--};
+ if (i <= j) {
+ addPrimitives[i++] = right;
+ addPrimitives[j--] = left;
+ }
+ } while (i <= j)
+ if (l < j) {
+ sortPrimitives(l, j);
+ }
+ if (i < r) {
+ sortPrimitives(i, r);
+ }
+ }
+
+ /**
+ * @private
+ * Сортировка только по мобильности
+ *
+ * @param l начальный элемент
+ * @param r конечный элемент
+ */
+ alternativa3d function sortPrimitivesByMobility(l:int, r:int):void {
+ var i:int = l;
+ var j:int = r;
+ var left:PolyPrimitive;
+ var mid:int = addPrimitives[(r + l)>>>1].mobility;
+ var right:PolyPrimitive;
+ do {
+ while ((left = addPrimitives[i]).mobility > mid) {i++};
+ while (mid > (right = addPrimitives[j]).mobility) {j--};
+ if (i <= j) {
+ addPrimitives[i++] = right;
+ addPrimitives[j--] = left;
+ }
+ } while (i <= j)
+ if (l < j) {
+ sortPrimitivesByMobility(l, j);
+ }
+ if (i < r) {
+ sortPrimitivesByMobility(i, r);
+ }
+ }
+
+
+ /**
+ * @private
+ * Анализ качества сплиттеров
+ */
+ private function splitQualityAnalise():void {
+ // Перебираем примитивы на добавление
+ var i:uint;
+ var length:uint = addPrimitives.length;
+ var maxSplits:uint = 0;
+ var maxDisbalance:uint = 0;
+ var splitter:PolyPrimitive;
+ for (i = 0; i < length; i++) {
+ splitter = addPrimitives[i];
+ splitter.splits = 0;
+ splitter.disbalance = 0;
+ var normal:Point3D = splitter.face.globalNormal;
+ var offset:Number = splitter.face.globalOffset;
+ // Проверяем соотношение с другими примитивами не меньшей мобильности на добавление
+ for (var j:uint = 0; j < length; j++) {
+ if (i != j) {
+ var primitive:PolyPrimitive = addPrimitives[j];
+ if (splitter.mobility <= primitive.mobility) {
+ // Проверяем наличие точек спереди и сзади сплиттера
+ var pointsFront:Boolean = false;
+ var pointsBack:Boolean = false;
+ for (var k:uint = 0; k < primitive.num; k++) {
+ var point:Point3D = primitive.points[k];
+ var pointOffset:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - offset;
+ if (pointOffset > planeOffsetThreshold) {
+ if (!pointsFront) {
+ splitter.disbalance++;
+ pointsFront = true;
+ }
+ if (pointsBack) {
+ splitter.splits++;
+ break;
+ }
+ } else {
+ if (pointOffset < -planeOffsetThreshold) {
+ if (!pointsBack) {
+ splitter.disbalance--;
+ pointsBack = true;
+ }
+ if (pointsFront) {
+ splitter.splits++;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ // Абсолютное значение дисбаланса
+ splitter.disbalance = (splitter.disbalance > 0) ? splitter.disbalance : -splitter.disbalance;
+ // Ищем максимальное количество рассечений и значение дисбаланса
+ maxSplits = (maxSplits > splitter.splits) ? maxSplits : splitter.splits;
+ maxDisbalance = (maxDisbalance > splitter.disbalance) ? maxDisbalance : splitter.disbalance;
+ }
+ // Расчитываем качество сплиттеров
+ for (i = 0; i < length; i++) {
+ splitter = addPrimitives[i];
+ splitter.splitQuality = (1 - _splitBalance)*splitter.splits/maxSplits + _splitBalance*splitter.disbalance/maxDisbalance;
+ }
+ }
+
+ /**
+ * @private
+ * Добавление примитива в BSP-дерево
+ *
+ * @param node текущий узел дерева, в который добавляется примитив
+ * @param primitive добавляемый примитив
+ */
+ protected function addBSP(node:BSPNode, primitive:PolyPrimitive):void {
+ var point:Point3D;
+ var normal:Point3D;
+ var key:*;
+
+ // Сравниваем мобильности ноды и примитива
+ if (primitive.mobility < node.mobility) {
+
+ // Формируем список содержимого ноды и всех примитивов ниже
+ if (node.primitive != null) {
+ childPrimitives[node.primitive] = true;
+ changedPrimitives[node.primitive] = true;
+ node.primitive.node = null;
+ } else {
+ var p:PolyPrimitive;
+ for (key in node.backPrimitives) {
+ p = key;
+ childPrimitives[p] = true;
+ changedPrimitives[p] = true;
+ p.node = null;
+ }
+ for (key in node.frontPrimitives) {
+ p = key;
+ childPrimitives[p] = true;
+ changedPrimitives[p] = true;
+ p.node = null;
+ }
+ }
+ childBSP(node.back);
+ childBSP(node.front);
+
+ // Собираем дочерние примитивы в список нижних
+ assembleChildPrimitives();
+
+ // Если включен анализ сплиттеров
+ if (_splitAnalysis) {
+ // Рассчитываем качество рассечения примитивов
+ splitQualityAnalise();
+ // Сортируем массив примитивов c учётом качества
+ sortPrimitives(0, addPrimitives.length - 1);
+ } else {
+ // Сортируем массив по мобильности
+ sortPrimitivesByMobility(0, addPrimitives.length - 1);
+ }
+
+ // Добавляем примитив в ноду
+ node.primitive = primitive;
+
+ // Пометка об изменении примитива
+ changedPrimitives[primitive] = true;
+
+ // Сохраняем ноду
+ primitive.node = node;
+
+ // Сохраняем плоскость
+ node.normal.copy(primitive.face.globalNormal);
+ node.offset = primitive.face.globalOffset;
+
+ // Сохраняем мобильность
+ node.mobility = primitive.mobility;
+
+ // Чистим списки примитивов
+ node.backPrimitives = null;
+ node.frontPrimitives = null;
+
+ // Удаляем дочерние ноды
+ node.back = null;
+ node.front = null;
+
+ } else {
+ // Получаем нормаль из ноды
+ normal = node.normal;
+
+ var points:Array = primitive.points;
+ var uvs:Array = primitive.uvs;
+
+ // Собирательные флаги
+ var pointsFront:Boolean = false;
+ var pointsBack:Boolean = false;
+
+ // Собираем расстояния точек до плоскости
+ for (var i:uint = 0; i < primitive.num; i++) {
+ point = points[i];
+ var pointOffset:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset;
+ if (pointOffset > planeOffsetThreshold) {
+ pointsFront = true;
+ if (pointsBack) {
+ break;
+ }
+ } else {
+ if (pointOffset < -planeOffsetThreshold) {
+ pointsBack = true;
+ if (pointsFront) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (!pointsFront && !pointsBack) {
+ // Сохраняем ноду
+ primitive.node = node;
+
+ // Если был только базовый примитив, переносим его в список
+ if (node.primitive != null) {
+ node.frontPrimitives = new Set(true);
+ node.frontPrimitives[node.primitive] = true;
+ node.primitive = null;
+ }
+
+ // Примитив находится в плоскости ноды
+ if (Point3D.dot(primitive.face.globalNormal, normal) > 0) {
+ node.frontPrimitives[primitive] = true;
+ } else {
+ if (node.backPrimitives == null) {
+ node.backPrimitives = new Set(true);
+ }
+ node.backPrimitives[primitive] = true;
+ }
+
+ // Пометка об изменении примитива
+ changedPrimitives[primitive] = true;
+ } else {
+ if (!pointsBack) {
+ // Примитив спереди плоскости ноды
+ if (node.front == null) {
+ // Создаём переднюю ноду
+ node.front = BSPNode.createBSPNode(primitive);
+ node.front.parent = node;
+ changedPrimitives[primitive] = true;
+ } else {
+ // Добавляем примитив в переднюю ноду
+ addBSP(node.front, primitive);
+ }
+ } else {
+ if (!pointsFront) {
+ // Примитив сзади плоскости ноды
+ if (node.back == null) {
+ // Создаём заднюю ноду
+ node.back = BSPNode.createBSPNode(primitive);
+ node.back.parent = node;
+ changedPrimitives[primitive] = true;
+ } else {
+ // Добавляем примитив в заднюю ноду
+ addBSP(node.back, primitive);
+ }
+ } else {
+ // Рассечение
+ var backFragment:PolyPrimitive = PolyPrimitive.createPolyPrimitive();
+ var frontFragment:PolyPrimitive = PolyPrimitive.createPolyPrimitive();
+
+ var firstSplit:Boolean = true;
+
+ point = points[0];
+ var offset0:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset;
+ var offset1:Number = offset0;
+ var offset2:Number;
+ for (i = 0; i < primitive.num; i++) {
+ var j:uint;
+ if (i < primitive.num - 1) {
+ j = i + 1;
+ point = points[j];
+ offset2 = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset;
+ } else {
+ j = 0;
+ offset2 = offset0;
+ }
+
+ if (offset1 > planeOffsetThreshold) {
+ // Точка спереди плоскости ноды
+ frontFragment.points.push(points[i]);
+ frontFragment.uvs.push(primitive.uvs[i]);
+ } else {
+ if (offset1 < -planeOffsetThreshold) {
+ // Точка сзади плоскости ноды
+ backFragment.points.push(points[i]);
+ backFragment.uvs.push(primitive.uvs[i]);
+ } else {
+ // Рассечение по точке примитива
+ backFragment.points.push(points[i]);
+ backFragment.uvs.push(primitive.uvs[i]);
+ frontFragment.points.push(points[i]);
+ frontFragment.uvs.push(primitive.uvs[i]);
+ }
+ }
+
+ // Рассечение ребра
+ if (offset1 > planeOffsetThreshold && offset2 < -planeOffsetThreshold || offset1 < -planeOffsetThreshold && offset2 > planeOffsetThreshold) {
+ // Находим точку рассечения
+ var t:Number = offset1/(offset1 - offset2);
+ point = Point3D.interpolate(points[i], points[j], t);
+ backFragment.points.push(point);
+ frontFragment.points.push(point);
+ // Находим UV в точке рассечения
+ var uv:Point;
+ if (primitive.face.uvMatrixBase != null) {
+ uv = Point.interpolate(uvs[j], uvs[i], t);
+ } else {
+ uv = null;
+ }
+ backFragment.uvs.push(uv);
+ frontFragment.uvs.push(uv);
+ // Отмечаем рассечённое ребро
+ if (firstSplit) {
+ primitive.splitTime1 = t;
+ firstSplit = false;
+ } else {
+ primitive.splitTime2 = t;
+ }
+ }
+
+ offset1 = offset2;
+ }
+ backFragment.num = backFragment.points.length;
+ frontFragment.num = frontFragment.points.length;
+
+ // Назначаем мобильность
+ backFragment.mobility = primitive.mobility;
+ frontFragment.mobility = primitive.mobility;
+
+ // Устанавливаем связи рассечённых примитивов
+ backFragment.face = primitive.face;
+ frontFragment.face = primitive.face;
+ backFragment.parent = primitive;
+ frontFragment.parent = primitive;
+ backFragment.sibling = frontFragment;
+ frontFragment.sibling = backFragment;
+ primitive.backFragment = backFragment;
+ primitive.frontFragment = frontFragment;
+
+ // Добавляем фрагменты в дочерние ноды
+ if (node.back == null) {
+ node.back = BSPNode.createBSPNode(backFragment);
+ node.back.parent = node;
+ changedPrimitives[backFragment] = true;
+ } else {
+ addBSP(node.back, backFragment);
+ }
+ if (node.front == null) {
+ node.front = BSPNode.createBSPNode(frontFragment);
+ node.front.parent = node;
+ changedPrimitives[frontFragment] = true;
+ } else {
+ addBSP(node.front, frontFragment);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Удаление узла BSP-дерева, включая все дочерние узлы, помеченные для удаления.
+ *
+ * @param node удаляемый узел
+ * @return корневой узел поддерева, оставшегося после операции удаления
+ */
+ protected function removeBSPNode(node:BSPNode):BSPNode {
+ var replaceNode:BSPNode;
+ if (node != null) {
+ // Удаляем дочерние
+ node.back = removeBSPNode(node.back);
+ node.front = removeBSPNode(node.front);
+
+ if (!removeNodes[node]) {
+ // Если нода не удаляется, возвращает себя
+ replaceNode = node;
+
+ // Проверяем дочерние ноды
+ if (node.back != null) {
+ if (node.back != dummyNode) {
+ node.back.parent = node;
+ } else {
+ node.back = null;
+ }
+ }
+ if (node.front != null) {
+ if (node.front != dummyNode) {
+ node.front.parent = node;
+ } else {
+ node.front = null;
+ }
+ }
+ } else {
+ // Проверяем дочерние ветки
+ if (node.back == null) {
+ if (node.front != null) {
+ // Есть только передняя ветка
+ replaceNode = node.front;
+ node.front = null;
+ }
+ } else {
+ if (node.front == null) {
+ // Есть только задняя ветка
+ replaceNode = node.back;
+ node.back = null;
+ } else {
+ // Есть обе ветки - собираем дочерние примитивы
+ childBSP(node.back);
+ childBSP(node.front);
+ // Используем вспомогательную ноду
+ replaceNode = dummyNode;
+ // Удаляем связи с дочерними нодами
+ node.back = null;
+ node.front = null;
+ }
+ }
+
+ // Удаляем ноду из списка на удаление
+ delete removeNodes[node];
+ // Удаляем ноду
+ node.parent = null;
+ BSPNode.destroyBSPNode(node);
+ }
+ }
+ return replaceNode;
+ }
+
+ /**
+ * @private
+ * Удаление примитива из узла дерева
+ *
+ * @param primitive удаляемый примитив
+ */
+ alternativa3d function removeBSPPrimitive(primitive:PolyPrimitive):void {
+ var node:BSPNode = primitive.node;
+ primitive.node = null;
+
+ var single:Boolean = false;
+ var key:*;
+
+ // Пометка об изменении примитива
+ changedPrimitives[primitive] = true;
+
+ // Если нода единичная
+ if (node.primitive == primitive) {
+ removeNodes[node] = true;
+ node.primitive = null;
+ } else {
+ // Есть передние примитивы
+ if (node.frontPrimitives[primitive]) {
+ // Удаляем примитив спереди
+ delete node.frontPrimitives[primitive];
+
+ // Проверяем количество примитивов спереди
+ for (key in node.frontPrimitives) {
+ if (single) {
+ single = false;
+ break;
+ }
+ single = true;
+ }
+
+ if (key == null) {
+ // Передняя пуста, значит сзади кто-то есть
+
+ // Переворачиваем дочерние ноды
+ var t:BSPNode = node.back;
+ node.back = node.front;
+ node.front = t;
+
+ // Переворачиваем плоскость ноды
+ node.normal.invert();
+ node.offset = -node.offset;
+
+ // Проверяем количество примитивов сзади
+ for (key in node.backPrimitives) {
+ if (single) {
+ single = false;
+ break;
+ }
+ single = true;
+ }
+
+ // Если сзади один примитив
+ if (single) {
+ // Устанавливаем базовый примитив ноды
+ node.primitive = key;
+ // Устанавливаем мобильность
+ node.mobility = node.primitive.mobility;
+ // Стираем список передних примитивов
+ node.frontPrimitives = null;
+ } else {
+ // Если сзади несколько примитивов, переносим их в передние
+ node.frontPrimitives = node.backPrimitives;
+ // Пересчитываем мобильность ноды по передним примитивам
+ if (primitive.mobility == node.mobility) {
+ node.mobility = int.MAX_VALUE;
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ }
+ }
+
+ // Стираем список задних примитивов
+ node.backPrimitives = null;
+
+ } else {
+ // Если остался один примитив и сзади примитивов нет
+ if (single && node.backPrimitives == null) {
+ // Устанавливаем базовый примитив ноды
+ node.primitive = key;
+ // Устанавливаем мобильность
+ node.mobility = node.primitive.mobility;
+ // Стираем список передних примитивов
+ node.frontPrimitives = null;
+ } else {
+ // Пересчитываем мобильность ноды
+ if (primitive.mobility == node.mobility) {
+ node.mobility = int.MAX_VALUE;
+ for (key in node.backPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ }
+ }
+ }
+ } else {
+ // Удаляем примитив сзади
+ delete node.backPrimitives[primitive];
+
+ // Проверяем количество примитивов сзади
+ for (key in node.backPrimitives) {
+ break;
+ }
+
+ // Если сзади примитивов больше нет
+ if (key == null) {
+ // Проверяем количество примитивов спереди
+ for (key in node.frontPrimitives) {
+ if (single) {
+ single = false;
+ break;
+ }
+ single = true;
+ }
+
+ // Если спереди один примитив
+ if (single) {
+ // Устанавливаем базовый примитив ноды
+ node.primitive = key;
+ // Устанавливаем мобильность
+ node.mobility = node.primitive.mobility;
+ // Стираем список передних примитивов
+ node.frontPrimitives = null;
+ } else {
+ // Пересчитываем мобильность ноды по передним примитивам
+ if (primitive.mobility == node.mobility) {
+ node.mobility = int.MAX_VALUE;
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ }
+ }
+
+ // Стираем список задних примитивов
+ node.backPrimitives = null;
+ } else {
+ // Пересчитываем мобильность ноды
+ if (primitive.mobility == node.mobility) {
+ node.mobility = int.MAX_VALUE;
+ for (key in node.backPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Удаление и перевставка ветки
+ *
+ * @param node
+ */
+ protected function childBSP(node:BSPNode):void {
+ if (node != null && node != dummyNode) {
+ var primitive:PolyPrimitive = node.primitive;
+ if (primitive != null) {
+ childPrimitives[primitive] = true;
+ changedPrimitives[primitive] = true;
+ node.primitive = null;
+ primitive.node = null;
+ } else {
+ for (var key:* in node.backPrimitives) {
+ primitive = key;
+ childPrimitives[primitive] = true;
+ changedPrimitives[primitive] = true;
+ primitive.node = null;
+ }
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ childPrimitives[primitive] = true;
+ changedPrimitives[primitive] = true;
+ primitive.node = null;
+ }
+ node.backPrimitives = null;
+ node.frontPrimitives = null;
+ }
+ childBSP(node.back);
+ childBSP(node.front);
+ // Удаляем ноду
+ node.parent = null;
+ node.back = null;
+ node.front = null;
+ BSPNode.destroyBSPNode(node);
+ }
+ }
+
+ /**
+ * @private
+ * Сборка списка дочерних примитивов в коллектор
+ */
+ protected function assembleChildPrimitives():void {
+ var primitive:PolyPrimitive;
+ while ((primitive = childPrimitives.take()) != null) {
+ assemblePrimitive(primitive);
+ }
+ }
+
+ /**
+ * @private
+ * Сборка примитивов и разделение на добавленные и удалённые
+ *
+ * @param primitive
+ */
+ private function assemblePrimitive(primitive:PolyPrimitive):void {
+ // Если есть соседний примитив и он может быть собран
+ if (primitive.sibling != null && canAssemble(primitive.sibling)) {
+ // Собираем их в родительский
+ assemblePrimitive(primitive.parent);
+ // Зачищаем связи между примитивами
+ primitive.sibling.sibling = null;
+ primitive.sibling.parent = null;
+ PolyPrimitive.destroyPolyPrimitive(primitive.sibling);
+ primitive.sibling = null;
+ primitive.parent.backFragment = null;
+ primitive.parent.frontFragment = null;
+ primitive.parent = null;
+ PolyPrimitive.destroyPolyPrimitive(primitive);
+ } else {
+ // Если собраться не получилось или родительский
+ addPrimitives.push(primitive);
+ }
+ }
+
+ /**
+ * @private
+ * Проверка, может ли примитив в списке дочерних быть собран
+ *
+ * @param primitive
+ * @return
+ */
+ private function canAssemble(primitive:PolyPrimitive):Boolean {
+ if (childPrimitives[primitive]) {
+ delete childPrimitives[primitive];
+ return true;
+ } else {
+ var backFragment:PolyPrimitive = primitive.backFragment;
+ var frontFragment:PolyPrimitive = primitive.frontFragment;
+ if (backFragment != null) {
+ var assembleBack:Boolean = canAssemble(backFragment);
+ var assembleFront:Boolean = canAssemble(frontFragment);
+ if (assembleBack && assembleFront) {
+ backFragment.parent = null;
+ frontFragment.parent = null;
+ backFragment.sibling = null;
+ frontFragment.sibling = null;
+ primitive.backFragment = null;
+ primitive.frontFragment = null;
+ PolyPrimitive.destroyPolyPrimitive(backFragment);
+ PolyPrimitive.destroyPolyPrimitive(frontFragment);
+ return true;
+ } else {
+ if (assembleBack) {
+ addPrimitives.push(backFragment);
+ }
+ if (assembleFront) {
+ addPrimitives.push(frontFragment);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @private
+ * Очистка списков
+ */
+ private function clearPrimitives():void {
+ changedPrimitives.clear();
+ }
+
+ /**
+ * Корневой объект сцены.
+ */
+ public function get root():Object3D {
+ return _root;
+ }
+
+ /**
+ * @private
+ */
+ public function set root(value:Object3D):void {
+ // Если ещё не является корневым объектом
+ if (_root != value) {
+ // Если устанавливаем не пустой объект
+ if (value != null) {
+ // Если объект был в другом объекте
+ if (value._parent != null) {
+ // Удалить его оттуда
+ value._parent._children.remove(value);
+ } else {
+ // Если объект был корневым в сцене
+ if (value._scene != null && value._scene._root == value) {
+ value._scene.root = null;
+ }
+ }
+ // Удаляем ссылку на родителя
+ value.setParent(null);
+ // Указываем сцену
+ value.setScene(this);
+ // Устанавливаем уровни
+ value.setLevel(0);
+ }
+
+ // Если был корневой объект
+ if (_root != null) {
+ // Удаляем ссылку на родителя
+ _root.setParent(null);
+ // Удаляем ссылку на камеру
+ _root.setScene(null);
+ }
+
+ // Сохраняем корневой объект
+ _root = value;
+ }
+ }
+
+ /**
+ * Флаг активности анализа сплиттеров.
+ * В режиме анализа для каждого добавляемого в BSP-дерево полигона выполняется его оценка в качестве разделяющей
+ * плоскости (сплиттера). Наиболее качественные сплиттеры добавляются в BSP-дерево первыми.
+ *
+ * splitBalance можно влиять на конечный вид BSP-дерева.
+ *
+ * @see #splitBalance
+ * @default true
+ */
+ public function get splitAnalysis():Boolean {
+ return _splitAnalysis;
+ }
+
+ /**
+ * @private
+ */
+ public function set splitAnalysis(value:Boolean):void {
+ if (_splitAnalysis != value) {
+ _splitAnalysis = value;
+ addOperation(updateBSPOperation);
+ }
+ }
+
+ /**
+ * Параметр балансировки BSP-дерева при влюченном режиме анализа сплиттеров.
+ * Может принимать значения от 0 (минимизация фрагментирования полигонов) до 1 (максимальный баланс BSP-дерева).
+ *
+ * @see #splitAnalysis
+ * @default 0
+ */
+ public function get splitBalance():Number {
+ return _splitBalance;
+ }
+
+ /**
+ * @private
+ */
+ public function set splitBalance(value:Number):void {
+ value = (value < 0) ? 0 : ((value > 1) ? 1 : value);
+ if (_splitBalance != value) {
+ _splitBalance = value;
+ if (_splitAnalysis) {
+ addOperation(updateBSPOperation);
+ }
+ }
+ }
+
+ /**
+ * Визуализация BSP-дерева. Дерево рисуется в заданном контейнере. Каждый узел дерева обозначается точкой, имеющей
+ * цвет материала (в случае текстурного материала показывается цвет первой точки текстуры) первого полигона из этого
+ * узла. Задние узлы рисуются слева-снизу от родителя, передние справа-снизу.
+ *
+ * @param container контейнер для отрисовки дерева
+ */
+ public function drawBSP(container:Sprite):void {
+
+ container.graphics.clear();
+ while (container.numChildren > 0) {
+ container.removeChildAt(0);
+ }
+ if (bsp != null) {
+ drawBSPNode(bsp, container, 0, 0, 1);
+ }
+ }
+
+ /**
+ * @private
+ * Отрисовка узла BSP-дерева при визуализации
+ *
+ * @param node
+ * @param container
+ * @param x
+ * @param y
+ * @param size
+ */
+ private function drawBSPNode(node:BSPNode, container:Sprite, x:Number, y:Number, size:Number):void {
+ var s:Shape = new Shape();
+ container.addChild(s);
+ s.x = x;
+ s.y = y;
+ var color:uint = 0xFF0000;
+ var primitive:PolyPrimitive;
+ if (node.primitive != null) {
+ primitive = node.primitive;
+ } else {
+ if (node.frontPrimitives != null) {
+ primitive = node.frontPrimitives.peek();
+ }
+ }
+ if (primitive != null) {
+ if (primitive.face._surface != null && primitive.face._surface._material != null) {
+ if (primitive.face._surface._material is FillMaterial) {
+ color = FillMaterial(primitive.face._surface._material)._color;
+ }
+ if (primitive.face._surface._material is WireMaterial) {
+ color = WireMaterial(primitive.face._surface._material)._color;
+ }
+ if (primitive.face._surface._material is TextureMaterial) {
+ color = TextureMaterial(primitive.face._surface._material).texture._bitmapData.getPixel(0, 0);
+ }
+ }
+ }
+
+ if (node == dummyNode) {
+ color = 0xFF00FF;
+ }
+
+ s.graphics.beginFill(color);
+ s.graphics.drawCircle(0, 0, 3);
+ s.graphics.endFill();
+
+ var xOffset:Number = 100;
+ var yOffset:Number = 20;
+ if (node.back != null) {
+ container.graphics.lineStyle(0, 0x660000);
+ container.graphics.moveTo(x, y);
+ container.graphics.lineTo(x - xOffset*size, y + yOffset);
+ drawBSPNode(node.back, container, x - xOffset*size, y + yOffset, size*0.8);
+ }
+ if (node.front != null) {
+ container.graphics.lineStyle(0, 0x006600);
+ container.graphics.moveTo(x, y);
+ container.graphics.lineTo(x + xOffset*size, y + yOffset);
+ drawBSPNode(node.front, container, x + xOffset*size, y + yOffset, size*0.8);
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/Surface.as b/Alternativa3D5/5.3/alternativa/engine3d/core/Surface.as
new file mode 100644
index 0000000..6e69daa
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/core/Surface.as
@@ -0,0 +1,350 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.errors.FaceExistsError;
+ import alternativa.engine3d.errors.FaceNotFoundError;
+ import alternativa.engine3d.errors.InvalidIDError;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.types.Set;
+
+ use namespace alternativa3d;
+
+ /**
+ * Поверхность — набор граней, объединённых в группу. Поверхности используются для установки материалов,
+ * визуализирующих грани объекта.
+ */
+ public class Surface {
+ // Операции
+ /**
+ * @private
+ * Изменение набора граней
+ */
+ alternativa3d var changeFacesOperation:Operation = new Operation("changeFaces", this);
+ /**
+ * @private
+ * Изменение материала
+ */
+ alternativa3d var changeMaterialOperation:Operation = new Operation("changeMaterial", this);
+
+ /**
+ * @private
+ * Меш
+ */
+ alternativa3d var _mesh:Mesh;
+ /**
+ * @private
+ * Материал
+ */
+ alternativa3d var _material:SurfaceMaterial;
+ /**
+ * @private
+ * Грани
+ */
+ alternativa3d var _faces:Set = new Set();
+
+ /**
+ * Создание экземпляра поверхности.
+ */
+ public function Surface() {}
+
+ /**
+ * Добавление грани в поверхность.
+ *
+ * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани полигонального объекта
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError грань не найдена в полигональном объекте содержащем поверхность
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ * @throws alternativa.engine3d.errors.FaceExistsError поверхность уже содержит указанную грань
+ *
+ * @see Face
+ */
+ public function addFace(face:Object):void {
+ var byLink:Boolean = face is Face;
+
+ // Проверяем на нахождение поверхности в меше
+ if (_mesh == null) {
+ throw new FaceNotFoundError(face, this);
+ }
+
+ // Проверяем на null
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие грани в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Face(face)._mesh != _mesh) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_mesh._faces[face] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(face, this);
+ } else {
+ if (!(_mesh._faces[face] is Face)) {
+ throw new InvalidIDError(face, this);
+ }
+ }
+ }
+
+ // Находим грань
+ var f:Face = byLink ? Face(face) : _mesh._faces[face];
+
+ // Проверяем наличие грани в поверхности
+ if (_faces.has(f)) {
+ // Если грань уже в поверхности
+ throw new FaceExistsError(f, this);
+ }
+
+ // Проверяем грань на нахождение в другой поверхности
+ if (f._surface != null) {
+ // Удаляем её из той поверхности
+ f._surface._faces.remove(f);
+ f.removeFromSurface(f._surface);
+ }
+
+ // Добавляем грань в поверхность
+ _faces.add(f);
+ f.addToSurface(this);
+
+ // Отправляем операцию изменения набора граней
+ _mesh.addOperationToScene(changeFacesOperation);
+ }
+
+ /**
+ * Удаление грани из поверхности.
+ *
+ * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани полигонального объекта
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError поверхность не содержит указанную грань
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Face
+ */
+ public function removeFace(face:Object):void {
+ var byLink:Boolean = face is Face;
+
+ // Проверяем на нахождение поверхности в меше
+ if (_mesh == null) {
+ throw new FaceNotFoundError(face, this);
+ }
+
+ // Проверяем на null
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие грани в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Face(face)._mesh != _mesh) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_mesh._faces[face] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(face, this);
+ } else {
+ if (!(_mesh._faces[face] is Face)) {
+ throw new InvalidIDError(face, this);
+ }
+ }
+
+ }
+
+ // Находим грань
+ var f:Face = byLink ? Face(face) : _mesh._faces[face];
+
+ // Проверяем наличие грани в поверхности
+ if (!_faces.has(f)) {
+ // Если грань не в поверхности
+ throw new FaceNotFoundError(f, this);
+ }
+
+ // Удаляем грань из поверхности
+ _faces.remove(f);
+ f.removeFromSurface(this);
+
+ // Отправляем операцию изменения набора граней
+ _mesh.addOperationToScene(changeFacesOperation);
+ }
+
+ /**
+ * Материал поверхности. При установке нового значения, устанавливаемый материал будет удалён из старой поверхности.
+ */
+ public function get material():SurfaceMaterial {
+ return _material;
+ }
+
+ /**
+ * @private
+ */
+ public function set material(value:SurfaceMaterial):void {
+ if (_material != value) {
+ // Если был материал
+ if (_material != null) {
+ // Удалить материал из поверхности
+ _material.removeFromSurface(this);
+ // Удалить материал из меша
+ if (_mesh != null) {
+ _material.removeFromMesh(_mesh);
+ // Удалить материал из сцены
+ if (_mesh._scene != null) {
+ _material.removeFromScene(_mesh._scene);
+ }
+ }
+ }
+ // Если новый материал
+ if (value != null) {
+ // Если материал был в другой поверхности
+ if (value._surface != null) {
+ // Удалить его оттуда
+ value._surface.material = null;
+ }
+ // Добавить материал в поверхность
+ value.addToSurface(this);
+ // Добавить материал в меш
+ if (_mesh != null) {
+ value.addToMesh(_mesh);
+ // Добавить материал в сцену
+ if (_mesh._scene != null) {
+ value.addToScene(_mesh._scene);
+ }
+ }
+ }
+ // Сохраняем материал
+ _material = value;
+ // Отправляем операцию изменения материала
+ addMaterialChangedOperationToScene();
+ }
+ }
+
+ /**
+ * Набор граней поверхности.
+ */
+ public function get faces():Set {
+ return _faces.clone();
+ }
+
+ /**
+ * Полигональный объект, которому принадлежит поверхность.
+ */
+ public function get mesh():Mesh {
+ return _mesh;
+ }
+
+ /**
+ * Идентификатор поверхности в полигональном объекте. Если поверхность не принадлежит ни одному объекту,
+ * значение идентификатора равно null.
+ */
+ public function get id():Object {
+ return (_mesh != null) ? _mesh.getSurfaceId(this) : null;
+ }
+
+ /**
+ * @private
+ * Добавление в сцену.
+ *
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {
+ // Добавляем на сцену материал
+ if (_material != null) {
+ _material.addToScene(scene);
+ }
+ }
+
+ /**
+ * @private
+ * Удаление из сцены.
+ *
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(changeFacesOperation);
+ scene.removeOperation(changeMaterialOperation);
+ // Удаляем из сцены материал
+ if (_material != null) {
+ _material.removeFromScene(scene);
+ }
+ }
+
+ /**
+ * @private
+ * Добавление к мешу
+ * @param mesh
+ */
+ alternativa3d function addToMesh(mesh:Mesh):void {
+ // Подписка на операции меша
+
+ // Добавляем в меш материал
+ if (_material != null) {
+ _material.addToMesh(mesh);
+ }
+ // Сохранить меш
+ _mesh = mesh;
+ }
+
+ /**
+ * @private
+ * Удаление из меша
+ *
+ * @param mesh
+ */
+ alternativa3d function removeFromMesh(mesh:Mesh):void {
+ // Отписка от операций меша
+
+ // Удаляем из меша материал
+ if (_material != null) {
+ _material.removeFromMesh(mesh);
+ }
+ // Удалить ссылку на меш
+ _mesh = null;
+ }
+
+ /**
+ * @private
+ * Удаление граней
+ */
+ alternativa3d function removeFaces():void {
+ for (var key:* in _faces) {
+ var face:Face = key;
+ _faces.remove(face);
+ face.removeFromSurface(this);
+ }
+ }
+
+ /**
+ * @private
+ * Изменение материала
+ */
+ alternativa3d function addMaterialChangedOperationToScene():void {
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeMaterialOperation);
+ }
+ }
+
+ /**
+ * Строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ var length:uint = _faces.length;
+ var res:String = "[Surface ID:" + id + ((length > 0) ? " faces:" : "");
+ var i:uint = 0;
+ for (var key:* in _faces) {
+ var face:Face = key;
+ res += face.id + ((i < length - 1) ? ", " : "");
+ i++;
+ }
+ res += "]";
+ return res;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/core/Vertex.as b/Alternativa3D5/5.3/alternativa/engine3d/core/Vertex.as
new file mode 100644
index 0000000..cceb91e
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/core/Vertex.as
@@ -0,0 +1,250 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ use namespace alternativa3d;
+
+ /**
+ * Вершина полигона в трёхмерном пространстве. Вершина хранит свои координаты, а также ссылки на
+ * полигональный объект и грани этого объекта, которым она принадлежит.
+ */
+ final public class Vertex {
+ // Операции
+ /**
+ * @private
+ * Изменение локальных координат
+ */
+ alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this);
+ /**
+ * @private
+ * Расчёт глобальных координат
+ */
+ alternativa3d var calculateCoordsOperation:Operation = new Operation("calculateCoords", this, calculateCoords, Operation.VERTEX_CALCULATE_COORDS);
+
+ /**
+ * @private
+ * Меш
+ */
+ alternativa3d var _mesh:Mesh;
+ /**
+ * @private
+ * Координаты точки
+ */
+ alternativa3d var _coords:Point3D;
+ /**
+ * @private
+ * Грани
+ */
+ alternativa3d var _faces:Set = new Set();
+ /**
+ * @private
+ * Координаты в сцене
+ */
+ alternativa3d var globalCoords:Point3D = new Point3D();
+
+ /**
+ * Создание экземпляра вершины.
+ *
+ * @param x координата вершины по оси X
+ * @param y координата вершины по оси Y
+ * @param z координата вершины по оси Z
+ */
+ public function Vertex(x:Number = 0, y:Number = 0, z:Number = 0) {
+ _coords = new Point3D(x, y, z);
+
+ // Изменение координат инициирует пересчёт глобальных координат
+ changeCoordsOperation.addSequel(calculateCoordsOperation);
+ }
+
+ /**
+ * Вызывается из операции calculateCoordsOperation для расчета глобальных координат вершины
+ */
+ private function calculateCoords():void {
+ globalCoords.copy(_coords);
+ globalCoords.transform(_mesh.transformation);
+ }
+
+ /**
+ * @private
+ */
+ public function set x(value:Number):void {
+ if (_coords.x != value) {
+ _coords.x = value;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeCoordsOperation);
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set y(value:Number):void {
+ if (_coords.y != value) {
+ _coords.y = value;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeCoordsOperation);
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set z(value:Number):void {
+ if (_coords.z != value) {
+ _coords.z = value;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeCoordsOperation);
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set coords(value:Point3D):void {
+ if (!_coords.equals(value)) {
+ _coords.copy(value);
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeCoordsOperation);
+ }
+ }
+ }
+
+ /**
+ * координата вершины по оси X.
+ */
+ public function get x():Number {
+ return _coords.x;
+ }
+
+ /**
+ * координата вершины по оси Y.
+ */
+ public function get y():Number {
+ return _coords.y;
+ }
+
+ /**
+ * координата вершины по оси Z.
+ */
+ public function get z():Number {
+ return _coords.z;
+ }
+
+ /**
+ * Координаты вершины.
+ */
+ public function get coords():Point3D {
+ return _coords.clone();
+ }
+
+ /**
+ * Полигональный объект, которому принадлежит вершина.
+ */
+ public function get mesh():Mesh {
+ return _mesh;
+ }
+
+ /**
+ * Множество граней, которым принадлежит вершина. Каждый элемент множества является объектом класса
+ * altertnativa.engine3d.core.Face.
+ *
+ * @see Face
+ */
+ public function get faces():Set {
+ return _faces.clone();
+ }
+
+ /**
+ * Идентификатор вершины в полигональном объекте. Если вершина не принадлежит полигональному объекту, возвращается null.
+ */
+ public function get id():Object {
+ return (_mesh != null) ? _mesh.getVertexId(this) : null;
+ }
+
+ /**
+ * @private
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {
+ // При добавлении на сцену расчитать глобальные координаты
+ scene.addOperation(calculateCoordsOperation);
+ }
+
+ /**
+ * @private
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(calculateCoordsOperation);
+ scene.removeOperation(changeCoordsOperation);
+ }
+
+ /**
+ * @private
+ * @param mesh
+ */
+ alternativa3d function addToMesh(mesh:Mesh):void {
+ // Подписка на операции меша
+ mesh.changeCoordsOperation.addSequel(calculateCoordsOperation);
+ mesh.changeRotationOrScaleOperation.addSequel(calculateCoordsOperation);
+ // Сохранить меш
+ _mesh = mesh;
+ }
+
+ /**
+ * @private
+ * @param mesh
+ */
+ alternativa3d function removeFromMesh(mesh:Mesh):void {
+ // Отписка от операций меша
+ mesh.changeCoordsOperation.removeSequel(calculateCoordsOperation);
+ mesh.changeRotationOrScaleOperation.removeSequel(calculateCoordsOperation);
+ // Удалить зависимые грани
+ for (var key:* in _faces) {
+ var face:Face = key;
+ mesh.removeFace(face);
+ }
+ // Удалить ссылку на меш
+ _mesh = null;
+ }
+
+ /**
+ * @private
+ * @param face
+ */
+ alternativa3d function addToFace(face:Face):void {
+ // Подписка грани на операции
+ changeCoordsOperation.addSequel(face.calculateUVOperation);
+ changeCoordsOperation.addSequel(face.calculateNormalOperation);
+ // Добавить грань в список
+ _faces.add(face);
+ }
+
+ /**
+ * @private
+ * @param face
+ */
+ alternativa3d function removeFromFace(face:Face):void {
+ // Отписка грани от операций
+ changeCoordsOperation.removeSequel(face.calculateUVOperation);
+ changeCoordsOperation.removeSequel(face.calculateNormalOperation);
+ // Удалить грань из списка
+ _faces.remove(face);
+ }
+
+ /**
+ * Строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ return "[Vertex ID:" + id + " " + _coords.x.toFixed(2) + ", " + _coords.y.toFixed(2) + ", " + _coords.z.toFixed(2) + "]";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/display/Skin.as b/Alternativa3D5/5.3/alternativa/engine3d/display/Skin.as
new file mode 100644
index 0000000..928f8ae
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/display/Skin.as
@@ -0,0 +1,68 @@
+package alternativa.engine3d.display {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Operation;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+
+ import flash.display.Graphics;
+ import flash.display.Sprite;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ */
+ public class Skin extends Sprite {
+
+ /**
+ * @private
+ * Графика скина (для быстрого доступа)
+ */
+ alternativa3d var gfx:Graphics = graphics;
+
+ /**
+ * @private
+ * Ссылка на следующий скин
+ */
+ alternativa3d var nextSkin:Skin;
+
+ /**
+ * @private
+ * Примитив
+ */
+ alternativa3d var primitive:PolyPrimitive;
+
+ /**
+ * @private
+ * Материал
+ */
+ alternativa3d var material:SurfaceMaterial;
+
+ // Хранилище неиспользуемых скинов
+ static private var collector:Array = new Array();
+
+ /**
+ * @private
+ * Создание скина.
+ */
+ static alternativa3d function createSkin():Skin {
+ // Достаём скин из коллектора
+ var skin:Skin = collector.pop();
+ // Если коллектор пуст, создаём новый скин
+ if (skin == null) {
+ skin = new Skin();
+ }
+ return skin;
+ }
+
+ /**
+ * @private
+ * Удаление скина, все ссылки должны быть почищены.
+ */
+ static alternativa3d function destroySkin(skin:Skin):void {
+ collector.push(skin);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/display/View.as b/Alternativa3D5/5.3/alternativa/engine3d/display/View.as
new file mode 100644
index 0000000..b13659a
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/display/View.as
@@ -0,0 +1,149 @@
+package alternativa.engine3d.display {
+ import alternativa.engine3d.*;
+
+ import flash.display.Sprite;
+ import flash.geom.Rectangle;
+ import alternativa.engine3d.core.Camera3D;
+
+ use namespace alternativa3d;
+
+ /**
+ * Область для вывода изображения с камеры.
+ */
+ public class View extends Sprite {
+
+ /**
+ * @private
+ * Область отрисовки спрайтов
+ */
+ alternativa3d var canvas:Sprite;
+
+ private var _camera:Camera3D;
+
+ /**
+ * @private
+ * Ширина области вывода
+ */
+ alternativa3d var _width:Number;
+ /**
+ * @private
+ * Высота области вывода
+ */
+ alternativa3d var _height:Number;
+
+ /**
+ * Создание экземпляра области вывода.
+ *
+ * @param camera камера, изображение с которой должно выводиться
+ * @param width ширина области вывода
+ * @param height высота области вывода
+ */
+ public function View(camera:Camera3D = null, width:Number = 0, height:Number = 0) {
+ canvas = new Sprite();
+ canvas.mouseEnabled = false;
+ canvas.mouseChildren = false;
+ canvas.tabEnabled = false;
+ canvas.tabChildren = false;
+ addChild(canvas);
+
+ this.camera = camera;
+ this.width = width;
+ this.height = height;
+ }
+
+ /**
+ * Камера с которой ведётся отображение.
+ */
+ public function get camera():Camera3D {
+ return _camera;
+ }
+
+ /**
+ * @private
+ */
+ public function set camera(value:Camera3D):void {
+ if (_camera != value) {
+ // Если была камера
+ if (_camera != null) {
+ // Удалить камеру
+ _camera.removeFromView(this);
+ }
+ // Если новая камера
+ if (value != null) {
+ // Если камера была в другом вьюпорте
+ if (value._view != null) {
+ // Удалить её оттуда
+ value._view.camera = null;
+ }
+ // Добавить камеру
+ value.addToView(this);
+ } else {
+ // Зачистка скинов
+ if (canvas.numChildren > 0) {
+ var skin:Skin = Skin(canvas.getChildAt(0));
+ while (skin != null) {
+ // Сохраняем следующий
+ var next:Skin = skin.nextSkin;
+ // Удаляем из канваса
+ canvas.removeChild(skin);
+ // Очистка скина
+ if (skin.material != null) {
+ skin.material.clear(skin);
+ }
+ // Зачищаем ссылки
+ skin.nextSkin = null;
+ skin.primitive = null;
+ skin.material = null;
+ // Удаляем
+ Skin.destroySkin(skin);
+ // Следующий устанавливаем текущим
+ skin = next;
+ }
+ }
+ }
+ // Сохраняем камеру
+ _camera = value;
+ }
+ }
+
+ /**
+ * Ширина области вывода в пикселях.
+ */
+ override public function get width():Number {
+ return _width;
+ }
+
+ /**
+ * @private
+ */
+ override public function set width(value:Number):void {
+ if (_width != value) {
+ _width = value;
+ canvas.x = _width*0.5;
+ if (_camera != null) {
+ camera.addOperationToScene(camera.calculatePlanesOperation);
+ }
+ }
+ }
+
+ /**
+ * Высота области вывода в пикселях.
+ */
+ override public function get height():Number {
+ return _height;
+ }
+
+ /**
+ * @private
+ */
+ override public function set height(value:Number):void {
+ if (_height != value) {
+ _height = value;
+ canvas.y = _height*0.5;
+ if (_camera != null) {
+ camera.addOperationToScene(camera.calculatePlanesOperation);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/Engine3DError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/Engine3DError.as
new file mode 100644
index 0000000..bcf6397
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/Engine3DError.as
@@ -0,0 +1,27 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Базовый класс для ошибок 3d-engine.
+ */
+ public class Engine3DError extends Error {
+
+ /**
+ * Источник ошибки - объект в котором произошла ошибка.
+ */
+ public var source:Object;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param message описание ошибки
+ * @param source источник ошибки
+ */
+ public function Engine3DError(message:String = "", source:Object = null) {
+ super(message);
+ this.source = source;
+ this.name = "Engine3DError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceExistsError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceExistsError.as
new file mode 100644
index 0000000..ecc7f59
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceExistsError.as
@@ -0,0 +1,35 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+ import alternativa.engine3d.core.Surface;
+
+ /**
+ * Ошибка, возникающая при попытке добавить в какой-либо объект грань, уже содержащуюся в данном объекте.
+ */
+ public class FaceExistsError extends ObjectExistsError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param face экземпляр или идентификатор грани, которая уже содержится в объекте
+ * @param source источник ошибки
+ */
+ public function FaceExistsError(face:Object = null, source:Object = null) {
+ var message:String;
+ if (source is Mesh) {
+ message = "Mesh ";
+ } else if (source is Surface) {
+ message = "Surface ";
+ }
+ if (face is Face) {
+ message += "%1. Face %2 already exists.";
+ } else {
+ message += "%1. Face with ID '%2' already exists.";
+ }
+ super(TextUtils.insertVars(message, source, face), face, source);
+ this.name = "FaceExistsError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as
new file mode 100644
index 0000000..3341f63
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as
@@ -0,0 +1,29 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая недостаточное количество вершин для создания грани.
+ * Для создания грани должно быть указано не менее трех вершин.
+ */
+ public class FaceNeedMoreVerticesError extends Engine3DError {
+
+ /**
+ * Количество переданных для создания грани вершин
+ */
+ public var count:uint;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param mesh объект, в котором произошла ошибка
+ * @param count количество вершин, переданное для создания грани
+ */
+ public function FaceNeedMoreVerticesError(mesh:Mesh = null, count:uint = 0) {
+ super(TextUtils.insertVars("Mesh %1. %2 vertices not enough for face creation.", mesh, count), mesh);
+ this.count = count;
+ this.name = "FaceNeedMoreVerticesError";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceNotFoundError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceNotFoundError.as
new file mode 100644
index 0000000..912d6a4
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/FaceNotFoundError.as
@@ -0,0 +1,34 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, возникающая, если грань не найдена в объекте.
+ */
+ public class FaceNotFoundError extends ObjectNotFoundError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param face экземпляр или идентификатор грани
+ * @param source объект, в котором произошла ошибка
+ */
+ public function FaceNotFoundError(face:Object = null, source:Object = null) {
+ var message:String;
+ if (source is Mesh) {
+ message = "Mesh ";
+ } else {
+ message = "Surface ";
+ }
+ if (face is Face) {
+ message += "%1. Face %2 not found.";
+ } else {
+ message += "%1. Face with ID '%2' not found.";
+ }
+ super(TextUtils.insertVars(message, source, face), face, source);
+ this.name = "FaceNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/InvalidIDError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/InvalidIDError.as
new file mode 100644
index 0000000..fbf4666
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/InvalidIDError.as
@@ -0,0 +1,34 @@
+package alternativa.engine3d.errors {
+ import alternativa.utils.TextUtils;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Surface;
+
+
+ /**
+ * Ошибка, обозначающая, что идентификатор зарезервирован и не может быть использован.
+ */
+ public class InvalidIDError extends Engine3DError {
+ /**
+ * Зарезервированный идентификатор
+ */
+ public var id:Object;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param id идентификатор
+ * @param source объект, в котором произошла ошибка
+ */
+ public function InvalidIDError(id:Object = null, source:Object = null) {
+ var message:String;
+ if (source is Mesh) {
+ message = "Mesh %2. ";
+ } else if (source is Surface) {
+ message = "Surface %2. ";
+ }
+ super(TextUtils.insertVars(message + "ID %1 is reserved and cannot be used", [id, source]), source);
+ this.id = id;
+ this.name = "InvalidIDError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/Object3DHierarchyError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/Object3DHierarchyError.as
new file mode 100644
index 0000000..8d61bd0
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/Object3DHierarchyError.as
@@ -0,0 +1,29 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, связанная с нарушением иерархии объектов сцены.
+ */
+ public class Object3DHierarchyError extends Engine3DError
+ {
+
+ /**
+ * Объект сцены, нарушающий иерархию
+ */
+ public var object:Object3D;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param object объект, нарушающий иерархию
+ * @param source источник ошибки
+ */
+ public function Object3DHierarchyError(object:Object3D = null, source:Object3D = null) {
+ super(TextUtils.insertVars("Object3D %1. Object %2 cannot be added", source, object), source);
+ this.object = object;
+ this.name = "Object3DHierarchyError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/Object3DNotFoundError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/Object3DNotFoundError.as
new file mode 100644
index 0000000..d1ddd1e
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/Object3DNotFoundError.as
@@ -0,0 +1,22 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, возникающая, когда объект сцены не был найден в списке связанных с необходимым объектом сцены.
+ */
+ public class Object3DNotFoundError extends ObjectNotFoundError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param object ненайденный объект сцены
+ * @param source объект сцены, в котором произошла ошибка
+ */
+ public function Object3DNotFoundError(object:Object3D = null, source:Object3D = null) {
+ super(TextUtils.insertVars("Object3D %1. Object %2 not in child list", source, object), object, source);
+ this.name = "Object3DNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/ObjectExistsError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/ObjectExistsError.as
new file mode 100644
index 0000000..7743a53
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/ObjectExistsError.as
@@ -0,0 +1,26 @@
+package alternativa.engine3d.errors {
+
+ /**
+ * Ошибка, обозначающая, что объект уже присутствует в контейнере.
+ */
+ public class ObjectExistsError extends Engine3DError {
+
+ /**
+ * Экземпляр или идентификатор объекта, который уже присутствует в контейнере
+ */
+ public var object:Object;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param message описание ошибки
+ * @param object объект, который уже присутствует в контейнере
+ * @param source объект, вызвавший ошибку
+ */
+ public function ObjectExistsError(message:String = "", object:Object = null, source:Object = null) {
+ super(message, source);
+ this.object = object;
+ this.name = "ObjectExistsError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/ObjectNotFoundError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/ObjectNotFoundError.as
new file mode 100644
index 0000000..87b3d14
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/ObjectNotFoundError.as
@@ -0,0 +1,26 @@
+package alternativa.engine3d.errors {
+
+ /**
+ * Необходимый объект не был найден в контейнере.
+ */
+ public class ObjectNotFoundError extends Engine3DError {
+
+ /**
+ * Объект, который отсутствует в контейнере.
+ */
+ public var object:Object;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param message описание ошибки
+ * @param object отсутствующий объект
+ * @param source объект, вызвавший ошибку
+ */
+ public function ObjectNotFoundError(message:String = "", object:Object = null, source:Object = null) {
+ super(message, source);
+ this.object = object;
+ this.name = "ObjectNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/SurfaceExistsError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/SurfaceExistsError.as
new file mode 100644
index 0000000..e13bee7
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/SurfaceExistsError.as
@@ -0,0 +1,22 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая, что поверхность уже присутствует в контейнере.
+ */
+ public class SurfaceExistsError extends ObjectExistsError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param surface поверхность, которая уже присутствует в контейнере
+ * @param mesh объект, вызвавший ошибку
+ */
+ public function SurfaceExistsError(surface:Object = null, mesh:Mesh = null) {
+ super(TextUtils.insertVars("Mesh %1. Surface with ID '%2' already exists.", mesh, surface), surface, mesh);
+ this.name = "SurfaceExistsError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/SurfaceNotFoundError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/SurfaceNotFoundError.as
new file mode 100644
index 0000000..05457bf
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/SurfaceNotFoundError.as
@@ -0,0 +1,31 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая, что поверхность не найдена в контейнере.
+ */
+ public class SurfaceNotFoundError extends ObjectNotFoundError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param surface поверхность, которая отсутствует в объекте
+ * @param mesh объект, который вызвал ошибку
+ */
+ public function SurfaceNotFoundError(surface:Object = null, mesh:Mesh = null) {
+ if (mesh == null) {
+
+ }
+ if (surface is Surface) {
+ message = "Mesh %1. Surface %2 not found.";
+ } else {
+ message = "Mesh %1. Surface with ID '%2' not found.";
+ }
+ super(TextUtils.insertVars(message, mesh, surface), surface, mesh);
+ this.name = "SurfaceNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/VertexExistsError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/VertexExistsError.as
new file mode 100644
index 0000000..9f0fff5
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/VertexExistsError.as
@@ -0,0 +1,22 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая, что вершина уже содержится в объекте.
+ */
+ public class VertexExistsError extends ObjectExistsError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param vertex вершина, которая уже есть в объекте
+ * @param mesh объект, вызвавший ошибку
+ */
+ public function VertexExistsError(vertex:Object = null, mesh:Mesh = null) {
+ super(TextUtils.insertVars("Mesh %1. Vertex with ID '%2' already exists.", mesh, vertex), vertex, mesh);
+ this.name = "VertexExistsError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/errors/VertexNotFoundError.as b/Alternativa3D5/5.3/alternativa/engine3d/errors/VertexNotFoundError.as
new file mode 100644
index 0000000..edf80aa
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/errors/VertexNotFoundError.as
@@ -0,0 +1,28 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая, что вершина не найдена в объекте.
+ */
+ public class VertexNotFoundError extends ObjectNotFoundError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param vertex вершина, которая не найдена в объекте
+ * @param mesh объект, вызвавший ошибку
+ */
+ public function VertexNotFoundError(vertex:Object = null, mesh:Mesh = null) {
+ if (vertex is Vertex) {
+ message = "Mesh %1. Vertex %2 not found.";
+ } else {
+ message = "Mesh %1. Vertex with ID '%2' not found.";
+ }
+ super(TextUtils.insertVars(message, mesh, vertex), vertex, mesh);
+ this.name = "VertexNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/loaders/Loader3DS.as b/Alternativa3D5/5.3/alternativa/engine3d/loaders/Loader3DS.as
new file mode 100644
index 0000000..565a84f
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/loaders/Loader3DS.as
@@ -0,0 +1,1053 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.engine3d.materials.TextureMaterialPrecision;
+ import alternativa.engine3d.materials.WireMaterial;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Texture;
+ import alternativa.utils.ColorUtils;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.Bitmap;
+ import flash.display.BitmapData;
+ import flash.display.BlendMode;
+ import flash.display.Loader;
+ import flash.errors.IOError;
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IOErrorEvent;
+ import flash.geom.Matrix;
+ import flash.geom.Point;
+ import flash.net.URLLoader;
+ import flash.net.URLLoaderDataFormat;
+ import flash.net.URLRequest;
+ import flash.system.LoaderContext;
+ import flash.utils.ByteArray;
+ import flash.utils.Endian;
+ import alternativa.types.Map;
+ import flash.events.SecurityErrorEvent;
+
+ use namespace alternativa3d;
+
+ /**
+ * Загрузчик моделей в формате 3DS.
+ *
+ *
+ *
+ *
+ * Порядок загрузки каждого объекта из 3DS-файла:
+ * Object3D;
+ * TextureMaterial,
+ * иначе в виде FillMaterial с указанным цветом. Если произошла ошибка при загрузке файла текстуры
+ * (например, файл отсутствует), то создаётся текстура-заглушка и генерируется исключение.
+ *
+ *
+ *
+ * Перед загрузкой файла можно установить ряд свойств, влияющих на создаваемые текстурные материалы.
+ */
+ public class Loader3DS extends EventDispatcher {
+
+ /**
+ * Коэффициент пересчёта в дюймы.
+ */
+ public static const INCHES:Number = 1;
+
+ /**
+ * Коэффициент пересчёта в футы.
+ */
+ public static const FEET:Number = 0.0833333334;
+ /**
+ * Коэффициент пересчёта в мили.
+ */
+ public static const MILES:Number = 0.0000157828;
+ /**
+ * Коэффициент пересчёта в миллиметры.
+ */
+ public static const MILLIMETERS:Number = 25.4000000259;
+ /**
+ * Коэффициент пересчёта в сантиметры.
+ */
+ public static const CENTIMETERS:Number = 2.5400000025;
+ /**
+ * Коэффициент пересчёта в метры.
+ */
+ public static const METERS:Number = 0.0254;
+ /**
+ * Коэффициент пересчёта в километры.
+ */
+ public static const KILOMETERS:Number = 0.0000254;
+
+ private static var stubBitmapData:BitmapData;
+
+ private var _content:Object3D;
+ private var version:uint;
+ private var objectDatas:Map;
+ private var animationDatas:Array;
+ private var materialDatas:Array;
+ private var bitmaps:Array;
+
+ private var urlLoader:URLLoader;
+ private var data:ByteArray;
+
+ private var counter:uint;
+ private var sequence:Array;
+ private var loader:Loader;
+ private var context:LoaderContext;
+ private var path:String;
+
+ /**
+ * Повтор текстуры при заливке для создаваемых текстурных материалов.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var repeat:Boolean = true;
+ /**
+ * Сглаживание текстур при увеличении масштаба.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var smooth:Boolean = false;
+ /**
+ * Режим наложения цвета для создаваемых текстурных материалов.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var blendMode:String = BlendMode.NORMAL;
+ /**
+ * Точность перспективной коррекции для создаваемых текстурных материалов.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var precision:Number = TextureMaterialPrecision.MEDIUM;
+
+ /**
+ * Коэффициент пересчёта единиц измерения модели.
+ */
+ public var units:Number = INCHES;
+ /**
+ * Устанавливаемый уровень мобильности загруженных объектов.
+ */
+ public var mobility:int = 0;
+
+ /**
+ * Прекращение текущей загрузки.
+ */
+ public function close():void {
+ try {
+ urlLoader.close();
+ } catch (e:Error) {
+ }
+ try {
+ loader.close();
+ } catch (e:Error) {
+ }
+ }
+
+ /**
+ * Загрузка сцены из 3DS-файла по указанному адресу. По окончании загрузки посылается сообщение Event.COMPLETE,
+ * после чего контейнер с загруженными объектами становится доступным через свойство content.
+ * IOErrorEvent.IO_ERROR и
+ * SecurityErrorEvent.SECURITY_ERROR соответственно.
+ * alternativa.engine3d.loaders.MaterialInfo.
+ * @see alternativa.engine3d.loaders.MaterialInfo
+ */
+ public function get library():Map {
+ return _library;
+ }
+
+ /**
+ * Метод выполняет загрузку файла материалов, разбор его содержимого, загрузку текстур при необходимости и
+ * формирование библиотеки материалов. После окончания работы метода посылается сообщение
+ * Event.COMPLETE и становится доступна библиотека материалов через свойство library.
+ * IOErrorEvent.IO_ERROR и
+ * SecurityErrorEvent.SECURITY_ERROR соответственно.
+ * Object3D.
+ *
+ *
+ *
+ *
+ *
+ * Команда
+ * Описание
+ * Действие
+ *
+ * o object_name
+ * Объявление нового объекта с именем object_name
+ * Если для текущего объекта были определены грани, то команда создаёт новый текущий объект с указанным именем,
+ * иначе у текущего объекта просто меняется имя на указанное.
+ *
+ *
+ * v x y z
+ * Объявление вершины с координатами x y z
+ * Вершина помещается в общий список вершин сцены для дальнейшего использования
+ *
+ *
+ * vt u [v]
+ * Объявление текстурной вершины с координатами u v
+ * Вершина помещается в общий список текстурных вершин сцены для дальнейшего использования
+ *
+ *
+ * f v0[/vt0] v1[/vt1] ... vN[/vtN]
+ * Объявление грани, состоящей из указанных вершин и опционально имеющую заданные текстурные координаты для вершин.
+ * Грань добавляется к текущему активному объекту. Если есть активный материал, то грань также добавляется в поверхность
+ * текущего объекта, соответствующую текущему материалу.
+ *
+ *
+ * usemtl material_name
+ * Установка текущего материала с именем material_name
+ * С момента установки текущего материала все грани, создаваемые в текущем объекте будут помещаться в поверхность,
+ * соотвествующую этому материалу и имеющую идентификатор, совпадающий с его именем.
+ *
+ *
+ * mtllib file1 file2 ...
+ * Объявление файлов, содержащих определения материалов
+ * Выполняется загрузка файлов и формирование библиотеки материалов
+ *
+ * var loader:LoaderOBJ = new LoaderOBJ();
+ * loader.addEventListener(Event.COMPLETE, onLoadingComplete);
+ * loader.load("foo.obj");
+ *
+ * function onLoadingComplete(e:Event):void {
+ * scene.root.addChild(e.target.content);
+ * }
+ *
+ */
+ public class LoaderOBJ extends EventDispatcher {
+
+ private static const COMMENT_CHAR:String = "#";
+
+ private static const CMD_OBJECT_NAME:String = "o";
+ private static const CMD_VERTEX:String = "v";
+ private static const CMD_TEXTURE_VERTEX:String = "vt";
+ private static const CMD_FACE:String = "f";
+ private static const CMD_MATERIAL_LIB:String = "mtllib";
+ private static const CMD_USE_MATERIAL:String = "usemtl";
+
+ private static const REGEXP_TRIM:RegExp = /^\s*(.*)\s*$/;
+ private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/;
+ private static const REGEXP_SPLIT_LINE:RegExp = /\s+/;
+
+ private var basePath:String;
+ private var objLoader:URLLoader;
+ private var mtlLoader:LoaderMTL;
+ private var loaderContext:LoaderContext;
+ private var loadMaterials:Boolean;
+ // Объект, содержащий все определённые в obj файле объекты
+ private var _content:Object3D;
+ // Текущий конструируемый объект
+ private var currentObject:Mesh;
+ // Стартовый индекс вершины в глобальном массиве вершин для текущего объекта
+ private var vIndexStart:int = 0;
+ // Стартовый индекс текстурной вершины в глобальном массиве текстурных вершин для текущего объекта
+ private var vtIndexStart:int = 0;
+ // Глобальный массив вершин, определённых во входном файле
+ private var globalVertices:Array;
+ // Глобальный массив текстурных вершин, определённых во входном файле
+ private var globalTextureVertices:Array;
+ // Имя текущего активного материала. Если значение равно null, то активного материала нет.
+ private var currentMaterialName:String;
+ // Массив граней текущего объекта, которым назначен текущий материал
+ private var materialFaces:Array;
+ // Массив имён файлов, содержащих определения материалов
+ private var materialFileNames:Array;
+ private var currentMaterialFileIndex:int;
+ private var materialLibrary:Map;
+
+ /**
+ * Сглаживание текстур при увеличении масштаба.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var smooth:Boolean = false;
+ /**
+ * Режим наложения цвета для создаваемых текстурных материалов.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var blendMode:String = BlendMode.NORMAL;
+ /**
+ * Точность перспективной коррекции для создаваемых текстурных материалов.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var precision:Number = TextureMaterialPrecision.MEDIUM;
+
+ /**
+ * Устанавливаемый уровень мобильности загруженных объектов.
+ */
+ public var mobility:int = 0;
+
+ /**
+ * При установленном значении true выполняется преобразование координат геометрических вершин посредством
+ * поворота на 90 градусов относительно оси X. Смысл флага в преобразовании системы координат, в которой вверх направлена
+ * ось Y, в систему координат, использующуюся в Alternativa3D (вверх направлена ось Z).
+ */
+ public var rotateModel:Boolean;
+
+ /**
+ * Создаёт новый экземпляр загрузчика.
+ */
+ public function LoaderOBJ() {
+ }
+
+ /**
+ * Контейнер, содержащий все загруженные из OBJ-файла модели.
+ */
+ public function get content():Object3D {
+ return _content;
+ }
+
+ /**
+ * Прекращение текущей загрузки.
+ */
+ public function close():void {
+ try {
+ objLoader.close();
+ } catch (e:Error) {
+ }
+ mtlLoader.close();
+ }
+
+ /**
+ * Загрузка сцены из OBJ-файла по указанному адресу. По окончании загрузки посылается сообщение Event.COMPLETE,
+ * после чего контейнер с загруженными объектами становится доступным через свойство content.
+ * IOErrorEvent.IO_ERROR и
+ * SecurityErrorEvent.SECURITY_ERROR соответственно.
+ * true, будут обработаны все файлы
+ * материалов, указанные в исходном OBJ-файле.
+ * @param context LoaderContext для загрузки файлов текстур
+ *
+ * @see #content
+ */
+ public function load(url:String, loadMaterials:Boolean = true, context:LoaderContext = null):void {
+ _content = null;
+ this.loadMaterials = loadMaterials;
+ this.loaderContext = context;
+ basePath = url.substring(0, url.lastIndexOf("/") + 1);
+ if (objLoader == null) {
+ objLoader = new URLLoader();
+ objLoader.addEventListener(Event.COMPLETE, onObjLoadComplete);
+ objLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError);
+ objLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError);
+ } else {
+ close();
+ }
+ objLoader.load(new URLRequest(url));
+ }
+
+ /**
+ * Обработка окончания загрузки obj файла.
+ *
+ * @param e
+ */
+ private function onObjLoadComplete(e:Event):void {
+ parse(objLoader.data);
+ }
+
+ /**
+ * Обработка ошибки при загрузке.
+ *
+ * @param e
+ */
+ private function onObjLoadError(e:ErrorEvent):void {
+ dispatchEvent(e);
+ }
+
+ /**
+ * Метод выполняет разбор данных, полученных из obj файла.
+ *
+ * @param s содержимое obj файла
+ * @param materialLibrary библиотека материалов
+ * @return объект, содержащий все трёхмерные объекты, определённые в obj файле
+ */
+ private function parse(data:String):void {
+ _content = new Object3D();
+ currentObject = new Mesh();
+ currentObject.mobility = mobility;
+ _content.addChild(currentObject);
+
+ globalVertices = new Array();
+ globalTextureVertices = new Array();
+ materialFileNames = new Array();
+
+ var lines:Array = data.split(REGEXP_SPLIT_FILE);
+ for each (var line:String in lines) {
+ parseLine(line);
+ }
+ moveFacesToSurface();
+ // Вся геометрия загружена и сформирована. Выполняется загрузка информации о материалах.
+ if (loadMaterials && materialFileNames.length > 0) {
+ loadMaterialsLibrary();
+ } else {
+ dispatchEvent(new Event(Event.COMPLETE));
+ }
+ }
+
+ /**
+ *
+ */
+ private function parseLine(line:String):void {
+ line = line.replace(REGEXP_TRIM,"$1");
+ if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) {
+ return;
+ }
+ var parts:Array = line.split(REGEXP_SPLIT_LINE);
+ switch (parts[0]) {
+ // Объявление нового объекта
+ case CMD_OBJECT_NAME:
+ defineObject(parts[1]);
+ break;
+ // Объявление вершины
+ case CMD_VERTEX:
+ globalVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3])));
+ break;
+ // Объявление текстурной вершины
+ case CMD_TEXTURE_VERTEX:
+ globalTextureVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3])));
+ break;
+ // Объявление грани
+ case CMD_FACE:
+ createFace(parts);
+ break;
+ case CMD_MATERIAL_LIB:
+ storeMaterialFileNames(parts);
+ break;
+ case CMD_USE_MATERIAL:
+ setNewMaterial(parts);
+ break;
+ }
+ }
+
+ /**
+ * Объявление нового объекта.
+ *
+ * @param objectName имя объекта
+ */
+ private function defineObject(objectName:String):void {
+ if (currentObject.faces.length == 0) {
+ // Если у текущего объекта нет граней, то он остаётся текущим, но меняется имя
+ currentObject.name = objectName;
+ } else {
+ // Если у текущего объекта есть грани, то обявление нового имени создаёт новый объект
+ moveFacesToSurface();
+ currentObject = new Mesh(objectName);
+ currentObject.mobility = mobility;
+ _content.addChild(currentObject);
+ }
+ vIndexStart = globalVertices.length;
+ vtIndexStart = globalTextureVertices.length;
+ }
+
+ /**
+ * Создание грани в текущем объекте.
+ *
+ * @param parts массив, содержащий индексы вершин грани, начиная с элемента с индексом 1
+ */
+ private function createFace(parts:Array):void {
+ // Стартовый индекс вершины в объекте для добавляемой грани
+ var startVertexIndex:int = currentObject.vertices.length;
+ // Создание вершин в объекте
+ var faceVertexCount:int = parts.length - 1;
+ var vtIndices:Array = new Array(3);
+ // Массив идентификаторов вершин грани
+ var faceVertices:Array = new Array(faceVertexCount);
+ for (var i:int = 0; i < faceVertexCount; i++) {
+ var indices:Array = parts[i + 1].split("/");
+ // Создание вершины
+ var vIdx:int = int(indices[0]);
+ // Если индекс положительный, то его значение уменьшается на единицу, т.к. в obj формате индексация начинается с 1.
+ // Если индекс отрицательный, то выполняется смещение на его значение назад от стартового глобального индекса вершин для текущего объекта.
+ var actualIndex:int = vIdx > 0 ? vIdx - 1 : vIndexStart + vIdx;
+
+ var vertex:Vertex = currentObject.vertices[actualIndex];
+ // Если вершины нет в объекте, она добавляется
+ if (vertex == null) {
+ var p:Point3D = globalVertices[actualIndex];
+ if (rotateModel) {
+ // В формате obj направление "вверх" совпадает с осью Y, поэтому выполняется поворот координат на 90 градусов по оси X
+ vertex = currentObject.createVertex(p.x, -p.z, p.y, actualIndex);
+ } else {
+ vertex = currentObject.createVertex(p.x, p.y, p.z, actualIndex);
+ }
+ }
+ faceVertices[i] = vertex;
+
+ // Запись индекса текстурной вершины
+ if (i < 3) {
+ vtIndices[i] = int(indices[1]);
+ }
+ }
+ // Создание грани
+ var face:Face = currentObject.createFace(faceVertices, currentObject.faces.length);
+ // Установка uv координат
+ if (vtIndices[0] != 0) {
+ p = globalTextureVertices[vtIndices[0] - 1];
+ face.aUV = new Point(p.x, p.y);
+ p = globalTextureVertices[vtIndices[1] - 1];
+ face.bUV = new Point(p.x, p.y);
+ p = globalTextureVertices[vtIndices[2] - 1];
+ face.cUV = new Point(p.x, p.y);
+ }
+ // Если есть активный материал, то грань заносится в массив для последующего формирования поверхности в объекте
+ if (currentMaterialName != null) {
+ materialFaces.push(face);
+ }
+ }
+
+ /**
+ * Загрузка библиотек материалов.
+ *
+ * @param parts массив, содержащий имена файлов материалов, начиная с элемента с индексом 1
+ */
+ private function storeMaterialFileNames(parts:Array):void {
+ for (var i:int = 1; i < parts.length; i++) {
+ materialFileNames.push(parts[i]);
+ }
+ }
+
+ /**
+ * Установка нового текущего материала.
+ *
+ * @param parts массив, во втором элементе которого содержится имя материала
+ */
+ private function setNewMaterial(parts:Array):void {
+ // Все сохранённые грани добавляются в соответствующую поверхность текущего объекта
+ moveFacesToSurface();
+ // Установка нового текущего материала
+ currentMaterialName = parts[1];
+ }
+
+ /**
+ * Добавление всех граней с текущим материалом в поверхность с идентификатором, совпадающим с именем материала.
+ */
+ private function moveFacesToSurface():void {
+ if (currentMaterialName != null && materialFaces.length > 0) {
+ if (currentObject.hasSurface(currentMaterialName)) {
+ // При наличии поверхности с таким идентификатором, грани добавляются в неё
+ var surface:Surface = currentObject.getSurfaceById(currentMaterialName);
+ for each (var face:* in materialFaces) {
+ surface.addFace(face);
+ }
+ } else {
+ // При отсутствии поверхности с таким идентификатором, создатся новая поверхность
+ currentObject.createSurface(materialFaces, currentMaterialName);
+ }
+ }
+ materialFaces = [];
+ }
+
+ /**
+ * Загрузка материалов.
+ */
+ private function loadMaterialsLibrary():void {
+ if (mtlLoader == null) {
+ mtlLoader = new LoaderMTL();
+ mtlLoader.addEventListener(Event.COMPLETE, onMaterialFileLoadComplete);
+ mtlLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError);
+ mtlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError);
+ }
+ materialLibrary = new Map();
+
+ currentMaterialFileIndex = -1;
+ loadNextMaterialFile();
+ }
+
+ /**
+ * Обработка успешной загрузки библиотеки материалов.
+ */
+ private function onMaterialFileLoadComplete(e:Event):void {
+ materialLibrary.concat(mtlLoader.library);
+ // Загрузка следующего файла материалов
+ loadNextMaterialFile();
+ }
+
+ /**
+ *
+ */
+ private function loadNextMaterialFile():void {
+ currentMaterialFileIndex++;
+ if (currentMaterialFileIndex == materialFileNames.length) {
+ setMaterials();
+ dispatchEvent(new Event(Event.COMPLETE));
+ } else {
+ mtlLoader.load(basePath + materialFileNames[currentMaterialFileIndex], loaderContext);
+ }
+ }
+
+ /**
+ * Установка материалов.
+ */
+ private function setMaterials():void {
+ if (materialLibrary != null) {
+ for (var objectKey:* in _content.children) {
+ var object:Mesh = objectKey;
+ for (var surfaceKey:* in object.surfaces) {
+ var surface:Surface = object.surfaces[surfaceKey];
+ // Поверхности имеют идентификаторы, соответствующие именам материалов
+ var materialInfo:MaterialInfo = materialLibrary[surfaceKey];
+ if (materialInfo != null) {
+ if (materialInfo.bitmapData == null) {
+ surface.material = new FillMaterial(materialInfo.color, materialInfo.alpha, blendMode);
+ } else {
+ surface.material = new TextureMaterial(new Texture(materialInfo.bitmapData, materialInfo.textureFileName), materialInfo.alpha, materialInfo.repeat, (materialInfo.bitmapData != LoaderMTL.stubBitmapData) ? smooth : false, blendMode, -1, 0, precision);
+ transformUVs(surface, materialInfo.mapOffset, materialInfo.mapSize);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Метод выполняет преобразование UV-координат текстурированных граней. В связи с тем, что в формате MRL предусмотрено
+ * масштабирование и смещение текстурной карты в UV-пространстве, а в движке такой фунциональности нет, необходимо
+ * эмулировать преобразования текстуры преобразованием UV-координат граней. Преобразования выполняются исходя из предположения,
+ * что текстурное пространство сначала масштабируется относительно центра, а затем сдвигается на указанную величину
+ * смещения.
+ *
+ * @param surface поверхность, грани которой обрабатываюся
+ * @param mapOffset смещение текстурной карты. Значение mapOffset.x указывает смещение по U, значение mapOffset.y
+ * указывает смещение по V.
+ * @param mapSize коэффициенты масштабирования текстурной карты. Значение mapSize.x указывает коэффициент масштабирования
+ * по оси U, значение mapSize.y указывает коэффициент масштабирования по оси V.
+ */
+ private function transformUVs(surface:Surface, mapOffset:Point, mapSize:Point):void {
+ for (var key:* in surface.faces) {
+ var face:Face = key;
+ var uv:Point = face.aUV;
+ uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
+ uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
+ face.aUV = uv;
+ uv = face.bUV;
+ uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
+ uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
+ face.bUV = uv;
+ uv = face.cUV;
+ uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
+ uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
+ face.cUV = uv;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/loaders/MTLTextureMapInfo.as b/Alternativa3D5/5.3/alternativa/engine3d/loaders/MTLTextureMapInfo.as
new file mode 100644
index 0000000..4bf9e57
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/loaders/MTLTextureMapInfo.as
@@ -0,0 +1,120 @@
+package alternativa.engine3d.loaders {
+ /**
+ * @private
+ * Класс содержит информацию о текстуре в формате MTL material format (Lightwave, OBJ) и функционал для разбора
+ * описания текстуры.
+ * Описание формата можно посмотреть по адресу: http://local.wasp.uwa.edu.au/~pbourke/dataformats/mtl/
+ */
+ internal class MTLTextureMapInfo {
+
+ // Ассоциация параметров команды объявления текстуры и методов для их чтения
+ private static const optionReaders:Object = {
+ "-clamp": clampReader,
+ "-o": offsetReader,
+ "-s": sizeReader,
+
+ "-blendu": stubReader,
+ "-blendv": stubReader,
+ "-bm": stubReader,
+ "-boost": stubReader,
+ "-cc": stubReader,
+ "-imfchan": stubReader,
+ "-mm": stubReader,
+ "-t": stubReader,
+ "-texres": stubReader
+ };
+
+ // Смещение в текстурном пространстве
+ public var offsetU:Number = 0;
+ public var offsetV:Number = 0;
+ public var offsetW:Number = 0;
+
+ // Масштабирование текстурного пространства
+ public var sizeU:Number = 1;
+ public var sizeV:Number = 1;
+ public var sizeW:Number = 1;
+
+ // Флаг повторения текстуры
+ public var repeat:Boolean = true;
+ // Имя файла текстуры
+ public var fileName:String;
+
+ /**
+ * Метод выполняет разбор данных о текстуре.
+ *
+ * @param parts Данные о текстуре. Массив должен содержать части разделённой по пробелам входной строки MTL-файла.
+ * @return объект, содержащий данные о текстуре
+ */
+ public static function parse(parts:Array):MTLTextureMapInfo {
+ var info:MTLTextureMapInfo = new MTLTextureMapInfo();
+ // Начальное значение индекса единица, т.к. первый элемент массива содержит тип текстуры
+ var index:int = 1;
+ var reader:Function;
+ // Чтение параметров текстуры
+ while ((reader = optionReaders[parts[index]]) != null) {
+ index = reader(index, parts, info);
+ }
+ // Если не было ошибок, последний элемент массива должен содержать имя файла текстуры
+ info.fileName = parts[index];
+ return info;
+ }
+
+ /**
+ * Читатель-заглушка. Пропускает все неподдерживаемые параметры.
+ */
+ private static function stubReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
+ index++;
+ var maxIndex:int = parts.length - 1;
+ while ((MTLTextureMapInfo.optionReaders[parts[index]] == null) && (index < maxIndex)) {
+ index++;
+ }
+ return index;
+ }
+
+ /**
+ * Метод чтения параметров масштабирования текстурного пространства.
+ */
+ private static function sizeReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
+ info.sizeU = Number(parts[index + 1]);
+ index += 2;
+ var value:Number = Number(parts[index]);
+ if (!isNaN(value)) {
+ info.sizeV = value;
+ index++;
+ value = Number(parts[index]);
+ if (!isNaN(value)) {
+ info.sizeW = value;
+ index++;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Метод чтения параметров смещения текстуры.
+ */
+ private static function offsetReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
+ info.offsetU = Number(parts[index + 1]);
+ index += 2;
+ var value:Number = Number(parts[index]);
+ if (!isNaN(value)) {
+ info.offsetV = value;
+ index++;
+ value = Number(parts[index]);
+ if (!isNaN(value)) {
+ info.offsetW = value;
+ index++;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Метод чтения параметра повторения текстуры.
+ */
+ private static function clampReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
+ info.repeat = parts[index + 1] == "off";
+ return index + 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/loaders/MaterialInfo.as b/Alternativa3D5/5.3/alternativa/engine3d/loaders/MaterialInfo.as
new file mode 100644
index 0000000..0a9a9ee
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/loaders/MaterialInfo.as
@@ -0,0 +1,20 @@
+package alternativa.engine3d.loaders {
+ import flash.display.BitmapData;
+ import flash.geom.Point;
+
+ /**
+ * @private
+ * Класс содержит обобщённую информацию о материале.
+ */
+ internal class MaterialInfo {
+ public var color:uint;
+ public var alpha:Number;
+
+ public var textureFileName:String;
+ public var bitmapData:BitmapData;
+ public var repeat:Boolean;
+
+ public var mapOffset:Point;
+ public var mapSize:Point;
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/materials/DevMaterial.as b/Alternativa3D5/5.3/alternativa/engine3d/materials/DevMaterial.as
new file mode 100644
index 0000000..0d3d0c3
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/materials/DevMaterial.as
@@ -0,0 +1,198 @@
+package alternativa.engine3d.materials {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.display.Skin;
+
+ import flash.display.BlendMode;
+ import flash.display.Graphics;
+ import alternativa.utils.ColorUtils;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.core.BSPNode;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ * Материал, заполняющий грань сплошной заливкой цветом в соответствии с уровнем мобильности. Помимо заливки материал может рисовать границу
+ * полигона линией заданной толщины и цвета.
+ */
+ public class DevMaterial extends SurfaceMaterial {
+ /**
+ * @private
+ * Цвет
+ */
+ alternativa3d var _color:uint;
+
+ /**
+ * @private
+ * Толщина линий обводки
+ */
+ alternativa3d var _wireThickness:Number;
+
+ /**
+ * @private
+ * Цвет линий обводки
+ */
+ alternativa3d var _wireColor:uint;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param color цвет заливки
+ * @param alpha прозрачность
+ * @param blendMode режим наложения цвета
+ * @param wireThickness толщина линии обводки
+ * @param wireColor цвет линии обводки
+ */
+ public function DevMaterial(color:uint = 0xFFFFFF, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) {
+ super(alpha, blendMode);
+ _color = color;
+ _wireThickness = wireThickness;
+ _wireColor = wireColor;
+ }
+
+ /**
+ * @private
+ *
+ * @param camera
+ * @param skin
+ * @param length
+ * @param points
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+
+ var i:uint;
+ var point:DrawPoint;
+ var gfx:Graphics = skin.gfx;
+
+ /*
+ //Мобильность
+ var param:int = skin.primitive.mobility*10;
+ */
+
+ /*
+ // Уровень распиленности
+ var param:int = 0;
+ var prm:PolyPrimitive = skin.primitive;
+ while (prm != null) {
+ prm = prm.parent;
+ param++;
+ }
+ param *= 10;
+ */
+
+ // Уровень в BSP-дереве
+ var param:int = 0;
+ var node:BSPNode = skin.primitive.node;
+ while (node != null) {
+ node = node.parent;
+ param++;
+ }
+ param *= 5;
+
+ var c:uint = ColorUtils.rgb(param, param, param);
+
+ if (camera._orthographic) {
+ gfx.beginFill(c);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ gfx.moveTo(point.x, point.y);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ gfx.beginFill(c);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ var perspective:Number = camera.focalLength/point.z;
+ gfx.moveTo(point.x*perspective, point.y*perspective);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ }
+ }
+
+ /**
+ * Цвет заливки.
+ */
+ public function get color():uint {
+ return _color;
+ }
+
+ /**
+ * @private
+ */
+ public function set color(value:uint):void {
+ if (_color != value) {
+ _color = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Толщина линии обводки. Если значение отрицательное, то отрисовка линии не выполняется.
+ */
+ public function get wireThickness():Number {
+ return _wireThickness;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireThickness(value:Number):void {
+ if (_wireThickness != value) {
+ _wireThickness = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Цвет линии обводки.
+ */
+ public function get wireColor():uint {
+ return _wireColor;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireColor(value:uint):void {
+ if (_wireColor != value) {
+ _wireColor = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ var res:DevMaterial = new DevMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor);
+ return res;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/materials/DrawPoint.as b/Alternativa3D5/5.3/alternativa/engine3d/materials/DrawPoint.as
new file mode 100644
index 0000000..b661756
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/materials/DrawPoint.as
@@ -0,0 +1,22 @@
+package alternativa.engine3d.materials {
+ /**
+ * @private
+ */
+ public final class DrawPoint {
+
+ public var x:Number;
+ public var y:Number;
+ public var z:Number;
+ public var u:Number;
+ public var v:Number;
+
+ public function DrawPoint(x:Number, y:Number, z:Number, u:Number = 0, v:Number = 0) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.u = u;
+ this.v = v;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/materials/FillMaterial.as b/Alternativa3D5/5.3/alternativa/engine3d/materials/FillMaterial.as
new file mode 100644
index 0000000..6372fac
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/materials/FillMaterial.as
@@ -0,0 +1,167 @@
+package alternativa.engine3d.materials {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.display.Skin;
+
+ import flash.display.BlendMode;
+ import flash.display.Graphics;
+
+ use namespace alternativa3d;
+
+ /**
+ * Материал, заполняющий грань сплошной одноцветной заливкой. Помимо заливки цветом материал может рисовать границу
+ * полигона линией заданной толщины и цвета.
+ */
+ public class FillMaterial extends SurfaceMaterial {
+ /**
+ * @private
+ * Цвет
+ */
+ alternativa3d var _color:uint;
+
+ /**
+ * @private
+ * Толщина линий обводки
+ */
+ alternativa3d var _wireThickness:Number;
+
+ /**
+ * @private
+ * Цвет линий обводки
+ */
+ alternativa3d var _wireColor:uint;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param color цвет заливки
+ * @param alpha прозрачность
+ * @param blendMode режим наложения цвета
+ * @param wireThickness толщина линии обводки
+ * @param wireColor цвет линии обводки
+ */
+ public function FillMaterial(color:uint, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) {
+ super(alpha, blendMode);
+ _color = color;
+ _wireThickness = wireThickness;
+ _wireColor = wireColor;
+ }
+
+ /**
+ * @private
+ *
+ * @param camera
+ * @param skin
+ * @param length
+ * @param points
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+
+ var i:uint;
+ var point:DrawPoint;
+ var gfx:Graphics = skin.gfx;
+
+ if (camera._orthographic) {
+ gfx.beginFill(_color);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ gfx.moveTo(point.x, point.y);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ gfx.beginFill(_color);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ var perspective:Number = camera.focalLength/point.z;
+ gfx.moveTo(point.x*perspective, point.y*perspective);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ }
+ }
+
+ /**
+ * Цвет заливки.
+ */
+ public function get color():uint {
+ return _color;
+ }
+
+ /**
+ * @private
+ */
+ public function set color(value:uint):void {
+ if (_color != value) {
+ _color = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Толщина линии обводки. Если значение отрицательное, то отрисовка линии не выполняется.
+ */
+ public function get wireThickness():Number {
+ return _wireThickness;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireThickness(value:Number):void {
+ if (_wireThickness != value) {
+ _wireThickness = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Цвет линии обводки.
+ */
+ public function get wireColor():uint {
+ return _wireColor;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireColor(value:uint):void {
+ if (_wireColor != value) {
+ _wireColor = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ var res:FillMaterial = new FillMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor);
+ return res;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/materials/Material.as b/Alternativa3D5/5.3/alternativa/engine3d/materials/Material.as
new file mode 100644
index 0000000..399304f
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/materials/Material.as
@@ -0,0 +1,20 @@
+package alternativa.engine3d.materials {
+ import alternativa.engine3d.*;
+
+ use namespace alternativa3d;
+
+ /**
+ * Базовый класс для материалов.
+ */
+ public class Material {
+
+ /**
+ * Создание клона материала.
+ *
+ * @return клон материала
+ */
+ public function clone():Material {
+ return new Material();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/materials/SurfaceMaterial.as b/Alternativa3D5/5.3/alternativa/engine3d/materials/SurfaceMaterial.as
new file mode 100644
index 0000000..96bc64f
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/materials/SurfaceMaterial.as
@@ -0,0 +1,178 @@
+package alternativa.engine3d.materials {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.core.Scene3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.display.Skin;
+
+ import flash.display.BlendMode;
+
+ use namespace alternativa3d;
+
+ /**
+ * Базовый класс для материалов поверхности.
+ */
+ public class SurfaceMaterial extends Material {
+ /**
+ * @private
+ * Поверхность
+ */
+ alternativa3d var _surface:Surface;
+ /**
+ * @private
+ * Альфа
+ */
+ alternativa3d var _alpha:Number;
+ /**
+ * @private
+ * Режим наложения
+ */
+ alternativa3d var _blendMode:String = BlendMode.NORMAL;
+ /**
+ * @private
+ * Материал использует информация об UV-координатах
+ */
+ alternativa3d var useUV:Boolean = false;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param alpha прозрачность материала
+ * @param blendMode режим наложения цвета
+ */
+ public function SurfaceMaterial(alpha:Number = 1, blendMode:String = BlendMode.NORMAL) {
+ _alpha = alpha;
+ _blendMode = blendMode;
+ }
+
+ /**
+ * Поверхность материала.
+ */
+ public function get surface():Surface {
+ return _surface;
+ }
+
+ /**
+ * @private
+ * Добавление на сцену
+ *
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {}
+
+ /**
+ * @private
+ * Удаление из сцены
+ *
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {}
+
+ /**
+ * @private
+ * Добавление к мешу
+ *
+ * @param mesh
+ */
+ alternativa3d function addToMesh(mesh:Mesh):void {}
+
+ /**
+ * @private
+ * Удаление из меша
+ *
+ * @param mesh
+ */
+ alternativa3d function removeFromMesh(mesh:Mesh):void {}
+
+ /**
+ * @private
+ * Добавление на поверхность
+ *
+ * @param surface
+ */
+ alternativa3d function addToSurface(surface:Surface):void {
+ // Сохраняем поверхность
+ _surface = surface;
+ }
+
+ /**
+ * @private
+ * Удаление с поверхности
+ *
+ * @param surface
+ */
+ alternativa3d function removeFromSurface(surface:Surface):void {
+ // Удаляем ссылку на поверхность
+ _surface = null;
+ }
+
+ /**
+ * Прозрачность материала.
+ */
+ public function get alpha():Number {
+ return _alpha;
+ }
+
+ /**
+ * @private
+ */
+ public function set alpha(value:Number):void {
+ if (_alpha != value) {
+ _alpha = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Режим наложения цвета.
+ */
+ public function get blendMode():String {
+ return _blendMode;
+ }
+
+ /**
+ * @private
+ */
+ public function set blendMode(value:String):void {
+ if (_blendMode != value) {
+ _blendMode = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
+ return true;
+ }
+
+ /**
+ * @private
+ */
+ alternativa3d function clear(skin:Skin):void {
+ skin.gfx.clear();
+ }
+
+ /**
+ * @private
+ */
+ alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ return new SurfaceMaterial(_alpha, _blendMode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/materials/TextureMaterial.as b/Alternativa3D5/5.3/alternativa/engine3d/materials/TextureMaterial.as
new file mode 100644
index 0000000..b4b81c2
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/materials/TextureMaterial.as
@@ -0,0 +1,368 @@
+package alternativa.engine3d.materials {
+ import __AS3__.vec.Vector;
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.display.Skin;
+ import alternativa.types.*;
+
+ import flash.display.BlendMode;
+ import flash.geom.Matrix;
+ import flash.display.BitmapData;
+ import flash.display.Graphics;
+
+ use namespace alternativa3d;
+ use namespace alternativatypes;
+
+ /**
+ * Материал, заполняющий полигон текстурой. Помимо наложения текстуры материал может рисовать границу полигона линией
+ * заданной толщины и цвета.
+ */
+ public class TextureMaterial extends SurfaceMaterial {
+
+ private static var stubBitmapData:BitmapData;
+ private static var stubMatrix:Matrix;
+
+ private var gfx:Graphics;
+ private var textureMatrix:Matrix = new Matrix();
+ private var focalLength:Number;
+ private var distortion:Number;
+
+ /**
+ * @private
+ * Текстура
+ */
+ alternativa3d var _texture:Texture;
+ /**
+ * @private
+ * Повтор текстуры
+ */
+ alternativa3d var _repeat:Boolean;
+ /**
+ * @private
+ * Сглаженность текстуры
+ */
+ alternativa3d var _smooth:Boolean;
+ /**
+ * @private
+ * Точность перспективной коррекции
+ */
+ alternativa3d var _precision:Number;
+
+ /**
+ * @private
+ * Толщина линий обводки
+ */
+ alternativa3d var _wireThickness:Number;
+
+ /**
+ * @private
+ * Цвет линий обводки
+ */
+ alternativa3d var _wireColor:uint;
+
+ /**
+ * Создание экземпляра текстурного материала.
+ *
+ * @param texture текстура материала
+ * @param alpha прозрачность материала
+ * @param repeat повтор текстуры при заполнении
+ * @param smooth сглаживание текстуры при увеличении масштаба
+ * @param blendMode режим наложения цвета
+ * @param wireThickness толщина линии обводки
+ * @param wireColor цвет линии обводки
+ * @param precision точность перспективной коррекции. Может быть задана одной из констант класса
+ * TextureMaterialPrecision или числом типа Number. Во втором случае, чем меньшее значение будет
+ * установлено, тем более качественная перспективная коррекция будет выполнена, и тем больше времени будет затрачено
+ * на расчёт кадра.
+ */
+ public function TextureMaterial(texture:Texture, alpha:Number = 1, repeat:Boolean = true, smooth:Boolean = false, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0, precision:Number = TextureMaterialPrecision.MEDIUM) {
+ super(alpha, blendMode);
+ _texture = texture;
+ _repeat = repeat;
+ _smooth = smooth;
+ _wireThickness = wireThickness;
+ _wireColor = wireColor;
+ _precision = precision;
+ useUV = true;
+ }
+
+ /**
+ * @private
+ * @param primitive
+ * @return
+ */
+ override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
+ return _texture != null;
+ }
+
+ /**
+ * @private
+ * @param camera
+ * @param skin
+ * @param length
+ * @param points
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+
+ var i:uint;
+ var point:DrawPoint;
+ gfx = skin.gfx;
+
+ // Проверка на нулевую UV-матрицу
+ if (skin.primitive.face.uvMatrixBase == null) {
+ if (stubBitmapData == null) {
+ // Создание текстуры-заглушки
+ stubBitmapData = new BitmapData(2, 2, false, 0);
+ stubBitmapData.setPixel(0, 0, 0xFF00FF);
+ stubBitmapData.setPixel(1, 1, 0xFF00FF);
+ stubMatrix = new Matrix(10, 0, 0, 10, 0, 0);
+ }
+ gfx.beginBitmapFill(stubBitmapData, stubMatrix);
+ if (camera._orthographic) {
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ gfx.moveTo(point.x, point.y);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ var perspective:Number = camera.focalLength/point.z;
+ gfx.moveTo(point.x*perspective, point.y*perspective);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ }
+ return;
+ }
+
+ if (camera._orthographic) {
+ // Расчитываем матрицу наложения текстуры
+ var face:Face = skin.primitive.face;
+ // Если матрица не расчитана, считаем
+ if (!camera.uvMatricesCalculated[face]) {
+ camera.calculateUVMatrix(face, _texture._width, _texture._height);
+ }
+ gfx.beginBitmapFill(_texture._bitmapData, face.uvMatrix, _repeat, _smooth);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ gfx.moveTo(point.x, point.y);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ // Отрисовка
+ focalLength = camera.focalLength;
+ //distortion = camera.focalDistortion*_precision;
+
+ var front:int = 0;
+ var back:int = length - 1;
+
+ var newFront:int = 1;
+ var newBack:int = (back > 0) ? (back - 1) : (length - 1);
+ var direction:Boolean = true;
+
+ var a:DrawPoint = points[back];
+ var b:DrawPoint;
+ var c:DrawPoint = points[front];
+
+ var drawVertices:Vector.flash.display.Graphics#beginBitmapFill().
+ */
+ public function get repeat():Boolean {
+ return _repeat;
+ }
+
+ /**
+ * @private
+ */
+ public function set repeat(value:Boolean):void {
+ if (_repeat != value) {
+ _repeat = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Сглаживание текстуры при увеличении масштаба. Более подробную информацию можно найти в описании метода
+ * flash.display.Graphics#beginBitmapFill().
+ */
+ public function get smooth():Boolean {
+ return _smooth;
+ }
+
+ /**
+ * @private
+ */
+ public function set smooth(value:Boolean):void {
+ if (_smooth != value) {
+ _smooth = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Толщина линии обводки полигона. Если значение отрицательное, то отрисовка линий не выполняется.
+ */
+ public function get wireThickness():Number {
+ return _wireThickness;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireThickness(value:Number):void {
+ if (_wireThickness != value) {
+ _wireThickness = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Цвет линии обводки полигона.
+ */
+ public function get wireColor():uint {
+ return _wireColor;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireColor(value:uint):void {
+ if (_wireColor != value) {
+ _wireColor = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Точность перспективной коррекции.
+ */
+ public function get precision():Number {
+ return _precision;
+ }
+
+ /**
+ * @private
+ */
+ public function set precision(value:Number):void {
+ if (_precision != value) {
+ _precision = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ var res:TextureMaterial = new TextureMaterial(_texture, _alpha, _repeat, _smooth, _blendMode, _wireThickness, _wireColor, _precision);
+ return res;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/materials/TextureMaterialPrecision.as b/Alternativa3D5/5.3/alternativa/engine3d/materials/TextureMaterialPrecision.as
new file mode 100644
index 0000000..f5b2509
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/materials/TextureMaterialPrecision.as
@@ -0,0 +1,37 @@
+package alternativa.engine3d.materials {
+
+ /**
+ * Класс содержит константы точности перспективной коррекции текстурного материала.
+ */
+ public class TextureMaterialPrecision {
+
+ /**
+ * Адаптивная триангуляция не будет выполняться, только простая триангуляция
+ */
+ public static const NONE:Number = -1;
+ /**
+ * Очень низкое качество адаптивной триангуляции
+ */
+ public static const VERY_LOW:Number = 50;
+ /**
+ * Низкое качество адаптивной триангуляции
+ */
+ public static const LOW:Number = 25;
+ /**
+ * Среднее качество адаптивной триангуляции
+ */
+ public static const MEDIUM:Number = 10;
+ /**
+ * Высокое качество адаптивной триангуляции
+ */
+ public static const HIGH:Number = 6;
+ /**
+ * Очень высокое качество адаптивной триангуляции
+ */
+ public static const VERY_HIGH:Number = 3;
+ /**
+ * Адаптивная триангуляция выполняется в максимальном качестве для выбранного расширения.
+ */
+ public static const BEST:Number = 1;
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/materials/WireMaterial.as b/Alternativa3D5/5.3/alternativa/engine3d/materials/WireMaterial.as
new file mode 100644
index 0000000..61bbe19
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/materials/WireMaterial.as
@@ -0,0 +1,132 @@
+package alternativa.engine3d.materials {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.display.Skin;
+
+ import flash.display.BlendMode;
+ import flash.display.Graphics;
+ import alternativa.engine3d.core.PolyPrimitive;
+
+ use namespace alternativa3d;
+
+ /**
+ * Материал для рисования рёбер полигонов.
+ */
+ public class WireMaterial extends SurfaceMaterial {
+ /**
+ * @private
+ * Цвет
+ */
+ alternativa3d var _color:uint;
+ /**
+ * @private
+ * Толщина линий
+ */
+ alternativa3d var _thickness:Number;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param thickness толщина линий
+ * @param color цвет линий
+ * @param alpha прозрачность линий
+ * @param blendMode режим наложения цвета
+ */
+ public function WireMaterial(thickness:Number = 0, color:uint = 0, alpha:Number = 1, blendMode:String = BlendMode.NORMAL) {
+ super(alpha, blendMode);
+ _color = color;
+ _thickness = thickness;
+ }
+
+ /**
+ * @private
+ * @param primitive
+ * @return
+ */
+ override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
+ return _thickness >= 0;
+ }
+
+ /**
+ * @private
+ * @param camera
+ * @param skin
+ * @param length
+ * @param points
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+
+ var i:uint;
+ var point:DrawPoint;
+ var gfx:Graphics = skin.gfx;
+
+ if (camera._orthographic) {
+ gfx.lineStyle(_thickness, _color);
+ point = points[length - 1];
+ gfx.moveTo(point.x, point.y);
+ for (i = 0; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ // Отрисовка
+ gfx.lineStyle(_thickness, _color);
+ point = points[length - 1];
+ var perspective:Number = camera.focalLength/point.z;
+ gfx.moveTo(point.x*perspective, point.y*perspective);
+ for (i = 0; i < length; i++) {
+ point = points[i];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ }
+ }
+
+ /**
+ * Цвет линий.
+ */
+ public function get color():uint {
+ return _color;
+ }
+
+ /**
+ * @private
+ */
+ public function set color(value:uint):void {
+ if (_color != value) {
+ _color = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Толщина линий. Если толщина отрицательная, то отрисовка не происходит.
+ */
+ public function get thickness():Number {
+ return _thickness;
+ }
+
+ /**
+ * @private
+ */
+ public function set thickness(value:Number):void {
+ if (_thickness != value) {
+ _thickness = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ return new WireMaterial(_thickness, _color, _alpha, _blendMode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/physics/Collision.as b/Alternativa3D5/5.3/alternativa/engine3d/physics/Collision.as
new file mode 100644
index 0000000..8ad22a0
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/physics/Collision.as
@@ -0,0 +1,17 @@
+package alternativa.engine3d.physics {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.types.Point3D;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ */
+ public class Collision {
+ public var face:Face;
+ public var normal:Point3D;
+ public var offset:Number;
+ public var point:Point3D;
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/physics/CollisionPlane.as b/Alternativa3D5/5.3/alternativa/engine3d/physics/CollisionPlane.as
new file mode 100644
index 0000000..d0dc0a5
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/physics/CollisionPlane.as
@@ -0,0 +1,44 @@
+package alternativa.engine3d.physics {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.BSPNode;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ */
+ public class CollisionPlane {
+ public var node:BSPNode;
+ public var infront:Boolean;
+ public var sourceOffset:Number;
+ public var destinationOffset:Number;
+
+ // Хранилище неиспользуемых плоскостей
+ static private var collector:Array = new Array();
+
+ // Создать плоскость
+ static alternativa3d function createCollisionPlane(node:BSPNode, infront:Boolean, sourceOffset:Number, destinationOffset:Number):CollisionPlane {
+
+ // Достаём плоскость из коллектора
+ var plane:CollisionPlane = collector.pop();
+ // Если коллектор пуст, создаём новую плоскость
+ if (plane == null) {
+ plane = new CollisionPlane();
+ }
+
+ plane.node = node;
+ plane.infront = infront;
+ plane.sourceOffset = sourceOffset;
+ plane.destinationOffset = destinationOffset;
+
+ return plane;
+ }
+
+ // Удалить плоскость, все ссылки должны быть почищены
+ static alternativa3d function destroyCollisionPlane(plane:CollisionPlane):void {
+ collector.push(plane);
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/physics/SphereCollider.as b/Alternativa3D5/5.3/alternativa/engine3d/physics/SphereCollider.as
new file mode 100644
index 0000000..cef6dc6
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/physics/SphereCollider.as
@@ -0,0 +1,470 @@
+package alternativa.engine3d.physics {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.BSPNode;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.core.Scene3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ * Класс реализует алгоритм определения столкновений сферы с полигонами сцены.
+ */
+ public class SphereCollider {
+
+ // Максимальное кол-во попыток найти свободное от столкновения со сценой направление
+ private static const maxCollisions:uint = 5000;
+
+ /**
+ * @private
+ * Радиус сферы
+ */
+ public var sphereRadius:Number;
+
+ /**
+ * @private
+ * Список объектов, с которыми не будут проверяться столкновения
+ */
+ public var ignoreSet:Set;
+
+ /**
+ * @private
+ * Установка погрешности определения расстояний.
+ */
+ public var offsetThreshold:Number = 0.01;
+
+ private var scene:Scene3D;
+
+ private var collisionSource:Point3D;
+ private var collisionVector:Point3D;
+ private var collisionVelocity:Point3D = new Point3D();
+ private var collisionDestination:Point3D = new Point3D();
+ private var collisionPlanes:Array = new Array();
+ private var collisionPlanePoint:Point3D = new Point3D();
+ private var collisionPrimitive:PolyPrimitive;
+ private var collisionPrimitivePoint:Point3D = new Point3D();
+ private var collisionPrimitiveNearest:PolyPrimitive;
+ private var collisionPrimitiveNearestLengthSqr:Number;
+ private var collisionPoint:Point3D = new Point3D();
+ private var collisionNormal:Point3D = new Point3D();
+ private var collisionOffset:Number;
+ private var coords:Point3D = new Point3D();
+ private var collision:Collision = new Collision();
+
+ /**
+ * @private
+ * Создание экземпляра класса.
+ *
+ * @param scene сцена, в которой определяется столкновение
+ */
+ public function SphereCollider(scene:Scene3D, radius:Number = 0) {
+ if (scene == null) {
+ throw new Error("SphereCollider: scene is null");
+ }
+ this.scene = scene;
+ sphereRadius = radius;
+ }
+
+ /**
+ * @private
+ * Считает конечное положения сферы по направлению вектора перемещения, с учетом столкновений
+ * её с объектами сцены, кроме объектов из списка ignoreSet
+ *
+ * @param source начальное положение сферы
+ * @param velocity вектор перемещения сферы
+ * @param destination результат выполнения метода - конечное положение сферы
+ */
+ public function calculateDestination(source:Point3D, velocity:Point3D, destination:Point3D):void {
+
+ // Сохраняем вектор перемещения во внутренний вектор
+ collisionVelocity.x = velocity.x;
+ collisionVelocity.y = velocity.y;
+ collisionVelocity.z = velocity.z;
+
+ // Расчеты не производятся, если скорость мала
+ if (collisionVelocity.x <= offsetThreshold && collisionVelocity.x >= -offsetThreshold &&
+ collisionVelocity.y <= offsetThreshold && collisionVelocity.y >= -offsetThreshold &&
+ collisionVelocity.z <= offsetThreshold && collisionVelocity.z >= -offsetThreshold) {
+ destination.x = source.x;
+ destination.y = source.y;
+ destination.z = source.z;
+ return;
+ }
+
+ coords.x = source.x;
+ coords.y = source.y;
+ coords.z = source.z;
+
+ destination.x = source.x + collisionVelocity.x;
+ destination.y = source.y + collisionVelocity.y;
+ destination.z = source.z + collisionVelocity.z;
+ var collisions:uint = 0;
+ do {
+ var collision:Collision = calculateCollision(coords, collisionVelocity, this.collision);
+ if (collision != null) {
+ // Вынос точки назначения из-за плоскости столкновения на высоту радиуса сферы над плоскостью по направлению нормали
+ var offset:Number = sphereRadius + offsetThreshold + collision.offset - destination.x*collision.normal.x - destination.y*collision.normal.y - destination.z*collision.normal.z;
+ destination.x += collision.normal.x * offset;
+ destination.y += collision.normal.y * offset;
+ destination.z += collision.normal.z * offset;
+ // Коррекция текущих кординат центра сферы для следующей итерации
+ coords.x = collision.point.x + collision.normal.x * (sphereRadius + offsetThreshold);
+ coords.y = collision.point.y + collision.normal.y * (sphereRadius + offsetThreshold);
+ coords.z = collision.point.z + collision.normal.z * (sphereRadius + offsetThreshold);
+ // Коррекция вектора скорости. Результирующий вектор направлен вдоль плоскости столкновения.
+ collisionVelocity.x = destination.x - coords.x;
+ collisionVelocity.y = destination.y - coords.y;
+ collisionVelocity.z = destination.z - coords.z;
+
+ // Если смещение слишком мало, останавливаемся
+ if (collisionVelocity.x <= offsetThreshold && collisionVelocity.x >= -offsetThreshold &&
+ collisionVelocity.y <= offsetThreshold && collisionVelocity.y >= -offsetThreshold &&
+ collisionVelocity.z <= offsetThreshold && collisionVelocity.z >= -offsetThreshold) {
+ break;
+ }
+ }
+ } while ((collision != null) && (++collisions < maxCollisions));
+
+ // Если после maxCollisions ходов выход не найден, то остаемся на старом месте
+ if (collisions == maxCollisions) {
+ destination.x = source.x;
+ destination.y = source.y;
+ destination.z = source.z;
+ }
+ }
+
+ /**
+ * @private
+ * Определение столкновения сферы с полигонами сцены.
+ *
+ * @param source исходная точка положения сферы в сцене
+ * @param vector вектор перемещения сферы в сцене
+ * @param collision экземпляр класса для возврата в него результата
+ *
+ * @return объект, содержащий параметры столкновения или null в случае отсутствия столкновений
+ */
+ public function calculateCollision(source:Point3D, vector:Point3D, collision:Collision = null):Collision {
+ collisionSource = source;
+ collisionVector = vector;
+ collisionDestination.x = collisionSource.x + collisionVector.x;
+ collisionDestination.y = collisionSource.y + collisionVector.y;
+ collisionDestination.z = collisionSource.z + collisionVector.z;
+
+ // Собираем потенциальные плоскости столкновения
+ collectCollisionPlanes(scene.bsp);
+
+ // Перебираем плоскости по мере удалённости
+ collisionPlanes.sortOn("sourceOffset", Array.NUMERIC | Array.DESCENDING);
+ var plane:CollisionPlane;
+ // Пока не найдём столкновение с примитивом или плоскости не кончатся
+ while ((plane = collisionPlanes.pop()) != null) {
+ if (collisionPrimitive == null) {
+ calculateCollisionWithPlane(plane);
+ }
+ plane.node = null;
+ CollisionPlane.destroyCollisionPlane(plane);
+ }
+
+ if (collisionPrimitive != null) {
+ if (collision == null) {
+ collision = new Collision();
+ }
+ collision.face = collisionPrimitive.face;
+ collision.normal = collisionNormal.clone();
+ collision.point = collisionPoint.clone();
+ collision.offset = collisionOffset;
+ } else {
+ collision = null;
+ }
+
+ collisionSource = null;
+ collisionVector = null;
+ collisionPrimitive = null;
+ return collision;
+ }
+
+ /**
+ * @private
+ * Сбор потенциальных плоскостей столкновения.
+ *
+ * @param node текущий узел BSP-дерева
+ */
+ private function collectCollisionPlanes(node:BSPNode):void {
+ if (node != null) {
+ var sourceOffset:Number = collisionSource.x * node.normal.x + collisionSource.y * node.normal.y + collisionSource.z * node.normal.z - node.offset;
+ var destinationOffset:Number = collisionDestination.x * node.normal.x + collisionDestination.y * node.normal.y + collisionDestination.z * node.normal.z - node.offset;
+ var plane:CollisionPlane;
+
+ if (sourceOffset >= 0) {
+ // Перед нодой
+
+ // Проверяем передние ноды
+ collectCollisionPlanes(node.front);
+
+ if (destinationOffset < sphereRadius) {
+
+ // Нашли пересечение с плоскостью
+ plane = CollisionPlane.createCollisionPlane(node, true, sourceOffset, destinationOffset);
+ collisionPlanes.push(plane);
+
+ // Проверяем задние ноды
+ collectCollisionPlanes(node.back);
+ }
+ } else {
+ // За нодой
+
+ // Проверяем задние ноды
+ collectCollisionPlanes(node.back);
+
+ if (destinationOffset > -sphereRadius) {
+
+ // Если в ноде есть сзади примитивы
+ if (node.backPrimitives != null) {
+ // Нашли пересечение с плоскостью
+ plane = CollisionPlane.createCollisionPlane(node, false, -sourceOffset, -destinationOffset);
+ collisionPlanes.push(plane);
+ }
+
+ // Проверяем передние ноды
+ collectCollisionPlanes(node.front);
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Определение пересечения сферы с примитивами, лежащими в заданной плоскости.
+ *
+ * @param plane плоскость, содержащая примитивы для проверки
+ */
+ private function calculateCollisionWithPlane(plane:CollisionPlane):void {
+ collisionPlanePoint.copy(collisionSource);
+
+ var normal:Point3D = plane.node.normal;
+ // Если сфера врезана в плоскость
+ if (plane.sourceOffset <= sphereRadius) {
+ if (plane.infront) {
+ collisionPlanePoint.x -= normal.x * plane.sourceOffset;
+ collisionPlanePoint.y -= normal.y * plane.sourceOffset;
+ collisionPlanePoint.z -= normal.z * plane.sourceOffset;
+ } else {
+ collisionPlanePoint.x += normal.x * plane.sourceOffset;
+ collisionPlanePoint.y += normal.y * plane.sourceOffset;
+ collisionPlanePoint.z += normal.z * plane.sourceOffset;
+ }
+ } else {
+ // Находим центр сферы во время столкновения с плоскостью
+ var time:Number = (plane.sourceOffset - sphereRadius) / (plane.sourceOffset - plane.destinationOffset);
+ collisionPlanePoint.x = collisionSource.x + collisionVector.x * time;
+ collisionPlanePoint.y = collisionSource.y + collisionVector.y * time;
+ collisionPlanePoint.z = collisionSource.z + collisionVector.z * time;
+
+ // Устанавливаем точку пересечения cферы с плоскостью
+ if (plane.infront) {
+ collisionPlanePoint.x -= normal.x * sphereRadius;
+ collisionPlanePoint.y -= normal.y * sphereRadius;
+ collisionPlanePoint.z -= normal.z * sphereRadius;
+ } else {
+ collisionPlanePoint.x += normal.x * sphereRadius;
+ collisionPlanePoint.y += normal.y * sphereRadius;
+ collisionPlanePoint.z += normal.z * sphereRadius;
+ }
+ }
+
+ // Проверяем примитивы плоскости
+ var primitive:*;
+ collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE;
+ collisionPrimitiveNearest = null;
+ if (plane.infront) {
+ if (plane.node.primitive != null) {
+ if (ignoreSet == null || ignoreSet[plane.node.primitive.face._mesh] == undefined) {
+ calculateCollisionWithPrimitive(plane.node.primitive);
+ }
+ } else {
+ for (primitive in plane.node.frontPrimitives) {
+ if (ignoreSet == null || ignoreSet[primitive.face._mesh] == undefined) {
+ calculateCollisionWithPrimitive(primitive);
+ if (collisionPrimitive != null) break;
+ }
+ }
+ }
+ } else {
+ for (primitive in plane.node.backPrimitives) {
+ if (ignoreSet == null || ignoreSet[primitive.face._mesh] == undefined) {
+ calculateCollisionWithPrimitive(primitive);
+ if (collisionPrimitive != null) break;
+ }
+ }
+ }
+
+ if (collisionPrimitive != null) {
+ // Если точка пересечения попала в примитив
+
+ // Нормаль плоскости при столкновении - нормаль плоскости
+ if (plane.infront) {
+ collisionNormal.x = normal.x;
+ collisionNormal.y = normal.y;
+ collisionNormal.z = normal.z;
+ collisionOffset = plane.node.offset;
+ } else {
+ collisionNormal.x = -normal.x;
+ collisionNormal.y = -normal.y;
+ collisionNormal.z = -normal.z;
+ collisionOffset = -plane.node.offset;
+ }
+
+ // Точка столкновения в точке столкновения с плоскостью
+ collisionPoint.x = collisionPlanePoint.x;
+ collisionPoint.y = collisionPlanePoint.y;
+ collisionPoint.z = collisionPlanePoint.z;
+
+ } else {
+ // Если точка пересечения не попала ни в один примитив, проверяем столкновение с ближайшей
+
+ // Вектор из ближайшей точки в центр сферы
+ var nearestPointToSourceX:Number = collisionSource.x - collisionPrimitivePoint.x;
+ var nearestPointToSourceY:Number = collisionSource.y - collisionPrimitivePoint.y;
+ var nearestPointToSourceZ:Number = collisionSource.z - collisionPrimitivePoint.z;
+
+ // Если движение в сторону точки
+ if (nearestPointToSourceX * collisionVector.x + nearestPointToSourceY * collisionVector.y + nearestPointToSourceZ * collisionVector.z <= 0) {
+
+ // Ищем нормализованный вектор обратного направления
+ var vectorLength:Number = Math.sqrt(collisionVector.x * collisionVector.x + collisionVector.y * collisionVector.y + collisionVector.z * collisionVector.z);
+ var vectorX:Number = -collisionVector.x / vectorLength;
+ var vectorY:Number = -collisionVector.y / vectorLength;
+ var vectorZ:Number = -collisionVector.z / vectorLength;
+
+ // Длина вектора из ближайшей точки в центр сферы
+ var nearestPointToSourceLengthSqr:Number = nearestPointToSourceX * nearestPointToSourceX + nearestPointToSourceY * nearestPointToSourceY + nearestPointToSourceZ * nearestPointToSourceZ;
+
+ // Проекция вектора из ближайшей точки в центр сферы на нормализованный вектор обратного направления
+ var projectionLength:Number = nearestPointToSourceX * vectorX + nearestPointToSourceY * vectorY + nearestPointToSourceZ * vectorZ;
+
+ var projectionInsideSphereLengthSqr:Number = sphereRadius * sphereRadius - nearestPointToSourceLengthSqr + projectionLength * projectionLength;
+
+ if (projectionInsideSphereLengthSqr > 0) {
+ // Находим расстояние из ближайшей точки до сферы
+ var distance:Number = projectionLength - Math.sqrt(projectionInsideSphereLengthSqr);
+
+ if (distance < vectorLength) {
+ // Столкновение сферы с ближайшей точкой произошло
+
+ // Точка столкновения в ближайшей точке
+ collisionPoint.x = collisionPrimitivePoint.x;
+ collisionPoint.y = collisionPrimitivePoint.y;
+ collisionPoint.z = collisionPrimitivePoint.z;
+
+ // Находим нормаль плоскости столкновения
+ var nearestPointToSourceLength:Number = Math.sqrt(nearestPointToSourceLengthSqr);
+ collisionNormal.x = nearestPointToSourceX / nearestPointToSourceLength;
+ collisionNormal.y = nearestPointToSourceY / nearestPointToSourceLength;
+ collisionNormal.z = nearestPointToSourceZ / nearestPointToSourceLength;
+
+ // Смещение плоскости столкновения
+ collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z;
+ collisionPrimitive = collisionPrimitiveNearest;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Определение столкновения с примитивом.
+ *
+ * @param primitive примитив, столкновение с которым проверяется
+ */
+ private function calculateCollisionWithPrimitive(primitive:PolyPrimitive):void {
+
+ var length:uint = primitive.num;
+ var points:Array = primitive.points;
+ var normal:Point3D = primitive.face.globalNormal;
+ var inside:Boolean = true;
+
+ for (var i:uint = 0; i < length; i++) {
+
+ var p1:Point3D = points[i];
+ var p2:Point3D = points[(i < length - 1) ? (i + 1) : 0];
+
+ var edgeX:Number = p2.x - p1.x;
+ var edgeY:Number = p2.y - p1.y;
+ var edgeZ:Number = p2.z - p1.z;
+
+ var vectorX:Number = collisionPlanePoint.x - p1.x;
+ var vectorY:Number = collisionPlanePoint.y - p1.y;
+ var vectorZ:Number = collisionPlanePoint.z - p1.z;
+
+ var crossX:Number = vectorY * edgeZ - vectorZ * edgeY;
+ var crossY:Number = vectorZ * edgeX - vectorX * edgeZ;
+ var crossZ:Number = vectorX * edgeY - vectorY * edgeX;
+
+ if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) {
+ // Точка за пределами полигона
+ inside = false;
+
+ var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ;
+ var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr;
+
+ // Если расстояние до прямой меньше текущего ближайшего
+ if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) {
+
+ // Ищем нормализованный вектор ребра
+ var edgeLength:Number = Math.sqrt(edgeLengthSqr);
+ var edgeNormX:Number = edgeX / edgeLength;
+ var edgeNormY:Number = edgeY / edgeLength;
+ var edgeNormZ:Number = edgeZ / edgeLength;
+
+ // Находим расстояние до точки перпендикуляра вдоль ребра
+ var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ;
+
+ var vectorLengthSqr:Number;
+ if (t < 0) {
+ // Ближайшая точка - первая
+ vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
+ if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
+ collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
+ collisionPrimitivePoint.x = p1.x;
+ collisionPrimitivePoint.y = p1.y;
+ collisionPrimitivePoint.z = p1.z;
+ collisionPrimitiveNearest = primitive;
+ }
+ } else {
+ if (t > edgeLength) {
+ // Ближайшая точка - вторая
+ vectorX = collisionPlanePoint.x - p2.x;
+ vectorY = collisionPlanePoint.y - p2.y;
+ vectorZ = collisionPlanePoint.z - p2.z;
+ vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
+ if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
+ collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
+ collisionPrimitivePoint.x = p2.x;
+ collisionPrimitivePoint.y = p2.y;
+ collisionPrimitivePoint.z = p2.z;
+ collisionPrimitiveNearest = primitive;
+ }
+ } else {
+ // Ближайшая точка на ребре
+ collisionPrimitiveNearestLengthSqr = edgeDistanceSqr;
+ collisionPrimitivePoint.x = p1.x + edgeNormX * t;
+ collisionPrimitivePoint.y = p1.y + edgeNormY * t;
+ collisionPrimitivePoint.z = p1.z + edgeNormZ * t;
+ collisionPrimitiveNearest = primitive;
+ }
+ }
+ }
+ }
+ }
+
+ // Если попали в примитив
+ if (inside) {
+ collisionPrimitive = primitive;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/primitives/Box.as b/Alternativa3D5/5.3/alternativa/engine3d/primitives/Box.as
new file mode 100644
index 0000000..5e730b5
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/primitives/Box.as
@@ -0,0 +1,316 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Прямоугольный параллелепипед.
+ */
+ public class Box extends Mesh {
+
+ /**
+ * Создание нового параллелепипеда.
+ * "front", "back", "left", "right", "top", "bottom"
+ * на каждую из которых может быть установлен свой материал.true, то нормали будут направлены внутрь фигуры.
+ * @param triangulate флаг триангуляции. Если указано значение true, четырехугольники в параллелепипеде будут триангулированы.
+ */
+ public function Box(width:Number = 100, length:Number = 100, height:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false, triangulate:Boolean = false) {
+ super();
+
+ if ((widthSegments == 0) || (lengthSegments == 0) || (heightSegments == 0)) {
+ return;
+ }
+ width = (width < 0)? 0 : width;
+ length = (length < 0)? 0 : length;
+ height = (height < 0)? 0 : height;
+
+ var wh:Number = width/2;
+ var lh:Number = length/2;
+ var hh:Number = height/2;
+ var ws:Number = width/widthSegments;
+ var ls:Number = length/lengthSegments;
+ var hs:Number = height/heightSegments;
+ var x:int;
+ var y:int;
+ var z:int;
+
+ // Создание точек
+ for (x = 0; x <= widthSegments; x++) {
+ for (y = 0; y <= lengthSegments; y++) {
+ for (z = 0; z <= heightSegments; z++) {
+ if (x == 0 || x == widthSegments || y == 0 || y == lengthSegments || z == 0 || z == heightSegments) {
+ createVertex(x*ws - wh, y*ls - lh, z*hs - hh, x + "_" + y + "_" + z);
+ }
+ }
+ }
+ }
+
+ // Создание поверхностей
+ var front:Surface = createSurface(null, "front");
+ var back:Surface = createSurface(null, "back");
+ var left:Surface = createSurface(null, "left");
+ var right:Surface = createSurface(null, "right");
+ var top:Surface = createSurface(null, "top");
+ var bottom:Surface = createSurface(null, "bottom");
+
+ // Создание граней
+ var wd:Number = 1/widthSegments;
+ var ld:Number = 1/lengthSegments;
+ var hd:Number = 1/heightSegments;
+ var faceId:String;
+
+ // Для оптимизаций UV при триангуляции
+ var aUV:Point;
+ var cUV:Point;
+
+ // Построение верхней грани
+ for (y = 0; y < lengthSegments; y++) {
+ for (x = 0; x < widthSegments; x++) {
+ faceId = "top_"+x+"_"+y;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point(x*wd, (lengthSegments - y)*ld);
+ cUV = new Point((x + 1)*wd, (lengthSegments - y - 1)*ld);
+ createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":1");
+ setUVsToFace(aUV, new Point(x*wd, (lengthSegments - y - 1)*ld), cUV, faceId + ":1");
+ createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":0");
+ setUVsToFace(cUV, new Point((x + 1)*wd, (lengthSegments - y)*ld), aUV, faceId + ":0");
+ } else {
+ createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments], faceId);
+ setUVsToFace(new Point(x*wd, (lengthSegments - y)*ld), new Point(x*wd, (lengthSegments - y - 1)*ld), new Point((x + 1)*wd, (lengthSegments - y - 1)*ld), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point(x*wd, y*ld);
+ cUV = new Point((x + 1)*wd, (y + 1)*ld);
+ createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":0");
+ setUVsToFace(aUV, new Point((x + 1)*wd, y*ld), cUV, faceId + ":0");
+ createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":1");
+ setUVsToFace(cUV, new Point(x*wd, (y + 1)*ld), aUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments], faceId);
+ setUVsToFace(new Point(x*wd, y*ld), new Point((x + 1)*wd, y*ld), new Point((x + 1)*wd, (y + 1)*ld), faceId);
+ }
+ }
+ if (triangulate) {
+ top.addFace(faceId + ":0");
+ top.addFace(faceId + ":1");
+ } else {
+ top.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение нижней грани
+ for (y = 0; y < lengthSegments; y++) {
+ for (x = 0; x < widthSegments; x++) {
+ faceId = "bottom_" + x + "_" + y;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point((widthSegments - x)*wd, (lengthSegments - y)*ld);
+ cUV = new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld);
+ createFace([x + "_" + y + "_" + 0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":0");
+ setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), cUV, faceId + ":0");
+ createFace([(x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0, x + "_" + y + "_" + 0], faceId + ":1");
+ setUVsToFace(cUV, new Point((widthSegments - x)*wd, (lengthSegments - y - 1)*ld), aUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + y + "_"+0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0], faceId);
+ setUVsToFace(new Point((widthSegments - x)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point((widthSegments - x)*wd, y*ld);
+ cUV = new Point((widthSegments - x - 1)*wd, (y + 1)*ld);
+ createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":1");
+ setUVsToFace(aUV, new Point((widthSegments - x)*wd, (y + 1)*ld), cUV, faceId + ":1");
+ createFace([(x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0, x + "_" + y + "_" + 0], faceId + ":0");
+ setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, y*ld), aUV, faceId + ":0");
+ } else {
+ createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) +"_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0], faceId);
+ setUVsToFace(new Point((widthSegments - x)*wd, y*ld), new Point((widthSegments - x)*wd, (y + 1)*ld), new Point((widthSegments - x - 1)*wd, (y + 1)*ld), faceId);
+ }
+ }
+ if (triangulate) {
+ bottom.addFace(faceId + ":0");
+ bottom.addFace(faceId + ":1");
+ } else {
+ bottom.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение фронтальной грани
+ for (z = 0; z < heightSegments; z++) {
+ for (x = 0; x < widthSegments; x++) {
+ faceId = "front_"+x+"_"+z;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point((widthSegments - x)*wd, z*hd);
+ cUV = new Point((widthSegments - x - 1)*wd, (z + 1)*hd);
+ createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":1");
+ setUVsToFace(aUV, new Point((widthSegments - x)*wd, (z + 1)*hd), cUV, faceId + ":1");
+ createFace([(x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z, x + "_" + 0 + "_" + z], faceId + ":0");
+ setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, z*hd), aUV, faceId + ":0");
+ } else {
+ createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z], faceId);
+ setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point(x*wd, z*hd);
+ cUV = new Point((x + 1)*wd, (z + 1)*hd);
+ createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":0");
+ setUVsToFace(aUV, new Point((x + 1)*wd, z*hd), cUV, faceId + ":0");
+ createFace([(x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + z], faceId + ":1");
+ setUVsToFace(cUV, new Point(x*wd, (z + 1)*hd), aUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1)], faceId);
+ setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId);
+ }
+ }
+ if (triangulate) {
+ front.addFace(faceId + ":0");
+ front.addFace(faceId + ":1");
+ } else {
+ front.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение задней грани
+ for (z = 0; z < heightSegments; z++) {
+ for (x = 0; x < widthSegments; x++) {
+ faceId = "back_"+x+"_"+z;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point(x * wd, (z + 1) * hd);
+ cUV = new Point((x + 1) * wd, z * hd);
+ createFace([x + "_" + lengthSegments+"_" + (z + 1), x + "_"+lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0");
+ setUVsToFace(aUV, new Point(x * wd, z * hd), cUV, faceId + ":0");
+ createFace([(x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId + ":1");
+ setUVsToFace(cUV, new Point((x + 1) * wd, (z + 1) * hd), aUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId);
+ setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point((widthSegments - x)*wd, (z + 1)*hd);
+ cUV = new Point((widthSegments - x - 1)*wd, z*hd);
+ createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0");
+ setUVsToFace(new Point((widthSegments - x)*wd, z*hd), aUV, cUV, faceId + ":0");
+ createFace([x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":1");
+ setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (z + 1)*hd), cUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId);
+ setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId);
+ }
+ }
+ if (triangulate) {
+ back.addFace(faceId + ":0");
+ back.addFace(faceId + ":1");
+ } else {
+ back.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение левой грани
+ for (y = 0; y < lengthSegments; y++) {
+ for (z = 0; z < heightSegments; z++) {
+ faceId = "left_" + y + "_" + z;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point(y*ld, (z + 1)*hd);
+ cUV = new Point((y + 1)*ld, z*hd);
+ createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z], faceId + ":0");
+ setUVsToFace(aUV, new Point(y*ld, z*hd), cUV, faceId + ":0");
+ createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId + ":1");
+ setUVsToFace(cUV, new Point((y + 1)*ld, (z + 1)*hd), aUV, faceId + ":1");
+ } else {
+ createFace([0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId);
+ setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point((lengthSegments - y - 1)*ld, z*hd);
+ cUV = new Point((lengthSegments - y)*ld, (z + 1)*hd);
+ createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1)], faceId + ":0");
+ setUVsToFace(aUV, new Point((lengthSegments - y)*ld, z*hd), cUV, faceId + ":0");
+ createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + (y + 1) + "_" + z], faceId + ":1");
+ setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), aUV, faceId + ":1");
+ } else {
+ createFace([0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + z], faceId);
+ setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId);
+ }
+ }
+ if (triangulate) {
+ left.addFace(faceId + ":0");
+ left.addFace(faceId + ":1");
+ } else {
+ left.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение правой грани
+ for (y = 0; y < lengthSegments; y++) {
+ for (z = 0; z < heightSegments; z++) {
+ faceId = "right_" + y + "_" + z;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point((lengthSegments - y)*ld, z*hd);
+ cUV = new Point((lengthSegments - y - 1)*ld, (z + 1)*hd);
+ createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":1");
+ setUVsToFace(aUV, new Point((lengthSegments - y)*ld, (z + 1)*hd), cUV, faceId + ":1");
+ createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + y + "_" + z], faceId + ":0");
+ setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, z*hd), aUV, faceId + ":0");
+ } else {
+ createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z], faceId);
+ setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point(y*ld, z*hd);
+ cUV = new Point((y + 1)*ld, (z + 1)*hd);
+ createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":0");
+ setUVsToFace(aUV, new Point((y + 1)*ld, z*hd), cUV, faceId + ":0");
+ createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + y + "_" + z], faceId + ":1");
+ setUVsToFace(cUV, new Point(y*ld, (z + 1)*hd), aUV, faceId + ":1");
+ } else {
+ createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1)], faceId);
+ setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId);
+ }
+ }
+ if (triangulate) {
+ right.addFace(faceId + ":0");
+ right.addFace(faceId + ":1");
+ } else {
+ right.addFace(faceId);
+ }
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Box(0, 0, 0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/primitives/Cone.as b/Alternativa3D5/5.3/alternativa/engine3d/primitives/Cone.as
new file mode 100644
index 0000000..f98c012
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/primitives/Cone.as
@@ -0,0 +1,264 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.utils.MathUtils;
+ import alternativa.engine3d.core.Face;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Усеченный конус или цилиндр.
+ */
+ public class Cone extends Mesh {
+
+ /**
+ * Создает примитив усеченный конус или цилиндр.
+ * topRadius = 0 или bottomRadius = 0 будет построен конус. При установленном triangulate установлен в false и на примитив не может быть наложена текстура.
+ * Только при установленном параметре triangulate в true это возможно."side".
+ * При установленном параметре bottomRadius не равном нулю в примитиве создается поверхность "bottom",
+ * при установленном параметре topRadius в примитиве создается поверхность "top".
+ * На каждую из поверхностей может быть наложен свой материалtrue нормли будут направлены внутрь примитива.
+ * @param triangulate флаг триангуляции. При значении true все четырехугольные грани примитива будут триангулированы
+ * и появится возможность наложить на примитив текстуру.
+ */
+ public function Cone(height:Number = 100, bottomRadius:Number = 100, topRadius:Number = 0, heightSegments:uint = 1, radialSegments:uint = 12, reverse:Boolean = false, triangulate:Boolean = false) {
+
+ if ((radialSegments < 3) || (heightSegments < 1) || (heightSegments == 1 && topRadius == 0 && bottomRadius == 0)) {
+ return;
+ }
+ height = (height < 0)? 0 : height;
+ bottomRadius = (bottomRadius < 0)? 0 : bottomRadius;
+ topRadius = (topRadius < 0)? 0 : topRadius;
+
+ const radialSegment:Number = MathUtils.DEG360/radialSegments;
+ const radiusSegment:Number = (bottomRadius - topRadius)/heightSegments;
+ const heightSegment:Number = height/heightSegments;
+ const halfHeight:Number = height*0.5
+ const uSegment:Number = 1/radialSegments;
+ const vSegment:Number = 1/heightSegments;
+
+ // Создание вершин
+ if (topRadius == 0 || triangulate) {
+ var poleUp:Vertex = createVertex(0, 0, halfHeight, "poleUp");
+ }
+ if (bottomRadius == 0 || triangulate) {
+ var poleDown:Vertex = createVertex(0, 0, -halfHeight, "poleDown");
+ }
+
+ var radial:uint;
+ var segment:uint;
+
+ var topSegment:uint = heightSegments - int(topRadius == 0);
+ var bottomSegment:uint = int(bottomRadius == 0) ;
+ for (segment = bottomSegment; segment <= topSegment; segment++) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ var currentAngle:Number = radialSegment*radial;
+ var currentRadius:Number = bottomRadius - (radiusSegment*segment);
+ createVertex(Math.cos(currentAngle)*currentRadius, Math.sin(currentAngle)*currentRadius, heightSegment*segment - halfHeight, radial + "_" + segment);
+ }
+ }
+
+ // Создание граней и поверхности
+ var face:Face;
+
+ var points:Array;
+
+ var side:Surface = createSurface(null, "side");
+
+ if (topRadius == 0) {
+ // Создание граней у верхнего полюса
+ var prevRadial:uint = radialSegments - 1;
+ var centerUV:Point = new Point(0.5, 1);
+ var v:Number = topSegment*vSegment;
+ if (reverse) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], prevRadial + "_" + topSegment);
+ if (triangulate) {
+ setUVsToFace(centerUV, new Point(1 - (prevRadial + 1)*uSegment, v) , new Point(1 - prevRadial*uSegment, v), face);
+ }
+ side.addFace(face);
+ prevRadial = radial;
+ }
+ } else {
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], prevRadial + "_" + topSegment);
+ if (triangulate) {
+ setUVsToFace(centerUV, new Point(prevRadial*uSegment, v), new Point((prevRadial + 1)*uSegment, v), face);
+ }
+ side.addFace(face);
+ prevRadial = radial;
+ }
+ }
+ } else {
+ // Создание граней верхней крышки
+ var top:Surface = createSurface(null, "top");
+ if (triangulate) {
+ prevRadial = radialSegments - 1;
+ centerUV = new Point(0.5, 0.5);
+ var UV:Point;
+ var prevUV:Point;
+ if (reverse) {
+ prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5);
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], "top_" + prevRadial);
+ currentAngle = radial * radialSegment;
+ UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5);
+ setUVsToFace(centerUV, UV, prevUV, face);
+ top.addFace(face);
+ prevUV = UV;
+ prevRadial = radial;
+ }
+ } else {
+ prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5);
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], "top_" + prevRadial);
+ currentAngle = radial*radialSegment;
+ UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5);
+ setUVsToFace(centerUV, prevUV, UV, face);
+ top.addFace(face);
+ prevUV = UV;
+ prevRadial = radial;
+ }
+ }
+ } else {
+ points = new Array();
+ if (reverse) {
+ for (radial = (radialSegments - 1); radial < uint(-1); radial--) {
+ points.push(radial + "_" + topSegment);
+ }
+ } else {
+ for (radial = 0; radial < radialSegments; radial++) {
+ points.push(radial + "_" + topSegment);
+ }
+ }
+ top.addFace(createFace(points, "top"));
+ }
+ }
+ // Создание боковых граней
+ var face2:Face;
+ var aUV:Point;
+ var cUV:Point;
+ for (segment = bottomSegment; segment < topSegment; segment++) {
+ prevRadial = radialSegments - 1;
+ v = segment * vSegment;
+ for (radial = 0; radial < radialSegments; radial++) {
+ if (triangulate) {
+ if (reverse) {
+ face = createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":0");
+ face2 = createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1");
+ aUV = new Point(1 - (prevRadial + 1)*uSegment, v + vSegment)
+ cUV = new Point(1 - prevRadial*uSegment, v);
+ setUVsToFace(aUV, new Point(1 - (prevRadial + 1)*uSegment, v), cUV, face);
+ setUVsToFace(cUV, new Point(1 - prevRadial*uSegment, v + vSegment), aUV, face2);
+ } else {
+ face = createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0");
+ face2 = createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":1");
+ aUV = new Point(prevRadial*uSegment, v)
+ cUV = new Point((prevRadial + 1)*uSegment, v + vSegment);
+ setUVsToFace(aUV, new Point((prevRadial + 1)*uSegment, v), cUV, face);
+ setUVsToFace(cUV, new Point(prevRadial*uSegment, v + vSegment), aUV, face2);
+ }
+ side.addFace(face);
+ side.addFace(face2);
+ } else {
+ if (reverse) {
+ side.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment));
+ } else {
+ side.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1)], prevRadial + "_" + segment));
+ }
+ }
+ prevRadial = radial;
+ }
+ }
+
+ if (bottomRadius == 0) {
+ // Создание граней у нижнего полюса
+ prevRadial = radialSegments - 1;
+ centerUV = new Point(0.5, 0);
+ v = bottomSegment*vSegment;
+ if (reverse) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], prevRadial + "_0");
+ if (triangulate) {
+ setUVsToFace(centerUV, new Point(1 - prevRadial*uSegment, v), new Point(1 - (prevRadial + 1)*uSegment, v), face);
+ }
+ side.addFace(face);
+ prevRadial = radial;
+ }
+ } else {
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], prevRadial + "_0");
+ if (triangulate) {
+ setUVsToFace(centerUV, new Point((prevRadial + 1)*uSegment, v), new Point(prevRadial*uSegment, v), face);
+ }
+ side.addFace(face);
+ prevRadial = radial;
+ }
+ }
+ } else {
+ // Создание граней нижней крышки
+ var bottom:Surface = createSurface(null, "bottom");
+ if (triangulate) {
+ prevRadial = radialSegments - 1;
+ centerUV = new Point(0.5, 0.5);
+ if (reverse) {
+ prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5);
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], "bottom_" + prevRadial);
+ currentAngle = radial*radialSegment;
+ UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5);
+ setUVsToFace(centerUV, prevUV, UV, face);
+ bottom.addFace(face);
+ prevUV = UV;
+ prevRadial = radial;
+ }
+ } else {
+ prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5);
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], "bottom_" + prevRadial);
+ currentAngle = radial * radialSegment;
+ UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5);
+ setUVsToFace(centerUV, UV, prevUV, face);
+ bottom.addFace(face);
+ prevUV = UV;
+ prevRadial = radial;
+ }
+ }
+ } else {
+ points = new Array();
+ if (reverse) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ points.push(radial + "_" + bottomSegment);
+ }
+ } else {
+ for (radial = (radialSegments - 1); radial < uint(-1); radial--) {
+ points.push(radial + "_" + bottomSegment);
+ }
+ }
+ bottom.addFace(createFace(points, "bottom"));
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Cone(0, 0, 0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/primitives/GeoPlane.as b/Alternativa3D5/5.3/alternativa/engine3d/primitives/GeoPlane.as
new file mode 100644
index 0000000..ea681b4
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/primitives/GeoPlane.as
@@ -0,0 +1,197 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Геоплоскость.
+ */
+ public class GeoPlane extends Mesh {
+
+ /**
+ * Создает геоплоскость.
+ * reverse установленном в true примитив будет содержать грань - "back".
+ * При значении reverse установленном в false примитив будет содержать грань - "front".
+ * Параметр twoSided указывает методу создать обе поверхности.true, то создаётся двусторонняя поверхность
+ * @param reverse флаг инвертирования нормалей
+ */
+ public function GeoPlane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false) {
+ super();
+
+ if ((widthSegments == 0) || (lengthSegments == 0)) {
+ return;
+ }
+ width = (width < 0)? 0 : width;
+ length = (length < 0)? 0 : length;
+
+ // Середина
+ var wh:Number = width/2;
+ var hh:Number = length/2;
+ // Размеры сегмента
+ var ws:Number = width/widthSegments;
+ var hs:Number = length/lengthSegments;
+
+ // Размеры UV-сегмента
+ var us:Number = 1/widthSegments;
+ var vs:Number = 1/lengthSegments;
+
+ // Создание точек
+ var x:uint;
+ var y:uint;
+ var frontUV:Array = new Array();
+ var backUV:Array = ((lengthSegments & 1) == 0) ? null : new Array();
+ for (y = 0; y <= lengthSegments; y++) {
+ frontUV[y] = new Array();
+ if (backUV != null) {
+ backUV[y] = new Array();
+ }
+ for (x = 0; x <= widthSegments; x++) {
+ if ((y & 1) == 0) {
+ // Если чётный ряд
+ createVertex(x*ws - wh, y*hs - hh, 0, y + "_" + x);
+ frontUV[y][x] = new Point(x*us, y*vs);
+ if (backUV != null) {
+ backUV[y][x] = new Point(x*us, 1 - y*vs);
+ }
+ } else {
+ // Если нечётный ряд
+ if (x == 0) {
+ // Первая точка
+ createVertex(-wh, y*hs - hh, 0, y + "_" + x);
+ frontUV[y][x] = new Point(0, y*vs);
+ if (backUV != null) {
+ backUV[y][x] = new Point(0, 1 - y*vs);
+ }
+ } else {
+ createVertex(x*ws - wh - ws/2, y*hs - hh, 0, y + "_" + x);
+ frontUV[y][x] = new Point((x - 0.5)*us, y*vs);
+ if (backUV != null) {
+ backUV[y][x] = new Point((x - 0.5)*us, 1 - y*vs);
+ }
+ if (x == widthSegments) {
+ // Последняя точка
+ createVertex(wh, y*hs - hh, 0, y + "_" + (x + 1));
+ frontUV[y][x + 1] = new Point(1, y*vs);
+ if (backUV != null) {
+ backUV[y][x + 1] = new Point(1, 1 - y*vs);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Создание поверхностей
+ var front:Surface;
+ var back:Surface;
+
+ if (twoSided || !reverse) {
+ front = createSurface(null, "front");
+ }
+ if (twoSided || reverse) {
+ back = createSurface(null, "back");
+ }
+
+ // Создание полигонов
+ var face:Face;
+ for (y = 0; y < lengthSegments; y++) {
+ for (var n:uint = 0; n <= (widthSegments << 1); n++) {
+ x = n >> 1;
+ if ((y & 1) == 0) {
+ // Если чётный ряд
+ if ((n & 1) == 0) {
+ // Если остриём вверх
+ if (twoSided || !reverse) {
+ face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]);
+ setUVsToFace(frontUV[y][x], frontUV[y + 1][x + 1], frontUV[y + 1][x], face);
+ front.addFace(face);
+ }
+ if (twoSided || reverse) {
+ face = createFace([y + "_" + x, (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]);
+ if (backUV != null) {
+ setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y + 1][x + 1], face);
+ } else {
+ setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face);
+ }
+ back.addFace(face);
+ }
+ } else {
+ // Если остриём вниз
+ if (twoSided || !reverse) {
+ face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + (x + 1)]);
+ setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x + 1], face);
+ front.addFace(face);
+ }
+ if (twoSided || reverse) {
+ face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), y + "_" + (x + 1)]);
+ if (backUV != null) {
+ setUVsToFace(backUV[y][x], backUV[y + 1][x + 1], backUV[y][x + 1], face);
+ } else {
+ setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x + 1], frontUV[lengthSegments - y][x + 1], face);
+ }
+ back.addFace(face);
+ }
+ }
+ } else {
+ // Если нечётный ряд
+ if ((n & 1) == 0) {
+ // Если остриём вниз
+ if (twoSided || !reverse) {
+ face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + x]);
+ setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x], face);
+ front.addFace(face);
+ }
+ if (twoSided || reverse) {
+ face = createFace([y + "_" + x, (y + 1) + "_" + x, y + "_" + (x + 1)]);
+ if (backUV != null) {
+ setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y][x + 1], face);
+ } else {
+ setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y][x + 1], face);
+ }
+ back.addFace(face);
+ }
+ } else {
+ // Если остриём вверх
+ if (twoSided || !reverse) {
+ face = createFace([y + "_" + (x + 1), (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]);
+ setUVsToFace(frontUV[y][x+1], frontUV[y + 1][x + 1], frontUV[y + 1][x], face);
+ front.addFace(face);
+ }
+ if (twoSided || reverse) {
+ face = createFace([y + "_" + (x + 1), (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]);
+ if (backUV != null) {
+ setUVsToFace(backUV[y][x + 1], backUV[y + 1][x], backUV[y + 1][x + 1], face);
+ } else {
+ setUVsToFace(frontUV[lengthSegments - y][x + 1], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face);
+ }
+ back.addFace(face);
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new GeoPlane(0, 0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/primitives/GeoSphere.as b/Alternativa3D5/5.3/alternativa/engine3d/primitives/GeoSphere.as
new file mode 100644
index 0000000..7ba3bf2
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/primitives/GeoSphere.as
@@ -0,0 +1,321 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.utils.MathUtils;
+ import alternativa.engine3d.core.Face;
+ import flash.geom.Point;
+ import alternativa.types.Point3D;
+
+ use namespace alternativa3d;
+
+ /**
+ * Геосфера.
+ */
+ public class GeoSphere extends Mesh {
+
+ /**
+ * Создает геосферу.
+ * [0, 1],
+ * поэтому для материала с текстурой необходимо устанавливать флаг repeat.
+ *
+ * @param radius радиус геосферы. Не может быть меньше нуля.
+ * @param segments количество сегментов геосферы
+ * @param reverse флаг направления нормалей. При значении true нормали направлены внуть геосферы.
+ */
+ public function GeoSphere(radius:Number = 100, segments:uint = 2, reverse:Boolean = false) {
+ if (segments == 0) {
+ return;
+ }
+ radius = (radius < 0)? 0 : radius;
+
+ const sections:uint = 20;
+
+ //var nfaces:uint = sections*segments*segments;
+ //var nverts:Number = nfaces/2 + 2;
+ var points:Array = new Array();
+
+ var i:uint;
+ var f:uint;
+
+ var theta:Number;
+ var sin:Number;
+ var cos:Number;
+ // z расстояние до нижней и верхней крышки полюса
+ var subz:Number = 4.472136E-001*radius;
+ // радиус на расстоянии subz
+ var subrad:Number = 2*subz;
+ points.push(createVertex(0, 0, radius, "poleUp"));
+ // Создание вершин верхней крышки
+ for (i = 0; i < 5; i++) {
+ theta = MathUtils.DEG360*i/5;
+ sin = Math.sin(theta);
+ cos = Math.cos(theta);
+ points.push(createVertex(subrad*cos, subrad*sin, subz));
+ }
+ // Создание вершин нижней крышки
+ for (i = 0; i < 5; i++) {
+ theta = MathUtils.DEG180*((i << 1) + 1)/5;
+ sin = Math.sin(theta);
+ cos = Math.cos(theta);
+ points.push(createVertex(subrad*cos, subrad*sin, -subz));
+ }
+ points.push(createVertex(0, 0, -radius, "poleDown"));
+
+ for (i = 1; i < 6; i++) {
+ interpolate(0, i, segments, points);
+ }
+ for (i = 1; i < 6; i++) {
+ interpolate(i, i % 5 + 1, segments, points);
+ }
+ for (i = 1; i < 6; i++) {
+ interpolate(i, i + 5, segments, points);
+ }
+ for (i = 1; i < 6; i++) {
+ interpolate(i, (i + 3) % 5 + 6, segments, points);
+ }
+ for (i = 1; i < 6; i++) {
+ interpolate(i + 5, i % 5 + 6, segments, points);
+ }
+ for (i = 6; i < 11; i++) {
+ interpolate(11, i, segments, points);
+ }
+ for (f = 0; f < 5; f++) {
+ for (i = 1; i <= segments - 2; i++) {
+ interpolate(12 + f*(segments - 1) + i, 12 + (f + 1) % 5*(segments - 1) + i, i + 1, points);
+ }
+ }
+ for (f = 0; f < 5; f++) {
+ for (i = 1; i <= segments - 2; i++) {
+ interpolate(12 + (f + 15)*(segments - 1) + i, 12 + (f + 10)*(segments - 1) + i, i + 1, points);
+ }
+ }
+ for (f = 0; f < 5; f++) {
+ for (i = 1; i <= segments - 2; i++) {
+ interpolate(12 + ((f + 1) % 5 + 15)*(segments - 1) + segments - 2 - i, 12 + (f + 10)*(segments - 1) + segments - 2 - i, i + 1, points);
+ }
+ }
+ for (f = 0; f < 5; f++) {
+ for (i = 1; i <= segments - 2; i++) {
+ interpolate(12 + ((f + 1) % 5 + 25)*(segments - 1) + i, 12 + (f + 25)*(segments - 1) + i, i + 1, points);
+ }
+ }
+ // Создание граней
+ var face:Face;
+ var surface:Surface = createSurface();
+ for (f = 0; f < sections; f++) {
+ for (var row:uint = 0; row < segments; row++) {
+ for (var column:uint = 0; column <= row; column++) {
+ var a:uint = findVertices(segments, f, row, column);
+ var b:uint = findVertices(segments, f, row + 1, column);
+ var c:uint = findVertices(segments, f, row + 1, column + 1);
+ var va:Vertex = points[a];
+ var vb:Vertex = points[b];
+ var vc:Vertex = points[c];
+ var aUV:Point;
+ var bUV:Point;
+ var cUV:Point;
+ var coordA:Point3D = va._coords;
+ var coordB:Point3D = vb._coords;
+ var coordC:Point3D = vc._coords;
+
+ if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) {
+ aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) {
+ bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) {
+ cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ // полюс
+ if (a == 0 || a == 11) {
+ aUV.x = bUV.x + (cUV.x - bUV.x)*0.5;
+ }
+ if (b == 0 || b == 11) {
+ bUV.x = aUV.x + (cUV.x - aUV.x)*0.5;
+ }
+ if (c == 0 || c == 11) {
+ cUV.x = aUV.x + (bUV.x - aUV.x)*0.5;
+ }
+
+ if (reverse) {
+ face = createFace([va, vc, vb], (column << 1) + "_" + row + "_" + f);
+ aUV.x = 1 - aUV.x;
+ bUV.x = 1 - bUV.x;
+ cUV.x = 1 - cUV.x;
+ setUVsToFace(aUV, cUV, bUV, face);
+ } else {
+ face = createFace([va, vb, vc], (column << 1) + "_" + row + "_" + f);
+ setUVsToFace(aUV, bUV, cUV, face);
+ }
+ surface.addFace(face);
+ //trace(a + "_" + b + "_" + c);
+ if (column < row) {
+ b = findVertices(segments, f, row, column + 1);
+ var vd:Vertex = points[b];
+ coordB = vd._coords;
+
+ if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) {
+ aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) {
+ bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) {
+ cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (a == 0 || a == 11) {
+ aUV.x = bUV.x + (cUV.x - bUV.x)*0.5;
+ }
+ if (b == 0 || b == 11) {
+ bUV.x = aUV.x + (cUV.x - aUV.x)*0.5;
+ }
+ if (c == 0 || c == 11) {
+ cUV.x = aUV.x + (bUV.x - aUV.x)*0.5;
+ }
+
+ if (reverse) {
+ face = createFace([va, vd, vc], ((column << 1) + 1) + "_" + row + "_" + f);
+ aUV.x = 1 - aUV.x;
+ bUV.x = 1 - bUV.x;
+ cUV.x = 1 - cUV.x;
+ setUVsToFace(aUV, bUV, cUV, face);
+ } else {
+ face = createFace([va, vc, vd], ((column << 1) + 1) + "_" + row + "_" + f);
+ setUVsToFace(aUV, cUV, bUV, face);
+ }
+ surface.addFace(face);
+ }
+ }
+ }
+ }
+ }
+
+/* private function getUVSpherical(point:Point3D, radius:Number = 0, reverse:Boolean = false):Point {
+ if (radius == 0) {
+ radius = point.length;
+ }
+ if (reverse) {
+ var u:Number = 0.5 - Math.atan2(point.y, point.x)/MathUtils.DEG360;
+ } else {
+ u = Math.atan2(point.y, point.x)/MathUtils.DEG360 + 0.5;
+ }
+ return new Point(u, Math.asin(point.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ */
+ private function interpolate(v1:uint, v2:uint, num:uint, points:Array):void {
+ if (num < 2) {
+ return;
+ }
+ var a:Vertex = Vertex(points[v1]);
+ var b:Vertex = Vertex(points[v2]);
+ var cos:Number = (a.x*b.x + a.y*b.y + a.z*b.z)/(a.x*a.x + a.y*a.y + a.z*a.z);
+ cos = (cos < -1) ? -1 : ((cos > 1) ? 1 : cos);
+ var theta:Number = Math.acos(cos);
+ var sin:Number = Math.sin(theta);
+ for (var e:uint = 1; e < num; e++) {
+ var theta1:Number = theta*e/num;
+ var theta2:Number = theta*(num - e)/num;
+ var st1:Number = Math.sin(theta1);
+ var st2:Number = Math.sin(theta2);
+ points.push(createVertex((a.x*st2 + b.x*st1)/sin, (a.y*st2 + b.y*st1)/sin, (a.z*st2 + b.z*st1)/sin));
+ }
+ }
+
+ private function findVertices(segments:uint, section:uint, row:uint, column:uint):uint {
+ if (row == 0) {
+ if (section < 5) {
+ return (0);
+ }
+ if (section > 14) {
+ return (11);
+ }
+ return (section - 4);
+ }
+ if (row == segments && column == 0) {
+ if (section < 5) {
+ return (section + 1);
+ }
+ if (section < 10) {
+ return ((section + 4) % 5 + 6);
+ }
+ if (section < 15) {
+ return ((section + 1) % 5 + 1);
+ }
+ return ((section + 1) % 5 + 6);
+ }
+ if (row == segments && column == segments) {
+ if (section < 5) {
+ return ((section + 1) % 5 + 1);
+ }
+ if (section < 10) {
+ return (section + 1);
+ }
+ if (section < 15) {
+ return (section - 9);
+ }
+ return (section - 9);
+ }
+ if (row == segments) {
+ if (section < 5) {
+ return (12 + (5 + section)*(segments - 1) + column - 1);
+ }
+ if (section < 10) {
+ return (12 + (20 + (section + 4) % 5)*(segments - 1) + column - 1);
+ }
+ if (section < 15) {
+ return (12 + (section - 5)*(segments - 1) + segments - 1 - column);
+ }
+ return (12 + (5 + section)*(segments - 1) + segments - 1 - column);
+ }
+ if (column == 0) {
+ if (section < 5) {
+ return (12 + section*(segments - 1) + row - 1);
+ }
+ if (section < 10) {
+ return (12 + (section % 5 + 15)*(segments - 1) + row - 1);
+ }
+ if (section < 15) {
+ return (12 + ((section + 1) % 5 + 15)*(segments - 1) + segments - 1 - row);
+ }
+ return (12 + ((section + 1) % 5 + 25)*(segments - 1) + row - 1);
+ }
+ if (column == row) {
+ if (section < 5) {
+ return (12 + (section + 1) % 5*(segments - 1) + row - 1);
+ }
+ if (section < 10) {
+ return (12 + (section % 5 + 10)*(segments - 1) + row - 1);
+ }
+ if (section < 15) {
+ return (12 + (section % 5 + 10)*(segments - 1) + segments - row - 1);
+ }
+ return (12 + (section % 5 + 25)*(segments - 1) + row - 1);
+ }
+ return (12 + 30*(segments - 1) + section*(segments - 1)*(segments - 2)/2 + (row - 1)*(row - 2)/2 + column - 1);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new GeoSphere(0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/primitives/Plane.as b/Alternativa3D5/5.3/alternativa/engine3d/primitives/Plane.as
new file mode 100644
index 0000000..d346048
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/primitives/Plane.as
@@ -0,0 +1,117 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Плоскость.
+ */
+ public class Plane extends Mesh {
+
+ /**
+ * Создает плоскость.
+ * reverse установленном в true примитив будет содержать грань - "back".
+ * При значении reverse установленном в false примитив будет содержать грань - "front".
+ * Параметр twoSided указывает методу создать обе поверхности.true, то формируется двусторонняя плоскость
+ * @param reverse инвертирование нормалей
+ * @param triangulate флаг триангуляции. Если указано значение true, четырехугольники в плоскости будут триангулированы.
+ */
+ public function Plane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false, triangulate:Boolean = false) {
+ super();
+
+ if ((widthSegments == 0) || (lengthSegments == 0)) {
+ return;
+ }
+ width = (width < 0)? 0 : width;
+ length = (length < 0)? 0 : length;
+
+ // Середина
+ var wh:Number = width/2;
+ var lh:Number = length/2;
+
+ // Размеры сегмента
+ var ws:Number = width/widthSegments;
+ var ls:Number = length/lengthSegments;
+
+ // Размеры UV-сегмента
+ var wd:Number = 1/widthSegments;
+ var ld:Number = 1/lengthSegments;
+
+ // Создание точек и UV
+ var x:int;
+ var y:int;
+ var uv:Array = new Array();
+ for (y = 0; y <= lengthSegments; y++) {
+ uv[y] = new Array();
+ for (x = 0; x <= widthSegments; x++) {
+ uv[y][x] = new Point(x*wd, y*ld);
+ createVertex(x*ws - wh, y*ls - lh, 0, x+"_"+y);
+ }
+ }
+
+ // Создание поверхностей
+ var front:Surface;
+ var back:Surface;
+
+ if (twoSided || !reverse) {
+ front = createSurface(null, "front");
+ }
+ if (twoSided || reverse) {
+ back = createSurface(null, "back");
+ }
+
+ // Создание полигонов
+ for (y = 0; y < lengthSegments; y++) {
+ for (x = 0; x < widthSegments; x++) {
+ if (twoSided || !reverse) {
+ if (triangulate) {
+ createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1)], "front" + x + "_" + y + ":0");
+ setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y + ":0");
+ createFace([(x + 1) + "_" + (y + 1), x + "_" + (y + 1), x + "_" + y], "front" + x + "_" + y + ":1");
+ setUVsToFace(uv[y + 1][x + 1], uv[y + 1][x], uv[y][x], "front" + x + "_" + y + ":1");
+ front.addFace("front" + x + "_" + y + ":0");
+ front.addFace("front" + x + "_" + y + ":1");
+ } else {
+ createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1), x + "_" + (y + 1)], "front" + x + "_" + y);
+ setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y);
+ front.addFace("front" + x + "_" + y);
+ }
+ }
+ if (twoSided || reverse) {
+ if (triangulate) {
+ createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1)], "back" + x + "_" + y + ":0");
+ setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y + ":0");
+ createFace([(x + 1) + "_" + (y + 1), (x + 1) + "_" + y, x + "_" + y], "back" + x + "_" + y + ":1");
+ setUVsToFace(uv[lengthSegments - y - 1][x + 1], uv[lengthSegments - y][x + 1], uv[lengthSegments - y][x], "back" + x + "_" + y + ":1");
+ back.addFace("back" + x + "_" + y + ":0");
+ back.addFace("back"+x+"_"+y + ":1");
+ } else {
+ createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1), (x + 1) + "_" + y], "back" + x + "_" + y);
+ setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y);
+ back.addFace("back" + x + "_" + y);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Plane(0, 0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/engine3d/primitives/Sphere.as b/Alternativa3D5/5.3/alternativa/engine3d/primitives/Sphere.as
new file mode 100644
index 0000000..994d7f7
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/engine3d/primitives/Sphere.as
@@ -0,0 +1,144 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.utils.MathUtils;
+ import alternativa.engine3d.core.Face;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Сфера.
+ */
+ public class Sphere extends Mesh {
+
+ /**
+ * Создает сферу.
+ * triangulate установлен в false и на сферу нельзя наложить текстуру.
+ * Только при установленном triangulate в true это возможно.true нормали направлены внутрь сферы.
+ * @param triangulate флаг триангуляции. Если указано значение true, грани будут триангулированы,
+ * и будет возможно наложить на примитив текстуру.
+ */
+ public function Sphere(radius:Number = 100, radialSegments:uint = 8, heightSegments:uint = 8, reverse:Boolean = false, triangulate:Boolean = false) {
+ if ((radialSegments < 3) || (heightSegments < 2)) {
+ return;
+ }
+ radius = (radius < 0)? 0 : radius;
+
+ var poleUp:Vertex = createVertex(0, 0, radius, "poleUp");
+ var poleDown:Vertex = createVertex(0, 0, -radius, "poleDown");
+
+ const radialAngle:Number = MathUtils.DEG360/radialSegments;
+ const heightAngle:Number = MathUtils.DEG360/(heightSegments << 1);
+
+ var radial:uint;
+ var segment:uint;
+
+ // Создание вершин
+ for (segment = 1; segment < heightSegments; segment++) {
+ var currentHeightAngle:Number = heightAngle*segment;
+ var segmentRadius:Number = Math.sin(currentHeightAngle)*radius;
+ var segmentZ:Number = Math.cos(currentHeightAngle)*radius;
+ for (radial = 0; radial < radialSegments; radial++) {
+ var currentRadialAngle:Number = radialAngle*radial;
+ createVertex(-Math.sin(currentRadialAngle)*segmentRadius, Math.cos(currentRadialAngle)*segmentRadius, segmentZ, radial + "_" + segment);
+ }
+ }
+
+ // Создание граней и поверхности
+ var surface:Surface = createSurface();
+
+ var prevRadial:uint = radialSegments - 1;
+ var lastSegmentString:String = "_" + (heightSegments - 1);
+
+ var uStep:Number = 1/radialSegments;
+ var vStep:Number = 1/heightSegments;
+
+ var face:Face;
+
+ // Для триангуляции
+ var aUV:Point;
+ var cUV:Point;
+
+ var u:Number;
+
+ if (reverse) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ // Грани верхнего полюса
+ surface.addFace(createFace([poleUp, radial + "_1", prevRadial + "_1"], prevRadial + "_0"));
+ // Грани нижнего полюса
+ surface.addFace(createFace([radial + lastSegmentString, poleDown, prevRadial + lastSegmentString], prevRadial + lastSegmentString));
+
+ // Если включена триангуляция
+ if (triangulate) {
+ // Триангулируем середину и просчитываем маппинг
+ u = uStep*prevRadial;
+ setUVsToFace(new Point(1 - u, 1), new Point(1 - u - uStep, 1 - vStep), new Point(1 - u, 1 - vStep), prevRadial + "_0");
+ // Грани середки
+ for (segment = 1; segment < (heightSegments - 1); segment++) {
+ aUV = new Point(1 - u - uStep, 1 - (vStep*(segment + 1)));
+ cUV = new Point(1 - u, 1 - vStep*segment);
+ surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":0"));
+ surface.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1"));
+ setUVsToFace(aUV, new Point(1 - u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0");
+ setUVsToFace(cUV, new Point(1 - u - uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1");
+ }
+ setUVsToFace(new Point(1 - u - uStep, vStep), new Point(1 - u, 0), new Point(1 - u, vStep), prevRadial + lastSegmentString);
+
+ } else {
+ // Просто создаем середину
+ // Грани середки
+ for (segment = 1; segment < (heightSegments - 1); segment++) {
+ surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment, radial + "_" + segment], prevRadial + "_" + segment));
+ }
+ }
+ prevRadial = (radial == 0) ? 0 : prevRadial + 1;
+ }
+ } else {
+ for (radial = 0; radial < radialSegments; radial++) {
+ // Грани верхнего полюса
+ surface.addFace(createFace([poleUp, prevRadial + "_1", radial + "_1"], prevRadial + "_0"));
+ // Грани нижнего полюса
+ surface.addFace(createFace([prevRadial + lastSegmentString, poleDown, radial + lastSegmentString], prevRadial + lastSegmentString));
+
+ if (triangulate) {
+ u = uStep*prevRadial;
+ setUVsToFace(new Point(u, 1), new Point(u, 1 - vStep), new Point(u + uStep, 1 - vStep), prevRadial + "_0");
+ // Грани середки
+ for (segment = 1; segment < (heightSegments - 1); segment++) {
+ aUV = new Point(u, 1 - (vStep*segment));
+ cUV = new Point(u + uStep, 1 - vStep * (segment + 1));
+ surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0"));
+ surface.addFace(createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":1"));
+ setUVsToFace(aUV, new Point(u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0");
+ setUVsToFace(cUV, new Point(u + uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1");
+ }
+ setUVsToFace(new Point(u, vStep), new Point(u, 0), new Point(u + uStep, vStep), prevRadial + lastSegmentString);
+ } else {
+ // Грани середки
+ for (segment = 1; segment < (heightSegments - 1); segment++) {
+ surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment));
+ }
+ }
+ prevRadial = (radial == 0) ? 0 : prevRadial + 1;
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Sphere(0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.3/alternativa/utils/MeshUtils.as b/Alternativa3D5/5.3/alternativa/utils/MeshUtils.as
new file mode 100644
index 0000000..d6cc103
--- /dev/null
+++ b/Alternativa3D5/5.3/alternativa/utils/MeshUtils.as
@@ -0,0 +1,861 @@
+package alternativa.utils {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.engine3d.materials.TextureMaterialPrecision;
+ import alternativa.engine3d.materials.WireMaterial;
+ import alternativa.types.*;
+
+ import flash.display.BlendMode;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+ use namespace alternativatypes;
+
+ /**
+ * Утилиты для работы с Mesh-объектами.
+ */
+ public class MeshUtils {
+
+ static private var verticesSort:Array = ["x", "y", "z"];
+ static private var verticesSortOptions:Array = [Array.NUMERIC, Array.NUMERIC, Array.NUMERIC];
+
+ /**
+ * Объединение нескольких Mesh-объектов. Объекты, переданные как аргументы метода, не изменяются.
+ *
+ * @param meshes объединяемые объекты класса alternativa.engine3d.core.Mesh
+ *
+ * @return новый Mesh-объект, содержащий результат объединения переданных Mesh-объектов
+ */
+ static public function uniteMeshes(... meshes):Mesh {
+ var res:Mesh = new Mesh();
+
+ var length:uint = meshes.length;
+ var key:*;
+ var vertex:Vertex;
+ var face:Face;
+ var j:uint;
+ for (var i:uint = 0; i < length; i++) {
+ var mesh:Mesh = meshes[i];
+ var vertices:Map = mesh._vertices.clone();
+ for (key in vertices) {
+ vertex = vertices[key];
+ vertices[key] = res.createVertex(vertex.x, vertex.y, vertex.z);
+ }
+ var faces:Map = mesh._faces.clone();
+ for (key in faces) {
+ face = faces[key];
+ var faceVertices:Array = new Array().concat(face._vertices);
+ for (j = 0; j < face._verticesCount; j++) {
+ vertex = faceVertices[j];
+ faceVertices[j] = vertices[vertex.id];
+ }
+ faces[key] = res.createFace(faceVertices);
+ res.setUVsToFace(face._aUV, face._bUV, face._cUV, faces[key]);
+ }
+ for (key in mesh._surfaces) {
+ var surface:Surface = mesh._surfaces[key];
+ var surfaceFaces:Array = surface._faces.toArray();
+ var numFaces:uint = surfaceFaces.length;
+ for (j = 0; j < numFaces; j++) {
+ face = surfaceFaces[j];
+ surfaceFaces[j] = faces[face.id];
+ }
+ var newSurface:Surface = res.createSurface(surfaceFaces);
+ newSurface.material = SurfaceMaterial(surface.material.clone());
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Слияние вершин Mesh-объекта с одинаковыми координатами. Равенство координат проверяется с учётом погрешности.
+ *
+ * @param mesh объект, вершины которого объединяются
+ * @param threshold погрешность измерения расстояний
+ */
+ static public function autoWeldVertices(mesh:Mesh, threshold:Number = 0):void {
+ // Получаем список вершин меша и сортируем по координатам
+ var vertices:Array = mesh._vertices.toArray(true);
+ vertices.sortOn(verticesSort, verticesSortOptions);
+
+ // Поиск вершин с одинаковыми координатами
+ var weld:Map = new Map(true);
+ var vertex:Vertex;
+ var currentVertex:Vertex = vertices[0];
+ var length:uint = vertices.length;
+ var i:uint;
+ for (i = 1; i < length; i++) {
+ vertex = vertices[i];
+ if ((currentVertex.x - vertex.x <= threshold) && (currentVertex.x - vertex.x >= -threshold) && (currentVertex.y - vertex.y <= threshold) && (currentVertex.y - vertex.y >= -threshold) && (currentVertex.z - vertex.z <= threshold) && (currentVertex.z - vertex.z >= -threshold)) {
+ weld[vertex] = currentVertex;
+ } else {
+ currentVertex = vertex;
+ }
+ }
+
+ // Собираем грани объединяемых вершин
+ var faces:Set = new Set(true);
+ var keyVertex:*;
+ var keyFace:*;
+ for (keyVertex in weld) {
+ vertex = keyVertex;
+ for (keyFace in vertex._faces) {
+ faces[keyFace] = true;
+ }
+ }
+
+ // Заменяем грани
+ for (keyFace in faces) {
+ var face:Face = keyFace;
+ var id:Object = mesh.getFaceId(face);
+ var surface:Surface = face._surface;
+ var aUV:Point = face._aUV;
+ var bUV:Point = face._bUV;
+ var cUV:Point = face._cUV;
+ vertices = new Array().concat(face._vertices);
+ length = vertices.length;
+ for (i = 0; i < length; i++) {
+ vertex = weld[vertices[i]];
+ if (vertex != null) {
+ vertices[i] = vertex;
+ }
+ }
+ mesh.removeFace(face);
+ face = mesh.createFace(vertices, id);
+ if (surface != null) {
+ surface.addFace(face);
+ }
+ face.aUV = aUV;
+ face.bUV = bUV;
+ face.cUV = cUV;
+ }
+
+ // Удаляем вершины
+ for (keyVertex in weld) {
+ mesh.removeVertex(keyVertex);
+ }
+ }
+
+ /**
+ * Объединение соседних граней, образующих плоский выпуклый многоугольник.
+ *
+ * @param mesh объект, грани которого объединяются
+ * @param angleThreshold погрешность измерения углов
+ * @param uvThreshold погрешность измерения UV-координат
+ */
+ static public function autoWeldFaces(mesh:Mesh, angleThreshold:Number = 0, uvThreshold:Number = 0):void {
+ angleThreshold = Math.cos(angleThreshold);
+
+ var face:Face;
+ var sibling:Face;
+ var key:*;
+ var i:uint;
+
+ // Формируем списки граней
+ var faces1:Set = new Set(true);
+ var faces2:Set = new Set(true);
+
+ // Формируем список нормалей
+ var normals:Map = new Map(true);
+ for each (face in mesh._faces.clone()) {
+ var faceNormal:Point3D = face.normal;
+ if (faceNormal.x != 0 || faceNormal.y != 0 || faceNormal.z != 0) {
+ faces1[face] = true;
+ normals[face] = faceNormal;
+ } else {
+ mesh.removeFace(face);
+ }
+ }
+
+ // Объединение
+ do {
+ // Флаг объединения
+ var weld:Boolean = false;
+ // Объединяем грани
+ while ((face = faces1.take()) != null) {
+ //var num:uint = face.num;
+ //var vertices:Array = face.vertices;
+ var currentWeld:Boolean = false;
+
+ // Проверка общих граней по точкам
+
+
+ // Проверка общих граней по рёбрам
+
+ // Перебираем точки грани
+ for (i = 0; (i < face._verticesCount) && !currentWeld; i++) {
+ var faceIndex1:uint = i;
+ var faceIndex2:uint;
+ var siblingIndex1:int;
+ var siblingIndex2:uint;
+
+ // Перебираем грани текущей точки
+ var vertex:Vertex = face._vertices[faceIndex1];
+ var vertexFaces:Set = vertex.faces;
+ for (key in vertexFaces) {
+ sibling = key;
+ // Если грань в списке на объединение и в одной поверхности
+ if (faces1[sibling] && face._surface == sibling._surface) {
+ faceIndex2 = (faceIndex1 < face._verticesCount - 1) ? (faceIndex1 + 1) : 0;
+ siblingIndex1 = sibling._vertices.indexOf(face._vertices[faceIndex2]);
+ // Если общее ребро
+ if (siblingIndex1 >= 0) {
+ // Если грани сонаправлены
+ var normal:Point3D = normals[face];
+ if (Point3D.dot(normal, normals[sibling]) >= angleThreshold) {
+ // Если в точках объединения нет перегибов
+ siblingIndex2 = (siblingIndex1 < sibling._verticesCount - 1) ? (siblingIndex1 + 1) : 0;
+
+ // Расширяем грани объединения
+ var i1:uint;
+ var i2:uint;
+ while (true) {
+ i1 = (faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1);
+ i2 = (siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0;
+ if (face._vertices[i1] == sibling._vertices[i2]) {
+ faceIndex1 = i1;
+ siblingIndex2 = i2;
+ } else {
+ break;
+ }
+ }
+
+ while (true) {
+ i1 = (faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0;
+ i2 = (siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1);
+ if (face._vertices[i1] == sibling._vertices[i2]) {
+ faceIndex2 = i1;
+ siblingIndex1 = i2;
+ } else {
+ break;
+ }
+ }
+
+ vertex = face._vertices[faceIndex1];
+ var a:Point3D = vertex.coords;
+ vertex = face._vertices[faceIndex2];
+ var b:Point3D = vertex.coords;
+
+ // Считаем первый перегиб
+ vertex = sibling._vertices[(siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0];
+ var c:Point3D = vertex.coords;
+ vertex = face._vertices[(faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1)];
+ var d:Point3D = vertex.coords;
+
+ var cx:Number = c.x - a.x;
+ var cy:Number = c.y - a.y;
+ var cz:Number = c.z - a.z;
+ var dx:Number = d.x - a.x;
+ var dy:Number = d.y - a.y;
+ var dz:Number = d.z - a.z;
+
+ var crossX:Number = cy*dz - cz*dy;
+ var crossY:Number = cz*dx - cx*dz;
+ var crossZ:Number = cx*dy - cy*dx;
+
+ if (crossX == 0 && crossY == 0 && crossZ == 0) {
+ if (cx*dx + cy*dy + cz*dz > 0) {
+ break;
+ }
+ }
+
+ var dot:Number = crossX*normal.x + crossY*normal.y + crossZ*normal.z;
+
+ // Если в первой точке перегиба нет
+ if (dot >= 0) {
+
+ // Считаем второй перегиб
+ vertex = face._vertices[(faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0];
+ c = vertex.coords;
+ vertex = sibling._vertices[(siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1)];
+ d = vertex.coords;
+
+ cx = c.x - b.x;
+ cy = c.y - b.y;
+ cz = c.z - b.z;
+ dx = d.x - b.x;
+ dy = d.y - b.y;
+ dz = d.z - b.z;
+
+ crossX = cy*dz - cz*dy;
+ crossY = cz*dx - cx*dz;
+ crossZ = cx*dy - cy*dx;
+
+ if (crossX == 0 && crossY == 0 && crossZ == 0) {
+ if (cx*dx + cy*dy + cz*dz > 0) {
+ break;
+ }
+ }
+
+ dot = crossX*normal.x + crossY*normal.y + crossZ*normal.z;
+
+ // Если во второй точке перегиба нет
+ if (dot >= 0) {
+
+ // Флаг наличия UV у обеих граней
+ var hasUV:Boolean = (face._aUV != null && face._bUV != null && face._cUV != null && sibling._aUV != null && sibling._bUV != null && sibling._cUV != null);
+
+ if (hasUV || (face._aUV == null && face._bUV == null && face._cUV == null && sibling._aUV == null && sibling._bUV == null && sibling._cUV == null)) {
+
+ // Если грани имеют UV, проверяем совместимость
+ if (hasUV) {
+ vertex = sibling._vertices[0];
+ var uv:Point = face.getUVFast(vertex.coords, normal);
+ if ((uv.x - sibling._aUV.x > uvThreshold) || (uv.x - sibling._aUV.x < -uvThreshold) || (uv.y - sibling._aUV.y > uvThreshold) || (uv.y - sibling._aUV.y < -uvThreshold)) {
+ break;
+ }
+
+ vertex = sibling._vertices[1];
+ uv = face.getUVFast(vertex.coords, normal);
+ if ((uv.x - sibling._bUV.x > uvThreshold) || (uv.x - sibling._bUV.x < -uvThreshold) || (uv.y - sibling._bUV.y > uvThreshold) || (uv.y - sibling._bUV.y < -uvThreshold)) {
+ break;
+ }
+
+ vertex = sibling._vertices[2];
+ uv = face.getUVFast(vertex.coords, normal);
+ if ((uv.x - sibling._cUV.x > uvThreshold) || (uv.x - sibling._cUV.x < -uvThreshold) || (uv.y - sibling._cUV.y > uvThreshold) || (uv.y - sibling._cUV.y < -uvThreshold)) {
+ break;
+ }
+ }
+
+ // Формируем новую грань
+ var newVertices:Array = new Array();
+ var n:uint = faceIndex2;
+ do {
+ newVertices.push(face._vertices[n]);
+ n = (n < face._verticesCount - 1) ? (n + 1) : 0;
+ } while (n != faceIndex1);
+ n = siblingIndex2;
+ do {
+ newVertices.push(sibling._vertices[n]);
+ n = (n < sibling._verticesCount - 1) ? (n + 1) : 0;
+ } while (n != siblingIndex1);
+
+ // Выбираем начальную точку
+ n = getBestBeginVertexIndex(newVertices);
+ for (var m:uint = 0; m < n; m++) {
+ newVertices.push(newVertices.shift());
+ }
+
+ // Заменяем грани новой
+ var surface:Surface = face._surface;
+ var newFace:Face = mesh.createFace(newVertices);
+ if (hasUV) {
+ newFace.aUV = face.getUVFast(newVertices[0].coords, normal);
+ newFace.bUV = face.getUVFast(newVertices[1].coords, normal);
+ newFace.cUV = face.getUVFast(newVertices[2].coords, normal);
+ }
+ if (surface != null) {
+ surface.addFace(newFace);
+ }
+ mesh.removeFace(face);
+ mesh.removeFace(sibling);
+
+ // Обновляем список нормалей
+ delete normals[sibling];
+ delete normals[face];
+ normals[newFace] = newFace.normal;
+
+ // Обновляем списки расчётов
+ delete faces1[sibling];
+ faces2[newFace] = true;
+
+ // Помечаем объединение
+ weld = true;
+ currentWeld = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+
+ // Если не удалось объединить, переносим грань
+ faces2[face] = true;
+ }
+
+ // Меняем списки
+ var fs:Set = faces1;
+ faces1 = faces2;
+ faces2 = fs;
+ } while (weld);
+
+ removeIsolatedVertices(mesh);
+ removeUselessVertices(mesh);
+ }
+
+ /**
+ * Удаление вершин объекта, не принадлежащим ни одной грани.
+ *
+ * @param mesh объект, вершины которого удаляются
+ */
+ static public function removeIsolatedVertices(mesh:Mesh):void {
+ for each (var vertex:Vertex in mesh._vertices.clone()) {
+ if (vertex._faces.isEmpty()) {
+ mesh.removeVertex(vertex);
+ }
+ }
+ }
+
+ /**
+ * Удаление вершин объекта, которые во всех своих гранях лежат на отрезке между предыдущей и следующей вершиной.
+ *
+ * @param mesh объект, вершины которого удаляются
+ */
+ static public function removeUselessVertices(mesh:Mesh):void {
+ var v:Vertex;
+ var key:*;
+ var face:Face;
+ var index:uint;
+ var length:uint;
+ for each (var vertex:Vertex in mesh._vertices.clone()) {
+ var unless:Boolean = true;
+ var indexes:Map = new Map(true);
+ for (key in vertex._faces) {
+ face = key;
+ length = face._vertices.length;
+ index = face._vertices.indexOf(vertex);
+ v = face._vertices[index];
+ var a:Point3D = v.coords;
+ v = face._vertices[(index < length - 1) ? (index + 1) : 0];
+ var b:Point3D = v.coords;
+ v = face._vertices[(index > 0) ? (index - 1) : (length - 1)];
+ var c:Point3D = v.coords;
+ var abx:Number = b.x - a.x;
+ var aby:Number = b.y - a.y;
+ var abz:Number = b.z - a.z;
+ var acx:Number = c.x - a.x;
+ var acy:Number = c.y - a.y;
+ var acz:Number = c.z - a.z;
+ if (aby*acz - abz*acy == 0 && abz*acx - abx*acz == 0 && abx*acy - aby*acx == 0) {
+ indexes[face] = index;
+ } else {
+ unless = false;
+ break;
+ }
+ }
+ if (unless && !indexes.isEmpty()) {
+ // Удаляем
+ for (key in indexes) {
+ var i:uint;
+ face = key;
+ index = indexes[face];
+ length = face._vertices.length;
+ var newVertices:Array = new Array();
+ for (i = 0; i < length; i++) {
+ if (i != index) {
+ newVertices.push(face._vertices[i]);
+ }
+ }
+ var n:uint = getBestBeginVertexIndex(newVertices);
+ for (i = 0; i < n; i++) {
+ newVertices.push(newVertices.shift());
+ }
+ var surface:Surface = face._surface;
+ var newFace:Face = mesh.createFace(newVertices);
+ if (face._aUV != null && face._bUV != null && face._cUV != null) {
+ var normal:Point3D = face.normal;
+ newFace.aUV = face.getUVFast(newVertices[0].coords, normal);
+ newFace.bUV = face.getUVFast(newVertices[1].coords, normal);
+ newFace.cUV = face.getUVFast(newVertices[2].coords, normal);
+ }
+ if (surface != null) {
+ surface.addFace(newFace);
+ }
+ mesh.removeFace(face);
+ }
+ mesh.removeVertex(vertex);
+ }
+ }
+ }
+
+ /**
+ * Удаление вырожденных граней.
+ *
+ * @param mesh объект, грани которого удаляются
+ */
+ static public function removeSingularFaces(mesh:Mesh):void {
+ for each (var face:Face in mesh._faces.clone()) {
+ var normal:Point3D = face.normal;
+ if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
+ mesh.removeFace(face);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Находит наиболее подходящую первую точку.
+ * @param vertices
+ * @return
+ */
+ static public function getBestBeginVertexIndex(vertices:Array):uint {
+ var bestIndex:uint = 0;
+ var num:uint = vertices.length;
+ if (num > 3) {
+ var maxCrossLength:Number = 0;
+ var v:Vertex = vertices[num - 1];
+ var c1:Point3D = v.coords;
+ v = vertices[0];
+ var c2:Point3D = v.coords;
+
+ var prevX:Number = c2.x - c1.x;
+ var prevY:Number = c2.y - c1.y;
+ var prevZ:Number = c2.z - c1.z;
+
+ for (var i:uint = 0; i < num; i++) {
+ c1 = c2;
+ v = vertices[(i < num - 1) ? (i + 1) : 0];
+ c2 = v.coords;
+
+ var nextX:Number = c2.x - c1.x;
+ var nextY:Number = c2.y - c1.y;
+ var nextZ:Number = c2.z - c1.z;
+
+ var crossX:Number = prevY*nextZ - prevZ*nextY;
+ var crossY:Number = prevZ*nextX - prevX*nextZ;
+ var crossZ:Number = prevX*nextY - prevY*nextX;
+
+
+ var crossLength:Number = crossX*crossX + crossY*crossY + crossZ*crossZ;
+ if (crossLength > maxCrossLength) {
+ maxCrossLength = crossLength;
+ bestIndex = i;
+ }
+
+ prevX = nextX;
+ prevY = nextY;
+ prevZ = nextZ;
+ }
+ // Берём предыдущий
+ bestIndex = (bestIndex > 0) ? (bestIndex - 1) : (num - 1);
+ }
+
+ return bestIndex;
+ }
+
+ /**
+ * Генерация AS-класса.
+ *
+ * @param mesh объект, на базе которого генерируется класс
+ * @param packageName имя пакета для генерируемого класса
+ * @return AS-класс в текстовом виде
+ */
+ static public function generateClass(mesh:Mesh, packageName:String = ""):String {
+
+ var className:String = mesh._name.charAt(0).toUpperCase() + mesh._name.substr(1);
+
+ var header:String = "package" + ((packageName != "") ? (" " + packageName + " ") : " ") + "{\r\r";
+
+ var importSet:Object = new Object();
+ importSet["alternativa.engine3d.core.Mesh"] = true;
+
+ var materialSet:Map = new Map(true);
+ var materialName:String;
+ var materialNum:uint = 1;
+
+ var footer:String = "\t\t}\r\t}\r}";
+
+ var classHeader:String = "\tpublic class "+ className + " extends Mesh {\r\r";
+
+ var constructor:String = "\t\tpublic function " + className + "() {\r";
+ constructor += "\t\t\tsuper(\"" + mesh._name +"\");\r\r";
+
+
+ var newLine:Boolean = false;
+ if (mesh.mobility != 0) {
+ constructor += "\t\t\tmobility = " + mesh.mobility +";\r";
+ newLine = true;
+ }
+
+ if (mesh.x != 0 && mesh.y != 0 && mesh.z != 0) {
+ importSet["alternativa.types.Point3D"] = true;
+ constructor += "\t\t\tcoords = new Point3D(" + mesh.x + ", " + mesh.y + ", " + mesh.z +");\r";
+ newLine = true;
+ } else {
+ if (mesh.x != 0) {
+ constructor += "\t\t\tx = " + mesh.x + ";\r";
+ newLine = true;
+ }
+ if (mesh.y != 0) {
+ constructor += "\t\t\ty = " + mesh.y + ";\r";
+ newLine = true;
+ }
+ if (mesh.z != 0) {
+ constructor += "\t\t\tz = " + mesh.z + ";\r";
+ newLine = true;
+ }
+ }
+ if (mesh.rotationX != 0) {
+ constructor += "\t\t\trotationX = " + mesh.rotationX + ";\r";
+ newLine = true;
+ }
+ if (mesh.rotationY != 0) {
+ constructor += "\t\t\trotationY = " + mesh.rotationY + ";\r";
+ newLine = true;
+ }
+ if (mesh.rotationZ != 0) {
+ constructor += "\t\t\trotationZ = " + mesh.rotationZ + ";\r";
+ newLine = true;
+ }
+ if (mesh.scaleX != 1) {
+ constructor += "\t\t\tscaleX = " + mesh.scaleX + ";\r";
+ newLine = true;
+ }
+ if (mesh.scaleY != 1) {
+ constructor += "\t\t\tscaleY = " + mesh.scaleY + ";\r";
+ newLine = true;
+ }
+ if (mesh.scaleZ != 1) {
+ constructor += "\t\t\tscaleZ = " + mesh.scaleZ + ";\r";
+ newLine = true;
+ }
+
+ constructor += newLine ? "\r" : "";
+
+ function idToString(value:*):String {
+ return isNaN(value) ? ("\"" + value + "\"") : value;
+ }
+
+ function blendModeToString(value:String):String {
+ switch (value) {
+ case BlendMode.ADD: return "BlendMode.ADD";
+ case BlendMode.ALPHA: return "BlendMode.ALPHA";
+ case BlendMode.DARKEN: return "BlendMode.DARKEN";
+ case BlendMode.DIFFERENCE: return "BlendMode.DIFFERENCE";
+ case BlendMode.ERASE: return "BlendMode.ERASE";
+ case BlendMode.HARDLIGHT: return "BlendMode.HARDLIGHT";
+ case BlendMode.INVERT: return "BlendMode.INVERT";
+ case BlendMode.LAYER: return "BlendMode.LAYER";
+ case BlendMode.LIGHTEN: return "BlendMode.LIGHTEN";
+ case BlendMode.MULTIPLY: return "BlendMode.MULTIPLY";
+ case BlendMode.NORMAL: return "BlendMode.NORMAL";
+ case BlendMode.OVERLAY: return "BlendMode.OVERLAY";
+ case BlendMode.SCREEN: return "BlendMode.SCREEN";
+ case BlendMode.SUBTRACT: return "BlendMode.SUBTRACT";
+ default: return "BlendMode.NORMAL";
+ }
+ }
+
+ function colorToString(value:uint):String {
+ var hex:String = value.toString(16).toUpperCase();
+ var res:String = "0x";
+ var len:uint = 6 - hex.length;
+ for (var j:uint = 0; j < len; j++) {
+ res += "0";
+ }
+ res += hex;
+ return res;
+ }
+
+ var i:uint;
+ var length:uint;
+ var key:*;
+ var id:String;
+ var face:Face;
+ var surface:Surface;
+
+ newLine = false;
+ for (id in mesh._vertices) {
+ var vertex:Vertex = mesh._vertices[id];
+ var coords:Point3D = vertex.coords;
+ constructor += "\t\t\tcreateVertex(" + coords.x + ", " + coords.y + ", " + coords.z + ", " + idToString(id) + ");\r";
+ newLine = true;
+ }
+
+ constructor += newLine ? "\r" : "";
+
+ newLine = false;
+ for (id in mesh._faces) {
+ face = mesh._faces[id];
+ length = face._verticesCount;
+ constructor += "\t\t\tcreateFace(["
+ for (i = 0; i < length - 1; i++) {
+ constructor += idToString(mesh.getVertexId(face._vertices[i])) + ", ";
+ }
+ constructor += idToString(mesh.getVertexId(face._vertices[i])) + "], " + idToString(id) + ");\r";
+
+ if (face._aUV != null || face._bUV != null || face._cUV != null) {
+ importSet["flash.geom.Point"] = true;
+ constructor += "\t\t\tsetUVsToFace(new Point(" + face._aUV.x + ", " + face._aUV.y + "), new Point(" + face._bUV.x + ", " + face._bUV.y + "), new Point(" + face._cUV.x + ", " + face._cUV.y + "), " + idToString(id) + ");\r";
+ }
+ newLine = true;
+ }
+
+ constructor += newLine ? "\r" : "";
+
+ for (id in mesh._surfaces) {
+ surface = mesh._surfaces[id];
+ var facesStr:String = "";
+ for (key in surface._faces) {
+ facesStr += idToString(mesh.getFaceId(key)) + ", ";
+ }
+ constructor += "\t\t\tcreateSurface([" + facesStr.substr(0, facesStr.length - 2) + "], " + idToString(id) + ");\r";
+
+ if (surface.material != null) {
+ var material:String;
+ var defaultAlpha:Boolean = surface.material.alpha == 1;
+ var defaultBlendMode:Boolean = surface.material.blendMode == BlendMode.NORMAL;
+ if (surface.material is WireMaterial) {
+ importSet["alternativa.engine3d.materials.WireMaterial"] = true;
+ var defaultThickness:Boolean = WireMaterial(surface.material).thickness == 0;
+ var defaultColor:Boolean = WireMaterial(surface.material).color == 0;
+ material = "new WireMaterial(";
+ if (!defaultThickness || !defaultColor || !defaultAlpha || !defaultBlendMode) {
+ material += WireMaterial(surface.material).thickness;
+ if (!defaultColor || !defaultAlpha || !defaultBlendMode) {
+ material += ", " + colorToString(WireMaterial(surface.material).color);
+ if (!defaultAlpha || !defaultBlendMode) {
+ material += ", " + surface.material.alpha ;
+ if (!defaultBlendMode) {
+ importSet["flash.display.BlendMode"] = true;
+ material += ", " + blendModeToString(surface.material.blendMode);
+ }
+ }
+ }
+ }
+ }
+ var defaultWireThickness:Boolean;
+ var defaultWireColor:Boolean;
+ if (surface.material is FillMaterial) {
+ importSet["alternativa.engine3d.materials.FillMaterial"] = true;
+ defaultWireThickness = FillMaterial(surface.material).wireThickness < 0;
+ defaultWireColor = FillMaterial(surface.material).wireColor == 0;
+ material = "new FillMaterial(" + colorToString(FillMaterial(surface.material).color);
+ if (!defaultAlpha || !defaultBlendMode || !defaultWireThickness || !defaultWireColor) {
+ material += ", " + surface.material.alpha;
+ if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor) {
+ importSet["flash.display.BlendMode"] = true;
+ material += ", " + blendModeToString(surface.material.blendMode);
+ if (!defaultWireThickness || !defaultWireColor) {
+ material += ", " + FillMaterial(surface.material).wireThickness;
+ if (!defaultWireColor) {
+ material += ", " + colorToString(FillMaterial(surface.material).wireColor);
+ }
+ }
+ }
+ }
+ }
+ if (surface.material is TextureMaterial) {
+ importSet["alternativa.engine3d.materials.TextureMaterial"] = true;
+ var defaultRepeat:Boolean = TextureMaterial(surface.material).repeat;
+ var defaultSmooth:Boolean = !TextureMaterial(surface.material).smooth;
+ defaultWireThickness = TextureMaterial(surface.material).wireThickness < 0;
+ defaultWireColor = TextureMaterial(surface.material).wireColor == 0;
+ var defaultPrecision:Boolean = TextureMaterial(surface.material).precision == TextureMaterialPrecision.MEDIUM;
+
+ if (TextureMaterial(surface.material).texture == null) {
+ materialName = "null";
+ } else {
+ importSet["alternativa.types.Texture"] = true;
+ if (materialSet[TextureMaterial(surface.material).texture] == undefined) {
+ materialName = (TextureMaterial(surface.material).texture._name != null) ? TextureMaterial(surface.material).texture._name : "texture" + materialNum++;
+ materialSet[TextureMaterial(surface.material).texture] = materialName;
+ } else {
+ materialName = materialSet[TextureMaterial(surface.material).texture];
+ }
+ materialName = materialName.split(".")[0];
+ }
+ material = "new TextureMaterial(" + materialName;
+ if (!defaultAlpha || !defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).alpha;
+ if (!defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).repeat;
+ if (!defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).smooth;
+ if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ importSet["flash.display.BlendMode"] = true;
+ material += ", " + blendModeToString(surface.material.blendMode);
+ if (!defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).wireThickness;
+ if (!defaultWireColor || !defaultPrecision) {
+ material += ", " + colorToString(TextureMaterial(surface.material).wireColor);
+ if (!defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).precision;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ constructor += "\t\t\tsetMaterialToSurface(" + material + "), " + idToString(id) + ");\r";
+ }
+ }
+
+ var imports:String = "";
+ newLine = false;
+
+ var importArray:Array = new Array();
+ for (key in importSet) {
+ importArray.push(key);
+ }
+ importArray.sort();
+
+ length = importArray.length;
+ for (i = 0; i < length; i++) {
+ var pack:String = importArray[i];
+ var current:String = pack.substr(0, pack.indexOf("."));
+ imports += (current != prev && prev != null) ? "\r" : "";
+ imports += "\timport " + pack + ";\r";
+ var prev:String = current;
+ newLine = true;
+ }
+ imports += newLine ? "\r" : "";
+
+ var embeds:String = "";
+ newLine = false;
+ for each (materialName in materialSet) {
+ var materialClassName:String = materialName.split(".")[0];
+ var materialBmpName:String = materialClassName.charAt(0).toUpperCase() + materialClassName.substr(1);
+ embeds += "\t\t[Embed(source=\"" + materialName + "\")] private static const bmp" + materialBmpName + ":Class;\r";
+ embeds += "\t\tprivate static const " + materialClassName + ":Texture = new Texture(new bmp" + materialBmpName + "().bitmapData, \"" + materialName + "\");\r";
+ newLine = true;
+ }
+ embeds += newLine ? "\r" : "";
+
+ return header + imports + classHeader + embeds + constructor + footer;
+ }
+
+// /**
+// * @private
+// * Убирает лишние точки на ребрах и добавляет точки в месте распилов на соседних гранях
+// *
+// * @param object объект
+// * @param recursive если установлена в true, метод будет вызываться рекурсивно на потомках потомков потомков объекта тоже.
+// *
+// * @return количество добавленных точек с вычетом убранных
+// */
+// p function fixGaps(object:Object3D, recursive:Boolean = false):int {
+// var result:int = 0;
+// if (object is Mesh) {
+// var mesh:Mesh = Mesh(object);
+// var faces:Map = mesh._faces;
+// for each (var face:Face in faces) {
+// //face.
+// }
+// }
+// if (recursive) {
+// var children:Set = object._children;
+// for (var child:* in children) {
+// result += fixGaps(child, true);
+// }
+// }
+// return result;
+// }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/Alternativa3D.as b/Alternativa3D5/5.4/alternativa/Alternativa3D.as
new file mode 100644
index 0000000..ac916ba
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/Alternativa3D.as
@@ -0,0 +1,14 @@
+package alternativa {
+
+ /**
+ * Класс содержит информацию о версии библиотеки.
+ * Также используется для интеграции библиотеки в среду разработки Adobe Flash.
+ */
+ public class Alternativa3D {
+
+ /**
+ * Версия библиотеки в формате: поколение.feature-версия.fix-версия
+ */
+ public static const version:String = "5.4.1";
+ }
+}
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/alternativa3d.as b/Alternativa3D5/5.4/alternativa/engine3d/alternativa3d.as
new file mode 100644
index 0000000..8bce40e
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/alternativa3d.as
@@ -0,0 +1,3 @@
+package alternativa.engine3d {
+ public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d";
+}
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/controllers/CameraController.as b/Alternativa3D5/5.4/alternativa/engine3d/controllers/CameraController.as
new file mode 100644
index 0000000..0bc89a7
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/controllers/CameraController.as
@@ -0,0 +1,889 @@
+package alternativa.engine3d.controllers {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.physics.EllipsoidCollider;
+ import alternativa.types.Map;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+ import alternativa.utils.KeyboardUtils;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.DisplayObject;
+ import flash.events.KeyboardEvent;
+ import flash.events.MouseEvent;
+ import flash.geom.Point;
+ import flash.utils.getTimer;
+
+ use namespace alternativa3d;
+
+ /**
+ * Контроллер камеры. Контроллер обеспечивает управление движением и поворотами камеры с использованием
+ * клавиатуры и мыши, а также предоставляет простую проверку столкновений камеры с объектами сцены.
+ */
+ public class CameraController {
+ /**
+ * Имя действия для привязки клавиш движения вперёд.
+ */
+ public static const ACTION_FORWARD:String = "ACTION_FORWARD";
+ /**
+ * Имя действия для привязки клавиш движения назад.
+ */
+ public static const ACTION_BACK:String = "ACTION_BACK";
+ /**
+ * Имя действия для привязки клавиш движения влево.
+ */
+ public static const ACTION_LEFT:String = "ACTION_LEFT";
+ /**
+ * Имя действия для привязки клавиш движения вправо.
+ */
+ public static const ACTION_RIGHT:String = "ACTION_RIGHT";
+ /**
+ * Имя действия для привязки клавиш движения вверх.
+ */
+ public static const ACTION_UP:String = "ACTION_UP";
+ /**
+ * Имя действия для привязки клавиш движения вниз.
+ */
+ public static const ACTION_DOWN:String = "ACTION_DOWN";
+// public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT";
+// public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT";
+ /**
+ * Имя действия для привязки клавиш увеличения угла тангажа.
+ */
+ public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP";
+ /**
+ * Имя действия для привязки клавиш уменьшения угла тангажа.
+ */
+ public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN";
+ /**
+ * Имя действия для привязки клавиш уменьшения угла рысканья (поворота налево).
+ */
+ public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT";
+ /**
+ * Имя действия для привязки клавиш увеличения угла рысканья (поворота направо).
+ */
+ public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT";
+ /**
+ * Имя действия для привязки клавиш увеличения скорости.
+ */
+ public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE";
+
+ // флаги действий
+ private var _forward:Boolean;
+ private var _back:Boolean;
+ private var _left:Boolean;
+ private var _right:Boolean;
+ private var _up:Boolean;
+ private var _down:Boolean;
+ private var _pitchUp:Boolean;
+ private var _pitchDown:Boolean;
+ private var _yawLeft:Boolean;
+ private var _yawRight:Boolean;
+ private var _accelerate:Boolean;
+
+ private var _moveLocal:Boolean = true;
+
+ // Флаг включения управления камерой
+ private var _controlsEnabled:Boolean = false;
+
+ // Значение таймера в начале прошлого кадра
+ private var lastFrameTime:uint;
+
+ // Чувствительность обзора мышью. Коэффициент умножения базовых коэффициентов поворотов.
+ private var _mouseSensitivity:Number = 1;
+ // Коэффициент поворота камеры по тангажу. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
+ private var _mousePitch:Number = Math.PI / 360;
+ // Результирующий коэффициент поворота камеры мышью по тангажу
+ private var _mousePitchCoeff:Number = _mouseSensitivity * _mousePitch;
+ // Коэффициент поворота камеры по рысканью. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
+ private var _mouseYaw:Number = Math.PI / 360;
+ // Результирующий коэффициент поворота камеры мышью по расканью
+ private var _mouseYawCoeff:Number = _mouseSensitivity * _mouseYaw;
+
+ // Вспомогательные переменные для обзора мышью
+ private var mouseLookActive:Boolean;
+ private var startDragCoords:Point = new Point();
+ private var currentDragCoords:Point = new Point();
+ private var prevDragCoords:Point = new Point();
+ private var startRotX:Number;
+ private var startRotZ:Number;
+
+ // Скорость изменения тангажа в радианах за секунду при управлении с клавиатуры
+ private var _pitchSpeed:Number = 1;
+ // Скорость изменения рысканья в радианах за секунду при управлении с клавиатуры
+ private var _yawSpeed:Number = 1;
+ // Скорость изменения крена в радианах за секунду при управлении с клавиатуры
+// public var bankSpeed:Number = 2;
+// private var bankMatrix:Matrix3D = new Matrix3D();
+
+ // Скорость поступательного движения в единицах за секунду
+ private var _speed:Number = 100;
+ // Коэффициент увеличения скорости при соответствующей нажатой клавише
+ private var _speedMultiplier:Number = 2;
+
+ private var velocity:Point3D = new Point3D();
+ private var destination:Point3D = new Point3D();
+
+ private var _fovStep:Number = Math.PI / 180;
+ private var _zoomMultiplier:Number = 0.1;
+
+ // Привязка клавиш к действиям
+ private var keyBindings:Map = new Map();
+ // Привязка действий к обработчикам
+ private var actionBindings:Map = new Map();
+
+ // Источник событий клавиатуры и мыши
+ private var _eventsSource:DisplayObject;
+ // Управляемая камера
+ private var _camera:Camera3D;
+
+ // Класс реализации определния столкновений
+ private var _collider:EllipsoidCollider;
+ // Флаг необходимости проверки столкновений
+ private var _checkCollisions:Boolean;
+ // Радиус сферы для определения столкновений
+ private var _collisionRadius:Number = 0;
+ // Набор исключаемых из проверки столкновений объектов
+ private var _collisionIgnoreSet:Set = new Set(true);
+ // Флаг движения
+ private var _isMoving:Boolean;
+
+ private var _onStartMoving:Function;
+ private var _onStopMoving:Function;
+
+ /**
+ * Создание экземпляра контроллера.
+ *
+ * @param eventsSourceObject объект, используемый для получения событий мыши и клавиатуры
+ */
+ public function CameraController(eventsSourceObject:DisplayObject) {
+ if (eventsSourceObject == null) {
+ throw new ArgumentError("CameraController: eventsSource is null");
+ }
+ _eventsSource = eventsSourceObject;
+
+ actionBindings[ACTION_FORWARD] = forward;
+ actionBindings[ACTION_BACK] = back;
+ actionBindings[ACTION_LEFT] = left;
+ actionBindings[ACTION_RIGHT] = right;
+ actionBindings[ACTION_UP] = up;
+ actionBindings[ACTION_DOWN] = down;
+ actionBindings[ACTION_PITCH_UP] = pitchUp;
+ actionBindings[ACTION_PITCH_DOWN] = pitchDown;
+ actionBindings[ACTION_YAW_LEFT] = yawLeft;
+ actionBindings[ACTION_YAW_RIGHT] = yawRight;
+ actionBindings[ACTION_ACCELERATE] = accelerate;
+ }
+
+ /**
+ * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
+ *
+ *
+ */
+ public function setDefaultBindings():void {
+ unbindAll();
+ bindKey(KeyboardUtils.W, ACTION_FORWARD);
+ bindKey(KeyboardUtils.S, ACTION_BACK);
+ bindKey(KeyboardUtils.A, ACTION_LEFT);
+ bindKey(KeyboardUtils.D, ACTION_RIGHT);
+ bindKey(KeyboardUtils.SPACE, ACTION_UP);
+ bindKey(KeyboardUtils.CONTROL, ACTION_DOWN);
+ bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
+ bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
+ bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT);
+ bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT);
+ bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE);
+ }
+
+ /**
+ * Направление камеры на точку.
+ *
+ * @param point координаты точки направления камеры
+ */
+ public function lookAt(point:Point3D):void {
+ if (_camera == null) {
+ return;
+ }
+ var dx:Number = point.x - _camera.x;
+ var dy:Number = point.y - _camera.y;
+ var dz:Number = point.z - _camera.z;
+ _camera.rotationZ = -Math.atan2(dx, dy);
+ _camera.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - MathUtils.DEG90;
+ }
+
+ /**
+ * Callback-функция, вызываемая при начале движения камеры.
+ */
+ public function get onStartMoving():Function {
+ return _onStartMoving;
+ }
+
+ /**
+ * @private
+ */
+ public function set onStartMoving(value:Function):void {
+ _onStartMoving = value;
+ }
+
+ /**
+ * Callback-функция, вызываемая при прекращении движения камеры.
+ */
+ public function get onStopMoving():Function {
+ return _onStopMoving;
+ }
+
+ /**
+ * @private
+ */
+ public function set onStopMoving(value:Function):void {
+ _onStopMoving = value;
+ }
+
+ /**
+ * Набор объектов, исключаемых из проверки столкновений.
+ */
+ public function get collisionIgnoreSet():Set {
+ return _collisionIgnoreSet;
+ }
+
+ /**
+ * Источник событий клавиатуры и мыши.
+ */
+ public function get eventsSource():DisplayObject {
+ return _eventsSource;
+ }
+
+ /**
+ * @private
+ */
+ public function set eventsSource(value:DisplayObject):void {
+ if (_eventsSource != value) {
+ if (value == null) {
+ throw new ArgumentError("CameraController: eventsSource is null");
+ }
+ if (_controlsEnabled) {
+ unregisterEventsListeners();
+ }
+ _eventsSource = value;
+ if (_controlsEnabled) {
+ registerEventListeners();
+ }
+ }
+ }
+
+ /**
+ * Ассоциированная камера.
+ */
+ public function get camera():Camera3D {
+ return _camera;
+ }
+
+ /**
+ * @private
+ */
+ public function set camera(value:Camera3D):void {
+ if (_camera != value) {
+ _camera = value;
+ if (value == null) {
+ controlsEnabled = false;
+ } else {
+ createCollider();
+ }
+ }
+ }
+
+ /**
+ * Режим движения камеры. Если значение равно
+ * Клавиша Действие
+ * W ACTION_FORWARD
+ * S ACTION_BACK
+ * A ACTION_LEFT
+ * D ACTION_RIGHT
+ * SPACE ACTION_UP
+ * CONTROL ACTION_DOWN
+ * SHIFT ACTION_ACCELERATE
+ * UP ACTION_PITCH_UP
+ * DOWN ACTION_PITCH_DOWN
+ * LEFT ACTION_YAW_LEFT
+ * RIGHT ACTION_YAW_RIGHT true, то перемещения камеры происходят относительно
+ * локальной системы координат, иначе относительно глобальной.
+ *
+ * @default true
+ */
+ public function get moveLocal():Boolean {
+ return _moveLocal;
+ }
+
+ /**
+ * @private
+ */
+ public function set moveLocal(value:Boolean):void {
+ _moveLocal = value;
+ }
+
+ /**
+ * Включение режима проверки столкновений.
+ */
+ public function get checkCollisions():Boolean {
+ return _checkCollisions;
+ }
+
+ /**
+ * @private
+ */
+ public function set checkCollisions(value:Boolean):void {
+ _checkCollisions = value;
+ }
+
+ /**
+ * Радиус сферы для определения столкновений.
+ *
+ * @default 0
+ */
+ public function get collisionRadius():Number {
+ return _collisionRadius;
+ }
+
+ /**
+ * @private
+ */
+ public function set collisionRadius(value:Number):void {
+ _collisionRadius = value;
+ if (_collider != null) {
+ _collider.radiusX = _collisionRadius;
+ _collider.radiusY = _collisionRadius;
+ _collider.radiusZ = _collisionRadius;
+ }
+ }
+
+ /**
+ * Привязка клавиши к действию.
+ *
+ * @param keyCode код клавиши
+ * @param action наименование действия
+ */
+ public function bindKey(keyCode:uint, action:String):void {
+ var method:Function = actionBindings[action];
+ if (method != null) {
+ keyBindings[keyCode] = method;
+ }
+ }
+
+ /**
+ * Очистка привязки клавиши.
+ *
+ * @param keyCode код клавиши
+ */
+ public function unbindKey(keyCode:uint):void {
+ keyBindings.remove(keyCode);
+ }
+
+ /**
+ * Очистка привязки всех клавиш.
+ */
+ public function unbindAll():void {
+ keyBindings.clear();
+ }
+
+ /**
+ * Активация движения камеры вперёд.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function forward(value:Boolean):void {
+ _forward = value;
+ }
+
+ /**
+ * Активация движения камеры назад.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function back(value:Boolean):void {
+ _back = value;
+ }
+
+ /**
+ * Активация движения камеры влево.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function left(value:Boolean):void {
+ _left = value;
+ }
+
+ /**
+ * Активация движения камеры вправо.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function right(value:Boolean):void {
+ _right = value;
+ }
+
+ /**
+ * Активация движения камеры вверх.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function up(value:Boolean):void {
+ _up = value;
+ }
+
+ /**
+ * Активация движения камеры вниз.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function down(value:Boolean):void {
+ _down = value;
+ }
+
+ /**
+ * Активация поворота камеры вверх.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function pitchUp(value:Boolean):void {
+ _pitchUp = value;
+ }
+
+ /**
+ * Активация поворота камеры вниз.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function pitchDown(value:Boolean):void {
+ _pitchDown = value;
+ }
+
+ /**
+ * Активация поворота камеры влево.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function yawLeft(value:Boolean):void {
+ _yawLeft = value;
+ }
+
+ /**
+ * Активация поворота камеры вправо.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function yawRight(value:Boolean):void {
+ _yawRight = value;
+ }
+
+
+ /**
+ * Активация режима увеличенной скорости.
+ *
+ * @param value true для включения ускорения, false для выключения
+ */
+ public function accelerate(value:Boolean):void {
+ _accelerate = value;
+ }
+
+ /**
+ * @private
+ */
+ private function createCollider():void {
+ _collider = new EllipsoidCollider(_camera.scene, _collisionRadius);
+ _collider.offsetThreshold = 0.01;
+ _collider.collisionSet = _collisionIgnoreSet;
+ }
+
+ /**
+ * Чувствительность мыши — коэффициент умножения mousePitch и mouseYaw.
+ *
+ * @default 1
+ *
+ * @see #mousePitch()
+ * @see #mouseYaw()
+ */
+ public function get mouseSensitivity():Number {
+ return _mouseSensitivity;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseSensitivity(sensitivity:Number):void {
+ _mouseSensitivity = sensitivity;
+ _mousePitchCoeff = _mouseSensitivity * _mousePitch;
+ _mouseYawCoeff = _mouseSensitivity * _mouseYaw;
+ }
+
+ /**
+ * Скорость изменения угла тангажа при управлении мышью (радианы на пиксель).
+ *
+ * @default Math.PI / 360
+ */
+ public function get mousePitch():Number {
+ return _mousePitch;
+ }
+
+ /**
+ * @private
+ */
+ public function set mousePitch(pitch:Number):void {
+ _mousePitch = pitch;
+ _mousePitchCoeff = _mouseSensitivity * _mousePitch;
+ }
+
+ /**
+ * Скорость изменения угла рысканья при управлении мышью (радианы на пиксель).
+ *
+ * @default Math.PI / 360
+ */
+ public function get mouseYaw():Number {
+ return _mouseYaw;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseYaw(yaw:Number):void {
+ _mouseYaw = yaw;
+ _mouseYawCoeff = _mouseSensitivity * _mouseYaw;
+ }
+
+ /**
+ * Угловая скорость по тангажу при управлении с клавиатуры (радианы в секунду).
+ *
+ * @default 1
+ */
+ public function get pitchSpeed():Number {
+ return _pitchSpeed;
+ }
+
+ /**
+ * @private
+ */
+ public function set pitchSpeed(spd:Number):void {
+ _pitchSpeed = spd;
+ }
+
+ /**
+ * Угловая скорость по рысканью при управлении с клавиатуры (радианы в секунду).
+ *
+ * @default 1
+ */
+ public function get yawSpeed():Number {
+ return _yawSpeed;
+ }
+
+ /**
+ * @private
+ */
+ public function set yawSpeed(spd:Number):void {
+ _yawSpeed = spd;
+ }
+
+ /**
+ * Скорость поступательного движения (единицы в секунду).
+ */
+ public function get speed():Number {
+ return _speed;
+ }
+
+ /**
+ * @private
+ */
+ public function set speed(spd:Number):void {
+ _speed = spd;
+ }
+
+ /**
+ * Коэффициент увеличения скорости при активном действии ACTION_ACCELERATE.
+ *
+ * @default 2
+ */
+ public function get speedMultiplier():Number {
+ return _speedMultiplier;
+ }
+
+ /**
+ * @private
+ */
+ public function set speedMultiplier(value:Number):void {
+ _speedMultiplier = value;
+ }
+
+ /**
+ * Активность управления камеры.
+ *
+ * @default false
+ */
+ public function get controlsEnabled():Boolean {
+ return _controlsEnabled;
+ }
+
+ /**
+ * @private
+ */
+ public function set controlsEnabled(value:Boolean):void {
+ if (_camera == null || _controlsEnabled == value) return;
+ if (value) {
+ lastFrameTime = getTimer();
+ registerEventListeners();
+ }
+ else {
+ unregisterEventsListeners();
+ }
+ _controlsEnabled = value;
+ }
+
+ /**
+ * Базовый шаг изменения угла зрения в радианах. Реальный шаг получаеся умножением этого значения на величину
+ * MouseEvent.delta.
+ *
+ * @default Math.PI / 180
+ */
+ public function get fovStep():Number {
+ return _fovStep;
+ }
+
+ /**
+ * @private
+ */
+ public function set fovStep(value:Number):void {
+ _fovStep = value;
+ }
+
+ /**
+ * Множитель при изменении коэффициента увеличения. Закон изменения коэффициента увеличения описывается формулой:
+ * zoom (1 + MouseEvent.delta zoomMultiplier).
+ *
+ * @default 0.1
+ */
+ public function get zoomMultiplier():Number {
+ return _zoomMultiplier;
+ }
+
+ /**
+ * @private
+ */
+ public function set zoomMultiplier(value:Number):void {
+ _zoomMultiplier = value;
+ }
+
+ /**
+ * @private
+ */
+ private function registerEventListeners():void {
+ _eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey);
+ _eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKey);
+ _eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
+ _eventsSource.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
+ }
+
+ /**
+ * @private
+ */
+ private function unregisterEventsListeners():void {
+ _eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey);
+ _eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKey);
+ _eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
+ _eventsSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ _eventsSource.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
+ }
+
+ /**
+ * @private
+ * @param e
+ */
+ private function onKey(e:KeyboardEvent):void {
+ var method:Function = keyBindings[e.keyCode];
+ if (method != null) {
+ method.call(this, e.type == KeyboardEvent.KEY_DOWN);
+ }
+ }
+
+ /**
+ * @private
+ * @param e
+ */
+ private function onMouseDown(e:MouseEvent):void {
+ mouseLookActive = true;
+ currentDragCoords.x = startDragCoords.x = _eventsSource.stage.mouseX;
+ currentDragCoords.y = startDragCoords.y = _eventsSource.stage.mouseY;
+ startRotX = _camera.rotationX;
+ startRotZ = _camera.rotationZ;
+ _eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * @private
+ * @param e
+ */
+ private function onMouseUp(e:MouseEvent):void {
+ mouseLookActive = false;
+ _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * @private
+ * @param e
+ */
+ private function onMouseWheel(e:MouseEvent):void {
+ if (_camera.orthographic) {
+ _camera.zoom = _camera.zoom * (1 + e.delta * _zoomMultiplier);
+ } else {
+ _camera.fov -= _fovStep * e.delta;
+ }
+ }
+
+ /**
+ * Обработка управляющих воздействий.
+ * Метод должен вызываться каждый кадр перед вызовом Scene3D.calculate().
+ *
+ * @see alternativa.engine3d.core.Scene3D#calculate()
+ */
+ public function processInput(): void {
+ if (!_controlsEnabled || _camera == null) return;
+
+ // Время в секундах от начала предыдущего кадра
+ var frameTime:Number = getTimer() - lastFrameTime;
+ lastFrameTime += frameTime;
+ frameTime /= 1000;
+
+ // Обработка mouselook
+ if (mouseLookActive) {
+ prevDragCoords.x = currentDragCoords.x;
+ prevDragCoords.y = currentDragCoords.y;
+ currentDragCoords.x = _eventsSource.stage.mouseX;
+ currentDragCoords.y = _eventsSource.stage.mouseY;
+ if (!prevDragCoords.equals(currentDragCoords)) {
+ _camera.rotationZ = startRotZ + (startDragCoords.x - currentDragCoords.x) * _mouseYawCoeff;
+ var rotX:Number = startRotX + (startDragCoords.y - currentDragCoords.y) * _mousePitchCoeff;
+ _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
+ }
+ }
+
+ // Поворот относительно вертикальной оси (рысканье, yaw)
+ if (_yawLeft) {
+ _camera.rotationZ += _yawSpeed * frameTime;
+ } else if (_yawRight) {
+ _camera.rotationZ -= _yawSpeed * frameTime;
+ }
+
+ // Поворот относительно поперечной оси (тангаж, pitch)
+ if (_pitchUp) {
+ rotX = _camera.rotationX + _pitchSpeed * frameTime;
+ _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
+ } else if (_pitchDown) {
+ rotX = _camera.rotationX - _pitchSpeed * frameTime;
+ _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
+ }
+
+ // TODO: Поворот относительно продольной оси (крен, roll)
+
+ var frameDistance:Number = _speed * frameTime;
+ if (_accelerate) {
+ frameDistance *= _speedMultiplier;
+ }
+ velocity.x = 0;
+ velocity.y = 0;
+ velocity.z = 0;
+ var transformation:Matrix3D = _camera.transformation;
+
+ if (_moveLocal) {
+ // Режим относительных пермещений
+ // Движение вперед-назад
+ if (_forward) {
+ velocity.x += frameDistance * transformation.c;
+ velocity.y += frameDistance * transformation.g;
+ velocity.z += frameDistance * transformation.k;
+ } else if (_back) {
+ velocity.x -= frameDistance * transformation.c;
+ velocity.y -= frameDistance * transformation.g;
+ velocity.z -= frameDistance * transformation.k;
+ }
+ // Движение влево-вправо
+ if (_left) {
+ velocity.x -= frameDistance * transformation.a;
+ velocity.y -= frameDistance * transformation.e;
+ velocity.z -= frameDistance * transformation.i;
+ } else if (_right) {
+ velocity.x += frameDistance * transformation.a;
+ velocity.y += frameDistance * transformation.e;
+ velocity.z += frameDistance * transformation.i;
+ }
+ // Движение вверх-вниз
+ if (_up) {
+ velocity.x -= frameDistance * transformation.b;
+ velocity.y -= frameDistance * transformation.f;
+ velocity.z -= frameDistance * transformation.j;
+ } else if (_down) {
+ velocity.x += frameDistance * transformation.b;
+ velocity.y += frameDistance * transformation.f;
+ velocity.z += frameDistance * transformation.j;
+ }
+ }
+ else {
+ // Режим глобальных перемещений
+ var cosZ:Number = Math.cos(_camera.rotationZ);
+ var sinZ:Number = Math.sin(_camera.rotationZ);
+ // Движение вперед-назад
+ if (_forward) {
+ velocity.x -= frameDistance * sinZ;
+ velocity.y += frameDistance * cosZ;
+ } else if (_back) {
+ velocity.x += frameDistance * sinZ;
+ velocity.y -= frameDistance * cosZ;
+ }
+ // Движение влево-вправо
+ if (_left) {
+ velocity.x -= frameDistance * cosZ;
+ velocity.y -= frameDistance * sinZ;
+ } else if (_right) {
+ velocity.x += frameDistance * cosZ;
+ velocity.y += frameDistance * sinZ;
+ }
+ // Движение вверх-вниз
+ if (_up) {
+ velocity.z += frameDistance;
+ } else if (_down) {
+ velocity.z -= frameDistance;
+ }
+ }
+
+ // Коррекция модуля вектора скорости
+ if (velocity.x != 0 || velocity.y != 0 || velocity.z != 0) {
+ velocity.length = frameDistance;
+ }
+
+ // Проверка столкновений
+ if (_checkCollisions) {
+ _collider.calculateDestination(_camera.coords, velocity, destination);
+ _camera.x = destination.x;
+ _camera.y = destination.y;
+ _camera.z = destination.z;
+ } else {
+ _camera.x += velocity.x;
+ _camera.y += velocity.y;
+ _camera.z += velocity.z;
+ }
+
+ // Обработка начала/окончания движения
+ if (_camera.changeRotationOrScaleOperation.queued || _camera.changeCoordsOperation.queued) {
+ if (!_isMoving) {
+ _isMoving = true;
+ if (_onStartMoving != null) {
+ _onStartMoving.call(this);
+ }
+ }
+ } else {
+ if (_isMoving) {
+ _isMoving = false;
+ if (_onStopMoving != null) {
+ _onStopMoving.call(this);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/controllers/FlyController.as b/Alternativa3D5/5.4/alternativa/engine3d/controllers/FlyController.as
new file mode 100644
index 0000000..7f18af5
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/controllers/FlyController.as
@@ -0,0 +1,450 @@
+package alternativa.engine3d.controllers {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.utils.KeyboardUtils;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.DisplayObject;
+
+ use namespace alternativa3d;
+
+ /**
+ * Контроллер, реализующий управление, подобное управлению летательным аппаратом для объекта, находящегося в системе
+ * координат корневого объекта сцены. Повороты выполняются вокруг локальных осей объекта, собственные ускорения
+ * действуют вдоль локальных осей.
+ *
+ *
+ *
+ *
+ *
+ *
+ * Ось Направление Поворот
+ *
+ *
+ * X Вправо Тангаж
+ *
+ *
+ * Y Вперёд Крен
+ *
+ *
+ * Z Вверх Рысканье
+ *
+ *
+ *
+ * Поворот мышью реализован следующим образом: в момент активации режима поворота (нажата левая кнопка мыши или
+ * соответствующая кнопка на клавиатуре) текущее положение курсора становится точкой, относительно которой определяются
+ * дальнейшие отклонения. Отклонение курсора по вертикали в пикселях, умноженное на коэффициент чувствительности мыши
+ * по вертикали даёт угловую скорость по тангажу. Отклонение курсора по горизонтали в пикселях, умноженное на коэффициент
+ * чувствительности мыши по горизонтали даёт угловую скорость по крену.
+ */
+ public class FlyController extends ObjectController {
+ /**
+ * Имя действия для привязки клавиш поворота по крену влево.
+ */
+ public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT";
+ /**
+ * Имя действия для привязки клавиш поворота по крену вправо.
+ */
+ public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT";
+
+ private var _rollLeft:Boolean;
+ private var _rollRight:Boolean;
+
+ private var _rollSpeed:Number = 1;
+
+ private var rotations:Point3D;
+ private var rollMatrix:Matrix3D = new Matrix3D();
+ private var transformation:Matrix3D = new Matrix3D();
+ private var axis:Point3D = new Point3D();
+
+ private var velocity:Point3D = new Point3D();
+ private var displacement:Point3D = new Point3D();
+ private var destination:Point3D = new Point3D();
+ private var deltaVelocity:Point3D = new Point3D();
+ private var accelerationVector:Point3D = new Point3D();
+ private var currentTransform:Matrix3D = new Matrix3D();
+
+ private var _currentSpeed:Number = 1;
+ /**
+ * Текущие координаты мышиного курсора в режиме mouse look.
+ */
+ private var currentMouseCoords:Point3D = new Point3D();
+
+ /**
+ * Модуль вектора ускорния, получаемого от команд движения.
+ */
+ public var acceleration:Number = 1000;
+ /**
+ * Модуль вектора замедляющего ускорения.
+ */
+ public var deceleration:Number = 50;
+ /**
+ * Погрешность определения скорости. Скорость приравнивается к нулю, если её модуль не превышает заданного значения.
+ */
+ public var speedThreshold:Number = 1;
+ /**
+ * Переключение инерционного режима. В инерционном режиме отсутствует замедляющее ускорение, в результате чего вектор
+ * скорости объекта остаётся постоянным, если нет управляющих воздействий. При выключенном инерционном режиме к объекту
+ * прикладывается замедляющее ускорение.
+ */
+ public var inertialMode:Boolean;
+
+ /**
+ * @inheritDoc
+ */
+ public function FlyController(eventsSourceObject:DisplayObject) {
+ super(eventsSourceObject);
+
+ actionBindings[ACTION_ROLL_LEFT] = rollLeft;
+ actionBindings[ACTION_ROLL_RIGHT] = rollRight;
+ }
+
+ /**
+ * Текущая скорость движения.
+ */
+ public function get currentSpeed():Number {
+ return _currentSpeed;
+ }
+
+ /**
+ * Активация вращения по крену влево.
+ */
+ public function rollLeft(value:Boolean):void {
+ _rollLeft = value;
+ }
+
+ /**
+ * Активация вращения по крену вправо.
+ */
+ public function rollRight(value:Boolean):void {
+ _rollRight = value;
+ }
+
+ /**
+ * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
+ *
+ *
+ * Ось Направление Поворот
+ *
+ *
+ * X Вправо Тангаж
+ *
+ *
+ * Y Вниз Рысканье
+ *
+ *
+ * Z Вперёд Крен
+ *
+ *
+ */
+ override public function setDefaultBindings():void {
+ unbindAll();
+ bindKey(KeyboardUtils.W, ACTION_FORWARD);
+ bindKey(KeyboardUtils.S, ACTION_BACK);
+ bindKey(KeyboardUtils.A, ACTION_LEFT);
+ bindKey(KeyboardUtils.D, ACTION_RIGHT);
+ bindKey(KeyboardUtils.SPACE, ACTION_UP);
+ bindKey(KeyboardUtils.CONTROL, ACTION_DOWN);
+ bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
+ bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
+ bindKey(KeyboardUtils.LEFT, ACTION_ROLL_LEFT);
+ bindKey(KeyboardUtils.RIGHT, ACTION_ROLL_RIGHT);
+ bindKey(KeyboardUtils.Q, ACTION_YAW_LEFT);
+ bindKey(KeyboardUtils.E, ACTION_YAW_RIGHT);
+ bindKey(KeyboardUtils.M, ACTION_MOUSE_LOOK);
+ }
+
+ /**
+ * Метод выполняет поворот объекта относительно локальных осей в соответствии с имеющимися воздействиями.
+ *
+ * @param frameTime длительность текущего кадра в секундах
+ */
+ override protected function rotateObject(frameTime:Number):void {
+ var transformation:Matrix3D = _object.transformation;
+ if (_mouseLookActive) {
+ currentMouseCoords.x = _eventsSource.stage.mouseX;
+ currentMouseCoords.y = _eventsSource.stage.mouseY;
+ if (!currentMouseCoords.equals(startMouseCoords)) {
+ var deltaYaw:Number = (currentMouseCoords.x - startMouseCoords.x) * _mouseCoefficientX;
+ if (_object is Camera3D) {
+ axis.x = transformation.c;
+ axis.y = transformation.g;
+ axis.z = transformation.k;
+ } else {
+ axis.x = transformation.b;
+ axis.y = transformation.f;
+ axis.z = transformation.j;
+ }
+
+ rotateObjectAroundAxis(axis, deltaYaw * frameTime);
+
+ currentTransform.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1);
+ var deltaPitch:Number = (startMouseCoords.y - currentMouseCoords.y) * _mouseCoefficientY;
+ axis.x = currentTransform.a;
+ axis.y = currentTransform.e;
+ axis.z = currentTransform.i;
+
+ rotateObjectAroundAxis(axis, deltaPitch * frameTime);
+ }
+ }
+
+ // Поворот относительно продольной оси (крен, roll)
+ if (_rollLeft) {
+ if (_object is Camera3D) {
+ axis.x = transformation.c;
+ axis.y = transformation.g;
+ axis.z = transformation.k;
+ } else {
+ axis.x = transformation.b;
+ axis.y = transformation.f;
+ axis.z = transformation.j;
+ }
+ rotateObjectAroundAxis(axis, -_rollSpeed * frameTime);
+ } else if (_rollRight) {
+ if (_object is Camera3D) {
+ axis.x = transformation.c;
+ axis.y = transformation.g;
+ axis.z = transformation.k;
+ } else {
+ axis.x = transformation.b;
+ axis.y = transformation.f;
+ axis.z = transformation.j;
+ }
+ rotateObjectAroundAxis(axis, _rollSpeed * frameTime);
+ }
+
+ // Поворот относительно поперечной оси (тангаж, pitch)
+ if (_pitchUp) {
+ axis.x = transformation.a;
+ axis.y = transformation.e;
+ axis.z = transformation.i;
+ rotateObjectAroundAxis(axis, _pitchSpeed * frameTime);
+ } else if (_pitchDown) {
+ axis.x = transformation.a;
+ axis.y = transformation.e;
+ axis.z = transformation.i;
+ rotateObjectAroundAxis(axis, -_pitchSpeed * frameTime);
+ }
+
+ // Поворот относительно вертикальной оси (рысканье, yaw)
+ if (_yawRight) {
+ if (_object is Camera3D) {
+ axis.x = transformation.b;
+ axis.y = transformation.f;
+ axis.z = transformation.j;
+ rotateObjectAroundAxis(axis, _yawSpeed * frameTime);
+ } else {
+ axis.x = transformation.c;
+ axis.y = transformation.g;
+ axis.z = transformation.k;
+ rotateObjectAroundAxis(axis, -_yawSpeed * frameTime);
+ }
+ } else if (_yawLeft) {
+ if (_object is Camera3D) {
+ axis.x = transformation.b;
+ axis.y = transformation.f;
+ axis.z = transformation.j;
+ rotateObjectAroundAxis(axis, -_yawSpeed * frameTime);
+ } else {
+ axis.x = transformation.c;
+ axis.y = transformation.g;
+ axis.z = transformation.k;
+ rotateObjectAroundAxis(axis, _yawSpeed * frameTime);
+ }
+ }
+ }
+
+ /**
+ * Метод вычисляет вектор потенциального смещения эллипсоида.
+ *
+ * @param frameTime длительность текущего кадра в секундах
+ * @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта
+ */
+ override protected function getDisplacement(frameTime:Number, displacement:Point3D):void {
+ // Движение вперед-назад
+ accelerationVector.x = 0;
+ accelerationVector.y = 0;
+ accelerationVector.z = 0;
+ if (_forward) {
+ accelerationVector.y = 1;
+ } else if (_back) {
+ accelerationVector.y = -1;
+ }
+ // Движение влево-вправо
+ if (_right) {
+ accelerationVector.x = 1;
+ } else if (_left) {
+ accelerationVector.x = -1;
+ }
+ // Движение ввверх-вниз
+ if (_up) {
+ accelerationVector.z = 1;
+ } else if (_down) {
+ accelerationVector.z = -1;
+ }
+
+ var speedLoss:Number;
+ var len:Number;
+
+ if (accelerationVector.x != 0 || accelerationVector.y != 0 || accelerationVector.z != 0) {
+ // Управление активно
+ if (_object is Camera3D) {
+ var tmp:Number = accelerationVector.z;
+ accelerationVector.z = accelerationVector.y;
+ accelerationVector.y = -tmp;
+ }
+ accelerationVector.normalize();
+ accelerationVector.x *= acceleration;
+ accelerationVector.y *= acceleration;
+ accelerationVector.z *= acceleration;
+ currentTransform.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1);
+ accelerationVector.transform(currentTransform);
+ deltaVelocity.x = accelerationVector.x;
+ deltaVelocity.y = accelerationVector.y;
+ deltaVelocity.z = accelerationVector.z;
+ deltaVelocity.x *= frameTime;
+ deltaVelocity.y *= frameTime;
+ deltaVelocity.z *= frameTime;
+
+ if (!inertialMode) {
+ speedLoss = deceleration * frameTime;
+ var dot:Number = Point3D.dot(velocity, accelerationVector);
+ if (dot > 0) {
+ len = accelerationVector.length;
+ var x:Number = accelerationVector.x / len;
+ var y:Number = accelerationVector.y / len;
+ var z:Number = accelerationVector.z / len;
+ len = dot / len;
+ x = velocity.x - len * x;
+ y = velocity.y - len * y;
+ z = velocity.z - len * z;
+ len = Math.sqrt(x*x + y*y + z*z);
+ if (len > speedLoss) {
+ x *= speedLoss / len;
+ y *= speedLoss / len;
+ z *= speedLoss / len;
+ }
+ velocity.x -= x;
+ velocity.y -= y;
+ velocity.z -= z;
+ } else {
+ len = velocity.length;
+ velocity.length = (len > speedLoss) ? (len - speedLoss) : 0;
+ }
+ }
+
+ velocity.x += deltaVelocity.x;
+ velocity.y += deltaVelocity.y;
+ velocity.z += deltaVelocity.z;
+
+ if (velocity.length > _speed) {
+ velocity.length = _speed;
+ }
+ } else {
+ // Управление неактивно
+ if (!inertialMode) {
+ speedLoss = deceleration * frameTime;
+ len = velocity.length;
+ velocity.length = (len > speedLoss) ? (len - speedLoss) : 0;
+ }
+ }
+
+ displacement.x = velocity.x * frameTime;
+ displacement.y = velocity.y * frameTime;
+ displacement.z = velocity.z * frameTime;
+ }
+
+ /**
+ * Метод применяет потенциальный вектор смещения к эллипсоиду с учётом столкновений с геометрией сцены, если включён
+ * соотвествующий режим.
+ *
+ * @param frameTime время кадра в секундах
+ * @param displacement векотр потенциального смещения эллипсоида
+ */
+ override protected function applyDisplacement(frameTime:Number, displacement:Point3D):void {
+ if (checkCollisions) {
+ _collider.calculateDestination(_coords, displacement, destination);
+
+ displacement.x = destination.x - _coords.x;
+ displacement.y = destination.y - _coords.y;
+ displacement.z = destination.z - _coords.z;
+ } else {
+ destination.x = _coords.x + displacement.x;
+ destination.y = _coords.y + displacement.y;
+ destination.z = _coords.z + displacement.z;
+ }
+
+ velocity.x = displacement.x / frameTime;
+ velocity.y = displacement.y / frameTime;
+ velocity.z = displacement.z / frameTime;
+
+ _coords.x = destination.x;
+ _coords.y = destination.y;
+ _coords.z = destination.z;
+ setObjectCoords();
+
+ var len:Number = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
+ if (len < speedThreshold) {
+ velocity.x = 0;
+ velocity.y = 0;
+ velocity.z = 0;
+ _currentSpeed = 0;
+ } else {
+ _currentSpeed = len;
+ }
+ }
+
+ /**
+ * Поворот объекта вокруг заданной оси.
+ *
+ * @param axis
+ * @param angle
+ */
+ private function rotateObjectAroundAxis(axis:Point3D, angle:Number):void {
+ transformation.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1);
+ rollMatrix.fromAxisAngle(axis, angle);
+ rollMatrix.inverseCombine(transformation);
+ rotations = rollMatrix.getRotations(rotations);
+ _object.rotationX = rotations.x;
+ _object.rotationY = rotations.y;
+ _object.rotationZ = rotations.z;
+ }
+
+ /**
+ * Направление объекта на точку. В результате работы метода локальная ось объекта, соответствующая направлению "вперёд"
+ * будет направлена на указанную точку, а угол поворота вокруг этой оси будет равен нулю.
+ *
+ * @param point координаты точки, на которую должен быть направлен объект
+ */
+ public function lookAt(point:Point3D):void {
+ if (_object == null) {
+ return;
+ }
+ var dx:Number = point.x - _object.x;
+ var dy:Number = point.y - _object.y;
+ var dz:Number = point.z - _object.z;
+ _object.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - (_object is Camera3D ? MathUtils.DEG90 : 0);
+ _object.rotationY = 0;
+ _object.rotationZ = -Math.atan2(dx, dy);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/controllers/ObjectController.as b/Alternativa3D5/5.4/alternativa/engine3d/controllers/ObjectController.as
new file mode 100644
index 0000000..330c337
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/controllers/ObjectController.as
@@ -0,0 +1,839 @@
+package alternativa.engine3d.controllers {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.physics.EllipsoidCollider;
+ import alternativa.types.Map;
+ import alternativa.types.Point3D;
+ import alternativa.utils.MathUtils;
+ import alternativa.utils.ObjectUtils;
+
+ import flash.display.DisplayObject;
+ import flash.events.KeyboardEvent;
+ import flash.events.MouseEvent;
+ import flash.utils.getTimer;
+
+ use namespace alternativa3d;
+
+ /**
+ * Базовый контроллер для изменения ориентации и положения объекта в сцене с помощью клавиатуры и мыши. В классе
+ * реализована поддержка назначения обработчиков клавиатурных команд, а также обработчики для основных команд
+ * перемещения.
+ */
+ public class ObjectController {
+ /**
+ * Имя действия для привязки клавиш движения вперёд.
+ */
+ public static const ACTION_FORWARD:String = "ACTION_FORWARD";
+ /**
+ * Имя действия для привязки клавиш движения назад.
+ */
+ public static const ACTION_BACK:String = "ACTION_BACK";
+ /**
+ * Имя действия для привязки клавиш движения влево.
+ */
+ public static const ACTION_LEFT:String = "ACTION_LEFT";
+ /**
+ * Имя действия для привязки клавиш движения вправо.
+ */
+ public static const ACTION_RIGHT:String = "ACTION_RIGHT";
+ /**
+ * Имя действия для привязки клавиш движения вверх.
+ */
+ public static const ACTION_UP:String = "ACTION_UP";
+ /**
+ * Имя действия для привязки клавиш движения вниз.
+ */
+ public static const ACTION_DOWN:String = "ACTION_DOWN";
+ /**
+ * Имя действия для привязки клавиш поворота вверх.
+ */
+ public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP";
+ /**
+ * Имя действия для привязки клавиш поворота вниз.
+ */
+ public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN";
+ /**
+ * Имя действия для привязки клавиш поворота налево.
+ */
+ public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT";
+ /**
+ * Имя действия для привязки клавиш поворота направо.
+ */
+ public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT";
+ /**
+ * Имя действия для привязки клавиш увеличения скорости.
+ */
+ public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE";
+ /**
+ * Имя действия для привязки клавиш активации обзора мышью.
+ */
+ public static const ACTION_MOUSE_LOOK:String = "ACTION_MOUSE_LOOK";
+
+ /**
+ * Флаг движения вперёд.
+ */
+ protected var _forward:Boolean;
+ /**
+ * Флаг движения назад.
+ */
+ protected var _back:Boolean;
+ /**
+ * Флаг движения влево.
+ */
+ protected var _left:Boolean;
+ /**
+ * Флаг движения вправо.
+ */
+ protected var _right:Boolean;
+ /**
+ * Флаг движения вверх.
+ */
+ protected var _up:Boolean;
+ /**
+ * Флаг движения вниз.
+ */
+ protected var _down:Boolean;
+ /**
+ * Флаг поворота относительно оси X в положительном направлении (взгляд вверх).
+ */
+ protected var _pitchUp:Boolean;
+ /**
+ * Флаг поворота относительно оси X в отрицательном направлении (взгляд вниз).
+ */
+ protected var _pitchDown:Boolean;
+ /**
+ * Флаг поворота относительно оси Z в положительном направлении (взгляд налево).
+ */
+ protected var _yawLeft:Boolean;
+ /**
+ * Флаг активности поворота относительно оси Z в отрицательном направлении (взгляд направо).
+ */
+ protected var _yawRight:Boolean;
+ /**
+ * Флаг активности режима ускорения.
+ */
+ protected var _accelerate:Boolean;
+ /**
+ * Флаг активности режима поворотов мышью.
+ */
+ protected var _mouseLookActive:Boolean;
+ /**
+ * Начальные координаты мышиного курсора в режиме mouse look.
+ */
+ protected var startMouseCoords:Point3D = new Point3D();
+ /**
+ * Флаг активности контроллера.
+ */
+ protected var _enabled:Boolean = true;
+ /**
+ * Источник событий клавиатуры и мыши
+ */
+ protected var _eventsSource:DisplayObject;
+ /**
+ * Ассоциативный массив, связывающий коды клавиатурных клавиш с именами команд.
+ */
+ protected var keyBindings:Map = new Map();
+ /**
+ * Ассоциативный массив, связывающий имена команд с реализующими их функциями. Функции должны иметь вид
+ * function(value:Boolean):void. Значение параметра
+ * Клавиша Действие
+ * W ACTION_FORWARD
+ * S ACTION_BACK
+ * A ACTION_LEFT
+ * D ACTION_RIGHT
+ * SPACE ACTION_UP
+ * CONTROL ACTION_DOWN
+ * UP ACTION_PITCH_UP
+ * DOWN ACTION_PITCH_DOWN
+ * LEFT ACTION_ROLL_LEFT
+ * RIGHT ACTION_ROLL_RIGHT
+ * Q ACTION_YAW_LEFT
+ * E ACTION_YAW_RIGHT
+ * M ACTION_MOUSE_LOOK value указывает, нажата или отпущена соответсвующая команде
+ * клавиша.
+ */
+ protected var actionBindings:Map = new Map();
+ /**
+ * Флаг активности клавиатуры.
+ */
+ protected var _keyboardEnabled:Boolean;
+ /**
+ * Флаг активности мыши.
+ */
+ protected var _mouseEnabled:Boolean;
+ /**
+ * Общая чувствительность мыши. Коэффициент умножения чувствительности по вертикали и горизонтали.
+ */
+ protected var _mouseSensitivity:Number = 1;
+ /**
+ * Коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
+ */
+ protected var _mouseSensitivityY:Number = Math.PI / 360;
+ /**
+ * Коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
+ */
+ protected var _mouseSensitivityX:Number = Math.PI / 360;
+ /**
+ * Результирующий коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
+ */
+ protected var _mouseCoefficientY:Number = _mouseSensitivity * _mouseSensitivityY;
+ /**
+ * Результирующий коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
+ */
+ protected var _mouseCoefficientX:Number = _mouseSensitivity * _mouseSensitivityX;
+ /**
+ * Угловая скорость поворота вокруг поперечной оси в радианах за секунду.
+ */
+ protected var _pitchSpeed:Number = 1;
+ /**
+ * Угловая скорость поворота вокруг вертикальной оси в радианах за секунду.
+ */
+ protected var _yawSpeed:Number = 1;
+ /**
+ * Скорость поступательного движения в единицах за секунду.
+ */
+ protected var _speed:Number = 100;
+ /**
+ * Коэффициент увеличения скорости при соответствующей активной команде.
+ */
+ protected var _speedMultiplier:Number = 2;
+ /**
+ * Управляемый объект.
+ */
+ protected var _object:Object3D;
+ /**
+ * Время в секундах, прошедшее с последнего вызова метода processInput (обычно с последнего кадра).
+ */
+ protected var lastFrameTime:uint;
+ /**
+ * Текущие координаты контроллера.
+ */
+ protected var _coords:Point3D = new Point3D();
+ /**
+ * Индикатор движения объекта (перемещения или поворота) в текущем кадре.
+ */
+ protected var _isMoving:Boolean;
+ /**
+ * Объект для определения столкновений.
+ */
+ protected var _collider:EllipsoidCollider = new EllipsoidCollider();
+
+ /**
+ * Включение и выключение режима проверки столкновений.
+ */
+ public var checkCollisions:Boolean;
+ /**
+ * Функция вида function():void, вызываемая при начале движения объекта. Под движением
+ * понимается изменение координат или ориентации.
+ */
+ public var onStartMoving:Function;
+ /**
+ * Функция вида function():void, вызываемая при прекращении движения объекта. Под движением
+ * понимается изменение координат или ориентации.
+ */
+ public var onStopMoving:Function;
+
+ // Вектор смещения
+ private var _displacement:Point3D = new Point3D();
+
+ /**
+ * Создаёт новый экземпляр контролллера.
+ *
+ * @param eventsSourceObject источник событий клавиатуры и мыши
+ */
+ public function ObjectController(eventsSourceObject:DisplayObject) {
+ if (eventsSourceObject == null) {
+ throw new ArgumentError(ObjectUtils.getClassName(this) + ": eventsSourceObject is null");
+ }
+ _eventsSource = eventsSourceObject;
+
+ actionBindings[ACTION_FORWARD] = moveForward;
+ actionBindings[ACTION_BACK] = moveBack;
+ actionBindings[ACTION_LEFT] = moveLeft;
+ actionBindings[ACTION_RIGHT] = moveRight;
+ actionBindings[ACTION_UP] = moveUp;
+ actionBindings[ACTION_DOWN] = moveDown;
+ actionBindings[ACTION_PITCH_UP] = pitchUp;
+ actionBindings[ACTION_PITCH_DOWN] = pitchDown;
+ actionBindings[ACTION_YAW_LEFT] = yawLeft;
+ actionBindings[ACTION_YAW_RIGHT] = yawRight;
+ actionBindings[ACTION_ACCELERATE] = accelerate;
+ actionBindings[ACTION_MOUSE_LOOK] = setMouseLook;
+
+ keyboardEnabled = true;
+ mouseEnabled = true;
+ }
+
+ /**
+ * Включение и выключение контроллера. Выключенный контроллер пропускает выполнение метода processInput().
+ *
+ * @default true
+ *
+ * @see #processInput()
+ */
+ public function get enabled():Boolean {
+ return _enabled;
+ }
+
+ /**
+ * @private
+ */
+ public function set enabled(value:Boolean):void {
+ _enabled = value;
+ }
+
+ /**
+ * Координаты контроллера. Координаты совпадают с координатами центра эллипсоида, используемого для определения
+ * столкновений. Координаты управляемого объекта могут не совпадать с координатами контроллера.
+ *
+ * @see #setObjectCoords()
+ */
+ public function get coords():Point3D {
+ return _coords.clone();
+ }
+
+ /**
+ * @private
+ */
+ public function set coords(value:Point3D):void {
+ _coords.copy(value);
+ setObjectCoords();
+ }
+
+ /**
+ * Чтение координат контроллера в заданную переменную.
+ *
+ * @param point переменная, в которую записываются координаты контроллера
+ */
+ public function readCoords(point:Point3D):void {
+ point.copy(_coords);
+ }
+
+ /**
+ * Управляемый объект.
+ */
+ public function get object():Object3D {
+ return _object;
+ }
+
+ /**
+ * @private
+ * При установке объекта устанавливается сцена для коллайдера.
+ */
+ public function set object(value:Object3D):void {
+ _object = value;
+ _collider.scene = _object == null ? null : _object.scene;
+ }
+
+ /**
+ * Объект, реализующий проверку столкновений для эллипсоида.
+ */
+ public function get collider():EllipsoidCollider {
+ return _collider;
+ }
+
+ /**
+ * Активация движения вперёд.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveForward(value:Boolean):void {
+ _forward = value;
+ }
+
+ /**
+ * Активация движения назад.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveBack(value:Boolean):void {
+ _back = value;
+ }
+
+ /**
+ * Активация движения влево.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveLeft(value:Boolean):void {
+ _left = value;
+ }
+
+ /**
+ * Активация движения вправо.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveRight(value:Boolean):void {
+ _right = value;
+ }
+
+ /**
+ * Активация движения вверх.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveUp(value:Boolean):void {
+ _up = value;
+ }
+
+ /**
+ * Активация движения вниз.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveDown(value:Boolean):void {
+ _down = value;
+ }
+
+ /**
+ * Активация поворота вверх.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function pitchUp(value:Boolean):void {
+ _pitchUp = value;
+ }
+
+ /**
+ * Активация поворота вниз.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function pitchDown(value:Boolean):void {
+ _pitchDown = value;
+ }
+
+ /**
+ * Активация поворота влево.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function yawLeft(value:Boolean):void {
+ _yawLeft = value;
+ }
+
+ /**
+ * Активация поворота вправо.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function yawRight(value:Boolean):void {
+ _yawRight = value;
+ }
+
+ /**
+ * Активация режима увеличенной скорости.
+ *
+ * @param value true для включения ускорения, false для выключения
+ */
+ public function accelerate(value:Boolean):void {
+ _accelerate = value;
+ }
+
+ /**
+ * Угловая скорость поворота вокруг поперечной оси (радианы в секунду).
+ *
+ * @default 1
+ */
+ public function get pitchSpeed():Number {
+ return _pitchSpeed;
+ }
+
+ /**
+ * @private
+ */
+ public function set pitchSpeed(spd:Number):void {
+ _pitchSpeed = spd;
+ }
+
+ /**
+ * Угловая скорость поворота вокруг вертикальной оси (радианы в секунду).
+ *
+ * @default 1
+ */
+ public function get yawSpeed():Number {
+ return _yawSpeed;
+ }
+
+ /**
+ * @private
+ */
+ public function set yawSpeed(spd:Number):void {
+ _yawSpeed = spd;
+ }
+
+ /**
+ * Скорость движения в единицах за секунду. При установке отрицательного значения берётся модуль.
+ *
+ * @default 100
+ */
+ public function get speed():Number {
+ return _speed;
+ }
+
+ /**
+ * @private
+ */
+ public function set speed(value:Number):void {
+ _speed = value < 0 ? -value : value;
+ }
+
+ /**
+ * Коэффициент увеличения скорости при активном действии ACTION_ACCELERATE.
+ *
+ * @default 2
+ */
+ public function get speedMultiplier():Number {
+ return _speedMultiplier;
+ }
+
+ /**
+ * @private
+ */
+ public function set speedMultiplier(value:Number):void {
+ _speedMultiplier = value;
+ }
+
+ /**
+ * Чувствительность мыши — коэффициент умножения mouseSensitivityX и mouseSensitivityY.
+ *
+ * @default 1
+ *
+ * @see #mouseSensitivityY()
+ * @see #mouseSensitivityX()
+ */
+ public function get mouseSensitivity():Number {
+ return _mouseSensitivity;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseSensitivity(sensitivity:Number):void {
+ _mouseSensitivity = sensitivity;
+ _mouseCoefficientY = _mouseSensitivity * _mouseSensitivityY;
+ _mouseCoefficientX = _mouseSensitivity * _mouseSensitivityX;
+ }
+
+ /**
+ * Чувтсвительность мыши по вертикали.
+ *
+ * @default Math.PI / 360
+ *
+ * @see #mouseSensitivity()
+ * @see #mouseSensitivityX()
+ */
+ public function get mouseSensitivityY():Number {
+ return _mouseSensitivityY;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseSensitivityY(value:Number):void {
+ _mouseSensitivityY = value;
+ _mouseCoefficientY = _mouseSensitivity * _mouseSensitivityY;
+ }
+
+ /**
+ * Чувтсвительность мыши по горизонтали.
+ *
+ * @default Math.PI / 360
+ *
+ * @see #mouseSensitivity()
+ * @see #mouseSensitivityY()
+ */
+ public function get mouseSensitivityX():Number {
+ return _mouseSensitivityX;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseSensitivityX(value:Number):void {
+ _mouseSensitivityX = value;
+ _mouseCoefficientX = _mouseSensitivity * _mouseSensitivityX;
+ }
+
+ /**
+ * Включение/выключение режима вращения объекта мышью. При включении режима вполняется метод startMouseLook(),
+ * при выключении — stoptMouseLook().
+ *
+ * @see #startMouseLook()
+ * @see #stopMouseLook()
+ */
+ public function setMouseLook(value:Boolean):void {
+ if (_mouseLookActive != value) {
+ _mouseLookActive = value;
+ if (_mouseLookActive) {
+ startMouseLook();
+ } else {
+ stopMouseLook();
+ }
+ }
+ }
+
+ /**
+ * Метод выполняет необходимые действия при включении режима вращения объекта мышью.
+ * Реализация по умолчанию записывает начальные глобальные координаты курсора мыши в переменную startMouseCoords.
+ *
+ * @see #startMouseCoords
+ * @see #setMouseLook()
+ * @see #stopMouseLook()
+ */
+ protected function startMouseLook():void {
+ startMouseCoords.x = _eventsSource.stage.mouseX;
+ startMouseCoords.y = _eventsSource.stage.mouseY;
+ }
+
+ /**
+ * Метод выполняет необходимые действия при выключении вращения объекта мышью. Реализация по умолчанию не делает
+ * ничего.
+ *
+ * @see #setMouseLook()
+ * @see #startMouseLook()
+ */
+ protected function stopMouseLook():void {
+ }
+
+ /**
+ * Метод выполняет обработку всех воздействий на объект. Если объект не установлен или свойство enabled
+ * равно false, метод не выполняется.
+ *
+ *
+ */
+ public function processInput():void {
+ if (!_enabled || _object == null) {
+ return;
+ }
+ var frameTime:Number = getTimer() - lastFrameTime;
+ lastFrameTime += frameTime;
+ if (frameTime > 100) {
+ frameTime = 100;
+ }
+ frameTime /= 1000;
+
+ rotateObject(frameTime);
+ getDisplacement(frameTime, _displacement);
+ applyDisplacement(frameTime, _displacement);
+
+ // Обработка начала/окончания движения
+ if (_object.changeRotationOrScaleOperation.queued || _object.changeCoordsOperation.queued) {
+ if (!_isMoving) {
+ _isMoving = true;
+ if (onStartMoving != null) {
+ onStartMoving.call(this);
+ }
+ }
+ } else {
+ if (_isMoving) {
+ _isMoving = false;
+ if (onStopMoving != null) {
+ onStopMoving.call(this);
+ }
+ }
+ }
+ }
+
+ /**
+ * Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Реализация по умолчанию не делает ничего.
+ *
+ * @param frameTime длительность текущего кадра в секундах
+ */
+ protected function rotateObject(frameTime:Number):void {
+ }
+
+ /**
+ * Метод вычисляет потенциальное смещение объекта за кадр. Реализация по умолчанию не делает ничего.
+ *
+ * @param frameTime длительность текущего кадра в секундах
+ * @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта
+ */
+ protected function getDisplacement(frameTime:Number, displacement:Point3D):void {
+ }
+
+ /**
+ * Метод применяет потенциальное смещение объекта. Реализация по умолчанию не делает ничего.
+ *
+ * @param frameTime длительность текущего кадра в секундах
+ * @param displacement смещение объекта, которое нужно обработать
+ */
+ protected function applyDisplacement(frameTime:Number, displacement:Point3D):void {
+ }
+
+ /**
+ * Метод выполняет привязку клавиши к действию. Одной клавише может быть назначено только одно действие.
+ *
+ * @param keyCode код клавиши
+ * @param action наименование действия
+ *
+ * @see #unbindKey()
+ * @see #unbindAll()
+ */
+ public function bindKey(keyCode:uint, action:String):void {
+ var method:Function = actionBindings[action];
+ if (method != null) {
+ keyBindings[keyCode] = method;
+ }
+ }
+
+ /**
+ * Очистка привязки клавиши.
+ *
+ * @param keyCode код клавиши
+ *
+ * @see #bindKey()
+ * @see #unbindAll()
+ */
+ public function unbindKey(keyCode:uint):void {
+ keyBindings.remove(keyCode);
+ }
+
+ /**
+ * Очистка привязки всех клавиш.
+ *
+ * @see #bindKey()
+ * @see #unbindKey()
+ */
+ public function unbindAll():void {
+ keyBindings.clear();
+ }
+
+ /**
+ * Метод устанавливает привязки клавиш по умолчанию. Реализация по умолчанию не делает ничего.
+ *
+ * @see #bindKey()
+ * @see #unbindKey()
+ * @see #unbindAll()
+ */
+ public function setDefaultBindings():void {
+ }
+
+ /**
+ * Включение и выключение обработки клавиатурных событий. При включении выполняется метод registerKeyboardListeners,
+ * при выключении — unregisterKeyboardListeners.
+ *
+ * @see #registerKeyboardListeners()
+ * @see #unregisterKeyboardListeners()
+ */
+ public function get keyboardEnabled():Boolean {
+ return _keyboardEnabled;
+ }
+
+ /**
+ * @private
+ */
+ public function set keyboardEnabled(value:Boolean):void {
+ if (_keyboardEnabled != value) {
+ _keyboardEnabled = value;
+ if (_keyboardEnabled) {
+ registerKeyboardListeners();
+ } else {
+ unregisterKeyboardListeners();
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Запуск обработчиков клавиатурных команд.
+ */
+ private function onKeyboardEvent(e:KeyboardEvent):void {
+ var method:Function = keyBindings[e.keyCode];
+ if (method != null) {
+ method.call(this, e.type == KeyboardEvent.KEY_DOWN);
+ }
+ }
+
+ /**
+ * Регистрация необходимых обработчиков при включении обработки клавиатурных событий.
+ *
+ * @see #unregisterKeyboardListeners()
+ */
+ protected function registerKeyboardListeners():void {
+ _eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent);
+ _eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent);
+ }
+
+ /**
+ * Удаление обработчиков при выключении обработки клавиатурных событий.
+ *
+ * @see #registerKeyboardListeners()
+ */
+ protected function unregisterKeyboardListeners():void {
+ _eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent);
+ _eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent);
+ }
+
+ /**
+ * Включение и выключение обработки мышиных событий. При включении выполняется метод registerMouseListeners,
+ * при выключении — unregisterMouseListeners.
+ *
+ * @see #registerMouseListeners()
+ * @see #unregisterMouseListeners()
+ */
+ public function get mouseEnabled():Boolean {
+ return _mouseEnabled;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseEnabled(value:Boolean):void {
+ if (_mouseEnabled != value) {
+ _mouseEnabled = value;
+ if (_mouseEnabled) {
+ registerMouseListeners();
+ } else {
+ unregisterMouseListeners();
+ }
+ }
+ }
+
+ /**
+ * Регистрация необходимых обработчиков при включении обработки мышиных событий.
+ *
+ * @see #unregisterMouseListeners()
+ */
+ protected function registerMouseListeners():void {
+ _eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
+ }
+
+ /**
+ * Удаление используемых обработчиков при выключении обработки мышиных событий.
+ *
+ * @see #registerMouseListeners()
+ */
+ protected function unregisterMouseListeners():void {
+ _eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
+ _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * Активация mouselook
+ */
+ private function onMouseDown(e:MouseEvent):void {
+ setMouseLook(true);
+ _eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * Отключение mouselook
+ */
+ private function onMouseUp(e:MouseEvent):void {
+ setMouseLook(false);
+ _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * Установка координат управляемого объекта в зависимости от текущих координат контроллера.
+ */
+ protected function setObjectCoords():void {
+ _object.coords = _coords;
+ }
+
+ /**
+ * Индикатор режима увеличенной скорости.
+ */
+ public function get accelerated():Boolean {
+ return _accelerate;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/controllers/WalkController.as b/Alternativa3D5/5.4/alternativa/engine3d/controllers/WalkController.as
new file mode 100644
index 0000000..0dd11fc
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/controllers/WalkController.as
@@ -0,0 +1,508 @@
+package alternativa.engine3d.controllers {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.physics.Collision;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.utils.KeyboardUtils;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.DisplayObject;
+
+ use namespace alternativa3d;
+
+ /**
+ * Контроллер, реализующий управление движением объекта, находящегося в системе координат корневого объекта сцены.
+ *
+ * Camera3D, направлением "вперёд" считается направление его оси
+ * Y, направлением "вверх" — направление оси Z. Для объектов класса
+ * Camera3D направление "вперёд" совпадает с направлением локальной оси Z, а направление
+ * "вверх" противоположно направлению локальной оси Y.
+ *
+ * collider. Координаты управляемого
+ * объекта вычисляются исходя из положения центра эллипсоида и положения объекта на вертикальной оси эллипсоида,
+ * задаваемого параметром objectZPosition.
+ *
+ * ACTION_UP в режиме ходьбы при ненулевой гравитации вызывает прыжок, в остальных случаях
+ * происходит движение вверх.
+ */
+ public class WalkController extends ObjectController {
+ /**
+ * Величина ускорения свободного падения. При положительном значении сила тяжести направлена против оси Z,
+ * при отрицательном — по оси Z.
+ */
+ public var gravity:Number = 0;
+ /**
+ * Вертикальная скорость прыжка.
+ */
+ public var jumpSpeed:Number = 0;
+ /**
+ * Объект, на котором стоит эллипсоид при ненулевой гравитации.
+ */
+ private var _groundMesh:Mesh;
+ /**
+ * Погрешность определения скорости. В режиме полёта или в режиме ходьбы при нахождении на поверхности
+ * скорость приравнивается к нулю, если её модуль не превышает заданного значения.
+ */
+ public var speedThreshold:Number = 1;
+
+ // Коэффициент эффективности управления перемещением при нахождении в воздухе в режиме ходьбы и нулевой гравитации.
+ private var _airControlCoefficient:Number = 1;
+
+ private var _currentSpeed:Number = 0;
+
+ private var minGroundCos:Number = Math.cos(MathUtils.toRadian(70));
+
+ private var destination:Point3D = new Point3D();
+ private var collision:Collision = new Collision();
+
+ private var _objectZPosition:Number = 0.5;
+ private var _flyMode:Boolean;
+ private var _onGround:Boolean;
+
+ private var velocity:Point3D = new Point3D();
+ private var tmpVelocity:Point3D = new Point3D();
+
+ private var controlsActive:Boolean;
+
+ private var inJump:Boolean;
+ private var startRotX:Number;
+ private var startRotZ:Number;
+
+ // Координаты мышиного курсора в режиме mouse look в предыдущем кадре.
+ private var prevMouseCoords:Point3D = new Point3D();
+ // Текущие координаты мышиного курсора в режиме mouse look.
+ private var currentMouseCoords:Point3D = new Point3D();
+
+ /**
+ * @inheritDoc
+ */
+ public function WalkController(eventSourceObject:DisplayObject) {
+ super(eventSourceObject);
+ }
+
+ /**
+ * Объект, на котором стоит эллипсоид при ненулевой гравитации.
+ */
+ public function get groundMesh():Mesh {
+ return _groundMesh;
+ }
+
+ /**
+ * Направление объекта на точку. В результате работы метода локальная ось объекта, соответствующая направлению "вперёд"
+ * будет направлена на указанную точку, а угол поворота вокруг этой оси будет равен нулю.
+ *
+ * @param point координаты точки, на которую должен быть направлен объект
+ */
+ public function lookAt(point:Point3D):void {
+ if (_object == null) {
+ return;
+ }
+ var dx:Number = point.x - _object.x;
+ var dy:Number = point.y - _object.y;
+ var dz:Number = point.z - _object.z;
+ _object.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - (_object is Camera3D ? MathUtils.DEG90 : 0);
+ _object.rotationY = 0;
+ _object.rotationZ = -Math.atan2(dx, dy);
+ }
+
+ /**
+ * Коэффициент эффективности управления перемещением в режиме ходьбы при нахождении в воздухе и ненулевой гравитации.
+ * Значение 0 обозначает полное отсутствие контроля, значение 1 указывает, что управление так же эффективно, как при
+ * нахождении на поверхности.
+ *
+ * @default 1
+ */
+ public function get airControlCoefficient():Number {
+ return _airControlCoefficient;
+ }
+
+ /**
+ * @private
+ */
+ public function set airControlCoefficient(value:Number):void {
+ _airControlCoefficient = value > 0 ? value : -value;
+ }
+
+ /**
+ * Максимальный угол наклона поверхности в радианах, на которой возможен прыжок и на которой объект стоит на месте
+ * в отсутствие управляющих воздействий. Если угол наклона поверхности превышает заданное значение, свойство
+ * onGround будет иметь значение false.
+ *
+ * @see #onGround
+ */
+ public function get maxGroundAngle():Number {
+ return Math.acos(minGroundCos);
+ }
+
+ /**
+ * @private
+ */
+ public function set maxGroundAngle(value:Number):void {
+ minGroundCos = Math.cos(value);
+ }
+
+ /**
+ * Положение управляемого объекта на оси Z эллипсоида. Значение 0 указывает положение в нижней точке эллипсоида,
+ * значение 1 -- положение в верхней точке эллипсоида.
+ */
+ public function get objectZPosition():Number {
+ return _objectZPosition;
+ }
+
+ /**
+ * @private
+ */
+ public function set objectZPosition(value:Number):void {
+ _objectZPosition = value;
+ setObjectCoords();
+ }
+
+ /**
+ * Включене и выключение режима полёта.
+ *
+ * @default false
+ */
+ public function get flyMode():Boolean {
+ return _flyMode;
+ }
+
+ /**
+ * @private
+ */
+ public function set flyMode(value:Boolean):void {
+ _flyMode = value;
+ }
+
+ /**
+ * Индикатор положения эллипсоида на поверхности в режиме ходьбы. Считается, что эллипсоид находится на поверхности,
+ * если угол наклона поверхности под ним не превышает заданного свойством maxGroundAngle значения.
+ *
+ * @see #maxGroundAngle
+ */
+ public function get onGround():Boolean {
+ return _onGround;
+ }
+
+ /**
+ * Модуль текущей скорости.
+ */
+ public function get currentSpeed():Number {
+ return _currentSpeed;
+ }
+
+ /**
+ * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
+ *
+ *
+ */
+ override public function setDefaultBindings():void {
+ unbindAll();
+ bindKey(KeyboardUtils.W, ACTION_FORWARD);
+ bindKey(KeyboardUtils.S, ACTION_BACK);
+ bindKey(KeyboardUtils.A, ACTION_LEFT);
+ bindKey(KeyboardUtils.D, ACTION_RIGHT);
+ bindKey(KeyboardUtils.SPACE, ACTION_UP);
+ bindKey(KeyboardUtils.CONTROL, ACTION_DOWN);
+ bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
+ bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
+ bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT);
+ bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT);
+ bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE);
+ bindKey(KeyboardUtils.M, ACTION_MOUSE_LOOK);
+ }
+
+ /**
+ * Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Взгляд вверх и вниз ограничен
+ * отклонением в 90 градусов от горизонтали.
+ *
+ * @param frameTime длительность текущего кадра в секундах
+ */
+ override protected function rotateObject(frameTime:Number):void {
+ // Mouse look
+ var rotX:Number;
+ if (_mouseLookActive) {
+ prevMouseCoords.copy(currentMouseCoords);
+ currentMouseCoords.x = _eventsSource.stage.mouseX;
+ currentMouseCoords.y = _eventsSource.stage.mouseY;
+ if (!prevMouseCoords.equals(currentMouseCoords)) {
+ _object.rotationZ = startRotZ + (startMouseCoords.x - currentMouseCoords.x) * _mouseCoefficientX;
+ rotX = startRotX + (startMouseCoords.y - currentMouseCoords.y) * _mouseCoefficientY;
+ if (_object is Camera3D) {
+ // Коррекция поворота для камеры
+ _object.rotationX = (rotX > MathUtils.DEG90) ? 0 : (rotX < -MathUtils.DEG90) ? -Math.PI : rotX - MathUtils.DEG90;
+ } else {
+ _object.rotationX = (rotX > MathUtils.DEG90) ? MathUtils.DEG90 : (rotX < -MathUtils.DEG90) ? -MathUtils.DEG90 : rotX;
+ }
+ }
+ }
+
+ // Повороты влево-вправо
+ if (_yawLeft) {
+ _object.rotationZ += _yawSpeed * frameTime;
+ } else if (_yawRight) {
+ _object.rotationZ -= _yawSpeed * frameTime;
+ }
+ // Взгляд вверх-вниз
+ rotX = NaN;
+ if (_pitchUp) {
+ rotX = _object.rotationX + _pitchSpeed * frameTime;
+ } else if (_pitchDown) {
+ rotX = _object.rotationX - _pitchSpeed * frameTime;
+ }
+ if (!isNaN(rotX)) {
+ if (_object is Camera3D) {
+ // Коррекция поворота для камеры
+ _object.rotationX = (rotX > 0) ? 0 : (rotX < -Math.PI) ? -Math.PI : rotX;
+ } else {
+ _object.rotationX = (rotX > MathUtils.DEG90) ? MathUtils.DEG90 : (rotX < -MathUtils.DEG90) ? -MathUtils.DEG90 : rotX;
+ }
+ }
+ }
+
+ /**
+ * Метод вычисляет вектор потенциального смещения эллипсоида, учитывая режим перемещения, команды перемещения и силу тяжести.
+ *
+ * @param frameTime длительность текущего кадра в секундах
+ * @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта
+ */
+ override protected function getDisplacement(frameTime:Number, displacement:Point3D):void {
+ var cos:Number = 0;
+ if (checkCollisions && !_flyMode) {
+ // Проверка наличия под ногами поверхности
+ displacement.x = 0;
+ displacement.y = 0;
+ displacement.z = - 0.5 * gravity * frameTime * frameTime;
+ if (_collider.getCollision(_coords, displacement, collision)) {
+ cos = collision.normal.z;
+ _groundMesh = collision.face._mesh;
+ } else {
+ _groundMesh = null;
+ }
+ }
+ _onGround = cos > minGroundCos;
+
+ if (_onGround && inJump) {
+ inJump = false;
+ }
+
+ var len:Number;
+ var x:Number;
+ var y:Number;
+ var z:Number;
+
+ // При наличии управляющих воздействий расчитывается приращение скорости
+ controlsActive = _forward || _back || _right || _left || _up || _down;
+ if (controlsActive) {
+ if (_flyMode) {
+ tmpVelocity.x = 0;
+ tmpVelocity.y = 0;
+ tmpVelocity.z = 0;
+ // Режим полёта, ускорения направлены вдоль локальных осей
+ // Ускорение вперёд-назад
+ if (_forward) {
+ tmpVelocity.y = 1;
+ } else if (_back) {
+ tmpVelocity.y = -1;
+ }
+ // Ускорение влево-вправо
+ if (_right) {
+ tmpVelocity.x = 1;
+ } else if (_left) {
+ tmpVelocity.x = -1;
+ }
+ // Ускорение вверх-вниз
+ if (_up) {
+ tmpVelocity.z = 1;
+ } else if (_down) {
+ tmpVelocity.z = -1;
+ }
+ var matrix:Matrix3D = _object.transformation;
+ x = tmpVelocity.x;
+ if (_object is Camera3D) {
+ y = -tmpVelocity.z;
+ z = tmpVelocity.y;
+ } else {
+ y = tmpVelocity.y;
+ z = tmpVelocity.z;
+ }
+ // Поворот вектора из локальной системы координат объекта в глобальную
+ velocity.x += (x * matrix.a + y * matrix.b + z * matrix.c) * _speed;
+ velocity.y += (x * matrix.e + y * matrix.f + z * matrix.g) * _speed;
+ velocity.z += (x * matrix.i + y * matrix.j + z * matrix.k) * _speed;
+ } else {
+
+ // Режим хождения, ускорения вперёд-назад-влево-вправо лежат в глобальной плоскости XY, вверх-вниз направлены вдоль глобальной оси Z
+ var heading:Number = _object.rotationZ;
+ var headingCos:Number = Math.cos(heading);
+ var headingSin:Number = Math.sin(heading);
+
+ var spd:Number = _speed;
+ if (gravity != 0 && !_onGround) {
+ spd *= _airControlCoefficient;
+ }
+
+ // Вперёд-назад
+ if (_forward) {
+ velocity.x -= spd * headingSin;
+ velocity.y += spd * headingCos;
+ } else if (_back) {
+ velocity.x += spd * headingSin;
+ velocity.y -= spd * headingCos;
+ }
+ // Влево-вправо
+ if (_right) {
+ velocity.x += spd * headingCos;
+ velocity.y += spd * headingSin;
+ } else if (_left) {
+ velocity.x -= spd * headingCos;
+ velocity.y -= spd * headingSin;
+ }
+ if (gravity == 0) {
+ // Ускорение вверх-вниз
+ if (_up) {
+ velocity.z += _speed;
+ } else if (_down) {
+ velocity.z -= _speed;
+ }
+ }
+ }
+ } else {
+ // Управление неактивно, замедление движения
+ len = 1 / Math.pow(3, frameTime * 10);
+ if (_flyMode || gravity == 0) {
+ velocity.x *= len;
+ velocity.y *= len;
+ velocity.z *= len;
+ } else {
+ if (_onGround) {
+ velocity.x *= len;
+ velocity.y *= len;
+ if (velocity.z < 0) {
+ velocity.z *= len;
+ }
+ } else {
+ if (cos > 0 && velocity.z > 0) {
+ velocity.z = 0;
+ }
+ }
+ }
+ }
+ // Прыжок
+ if (_onGround && _up && !inJump) {
+ velocity.z = jumpSpeed;
+ inJump = true;
+ _onGround = false;
+ cos = 0;
+ }
+ // В режиме ходьбы добавляется ускорение свободного падения, если находимся не на ровной поверхности
+ if (!(_flyMode || _onGround)) {
+ velocity.z -= gravity * frameTime;
+ }
+
+ // Ограничение скорости
+ var max:Number = _accelerate ? _speed * _speedMultiplier : _speed;
+ if (_flyMode || gravity == 0) {
+ len = velocity.length;
+ if (len > max) {
+ velocity.length = max;
+ }
+ } else {
+ len = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y);
+ if (len > max) {
+ velocity.x *= max / len;
+ velocity.y *= max / len;
+ }
+ if (cos > 0 && velocity.z > 0) {
+ velocity.z = 0;
+ }
+ }
+
+ // Cмещение за кадр
+ displacement.x = velocity.x * frameTime;
+ displacement.y = velocity.y * frameTime;
+ displacement.z = velocity.z * frameTime;
+ }
+
+ /**
+ * Метод применяет потенциальный вектор смещения к эллипсоиду с учётом столкновений с геометрией сцены, если включён
+ * соотвествующий режим.
+ *
+ * @param frameTime время кадра в секундах
+ * @param displacement векотр потенциального смещения эллипсоида
+ */
+ override protected function applyDisplacement(frameTime:Number, displacement:Point3D):void {
+ if (checkCollisions) {
+ _collider.calculateDestination(_coords, displacement, destination);
+
+ displacement.x = destination.x - _coords.x;
+ displacement.y = destination.y - _coords.y;
+ displacement.z = destination.z - _coords.z;
+ } else {
+ destination.x = _coords.x + displacement.x;
+ destination.y = _coords.y + displacement.y;
+ destination.z = _coords.z + displacement.z;
+ }
+
+ velocity.x = displacement.x / frameTime;
+ velocity.y = displacement.y / frameTime;
+ velocity.z = displacement.z / frameTime;
+
+ _coords.x = destination.x;
+ _coords.y = destination.y;
+ _coords.z = destination.z;
+ setObjectCoords();
+
+ var len:Number = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
+ if (len < speedThreshold) {
+ velocity.x = 0;
+ velocity.y = 0;
+ velocity.z = 0;
+ _currentSpeed = 0;
+ } else {
+ _currentSpeed = len;
+ }
+ }
+
+ /**
+ * Метод устанавливает координаты управляемого объекта в зависимости от параметра
+ * Клавиша Действие
+ * W ACTION_FORWARD
+ * S ACTION_BACK
+ * A ACTION_LEFT
+ * D ACTION_RIGHT
+ * SPACE ACTION_UP
+ * CONTROL ACTION_DOWN
+ * SHIFT ACTION_ACCELERATE
+ * UP ACTION_PITCH_UP
+ * DOWN ACTION_PITCH_DOWN
+ * LEFT ACTION_YAW_LEFT
+ * RIGHT ACTION_YAW_RIGHT
+ * M ACTION_MOUSE_LOOK objectZPosition.
+ *
+ * @see #objectZPosition
+ */
+ override protected function setObjectCoords():void {
+ _object.x = _coords.x;
+ _object.y = _coords.y;
+ _object.z = _coords.z + (2 * _objectZPosition - 1) * _collider.radiusZ;
+ }
+
+ /**
+ * Метод выполняет необходимые действия при включении вращения объекта мышью.
+ */
+ override protected function startMouseLook():void {
+ super.startMouseLook();
+ startRotX = _object is Camera3D ? _object.rotationX + MathUtils.DEG90 : _object.rotationX;
+ startRotZ = _object.rotationZ;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/BSPNode.as b/Alternativa3D5/5.4/alternativa/engine3d/core/BSPNode.as
new file mode 100644
index 0000000..5f3ccf3
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/core/BSPNode.as
@@ -0,0 +1,65 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ */
+ public final class BSPNode {
+
+ // Родительская нода
+ alternativa3d var parent:BSPNode;
+
+ // Дочерние ветки
+ alternativa3d var front:BSPNode;
+ alternativa3d var back:BSPNode;
+
+ // Нормаль плоскости ноды
+ alternativa3d var normal:Point3D = new Point3D();
+
+ // Смещение плоскости примитива
+ alternativa3d var offset:Number;
+
+ // Минимальная мобильность ноды
+ alternativa3d var mobility:int = int.MAX_VALUE;
+
+ // Набор примитивов в ноде
+ alternativa3d var primitive:PolyPrimitive;
+ alternativa3d var backPrimitives:Set;
+ alternativa3d var frontPrimitives:Set;
+
+ // Хранилище неиспользуемых нод
+ static private var collector:Array = new Array();
+
+ // Создать ноду на основе примитива
+ static alternativa3d function createBSPNode(primitive:PolyPrimitive):BSPNode {
+ // Достаём ноду из коллектора
+ var node:BSPNode = collector.pop();
+ // Если коллектор пуст, создаём новую ноду
+ if (node == null) {
+ node = new BSPNode();
+ }
+
+ // Добавляем примитив в ноду
+ node.primitive = primitive;
+ // Сохраняем ноду
+ primitive.node = node;
+ // Сохраняем плоскость
+ node.normal.copy(primitive.face.globalNormal);
+ node.offset = primitive.face.globalOffset;
+ // Сохраняем мобильность
+ node.mobility = primitive.mobility;
+ return node;
+ }
+
+ // Удалить ноду, все ссылки должны быть почищены
+ static alternativa3d function destroyBSPNode(node:BSPNode):void {
+ //trace(node.back, node.front, node.parent, node.primitive, node.backPrimitives, node.frontPrimitives);
+ collector.push(node);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/Camera3D.as b/Alternativa3D5/5.4/alternativa/engine3d/core/Camera3D.as
new file mode 100644
index 0000000..66ef3e7
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/core/Camera3D.as
@@ -0,0 +1,902 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.display.Skin;
+ import alternativa.engine3d.display.View;
+ import alternativa.engine3d.materials.DrawPoint;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ import flash.geom.Matrix;
+ import flash.geom.Point;
+ import alternativa.utils.MathUtils;
+
+ use namespace alternativa3d;
+
+ /**
+ * Камера для отображения 3D-сцены на экране.
+ *
+ * alternativa.engine3d.display.View.
+ *
+ * @see alternativa.engine3d.display.View
+ */
+ public class Camera3D extends Object3D {
+
+ /**
+ * @private
+ * Расчёт матрицы пространства камеры
+ */
+ alternativa3d var calculateMatrixOperation:Operation = new Operation("calculateMatrix", this, calculateMatrix, Operation.CAMERA_CALCULATE_MATRIX);
+ /**
+ * @private
+ * Расчёт плоскостей отсечения
+ */
+ alternativa3d var calculatePlanesOperation:Operation = new Operation("calculatePlanes", this, calculatePlanes, Operation.CAMERA_CALCULATE_PLANES);
+ /**
+ * @private
+ * Отрисовка
+ */
+ alternativa3d var renderOperation:Operation = new Operation("render", this, render, Operation.CAMERA_RENDER);
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ /**
+ * @private
+ * Поле зрения
+ */
+ alternativa3d var _fov:Number = Math.PI/2;
+ /**
+ * @private
+ * Фокусное расстояние
+ */
+ alternativa3d var focalLength:Number;
+ /**
+ * @private
+ * Перспективное искажение
+ */
+ alternativa3d var focalDistortion:Number;
+
+ /**
+ * @private
+ * Флаги рассчитанности UV-матриц
+ */
+ alternativa3d var uvMatricesCalculated:Set = new Set(true);
+
+ // Всмомогательные точки для расчёта UV-матриц
+ private var textureA:Point3D = new Point3D();
+ private var textureB:Point3D = new Point3D();
+ private var textureC:Point3D = new Point3D();
+
+ /**
+ * @private
+ * Вид из камеры
+ */
+ alternativa3d var _view:View;
+
+ /**
+ * @private
+ * Режим отрисовки
+ */
+ alternativa3d var _orthographic:Boolean = false;
+ private var fullDraw:Boolean;
+
+ // Масштаб
+ private var _zoom:Number = 1;
+
+ // Синус половинчатого угла обзора камеры
+ private var viewAngle:Number;
+
+ // Направление камеры
+ private var direction:Point3D = new Point3D(0, 0, 1);
+
+ // Обратная трансформация камеры
+ private var cameraMatrix:Matrix3D = new Matrix3D();
+
+ // Скины
+ private var firstSkin:Skin;
+ private var prevSkin:Skin;
+ private var currentSkin:Skin;
+
+ // Плоскости отсечения
+ private var leftPlane:Point3D = new Point3D();
+ private var rightPlane:Point3D = new Point3D();
+ private var topPlane:Point3D = new Point3D();
+ private var bottomPlane:Point3D = new Point3D();
+ private var leftOffset:Number;
+ private var rightOffset:Number;
+ private var topOffset:Number;
+ private var bottomOffset:Number;
+
+ // Вспомогательные массивы точек для отрисовки
+ private var points1:Array = new Array();
+ private var points2:Array = new Array();
+
+ /**
+ * Создание нового экземпляра камеры.
+ *
+ * @param name имя экземпляра
+ */
+ public function Camera3D(name:String = null) {
+ super(name);
+ }
+
+ /**
+ * @private
+ */
+ private function calculateMatrix():void {
+ // Расчёт матрицы пространства камеры
+ cameraMatrix.copy(transformation);
+ cameraMatrix.invert();
+ if (_orthographic) {
+ cameraMatrix.scale(_zoom, _zoom, _zoom);
+ }
+ // Направление камеры
+ direction.x = transformation.c;
+ direction.y = transformation.g;
+ direction.z = transformation.k;
+ direction.normalize();
+ }
+
+ /**
+ * @private
+ * Расчёт плоскостей отсечения
+ */
+ private function calculatePlanes():void {
+ var halfWidth:Number = _view._width*0.5;
+ var halfHeight:Number = _view._height*0.5;
+
+ var aw:Number = transformation.a*halfWidth;
+ var ew:Number = transformation.e*halfWidth;
+ var iw:Number = transformation.i*halfWidth;
+ var bh:Number = transformation.b*halfHeight;
+ var fh:Number = transformation.f*halfHeight;
+ var jh:Number = transformation.j*halfHeight;
+ if (_orthographic) {
+ // Расчёт плоскостей отсечения в изометрии
+ aw /= _zoom;
+ ew /= _zoom;
+ iw /= _zoom;
+ bh /= _zoom;
+ fh /= _zoom;
+ jh /= _zoom;
+
+ // Левая плоскость
+ leftPlane.x = transformation.f*transformation.k - transformation.j*transformation.g;
+ leftPlane.y = transformation.j*transformation.c - transformation.b*transformation.k;
+ leftPlane.z = transformation.b*transformation.g - transformation.f*transformation.c;
+ leftOffset = (transformation.d - aw)*leftPlane.x + (transformation.h - ew)*leftPlane.y + (transformation.l - iw)*leftPlane.z;
+
+ // Правая плоскость
+ rightPlane.x = -leftPlane.x;
+ rightPlane.y = -leftPlane.y;
+ rightPlane.z = -leftPlane.z;
+ rightOffset = (transformation.d + aw)*rightPlane.x + (transformation.h + ew)*rightPlane.y + (transformation.l + iw)*rightPlane.z;
+
+ // Верхняя плоскость
+ topPlane.x = transformation.g*transformation.i - transformation.k*transformation.e;
+ topPlane.y = transformation.k*transformation.a - transformation.c*transformation.i;
+ topPlane.z = transformation.c*transformation.e - transformation.g*transformation.a;
+ topOffset = (transformation.d - bh)*topPlane.x + (transformation.h - fh)*topPlane.y + (transformation.l - jh)*topPlane.z;
+
+ // Нижняя плоскость
+ bottomPlane.x = -topPlane.x;
+ bottomPlane.y = -topPlane.y;
+ bottomPlane.z = -topPlane.z;
+ bottomOffset = (transformation.d + bh)*bottomPlane.x + (transformation.h + fh)*bottomPlane.y + (transformation.l + jh)*bottomPlane.z;
+ } else {
+ // Вычисляем расстояние фокуса
+ focalLength = Math.sqrt(_view._width*_view._width + _view._height*_view._height)*0.5/Math.tan(0.5*_fov);
+ // Вычисляем минимальное (однопиксельное) искажение перспективной коррекции
+ focalDistortion = 1/(focalLength*focalLength);
+
+ // Расчёт плоскостей отсечения в перспективе
+ var cl:Number = transformation.c*focalLength;
+ var gl:Number = transformation.g*focalLength;
+ var kl:Number = transformation.k*focalLength;
+
+ // Угловые вектора пирамиды видимости
+ var leftTopX:Number = -aw - bh + cl;
+ var leftTopY:Number = -ew - fh + gl;
+ var leftTopZ:Number = -iw - jh + kl;
+ var rightTopX:Number = aw - bh + cl;
+ var rightTopY:Number = ew - fh + gl;
+ var rightTopZ:Number = iw - jh + kl;
+ var leftBottomX:Number = -aw + bh + cl;
+ var leftBottomY:Number = -ew + fh + gl;
+ var leftBottomZ:Number = -iw + jh + kl;
+ var rightBottomX:Number = aw + bh + cl;
+ var rightBottomY:Number = ew + fh + gl;
+ var rightBottomZ:Number = iw + jh + kl;
+
+ // Левая плоскость
+ leftPlane.x = leftBottomY*leftTopZ - leftBottomZ*leftTopY;
+ leftPlane.y = leftBottomZ*leftTopX - leftBottomX*leftTopZ;
+ leftPlane.z = leftBottomX*leftTopY - leftBottomY*leftTopX;
+ leftOffset = transformation.d*leftPlane.x + transformation.h*leftPlane.y + transformation.l*leftPlane.z;
+
+ // Правая плоскость
+ rightPlane.x = rightTopY*rightBottomZ - rightTopZ*rightBottomY;
+ rightPlane.y = rightTopZ*rightBottomX - rightTopX*rightBottomZ;
+ rightPlane.z = rightTopX*rightBottomY - rightTopY*rightBottomX;
+ rightOffset = transformation.d*rightPlane.x + transformation.h*rightPlane.y + transformation.l*rightPlane.z;
+
+ // Верхняя плоскость
+ topPlane.x = leftTopY*rightTopZ - leftTopZ*rightTopY;
+ topPlane.y = leftTopZ*rightTopX - leftTopX*rightTopZ;
+ topPlane.z = leftTopX*rightTopY - leftTopY*rightTopX;
+ topOffset = transformation.d*topPlane.x + transformation.h*topPlane.y + transformation.l*topPlane.z;
+
+ // Нижняя плоскость
+ bottomPlane.x = rightBottomY*leftBottomZ - rightBottomZ*leftBottomY;
+ bottomPlane.y = rightBottomZ*leftBottomX - rightBottomX*leftBottomZ;
+ bottomPlane.z = rightBottomX*leftBottomY - rightBottomY*leftBottomX;
+ bottomOffset = transformation.d*bottomPlane.x + transformation.h*bottomPlane.y + transformation.l*bottomPlane.z;
+
+
+ // Расчёт угла конуса
+ var length:Number = Math.sqrt(leftTopX*leftTopX + leftTopY*leftTopY + leftTopZ*leftTopZ);
+ leftTopX /= length;
+ leftTopY /= length;
+ leftTopZ /= length;
+ length = Math.sqrt(rightTopX*rightTopX + rightTopY*rightTopY + rightTopZ*rightTopZ);
+ rightTopX /= length;
+ rightTopY /= length;
+ rightTopZ /= length;
+ length = Math.sqrt(leftBottomX*leftBottomX + leftBottomY*leftBottomY + leftBottomZ*leftBottomZ);
+ leftBottomX /= length;
+ leftBottomY /= length;
+ leftBottomZ /= length;
+ length = Math.sqrt(rightBottomX*rightBottomX + rightBottomY*rightBottomY + rightBottomZ*rightBottomZ);
+ rightBottomX /= length;
+ rightBottomY /= length;
+ rightBottomZ /= length;
+
+ viewAngle = leftTopX*direction.x + leftTopY*direction.y + leftTopZ*direction.z;
+ var dot:Number = rightTopX*direction.x + rightTopY*direction.y + rightTopZ*direction.z;
+ viewAngle = (dot < viewAngle) ? dot : viewAngle;
+ dot = leftBottomX*direction.x + leftBottomY*direction.y + leftBottomZ*direction.z;
+ viewAngle = (dot < viewAngle) ? dot : viewAngle;
+ dot = rightBottomX*direction.x + rightBottomY*direction.y + rightBottomZ*direction.z;
+ viewAngle = (dot < viewAngle) ? dot : viewAngle;
+
+ viewAngle = Math.sin(Math.acos(viewAngle));
+ }
+ }
+
+ /**
+ * @private
+ */
+ private function render():void {
+ // Режим отрисовки
+ fullDraw = (calculateMatrixOperation.queued || calculatePlanesOperation.queued);
+
+ // Очистка рассчитанных текстурных матриц
+ uvMatricesCalculated.clear();
+
+ // Отрисовка
+ prevSkin = null;
+ currentSkin = firstSkin;
+ renderBSPNode(_scene.bsp);
+
+ // Удаление ненужных скинов
+ while (currentSkin != null) {
+ removeCurrentSkin();
+ }
+ }
+
+ /**
+ * @private
+ */
+ private function renderBSPNode(node:BSPNode):void {
+ if (node != null) {
+ var primitive:*;
+ var normal:Point3D = node.normal;
+ var cameraAngle:Number = direction.x*normal.x + direction.y*normal.y + direction.z*normal.z;
+ var cameraOffset:Number;
+ if (!_orthographic) {
+ cameraOffset = globalCoords.x*normal.x + globalCoords.y*normal.y + globalCoords.z*normal.z - node.offset;
+ }
+ if (node.primitive != null) {
+ // В ноде только базовый примитив
+ if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) {
+ // Камера спереди ноды
+ if (_orthographic || cameraAngle < viewAngle) {
+ renderBSPNode(node.back);
+ drawSkin(node.primitive);
+ }
+ renderBSPNode(node.front);
+ } else {
+ // Камера сзади ноды
+ if (_orthographic || cameraAngle > -viewAngle) {
+ renderBSPNode(node.front);
+ }
+ renderBSPNode(node.back);
+ }
+ } else {
+ // В ноде несколько примитивов
+ if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) {
+ // Камера спереди ноды
+ if (_orthographic || cameraAngle < viewAngle) {
+ renderBSPNode(node.back);
+ for (primitive in node.frontPrimitives) {
+ drawSkin(primitive);
+ }
+ }
+ renderBSPNode(node.front);
+ } else {
+ // Камера сзади ноды
+ if (_orthographic || cameraAngle > -viewAngle) {
+ renderBSPNode(node.front);
+ for (primitive in node.backPrimitives) {
+ drawSkin(primitive);
+ }
+ }
+ renderBSPNode(node.back);
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Отрисовка скина примитива
+ */
+ private function drawSkin(primitive:PolyPrimitive):void {
+ if (!fullDraw && currentSkin != null && currentSkin.primitive == primitive && !_scene.changedPrimitives[primitive]) {
+ // Пропуск скина
+ prevSkin = currentSkin;
+ currentSkin = currentSkin.nextSkin;
+ } else {
+ // Проверка поверхности
+ var surface:Surface = primitive.face._surface;
+ if (surface == null) {
+ return;
+ }
+ // Проверка материала
+ var material:SurfaceMaterial = surface._material;
+ if (material == null || !material.canDraw(primitive)) {
+ return;
+ }
+ // Отсечение выходящих за окно просмотра частей
+ var i:uint;
+ var length:uint = primitive.num;
+ var primitivePoint:Point3D;
+ var primitiveUV:Point;
+ var point:DrawPoint;
+ var useUV:Boolean = !_orthographic && material.useUV && primitive.face.uvMatrixBase;
+ if (useUV) {
+ // Формируем список точек и UV-координат полигона
+ for (i = 0; i < length; i++) {
+ primitivePoint = primitive.points[i];
+ primitiveUV = primitive.uvs[i];
+ point = points1[i];
+ if (point == null) {
+ points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z, primitiveUV.x, primitiveUV.y);
+ } else {
+ point.x = primitivePoint.x;
+ point.y = primitivePoint.y;
+ point.z = primitivePoint.z;
+ point.u = primitiveUV.x;
+ point.v = primitiveUV.y;
+ }
+ }
+ } else {
+ // Формируем список точек полигона
+ for (i = 0; i < length; i++) {
+ primitivePoint = primitive.points[i];
+ point = points1[i];
+ if (point == null) {
+ points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z);
+ } else {
+ point.x = primitivePoint.x;
+ point.y = primitivePoint.y;
+ point.z = primitivePoint.z;
+ }
+ }
+ }
+ // Отсечение по левой стороне
+ length = clip(length, points1, points2, leftPlane, leftOffset, useUV);
+ if (length < 3) {
+ return;
+ }
+ // Отсечение по правой стороне
+ length = clip(length, points2, points1, rightPlane, rightOffset, useUV);
+ if (length < 3) {
+ return;
+ }
+ // Отсечение по верхней стороне
+ length = clip(length, points1, points2, topPlane, topOffset, useUV);
+ if (length < 3) {
+ return;
+ }
+ // Отсечение по нижней стороне
+ length = clip(length, points2, points1, bottomPlane, bottomOffset, useUV);
+ if (length < 3) {
+ return;
+ }
+
+ if (fullDraw || _scene.changedPrimitives[primitive]) {
+
+ // Если конец списка скинов
+ if (currentSkin == null) {
+ // Добавляем скин в конец
+ addCurrentSkin();
+ } else {
+ if (fullDraw || _scene.changedPrimitives[currentSkin.primitive]) {
+ // Очистка скина
+ currentSkin.material.clear(currentSkin);
+ } else {
+ // Вставка скина перед текущим
+ insertCurrentSkin();
+ }
+ }
+
+ // Переводим координаты в систему камеры
+ var x:Number;
+ var y:Number;
+ var z:Number;
+ for (i = 0; i < length; i++) {
+ point = points1[i];
+ x = point.x;
+ y = point.y;
+ z = point.z;
+ point.x = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d;
+ point.y = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h;
+ point.z = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l;
+ }
+
+ // Назначаем скину примитив и материал
+ currentSkin.primitive = primitive;
+ currentSkin.material = material;
+ material.draw(this, currentSkin, length, points1);
+
+ // Переключаемся на следующий скин
+ prevSkin = currentSkin;
+ currentSkin = currentSkin.nextSkin;
+
+ } else {
+
+ // Удаление ненужных скинов
+ while (currentSkin != null && _scene.changedPrimitives[currentSkin.primitive]) {
+ removeCurrentSkin();
+ }
+
+ // Переключение на следующий скин
+ if (currentSkin != null) {
+ prevSkin = currentSkin;
+ currentSkin = currentSkin.nextSkin;
+ }
+
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Отсечение полигона плоскостью.
+ */
+ private function clip(length:uint, points1:Array, points2:Array, plane:Point3D, offset:Number, calculateUV:Boolean):uint {
+ var i:uint;
+ var k:Number;
+ var index:uint = 0;
+ var point:DrawPoint;
+ var point1:DrawPoint;
+ var point2:DrawPoint;
+ var offset1:Number;
+ var offset2:Number;
+
+ point1 = points1[length - 1];
+ offset1 = plane.x*point1.x + plane.y*point1.y + plane.z*point1.z - offset;
+
+ if (calculateUV) {
+
+ for (i = 0; i < length; i++) {
+
+ point2 = points1[i];
+ offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset;
+
+ if (offset2 > 0) {
+ if (offset1 <= 0) {
+ k = offset2/(offset2 - offset1);
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k);
+ points2[index] = point;
+ } else {
+ point.x = point2.x - (point2.x - point1.x)*k;
+ point.y = point2.y - (point2.y - point1.y)*k;
+ point.z = point2.z - (point2.z - point1.z)*k;
+ point.u = point2.u - (point2.u - point1.u)*k;
+ point.v = point2.v - (point2.v - point1.v)*k;
+ }
+ index++;
+ }
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x, point2.y, point2.z, point2.u, point2.v);
+ points2[index] = point;
+ } else {
+ point.x = point2.x;
+ point.y = point2.y;
+ point.z = point2.z;
+ point.u = point2.u;
+ point.v = point2.v;
+ }
+ index++;
+ } else {
+ if (offset1 > 0) {
+ k = offset2/(offset2 - offset1);
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k);
+ points2[index] = point;
+ } else {
+ point.x = point2.x - (point2.x - point1.x)*k;
+ point.y = point2.y - (point2.y - point1.y)*k;
+ point.z = point2.z - (point2.z - point1.z)*k;
+ point.u = point2.u - (point2.u - point1.u)*k;
+ point.v = point2.v - (point2.v - point1.v)*k;
+ }
+ index++;
+ }
+ }
+ offset1 = offset2;
+ point1 = point2;
+ }
+
+ } else {
+
+ for (i = 0; i < length; i++) {
+
+ point2 = points1[i];
+ offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset;
+
+ if (offset2 > 0) {
+ if (offset1 <= 0) {
+ k = offset2/(offset2 - offset1);
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k);
+ points2[index] = point;
+ } else {
+ point.x = point2.x - (point2.x - point1.x)*k;
+ point.y = point2.y - (point2.y - point1.y)*k;
+ point.z = point2.z - (point2.z - point1.z)*k;
+ }
+ index++;
+ }
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x, point2.y, point2.z);
+ points2[index] = point;
+ } else {
+ point.x = point2.x;
+ point.y = point2.y;
+ point.z = point2.z;
+ }
+ index++;
+ } else {
+ if (offset1 > 0) {
+ k = offset2/(offset2 - offset1);
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k);
+ points2[index] = point;
+ } else {
+ point.x = point2.x - (point2.x - point1.x)*k;
+ point.y = point2.y - (point2.y - point1.y)*k;
+ point.z = point2.z - (point2.z - point1.z)*k;
+ }
+ index++;
+ }
+ }
+ offset1 = offset2;
+ point1 = point2;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * @private
+ * Добавление текущего скина.
+ */
+ private function addCurrentSkin():void {
+ currentSkin = Skin.createSkin();
+ _view.canvas.addChild(currentSkin);
+ if (prevSkin == null) {
+ firstSkin = currentSkin;
+ } else {
+ prevSkin.nextSkin = currentSkin;
+ }
+ }
+
+ /**
+ * @private
+ * Вставляем под текущий скин.
+ */
+ private function insertCurrentSkin():void {
+ var skin:Skin = Skin.createSkin();
+ _view.canvas.addChildAt(skin, _view.canvas.getChildIndex(currentSkin));
+ skin.nextSkin = currentSkin;
+ if (prevSkin == null) {
+ firstSkin = skin;
+ } else {
+ prevSkin.nextSkin = skin;
+ }
+ currentSkin = skin;
+ }
+
+ /**
+ * @private
+ * Удаляет текущий скин.
+ */
+ private function removeCurrentSkin():void {
+ // Сохраняем следующий
+ var next:Skin = currentSkin.nextSkin;
+ // Удаляем из канваса
+ _view.canvas.removeChild(currentSkin);
+ // Очистка скина
+ if (currentSkin.material != null) {
+ currentSkin.material.clear(currentSkin);
+ }
+ // Зачищаем ссылки
+ currentSkin.nextSkin = null;
+ currentSkin.primitive = null;
+ currentSkin.material = null;
+ // Удаляем
+ Skin.destroySkin(currentSkin);
+ // Следующий устанавливаем текущим
+ currentSkin = next;
+ // Устанавливаем связь от предыдущего скина
+ if (prevSkin == null) {
+ firstSkin = currentSkin;
+ } else {
+ prevSkin.nextSkin = currentSkin;
+ }
+ }
+
+ /**
+ * @private
+ */
+ alternativa3d function calculateUVMatrix(face:Face, width:uint, height:uint):void {
+
+ // Расчёт точек базового примитива в координатах камеры
+ var point:Point3D = face.primitive.points[0];
+ textureA.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
+ textureA.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
+ point = face.primitive.points[1];
+ textureB.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
+ textureB.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
+ point = face.primitive.points[2];
+ textureC.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
+ textureC.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
+
+ // Находим AB и AC
+ var abx:Number = textureB.x - textureA.x;
+ var aby:Number = textureB.y - textureA.y;
+ var acx:Number = textureC.x - textureA.x;
+ var acy:Number = textureC.y - textureA.y;
+
+ // Расчёт текстурной матрицы
+ var uvMatrixBase:Matrix = face.uvMatrixBase;
+ var uvMatrix:Matrix = face.uvMatrix;
+ uvMatrix.a = (uvMatrixBase.a*abx + uvMatrixBase.b*acx)/width;
+ uvMatrix.b = (uvMatrixBase.a*aby + uvMatrixBase.b*acy)/width;
+ uvMatrix.c = -(uvMatrixBase.c*abx + uvMatrixBase.d*acx)/height;
+ uvMatrix.d = -(uvMatrixBase.c*aby + uvMatrixBase.d*acy)/height;
+ uvMatrix.tx = (uvMatrixBase.tx + uvMatrixBase.c)*abx + (uvMatrixBase.ty + uvMatrixBase.d)*acx + textureA.x + cameraMatrix.d;
+ uvMatrix.ty = (uvMatrixBase.tx + uvMatrixBase.c)*aby + (uvMatrixBase.ty + uvMatrixBase.d)*acy + textureA.y + cameraMatrix.h;
+
+ // Помечаем, как рассчитанную
+ uvMatricesCalculated[face] = true;
+ }
+
+ /**
+ * Поле вывода, в котором происходит отрисовка камеры.
+ */
+ public function get view():View {
+ return _view;
+ }
+
+ /**
+ * @private
+ */
+ public function set view(value:View):void {
+ if (value != _view) {
+ if (_view != null) {
+ _view.camera = null;
+ }
+ if (value != null) {
+ value.camera = this;
+ }
+ }
+ }
+
+ /**
+ * Включение режима аксонометрической проекции.
+ *
+ * @default false
+ */
+ public function get orthographic():Boolean {
+ return _orthographic;
+ }
+
+ /**
+ * @private
+ */
+ public function set orthographic(value:Boolean):void {
+ if (_orthographic != value) {
+ // Отправляем сигнал об изменении типа камеры
+ addOperationToScene(calculateMatrixOperation);
+ // Сохраняем новое значение
+ _orthographic = value;
+ }
+ }
+
+ /**
+ * Угол поля зрения в радианах в режиме перспективной проекции. При изменении FOV изменяется фокусное расстояние
+ * камеры по формуле f = d/tan(fov/2), где d является половиной диагонали поля вывода.
+ * Угол зрения ограничен диапазоном 0-180 градусов.
+ */
+ public function get fov():Number {
+ return _fov;
+ }
+
+ /**
+ * @private
+ */
+ public function set fov(value:Number):void {
+ value = (value < 0) ? 0 : ((value > (Math.PI - 0.0001)) ? (Math.PI - 0.0001) : value);
+ if (_fov != value) {
+ // Если перспектива
+ if (!_orthographic) {
+ // Отправляем сигнал об изменении плоскостей отсечения
+ addOperationToScene(calculatePlanesOperation);
+ }
+ // Сохраняем новое значение
+ _fov = value;
+ }
+ }
+
+ /**
+ * Коэффициент увеличения изображения в режиме аксонометрической проекции.
+ */
+ public function get zoom():Number {
+ return _zoom;
+ }
+
+ /**
+ * @private
+ */
+ public function set zoom(value:Number):void {
+ value = (value < 0) ? 0 : value;
+ if (_zoom != value) {
+ // Если изометрия
+ if (_orthographic) {
+ // Отправляем сигнал об изменении zoom
+ addOperationToScene(calculateMatrixOperation);
+ }
+ // Сохраняем новое значение
+ _zoom = value;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function addToScene(scene:Scene3D):void {
+ super.addToScene(scene);
+ if (_view != null) {
+ // Отправляем операцию расчёта плоскостей отсечения
+ scene.addOperation(calculatePlanesOperation);
+ // Подписываемся на сигналы сцены
+ scene.changePrimitivesOperation.addSequel(renderOperation);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function removeFromScene(scene:Scene3D):void {
+ super.removeFromScene(scene);
+
+ // Удаляем все операции из очереди
+ scene.removeOperation(calculateMatrixOperation);
+ scene.removeOperation(calculatePlanesOperation);
+ scene.removeOperation(renderOperation);
+
+ if (_view != null) {
+ // Отписываемся от сигналов сцены
+ scene.changePrimitivesOperation.removeSequel(renderOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ alternativa3d function addToView(view:View):void {
+ // Сохраняем первый скин
+ firstSkin = (view.canvas.numChildren > 0) ? Skin(view.canvas.getChildAt(0)) : null;
+
+ // Подписка на свои операции
+
+ // При изменении камеры пересчёт матрицы
+ calculateTransformationOperation.addSequel(calculateMatrixOperation);
+ // При изменении матрицы или FOV пересчёт плоскостей отсечения
+ calculateMatrixOperation.addSequel(calculatePlanesOperation);
+ // При изменении плоскостей перерисовка
+ calculatePlanesOperation.addSequel(renderOperation);
+
+ if (_scene != null) {
+ // Отправляем сигнал перерисовки
+ _scene.addOperation(calculateMatrixOperation);
+ // Подписываемся на сигналы сцены
+ _scene.changePrimitivesOperation.addSequel(renderOperation);
+ }
+
+ // Сохраняем вид
+ _view = view;
+ }
+
+ /**
+ * @private
+ */
+ alternativa3d function removeFromView(view:View):void {
+ // Сброс ссылки на первый скин
+ firstSkin = null;
+
+ // Отписка от своих операций
+
+ // При изменении камеры пересчёт матрицы
+ calculateTransformationOperation.removeSequel(calculateMatrixOperation);
+ // При изменении матрицы или FOV пересчёт плоскостей отсечения
+ calculateMatrixOperation.removeSequel(calculatePlanesOperation);
+ // При изменении плоскостей перерисовка
+ calculatePlanesOperation.removeSequel(renderOperation);
+
+ if (_scene != null) {
+ // Удаляем все операции из очереди
+ _scene.removeOperation(calculateMatrixOperation);
+ _scene.removeOperation(calculatePlanesOperation);
+ _scene.removeOperation(renderOperation);
+ // Отписываемся от сигналов сцены
+ _scene.changePrimitivesOperation.removeSequel(renderOperation);
+ }
+ // Удаляем ссылку на вид
+ _view = null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function defaultName():String {
+ return "camera" + ++counter;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Camera3D();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function clonePropertiesFrom(source:Object3D):void {
+ super.clonePropertiesFrom(source);
+
+ var src:Camera3D = Camera3D(source);
+ orthographic = src._orthographic;
+ zoom = src._zoom;
+ fov = src._fov;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/Face.as b/Alternativa3D5/5.4/alternativa/engine3d/core/Face.as
new file mode 100644
index 0000000..f5c0906
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/core/Face.as
@@ -0,0 +1,921 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ import flash.geom.Matrix;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Грань, образованная тремя или более вершинами. Грани являются составными частями полигональных объектов. Каждая грань
+ * содержит информацию об объекте и поверхности, которым она принадлежит. Для обеспечения возможности наложения
+ * текстуры на грань, первым трём её вершинам могут быть заданы UV-координаты, на основании которых расчитывается
+ * матрица трансформации текстуры.
+ */
+ final public class Face {
+ // Операции
+ /**
+ * @private
+ * Расчёт глобальной нормали плоскости грани.
+ */
+ alternativa3d var calculateNormalOperation:Operation = new Operation("calculateNormal", this, calculateNormal, Operation.FACE_CALCULATE_NORMAL);
+ /**
+ * @private
+ * Расчёт UV-координат (выполняется до трансформации, чтобы UV корректно разбились при построении BSP).
+ */
+ alternativa3d var calculateUVOperation:Operation = new Operation("calculateUV", this, calculateUV, Operation.FACE_CALCULATE_UV);
+ /**
+ * @private
+ * Обновление примитива в сцене.
+ */
+ alternativa3d var updatePrimitiveOperation:Operation = new Operation("updatePrimitive", this, updatePrimitive, Operation.FACE_UPDATE_PRIMITIVE);
+ /**
+ * @private
+ * Обновление материала.
+ */
+ alternativa3d var updateMaterialOperation:Operation = new Operation("updateMaterial", this, updateMaterial, Operation.FACE_UPDATE_MATERIAL);
+ /**
+ * @private
+ * Расчёт UV для фрагментов (выполняется после трансформации, если её не было).
+ */
+ alternativa3d var calculateFragmentsUVOperation:Operation = new Operation("calculateFragmentsUV", this, calculateFragmentsUV, Operation.FACE_CALCULATE_FRAGMENTS_UV);
+
+ /**
+ * @private
+ * Меш
+ */
+ alternativa3d var _mesh:Mesh;
+ /**
+ * @private
+ * Поверхность
+ */
+ alternativa3d var _surface:Surface;
+ /**
+ * @private
+ * Вершины грани
+ */
+ alternativa3d var _vertices:Array;
+ /**
+ * @private
+ * Количество вершин
+ */
+ alternativa3d var _verticesCount:uint;
+ /**
+ * @private
+ * Примитив
+ */
+ alternativa3d var primitive:PolyPrimitive;
+
+ // UV-координаты
+ /**
+ * @private
+ */
+ alternativa3d var _aUV:Point;
+ /**
+ * @private
+ */
+ alternativa3d var _bUV:Point;
+ /**
+ * @private
+ */
+ alternativa3d var _cUV:Point;
+
+ /**
+ * @private
+ * Коэффициенты базовой UV-матрицы
+ */
+ alternativa3d var uvMatrixBase:Matrix;
+
+ /**
+ * @private
+ * UV Матрица перевода текстурных координат в изометрическую камеру.
+ */
+ alternativa3d var uvMatrix:Matrix;
+ /**
+ * @private
+ * Нормаль плоскости
+ */
+ alternativa3d var globalNormal:Point3D = new Point3D();
+ /**
+ * @private
+ * Смещение плоскости
+ */
+ alternativa3d var globalOffset:Number;
+
+ /**
+ * Создание экземпляра грани.
+ *
+ * @param vertices массив объектов типа alternativa.engine3d.core.Vertex, задающий вершины грани в
+ * порядке обхода лицевой стороны грани против часовой стрелки.
+ *
+ * @see Vertex
+ */
+ public function Face(vertices:Array) {
+ // Сохраняем вершины
+ _vertices = vertices;
+ _verticesCount = vertices.length;
+
+ // Создаём оригинальный примитив
+ primitive = PolyPrimitive.createPolyPrimitive();
+ primitive.face = this;
+ primitive.num = _verticesCount;
+
+ // Обрабатываем вершины
+ for (var i:uint = 0; i < _verticesCount; i++) {
+ var vertex:Vertex = vertices[i];
+ // Добавляем координаты вершины в примитив
+ primitive.points.push(vertex.globalCoords);
+ // Добавляем пустые UV-координаты в примитив
+ primitive.uvs.push(null);
+ // Добавляем вершину в грань
+ vertex.addToFace(this);
+ }
+
+ // Расчёт нормали
+ calculateNormalOperation.addSequel(updatePrimitiveOperation);
+
+ // Расчёт UV грани инициирует расчёт UV фрагментов и перерисовку
+ calculateUVOperation.addSequel(calculateFragmentsUVOperation);
+ calculateUVOperation.addSequel(updateMaterialOperation);
+ }
+
+ /**
+ * @private
+ * Расчёт нормали в глобальных координатах
+ */
+ private function calculateNormal():void {
+ // Вектор AB
+ var vertex:Vertex = _vertices[0];
+ var av:Point3D = vertex.globalCoords;
+ vertex = _vertices[1];
+ var bv:Point3D = vertex.globalCoords;
+ var abx:Number = bv.x - av.x;
+ var aby:Number = bv.y - av.y;
+ var abz:Number = bv.z - av.z;
+ // Вектор AC
+ vertex = _vertices[2];
+ var cv:Point3D = vertex.globalCoords;
+ var acx:Number = cv.x - av.x;
+ var acy:Number = cv.y - av.y;
+ var acz:Number = cv.z - av.z;
+ // Перпендикуляр к плоскости
+ globalNormal.x = acz*aby - acy*abz;
+ globalNormal.y = acx*abz - acz*abx;
+ globalNormal.z = acy*abx - acx*aby;
+ // Нормализация перпендикуляра
+ globalNormal.normalize();
+ }
+
+ /**
+ * @private
+ * Расчитывает глобальное смещение плоскости грани.
+ * Помечает конечные примитивы на удаление, а базовый на добавление в сцене.
+ */
+ private function updatePrimitive():void {
+ // Расчёт смещения
+ var vertex:Vertex = _vertices[0];
+ globalOffset = vertex.globalCoords.x*globalNormal.x + vertex.globalCoords.y*globalNormal.y + vertex.globalCoords.z*globalNormal.z;
+
+ removePrimitive(primitive);
+ primitive.mobility = _mesh.inheritedMobility;
+ _mesh._scene.addPrimitives.push(primitive);
+ }
+
+ /**
+ * @private
+ * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на удаление из сцены
+ */
+ private function removePrimitive(primitive:PolyPrimitive):void {
+ if (primitive.backFragment != null) {
+ removePrimitive(primitive.backFragment);
+ removePrimitive(primitive.frontFragment);
+ primitive.backFragment = null;
+ primitive.frontFragment = null;
+ if (primitive != this.primitive) {
+ primitive.parent = null;
+ primitive.sibling = null;
+ PolyPrimitive.destroyPolyPrimitive(primitive);
+ }
+ } else {
+ // Если примитив в BSP-дереве
+ if (primitive.node != null) {
+ // Удаление примитива
+ _mesh._scene.removeBSPPrimitive(primitive);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Пометка на перерисовку фрагментов грани.
+ */
+ private function updateMaterial():void {
+ if (!updatePrimitiveOperation.queued) {
+ changePrimitive(primitive);
+ }
+ }
+
+ /**
+ * @private
+ * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на перерисовку
+ */
+ private function changePrimitive(primitive:PolyPrimitive):void {
+ if (primitive.backFragment != null) {
+ changePrimitive(primitive.backFragment);
+ changePrimitive(primitive.frontFragment);
+ } else {
+ _mesh._scene.changedPrimitives[primitive] = true;
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт UV-матрицы на основании первых трёх UV-координат.
+ * Расчёт UV-координат для оставшихся точек.
+ */
+ private function calculateUV():void {
+ var i:uint;
+ // Расчёт UV-матрицы
+ if (_aUV != null && _bUV != null && _cUV != null) {
+ var abu:Number = _bUV.x - _aUV.x;
+ var abv:Number = _bUV.y - _aUV.y;
+ var acu:Number = _cUV.x - _aUV.x;
+ var acv:Number = _cUV.y - _aUV.y;
+ var det:Number = abu*acv - abv*acu;
+ if (det != 0) {
+ if (uvMatrixBase == null) {
+ uvMatrixBase = new Matrix();
+ uvMatrix = new Matrix();
+ }
+ uvMatrixBase.a = acv/det;
+ uvMatrixBase.b = -abv/det;
+ uvMatrixBase.c = -acu/det;
+ uvMatrixBase.d = abu/det;
+ uvMatrixBase.tx = -(uvMatrixBase.a*_aUV.x + uvMatrixBase.c*_aUV.y);
+ uvMatrixBase.ty = -(uvMatrixBase.b*_aUV.x + uvMatrixBase.d*_aUV.y);
+
+ // Заполняем UV в базовом примитиве
+ primitive.uvs[0] = _aUV;
+ primitive.uvs[1] = _bUV;
+ primitive.uvs[2] = _cUV;
+
+ // Расчёт недостающих UV
+ if (_verticesCount > 3) {
+ var a:Point3D = primitive.points[0];
+ var b:Point3D = primitive.points[1];
+ var c:Point3D = primitive.points[2];
+
+ var ab1:Number;
+ var ab2:Number;
+ var ac1:Number;
+ var ac2:Number;
+ var ad1:Number;
+ var ad2:Number;
+ var abk:Number;
+ var ack:Number;
+
+ var uv:Point;
+ var point:Point3D;
+
+ // Выбор наиболее подходящих осей для расчёта
+ if (((globalNormal.x < 0) ? -globalNormal.x : globalNormal.x) > ((globalNormal.y < 0) ? -globalNormal.y : globalNormal.y)) {
+ if (((globalNormal.x < 0) ? -globalNormal.x : globalNormal.x) > ((globalNormal.z < 0) ? -globalNormal.z : globalNormal.z)) {
+ // Ось X
+ ab1 = b.y - a.y;
+ ab2 = b.z - a.z;
+ ac1 = c.y - a.y;
+ ac2 = c.z - a.z;
+ det = ab1*ac2 - ac1*ab2;
+ for (i = 3; i < _verticesCount; i++) {
+ point = primitive.points[i];
+ ad1 = point.y - a.y;
+ ad2 = point.z - a.z;
+ abk = (ad1*ac2 - ac1*ad2)/det;
+ ack = (ab1*ad2 - ad1*ab2)/det;
+ uv = primitive.uvs[i];
+ if (uv == null) {
+ uv = new Point();
+ primitive.uvs[i] = uv;
+ }
+ uv.x = _aUV.x + abu*abk + acu*ack;
+ uv.y = _aUV.y + abv*abk + acv*ack;
+ }
+ } else {
+ // Ось Z
+ ab1 = b.x - a.x;
+ ab2 = b.y - a.y;
+ ac1 = c.x - a.x;
+ ac2 = c.y - a.y;
+ det = ab1*ac2 - ac1*ab2;
+ for (i = 3; i < _verticesCount; i++) {
+ point = primitive.points[i];
+ ad1 = point.x - a.x;
+ ad2 = point.y - a.y;
+ abk = (ad1*ac2 - ac1*ad2)/det;
+ ack = (ab1*ad2 - ad1*ab2)/det;
+ uv = primitive.uvs[i];
+ if (uv == null) {
+ uv = new Point();
+ primitive.uvs[i] = uv;
+ }
+ uv.x = _aUV.x + abu*abk + acu*ack;
+ uv.y = _aUV.y + abv*abk + acv*ack;
+ }
+ }
+ } else {
+ if (((globalNormal.y < 0) ? -globalNormal.y : globalNormal.y) > ((globalNormal.z < 0) ? -globalNormal.z : globalNormal.z)) {
+ // Ось Y
+ ab1 = b.x - a.x;
+ ab2 = b.z - a.z;
+ ac1 = c.x - a.x;
+ ac2 = c.z - a.z;
+ det = ab1*ac2 - ac1*ab2;
+ for (i = 3; i < _verticesCount; i++) {
+ point = primitive.points[i];
+ ad1 = point.x - a.x;
+ ad2 = point.z - a.z;
+ abk = (ad1*ac2 - ac1*ad2)/det;
+ ack = (ab1*ad2 - ad1*ab2)/det;
+ uv = primitive.uvs[i];
+ if (uv == null) {
+ uv = new Point();
+ primitive.uvs[i] = uv;
+ }
+ uv.x = _aUV.x + abu*abk + acu*ack;
+ uv.y = _aUV.y + abv*abk + acv*ack;
+ }
+ } else {
+ // Ось Z
+ ab1 = b.x - a.x;
+ ab2 = b.y - a.y;
+ ac1 = c.x - a.x;
+ ac2 = c.y - a.y;
+ det = ab1*ac2 - ac1*ab2;
+ for (i = 3; i < _verticesCount; i++) {
+ point = primitive.points[i];
+ ad1 = point.x - a.x;
+ ad2 = point.y - a.y;
+ abk = (ad1*ac2 - ac1*ad2)/det;
+ ack = (ab1*ad2 - ad1*ab2)/det;
+ uv = primitive.uvs[i];
+ if (uv == null) {
+ uv = new Point();
+ primitive.uvs[i] = uv;
+ }
+ uv.x = _aUV.x + abu*abk + acu*ack;
+ uv.y = _aUV.y + abv*abk + acv*ack;
+ }
+ }
+ }
+ }
+ } else {
+ // Удаляем UV-матрицу
+ uvMatrixBase = null;
+ uvMatrix = null;
+ // Удаляем UV-координаты из базового примитива
+ for (i = 0; i < _verticesCount; i++) {
+ primitive.uvs[i] = null;
+ }
+ }
+ } else {
+ // Удаляем UV-матрицу
+ uvMatrixBase = null;
+ uvMatrix = null;
+ // Удаляем UV-координаты из базового примитива
+ for (i = 0; i < _verticesCount; i++) {
+ primitive.uvs[i] = null;
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт UV-координат для фрагментов примитива, если не было трансформации
+ */
+ private function calculateFragmentsUV():void {
+ // Если в этом цикле не было трансформации
+ if (!updatePrimitiveOperation.queued) {
+ if (uvMatrixBase != null) {
+ // Рассчитываем UV в примитиве
+ calculatePrimitiveUV(primitive);
+ } else {
+ // Удаляем UV в примитиве
+ removePrimitiveUV(primitive);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт UV для точек базового примитива.
+ *
+ * @param primitive
+ */
+ private function calculatePrimitiveUV(primitive:PolyPrimitive):void {
+ if (primitive.backFragment != null) {
+ var points:Array = primitive.points;
+ var backPoints:Array = primitive.backFragment.points;
+ var frontPoints:Array = primitive.frontFragment.points;
+ var uvs:Array = primitive.uvs;
+ var backUVs:Array = primitive.backFragment.uvs;
+ var frontUVs:Array = primitive.frontFragment.uvs;
+ var index1:uint = 0;
+ var index2:uint = 0;
+ var point:Point3D;
+ var uv:Point;
+ var uv1:Point;
+ var uv2:Point;
+ var t:Number;
+ var firstSplit:Boolean = true;
+ for (var i:uint = 0; i < primitive.num; i++) {
+ var split:Boolean = true;
+ point = points[i];
+ if (point == frontPoints[index2]) {
+ if (frontUVs[index2] == null) {
+ frontUVs[index2] = uvs[i];
+ }
+ split = false;
+ index2++;
+ }
+ if (point == backPoints[index1]) {
+ if (backUVs[index1] == null) {
+ backUVs[index1] = uvs[i];
+ }
+ split = false;
+ index1++;
+ }
+
+ if (split) {
+ uv1 = uvs[(i == 0) ? (primitive.num - 1) : (i - 1)];
+ uv2 = uvs[i];
+ t = (firstSplit) ? primitive.splitTime1 : primitive.splitTime2;
+ uv = frontUVs[index2];
+ if (uv == null) {
+ uv = new Point(uv1.x + (uv2.x - uv1.x)*t, uv1.y + (uv2.y - uv1.y)*t);
+ frontUVs[index2] = uv;
+ backUVs[index1] = uv;
+ } else {
+ uv.x = uv1.x + (uv2.x - uv1.x)*t;
+ uv.y = uv1.y + (uv2.y - uv1.y)*t;
+ }
+ firstSplit = false;
+ index2++;
+ index1++;
+ if (point == frontPoints[index2]) {
+ if (frontUVs[index2] == null) {
+ frontUVs[index2] = uvs[i];
+ }
+ index2++;
+ }
+ if (point == backPoints[index1]) {
+ if (backUVs[index1] == null) {
+ backUVs[index1] = uvs[i];
+ }
+ index1++;
+ }
+ }
+ }
+ // Проверяем рассечение последнего ребра
+ if (index2 < primitive.frontFragment.num) {
+ uv1 = uvs[primitive.num - 1];
+ uv2 = uvs[0];
+ t = (firstSplit) ? primitive.splitTime1 : primitive.splitTime2;
+ uv = frontUVs[index2];
+ if (uv == null) {
+ uv = new Point(uv1.x + (uv2.x - uv1.x)*t, uv1.y + (uv2.y - uv1.y)*t);
+ frontUVs[index2] = uv;
+ backUVs[index1] = uv;
+ } else {
+ uv.x = uv1.x + (uv2.x - uv1.x)*t;
+ uv.y = uv1.y + (uv2.y - uv1.y)*t;
+ }
+ }
+
+ calculatePrimitiveUV(primitive.backFragment);
+ calculatePrimitiveUV(primitive.frontFragment);
+ }
+ }
+
+ /**
+ * @private
+ * Удаление UV в примитиве и его фрагментах
+ * @param primitive
+ */
+ private function removePrimitiveUV(primitive:PolyPrimitive):void {
+ // Очищаем список UV
+ for (var i:uint = 0; i < primitive.num; i++) {
+ primitive.uvs[i] = null;
+ }
+ // Если есть фрагменты, удаляем UV в них
+ if (primitive.backFragment != null) {
+ removePrimitiveUV(primitive.backFragment);
+ removePrimitiveUV(primitive.frontFragment);
+ }
+ }
+
+ /**
+ * Массив вершин грани, представленных объектами класса alternativa.engine3d.core.Vertex.
+ *
+ * @see Vertex
+ */
+ public function get vertices():Array {
+ return new Array().concat(_vertices);
+ }
+
+ /**
+ * Количество вершин грани.
+ */
+ public function get verticesCount():uint {
+ return _verticesCount;
+ }
+
+ /**
+ * Полигональный объект, которому принадлежит грань.
+ */
+ public function get mesh():Mesh {
+ return _mesh;
+ }
+
+ /**
+ * Поверхность, которой принадлежит грань.
+ */
+ public function get surface():Surface {
+ return _surface;
+ }
+
+ /**
+ * Идентификатор грани в полигональном объекте. В случае, если грань не принадлежит ни одному объекту, идентификатор
+ * имеет значение null.
+ */
+ public function get id():Object {
+ return (_mesh != null) ? _mesh.getFaceId(this) : null;
+ }
+
+ /**
+ * UV-координаты, соответствующие первой вершине грани.
+ */
+ public function get aUV():Point {
+ return (_aUV != null) ? _aUV.clone() : null;
+ }
+
+ /**
+ * UV-координаты, соответствующие второй вершине грани.
+ */
+ public function get bUV():Point {
+ return (_bUV != null) ? _bUV.clone() : null;
+ }
+
+ /**
+ * UV-координаты, соответствующие третьей вершине грани.
+ */
+ public function get cUV():Point {
+ return (_cUV != null) ? _cUV.clone() : null;
+ }
+
+ /**
+ * @private
+ */
+ public function set aUV(value:Point):void {
+ if (_aUV != null) {
+ if (value != null) {
+ if (!_aUV.equals(value)) {
+ _aUV.x = value.x;
+ _aUV.y = value.y;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ _aUV = null;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ if (value != null) {
+ _aUV = value.clone();
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set bUV(value:Point):void {
+ if (_bUV != null) {
+ if (value != null) {
+ if (!_bUV.equals(value)) {
+ _bUV.x = value.x;
+ _bUV.y = value.y;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ _bUV = null;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ if (value != null) {
+ _bUV = value.clone();
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set cUV(value:Point):void {
+ if (_cUV != null) {
+ if (value != null) {
+ if (!_cUV.equals(value)) {
+ _cUV.x = value.x;
+ _cUV.y = value.y;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ _cUV = null;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ if (value != null) {
+ _cUV = value.clone();
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ }
+ }
+
+ /**
+ * Нормаль в локальной системе координат.
+ */
+ public function get normal():Point3D {
+ var res:Point3D = new Point3D();
+ var vertex:Vertex = _vertices[0];
+ var av:Point3D = vertex.coords;
+ vertex = _vertices[1];
+ var bv:Point3D = vertex.coords;
+ var abx:Number = bv.x - av.x;
+ var aby:Number = bv.y - av.y;
+ var abz:Number = bv.z - av.z;
+ vertex = _vertices[2];
+ var cv:Point3D = vertex.coords;
+ var acx:Number = cv.x - av.x;
+ var acy:Number = cv.y - av.y;
+ var acz:Number = cv.z - av.z;
+ res.x = acz*aby - acy*abz;
+ res.y = acx*abz - acz*abx;
+ res.z = acy*abx - acx*aby;
+ if (res.x != 0 || res.y != 0 || res.z != 0) {
+ var k:Number = Math.sqrt(res.x*res.x + res.y*res.y + res.z*res.z);
+ res.x /= k;
+ res.y /= k;
+ res.z /= k;
+ }
+ return res;
+ }
+
+ /**
+ * Расчёт UV-координат для произвольной точки в системе координат объекта, которому принадлежит грань.
+ *
+ * @param point точка в плоскости грани, для которой производится расчёт UV-координат
+ * @return UV-координаты заданной точки
+ */
+ public function getUV(point:Point3D):Point {
+ return getUVFast(point, normal);
+ }
+
+ /**
+ * @private
+ * Расчёт UV-координат для произвольной точки в локальной системе координат без расчёта
+ * локальной нормали грани. Используется для оптимизации.
+ *
+ * @param point точка в плоскости грани, для которой производится расчёт UV-координат
+ * @param normal нормаль плоскости грани в локальной системе координат
+ * @return UV-координаты заданной точки
+ */
+ alternativa3d function getUVFast(point:Point3D, normal:Point3D):Point {
+ if (_aUV == null || _bUV == null || _cUV == null) {
+ return null;
+ }
+
+ // Выбор наиболее длинной оси нормали
+ var dir:uint;
+ if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.y < 0) ? -normal.y : normal.y)) {
+ if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.z < 0) ? -normal.z : normal.z)) {
+ dir = 0;
+ } else {
+ dir = 2;
+ }
+ } else {
+ if (((normal.y < 0) ? -normal.y : normal.y) > ((normal.z < 0) ? -normal.z : normal.z)) {
+ dir = 1;
+ } else {
+ dir = 2;
+ }
+ }
+
+ // Расчёт соотношения по векторам AB и AC
+ var v:Vertex = _vertices[0];
+ var a:Point3D = v._coords;
+ v = _vertices[1];
+ var b:Point3D = v._coords;
+ v = _vertices[2];
+ var c:Point3D = v._coords;
+
+ var ab1:Number = (dir == 0) ? (b.y - a.y) : (b.x - a.x);
+ var ab2:Number = (dir == 2) ? (b.y - a.y) : (b.z - a.z);
+ var ac1:Number = (dir == 0) ? (c.y - a.y) : (c.x - a.x);
+ var ac2:Number = (dir == 2) ? (c.y - a.y) : (c.z - a.z);
+ var det:Number = ab1*ac2 - ac1*ab2;
+
+ var ad1:Number = (dir == 0) ? (point.y - a.y) : (point.x - a.x);
+ var ad2:Number = (dir == 2) ? (point.y - a.y) : (point.z - a.z);
+ var abk:Number = (ad1*ac2 - ac1*ad2)/det;
+ var ack:Number = (ab1*ad2 - ad1*ab2)/det;
+
+ // Интерполяция по UV первых точек
+ var abu:Number = _bUV.x - _aUV.x;
+ var abv:Number = _bUV.y - _aUV.y;
+ var acu:Number = _cUV.x - _aUV.x;
+ var acv:Number = _cUV.y - _aUV.y;
+
+ return new Point(_aUV.x + abu*abk + acu*ack, _aUV.y + abv*abk + acv*ack);
+ }
+
+ /**
+ * Множество граней, имеющих общие рёбра с текущей гранью.
+ */
+ public function get edgeJoinedFaces():Set {
+ var res:Set = new Set(true);
+ // Перебираем точки грани
+ for (var i:uint = 0; i < _verticesCount; i++) {
+ var a:Vertex = _vertices[i];
+ var b:Vertex = _vertices[(i < _verticesCount - 1) ? (i + 1) : 0];
+
+ // Перебираем грани текущей точки
+ for (var key:* in a._faces) {
+ var face:Face = key;
+ // Если это другая грань и у неё также есть следующая точка
+ if (face != this && face._vertices.indexOf(b) >= 0) {
+ // Значит у граней общее ребро
+ res[face] = true;
+ }
+ }
+ }
+ return res;
+ }
+
+ /**
+ * @private
+ * Удаление всех вершин из грани.
+ * Очистка базового примитива.
+ */
+ alternativa3d function removeVertices():void {
+ // Удалить вершины
+ for (var i:uint = 0; i < _verticesCount; i++) {
+ // Удаляем из списка
+ var vertex:Vertex = _vertices.pop();
+ // Удаляем координаты вершины из примитива
+ primitive.points.pop();
+ // Удаляем вершину из грани
+ vertex.removeFromFace(this);
+ }
+ // Обнуляем количество вершин
+ _verticesCount = 0;
+ }
+
+ /**
+ * @private
+ * Добавление грани на сцену
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {
+ // При добавлении на сцену рассчитываем плоскость и UV
+ scene.addOperation(calculateNormalOperation);
+ scene.addOperation(calculateUVOperation);
+
+ // Подписываем сцену на операции
+ updatePrimitiveOperation.addSequel(scene.calculateBSPOperation);
+ updateMaterialOperation.addSequel(scene.changePrimitivesOperation);
+ }
+
+ /**
+ * @private
+ * Удаление грани из сцены
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(calculateUVOperation);
+ scene.removeOperation(calculateFragmentsUVOperation);
+ scene.removeOperation(calculateNormalOperation);
+ scene.removeOperation(updatePrimitiveOperation);
+ scene.removeOperation(updateMaterialOperation);
+
+ // Удаляем примитивы из сцены
+ removePrimitive(primitive);
+
+ // Посылаем операцию сцены на расчёт BSP
+ scene.addOperation(scene.calculateBSPOperation);
+
+ // Отписываем сцену от операций
+ updatePrimitiveOperation.removeSequel(scene.calculateBSPOperation);
+ updateMaterialOperation.removeSequel(scene.changePrimitivesOperation);
+ }
+
+ /**
+ * @private
+ * Добавление грани в меш
+ * @param mesh
+ */
+ alternativa3d function addToMesh(mesh:Mesh):void {
+ // Подписка на операции меша
+ mesh.changeCoordsOperation.addSequel(updatePrimitiveOperation);
+ mesh.changeRotationOrScaleOperation.addSequel(calculateNormalOperation);
+ mesh.calculateMobilityOperation.addSequel(updatePrimitiveOperation);
+ // Сохранить меш
+ _mesh = mesh;
+ }
+
+ /**
+ * @private
+ * Удаление грани из меша
+ * @param mesh
+ */
+ alternativa3d function removeFromMesh(mesh:Mesh):void {
+ // Отписка от операций меша
+ mesh.changeCoordsOperation.removeSequel(updatePrimitiveOperation);
+ mesh.changeRotationOrScaleOperation.removeSequel(calculateNormalOperation);
+ mesh.calculateMobilityOperation.removeSequel(updatePrimitiveOperation);
+ // Удалить ссылку на меш
+ _mesh = null;
+ }
+
+ /**
+ * @private
+ * Добавление к поверхности
+ *
+ * @param surface
+ */
+ alternativa3d function addToSurface(surface:Surface):void {
+ // Подписка поверхности на операции
+ surface.changeMaterialOperation.addSequel(updateMaterialOperation);
+ // Если при смене поверхности изменился материал
+ if (_mesh != null && (_surface != null && _surface._material != surface._material || _surface == null && surface._material != null)) {
+ // Отправляем сигнал смены материала
+ _mesh.addOperationToScene(updateMaterialOperation);
+ }
+ // Сохранить поверхность
+ _surface = surface;
+ }
+
+ /**
+ * @private
+ * Удаление из поверхности
+ *
+ * @param surface
+ */
+ alternativa3d function removeFromSurface(surface:Surface):void {
+ // Отписка поверхности от операций
+ surface.changeMaterialOperation.removeSequel(updateMaterialOperation);
+ // Если был материал
+ if (surface._material != null) {
+ // Отправляем сигнал смены материала
+ _mesh.addOperationToScene(updateMaterialOperation);
+ }
+ // Удалить ссылку на поверхность
+ _surface = null;
+ }
+
+ /**
+ * Строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ var res:String = "[Face ID:" + id + ((_verticesCount > 0) ? " vertices:" : "");
+ for (var i:uint = 0; i < _verticesCount; i++) {
+ var vertex:Vertex = _vertices[i];
+ res += vertex.id + ((i < _verticesCount - 1) ? ", " : "");
+ }
+ res += "]";
+ return res;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/Mesh.as b/Alternativa3D5/5.4/alternativa/engine3d/core/Mesh.as
new file mode 100644
index 0000000..4f38fb9
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/core/Mesh.as
@@ -0,0 +1,985 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.errors.FaceExistsError;
+ import alternativa.engine3d.errors.FaceNeedMoreVerticesError;
+ import alternativa.engine3d.errors.FaceNotFoundError;
+ import alternativa.engine3d.errors.InvalidIDError;
+ import alternativa.engine3d.errors.SurfaceExistsError;
+ import alternativa.engine3d.errors.SurfaceNotFoundError;
+ import alternativa.engine3d.errors.VertexExistsError;
+ import alternativa.engine3d.errors.VertexNotFoundError;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.types.Map;
+ import alternativa.utils.ObjectUtils;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Полигональный объект — базовый класс для трёхмерных объектов, состоящих из граней-полигонов. Объект
+ * содержит в себе наборы вершин, граней и поверхностей.
+ */
+ public class Mesh extends Object3D {
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ // Инкременты для идентификаторов вершин, граней и поверхностей
+ private var vertexIDCounter:uint = 0;
+ private var faceIDCounter:uint = 0;
+ private var surfaceIDCounter:uint = 0;
+
+ /**
+ * @private
+ * Список вершин
+ */
+ alternativa3d var _vertices:Map = new Map();
+ /**
+ * @private
+ * Список граней
+ */
+ alternativa3d var _faces:Map = new Map();
+ /**
+ * @private
+ * Список поверхностей
+ */
+ alternativa3d var _surfaces:Map = new Map();
+
+ /**
+ * Создание экземпляра полигонального объекта.
+ *
+ * @param name имя экземпляра
+ */
+ public function Mesh(name:String = null) {
+ super(name);
+ }
+
+ /**
+ * Добавление новой вершины к объекту.
+ *
+ * @param x координата X в локальной системе координат объекта
+ * @param y координата Y в локальной системе координат объекта
+ * @param z координата Z в локальной системе координат объекта
+ * @param id идентификатор вершины. Если указано значение null, идентификатор будет
+ * сформирован автоматически.
+ *
+ * @return экземпляр добавленной вершины
+ *
+ * @throws alternativa.engine3d.errors.VertexExistsError объект уже содержит вершину с указанным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function createVertex(x:Number = 0, y:Number = 0, z:Number = 0, id:Object = null):Vertex {
+ // Проверяем ID
+ if (id != null) {
+ // Если уже есть вершина с таким ID
+ if (_vertices[id] != undefined) {
+ if (_vertices[id] is Vertex) {
+ throw new VertexExistsError(id, this);
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ } else {
+ // Ищем первый свободный
+ while (_vertices[vertexIDCounter] != undefined) {
+ vertexIDCounter++;
+ }
+ id = vertexIDCounter;
+ }
+
+ // Создаём вершину
+ var v:Vertex = new Vertex(x, y, z);
+
+ // Добавляем вершину на сцену
+ if (_scene != null) {
+ v.addToScene(_scene);
+ }
+
+ // Добавляем вершину в меш
+ v.addToMesh(this);
+ _vertices[id] = v;
+
+ return v;
+ }
+
+ /**
+ * Удаление вершины из объекта. При удалении вершины из объекта также удаляются все грани, которым принадлежит данная вершина.
+ *
+ * @param vertex экземпляр класса alternativa.engine3d.core.Vertex или идентификатор удаляемой вершины
+ *
+ * @return экземпляр удалённой вершины
+ *
+ * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function removeVertex(vertex:Object):Vertex {
+ var byLink:Boolean = vertex is Vertex;
+
+ // Проверяем на null
+ if (vertex == null) {
+ throw new VertexNotFoundError(null, this);
+ }
+
+ // Проверяем наличие вершины в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Vertex(vertex)._mesh != this) {
+ // Если вершина не в меше
+ throw new VertexNotFoundError(vertex, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_vertices[vertex] == undefined) {
+ // Если нет вершины с таким ID
+ throw new VertexNotFoundError(vertex, this);
+ } else if (!(_vertices[vertex] is Vertex)) {
+ // По этому id не вершина
+ throw new InvalidIDError(vertex, this);
+ }
+ }
+
+ // Находим вершину и её ID
+ var v:Vertex = byLink ? Vertex(vertex) : _vertices[vertex];
+ var id:Object = byLink ? getVertexId(Vertex(vertex)) : vertex;
+
+ // Удаляем вершину из сцены
+ if (_scene != null) {
+ v.removeFromScene(_scene);
+ }
+
+ // Удаляем вершину из меша
+ v.removeFromMesh(this);
+ delete _vertices[id];
+
+ return v;
+ }
+
+ /**
+ * Добавление грани к объекту. В результате выполнения метода в объекте появляется новая грань, не привязанная
+ * ни к одной поверхности.
+ *
+ * @param vertices массив вершин грани, указанных в порядке обхода лицевой стороны грани против часовой
+ * стрелки. Каждый элемент массива может быть либо экземпляром класса alternativa.engine3d.core.Vertex,
+ * либо идентификатором в наборе вершин объекта. В обоих случаях объект должен содержать указанную вершину.
+ * @param id идентификатор грани. Если указано значение null, идентификатор будет
+ * сформирован автоматически.
+ *
+ * @return экземпляр добавленной грани
+ *
+ * @throws alternativa.engine3d.errors.FaceNeedMoreVerticesError в качестве массива вершин был передан
+ * null, либо количество вершин в массиве меньше трёх
+ * @throws alternativa.engine3d.errors.FaceExistsError объект уже содержит грань с заданным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит какую-либо вершину из входного массива
+ *
+ * @see Vertex
+ */
+ public function createFace(vertices:Array, id:Object = null):Face {
+
+ // Проверяем на null
+ if (vertices == null) {
+ throw new FaceNeedMoreVerticesError(this);
+ }
+
+ // Проверяем ID
+ if (id != null) {
+ // Если уже есть грань с таким ID
+ if (_faces[id] != undefined) {
+ if (_faces[id] is Face) {
+ throw new FaceExistsError(id, this);
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ } else {
+ // Ищем первый свободный ID
+ while (_faces[faceIDCounter] != undefined) {
+ faceIDCounter++;
+ }
+ id = faceIDCounter;
+ }
+
+ // Проверяем количество точек
+ var length:uint = vertices.length;
+ if (length < 3) {
+ throw new FaceNeedMoreVerticesError(this, length);
+ }
+
+ // Проверяем и формируем список вершин
+ var v:Array = new Array();
+ var vertex:Vertex;
+ for (var i:uint = 0; i < length; i++) {
+ if (vertices[i] is Vertex) {
+ // Если работаем со ссылками
+ vertex = vertices[i];
+ if (vertex._mesh != this) {
+ // Если вершина не в меше
+ throw new VertexNotFoundError(vertices[i], this);
+ }
+ } else {
+ // Если работаем с ID
+ if (_vertices[vertices[i]] == null) {
+ // Если нет вершины с таким ID
+ throw new VertexNotFoundError(vertices[i], this);
+ } else if (!(_vertices[vertices[i]] is Vertex)) {
+ // Если id зарезервировано
+ throw new InvalidIDError(vertices[i],this);
+ }
+ vertex = _vertices[vertices[i]];
+ }
+ v.push(vertex);
+ }
+
+ // Создаём грань
+ var f:Face = new Face(v);
+
+ // Добавляем грань на сцену
+ if (_scene != null) {
+ f.addToScene(_scene);
+ }
+
+ // Добавляем грань в меш
+ f.addToMesh(this);
+ _faces[id] = f;
+
+ return f;
+ }
+
+ /**
+ * Удаление грани из объекта. Грань также удаляется из поверхности объекта, которой она принадлежит.
+ *
+ * @param экземпляр класса alternativa.engine3d.core.Face или идентификатор удаляемой грани
+ *
+ * @return экземпляр удалённой грани
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function removeFace(face:Object):Face {
+ var byLink:Boolean = face is Face;
+
+ // Проверяем на null
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие грани в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Face(face)._mesh != this) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_faces[face] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(face, this);
+ } else if (!(_faces[face] is Face)) {
+ throw new InvalidIDError(face, this);
+ }
+ }
+
+ // Находим грань и её ID
+ var f:Face = byLink ? Face(face) : _faces[face] ;
+ var id:Object = byLink ? getFaceId(Face(face)) : face;
+
+ // Удаляем грань из сцены
+ if (_scene != null) {
+ f.removeFromScene(_scene);
+ }
+
+ // Удаляем вершины из грани
+ f.removeVertices();
+
+ // Удаляем грань из поверхности
+ if (f._surface != null) {
+ f._surface._faces.remove(f);
+ f.removeFromSurface(f._surface);
+ }
+
+ // Удаляем грань из меша
+ f.removeFromMesh(this);
+ delete _faces[id];
+
+ return f;
+ }
+
+ /**
+ * Добавление новой поверхности к объекту.
+ *
+ * @param faces набор граней, составляющих поверхность. Каждый элемент массива должен быть либо экземпляром класса
+ * alternativa.engine3d.core.Face, либо идентификатором грани. В обоих случаях объект должен содержать
+ * указанную грань. Если значение параметра равно null, то будет создана пустая поверхность. Если
+ * какая-либо грань содержится в другой поверхности, она будет перенесена в новую поверхность.
+ * @param id идентификатор новой поверхности. Если указано значение null, идентификатор будет
+ * сформирован автоматически.
+ *
+ * @return экземпляр добавленной поверхности
+ *
+ * @throws alternativa.engine3d.errors.SurfaceExistsError объект уже содержит поверхность с заданным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Face
+ */
+ public function createSurface(faces:Array = null, id:Object = null):Surface {
+ // Проверяем ID
+ if (id != null) {
+ // Если уже есть поверхность с таким ID
+ if (_surfaces[id] != undefined) {
+ if (_surfaces[id] is Surface) {
+ throw new SurfaceExistsError(id, this);
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ } else {
+ // Ищем первый свободный ID
+ while (_surfaces[surfaceIDCounter] != undefined) {
+ surfaceIDCounter++;
+ }
+ id = surfaceIDCounter;
+ }
+
+ // Создаём поверхность
+ var s:Surface = new Surface();
+
+ // Добавляем поверхность на сцену
+ if (_scene != null) {
+ s.addToScene(_scene);
+ }
+
+ // Добавляем поверхность в меш
+ s.addToMesh(this);
+ _surfaces[id] = s;
+
+ // Добавляем грани, если есть
+ if (faces != null) {
+ var length:uint = faces.length;
+ for (var i:uint = 0; i < length; i++) {
+ s.addFace(faces[i]);
+ }
+ }
+
+ return s;
+ }
+
+ /**
+ * Удаление поверхности объекта. Из удаляемой поверхности также удаляются все содержащиеся в ней грани.
+ *
+ * @param surface экземпляр класса alternativa.engine3d.core.Face или идентификатор удаляемой поверхности
+ *
+ * @return экземпляр удалённой поверхности
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function removeSurface(surface:Object):Surface {
+ var byLink:Boolean = surface is Surface;
+
+ // Проверяем на null
+ if (surface == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие поверхности в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Surface(surface)._mesh != this) {
+ // Если поверхность не в меше
+ throw new SurfaceNotFoundError(surface, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_surfaces[surface] == undefined) {
+ // Если нет поверхности с таким ID
+ throw new SurfaceNotFoundError(surface, this);
+ } else if (!(_surfaces[surface] is Surface)) {
+ throw new InvalidIDError(surface, this);
+ }
+ }
+
+ // Находим поверхность и её ID
+ var s:Surface = byLink ? Surface(surface) : _surfaces[surface];
+ var id:Object = byLink ? getSurfaceId(Surface(surface)) : surface;
+
+ // Удаляем поверхность из сцены
+ if (_scene != null) {
+ s.removeFromScene(_scene);
+ }
+
+ // Удаляем грани из поверхности
+ s.removeFaces();
+
+ // Удаляем поверхность из меша
+ s.removeFromMesh(this);
+ delete _surfaces[id];
+
+ return s;
+ }
+
+ /**
+ * Добавление всех граней объекта в указанную поверхность.
+ *
+ * @param surface экземпляр класса alternativa.engine3d.core.Surface или идентификатор поверхности, в
+ * которую добавляются грани. Если задан идентификатор, и объект не содержит поверхность с таким идентификатором,
+ * будет создана новая поверхность.
+ *
+ * @param removeSurfaces удалять или нет пустые поверхности после переноса граней
+ *
+ * @return экземпляр поверхности, в которую перенесены грани
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанный экземпляр поверхности
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function moveAllFacesToSurface(surface:Object = null, removeSurfaces:Boolean = false):Surface {
+ var returnSurface:Surface;
+ var returnSurfaceId:Object;
+ if (surface is Surface) {
+ // Работаем с экземпляром Surface
+ if (surface._mesh == this) {
+ returnSurface = Surface(surface);
+ } else {
+ throw new SurfaceNotFoundError(surface, this);
+ }
+ } else {
+ // Работаем с идентификатором
+ if (_surfaces[surface] == undefined) {
+ // Поверхности еще нет
+ returnSurface = createSurface(null, surface);
+ returnSurfaceId = surface;
+ } else {
+ if (_surfaces[surface] is Surface) {
+ returnSurface = _surfaces[surface];
+ } else {
+ // _surfaces[surface] по идентификатору возвращает не Surface
+ throw new InvalidIDError(surface, this);
+ }
+ }
+ }
+ // Перемещаем все грани
+ for each (var face:Face in _faces) {
+ if (face._surface != returnSurface) {
+ returnSurface.addFace(face);
+ }
+ }
+ if (removeSurfaces) {
+ // Удаляем старые, теперь вручную - меньше проверок, но рискованно
+ if (returnSurfaceId == null) {
+ returnSurfaceId = getSurfaceId(returnSurface);
+ }
+ var newSurfaces:Map = new Map();
+ newSurfaces[returnSurfaceId] = returnSurface;
+ delete _surfaces[returnSurfaceId];
+ // Удаляем оставшиеся
+ for (var currentSurfaceId:* in _surfaces) {
+ // Удаляем поверхность из сцены
+ var currentSurface:Surface = _surfaces[currentSurfaceId];
+ if (_scene != null) {
+ currentSurface.removeFromScene(_scene);
+ }
+ // Удаляем поверхность из меша
+ currentSurface.removeFromMesh(this);
+ delete _surfaces[currentSurfaceId];
+ }
+ // Новый список граней
+ _surfaces = newSurfaces;
+ }
+ return returnSurface;
+ }
+
+ /**
+ * Установка материала для указанной поверхности.
+ *
+ * @param material материал, назначаемый поверхности. Один экземпляр SurfaceMaterial можно назначить только одной поверхности.
+ * @param surface экземпляр класса alternativa.engine3d.core.Surface или идентификатор поверхности
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Surface
+ */
+ public function setMaterialToSurface(material:SurfaceMaterial, surface:Object):void {
+ var byLink:Boolean = surface is Surface;
+
+ // Проверяем на null
+ if (surface == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие поверхности в меше
+ if (byLink) {
+ // Если назначаем по ссылке
+ if (Surface(surface)._mesh != this) {
+ // Если поверхность не в меше
+ throw new SurfaceNotFoundError(surface, this);
+ }
+ } else {
+ // Если назначаем по ID
+ if (_surfaces[surface] == undefined) {
+ // Если нет поверхности с таким ID
+ throw new SurfaceNotFoundError(surface, this);
+ } else if (!(_surfaces[surface] is Surface)) {
+ throw new InvalidIDError(surface, this);
+ }
+ }
+
+ // Находим поверхность
+ var s:Surface = byLink ? Surface(surface) : _surfaces[surface];
+
+ // Назначаем материал
+ s.material = material;
+ }
+
+ /**
+ * Установка материала для всех поверхностей объекта. Для каждой поверхности устанавливается копия материала.
+ * При передаче null в качестве параметра происходит сброс материалов у всех поверхностей.
+ *
+ * @param material устанавливаемый материал
+ */
+ public function cloneMaterialToAllSurfaces(material:SurfaceMaterial):void {
+ for each (var surface:Surface in _surfaces) {
+ surface.material = (material != null) ? SurfaceMaterial(material.clone()) : null;
+ }
+ }
+
+ /**
+ * Установка UV-координат для указанной грани объекта. Матрица преобразования UV-координат расчитывается по
+ * UV-координатам первых трёх вершин грани, поэтому для корректного текстурирования эти вершины должны образовывать
+ * невырожденный треугольник в UV-пространстве.
+ *
+ * @param aUV UV-координаты, соответствующие первой вершине грани
+ * @param bUV UV-координаты, соответствующие второй вершине грани
+ * @param cUV UV-координаты, соответствующие третьей вершине грани
+ * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Face
+ */
+ public function setUVsToFace(aUV:Point, bUV:Point, cUV:Point, face:Object):void {
+ var byLink:Boolean = face is Face;
+
+ // Проверяем на null
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие грани в меше
+ if (byLink) {
+ // Если назначаем по ссылке
+ if (Face(face)._mesh != this) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ } else {
+ // Если назначаем по ID
+ if (_faces[face] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(face, this);
+ } else if (!(_faces[face] is Face)) {
+ throw new InvalidIDError(face, this);
+ }
+ }
+
+ // Находим грань
+ var f:Face = byLink ? Face(face) : _faces[face];
+
+ // Назначаем UV-координаты
+ f.aUV = aUV;
+ f.bUV = bUV;
+ f.cUV = cUV;
+ }
+
+ /**
+ * Набор вершин объекта. Ключами ассоциативного массива являются идентификаторы вершин, значениями - экземпляры вершин.
+ */
+ public function get vertices():Map {
+ return _vertices.clone();
+ }
+
+ /**
+ * Набор граней объекта. Ключами ассоциативного массива являются идентификаторы граней, значениями - экземпляры граней.
+ */
+ public function get faces():Map {
+ return _faces.clone();
+ }
+
+ /**
+ * Набор поверхностей объекта. Ключами ассоциативного массива являются идентификаторы поверхностей, значениями - экземпляры поверхностей.
+ */
+ public function get surfaces():Map {
+ return _surfaces.clone();
+ }
+
+ /**
+ * Получение вершины объекта по её идентификатору.
+ *
+ * @param id идентификатор вершины
+ *
+ * @return экземпляр вершины с указанным идентификатором
+ *
+ * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит вершину с указанным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function getVertexById(id:Object):Vertex {
+ if (id == null) {
+ throw new VertexNotFoundError(null, this);
+ }
+ if (_vertices[id] == undefined) {
+ // Если нет вершины с таким ID
+ throw new VertexNotFoundError(id, this);
+ } else {
+ if (_vertices[id] is Vertex) {
+ return _vertices[id];
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ }
+
+ /**
+ * Получение идентификатора вершины объекта.
+ *
+ * @param экземпляр вершины
+ *
+ * @return идентификатор указанной вершины
+ *
+ * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину
+ */
+ public function getVertexId(vertex:Vertex):Object {
+ if (vertex == null) {
+ throw new VertexNotFoundError(null, this);
+ }
+ if (vertex._mesh != this) {
+ // Если вершина не в меше
+ throw new VertexNotFoundError(vertex, this);
+ }
+ for (var i:Object in _vertices) {
+ if (_vertices[i] == vertex) {
+ return i;
+ }
+ }
+ throw new VertexNotFoundError(vertex, this);
+ }
+
+ /**
+ * Проверка наличия вершины в объекте.
+ *
+ * @param vertex экземпляр класса alternativa.engine3d.core.Vertex или идентификатор вершины
+ *
+ * @return true, если объект содержит указанную вершину, иначе false
+ *
+ * @throws alternativa.engine3d.errors.VertexNotFoundError в качестве vertex был передан null
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Vertex
+ */
+ public function hasVertex(vertex:Object):Boolean {
+ if (vertex == null) {
+ throw new VertexNotFoundError(null, this);
+ }
+ if (vertex is Vertex) {
+ // Проверка вершины
+ return vertex._mesh == this;
+ } else {
+ // Проверка ID вершины
+ if (_vertices[vertex] != undefined) {
+ // По этому ID есть объект
+ if (_vertices[vertex] is Vertex) {
+ // Объект является вершиной
+ return true;
+ } else {
+ // ID некорректный
+ throw new InvalidIDError(vertex, this);
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Получение грани объекта по ее идентификатору.
+ *
+ * @param id идентификатор грани
+ *
+ * @return экземпляр грани с указанным идентификатором
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит грань с указанным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function getFaceById(id:Object):Face {
+ if (id == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+ if (_faces[id] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(id, this);
+ } else {
+ if (_faces[id] is Face) {
+ return _faces[id];
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ }
+
+ /**
+ * Получение идентификатора грани объекта.
+ *
+ * @param face экземпляр грани
+ *
+ * @return идентификатор указанной грани
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
+ */
+ public function getFaceId(face:Face):Object {
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+ if (face._mesh != this) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ for (var i:Object in _faces) {
+ if (_faces[i] == face) {
+ return i;
+ }
+ }
+ throw new FaceNotFoundError(face, this);
+ }
+
+ /**
+ * Проверка наличия грани в объекте.
+ *
+ * @param face экземпляр класса Face или идентификатор грани
+ *
+ * @return true, если объект содержит указанную грань, иначе false
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError в качестве face был указан null
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function hasFace(face:Object):Boolean {
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+ if (face is Face) {
+ // Проверка грани
+ return face._mesh == this;
+ } else {
+ // Проверка ID грани
+ if (_faces[face] != undefined) {
+ // По этому ID есть объект
+ if (_faces[face] is Face) {
+ // Объект является гранью
+ return true;
+ } else {
+ // ID некорректный
+ throw new InvalidIDError(face, this);
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Получение поверхности объекта по ее идентификатору
+ *
+ * @param id идентификатор поверхности
+ *
+ * @return экземпляр поверхности с указанным идентификатором
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит поверхность с указанным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function getSurfaceById(id:Object):Surface {
+ if (id == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+ if (_surfaces[id] == undefined) {
+ // Если нет поверхности с таким ID
+ throw new SurfaceNotFoundError(id, this);
+ } else {
+ if (_surfaces[id] is Surface) {
+ return _surfaces[id];
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ }
+
+ /**
+ * Получение идентификатора поверхности объекта.
+ *
+ * @param surface экземпляр поверхности
+ *
+ * @return идентификатор указанной поверхности
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
+ */
+ public function getSurfaceId(surface:Surface):Object {
+ if (surface == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+ if (surface._mesh != this) {
+ // Если поверхность не в меше
+ throw new SurfaceNotFoundError(surface, this);
+ }
+ for (var i:Object in _surfaces) {
+ if (_surfaces[i] == surface) {
+ return i;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Проверка наличия поверхности в объекте.
+ *
+ * @param surface экземпляр класса Surface или идентификатор поверхности
+ *
+ * @return true, если объект содержит указанную поверхность, иначе bottomRadius = topRadius будет построен цилиндр.false
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError в качестве surface был передан null
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function hasSurface(surface:Object):Boolean {
+ if (surface == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+ if (surface is Surface) {
+ // Проверка поверхности
+ return surface._mesh == this;
+ } else {
+ // Проверка ID поверхности
+ if (_surfaces[surface] != undefined) {
+ // По этому ID есть объект
+ if (_surfaces[surface] is Surface) {
+ // Объект является поверхностью
+ return true;
+ } else {
+ // ID некорректный
+ throw new InvalidIDError(surface, this);
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function setScene(value:Scene3D):void {
+ if (_scene != value) {
+ var vertex:Vertex;
+ var face:Face;
+ var surface:Surface;
+ if (value != null) {
+ // Добавить вершины на сцену
+ for each (vertex in _vertices) {
+ vertex.addToScene(value);
+ }
+ // Добавить грани на сцену
+ for each (face in _faces) {
+ face.addToScene(value);
+ }
+ // Добавить поверхности на сцену
+ for each (surface in _surfaces) {
+ surface.addToScene(value);
+ }
+ } else {
+ // Удалить вершины из сцены
+ for each (vertex in _vertices) {
+ vertex.removeFromScene(_scene);
+ }
+ // Удалить грани из сцены
+ for each (face in _faces) {
+ face.removeFromScene(_scene);
+ }
+ // Удалить поверхности из сцены
+ for each (surface in _surfaces) {
+ surface.removeFromScene(_scene);
+ }
+ }
+ }
+ super.setScene(value);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function defaultName():String {
+ return "mesh" + ++counter;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function toString():String {
+ return "[" + ObjectUtils.getClassName(this) + " " + _name + " vertices: " + _vertices.length + " faces: " + _faces.length + "]";
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Mesh();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function clonePropertiesFrom(source:Object3D):void {
+ super.clonePropertiesFrom(source);
+
+ var src:Mesh = Mesh(source);
+
+ var id:*;
+ var len:int;
+ var i:int;
+ // Копирование вершин
+ var vertexMap:Map = new Map(true);
+ for (id in src._vertices) {
+ var sourceVertex:Vertex = src._vertices[id];
+ vertexMap[sourceVertex] = createVertex(sourceVertex.x, sourceVertex.y, sourceVertex.z, id);
+ }
+
+ // Копирование граней
+ var faceMap:Map = new Map(true);
+ for (id in src._faces) {
+ var sourceFace:Face = src._faces[id];
+ len = sourceFace._vertices.length;
+ var faceVertices:Array = new Array(len);
+ for (i = 0; i < len; i++) {
+ faceVertices[i] = vertexMap[sourceFace._vertices[i]];
+ }
+ var newFace:Face = createFace(faceVertices, id);
+ newFace.aUV = sourceFace._aUV;
+ newFace.bUV = sourceFace._bUV;
+ newFace.cUV = sourceFace._cUV;
+ faceMap[sourceFace] = newFace;
+ }
+
+ // Копирование поверхностей
+ for (id in src._surfaces) {
+ var sourceSurface:Surface = src._surfaces[id];
+ var surfaceFaces:Array = sourceSurface._faces.toArray();
+ len = surfaceFaces.length;
+ for (i = 0; i < len; i++) {
+ surfaceFaces[i] = faceMap[surfaceFaces[i]];
+ }
+ var surface:Surface = createSurface(surfaceFaces, id);
+ var sourceMaterial:SurfaceMaterial = sourceSurface.material;
+ if (sourceMaterial != null) {
+ surface.material = SurfaceMaterial(sourceMaterial.clone());
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/Object3D.as b/Alternativa3D5/5.4/alternativa/engine3d/core/Object3D.as
new file mode 100644
index 0000000..d2133da
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/core/Object3D.as
@@ -0,0 +1,708 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.errors.Object3DHierarchyError;
+ import alternativa.engine3d.errors.Object3DNotFoundError;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+ import alternativa.utils.ObjectUtils;
+
+ use namespace alternativa3d;
+
+ /**
+ * Базовый класс для объектов, находящихся в сцене. Класс реализует иерархию объектов сцены, а также содержит сведения
+ * о трансформации объекта как единого целого.
+ *
+ * X, Y, Z и параллельного переноса центра объекта из начала координат.
+ * Операции применяются в порядке их перечисления.
+ *
+ * createEmptyObject() и clonePropertiesFrom().
+ *
+ * @return клонированный экземпляр объекта
+ *
+ * @see #createEmptyObject()
+ * @see #clonePropertiesFrom()
+ */
+ public function clone():Object3D {
+ var copy:Object3D = createEmptyObject();
+ copy.clonePropertiesFrom(this);
+
+ // Клонирование детей
+ for (var key:* in _children) {
+ var child:Object3D = key;
+ copy.addChild(child.clone());
+ }
+
+ return copy;
+ }
+
+ /**
+ * Получение дочернего объекта с заданным именем.
+ *
+ * @param name имя дочернего объекта
+ * @return любой дочерний объект с заданным именем или null в случае отсутствия таких объектов
+ */
+ public function getChildByName(name:String):Object3D {
+ for (var key:* in _children) {
+ var child:Object3D = key;
+ if (child._name == name) {
+ return child;
+ }
+ }
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/Operation.as b/Alternativa3D5/5.4/alternativa/engine3d/core/Operation.as
new file mode 100644
index 0000000..ca430ac
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/core/Operation.as
@@ -0,0 +1,125 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.types.Set;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ */
+ public class Operation {
+
+ alternativa3d static const OBJECT_CALCULATE_TRANSFORMATION:uint = 0x01000000;
+ alternativa3d static const OBJECT_CALCULATE_MOBILITY:uint = 0x02000000;
+ alternativa3d static const VERTEX_CALCULATE_COORDS:uint = 0x03000000;
+ alternativa3d static const FACE_CALCULATE_NORMAL:uint = 0x04000000;
+ alternativa3d static const FACE_CALCULATE_UV:uint = 0x05000000;
+ alternativa3d static const FACE_UPDATE_PRIMITIVE:uint = 0x06000000;
+ alternativa3d static const SCENE_CALCULATE_BSP:uint = 0x07000000;
+ alternativa3d static const FACE_UPDATE_MATERIAL:uint = 0x08000000;
+ alternativa3d static const FACE_CALCULATE_FRAGMENTS_UV:uint = 0x09000000;
+ alternativa3d static const CAMERA_CALCULATE_MATRIX:uint = 0x0A000000;
+ alternativa3d static const CAMERA_CALCULATE_PLANES:uint = 0x0B000000;
+ alternativa3d static const CAMERA_RENDER:uint = 0x0C000000;
+ alternativa3d static const SCENE_CLEAR_PRIMITIVES:uint = 0x0D000000;
+
+ // Объект
+ alternativa3d var object:Object;
+
+ // Метод
+ alternativa3d var method:Function;
+
+ // Название метода
+ alternativa3d var name:String;
+
+ // Последствия
+ private var sequel:Operation;
+ private var sequels:Set;
+
+ // Приоритет операции
+ alternativa3d var priority:uint;
+
+ // Находится ли операция в очереди
+ alternativa3d var queued:Boolean = false;
+
+ public function Operation(name:String, object:Object = null, method:Function = null, priority:uint = 0) {
+ this.object = object;
+ this.method = method;
+ this.name = name;
+ this.priority = priority;
+ }
+
+ // Добавить последствие
+ alternativa3d function addSequel(operation:Operation):void {
+ if (sequel == null) {
+ if (sequels == null) {
+ sequel = operation;
+ } else {
+ sequels[operation] = true;
+ }
+ } else {
+ if (sequel != operation) {
+ sequels = new Set(true);
+ sequels[sequel] = true;
+ sequels[operation] = true;
+ sequel = null;
+ }
+ }
+ }
+
+ // Удалить последствие
+ alternativa3d function removeSequel(operation:Operation):void {
+ if (sequel == null) {
+ if (sequels != null) {
+ delete sequels[operation];
+ var key:*;
+ var single:Boolean = false;
+ for (key in sequels) {
+ if (single) {
+ single = false;
+ break;
+ }
+ single = true;
+ }
+ if (single) {
+ sequel = key;
+ sequels = null;
+ }
+ }
+ } else {
+ if (sequel == operation) {
+ sequel = null;
+ }
+ }
+ }
+
+ alternativa3d function collectSequels(collector:Array):void {
+ if (sequel == null) {
+ // Проверяем последствия
+ for (var key:* in sequels) {
+ var operation:Operation = key;
+ // Если операция ещё не в очереди
+ if (!operation.queued) {
+ // Добавляем её в очередь
+ collector.push(operation);
+ // Устанавливаем флаг очереди
+ operation.queued = true;
+ // Вызываем добавление в очередь её последствий
+ operation.collectSequels(collector);
+ }
+ }
+ } else {
+ if (!sequel.queued) {
+ collector.push(sequel);
+ sequel.queued = true;
+ sequel.collectSequels(collector);
+ }
+ }
+ }
+
+ public function toString():String {
+ return "[Operation " + (priority >>> 24) + "/" + (priority & 0xFFFFFF) + " " + object + "." + name + "]";
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/PolyPrimitive.as b/Alternativa3D5/5.4/alternativa/engine3d/core/PolyPrimitive.as
new file mode 100644
index 0000000..9ad7335
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/core/PolyPrimitive.as
@@ -0,0 +1,130 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ * Примитивный полигон (примитив), хранящийся в узле BSP-дерева.
+ */
+ public class PolyPrimitive {
+
+ /**
+ * @private
+ * Количество точек
+ */
+ alternativa3d var num:uint;
+ /**
+ * @private
+ * Точки
+ */
+ alternativa3d var points:Array = new Array();
+ /**
+ * @private
+ * UV-координаты
+ */
+ alternativa3d var uvs:Array = new Array();
+ /**
+ * @private
+ * Грань
+ */
+ alternativa3d var face:Face;
+ /**
+ * @private
+ * Родительский примитив
+ */
+ alternativa3d var parent:PolyPrimitive;
+ /**
+ * @private
+ * Соседний примитив (при наличии родительского)
+ */
+ alternativa3d var sibling:PolyPrimitive;
+ /**
+ * @private
+ * Фрагменты
+ */
+ alternativa3d var backFragment:PolyPrimitive;
+ /**
+ * @private
+ */
+ alternativa3d var frontFragment:PolyPrimitive;
+ /**
+ * @private
+ * Рассечения
+ */
+ alternativa3d var splitTime1:Number;
+ /**
+ * @private
+ */
+ alternativa3d var splitTime2:Number;
+ /**
+ * @private
+ * BSP-нода, в которой находится примитив
+ */
+ alternativa3d var node:BSPNode;
+ /**
+ * @private
+ * Значения для расчёта качества сплиттера
+ */
+ alternativa3d var splits:uint;
+ /**
+ * @private
+ */
+ alternativa3d var disbalance:int;
+ /**
+ * @private
+ * Качество примитива как сплиттера (меньше - лучше)
+ */
+ public var splitQuality:Number;
+ /**
+ * @private
+ * Приоритет в BSP-дереве. Чем ниже мобильность, тем примитив выше в дереве.
+ */
+ public var mobility:int;
+
+ // Хранилище неиспользуемых примитивов
+ static private var collector:Array = new Array();
+
+ /**
+ * @private
+ * Создать примитив
+ */
+ static alternativa3d function createPolyPrimitive():PolyPrimitive {
+ // Достаём примитив из коллектора
+ var primitive:PolyPrimitive = collector.pop();
+ // Если коллектор пуст, создаём новый примитив
+ if (primitive == null) {
+ primitive = new PolyPrimitive();
+ }
+ //trace(primitive.num, primitive.points.length, primitive.face, primitive.parent, primitive.sibling, primitive.fragment1, primitive.fragment2, primitive.node);
+ return primitive;
+ }
+
+ /**
+ * @private
+ * Кладёт примитив в коллектор для последующего реиспользования.
+ * Ссылка на грань и массивы точек зачищаются в этом методе.
+ * Ссылки на фрагменты (parent, sibling, back, front) должны быть зачищены перед запуском метода.
+ *
+ * Исключение:
+ * при сборке примитивов в сцене ссылки на back и front зачищаются после запуска метода.
+ *
+ * @param primitive примитив на реиспользование
+ */
+ static alternativa3d function destroyPolyPrimitive(primitive:PolyPrimitive):void {
+ primitive.face = null;
+ for (var i:uint = 0; i < primitive.num; i++) {
+ primitive.points.pop();
+ primitive.uvs.pop();
+ }
+ collector.push(primitive);
+ }
+
+ /**
+ * Строковое представление объекта.
+ */
+ public function toString():String {
+ return "[Primitive " + face._mesh._name + "]";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/Scene3D.as b/Alternativa3D5/5.4/alternativa/engine3d/core/Scene3D.as
new file mode 100644
index 0000000..757a2c2
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/core/Scene3D.as
@@ -0,0 +1,1256 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.engine3d.materials.WireMaterial;
+ import alternativa.types.*;
+
+ import flash.display.Shape;
+ import flash.display.Sprite;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+ use namespace alternativatypes;
+
+ /**
+ * Сцена является контейнером 3D-объектов, с которыми ведётся работа. Все взаимодействия объектов
+ * происходят в пределах одной сцены. Класс обеспечивает работу системы сигналов и реализует алгоритм построения
+ * BSP-дерева для содержимого сцены.
+ */
+ public class Scene3D {
+ // Операции
+ /**
+ * @private
+ * Полное обновление BSP-дерева
+ */
+ alternativa3d var updateBSPOperation:Operation = new Operation("updateBSP", this);
+ /**
+ * @private
+ * Изменение примитивов
+ */
+ alternativa3d var changePrimitivesOperation:Operation = new Operation("changePrimitives", this);
+ /**
+ * @private
+ * Расчёт BSP-дерева
+ */
+ alternativa3d var calculateBSPOperation:Operation = new Operation("calculateBSP", this, calculateBSP, Operation.SCENE_CALCULATE_BSP);
+ /**
+ * @private
+ * Очистка списков изменений
+ */
+ alternativa3d var clearPrimitivesOperation:Operation = new Operation("clearPrimitives", this, clearPrimitives, Operation.SCENE_CLEAR_PRIMITIVES);
+
+ /**
+ * @private
+ * Корневой объект
+ */
+ alternativa3d var _root:Object3D;
+
+ /**
+ * @private
+ * Список операций на выполнение
+ */
+ alternativa3d var operations:Array = new Array();
+ //protected var operationSort:Array = ["priority"];
+ //protected var operationSortOptions:Array = [Array.NUMERIC];
+ /**
+ * @private
+ * Вспомогательная пустая операция, используется при удалении операций из списка
+ */
+ alternativa3d var dummyOperation:Operation = new Operation("removed", this);
+
+ /**
+ * @private
+ * Флаг анализа сплиттеров
+ */
+ alternativa3d var _splitAnalysis:Boolean = true;
+ /**
+ * @private
+ * Cбалансированность дерева
+ */
+ alternativa3d var _splitBalance:Number = 0;
+ /**
+ * @private
+ * Список изменённых примитивов
+ */
+ alternativa3d var changedPrimitives:Set = new Set();
+
+ // Вспомогательный список для сборки дочерних примитивов
+ private var childPrimitives:Set = new Set();
+ /**
+ * @private
+ * Список примитивов на добавление/удаление
+ */
+ alternativa3d var addPrimitives:Array = new Array();
+
+// alternativa3d var addSort:Array = ["mobility"];
+// alternativa3d var addSortOptions:Array = [Array.NUMERIC | Array.DESCENDING];
+// alternativa3d var addSplitQualitySort:Array = ["mobility", "splitQuality"];
+// alternativa3d var addSplitQualitySortOptions:Array = [Array.NUMERIC | Array.DESCENDING, Array.NUMERIC | Array.DESCENDING];
+ /**
+ * @private
+ * Погрешность при определении точек на плоскости
+ */
+ private var _planeOffsetThreshold:Number = 0.01;
+ /**
+ * @private
+ * BSP-дерево
+ */
+ alternativa3d var bsp:BSPNode;
+
+ /**
+ * @private
+ * Список нод на удаление
+ */
+ alternativa3d var removeNodes:Set = new Set();
+ /**
+ * @private
+ * Вспомогательная пустая нода, используется при удалении нод из дерева
+ */
+ alternativa3d var dummyNode:BSPNode = new BSPNode();
+
+ /**
+ * Создание экземпляра сцены.
+ */
+ public function Scene3D() {
+ // Обновление BSP-дерева требует его пересчёта
+ updateBSPOperation.addSequel(calculateBSPOperation);
+ // Изменение примитивов в случае пересчёта дерева
+ calculateBSPOperation.addSequel(changePrimitivesOperation);
+ // При изменении примитивов необходимо очистить списки изменений
+ changePrimitivesOperation.addSequel(clearPrimitivesOperation);
+ }
+
+ /**
+ * Расчёт сцены. Метод анализирует все изменения, произошедшие с момента предыдущего расчёта, формирует список
+ * команд и исполняет их в необходимой последовательности. В результате расчёта происходит перерисовка во всех
+ * областях вывода, к которым подключены находящиеся в сцене камеры.
+ */
+ public function calculate():void {
+ if (operations[0] != undefined) {
+ // Формируем последствия
+ var operation:Operation;
+ var length:uint = operations.length;
+ var i:uint;
+ for (i = 0; i < length; i++) {
+ operation = operations[i];
+ operation.collectSequels(operations);
+ }
+ // Сортируем операции
+ length = operations.length;
+ //operations.sortOn(operationSort, operationSortOptions);
+ sortOperations(0, length - 1);
+ // Запускаем операции
+ //trace("----------------------------------------");
+ for (i = 0; i < length; i++) {
+ operation = operations[i];
+ if (operation.method != null) {
+ //trace("EXECUTE:", operation);
+ operation.method();
+ } else {
+ /*if (operation == dummyOperation) {
+ trace("REMOVED");
+ } else {
+ trace(operation);
+ }*/
+ }
+ }
+ // Очищаем список операций
+ for (i = 0; i < length; i++) {
+ operation = operations.pop();
+ operation.queued = false;
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Сортировка операций, если массив operations пуст будет ошибка
+ *
+ * @param l начальный элемент
+ * @param r конечный элемент
+ */
+ alternativa3d function sortOperations(l:int, r:int):void {
+ var i:int = l;
+ var j:int = r;
+ var left:Operation;
+ var mid:uint = operations[(r + l) >>> 1].priority;
+ var right:Operation;
+ do {
+ while ((left = operations[i]).priority < mid) {i++};
+ while (mid < (right = operations[j]).priority) {j--};
+ if (i <= j) {
+ operations[i++] = right;
+ operations[j--] = left;
+ }
+ } while (i <= j)
+ if (l < j) {
+ sortOperations(l, j);
+ }
+ if (i < r) {
+ sortOperations(i, r);
+ }
+ }
+
+ /**
+ * @private
+ * Добавление операции в список
+ *
+ * @param operation добавляемая операция
+ */
+ alternativa3d function addOperation(operation:Operation):void {
+ if (!operation.queued) {
+ operations.push(operation);
+ operation.queued = true;
+ }
+ }
+
+ /**
+ * @private
+ * удаление операции из списка
+ *
+ * @param operation удаляемая операция
+ */
+ alternativa3d function removeOperation(operation:Operation):void {
+ if (operation.queued) {
+ operations[operations.indexOf(operation)] = dummyOperation;
+ operation.queued = false;
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт изменений в BSP-дереве.
+ * Обработка удалённых и добавленных примитивов.
+ */
+ protected function calculateBSP():void {
+ if (updateBSPOperation.queued) {
+
+ // Удаление списка нод, помеченных на удаление
+ removeNodes.clear();
+
+ // Удаление BSP-дерева, перенос примитивов в список дочерних
+ childBSP(bsp);
+ bsp = null;
+
+ // Собираем дочерние примитивы в список нижних
+ assembleChildPrimitives();
+
+ } else {
+
+ var key:*;
+ var primitive:PolyPrimitive;
+
+ // Удаляем ноды из дерева
+ if (!removeNodes.isEmpty()) {
+ var node:BSPNode;
+ while ((node = removeNodes.peek()) != null) {
+
+ // Ищем верхнюю удаляемую ноду
+ var removeNode:BSPNode = node;
+ while ((node = node.parent) != null) {
+ if (removeNodes[node]) {
+ removeNode = node;
+ }
+ }
+
+ // Удаляем ветку
+ var parent:BSPNode = removeNode.parent;
+ var replace:BSPNode = removeBSPNode(removeNode);
+
+ // Если вернулась вспомогательная нода, игнорируем её
+ if (replace == dummyNode) {
+ replace = null;
+ }
+
+ // Если есть родительская нода
+ if (parent != null) {
+ // Заменяем себя на указанную ноду
+ if (parent.front == removeNode) {
+ parent.front = replace;
+ } else {
+ parent.back = replace;
+ }
+ } else {
+ // Если нет родительской ноды, значит заменяем корень на указанную ноду
+ bsp = replace;
+ }
+
+ // Устанавливаем связь с родителем для заменённой ноды
+ if (replace != null) {
+ replace.parent = parent;
+ }
+ }
+
+ // Собираем дочерние примитивы в список на добавление
+ assembleChildPrimitives();
+ }
+ }
+
+ // Если есть примитивы на добавление
+ if (addPrimitives[0] != undefined) {
+
+ // Если включен анализ сплиттеров
+ if (_splitAnalysis) {
+ // Рассчитываем качество рассечения примитивов
+ analyseSplitQuality();
+ // Сортируем массив примитивов c учётом качества
+ //addPrimitives.sortOn(addSplitQualitySort, addSplitQualitySortOptions);
+ sortPrimitives(0, addPrimitives.length - 1);
+ } else {
+ // Сортируем массив по мобильности
+ ///addPrimitives.sortOn(addSort, addSortOptions);
+ sortPrimitivesByMobility(0, addPrimitives.length - 1);
+ }
+
+ // Если корневого нода ещё нет, создаём
+ if (bsp == null) {
+ primitive = addPrimitives.pop();
+ bsp = BSPNode.createBSPNode(primitive);
+ changedPrimitives[primitive] = true;
+ }
+
+ // Встраиваем примитивы в дерево
+ while ((primitive = addPrimitives.pop()) != null) {
+ addBSP(bsp, primitive);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Сортировка граней, если массив addPrimitives пуст будет ошибка
+ *
+ * @param l начальный элемент
+ * @param r конечный элемент
+ */
+ alternativa3d function sortPrimitives(l:int, r:int):void {
+ var i:int = l;
+ var j:int = r;
+ var left:PolyPrimitive;
+ var mid:PolyPrimitive = addPrimitives[(r + l)>>>1];
+ var midMobility:Number = mid.mobility;
+ var midSplitQuality:Number = mid.splitQuality;
+ var right:PolyPrimitive;
+ do {
+ while (((left = addPrimitives[i]).mobility > midMobility) || ((left.mobility == midMobility) && (left.splitQuality > midSplitQuality))) {i++};
+ while ((midMobility > (right = addPrimitives[j]).mobility) || ((midMobility == right.mobility) && (midSplitQuality > right.splitQuality))) {j--};
+ if (i <= j) {
+ addPrimitives[i++] = right;
+ addPrimitives[j--] = left;
+ }
+ } while (i <= j)
+ if (l < j) {
+ sortPrimitives(l, j);
+ }
+ if (i < r) {
+ sortPrimitives(i, r);
+ }
+ }
+
+ /**
+ * @private
+ * Сортировка только по мобильности
+ *
+ * @param l начальный элемент
+ * @param r конечный элемент
+ */
+ alternativa3d function sortPrimitivesByMobility(l:int, r:int):void {
+ var i:int = l;
+ var j:int = r;
+ var left:PolyPrimitive;
+ var mid:int = addPrimitives[(r + l)>>>1].mobility;
+ var right:PolyPrimitive;
+ do {
+ while ((left = addPrimitives[i]).mobility > mid) {i++};
+ while (mid > (right = addPrimitives[j]).mobility) {j--};
+ if (i <= j) {
+ addPrimitives[i++] = right;
+ addPrimitives[j--] = left;
+ }
+ } while (i <= j)
+ if (l < j) {
+ sortPrimitivesByMobility(l, j);
+ }
+ if (i < r) {
+ sortPrimitivesByMobility(i, r);
+ }
+ }
+
+
+ /**
+ * @private
+ * Анализ качества сплиттеров
+ */
+ private function analyseSplitQuality():void {
+ // Перебираем примитивы на добавление
+ var i:uint;
+ var length:uint = addPrimitives.length;
+ var maxSplits:uint = 0;
+ var maxDisbalance:uint = 0;
+ var splitter:PolyPrimitive;
+ for (i = 0; i < length; i++) {
+ splitter = addPrimitives[i];
+ splitter.splits = 0;
+ splitter.disbalance = 0;
+ var normal:Point3D = splitter.face.globalNormal;
+ var offset:Number = splitter.face.globalOffset;
+ // Проверяем соотношение с другими примитивами не меньшей мобильности на добавление
+ for (var j:uint = 0; j < length; j++) {
+ if (i != j) {
+ var primitive:PolyPrimitive = addPrimitives[j];
+ if (splitter.mobility <= primitive.mobility) {
+ // Проверяем наличие точек спереди и сзади сплиттера
+ var pointsFront:Boolean = false;
+ var pointsBack:Boolean = false;
+ for (var k:uint = 0; k < primitive.num; k++) {
+ var point:Point3D = primitive.points[k];
+ var pointOffset:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - offset;
+ if (pointOffset > _planeOffsetThreshold) {
+ if (!pointsFront) {
+ splitter.disbalance++;
+ pointsFront = true;
+ }
+ if (pointsBack) {
+ splitter.splits++;
+ break;
+ }
+ } else {
+ if (pointOffset < -_planeOffsetThreshold) {
+ if (!pointsBack) {
+ splitter.disbalance--;
+ pointsBack = true;
+ }
+ if (pointsFront) {
+ splitter.splits++;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ // Абсолютное значение дисбаланса
+ splitter.disbalance = (splitter.disbalance > 0) ? splitter.disbalance : -splitter.disbalance;
+ // Ищем максимальное количество рассечений и значение дисбаланса
+ maxSplits = (maxSplits > splitter.splits) ? maxSplits : splitter.splits;
+ maxDisbalance = (maxDisbalance > splitter.disbalance) ? maxDisbalance : splitter.disbalance;
+ }
+ // Расчитываем качество сплиттеров
+ for (i = 0; i < length; i++) {
+ splitter = addPrimitives[i];
+ splitter.splitQuality = (1 - _splitBalance)*splitter.splits/maxSplits + _splitBalance*splitter.disbalance/maxDisbalance;
+ }
+ }
+
+ /**
+ * @private
+ * Добавление примитива в BSP-дерево
+ *
+ * @param node текущий узел дерева, в который добавляется примитив
+ * @param primitive добавляемый примитив
+ */
+ protected function addBSP(node:BSPNode, primitive:PolyPrimitive):void {
+ var point:Point3D;
+ var normal:Point3D;
+ var key:*;
+
+ // Сравниваем мобильности ноды и примитива
+ if (primitive.mobility < node.mobility) {
+
+ // Формируем список содержимого ноды и всех примитивов ниже
+ if (node.primitive != null) {
+ childPrimitives[node.primitive] = true;
+ changedPrimitives[node.primitive] = true;
+ node.primitive.node = null;
+ } else {
+ var p:PolyPrimitive;
+ for (key in node.backPrimitives) {
+ p = key;
+ childPrimitives[p] = true;
+ changedPrimitives[p] = true;
+ p.node = null;
+ }
+ for (key in node.frontPrimitives) {
+ p = key;
+ childPrimitives[p] = true;
+ changedPrimitives[p] = true;
+ p.node = null;
+ }
+ }
+ childBSP(node.back);
+ childBSP(node.front);
+
+ // Собираем дочерние примитивы в список нижних
+ assembleChildPrimitives();
+
+ // Если включен анализ сплиттеров
+ if (_splitAnalysis) {
+ // Рассчитываем качество рассечения примитивов
+ analyseSplitQuality();
+ // Сортируем массив примитивов c учётом качества
+ sortPrimitives(0, addPrimitives.length - 1);
+ } else {
+ // Сортируем массив по мобильности
+ sortPrimitivesByMobility(0, addPrimitives.length - 1);
+ }
+
+ // Добавляем примитив в ноду
+ node.primitive = primitive;
+
+ // Пометка об изменении примитива
+ changedPrimitives[primitive] = true;
+
+ // Сохраняем ноду
+ primitive.node = node;
+
+ // Сохраняем плоскость
+ node.normal.copy(primitive.face.globalNormal);
+ node.offset = primitive.face.globalOffset;
+
+ // Сохраняем мобильность
+ node.mobility = primitive.mobility;
+
+ // Чистим списки примитивов
+ node.backPrimitives = null;
+ node.frontPrimitives = null;
+
+ // Удаляем дочерние ноды
+ node.back = null;
+ node.front = null;
+
+ } else {
+ // Получаем нормаль из ноды
+ normal = node.normal;
+
+ var points:Array = primitive.points;
+ var uvs:Array = primitive.uvs;
+
+ // Собирательные флаги
+ var pointsFront:Boolean = false;
+ var pointsBack:Boolean = false;
+
+ // Собираем расстояния точек до плоскости
+ for (var i:uint = 0; i < primitive.num; i++) {
+ point = points[i];
+ var pointOffset:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset;
+ if (pointOffset > _planeOffsetThreshold) {
+ pointsFront = true;
+ if (pointsBack) {
+ break;
+ }
+ } else {
+ if (pointOffset < -_planeOffsetThreshold) {
+ pointsBack = true;
+ if (pointsFront) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (!pointsFront && !pointsBack) {
+ // Сохраняем ноду
+ primitive.node = node;
+
+ // Если был только базовый примитив, переносим его в список
+ if (node.primitive != null) {
+ node.frontPrimitives = new Set(true);
+ node.frontPrimitives[node.primitive] = true;
+ node.primitive = null;
+ }
+
+ // Примитив находится в плоскости ноды
+ if (Point3D.dot(primitive.face.globalNormal, normal) > 0) {
+ node.frontPrimitives[primitive] = true;
+ } else {
+ if (node.backPrimitives == null) {
+ node.backPrimitives = new Set(true);
+ }
+ node.backPrimitives[primitive] = true;
+ }
+
+ // Пометка об изменении примитива
+ changedPrimitives[primitive] = true;
+ } else {
+ if (!pointsBack) {
+ // Примитив спереди плоскости ноды
+ if (node.front == null) {
+ // Создаём переднюю ноду
+ node.front = BSPNode.createBSPNode(primitive);
+ node.front.parent = node;
+ changedPrimitives[primitive] = true;
+ } else {
+ // Добавляем примитив в переднюю ноду
+ addBSP(node.front, primitive);
+ }
+ } else {
+ if (!pointsFront) {
+ // Примитив сзади плоскости ноды
+ if (node.back == null) {
+ // Создаём заднюю ноду
+ node.back = BSPNode.createBSPNode(primitive);
+ node.back.parent = node;
+ changedPrimitives[primitive] = true;
+ } else {
+ // Добавляем примитив в заднюю ноду
+ addBSP(node.back, primitive);
+ }
+ } else {
+ // Рассечение
+ var backFragment:PolyPrimitive = PolyPrimitive.createPolyPrimitive();
+ var frontFragment:PolyPrimitive = PolyPrimitive.createPolyPrimitive();
+
+ var firstSplit:Boolean = true;
+
+ point = points[0];
+ var offset0:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset;
+ var offset1:Number = offset0;
+ var offset2:Number;
+ for (i = 0; i < primitive.num; i++) {
+ var j:uint;
+ if (i < primitive.num - 1) {
+ j = i + 1;
+ point = points[j];
+ offset2 = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset;
+ } else {
+ j = 0;
+ offset2 = offset0;
+ }
+
+ if (offset1 > _planeOffsetThreshold) {
+ // Точка спереди плоскости ноды
+ frontFragment.points.push(points[i]);
+ frontFragment.uvs.push(primitive.uvs[i]);
+ } else {
+ if (offset1 < -_planeOffsetThreshold) {
+ // Точка сзади плоскости ноды
+ backFragment.points.push(points[i]);
+ backFragment.uvs.push(primitive.uvs[i]);
+ } else {
+ // Рассечение по точке примитива
+ backFragment.points.push(points[i]);
+ backFragment.uvs.push(primitive.uvs[i]);
+ frontFragment.points.push(points[i]);
+ frontFragment.uvs.push(primitive.uvs[i]);
+ }
+ }
+
+ // Рассечение ребра
+ if (offset1 > _planeOffsetThreshold && offset2 < -_planeOffsetThreshold || offset1 < -_planeOffsetThreshold && offset2 > _planeOffsetThreshold) {
+ // Находим точку рассечения
+ var t:Number = offset1/(offset1 - offset2);
+ point = Point3D.interpolate(points[i], points[j], t);
+ backFragment.points.push(point);
+ frontFragment.points.push(point);
+ // Находим UV в точке рассечения
+ var uv:Point;
+ if (primitive.face.uvMatrixBase != null) {
+ uv = Point.interpolate(uvs[j], uvs[i], t);
+ } else {
+ uv = null;
+ }
+ backFragment.uvs.push(uv);
+ frontFragment.uvs.push(uv);
+ // Отмечаем рассечённое ребро
+ if (firstSplit) {
+ primitive.splitTime1 = t;
+ firstSplit = false;
+ } else {
+ primitive.splitTime2 = t;
+ }
+ }
+
+ offset1 = offset2;
+ }
+ backFragment.num = backFragment.points.length;
+ frontFragment.num = frontFragment.points.length;
+
+ // Назначаем мобильность
+ backFragment.mobility = primitive.mobility;
+ frontFragment.mobility = primitive.mobility;
+
+ // Устанавливаем связи рассечённых примитивов
+ backFragment.face = primitive.face;
+ frontFragment.face = primitive.face;
+ backFragment.parent = primitive;
+ frontFragment.parent = primitive;
+ backFragment.sibling = frontFragment;
+ frontFragment.sibling = backFragment;
+ primitive.backFragment = backFragment;
+ primitive.frontFragment = frontFragment;
+
+ // Добавляем фрагменты в дочерние ноды
+ if (node.back == null) {
+ node.back = BSPNode.createBSPNode(backFragment);
+ node.back.parent = node;
+ changedPrimitives[backFragment] = true;
+ } else {
+ addBSP(node.back, backFragment);
+ }
+ if (node.front == null) {
+ node.front = BSPNode.createBSPNode(frontFragment);
+ node.front.parent = node;
+ changedPrimitives[frontFragment] = true;
+ } else {
+ addBSP(node.front, frontFragment);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Удаление узла BSP-дерева, включая все дочерние узлы, помеченные для удаления.
+ *
+ * @param node удаляемый узел
+ * @return корневой узел поддерева, оставшегося после операции удаления
+ */
+ protected function removeBSPNode(node:BSPNode):BSPNode {
+ var replaceNode:BSPNode;
+ if (node != null) {
+ // Удаляем дочерние
+ node.back = removeBSPNode(node.back);
+ node.front = removeBSPNode(node.front);
+
+ if (!removeNodes[node]) {
+ // Если нода не удаляется, возвращает себя
+ replaceNode = node;
+
+ // Проверяем дочерние ноды
+ if (node.back != null) {
+ if (node.back != dummyNode) {
+ node.back.parent = node;
+ } else {
+ node.back = null;
+ }
+ }
+ if (node.front != null) {
+ if (node.front != dummyNode) {
+ node.front.parent = node;
+ } else {
+ node.front = null;
+ }
+ }
+ } else {
+ // Проверяем дочерние ветки
+ if (node.back == null) {
+ if (node.front != null) {
+ // Есть только передняя ветка
+ replaceNode = node.front;
+ node.front = null;
+ }
+ } else {
+ if (node.front == null) {
+ // Есть только задняя ветка
+ replaceNode = node.back;
+ node.back = null;
+ } else {
+ // Есть обе ветки - собираем дочерние примитивы
+ childBSP(node.back);
+ childBSP(node.front);
+ // Используем вспомогательную ноду
+ replaceNode = dummyNode;
+ // Удаляем связи с дочерними нодами
+ node.back = null;
+ node.front = null;
+ }
+ }
+
+ // Удаляем ноду из списка на удаление
+ delete removeNodes[node];
+ // Удаляем ноду
+ node.parent = null;
+ BSPNode.destroyBSPNode(node);
+ }
+ }
+ return replaceNode;
+ }
+
+ /**
+ * @private
+ * Удаление примитива из узла дерева
+ *
+ * @param primitive удаляемый примитив
+ */
+ alternativa3d function removeBSPPrimitive(primitive:PolyPrimitive):void {
+ var node:BSPNode = primitive.node;
+ primitive.node = null;
+
+ var single:Boolean = false;
+ var key:*;
+
+ // Пометка об изменении примитива
+ changedPrimitives[primitive] = true;
+
+ // Если нода единичная
+ if (node.primitive == primitive) {
+ removeNodes[node] = true;
+ node.primitive = null;
+ } else {
+ // Есть передние примитивы
+ if (node.frontPrimitives[primitive]) {
+ // Удаляем примитив спереди
+ delete node.frontPrimitives[primitive];
+
+ // Проверяем количество примитивов спереди
+ for (key in node.frontPrimitives) {
+ if (single) {
+ single = false;
+ break;
+ }
+ single = true;
+ }
+
+ if (key == null) {
+ // Передняя пуста, значит сзади кто-то есть
+
+ // Переворачиваем дочерние ноды
+ var t:BSPNode = node.back;
+ node.back = node.front;
+ node.front = t;
+
+ // Переворачиваем плоскость ноды
+ node.normal.invert();
+ node.offset = -node.offset;
+
+ // Проверяем количество примитивов сзади
+ for (key in node.backPrimitives) {
+ if (single) {
+ single = false;
+ break;
+ }
+ single = true;
+ }
+
+ // Если сзади один примитив
+ if (single) {
+ // Устанавливаем базовый примитив ноды
+ node.primitive = key;
+ // Устанавливаем мобильность
+ node.mobility = node.primitive.mobility;
+ // Стираем список передних примитивов
+ node.frontPrimitives = null;
+ } else {
+ // Если сзади несколько примитивов, переносим их в передние
+ node.frontPrimitives = node.backPrimitives;
+ // Пересчитываем мобильность ноды по передним примитивам
+ if (primitive.mobility == node.mobility) {
+ node.mobility = int.MAX_VALUE;
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ }
+ }
+
+ // Стираем список задних примитивов
+ node.backPrimitives = null;
+
+ } else {
+ // Если остался один примитив и сзади примитивов нет
+ if (single && node.backPrimitives == null) {
+ // Устанавливаем базовый примитив ноды
+ node.primitive = key;
+ // Устанавливаем мобильность
+ node.mobility = node.primitive.mobility;
+ // Стираем список передних примитивов
+ node.frontPrimitives = null;
+ } else {
+ // Пересчитываем мобильность ноды
+ if (primitive.mobility == node.mobility) {
+ node.mobility = int.MAX_VALUE;
+ for (key in node.backPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ }
+ }
+ }
+ } else {
+ // Удаляем примитив сзади
+ delete node.backPrimitives[primitive];
+
+ // Проверяем количество примитивов сзади
+ for (key in node.backPrimitives) {
+ break;
+ }
+
+ // Если сзади примитивов больше нет
+ if (key == null) {
+ // Проверяем количество примитивов спереди
+ for (key in node.frontPrimitives) {
+ if (single) {
+ single = false;
+ break;
+ }
+ single = true;
+ }
+
+ // Если спереди один примитив
+ if (single) {
+ // Устанавливаем базовый примитив ноды
+ node.primitive = key;
+ // Устанавливаем мобильность
+ node.mobility = node.primitive.mobility;
+ // Стираем список передних примитивов
+ node.frontPrimitives = null;
+ } else {
+ // Пересчитываем мобильность ноды по передним примитивам
+ if (primitive.mobility == node.mobility) {
+ node.mobility = int.MAX_VALUE;
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ }
+ }
+
+ // Стираем список задних примитивов
+ node.backPrimitives = null;
+ } else {
+ // Пересчитываем мобильность ноды
+ if (primitive.mobility == node.mobility) {
+ node.mobility = int.MAX_VALUE;
+ for (key in node.backPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Удаление и перевставка ветки
+ *
+ * @param node
+ */
+ protected function childBSP(node:BSPNode):void {
+ if (node != null && node != dummyNode) {
+ var primitive:PolyPrimitive = node.primitive;
+ if (primitive != null) {
+ childPrimitives[primitive] = true;
+ changedPrimitives[primitive] = true;
+ node.primitive = null;
+ primitive.node = null;
+ } else {
+ for (var key:* in node.backPrimitives) {
+ primitive = key;
+ childPrimitives[primitive] = true;
+ changedPrimitives[primitive] = true;
+ primitive.node = null;
+ }
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ childPrimitives[primitive] = true;
+ changedPrimitives[primitive] = true;
+ primitive.node = null;
+ }
+ node.backPrimitives = null;
+ node.frontPrimitives = null;
+ }
+ childBSP(node.back);
+ childBSP(node.front);
+ // Удаляем ноду
+ node.parent = null;
+ node.back = null;
+ node.front = null;
+ BSPNode.destroyBSPNode(node);
+ }
+ }
+
+ /**
+ * @private
+ * Сборка списка дочерних примитивов в коллектор
+ */
+ protected function assembleChildPrimitives():void {
+ var primitive:PolyPrimitive;
+ while ((primitive = childPrimitives.take()) != null) {
+ assemblePrimitive(primitive);
+ }
+ }
+
+ /**
+ * @private
+ * Сборка примитивов и разделение на добавленные и удалённые
+ *
+ * @param primitive
+ */
+ private function assemblePrimitive(primitive:PolyPrimitive):void {
+ // Если есть соседний примитив и он может быть собран
+ if (primitive.sibling != null && canAssemble(primitive.sibling)) {
+ // Собираем их в родительский
+ assemblePrimitive(primitive.parent);
+ // Зачищаем связи между примитивами
+ primitive.sibling.sibling = null;
+ primitive.sibling.parent = null;
+ PolyPrimitive.destroyPolyPrimitive(primitive.sibling);
+ primitive.sibling = null;
+ primitive.parent.backFragment = null;
+ primitive.parent.frontFragment = null;
+ primitive.parent = null;
+ PolyPrimitive.destroyPolyPrimitive(primitive);
+ } else {
+ // Если собраться не получилось или родительский
+ addPrimitives.push(primitive);
+ }
+ }
+
+ /**
+ * @private
+ * Проверка, может ли примитив в списке дочерних быть собран
+ *
+ * @param primitive
+ * @return
+ */
+ private function canAssemble(primitive:PolyPrimitive):Boolean {
+ if (childPrimitives[primitive]) {
+ delete childPrimitives[primitive];
+ return true;
+ } else {
+ var backFragment:PolyPrimitive = primitive.backFragment;
+ var frontFragment:PolyPrimitive = primitive.frontFragment;
+ if (backFragment != null) {
+ var assembleBack:Boolean = canAssemble(backFragment);
+ var assembleFront:Boolean = canAssemble(frontFragment);
+ if (assembleBack && assembleFront) {
+ backFragment.parent = null;
+ frontFragment.parent = null;
+ backFragment.sibling = null;
+ frontFragment.sibling = null;
+ primitive.backFragment = null;
+ primitive.frontFragment = null;
+ PolyPrimitive.destroyPolyPrimitive(backFragment);
+ PolyPrimitive.destroyPolyPrimitive(frontFragment);
+ return true;
+ } else {
+ if (assembleBack) {
+ addPrimitives.push(backFragment);
+ }
+ if (assembleFront) {
+ addPrimitives.push(frontFragment);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @private
+ * Очистка списков
+ */
+ private function clearPrimitives():void {
+ changedPrimitives.clear();
+ }
+
+ /**
+ * Корневой объект сцены.
+ */
+ public function get root():Object3D {
+ return _root;
+ }
+
+ /**
+ * @private
+ */
+ public function set root(value:Object3D):void {
+ // Если ещё не является корневым объектом
+ if (_root != value) {
+ // Если устанавливаем не пустой объект
+ if (value != null) {
+ // Если объект был в другом объекте
+ if (value._parent != null) {
+ // Удалить его оттуда
+ value._parent._children.remove(value);
+ } else {
+ // Если объект был корневым в сцене
+ if (value._scene != null && value._scene._root == value) {
+ value._scene.root = null;
+ }
+ }
+ // Удаляем ссылку на родителя
+ value.setParent(null);
+ // Указываем сцену
+ value.setScene(this);
+ // Устанавливаем уровни
+ value.setLevel(0);
+ }
+
+ // Если был корневой объект
+ if (_root != null) {
+ // Удаляем ссылку на родителя
+ _root.setParent(null);
+ // Удаляем ссылку на камеру
+ _root.setScene(null);
+ }
+
+ // Сохраняем корневой объект
+ _root = value;
+ }
+ }
+
+ /**
+ * Флаг активности анализа сплиттеров.
+ * В режиме анализа для каждого добавляемого в BSP-дерево полигона выполняется его оценка в качестве разделяющей
+ * плоскости (сплиттера). Наиболее качественные сплиттеры добавляются в BSP-дерево первыми.
+ *
+ * splitBalance можно влиять на конечный вид BSP-дерева.
+ *
+ * @see #splitBalance
+ * @default true
+ */
+ public function get splitAnalysis():Boolean {
+ return _splitAnalysis;
+ }
+
+ /**
+ * @private
+ */
+ public function set splitAnalysis(value:Boolean):void {
+ if (_splitAnalysis != value) {
+ _splitAnalysis = value;
+ addOperation(updateBSPOperation);
+ }
+ }
+
+ /**
+ * Параметр балансировки BSP-дерева при влюченном режиме анализа сплиттеров.
+ * Может принимать значения от 0 (минимизация фрагментирования полигонов) до 1 (максимальный баланс BSP-дерева).
+ *
+ * @see #splitAnalysis
+ * @default 0
+ */
+ public function get splitBalance():Number {
+ return _splitBalance;
+ }
+
+ /**
+ * @private
+ */
+ public function set splitBalance(value:Number):void {
+ value = (value < 0) ? 0 : ((value > 1) ? 1 : value);
+ if (_splitBalance != value) {
+ _splitBalance = value;
+ if (_splitAnalysis) {
+ addOperation(updateBSPOperation);
+ }
+ }
+ }
+
+ /**
+ * Погрешность определения расстояний и координат. При построении BSP-дерева точка считается попавшей в плоскость сплиттера, если расстояние от точки до плоскости меньше planeOffsetThreshold.
+ *
+ * @default 0.01
+ */
+ public function get planeOffsetThreshold():Number {
+ return _planeOffsetThreshold;
+ }
+
+ /**
+ * @private
+ */
+ public function set planeOffsetThreshold(value:Number):void {
+ value = (value < 0) ? 0 : value;
+ if (_planeOffsetThreshold != value) {
+ _planeOffsetThreshold = value;
+ addOperation(updateBSPOperation);
+ }
+ }
+
+ /**
+ * Визуализация BSP-дерева. Дерево рисуется в заданном контейнере. Каждый узел дерева обозначается точкой, имеющей
+ * цвет материала (в случае текстурного материала показывается цвет первой точки текстуры) первого полигона из этого
+ * узла. Задние узлы рисуются слева-снизу от родителя, передние справа-снизу.
+ *
+ * @param container контейнер для отрисовки дерева
+ */
+ public function drawBSP(container:Sprite):void {
+
+ container.graphics.clear();
+ while (container.numChildren > 0) {
+ container.removeChildAt(0);
+ }
+ if (bsp != null) {
+ drawBSPNode(bsp, container, 0, 0, 1);
+ }
+ }
+
+ /**
+ * @private
+ * Отрисовка узла BSP-дерева при визуализации
+ *
+ * @param node
+ * @param container
+ * @param x
+ * @param y
+ * @param size
+ */
+ private function drawBSPNode(node:BSPNode, container:Sprite, x:Number, y:Number, size:Number):void {
+ var s:Shape = new Shape();
+ container.addChild(s);
+ s.x = x;
+ s.y = y;
+ var color:uint = 0xFF0000;
+ var primitive:PolyPrimitive;
+ if (node.primitive != null) {
+ primitive = node.primitive;
+ } else {
+ if (node.frontPrimitives != null) {
+ primitive = node.frontPrimitives.peek();
+ }
+ }
+ if (primitive != null) {
+ if (primitive.face._surface != null && primitive.face._surface._material != null) {
+ if (primitive.face._surface._material is FillMaterial) {
+ color = FillMaterial(primitive.face._surface._material)._color;
+ }
+ if (primitive.face._surface._material is WireMaterial) {
+ color = WireMaterial(primitive.face._surface._material)._color;
+ }
+ if (primitive.face._surface._material is TextureMaterial) {
+ color = TextureMaterial(primitive.face._surface._material).texture._bitmapData.getPixel(0, 0);
+ }
+ }
+ }
+
+ if (node == dummyNode) {
+ color = 0xFF00FF;
+ }
+
+ s.graphics.beginFill(color);
+ s.graphics.drawCircle(0, 0, 3);
+ s.graphics.endFill();
+
+ var xOffset:Number = 100;
+ var yOffset:Number = 20;
+ if (node.back != null) {
+ container.graphics.lineStyle(0, 0x660000);
+ container.graphics.moveTo(x, y);
+ container.graphics.lineTo(x - xOffset*size, y + yOffset);
+ drawBSPNode(node.back, container, x - xOffset*size, y + yOffset, size*0.8);
+ }
+ if (node.front != null) {
+ container.graphics.lineStyle(0, 0x006600);
+ container.graphics.moveTo(x, y);
+ container.graphics.lineTo(x + xOffset*size, y + yOffset);
+ drawBSPNode(node.front, container, x + xOffset*size, y + yOffset, size*0.8);
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/Surface.as b/Alternativa3D5/5.4/alternativa/engine3d/core/Surface.as
new file mode 100644
index 0000000..6e69daa
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/core/Surface.as
@@ -0,0 +1,350 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.errors.FaceExistsError;
+ import alternativa.engine3d.errors.FaceNotFoundError;
+ import alternativa.engine3d.errors.InvalidIDError;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.types.Set;
+
+ use namespace alternativa3d;
+
+ /**
+ * Поверхность — набор граней, объединённых в группу. Поверхности используются для установки материалов,
+ * визуализирующих грани объекта.
+ */
+ public class Surface {
+ // Операции
+ /**
+ * @private
+ * Изменение набора граней
+ */
+ alternativa3d var changeFacesOperation:Operation = new Operation("changeFaces", this);
+ /**
+ * @private
+ * Изменение материала
+ */
+ alternativa3d var changeMaterialOperation:Operation = new Operation("changeMaterial", this);
+
+ /**
+ * @private
+ * Меш
+ */
+ alternativa3d var _mesh:Mesh;
+ /**
+ * @private
+ * Материал
+ */
+ alternativa3d var _material:SurfaceMaterial;
+ /**
+ * @private
+ * Грани
+ */
+ alternativa3d var _faces:Set = new Set();
+
+ /**
+ * Создание экземпляра поверхности.
+ */
+ public function Surface() {}
+
+ /**
+ * Добавление грани в поверхность.
+ *
+ * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани полигонального объекта
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError грань не найдена в полигональном объекте содержащем поверхность
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ * @throws alternativa.engine3d.errors.FaceExistsError поверхность уже содержит указанную грань
+ *
+ * @see Face
+ */
+ public function addFace(face:Object):void {
+ var byLink:Boolean = face is Face;
+
+ // Проверяем на нахождение поверхности в меше
+ if (_mesh == null) {
+ throw new FaceNotFoundError(face, this);
+ }
+
+ // Проверяем на null
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие грани в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Face(face)._mesh != _mesh) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_mesh._faces[face] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(face, this);
+ } else {
+ if (!(_mesh._faces[face] is Face)) {
+ throw new InvalidIDError(face, this);
+ }
+ }
+ }
+
+ // Находим грань
+ var f:Face = byLink ? Face(face) : _mesh._faces[face];
+
+ // Проверяем наличие грани в поверхности
+ if (_faces.has(f)) {
+ // Если грань уже в поверхности
+ throw new FaceExistsError(f, this);
+ }
+
+ // Проверяем грань на нахождение в другой поверхности
+ if (f._surface != null) {
+ // Удаляем её из той поверхности
+ f._surface._faces.remove(f);
+ f.removeFromSurface(f._surface);
+ }
+
+ // Добавляем грань в поверхность
+ _faces.add(f);
+ f.addToSurface(this);
+
+ // Отправляем операцию изменения набора граней
+ _mesh.addOperationToScene(changeFacesOperation);
+ }
+
+ /**
+ * Удаление грани из поверхности.
+ *
+ * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани полигонального объекта
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError поверхность не содержит указанную грань
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Face
+ */
+ public function removeFace(face:Object):void {
+ var byLink:Boolean = face is Face;
+
+ // Проверяем на нахождение поверхности в меше
+ if (_mesh == null) {
+ throw new FaceNotFoundError(face, this);
+ }
+
+ // Проверяем на null
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие грани в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Face(face)._mesh != _mesh) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_mesh._faces[face] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(face, this);
+ } else {
+ if (!(_mesh._faces[face] is Face)) {
+ throw new InvalidIDError(face, this);
+ }
+ }
+
+ }
+
+ // Находим грань
+ var f:Face = byLink ? Face(face) : _mesh._faces[face];
+
+ // Проверяем наличие грани в поверхности
+ if (!_faces.has(f)) {
+ // Если грань не в поверхности
+ throw new FaceNotFoundError(f, this);
+ }
+
+ // Удаляем грань из поверхности
+ _faces.remove(f);
+ f.removeFromSurface(this);
+
+ // Отправляем операцию изменения набора граней
+ _mesh.addOperationToScene(changeFacesOperation);
+ }
+
+ /**
+ * Материал поверхности. При установке нового значения, устанавливаемый материал будет удалён из старой поверхности.
+ */
+ public function get material():SurfaceMaterial {
+ return _material;
+ }
+
+ /**
+ * @private
+ */
+ public function set material(value:SurfaceMaterial):void {
+ if (_material != value) {
+ // Если был материал
+ if (_material != null) {
+ // Удалить материал из поверхности
+ _material.removeFromSurface(this);
+ // Удалить материал из меша
+ if (_mesh != null) {
+ _material.removeFromMesh(_mesh);
+ // Удалить материал из сцены
+ if (_mesh._scene != null) {
+ _material.removeFromScene(_mesh._scene);
+ }
+ }
+ }
+ // Если новый материал
+ if (value != null) {
+ // Если материал был в другой поверхности
+ if (value._surface != null) {
+ // Удалить его оттуда
+ value._surface.material = null;
+ }
+ // Добавить материал в поверхность
+ value.addToSurface(this);
+ // Добавить материал в меш
+ if (_mesh != null) {
+ value.addToMesh(_mesh);
+ // Добавить материал в сцену
+ if (_mesh._scene != null) {
+ value.addToScene(_mesh._scene);
+ }
+ }
+ }
+ // Сохраняем материал
+ _material = value;
+ // Отправляем операцию изменения материала
+ addMaterialChangedOperationToScene();
+ }
+ }
+
+ /**
+ * Набор граней поверхности.
+ */
+ public function get faces():Set {
+ return _faces.clone();
+ }
+
+ /**
+ * Полигональный объект, которому принадлежит поверхность.
+ */
+ public function get mesh():Mesh {
+ return _mesh;
+ }
+
+ /**
+ * Идентификатор поверхности в полигональном объекте. Если поверхность не принадлежит ни одному объекту,
+ * значение идентификатора равно null.
+ */
+ public function get id():Object {
+ return (_mesh != null) ? _mesh.getSurfaceId(this) : null;
+ }
+
+ /**
+ * @private
+ * Добавление в сцену.
+ *
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {
+ // Добавляем на сцену материал
+ if (_material != null) {
+ _material.addToScene(scene);
+ }
+ }
+
+ /**
+ * @private
+ * Удаление из сцены.
+ *
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(changeFacesOperation);
+ scene.removeOperation(changeMaterialOperation);
+ // Удаляем из сцены материал
+ if (_material != null) {
+ _material.removeFromScene(scene);
+ }
+ }
+
+ /**
+ * @private
+ * Добавление к мешу
+ * @param mesh
+ */
+ alternativa3d function addToMesh(mesh:Mesh):void {
+ // Подписка на операции меша
+
+ // Добавляем в меш материал
+ if (_material != null) {
+ _material.addToMesh(mesh);
+ }
+ // Сохранить меш
+ _mesh = mesh;
+ }
+
+ /**
+ * @private
+ * Удаление из меша
+ *
+ * @param mesh
+ */
+ alternativa3d function removeFromMesh(mesh:Mesh):void {
+ // Отписка от операций меша
+
+ // Удаляем из меша материал
+ if (_material != null) {
+ _material.removeFromMesh(mesh);
+ }
+ // Удалить ссылку на меш
+ _mesh = null;
+ }
+
+ /**
+ * @private
+ * Удаление граней
+ */
+ alternativa3d function removeFaces():void {
+ for (var key:* in _faces) {
+ var face:Face = key;
+ _faces.remove(face);
+ face.removeFromSurface(this);
+ }
+ }
+
+ /**
+ * @private
+ * Изменение материала
+ */
+ alternativa3d function addMaterialChangedOperationToScene():void {
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeMaterialOperation);
+ }
+ }
+
+ /**
+ * Строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ var length:uint = _faces.length;
+ var res:String = "[Surface ID:" + id + ((length > 0) ? " faces:" : "");
+ var i:uint = 0;
+ for (var key:* in _faces) {
+ var face:Face = key;
+ res += face.id + ((i < length - 1) ? ", " : "");
+ i++;
+ }
+ res += "]";
+ return res;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/core/Vertex.as b/Alternativa3D5/5.4/alternativa/engine3d/core/Vertex.as
new file mode 100644
index 0000000..cceb91e
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/core/Vertex.as
@@ -0,0 +1,250 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ use namespace alternativa3d;
+
+ /**
+ * Вершина полигона в трёхмерном пространстве. Вершина хранит свои координаты, а также ссылки на
+ * полигональный объект и грани этого объекта, которым она принадлежит.
+ */
+ final public class Vertex {
+ // Операции
+ /**
+ * @private
+ * Изменение локальных координат
+ */
+ alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this);
+ /**
+ * @private
+ * Расчёт глобальных координат
+ */
+ alternativa3d var calculateCoordsOperation:Operation = new Operation("calculateCoords", this, calculateCoords, Operation.VERTEX_CALCULATE_COORDS);
+
+ /**
+ * @private
+ * Меш
+ */
+ alternativa3d var _mesh:Mesh;
+ /**
+ * @private
+ * Координаты точки
+ */
+ alternativa3d var _coords:Point3D;
+ /**
+ * @private
+ * Грани
+ */
+ alternativa3d var _faces:Set = new Set();
+ /**
+ * @private
+ * Координаты в сцене
+ */
+ alternativa3d var globalCoords:Point3D = new Point3D();
+
+ /**
+ * Создание экземпляра вершины.
+ *
+ * @param x координата вершины по оси X
+ * @param y координата вершины по оси Y
+ * @param z координата вершины по оси Z
+ */
+ public function Vertex(x:Number = 0, y:Number = 0, z:Number = 0) {
+ _coords = new Point3D(x, y, z);
+
+ // Изменение координат инициирует пересчёт глобальных координат
+ changeCoordsOperation.addSequel(calculateCoordsOperation);
+ }
+
+ /**
+ * Вызывается из операции calculateCoordsOperation для расчета глобальных координат вершины
+ */
+ private function calculateCoords():void {
+ globalCoords.copy(_coords);
+ globalCoords.transform(_mesh.transformation);
+ }
+
+ /**
+ * @private
+ */
+ public function set x(value:Number):void {
+ if (_coords.x != value) {
+ _coords.x = value;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeCoordsOperation);
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set y(value:Number):void {
+ if (_coords.y != value) {
+ _coords.y = value;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeCoordsOperation);
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set z(value:Number):void {
+ if (_coords.z != value) {
+ _coords.z = value;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeCoordsOperation);
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set coords(value:Point3D):void {
+ if (!_coords.equals(value)) {
+ _coords.copy(value);
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeCoordsOperation);
+ }
+ }
+ }
+
+ /**
+ * координата вершины по оси X.
+ */
+ public function get x():Number {
+ return _coords.x;
+ }
+
+ /**
+ * координата вершины по оси Y.
+ */
+ public function get y():Number {
+ return _coords.y;
+ }
+
+ /**
+ * координата вершины по оси Z.
+ */
+ public function get z():Number {
+ return _coords.z;
+ }
+
+ /**
+ * Координаты вершины.
+ */
+ public function get coords():Point3D {
+ return _coords.clone();
+ }
+
+ /**
+ * Полигональный объект, которому принадлежит вершина.
+ */
+ public function get mesh():Mesh {
+ return _mesh;
+ }
+
+ /**
+ * Множество граней, которым принадлежит вершина. Каждый элемент множества является объектом класса
+ * altertnativa.engine3d.core.Face.
+ *
+ * @see Face
+ */
+ public function get faces():Set {
+ return _faces.clone();
+ }
+
+ /**
+ * Идентификатор вершины в полигональном объекте. Если вершина не принадлежит полигональному объекту, возвращается null.
+ */
+ public function get id():Object {
+ return (_mesh != null) ? _mesh.getVertexId(this) : null;
+ }
+
+ /**
+ * @private
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {
+ // При добавлении на сцену расчитать глобальные координаты
+ scene.addOperation(calculateCoordsOperation);
+ }
+
+ /**
+ * @private
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(calculateCoordsOperation);
+ scene.removeOperation(changeCoordsOperation);
+ }
+
+ /**
+ * @private
+ * @param mesh
+ */
+ alternativa3d function addToMesh(mesh:Mesh):void {
+ // Подписка на операции меша
+ mesh.changeCoordsOperation.addSequel(calculateCoordsOperation);
+ mesh.changeRotationOrScaleOperation.addSequel(calculateCoordsOperation);
+ // Сохранить меш
+ _mesh = mesh;
+ }
+
+ /**
+ * @private
+ * @param mesh
+ */
+ alternativa3d function removeFromMesh(mesh:Mesh):void {
+ // Отписка от операций меша
+ mesh.changeCoordsOperation.removeSequel(calculateCoordsOperation);
+ mesh.changeRotationOrScaleOperation.removeSequel(calculateCoordsOperation);
+ // Удалить зависимые грани
+ for (var key:* in _faces) {
+ var face:Face = key;
+ mesh.removeFace(face);
+ }
+ // Удалить ссылку на меш
+ _mesh = null;
+ }
+
+ /**
+ * @private
+ * @param face
+ */
+ alternativa3d function addToFace(face:Face):void {
+ // Подписка грани на операции
+ changeCoordsOperation.addSequel(face.calculateUVOperation);
+ changeCoordsOperation.addSequel(face.calculateNormalOperation);
+ // Добавить грань в список
+ _faces.add(face);
+ }
+
+ /**
+ * @private
+ * @param face
+ */
+ alternativa3d function removeFromFace(face:Face):void {
+ // Отписка грани от операций
+ changeCoordsOperation.removeSequel(face.calculateUVOperation);
+ changeCoordsOperation.removeSequel(face.calculateNormalOperation);
+ // Удалить грань из списка
+ _faces.remove(face);
+ }
+
+ /**
+ * Строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ return "[Vertex ID:" + id + " " + _coords.x.toFixed(2) + ", " + _coords.y.toFixed(2) + ", " + _coords.z.toFixed(2) + "]";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/display/Skin.as b/Alternativa3D5/5.4/alternativa/engine3d/display/Skin.as
new file mode 100644
index 0000000..cdcd378
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/display/Skin.as
@@ -0,0 +1,66 @@
+package alternativa.engine3d.display {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+
+ import flash.display.Graphics;
+ import flash.display.Sprite;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ * Контейнер, исползуемый материалами для отрисовки примитивов. Каждый примитив BSP-дерева рисуется в своём контейнере.
+ */
+ public class Skin extends Sprite {
+
+ /**
+ * @private
+ * Графика скина (для быстрого доступа)
+ */
+ alternativa3d var gfx:Graphics = graphics;
+
+ /**
+ * @private
+ * Ссылка на следующий скин
+ */
+ alternativa3d var nextSkin:Skin;
+
+ /**
+ * @private
+ * Примитив
+ */
+ alternativa3d var primitive:PolyPrimitive;
+
+ /**
+ * @private
+ * Материал, связанный со скином.
+ */
+ alternativa3d var material:SurfaceMaterial;
+
+ // Хранилище неиспользуемых скинов
+ static private var collector:Array = new Array();
+
+ /**
+ * @private
+ * Создание скина.
+ */
+ static alternativa3d function createSkin():Skin {
+ // Достаём скин из коллектора
+ var skin:Skin = collector.pop();
+ // Если коллектор пуст, создаём новый скин
+ if (skin == null) {
+ skin = new Skin();
+ }
+ return skin;
+ }
+
+ /**
+ * @private
+ * Удаление скина, все ссылки должны быть почищены.
+ */
+ static alternativa3d function destroySkin(skin:Skin):void {
+ collector.push(skin);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/display/View.as b/Alternativa3D5/5.4/alternativa/engine3d/display/View.as
new file mode 100644
index 0000000..5ac3e5d
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/display/View.as
@@ -0,0 +1,186 @@
+package alternativa.engine3d.display {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Face;
+
+ import flash.display.Sprite;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Область для вывода изображения с камеры.
+ */
+ public class View extends Sprite {
+
+ /**
+ * @private
+ * Область отрисовки спрайтов
+ */
+ alternativa3d var canvas:Sprite;
+
+ private var _camera:Camera3D;
+
+ /**
+ * @private
+ * Ширина области вывода
+ */
+ alternativa3d var _width:Number;
+ /**
+ * @private
+ * Высота области вывода
+ */
+ alternativa3d var _height:Number;
+
+ /**
+ * Создание экземпляра области вывода.
+ *
+ * @param camera камера, изображение с которой должно выводиться
+ * @param width ширина области вывода
+ * @param height высота области вывода
+ */
+ public function View(camera:Camera3D = null, width:Number = 0, height:Number = 0) {
+ canvas = new Sprite();
+ canvas.mouseEnabled = false;
+ canvas.mouseChildren = false;
+ canvas.tabEnabled = false;
+ canvas.tabChildren = false;
+ addChild(canvas);
+
+ this.camera = camera;
+ this.width = width;
+ this.height = height;
+ }
+
+ /**
+ * Камера с которой ведётся отображение.
+ */
+ public function get camera():Camera3D {
+ return _camera;
+ }
+
+ /**
+ * @private
+ */
+ public function set camera(value:Camera3D):void {
+ if (_camera != value) {
+ // Если была камера
+ if (_camera != null) {
+ // Удалить камеру
+ _camera.removeFromView(this);
+ }
+ // Если новая камера
+ if (value != null) {
+ // Если камера была в другом вьюпорте
+ if (value._view != null) {
+ // Удалить её оттуда
+ value._view.camera = null;
+ }
+ // Добавить камеру
+ value.addToView(this);
+ } else {
+ // Зачистка скинов
+ if (canvas.numChildren > 0) {
+ var skin:Skin = Skin(canvas.getChildAt(0));
+ while (skin != null) {
+ // Сохраняем следующий
+ var next:Skin = skin.nextSkin;
+ // Удаляем из канваса
+ canvas.removeChild(skin);
+ // Очистка скина
+ if (skin.material != null) {
+ skin.material.clear(skin);
+ }
+ // Зачищаем ссылки
+ skin.nextSkin = null;
+ skin.primitive = null;
+ skin.material = null;
+ // Удаляем
+ Skin.destroySkin(skin);
+ // Следующий устанавливаем текущим
+ skin = next;
+ }
+ }
+ }
+ // Сохраняем камеру
+ _camera = value;
+ }
+ }
+
+ /**
+ * Ширина области вывода в пикселях.
+ */
+ override public function get width():Number {
+ return _width;
+ }
+
+ /**
+ * @private
+ */
+ override public function set width(value:Number):void {
+ if (_width != value) {
+ _width = value;
+ canvas.x = _width*0.5;
+ if (_camera != null) {
+ camera.addOperationToScene(camera.calculatePlanesOperation);
+ }
+ }
+ }
+
+ /**
+ * Высота области вывода в пикселях.
+ */
+ override public function get height():Number {
+ return _height;
+ }
+
+ /**
+ * @private
+ */
+ override public function set height(value:Number):void {
+ if (_height != value) {
+ _height = value;
+ canvas.y = _height*0.5;
+ if (_camera != null) {
+ camera.addOperationToScene(camera.calculatePlanesOperation);
+ }
+ }
+ }
+
+ /**
+ * Метод возвращает грань, находящуюся под указанной точкой в области вывода.
+ *
+ * @param viewPoint координаты точки относительно области вывода
+ *
+ * @return ближайшая к камере грань под заданной точкой области вывода
+ */
+ public function getFaceUnderPoint(viewPoint:Point):Face {
+ var p:Point = localToGlobal(viewPoint);
+ var objects:Array = canvas.getObjectsUnderPoint(p);
+ var skin:Skin = objects.pop() as Skin;
+ if (skin != null) {
+ return skin.primitive.face;
+ }
+ return null;
+ }
+
+ /**
+ * Метод возвращает грани, находящиеся под указанной точкой в области вывода.
+ *
+ * @param viewPoint координаты точки относительно области вывода
+ *
+ * @return массив граней, расположенных под заданной точкой области вывода. Первым элементом массива является самая дальняя грань.
+ */
+ public function getFacesUnderPoint(viewPoint:Point):Array {
+ var p:Point = localToGlobal(viewPoint);
+ var objects:Array = canvas.getObjectsUnderPoint(p);
+ var res:Array = new Array();
+ var length:uint = objects.length;
+ for (var i:uint = 0; i < length; i++) {
+ var skin:Skin = objects[i];
+ res.push(skin.primitive.face);
+ }
+ return res;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/Engine3DError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/Engine3DError.as
new file mode 100644
index 0000000..bcf6397
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/Engine3DError.as
@@ -0,0 +1,27 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Базовый класс для ошибок 3d-engine.
+ */
+ public class Engine3DError extends Error {
+
+ /**
+ * Источник ошибки - объект в котором произошла ошибка.
+ */
+ public var source:Object;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param message описание ошибки
+ * @param source источник ошибки
+ */
+ public function Engine3DError(message:String = "", source:Object = null) {
+ super(message);
+ this.source = source;
+ this.name = "Engine3DError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceExistsError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceExistsError.as
new file mode 100644
index 0000000..ecc7f59
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceExistsError.as
@@ -0,0 +1,35 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+ import alternativa.engine3d.core.Surface;
+
+ /**
+ * Ошибка, возникающая при попытке добавить в какой-либо объект грань, уже содержащуюся в данном объекте.
+ */
+ public class FaceExistsError extends ObjectExistsError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param face экземпляр или идентификатор грани, которая уже содержится в объекте
+ * @param source источник ошибки
+ */
+ public function FaceExistsError(face:Object = null, source:Object = null) {
+ var message:String;
+ if (source is Mesh) {
+ message = "Mesh ";
+ } else if (source is Surface) {
+ message = "Surface ";
+ }
+ if (face is Face) {
+ message += "%1. Face %2 already exists.";
+ } else {
+ message += "%1. Face with ID '%2' already exists.";
+ }
+ super(TextUtils.insertVars(message, source, face), face, source);
+ this.name = "FaceExistsError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as
new file mode 100644
index 0000000..3341f63
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as
@@ -0,0 +1,29 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая недостаточное количество вершин для создания грани.
+ * Для создания грани должно быть указано не менее трех вершин.
+ */
+ public class FaceNeedMoreVerticesError extends Engine3DError {
+
+ /**
+ * Количество переданных для создания грани вершин
+ */
+ public var count:uint;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param mesh объект, в котором произошла ошибка
+ * @param count количество вершин, переданное для создания грани
+ */
+ public function FaceNeedMoreVerticesError(mesh:Mesh = null, count:uint = 0) {
+ super(TextUtils.insertVars("Mesh %1. %2 vertices not enough for face creation.", mesh, count), mesh);
+ this.count = count;
+ this.name = "FaceNeedMoreVerticesError";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceNotFoundError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceNotFoundError.as
new file mode 100644
index 0000000..912d6a4
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/FaceNotFoundError.as
@@ -0,0 +1,34 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, возникающая, если грань не найдена в объекте.
+ */
+ public class FaceNotFoundError extends ObjectNotFoundError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param face экземпляр или идентификатор грани
+ * @param source объект, в котором произошла ошибка
+ */
+ public function FaceNotFoundError(face:Object = null, source:Object = null) {
+ var message:String;
+ if (source is Mesh) {
+ message = "Mesh ";
+ } else {
+ message = "Surface ";
+ }
+ if (face is Face) {
+ message += "%1. Face %2 not found.";
+ } else {
+ message += "%1. Face with ID '%2' not found.";
+ }
+ super(TextUtils.insertVars(message, source, face), face, source);
+ this.name = "FaceNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/InvalidIDError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/InvalidIDError.as
new file mode 100644
index 0000000..fbf4666
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/InvalidIDError.as
@@ -0,0 +1,34 @@
+package alternativa.engine3d.errors {
+ import alternativa.utils.TextUtils;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Surface;
+
+
+ /**
+ * Ошибка, обозначающая, что идентификатор зарезервирован и не может быть использован.
+ */
+ public class InvalidIDError extends Engine3DError {
+ /**
+ * Зарезервированный идентификатор
+ */
+ public var id:Object;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param id идентификатор
+ * @param source объект, в котором произошла ошибка
+ */
+ public function InvalidIDError(id:Object = null, source:Object = null) {
+ var message:String;
+ if (source is Mesh) {
+ message = "Mesh %2. ";
+ } else if (source is Surface) {
+ message = "Surface %2. ";
+ }
+ super(TextUtils.insertVars(message + "ID %1 is reserved and cannot be used", [id, source]), source);
+ this.id = id;
+ this.name = "InvalidIDError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/Object3DHierarchyError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/Object3DHierarchyError.as
new file mode 100644
index 0000000..8d61bd0
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/Object3DHierarchyError.as
@@ -0,0 +1,29 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, связанная с нарушением иерархии объектов сцены.
+ */
+ public class Object3DHierarchyError extends Engine3DError
+ {
+
+ /**
+ * Объект сцены, нарушающий иерархию
+ */
+ public var object:Object3D;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param object объект, нарушающий иерархию
+ * @param source источник ошибки
+ */
+ public function Object3DHierarchyError(object:Object3D = null, source:Object3D = null) {
+ super(TextUtils.insertVars("Object3D %1. Object %2 cannot be added", source, object), source);
+ this.object = object;
+ this.name = "Object3DHierarchyError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/Object3DNotFoundError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/Object3DNotFoundError.as
new file mode 100644
index 0000000..d1ddd1e
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/Object3DNotFoundError.as
@@ -0,0 +1,22 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, возникающая, когда объект сцены не был найден в списке связанных с необходимым объектом сцены.
+ */
+ public class Object3DNotFoundError extends ObjectNotFoundError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param object ненайденный объект сцены
+ * @param source объект сцены, в котором произошла ошибка
+ */
+ public function Object3DNotFoundError(object:Object3D = null, source:Object3D = null) {
+ super(TextUtils.insertVars("Object3D %1. Object %2 not in child list", source, object), object, source);
+ this.name = "Object3DNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/ObjectExistsError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/ObjectExistsError.as
new file mode 100644
index 0000000..7743a53
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/ObjectExistsError.as
@@ -0,0 +1,26 @@
+package alternativa.engine3d.errors {
+
+ /**
+ * Ошибка, обозначающая, что объект уже присутствует в контейнере.
+ */
+ public class ObjectExistsError extends Engine3DError {
+
+ /**
+ * Экземпляр или идентификатор объекта, который уже присутствует в контейнере
+ */
+ public var object:Object;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param message описание ошибки
+ * @param object объект, который уже присутствует в контейнере
+ * @param source объект, вызвавший ошибку
+ */
+ public function ObjectExistsError(message:String = "", object:Object = null, source:Object = null) {
+ super(message, source);
+ this.object = object;
+ this.name = "ObjectExistsError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/ObjectNotFoundError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/ObjectNotFoundError.as
new file mode 100644
index 0000000..87b3d14
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/ObjectNotFoundError.as
@@ -0,0 +1,26 @@
+package alternativa.engine3d.errors {
+
+ /**
+ * Необходимый объект не был найден в контейнере.
+ */
+ public class ObjectNotFoundError extends Engine3DError {
+
+ /**
+ * Объект, который отсутствует в контейнере.
+ */
+ public var object:Object;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param message описание ошибки
+ * @param object отсутствующий объект
+ * @param source объект, вызвавший ошибку
+ */
+ public function ObjectNotFoundError(message:String = "", object:Object = null, source:Object = null) {
+ super(message, source);
+ this.object = object;
+ this.name = "ObjectNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/SurfaceExistsError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/SurfaceExistsError.as
new file mode 100644
index 0000000..e13bee7
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/SurfaceExistsError.as
@@ -0,0 +1,22 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая, что поверхность уже присутствует в контейнере.
+ */
+ public class SurfaceExistsError extends ObjectExistsError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param surface поверхность, которая уже присутствует в контейнере
+ * @param mesh объект, вызвавший ошибку
+ */
+ public function SurfaceExistsError(surface:Object = null, mesh:Mesh = null) {
+ super(TextUtils.insertVars("Mesh %1. Surface with ID '%2' already exists.", mesh, surface), surface, mesh);
+ this.name = "SurfaceExistsError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/SurfaceNotFoundError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/SurfaceNotFoundError.as
new file mode 100644
index 0000000..05457bf
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/SurfaceNotFoundError.as
@@ -0,0 +1,31 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая, что поверхность не найдена в контейнере.
+ */
+ public class SurfaceNotFoundError extends ObjectNotFoundError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param surface поверхность, которая отсутствует в объекте
+ * @param mesh объект, который вызвал ошибку
+ */
+ public function SurfaceNotFoundError(surface:Object = null, mesh:Mesh = null) {
+ if (mesh == null) {
+
+ }
+ if (surface is Surface) {
+ message = "Mesh %1. Surface %2 not found.";
+ } else {
+ message = "Mesh %1. Surface with ID '%2' not found.";
+ }
+ super(TextUtils.insertVars(message, mesh, surface), surface, mesh);
+ this.name = "SurfaceNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/VertexExistsError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/VertexExistsError.as
new file mode 100644
index 0000000..9f0fff5
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/VertexExistsError.as
@@ -0,0 +1,22 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая, что вершина уже содержится в объекте.
+ */
+ public class VertexExistsError extends ObjectExistsError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param vertex вершина, которая уже есть в объекте
+ * @param mesh объект, вызвавший ошибку
+ */
+ public function VertexExistsError(vertex:Object = null, mesh:Mesh = null) {
+ super(TextUtils.insertVars("Mesh %1. Vertex with ID '%2' already exists.", mesh, vertex), vertex, mesh);
+ this.name = "VertexExistsError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/errors/VertexNotFoundError.as b/Alternativa3D5/5.4/alternativa/engine3d/errors/VertexNotFoundError.as
new file mode 100644
index 0000000..edf80aa
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/errors/VertexNotFoundError.as
@@ -0,0 +1,28 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая, что вершина не найдена в объекте.
+ */
+ public class VertexNotFoundError extends ObjectNotFoundError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param vertex вершина, которая не найдена в объекте
+ * @param mesh объект, вызвавший ошибку
+ */
+ public function VertexNotFoundError(vertex:Object = null, mesh:Mesh = null) {
+ if (vertex is Vertex) {
+ message = "Mesh %1. Vertex %2 not found.";
+ } else {
+ message = "Mesh %1. Vertex with ID '%2' not found.";
+ }
+ super(TextUtils.insertVars(message, mesh, vertex), vertex, mesh);
+ this.name = "VertexNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/loaders/Loader3DS.as b/Alternativa3D5/5.4/alternativa/engine3d/loaders/Loader3DS.as
new file mode 100644
index 0000000..098f91b
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/loaders/Loader3DS.as
@@ -0,0 +1,1055 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.engine3d.materials.TextureMaterialPrecision;
+ import alternativa.engine3d.materials.WireMaterial;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Texture;
+ import alternativa.utils.ColorUtils;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.Bitmap;
+ import flash.display.BitmapData;
+ import flash.display.BlendMode;
+ import flash.display.Loader;
+ import flash.errors.IOError;
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IOErrorEvent;
+ import flash.geom.Matrix;
+ import flash.geom.Point;
+ import flash.net.URLLoader;
+ import flash.net.URLLoaderDataFormat;
+ import flash.net.URLRequest;
+ import flash.system.LoaderContext;
+ import flash.utils.ByteArray;
+ import flash.utils.Endian;
+ import alternativa.types.Map;
+ import flash.events.SecurityErrorEvent;
+
+ use namespace alternativa3d;
+
+ /**
+ * Загрузчик моделей в формате 3DS.
+ *
+ *
+ *
+ *
+ * Порядок загрузки каждого объекта из 3DS-файла:
+ * Object3D;
+ * TextureMaterial,
+ * иначе в виде FillMaterial с указанным цветом. Если произошла ошибка при загрузке файла текстуры
+ * (например, файл отсутствует), то создаётся текстура-заглушка и генерируется исключение.
+ *
+ *
+ *
+ * Перед загрузкой файла можно установить ряд свойств, влияющих на создаваемые текстурные материалы.
+ */
+ public class Loader3DS extends EventDispatcher {
+
+ /**
+ * Коэффициент пересчёта в дюймы.
+ */
+ public static const INCHES:Number = 1;
+
+ /**
+ * Коэффициент пересчёта в футы.
+ */
+ public static const FEET:Number = 0.0833333334;
+ /**
+ * Коэффициент пересчёта в мили.
+ */
+ public static const MILES:Number = 0.0000157828;
+ /**
+ * Коэффициент пересчёта в миллиметры.
+ */
+ public static const MILLIMETERS:Number = 25.4000000259;
+ /**
+ * Коэффициент пересчёта в сантиметры.
+ */
+ public static const CENTIMETERS:Number = 2.5400000025;
+ /**
+ * Коэффициент пересчёта в метры.
+ */
+ public static const METERS:Number = 0.0254;
+ /**
+ * Коэффициент пересчёта в километры.
+ */
+ public static const KILOMETERS:Number = 0.0000254;
+
+ private static var stubBitmapData:BitmapData;
+
+ private var _content:Object3D;
+ private var version:uint;
+ private var objectDatas:Map;
+ private var animationDatas:Array;
+ private var materialDatas:Array;
+ private var bitmaps:Array;
+
+ private var urlLoader:URLLoader;
+ private var data:ByteArray;
+
+ private var counter:uint;
+ private var sequence:Array;
+ private var loader:Loader;
+ private var context:LoaderContext;
+ private var path:String;
+
+ /**
+ * Повтор текстуры при заливке для создаваемых текстурных материалов.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var repeat:Boolean = true;
+ /**
+ * Сглаживание текстур при увеличении масштаба.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var smooth:Boolean = false;
+ /**
+ * Режим наложения цвета для создаваемых текстурных материалов.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var blendMode:String = BlendMode.NORMAL;
+ /**
+ * Точность перспективной коррекции для создаваемых текстурных материалов.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var precision:Number = TextureMaterialPrecision.MEDIUM;
+
+ /**
+ * Коэффициент пересчёта единиц измерения модели.
+ */
+ public var units:Number = INCHES;
+ /**
+ * Устанавливаемый уровень мобильности загруженных объектов.
+ */
+ public var mobility:int = 0;
+
+ /**
+ * Прекращение текущей загрузки.
+ */
+ public function close():void {
+ try {
+ urlLoader.close();
+ } catch (e:Error) {
+ }
+ try {
+ loader.close();
+ } catch (e:Error) {
+ }
+ }
+
+ /**
+ * Загрузка сцены из 3DS-файла по указанному адресу. По окончании загрузки посылается сообщение Event.COMPLETE,
+ * после чего контейнер с загруженными объектами становится доступным через свойство content.
+ * IOErrorEvent.IO_ERROR и
+ * SecurityErrorEvent.SECURITY_ERROR соответственно.
+ * alternativa.engine3d.loaders.MaterialInfo.
+ * @see alternativa.engine3d.loaders.MaterialInfo
+ */
+ public function get library():Map {
+ return _library;
+ }
+
+ /**
+ * Метод выполняет загрузку файла материалов, разбор его содержимого, загрузку текстур при необходимости и
+ * формирование библиотеки материалов. После окончания работы метода посылается сообщение
+ * Event.COMPLETE и становится доступна библиотека материалов через свойство library.
+ * IOErrorEvent.IO_ERROR и
+ * SecurityErrorEvent.SECURITY_ERROR соответственно.
+ * Object3D.
+ *
+ *
+ *
+ *
+ *
+ * Команда
+ * Описание
+ * Действие
+ *
+ * o object_name
+ * Объявление нового объекта с именем object_name
+ * Если для текущего объекта были определены грани, то команда создаёт новый текущий объект с указанным именем,
+ * иначе у текущего объекта просто меняется имя на указанное.
+ *
+ *
+ * v x y z
+ * Объявление вершины с координатами x y z
+ * Вершина помещается в общий список вершин сцены для дальнейшего использования
+ *
+ *
+ * vt u [v]
+ * Объявление текстурной вершины с координатами u v
+ * Вершина помещается в общий список текстурных вершин сцены для дальнейшего использования
+ *
+ *
+ * f v0[/vt0] v1[/vt1] ... vN[/vtN]
+ * Объявление грани, состоящей из указанных вершин и опционально имеющую заданные текстурные координаты для вершин.
+ * Грань добавляется к текущему активному объекту. Если есть активный материал, то грань также добавляется в поверхность
+ * текущего объекта, соответствующую текущему материалу.
+ *
+ *
+ * usemtl material_name
+ * Установка текущего материала с именем material_name
+ * С момента установки текущего материала все грани, создаваемые в текущем объекте будут помещаться в поверхность,
+ * соотвествующую этому материалу и имеющую идентификатор, совпадающий с его именем.
+ *
+ *
+ * mtllib file1 file2 ...
+ * Объявление файлов, содержащих определения материалов
+ * Выполняется загрузка файлов и формирование библиотеки материалов
+ *
+ * var loader:LoaderOBJ = new LoaderOBJ();
+ * loader.addEventListener(Event.COMPLETE, onLoadingComplete);
+ * loader.load("foo.obj");
+ *
+ * function onLoadingComplete(e:Event):void {
+ * scene.root.addChild(e.target.content);
+ * }
+ *
+ */
+ public class LoaderOBJ extends EventDispatcher {
+
+ private static const COMMENT_CHAR:String = "#";
+
+ private static const CMD_OBJECT_NAME:String = "o";
+ private static const CMD_GROUP_NAME:String = "g";
+ private static const CMD_VERTEX:String = "v";
+ private static const CMD_TEXTURE_VERTEX:String = "vt";
+ private static const CMD_FACE:String = "f";
+ private static const CMD_MATERIAL_LIB:String = "mtllib";
+ private static const CMD_USE_MATERIAL:String = "usemtl";
+
+ private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/;
+ private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/;
+ private static const REGEXP_SPLIT_LINE:RegExp = /\s+/;
+
+ private var basePath:String;
+ private var objLoader:URLLoader;
+ private var mtlLoader:LoaderMTL;
+ private var loaderContext:LoaderContext;
+ private var loadMaterials:Boolean;
+ // Объект, содержащий все определённые в obj файле объекты
+ private var _content:Object3D;
+ // Текущий конструируемый объект
+ private var currentObject:Mesh;
+ // Стартовый индекс вершины в глобальном массиве вершин для текущего объекта
+ private var vIndexStart:int = 0;
+ // Стартовый индекс текстурной вершины в глобальном массиве текстурных вершин для текущего объекта
+ private var vtIndexStart:int = 0;
+ // Глобальный массив вершин, определённых во входном файле
+ private var globalVertices:Array;
+ // Глобальный массив текстурных вершин, определённых во входном файле
+ private var globalTextureVertices:Array;
+ // Имя текущего активного материала. Если значение равно null, то активного материала нет.
+ private var currentMaterialName:String;
+ // Массив граней текущего объекта, которым назначен текущий материал
+ private var materialFaces:Array;
+ // Массив имён файлов, содержащих определения материалов
+ private var materialFileNames:Array;
+ private var currentMaterialFileIndex:int;
+ private var materialLibrary:Map;
+
+ /**
+ * Сглаживание текстур при увеличении масштаба.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var smooth:Boolean = false;
+ /**
+ * Режим наложения цвета для создаваемых текстурных материалов.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var blendMode:String = BlendMode.NORMAL;
+ /**
+ * Точность перспективной коррекции для создаваемых текстурных материалов.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var precision:Number = TextureMaterialPrecision.MEDIUM;
+
+ /**
+ * Устанавливаемый уровень мобильности загруженных объектов.
+ */
+ public var mobility:int = 0;
+
+ /**
+ * При установленном значении true выполняется преобразование координат геометрических вершин посредством
+ * поворота на 90 градусов относительно оси X. Смысл флага в преобразовании системы координат, в которой вверх направлена
+ * ось Y, в систему координат, использующуюся в Alternativa3D (вверх направлена ось Z).
+ */
+ public var rotateModel:Boolean;
+
+ /**
+ * Создаёт новый экземпляр загрузчика.
+ */
+ public function LoaderOBJ() {
+ }
+
+ /**
+ * Контейнер, содержащий все загруженные из OBJ-файла модели.
+ */
+ public function get content():Object3D {
+ return _content;
+ }
+
+ /**
+ * Прекращение текущей загрузки.
+ */
+ public function close():void {
+ try {
+ objLoader.close();
+ } catch (e:Error) {
+ }
+ mtlLoader.close();
+ }
+
+ /**
+ * Загрузка сцены из OBJ-файла по указанному адресу. По окончании загрузки посылается сообщение Event.COMPLETE,
+ * после чего контейнер с загруженными объектами становится доступным через свойство content.
+ * IOErrorEvent.IO_ERROR и
+ * SecurityErrorEvent.SECURITY_ERROR соответственно.
+ * true, будут обработаны все файлы
+ * материалов, указанные в исходном OBJ-файле.
+ * @param context LoaderContext для загрузки файлов текстур
+ *
+ * @see #content
+ */
+ public function load(url:String, loadMaterials:Boolean = true, context:LoaderContext = null):void {
+ _content = null;
+ this.loadMaterials = loadMaterials;
+ this.loaderContext = context;
+ basePath = url.substring(0, url.lastIndexOf("/") + 1);
+ if (objLoader == null) {
+ objLoader = new URLLoader();
+ objLoader.addEventListener(Event.COMPLETE, onObjLoadComplete);
+ objLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError);
+ objLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError);
+ } else {
+ close();
+ }
+ objLoader.load(new URLRequest(url));
+ }
+
+ /**
+ * Обработка окончания загрузки obj файла.
+ *
+ * @param e
+ */
+ private function onObjLoadComplete(e:Event):void {
+ parse(objLoader.data);
+ }
+
+ /**
+ * Обработка ошибки при загрузке.
+ *
+ * @param e
+ */
+ private function onObjLoadError(e:ErrorEvent):void {
+ dispatchEvent(e);
+ }
+
+ /**
+ * Метод выполняет разбор данных, полученных из obj файла.
+ *
+ * @param s содержимое obj файла
+ * @param materialLibrary библиотека материалов
+ * @return объект, содержащий все трёхмерные объекты, определённые в obj файле
+ */
+ private function parse(data:String):void {
+ _content = new Object3D();
+ currentObject = new Mesh();
+ currentObject.mobility = mobility;
+ _content.addChild(currentObject);
+
+ globalVertices = new Array();
+ globalTextureVertices = new Array();
+ materialFileNames = new Array();
+
+ var lines:Array = data.split(REGEXP_SPLIT_FILE);
+ for each (var line:String in lines) {
+ parseLine(line);
+ }
+ moveFacesToSurface();
+ // Вся геометрия загружена и сформирована. Выполняется загрузка информации о материалах.
+ if (loadMaterials && materialFileNames.length > 0) {
+ loadMaterialsLibrary();
+ } else {
+ dispatchEvent(new Event(Event.COMPLETE));
+ }
+ }
+
+ /**
+ *
+ */
+ private function parseLine(line:String):void {
+ line = line.replace(REGEXP_TRIM,"$1");
+ if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) {
+ return;
+ }
+ var parts:Array = line.split(REGEXP_SPLIT_LINE);
+ switch (parts[0]) {
+ // Объявление нового объекта
+ case CMD_OBJECT_NAME:
+ defineObject(parts[1]);
+ break;
+ // Объявление вершины
+ case CMD_VERTEX:
+ globalVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3])));
+ break;
+ // Объявление текстурной вершины
+ case CMD_TEXTURE_VERTEX:
+ globalTextureVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3])));
+ break;
+ // Объявление грани
+ case CMD_FACE:
+ createFace(parts);
+ break;
+ case CMD_MATERIAL_LIB:
+ storeMaterialFileNames(parts);
+ break;
+ case CMD_USE_MATERIAL:
+ setNewMaterial(parts);
+ break;
+ }
+ }
+
+ /**
+ * Объявление нового объекта.
+ *
+ * @param objectName имя объекта
+ */
+ private function defineObject(objectName:String):void {
+ if (currentObject.faces.length == 0) {
+ // Если у текущего объекта нет граней, то он остаётся текущим, но меняется имя
+ currentObject.name = objectName;
+ } else {
+ // Если у текущего объекта есть грани, то обявление нового имени создаёт новый объект
+ moveFacesToSurface();
+ currentObject = new Mesh(objectName);
+ currentObject.mobility = mobility;
+ _content.addChild(currentObject);
+ }
+ vIndexStart = globalVertices.length;
+ vtIndexStart = globalTextureVertices.length;
+ }
+
+ /**
+ * Создание грани в текущем объекте.
+ *
+ * @param parts массив, содержащий индексы вершин грани, начиная с элемента с индексом 1
+ */
+ private function createFace(parts:Array):void {
+ // Стартовый индекс вершины в объекте для добавляемой грани
+ var startVertexIndex:int = currentObject.vertices.length;
+ // Создание вершин в объекте
+ var faceVertexCount:int = parts.length - 1;
+ var vtIndices:Array = new Array(3);
+ // Массив идентификаторов вершин грани
+ var faceVertices:Array = new Array(faceVertexCount);
+ for (var i:int = 0; i < faceVertexCount; i++) {
+ var indices:Array = parts[i + 1].split("/");
+ // Создание вершины
+ var vIdx:int = int(indices[0]);
+ // Если индекс положительный, то его значение уменьшается на единицу, т.к. в obj формате индексация начинается с 1.
+ // Если индекс отрицательный, то выполняется смещение на его значение назад от стартового глобального индекса вершин для текущего объекта.
+ var actualIndex:int = vIdx > 0 ? vIdx - 1 : vIndexStart + vIdx;
+
+ var vertex:Vertex = currentObject.vertices[actualIndex];
+ // Если вершины нет в объекте, она добавляется
+ if (vertex == null) {
+ var p:Point3D = globalVertices[actualIndex];
+ if (rotateModel) {
+ // В формате obj направление "вверх" совпадает с осью Y, поэтому выполняется поворот координат на 90 градусов по оси X
+ vertex = currentObject.createVertex(p.x, -p.z, p.y, actualIndex);
+ } else {
+ vertex = currentObject.createVertex(p.x, p.y, p.z, actualIndex);
+ }
+ }
+ faceVertices[i] = vertex;
+
+ // Запись индекса текстурной вершины
+ if (i < 3) {
+ vtIndices[i] = int(indices[1]);
+ }
+ }
+ // Создание грани
+ var face:Face = currentObject.createFace(faceVertices, currentObject.faces.length);
+ // Установка uv координат
+ if (vtIndices[0] != 0) {
+ p = globalTextureVertices[vtIndices[0] - 1];
+ face.aUV = new Point(p.x, p.y);
+ p = globalTextureVertices[vtIndices[1] - 1];
+ face.bUV = new Point(p.x, p.y);
+ p = globalTextureVertices[vtIndices[2] - 1];
+ face.cUV = new Point(p.x, p.y);
+ }
+ // Если есть активный материал, то грань заносится в массив для последующего формирования поверхности в объекте
+ if (currentMaterialName != null) {
+ materialFaces.push(face);
+ }
+ }
+
+ /**
+ * Загрузка библиотек материалов.
+ *
+ * @param parts массив, содержащий имена файлов материалов, начиная с элемента с индексом 1
+ */
+ private function storeMaterialFileNames(parts:Array):void {
+ for (var i:int = 1; i < parts.length; i++) {
+ materialFileNames.push(parts[i]);
+ }
+ }
+
+ /**
+ * Установка нового текущего материала.
+ *
+ * @param parts массив, во втором элементе которого содержится имя материала
+ */
+ private function setNewMaterial(parts:Array):void {
+ // Все сохранённые грани добавляются в соответствующую поверхность текущего объекта
+ moveFacesToSurface();
+ // Установка нового текущего материала
+ currentMaterialName = parts[1];
+ }
+
+ /**
+ * Добавление всех граней с текущим материалом в поверхность с идентификатором, совпадающим с именем материала.
+ */
+ private function moveFacesToSurface():void {
+ if (currentMaterialName != null && materialFaces.length > 0) {
+ if (currentObject.hasSurface(currentMaterialName)) {
+ // При наличии поверхности с таким идентификатором, грани добавляются в неё
+ var surface:Surface = currentObject.getSurfaceById(currentMaterialName);
+ for each (var face:* in materialFaces) {
+ surface.addFace(face);
+ }
+ } else {
+ // При отсутствии поверхности с таким идентификатором, создатся новая поверхность
+ currentObject.createSurface(materialFaces, currentMaterialName);
+ }
+ }
+ materialFaces = [];
+ }
+
+ /**
+ * Загрузка материалов.
+ */
+ private function loadMaterialsLibrary():void {
+ if (mtlLoader == null) {
+ mtlLoader = new LoaderMTL();
+ mtlLoader.addEventListener(Event.COMPLETE, onMaterialFileLoadComplete);
+ mtlLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError);
+ mtlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError);
+ }
+ materialLibrary = new Map();
+
+ currentMaterialFileIndex = -1;
+ loadNextMaterialFile();
+ }
+
+ /**
+ * Обработка успешной загрузки библиотеки материалов.
+ */
+ private function onMaterialFileLoadComplete(e:Event):void {
+ materialLibrary.concat(mtlLoader.library);
+ // Загрузка следующего файла материалов
+ loadNextMaterialFile();
+ }
+
+ /**
+ *
+ */
+ private function loadNextMaterialFile():void {
+ currentMaterialFileIndex++;
+ if (currentMaterialFileIndex == materialFileNames.length) {
+ setMaterials();
+ dispatchEvent(new Event(Event.COMPLETE));
+ } else {
+ mtlLoader.load(basePath + materialFileNames[currentMaterialFileIndex], loaderContext);
+ }
+ }
+
+ /**
+ * Установка материалов.
+ */
+ private function setMaterials():void {
+ if (materialLibrary != null) {
+ for (var objectKey:* in _content.children) {
+ var object:Mesh = objectKey;
+ for (var surfaceKey:* in object.surfaces) {
+ var surface:Surface = object.surfaces[surfaceKey];
+ // Поверхности имеют идентификаторы, соответствующие именам материалов
+ var materialInfo:MaterialInfo = materialLibrary[surfaceKey];
+ if (materialInfo != null) {
+ if (materialInfo.bitmapData == null) {
+ surface.material = new FillMaterial(materialInfo.color, materialInfo.alpha, blendMode);
+ } else {
+ surface.material = new TextureMaterial(new Texture(materialInfo.bitmapData, materialInfo.textureFileName), materialInfo.alpha, materialInfo.repeat, (materialInfo.bitmapData != LoaderMTL.stubBitmapData) ? smooth : false, blendMode, -1, 0, precision);
+ transformUVs(surface, materialInfo.mapOffset, materialInfo.mapSize);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Метод выполняет преобразование UV-координат текстурированных граней. В связи с тем, что в формате MRL предусмотрено
+ * масштабирование и смещение текстурной карты в UV-пространстве, а в движке такой фунциональности нет, необходимо
+ * эмулировать преобразования текстуры преобразованием UV-координат граней. Преобразования выполняются исходя из предположения,
+ * что текстурное пространство сначала масштабируется относительно центра, а затем сдвигается на указанную величину
+ * смещения.
+ *
+ * @param surface поверхность, грани которой обрабатываюся
+ * @param mapOffset смещение текстурной карты. Значение mapOffset.x указывает смещение по U, значение mapOffset.y
+ * указывает смещение по V.
+ * @param mapSize коэффициенты масштабирования текстурной карты. Значение mapSize.x указывает коэффициент масштабирования
+ * по оси U, значение mapSize.y указывает коэффициент масштабирования по оси V.
+ */
+ private function transformUVs(surface:Surface, mapOffset:Point, mapSize:Point):void {
+ for (var key:* in surface.faces) {
+ var face:Face = key;
+ var uv:Point = face.aUV;
+ if (uv != null) {
+ uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
+ uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
+ face.aUV = uv;
+ uv = face.bUV;
+ uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
+ uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
+ face.bUV = uv;
+ uv = face.cUV;
+ uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
+ uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
+ face.cUV = uv;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/loaders/MTLTextureMapInfo.as b/Alternativa3D5/5.4/alternativa/engine3d/loaders/MTLTextureMapInfo.as
new file mode 100644
index 0000000..4bf9e57
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/loaders/MTLTextureMapInfo.as
@@ -0,0 +1,120 @@
+package alternativa.engine3d.loaders {
+ /**
+ * @private
+ * Класс содержит информацию о текстуре в формате MTL material format (Lightwave, OBJ) и функционал для разбора
+ * описания текстуры.
+ * Описание формата можно посмотреть по адресу: http://local.wasp.uwa.edu.au/~pbourke/dataformats/mtl/
+ */
+ internal class MTLTextureMapInfo {
+
+ // Ассоциация параметров команды объявления текстуры и методов для их чтения
+ private static const optionReaders:Object = {
+ "-clamp": clampReader,
+ "-o": offsetReader,
+ "-s": sizeReader,
+
+ "-blendu": stubReader,
+ "-blendv": stubReader,
+ "-bm": stubReader,
+ "-boost": stubReader,
+ "-cc": stubReader,
+ "-imfchan": stubReader,
+ "-mm": stubReader,
+ "-t": stubReader,
+ "-texres": stubReader
+ };
+
+ // Смещение в текстурном пространстве
+ public var offsetU:Number = 0;
+ public var offsetV:Number = 0;
+ public var offsetW:Number = 0;
+
+ // Масштабирование текстурного пространства
+ public var sizeU:Number = 1;
+ public var sizeV:Number = 1;
+ public var sizeW:Number = 1;
+
+ // Флаг повторения текстуры
+ public var repeat:Boolean = true;
+ // Имя файла текстуры
+ public var fileName:String;
+
+ /**
+ * Метод выполняет разбор данных о текстуре.
+ *
+ * @param parts Данные о текстуре. Массив должен содержать части разделённой по пробелам входной строки MTL-файла.
+ * @return объект, содержащий данные о текстуре
+ */
+ public static function parse(parts:Array):MTLTextureMapInfo {
+ var info:MTLTextureMapInfo = new MTLTextureMapInfo();
+ // Начальное значение индекса единица, т.к. первый элемент массива содержит тип текстуры
+ var index:int = 1;
+ var reader:Function;
+ // Чтение параметров текстуры
+ while ((reader = optionReaders[parts[index]]) != null) {
+ index = reader(index, parts, info);
+ }
+ // Если не было ошибок, последний элемент массива должен содержать имя файла текстуры
+ info.fileName = parts[index];
+ return info;
+ }
+
+ /**
+ * Читатель-заглушка. Пропускает все неподдерживаемые параметры.
+ */
+ private static function stubReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
+ index++;
+ var maxIndex:int = parts.length - 1;
+ while ((MTLTextureMapInfo.optionReaders[parts[index]] == null) && (index < maxIndex)) {
+ index++;
+ }
+ return index;
+ }
+
+ /**
+ * Метод чтения параметров масштабирования текстурного пространства.
+ */
+ private static function sizeReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
+ info.sizeU = Number(parts[index + 1]);
+ index += 2;
+ var value:Number = Number(parts[index]);
+ if (!isNaN(value)) {
+ info.sizeV = value;
+ index++;
+ value = Number(parts[index]);
+ if (!isNaN(value)) {
+ info.sizeW = value;
+ index++;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Метод чтения параметров смещения текстуры.
+ */
+ private static function offsetReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
+ info.offsetU = Number(parts[index + 1]);
+ index += 2;
+ var value:Number = Number(parts[index]);
+ if (!isNaN(value)) {
+ info.offsetV = value;
+ index++;
+ value = Number(parts[index]);
+ if (!isNaN(value)) {
+ info.offsetW = value;
+ index++;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Метод чтения параметра повторения текстуры.
+ */
+ private static function clampReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
+ info.repeat = parts[index + 1] == "off";
+ return index + 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/loaders/MaterialInfo.as b/Alternativa3D5/5.4/alternativa/engine3d/loaders/MaterialInfo.as
new file mode 100644
index 0000000..0a9a9ee
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/loaders/MaterialInfo.as
@@ -0,0 +1,20 @@
+package alternativa.engine3d.loaders {
+ import flash.display.BitmapData;
+ import flash.geom.Point;
+
+ /**
+ * @private
+ * Класс содержит обобщённую информацию о материале.
+ */
+ internal class MaterialInfo {
+ public var color:uint;
+ public var alpha:Number;
+
+ public var textureFileName:String;
+ public var bitmapData:BitmapData;
+ public var repeat:Boolean;
+
+ public var mapOffset:Point;
+ public var mapSize:Point;
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/materials/DevMaterial.as b/Alternativa3D5/5.4/alternativa/engine3d/materials/DevMaterial.as
new file mode 100644
index 0000000..0d3d0c3
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/materials/DevMaterial.as
@@ -0,0 +1,198 @@
+package alternativa.engine3d.materials {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.display.Skin;
+
+ import flash.display.BlendMode;
+ import flash.display.Graphics;
+ import alternativa.utils.ColorUtils;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.core.BSPNode;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ * Материал, заполняющий грань сплошной заливкой цветом в соответствии с уровнем мобильности. Помимо заливки материал может рисовать границу
+ * полигона линией заданной толщины и цвета.
+ */
+ public class DevMaterial extends SurfaceMaterial {
+ /**
+ * @private
+ * Цвет
+ */
+ alternativa3d var _color:uint;
+
+ /**
+ * @private
+ * Толщина линий обводки
+ */
+ alternativa3d var _wireThickness:Number;
+
+ /**
+ * @private
+ * Цвет линий обводки
+ */
+ alternativa3d var _wireColor:uint;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param color цвет заливки
+ * @param alpha прозрачность
+ * @param blendMode режим наложения цвета
+ * @param wireThickness толщина линии обводки
+ * @param wireColor цвет линии обводки
+ */
+ public function DevMaterial(color:uint = 0xFFFFFF, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) {
+ super(alpha, blendMode);
+ _color = color;
+ _wireThickness = wireThickness;
+ _wireColor = wireColor;
+ }
+
+ /**
+ * @private
+ *
+ * @param camera
+ * @param skin
+ * @param length
+ * @param points
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+
+ var i:uint;
+ var point:DrawPoint;
+ var gfx:Graphics = skin.gfx;
+
+ /*
+ //Мобильность
+ var param:int = skin.primitive.mobility*10;
+ */
+
+ /*
+ // Уровень распиленности
+ var param:int = 0;
+ var prm:PolyPrimitive = skin.primitive;
+ while (prm != null) {
+ prm = prm.parent;
+ param++;
+ }
+ param *= 10;
+ */
+
+ // Уровень в BSP-дереве
+ var param:int = 0;
+ var node:BSPNode = skin.primitive.node;
+ while (node != null) {
+ node = node.parent;
+ param++;
+ }
+ param *= 5;
+
+ var c:uint = ColorUtils.rgb(param, param, param);
+
+ if (camera._orthographic) {
+ gfx.beginFill(c);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ gfx.moveTo(point.x, point.y);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ gfx.beginFill(c);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ var perspective:Number = camera.focalLength/point.z;
+ gfx.moveTo(point.x*perspective, point.y*perspective);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ }
+ }
+
+ /**
+ * Цвет заливки.
+ */
+ public function get color():uint {
+ return _color;
+ }
+
+ /**
+ * @private
+ */
+ public function set color(value:uint):void {
+ if (_color != value) {
+ _color = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Толщина линии обводки. Если значение отрицательное, то отрисовка линии не выполняется.
+ */
+ public function get wireThickness():Number {
+ return _wireThickness;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireThickness(value:Number):void {
+ if (_wireThickness != value) {
+ _wireThickness = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Цвет линии обводки.
+ */
+ public function get wireColor():uint {
+ return _wireColor;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireColor(value:uint):void {
+ if (_wireColor != value) {
+ _wireColor = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ var res:DevMaterial = new DevMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor);
+ return res;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/materials/DrawPoint.as b/Alternativa3D5/5.4/alternativa/engine3d/materials/DrawPoint.as
new file mode 100644
index 0000000..3a76932
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/materials/DrawPoint.as
@@ -0,0 +1,45 @@
+package alternativa.engine3d.materials {
+ /**
+ * @private
+ * Точка, подготовленная к отрисовке.
+ */
+ public final class DrawPoint {
+ /**
+ * Координата X в системе координат камеры.
+ */
+ public var x:Number;
+ /**
+ * Координата Y в системе координат камеры.
+ */
+ public var y:Number;
+ /**
+ * Координата Z в системе координат камеры.
+ */
+ public var z:Number;
+ /**
+ * Координата U в текстурном пространстве.
+ */
+ public var u:Number;
+ /**
+ * Координата V в текстурном пространстве.
+ */
+ public var v:Number;
+
+ /**
+ * Создаёт новый экземпляр класса.
+ *
+ * @param x координата X в системе координат камеры
+ * @param y координата Y в системе координат камеры
+ * @param z координата Z в системе координат камеры
+ * @param u координата U в текстурном пространстве
+ * @param v координата V в текстурном пространстве
+ */
+ public function DrawPoint(x:Number, y:Number, z:Number, u:Number = 0, v:Number = 0) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.u = u;
+ this.v = v;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/materials/FillMaterial.as b/Alternativa3D5/5.4/alternativa/engine3d/materials/FillMaterial.as
new file mode 100644
index 0000000..795bdb7
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/materials/FillMaterial.as
@@ -0,0 +1,163 @@
+package alternativa.engine3d.materials {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.display.Skin;
+
+ import flash.display.BlendMode;
+ import flash.display.Graphics;
+
+ use namespace alternativa3d;
+
+ /**
+ * Материал, заполняющий грань сплошной одноцветной заливкой. Помимо заливки цветом, материал может рисовать границу
+ * грани линией заданной толщины и цвета.
+ */
+ public class FillMaterial extends SurfaceMaterial {
+ /**
+ * @private
+ * Цвет
+ */
+ alternativa3d var _color:uint;
+
+ /**
+ * @private
+ * Толщина линий обводки
+ */
+ alternativa3d var _wireThickness:Number;
+
+ /**
+ * @private
+ * Цвет линий обводки
+ */
+ alternativa3d var _wireColor:uint;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param color цвет заливки
+ * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ * @param blendMode режим наложения цвета
+ * @param wireThickness толщина линии обводки
+ * @param wireColor цвет линии обводки
+ */
+ public function FillMaterial(color:uint, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) {
+ super(alpha, blendMode);
+ _color = color;
+ _wireThickness = wireThickness;
+ _wireColor = wireColor;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+
+ var i:uint;
+ var point:DrawPoint;
+ var gfx:Graphics = skin.gfx;
+
+ if (camera._orthographic) {
+ gfx.beginFill(_color);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ gfx.moveTo(point.x, point.y);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ gfx.beginFill(_color);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ var perspective:Number = camera.focalLength/point.z;
+ gfx.moveTo(point.x*perspective, point.y*perspective);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ }
+ }
+
+ /**
+ * Цвет заливки.
+ */
+ public function get color():uint {
+ return _color;
+ }
+
+ /**
+ * @private
+ */
+ public function set color(value:uint):void {
+ if (_color != value) {
+ _color = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Толщина линии обводки. Если значение отрицательное, то обводка не рисуется.
+ */
+ public function get wireThickness():Number {
+ return _wireThickness;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireThickness(value:Number):void {
+ if (_wireThickness != value) {
+ _wireThickness = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Цвет линии обводки.
+ */
+ public function get wireColor():uint {
+ return _wireColor;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireColor(value:uint):void {
+ if (_wireColor != value) {
+ _wireColor = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ var res:FillMaterial = new FillMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor);
+ return res;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/materials/Material.as b/Alternativa3D5/5.4/alternativa/engine3d/materials/Material.as
new file mode 100644
index 0000000..399304f
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/materials/Material.as
@@ -0,0 +1,20 @@
+package alternativa.engine3d.materials {
+ import alternativa.engine3d.*;
+
+ use namespace alternativa3d;
+
+ /**
+ * Базовый класс для материалов.
+ */
+ public class Material {
+
+ /**
+ * Создание клона материала.
+ *
+ * @return клон материала
+ */
+ public function clone():Material {
+ return new Material();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/materials/SurfaceMaterial.as b/Alternativa3D5/5.4/alternativa/engine3d/materials/SurfaceMaterial.as
new file mode 100644
index 0000000..8052c58
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/materials/SurfaceMaterial.as
@@ -0,0 +1,197 @@
+package alternativa.engine3d.materials {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.core.Scene3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.display.Skin;
+
+ import flash.display.BlendMode;
+
+ use namespace alternativa3d;
+
+ /**
+ * Базовый класс для материалов полигональных поверхностей.
+ */
+ public class SurfaceMaterial extends Material {
+ /**
+ * @private
+ * Поверхность
+ */
+ alternativa3d var _surface:Surface;
+ /**
+ * @private
+ * Альфа
+ */
+ alternativa3d var _alpha:Number;
+ /**
+ * @private
+ * Режим наложения цвета
+ */
+ alternativa3d var _blendMode:String = BlendMode.NORMAL;
+ /**
+ * @private
+ * Материал использует информация об UV-координатах
+ */
+ alternativa3d var useUV:Boolean = false;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ * @param blendMode режим наложения цвета
+ */
+ public function SurfaceMaterial(alpha:Number = 1, blendMode:String = BlendMode.NORMAL) {
+ _alpha = alpha;
+ _blendMode = blendMode;
+ }
+
+ /**
+ * Поверхность, которой назначен материал.
+ */
+ public function get surface():Surface {
+ return _surface;
+ }
+
+ /**
+ * @private
+ * Добавление на сцену
+ *
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {}
+
+ /**
+ * @private
+ * Удаление из сцены
+ *
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {}
+
+ /**
+ * @private
+ * Добавление к мешу
+ *
+ * @param mesh
+ */
+ alternativa3d function addToMesh(mesh:Mesh):void {}
+
+ /**
+ * @private
+ * Удаление из меша
+ *
+ * @param mesh
+ */
+ alternativa3d function removeFromMesh(mesh:Mesh):void {}
+
+ /**
+ * @private
+ * Добавление на поверхность
+ *
+ * @param surface
+ */
+ alternativa3d function addToSurface(surface:Surface):void {
+ // Сохраняем поверхность
+ _surface = surface;
+ }
+
+ /**
+ * @private
+ * Удаление с поверхности
+ *
+ * @param surface
+ */
+ alternativa3d function removeFromSurface(surface:Surface):void {
+ // Удаляем ссылку на поверхность
+ _surface = null;
+ }
+
+ /**
+ * Коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ */
+ public function get alpha():Number {
+ return _alpha;
+ }
+
+ /**
+ * @private
+ */
+ public function set alpha(value:Number):void {
+ if (_alpha != value) {
+ _alpha = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Режим наложения цвета.
+ */
+ public function get blendMode():String {
+ return _blendMode;
+ }
+
+ /**
+ * @private
+ */
+ public function set blendMode(value:String):void {
+ if (_blendMode != value) {
+ _blendMode = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Метод определяет, может ли материал нарисовать указанный примитив. Метод используется в системе отрисовки сцены и должен использоваться
+ * наследниками для указания видимости связанной с материалом поверхности или отдельного примитива. Реализация по умолчанию возвращает
+ * true.
+ *
+ * @param primitive примитив для проверки
+ *
+ * @return true, если материал может отрисовать указанный примитив, иначе false
+ */
+ alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
+ return true;
+ }
+
+ /**
+ * @private
+ * Метод очищает переданный скин (нарисованную графику, дочерние объекты и т.д.).
+ *
+ * @param skin скин для очистки
+ */
+ alternativa3d function clear(skin:Skin):void {
+ skin.gfx.clear();
+ }
+
+ /**
+ * @private
+ * Метод выполняет отрисовку в заданный скин.
+ *
+ * @param camera камера, вызвавшая метод
+ * @param skin скин, в котором нужно рисовать
+ * @param length длина массива points
+ * @param points массив точек, определяющих отрисовываемый полигон. Каждый элемент массива является объектом класса
+ * alternativa.engine3d.materials.DrawPoint
+ *
+ * @see DrawPoint
+ */
+ alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ return new SurfaceMaterial(_alpha, _blendMode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/materials/TextureMaterial.as b/Alternativa3D5/5.4/alternativa/engine3d/materials/TextureMaterial.as
new file mode 100644
index 0000000..d41d4cd
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/materials/TextureMaterial.as
@@ -0,0 +1,370 @@
+package alternativa.engine3d.materials {
+ import __AS3__.vec.Vector;
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.display.Skin;
+ import alternativa.types.*;
+
+ import flash.display.BlendMode;
+ import flash.geom.Matrix;
+ import flash.display.BitmapData;
+ import flash.display.Graphics;
+
+ use namespace alternativa3d;
+ use namespace alternativatypes;
+
+ /**
+ * Материал, заполняющий грань текстурой. Помимо наложения текстуры, материал может рисовать границу грани линией
+ * заданной толщины и цвета.
+ */
+ public class TextureMaterial extends SurfaceMaterial {
+
+ private static var stubBitmapData:BitmapData;
+ private static var stubMatrix:Matrix;
+
+ private var gfx:Graphics;
+ private var textureMatrix:Matrix = new Matrix();
+ private var focalLength:Number;
+ private var distortion:Number;
+
+ /**
+ * @private
+ * Текстура
+ */
+ alternativa3d var _texture:Texture;
+ /**
+ * @private
+ * Повтор текстуры
+ */
+ alternativa3d var _repeat:Boolean;
+ /**
+ * @private
+ * Сглаженность текстуры
+ */
+ alternativa3d var _smooth:Boolean;
+ /**
+ * @private
+ * Точность перспективной коррекции
+ */
+ alternativa3d var _precision:Number;
+
+ /**
+ * @private
+ * Толщина линий обводки
+ */
+ alternativa3d var _wireThickness:Number;
+
+ /**
+ * @private
+ * Цвет линий обводки
+ */
+ alternativa3d var _wireColor:uint;
+
+ /**
+ * Создание экземпляра текстурного материала.
+ *
+ * @param texture текстура материала
+ * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ * @param repeat повтор текстуры при заполнении
+ * @param smooth сглаживание текстуры при увеличении масштаба
+ * @param blendMode режим наложения цвета
+ * @param wireThickness толщина линии обводки
+ * @param wireColor цвет линии обводки
+ * @param precision точность перспективной коррекции. Может быть задана одной из констант класса
+ * TextureMaterialPrecision или числом типа Number. Во втором случае, чем ближе заданное значение к единице, тем более
+ * качественная перспективная коррекция будет выполнена, и тем больше времени будет затрачено на расчёт кадра.
+ *
+ * @see TextureMaterialPrecision
+ */
+ public function TextureMaterial(texture:Texture, alpha:Number = 1, repeat:Boolean = true, smooth:Boolean = false, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0, precision:Number = TextureMaterialPrecision.MEDIUM) {
+ super(alpha, blendMode);
+ _texture = texture;
+ _repeat = repeat;
+ _smooth = smooth;
+ _wireThickness = wireThickness;
+ _wireColor = wireColor;
+ _precision = precision;
+ useUV = true;
+ }
+
+ /**
+ * @private
+ * Метод определяет, может ли материал нарисовать указанный примитив. Метод используется в системе отрисовки сцены и должен использоваться
+ * наследниками для указания видимости связанной с материалом поверхности или отдельного примитива.
+ *
+ * @param primitive примитив для проверки
+ *
+ * @return true, если материал может отрисовать указанный примитив, иначе false
+ */
+ override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
+ return _texture != null;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+
+ var i:uint;
+ var point:DrawPoint;
+ gfx = skin.gfx;
+
+ // Проверка на нулевую UV-матрицу
+ if (skin.primitive.face.uvMatrixBase == null) {
+ if (stubBitmapData == null) {
+ // Создание текстуры-заглушки
+ stubBitmapData = new BitmapData(2, 2, false, 0);
+ stubBitmapData.setPixel(0, 0, 0xFF00FF);
+ stubBitmapData.setPixel(1, 1, 0xFF00FF);
+ stubMatrix = new Matrix(10, 0, 0, 10, 0, 0);
+ }
+ gfx.beginBitmapFill(stubBitmapData, stubMatrix);
+ if (camera._orthographic) {
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ gfx.moveTo(point.x, point.y);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ var perspective:Number = camera.focalLength/point.z;
+ gfx.moveTo(point.x*perspective, point.y*perspective);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ }
+ return;
+ }
+
+ if (camera._orthographic) {
+ // Расчитываем матрицу наложения текстуры
+ var face:Face = skin.primitive.face;
+ // Если матрица не расчитана, считаем
+ if (!camera.uvMatricesCalculated[face]) {
+ camera.calculateUVMatrix(face, _texture._width, _texture._height);
+ }
+ gfx.beginBitmapFill(_texture._bitmapData, face.uvMatrix, _repeat, _smooth);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ gfx.moveTo(point.x, point.y);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ // Отрисовка
+ focalLength = camera.focalLength;
+ //distortion = camera.focalDistortion*_precision;
+
+ var front:int = 0;
+ var back:int = length - 1;
+
+ var newFront:int = 1;
+ var newBack:int = (back > 0) ? (back - 1) : (length - 1);
+ var direction:Boolean = true;
+
+ var a:DrawPoint = points[back];
+ var b:DrawPoint;
+ var c:DrawPoint = points[front];
+
+ var drawVertices:Vector.flash.display.Graphics#beginBitmapFill().
+ */
+ public function get repeat():Boolean {
+ return _repeat;
+ }
+
+ /**
+ * @private
+ */
+ public function set repeat(value:Boolean):void {
+ if (_repeat != value) {
+ _repeat = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Сглаживание текстуры при увеличении масштаба. Более подробную информацию можно найти в описании метода
+ * flash.display.Graphics#beginBitmapFill().
+ */
+ public function get smooth():Boolean {
+ return _smooth;
+ }
+
+ /**
+ * @private
+ */
+ public function set smooth(value:Boolean):void {
+ if (_smooth != value) {
+ _smooth = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Толщина линии обводки полигона. Если значение отрицательное, то обводка не рисуется.
+ */
+ public function get wireThickness():Number {
+ return _wireThickness;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireThickness(value:Number):void {
+ if (_wireThickness != value) {
+ _wireThickness = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Цвет линии обводки полигона.
+ */
+ public function get wireColor():uint {
+ return _wireColor;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireColor(value:uint):void {
+ if (_wireColor != value) {
+ _wireColor = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Точность перспективной коррекции.
+ */
+ public function get precision():Number {
+ return _precision;
+ }
+
+ /**
+ * @private
+ */
+ public function set precision(value:Number):void {
+ if (_precision != value) {
+ _precision = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ var res:TextureMaterial = new TextureMaterial(_texture, _alpha, _repeat, _smooth, _blendMode, _wireThickness, _wireColor, _precision);
+ return res;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/materials/TextureMaterialPrecision.as b/Alternativa3D5/5.4/alternativa/engine3d/materials/TextureMaterialPrecision.as
new file mode 100644
index 0000000..ca955e5
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/materials/TextureMaterialPrecision.as
@@ -0,0 +1,39 @@
+package alternativa.engine3d.materials {
+
+ /**
+ * Класс содержит константы точности перспективной коррекции текстурного материала.
+ *
+ * @see TextureMaterial
+ */
+ public class TextureMaterialPrecision {
+
+ /**
+ * Адаптивная триангуляция не будет выполняться, только простая триангуляция.
+ */
+ public static const NONE:Number = -1;
+ /**
+ * Очень низкое качество адаптивной триангуляции.
+ */
+ public static const VERY_LOW:Number = 50;
+ /**
+ * Низкое качество адаптивной триангуляции.
+ */
+ public static const LOW:Number = 25;
+ /**
+ * Среднее качество адаптивной триангуляции.
+ */
+ public static const MEDIUM:Number = 10;
+ /**
+ * Высокое качество адаптивной триангуляции.
+ */
+ public static const HIGH:Number = 6;
+ /**
+ * Очень высокое качество адаптивной триангуляции.
+ */
+ public static const VERY_HIGH:Number = 3;
+ /**
+ * Максимальное качество адаптивной триангуляции.
+ */
+ public static const BEST:Number = 1;
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/materials/WireMaterial.as b/Alternativa3D5/5.4/alternativa/engine3d/materials/WireMaterial.as
new file mode 100644
index 0000000..c9c6d4e
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/materials/WireMaterial.as
@@ -0,0 +1,128 @@
+package alternativa.engine3d.materials {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.display.Skin;
+
+ import flash.display.BlendMode;
+ import flash.display.Graphics;
+ import alternativa.engine3d.core.PolyPrimitive;
+
+ use namespace alternativa3d;
+
+ /**
+ * Материал для рисования рёбер граней.
+ */
+ public class WireMaterial extends SurfaceMaterial {
+ /**
+ * @private
+ * Цвет
+ */
+ alternativa3d var _color:uint;
+ /**
+ * @private
+ * Толщина линий
+ */
+ alternativa3d var _thickness:Number;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param thickness толщина линий
+ * @param color цвет линий
+ * @param alpha коэффициент непрозрачности линий. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ * @param blendMode режим наложения цвета
+ */
+ public function WireMaterial(thickness:Number = 0, color:uint = 0, alpha:Number = 1, blendMode:String = BlendMode.NORMAL) {
+ super(alpha, blendMode);
+ _color = color;
+ _thickness = thickness;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
+ return _thickness >= 0;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+
+ var i:uint;
+ var point:DrawPoint;
+ var gfx:Graphics = skin.gfx;
+
+ if (camera._orthographic) {
+ gfx.lineStyle(_thickness, _color);
+ point = points[length - 1];
+ gfx.moveTo(point.x, point.y);
+ for (i = 0; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ // Отрисовка
+ gfx.lineStyle(_thickness, _color);
+ point = points[length - 1];
+ var perspective:Number = camera.focalLength/point.z;
+ gfx.moveTo(point.x*perspective, point.y*perspective);
+ for (i = 0; i < length; i++) {
+ point = points[i];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ }
+ }
+
+ /**
+ * Цвет линий.
+ */
+ public function get color():uint {
+ return _color;
+ }
+
+ /**
+ * @private
+ */
+ public function set color(value:uint):void {
+ if (_color != value) {
+ _color = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Толщина линий. Если толщина отрицательная, то отрисовка не выполняется.
+ */
+ public function get thickness():Number {
+ return _thickness;
+ }
+
+ /**
+ * @private
+ */
+ public function set thickness(value:Number):void {
+ if (_thickness != value) {
+ _thickness = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ return new WireMaterial(_thickness, _color, _alpha, _blendMode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/physics/Collision.as b/Alternativa3D5/5.4/alternativa/engine3d/physics/Collision.as
new file mode 100644
index 0000000..5e0e560
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/physics/Collision.as
@@ -0,0 +1,30 @@
+package alternativa.engine3d.physics {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.types.Point3D;
+
+ use namespace alternativa3d;
+
+ /**
+ * Параметры столкновения эллипсоида с гранью объекта. Плоскостью столкновения является касательная к
+ * эллипсоиду плоскость, проходящая через точку столкновения с гранью.
+ */
+ public class Collision {
+ /**
+ * Грань, с которой произошло столкновение.
+ */
+ public var face:Face;
+ /**
+ * Нормаль плоскости столкновения.
+ */
+ public var normal:Point3D;
+ /**
+ * Смещение плоскости столкновения.
+ */
+ public var offset:Number;
+ /**
+ * Координаты точки столкновения.
+ */
+ public var point:Point3D;
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/physics/CollisionPlane.as b/Alternativa3D5/5.4/alternativa/engine3d/physics/CollisionPlane.as
new file mode 100644
index 0000000..65c3634
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/physics/CollisionPlane.as
@@ -0,0 +1,60 @@
+package alternativa.engine3d.physics {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.BSPNode;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ */
+ public class CollisionPlane {
+ // Узел BSP дерева, который содержит плоскость
+ public var node:BSPNode;
+ // Индикатор положения объекта относительно плоскости (спереди или сзади)
+ public var infront:Boolean;
+ // Расстояние до плоскости в начальной точке (всегда положительное)
+ public var sourceOffset:Number;
+ // Расстояние до плоскости в конечной точке
+ public var destinationOffset:Number;
+
+ // Хранилище неиспользуемых плоскостей
+ static private var collector:Array = new Array();
+
+
+ /**
+ * Создание плоскости
+ *
+ * @param node
+ * @param infront
+ * @param sourceOffset
+ * @param destinationOffset
+ * @return
+ */
+ static alternativa3d function createCollisionPlane(node:BSPNode, infront:Boolean, sourceOffset:Number, destinationOffset:Number):CollisionPlane {
+
+ // Достаём плоскость из коллектора
+ var plane:CollisionPlane = collector.pop();
+ // Если коллектор пуст, создаём новую плоскость
+ if (plane == null) {
+ plane = new CollisionPlane();
+ }
+
+ plane.node = node;
+ plane.infront = infront;
+ plane.sourceOffset = sourceOffset;
+ plane.destinationOffset = destinationOffset;
+
+ return plane;
+ }
+
+ /**
+ * Удаление плоскости, все ссылки должны быть почищены
+ *
+ * @param plane
+ */
+ static alternativa3d function destroyCollisionPlane(plane:CollisionPlane):void {
+ plane.node = null;
+ collector.push(plane);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/physics/CollisionSetMode.as b/Alternativa3D5/5.4/alternativa/engine3d/physics/CollisionSetMode.as
new file mode 100644
index 0000000..7dd776e
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/physics/CollisionSetMode.as
@@ -0,0 +1,18 @@
+package alternativa.engine3d.physics {
+ /**
+ * Константы, определяющие режим учёта объектов сцены, заданных в множестве EllipsoidCollider.collisionSet
+ * при определении столкновений.
+ *
+ * @see EllipsoidCollider#collisionSet
+ */
+ public class CollisionSetMode {
+ /**
+ * Грани объектов игнорируются при определении столкновений.
+ */
+ static public const EXCLUDE:int = 1;
+ /**
+ * Учитываются только столкновения с гранями, принадлежащим перечисленным в множестве объектам.
+ */
+ static public const INCLUDE:int = 2;
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/physics/EllipsoidCollider.as b/Alternativa3D5/5.4/alternativa/engine3d/physics/EllipsoidCollider.as
new file mode 100644
index 0000000..6f0732f
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/physics/EllipsoidCollider.as
@@ -0,0 +1,993 @@
+package alternativa.engine3d.physics {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.BSPNode;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.core.Scene3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+ import alternativa.utils.ObjectUtils;
+
+ use namespace alternativa3d;
+
+ /**
+ * Класс реализует алгоритм непрерывного определения столкновений эллипсоида с плоскими выпуклыми многоугольниками.
+ */
+ public class EllipsoidCollider {
+ // Максимальное количество попыток найти свободное от столкновения со сценой направление
+ private static const MAX_COLLISIONS:uint = 50;
+ // Радиус наибольшей сферы
+ private var _radius:Number = 100;
+ private var _radius2:Number = _radius * _radius;
+ private var _radiusX:Number = _radius;
+ private var _radiusY:Number = _radius;
+ private var _radiusZ:Number = _radius;
+ private var _radiusX2:Number = _radiusX * _radiusX;
+ private var _radiusY2:Number = _radiusY * _radiusY;
+ private var _radiusZ2:Number = _radiusZ * _radiusZ;
+ // Коэффициенты масштабирования осей
+ private var _scaleX:Number = 1;
+ private var _scaleY:Number = 1;
+ private var _scaleZ:Number = 1;
+ // Квадраты коэффициентов масштабирования осей
+ private var _scaleX2:Number = 1;
+ private var _scaleY2:Number = 1;
+ private var _scaleZ2:Number = 1;
+
+ private var collisionSource:Point3D;
+ private var currentDisplacement:Point3D = new Point3D();
+ private var collisionDestination:Point3D = new Point3D();
+
+ private var collisionPlanes:Array = new Array();
+ private var collisionPrimitive:PolyPrimitive;
+ private var collisionPrimitiveNearest:PolyPrimitive;
+ private var collisionPlanePoint:Point3D = new Point3D();
+ private var collisionPrimitiveNearestLengthSqr:Number;
+ private var collisionPrimitivePoint:Point3D = new Point3D();
+
+ private var collisionNormal:Point3D = new Point3D();
+ private var collisionPoint:Point3D = new Point3D();
+ private var collisionOffset:Number;
+
+ private var currentCoords:Point3D = new Point3D();
+ private var collision:Collision = new Collision();
+ private var collisionRadius:Number;
+ private var radiusVector:Point3D = new Point3D();
+ private var p1:Point3D = new Point3D();
+ private var p2:Point3D = new Point3D();;
+ private var localCollisionPlanePoint:Point3D = new Point3D();
+
+ // Флаг использования упорщённого алгоритма. Включается когда эллипсоид представляет собой сферу.
+ private var useSimpleAlgorithm:Boolean = true;
+
+ /**
+ * Сцена, в которой определяются столкновения.
+ */
+ public var scene:Scene3D;
+ /**
+ * Погрешность определения расстояний и координат. Две точки совпадают, если модуль разности любых соответствующих
+ * координат меньше указанной погрешности.
+ */
+ public var offsetThreshold:Number = 0.01;
+ /**
+ * Множество объектов, учитываемых в процессе определения столкновений. В качестве объектов могут выступать экземпляры
+ * классов Mesh и Surface. Каким образом учитываются перечисленные в множестве объекты зависит
+ * от значения поля collisionSetMode. Значение null эквивалентно заданию пустого множества.
+ *
+ * @see #collisionSetMode
+ * @see alternativa.engine3d.core.Mesh
+ * @see alternativa.engine3d.core.Surface
+ */
+ public var collisionSet:Set;
+ /**
+ * Параметр определяет, каким образом учитываются объекты, перечисленные в множестве collisionSet. Если
+ * значение параметра равно true, то грани объектов из множества игнорируются при определении столкновений.
+ * При значении параметра false учитываются только столкновения с гранями, принадлежащим перечисленным
+ * в множестве объектам.
+ *
+ * @default true
+ * @see #collisionSet
+ */
+ private var _collisionSetMode:int = CollisionSetMode.EXCLUDE;
+
+ /**
+ * Создаёт новый экземпляр класса.
+ *
+ * @param scene сцена, в которой определяются столкновения
+ * @param scaleX радиус эллипсоида по оси X
+ * @param scaleY радиус эллипсоида по оси Y
+ * @param scaleZ радиус эллипсоида по оси Z
+ */
+ public function EllipsoidCollider(scene:Scene3D = null, radiusX:Number = 100, radiusY:Number = 100, radiusZ:Number = 100) {
+ this.scene = scene;
+ this.radiusX = radiusX;
+ this.radiusY = radiusY;
+ this.radiusZ = radiusZ;
+ }
+
+ /**
+ * @private
+ */
+ public function get collisionSetMode():int {
+ return _collisionSetMode;
+ }
+
+ /**
+ * Параметр определяет, каким образом учитываются объекты, перечисленные в множестве collisionSet.
+ *
+ * @default CollisionSetMode.EXCLUDE
+ * @see #collisionSet
+ * @see CollisionSetMode
+ */
+ public function set collisionSetMode(value:int):void {
+ if (value != CollisionSetMode.EXCLUDE && value != CollisionSetMode.INCLUDE) {
+ throw ArgumentError(ObjectUtils.getClassName(this) + ".collisionSetMode invalid value");
+ }
+ _collisionSetMode = value;
+ }
+
+ /**
+ * Величина радиуса (полуоси) эллипсоида по оси X. При установке отрицательного значения берётся модуль.
+ *
+ * @default 100
+ */
+ public function get radiusX():Number {
+ return _radiusX;
+ }
+
+ /**
+ * @private
+ */
+ public function set radiusX(value:Number):void {
+ _radiusX = value >= 0 ? value : -value;
+ _radiusX2 = _radiusX * _radiusX;
+ calculateScales();
+ }
+
+ /**
+ * Величина радиуса (полуоси) эллипсоида по оси Y. При установке отрицательного значения берётся модуль.
+ *
+ * @default 100
+ */
+ public function get radiusY():Number {
+ return _radiusY;
+ }
+
+ /**
+ * @private
+ */
+ public function set radiusY(value:Number):void {
+ _radiusY = value >= 0 ? value : -value;
+ _radiusY2 = _radiusY * _radiusY;
+ calculateScales();
+ }
+
+ /**
+ * Величина радиуса (полуоси) эллипсоида по оси Z. При установке отрицательного значения берётся модуль.
+ *
+ * @default 100
+ */
+ public function get radiusZ():Number {
+ return _radiusZ;
+ }
+
+ /**
+ * @private
+ */
+ public function set radiusZ(value:Number):void {
+ _radiusZ = value >= 0 ? value : -value;
+ _radiusZ2 = _radiusZ * _radiusZ;
+ calculateScales();
+ }
+
+ /**
+ * Расчёт коэффициентов масштабирования осей.
+ */
+ private function calculateScales():void {
+ _radius = _radiusX;
+ if (_radiusY > _radius) {
+ _radius = _radiusY;
+ }
+ if (_radiusZ > _radius) {
+ _radius = _radiusZ;
+ }
+ _radius2 = _radius * _radius;
+ _scaleX = _radiusX / _radius;
+ _scaleY = _radiusY / _radius;
+ _scaleZ = _radiusZ / _radius;
+ _scaleX2 = _scaleX * _scaleX;
+ _scaleY2 = _scaleY * _scaleY;
+ _scaleZ2 = _scaleZ * _scaleZ;
+
+ useSimpleAlgorithm = (_radiusX == _radiusY) && (_radiusX == _radiusZ);
+ }
+
+ /**
+ * Расчёт конечного положения эллипсоида по заданному начальному положению и вектору смещения. Если задано значение
+ * поля scene, то при вычислении конечного положения учитываются столкновения с объектами сцены,
+ * принимая во внимание множество collisionSet и режим работы collisionSetMode. Если
+ * значение поля scene равно null, то результат работы метода будет простой суммой двух
+ * входных векторов.
+ *
+ * @param sourcePoint начальное положение центра эллипсоида в системе координат корневого объекта сцены
+ * @param displacementVector вектор перемещения эллипсоида в системе координат корневого объекта сцены. Если модуль
+ * каждого компонента вектора не превышает значения offsetThreshold, эллипсоид остаётся в начальной точке.
+ * @param destinationPoint в эту переменную записывается расчётное положение центра эллипсоида в системе координат
+ * корневого объекта сцены
+ *
+ * @see #scene
+ * @see #collisionSet
+ * @see #collisionSetMode
+ * @see #offsetThreshold
+ */
+ public function calculateDestination(sourcePoint:Point3D, displacementVector:Point3D, destinationPoint:Point3D):void {
+ // Расчеты не производятся, если перемещение мало
+ if (displacementVector.x < offsetThreshold && displacementVector.x > -offsetThreshold &&
+ displacementVector.y < offsetThreshold && displacementVector.y > -offsetThreshold &&
+ displacementVector.z < offsetThreshold && displacementVector.z > -offsetThreshold) {
+ destinationPoint.x = sourcePoint.x;
+ destinationPoint.y = sourcePoint.y;
+ destinationPoint.z = sourcePoint.z;
+ return;
+ }
+
+ // Начальные координаты
+ currentCoords.x = sourcePoint.x;
+ currentCoords.y = sourcePoint.y;
+ currentCoords.z = sourcePoint.z;
+ // Начальный вектор перемещения
+ currentDisplacement.x = displacementVector.x;
+ currentDisplacement.y = displacementVector.y;
+ currentDisplacement.z = displacementVector.z;
+ // Начальная точка назначения
+ destinationPoint.x = sourcePoint.x + currentDisplacement.x;
+ destinationPoint.y = sourcePoint.y + currentDisplacement.y;
+ destinationPoint.z = sourcePoint.z + currentDisplacement.z;
+
+ if (useSimpleAlgorithm) {
+ calculateDestinationS(sourcePoint, destinationPoint);
+ } else {
+ calculateDestinationE(sourcePoint, destinationPoint);
+ }
+ }
+
+ /**
+ * Вычисление точки назначения для сферы.
+ * @param sourcePoint
+ * @param destinationPoint
+ */
+ private function calculateDestinationS(sourcePoint:Point3D, destinationPoint:Point3D):void {
+ var collisionCount:uint = 0;
+ var hasCollision:Boolean;
+ do {
+ hasCollision = getCollision(currentCoords, currentDisplacement, collision);
+ if (hasCollision ) {
+ // Вынос точки назначения из-за плоскости столкновения на высоту радиуса сферы над плоскостью по направлению нормали
+ var offset:Number = _radius + offsetThreshold + collision.offset - destinationPoint.x*collision.normal.x - destinationPoint.y*collision.normal.y - destinationPoint.z*collision.normal.z;
+ destinationPoint.x += collision.normal.x * offset;
+ destinationPoint.y += collision.normal.y * offset;
+ destinationPoint.z += collision.normal.z * offset;
+ // Коррекция текущих кординат центра сферы для следующей итерации
+ currentCoords.x = collision.point.x + collision.normal.x * (_radius + offsetThreshold);
+ currentCoords.y = collision.point.y + collision.normal.y * (_radius + offsetThreshold);
+ currentCoords.z = collision.point.z + collision.normal.z * (_radius + offsetThreshold);
+ // Коррекция вектора скорости. Результирующий вектор направлен вдоль плоскости столкновения.
+ currentDisplacement.x = destinationPoint.x - currentCoords.x;
+ currentDisplacement.y = destinationPoint.y - currentCoords.y;
+ currentDisplacement.z = destinationPoint.z - currentCoords.z;
+
+ // Если смещение слишком мало, останавливаемся
+ if (currentDisplacement.x < offsetThreshold && currentDisplacement.x > -offsetThreshold &&
+ currentDisplacement.y < offsetThreshold && currentDisplacement.y > -offsetThreshold &&
+ currentDisplacement.z < offsetThreshold && currentDisplacement.z > -offsetThreshold) {
+ break;
+ }
+ }
+ } while (hasCollision && (++collisionCount < MAX_COLLISIONS));
+ // Если количество итераций достигло максимально возможного значения, то остаемся на старом месте
+ if (collisionCount == MAX_COLLISIONS) {
+ destinationPoint.x = sourcePoint.x;
+ destinationPoint.y = sourcePoint.y;
+ destinationPoint.z = sourcePoint.z;
+ }
+ }
+
+ /**
+ * Вычисление точки назначения для эллипсоида.
+ * @param destinationPoint
+ * @return
+ */
+ private function calculateDestinationE(sourcePoint:Point3D, destinationPoint:Point3D):void {
+ var collisionCount:uint = 0;
+ var hasCollision:Boolean;
+ // Цикл выполняется до тех пор, пока не будет найдено ни одного столкновения на очередной итерации или пока не
+ // будет достигнуто максимально допустимое количество столкновений, что означает зацикливание алгоритма и
+ // необходимость принудительного выхода.
+ do {
+ hasCollision = getCollision(currentCoords, currentDisplacement, collision);
+ if (hasCollision) {
+ // Вынос точки назначения из-за плоскости столкновения на высоту эффективного радиуса эллипсоида над плоскостью по направлению нормали
+ var offset:Number = collisionRadius + offsetThreshold + collision.offset - destinationPoint.x * collision.normal.x - destinationPoint.y * collision.normal.y - destinationPoint.z * collision.normal.z;
+ destinationPoint.x += collision.normal.x * offset;
+ destinationPoint.y += collision.normal.y * offset;
+ destinationPoint.z += collision.normal.z * offset;
+ // Коррекция текущих кординат центра эллипсоида для следующей итерации
+ collisionRadius = (collisionRadius + offsetThreshold) / collisionRadius;
+ currentCoords.x = collision.point.x - collisionRadius * radiusVector.x;
+ currentCoords.y = collision.point.y - collisionRadius * radiusVector.y;
+ currentCoords.z = collision.point.z - collisionRadius * radiusVector.z;
+ // Коррекция вектора смещения. Результирующий вектор направлен параллельно плоскости столкновения.
+ currentDisplacement.x = destinationPoint.x - currentCoords.x;
+ currentDisplacement.y = destinationPoint.y - currentCoords.y;
+ currentDisplacement.z = destinationPoint.z - currentCoords.z;
+ // Если смещение слишком мало, останавливаемся
+ if (currentDisplacement.x < offsetThreshold && currentDisplacement.x > -offsetThreshold &&
+ currentDisplacement.y < offsetThreshold && currentDisplacement.y > -offsetThreshold &&
+ currentDisplacement.z < offsetThreshold && currentDisplacement.z > -offsetThreshold) {
+ destinationPoint.x = currentCoords.x;
+ destinationPoint.y = currentCoords.y;
+ destinationPoint.z = currentCoords.z;
+ break;
+ }
+ }
+ } while (hasCollision && (++collisionCount < MAX_COLLISIONS));
+ // Если количество итераций достигло максимально возможного значения, то остаемся на старом месте
+ if (collisionCount == MAX_COLLISIONS) {
+ destinationPoint.x = sourcePoint.x;
+ destinationPoint.y = sourcePoint.y;
+ destinationPoint.z = sourcePoint.z;
+ }
+ }
+
+ /**
+ * Метод определяет наличие столкновения при смещении эллипсоида из заданной точки на величину указанного вектора
+ * перемещения, принимая во внимание множество collisionSet и режим работы collisionSetMode.
+ *
+ * @param sourcePoint начальное положение центра эллипсоида в системе координат корневого объекта сцены
+ * @param displacementVector вектор перемещения эллипсоида в системе координат корневого объекта сцены
+ * @param collision в эту переменную будут записаны данные о плоскости и точке столкновения в системе координат
+ * корневого объекта сцены
+ *
+ * @return true, если эллипсоид при заданном перемещении столкнётся с каким-либо полигоном сцены,
+ * false если столкновений нет или не задано значение поля scene.
+ *
+ * @see #scene
+ * @see #collisionSet
+ * @see #collisionSetMode
+ */
+ public function getCollision(sourcePoint:Point3D, displacementVector:Point3D, collision:Collision):Boolean {
+ if (scene == null) {
+ return false;
+ }
+
+ collisionSource = sourcePoint;
+
+ currentDisplacement.x = displacementVector.x;
+ currentDisplacement.y = displacementVector.y;
+ currentDisplacement.z = displacementVector.z;
+
+ collisionDestination.x = collisionSource.x + currentDisplacement.x;
+ collisionDestination.y = collisionSource.y + currentDisplacement.y;
+ collisionDestination.z = collisionSource.z + currentDisplacement.z;
+
+ collectPotentialCollisionPlanes(scene.bsp);
+ collisionPlanes.sortOn("sourceOffset", Array.NUMERIC | Array.DESCENDING);
+
+ var plane:CollisionPlane;
+ // Пока не найдём столкновение с примитивом или плоскости не кончатся
+ if (useSimpleAlgorithm) {
+ while ((plane = collisionPlanes.pop()) != null) {
+ if (collisionPrimitive == null) {
+ calculateCollisionWithPlaneS(plane);
+ }
+ CollisionPlane.destroyCollisionPlane(plane);
+ }
+ } else {
+ while ((plane = collisionPlanes.pop()) != null) {
+ if (collisionPrimitive == null) {
+ calculateCollisionWithPlaneE(plane);
+ }
+ CollisionPlane.destroyCollisionPlane(plane);
+ }
+ }
+
+ var collisionFound:Boolean = collisionPrimitive != null;
+ if (collisionFound) {
+ collision.face = collisionPrimitive.face;
+ collision.normal = collisionNormal;
+ collision.offset = collisionOffset;
+ collision.point = collisionPoint;
+ }
+
+ collisionPrimitive = null;
+ collisionSource = null;
+
+ return collisionFound;
+ }
+
+ /**
+ * Сбор потенциальных плоскостей столкновения.
+ *
+ * @param node текущий узел BSP-дерева
+ */
+ private function collectPotentialCollisionPlanes(node:BSPNode):void {
+ if (node == null) {
+ return;
+ }
+
+ var sourceOffset:Number = collisionSource.x * node.normal.x + collisionSource.y * node.normal.y + collisionSource.z * node.normal.z - node.offset;
+ var destinationOffset:Number = collisionDestination.x * node.normal.x + collisionDestination.y * node.normal.y + collisionDestination.z * node.normal.z - node.offset;
+ var plane:CollisionPlane;
+
+ if (sourceOffset >= 0) {
+ // Исходное положение центра перед плоскостью ноды
+ // Проверяем передние ноды
+ collectPotentialCollisionPlanes(node.front);
+ // Грубая оценка пересечения с плоскостью по радиусу ограничивающей сферы эллипсоида
+ if (destinationOffset < _radius) {
+ // Нашли потенциальное пересечение с плоскостью
+ plane = CollisionPlane.createCollisionPlane(node, true, sourceOffset, destinationOffset);
+ collisionPlanes.push(plane);
+ // Проверяем задние ноды
+ collectPotentialCollisionPlanes(node.back);
+ }
+ } else {
+ // Исходное положение центра за плоскостью ноды
+ // Проверяем задние ноды
+ collectPotentialCollisionPlanes(node.back);
+ // Грубая оценка пересечения с плоскостью по радиусу ограничивающей сферы эллипсоида
+ if (destinationOffset > -_radius) {
+ // Столкновение возможно только в случае если в ноде есть примитивы, направленные назад
+ if (node.backPrimitives != null) {
+ // Нашли потенциальное пересечение с плоскостью
+ plane = CollisionPlane.createCollisionPlane(node, false, -sourceOffset, -destinationOffset);
+ collisionPlanes.push(plane);
+ }
+ // Проверяем передние ноды
+ collectPotentialCollisionPlanes(node.front);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Определение пересечения сферы с примитивами, лежащими в заданной плоскости.
+ *
+ * @param plane плоскость, содержащая примитивы для проверки
+ */
+ private function calculateCollisionWithPlaneS(plane:CollisionPlane):void {
+ collisionPlanePoint.copy(collisionSource);
+
+ var normal:Point3D = plane.node.normal;
+ // Если сфера врезана в плоскость
+ if (plane.sourceOffset <= _radius) {
+ if (plane.infront) {
+ collisionPlanePoint.x -= normal.x * plane.sourceOffset;
+ collisionPlanePoint.y -= normal.y * plane.sourceOffset;
+ collisionPlanePoint.z -= normal.z * plane.sourceOffset;
+ } else {
+ collisionPlanePoint.x += normal.x * plane.sourceOffset;
+ collisionPlanePoint.y += normal.y * plane.sourceOffset;
+ collisionPlanePoint.z += normal.z * plane.sourceOffset;
+ }
+ } else {
+ // Находим центр сферы во время столкновения с плоскостью
+ var time:Number = (plane.sourceOffset - _radius) / (plane.sourceOffset - plane.destinationOffset);
+ collisionPlanePoint.x = collisionSource.x + currentDisplacement.x * time;
+ collisionPlanePoint.y = collisionSource.y + currentDisplacement.y * time;
+ collisionPlanePoint.z = collisionSource.z + currentDisplacement.z * time;
+
+ // Устанавливаем точку пересечения cферы с плоскостью
+ if (plane.infront) {
+ collisionPlanePoint.x -= normal.x * _radius;
+ collisionPlanePoint.y -= normal.y * _radius;
+ collisionPlanePoint.z -= normal.z * _radius;
+ } else {
+ collisionPlanePoint.x += normal.x * _radius;
+ collisionPlanePoint.y += normal.y * _radius;
+ collisionPlanePoint.z += normal.z * _radius;
+ }
+ }
+
+ // Проверяем примитивы плоскости
+ var primitive:*;
+ collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE;
+ collisionPrimitiveNearest = null;
+ if (plane.infront) {
+ if ((primitive = plane.node.primitive) != null) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveS(plane.node.primitive);
+ }
+ } else {
+ for (primitive in plane.node.frontPrimitives) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveS(primitive);
+ if (collisionPrimitive != null) break;
+ }
+ }
+ }
+ } else {
+ for (primitive in plane.node.backPrimitives) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveS(primitive);
+ if (collisionPrimitive != null) break;
+ }
+ }
+ }
+
+ if (collisionPrimitive != null) {
+ // Если точка пересечения попала в примитив
+
+ // Нормаль плоскости при столкновении - нормаль плоскости
+ if (plane.infront) {
+ collisionNormal.x = normal.x;
+ collisionNormal.y = normal.y;
+ collisionNormal.z = normal.z;
+ collisionOffset = plane.node.offset;
+ } else {
+ collisionNormal.x = -normal.x;
+ collisionNormal.y = -normal.y;
+ collisionNormal.z = -normal.z;
+ collisionOffset = -plane.node.offset;
+ }
+
+ // Точка столкновения в точке столкновения с плоскостью
+ collisionPoint.x = collisionPlanePoint.x;
+ collisionPoint.y = collisionPlanePoint.y;
+ collisionPoint.z = collisionPlanePoint.z;
+
+ } else {
+ // Если точка пересечения не попала ни в один примитив, проверяем столкновение с ближайшей
+
+ // Вектор из ближайшей точки в центр сферы
+ var nearestPointToSourceX:Number = collisionSource.x - collisionPrimitivePoint.x;
+ var nearestPointToSourceY:Number = collisionSource.y - collisionPrimitivePoint.y;
+ var nearestPointToSourceZ:Number = collisionSource.z - collisionPrimitivePoint.z;
+
+ // Если движение в сторону точки
+ if (nearestPointToSourceX * currentDisplacement.x + nearestPointToSourceY * currentDisplacement.y + nearestPointToSourceZ * currentDisplacement.z <= 0) {
+
+ // Ищем нормализованный вектор обратного направления
+ var vectorLength:Number = Math.sqrt(currentDisplacement.x * currentDisplacement.x + currentDisplacement.y * currentDisplacement.y + currentDisplacement.z * currentDisplacement.z);
+ var vectorX:Number = -currentDisplacement.x / vectorLength;
+ var vectorY:Number = -currentDisplacement.y / vectorLength;
+ var vectorZ:Number = -currentDisplacement.z / vectorLength;
+
+ // Длина вектора из ближайшей точки в центр сферы
+ var nearestPointToSourceLengthSqr:Number = nearestPointToSourceX * nearestPointToSourceX + nearestPointToSourceY * nearestPointToSourceY + nearestPointToSourceZ * nearestPointToSourceZ;
+
+ // Проекция вектора из ближайшей точки в центр сферы на нормализованный вектор обратного направления
+ var projectionLength:Number = nearestPointToSourceX * vectorX + nearestPointToSourceY * vectorY + nearestPointToSourceZ * vectorZ;
+
+ var projectionInsideSphereLengthSqr:Number = _radius2 - nearestPointToSourceLengthSqr + projectionLength * projectionLength;
+
+ if (projectionInsideSphereLengthSqr > 0) {
+ // Находим расстояние из ближайшей точки до сферы
+ var distance:Number = projectionLength - Math.sqrt(projectionInsideSphereLengthSqr);
+
+ if (distance < vectorLength) {
+ // Столкновение сферы с ближайшей точкой произошло
+
+ // Точка столкновения в ближайшей точке
+ collisionPoint.x = collisionPrimitivePoint.x;
+ collisionPoint.y = collisionPrimitivePoint.y;
+ collisionPoint.z = collisionPrimitivePoint.z;
+
+ // Находим нормаль плоскости столкновения
+ var nearestPointToSourceLength:Number = Math.sqrt(nearestPointToSourceLengthSqr);
+ collisionNormal.x = nearestPointToSourceX / nearestPointToSourceLength;
+ collisionNormal.y = nearestPointToSourceY / nearestPointToSourceLength;
+ collisionNormal.z = nearestPointToSourceZ / nearestPointToSourceLength;
+
+ // Смещение плоскости столкновения
+ collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z;
+ collisionPrimitive = collisionPrimitiveNearest;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Определение столкновения сферы с примитивом.
+ *
+ * @param primitive примитив, столкновение с которым проверяется
+ */
+ private function calculateCollisionWithPrimitiveS(primitive:PolyPrimitive):void {
+
+ var length:uint = primitive.num;
+ var points:Array = primitive.points;
+ var normal:Point3D = primitive.face.globalNormal;
+ var inside:Boolean = true;
+
+ for (var i:uint = 0; i < length; i++) {
+
+ var p1:Point3D = points[i];
+ var p2:Point3D = points[(i < length - 1) ? (i + 1) : 0];
+
+ var edgeX:Number = p2.x - p1.x;
+ var edgeY:Number = p2.y - p1.y;
+ var edgeZ:Number = p2.z - p1.z;
+
+ var vectorX:Number = collisionPlanePoint.x - p1.x;
+ var vectorY:Number = collisionPlanePoint.y - p1.y;
+ var vectorZ:Number = collisionPlanePoint.z - p1.z;
+
+ var crossX:Number = vectorY * edgeZ - vectorZ * edgeY;
+ var crossY:Number = vectorZ * edgeX - vectorX * edgeZ;
+ var crossZ:Number = vectorX * edgeY - vectorY * edgeX;
+
+ if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) {
+ // Точка за пределами полигона
+ inside = false;
+
+ var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ;
+ var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr;
+
+ // Если расстояние до прямой меньше текущего ближайшего
+ if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) {
+
+ // Ищем нормализованный вектор ребра
+ var edgeLength:Number = Math.sqrt(edgeLengthSqr);
+ var edgeNormX:Number = edgeX / edgeLength;
+ var edgeNormY:Number = edgeY / edgeLength;
+ var edgeNormZ:Number = edgeZ / edgeLength;
+
+ // Находим расстояние до точки перпендикуляра вдоль ребра
+ var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ;
+
+ var vectorLengthSqr:Number;
+ if (t < 0) {
+ // Ближайшая точка - первая
+ vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
+ if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
+ collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
+ collisionPrimitivePoint.x = p1.x;
+ collisionPrimitivePoint.y = p1.y;
+ collisionPrimitivePoint.z = p1.z;
+ collisionPrimitiveNearest = primitive;
+ }
+ } else {
+ if (t > edgeLength) {
+ // Ближайшая точка - вторая
+ vectorX = collisionPlanePoint.x - p2.x;
+ vectorY = collisionPlanePoint.y - p2.y;
+ vectorZ = collisionPlanePoint.z - p2.z;
+ vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
+ if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
+ collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
+ collisionPrimitivePoint.x = p2.x;
+ collisionPrimitivePoint.y = p2.y;
+ collisionPrimitivePoint.z = p2.z;
+ collisionPrimitiveNearest = primitive;
+ }
+ } else {
+ // Ближайшая точка на ребре
+ collisionPrimitiveNearestLengthSqr = edgeDistanceSqr;
+ collisionPrimitivePoint.x = p1.x + edgeNormX * t;
+ collisionPrimitivePoint.y = p1.y + edgeNormY * t;
+ collisionPrimitivePoint.z = p1.z + edgeNormZ * t;
+ collisionPrimitiveNearest = primitive;
+ }
+ }
+ }
+ }
+ }
+
+ // Если попали в примитив
+ if (inside) {
+ collisionPrimitive = primitive;
+ }
+ }
+
+ /**
+ * Проверка на действительное столкновение эллипсоида с плоскостью.
+ */
+ private function calculateCollisionWithPlaneE(plane:CollisionPlane):void {
+ var normalX:Number = plane.node.normal.x;
+ var normalY:Number = plane.node.normal.y;
+ var normalZ:Number = plane.node.normal.z;
+ // Смещение по направлению к плоскости вдоль нормали. Положительное смещение означает приближение к плоскости, отрицательное -- удаление
+ // от плоскости, в этом случае столкновения не происходит.
+ var displacementAlongNormal:Number = currentDisplacement.x * normalX + currentDisplacement.y * normalY + currentDisplacement.z * normalZ;
+ if (plane.infront) {
+ displacementAlongNormal = -displacementAlongNormal;
+ }
+ // Выходим из функции в случае удаления от плоскости
+ if (displacementAlongNormal < 0) {
+ return;
+ }
+ // Определение ближайшей к плоскости точки эллипсоида
+ var k:Number = _radius / Math.sqrt(normalX * normalX * _scaleX2 + normalY * normalY * _scaleY2 + normalZ * normalZ * _scaleZ2);
+ // Положение точки в локальной системе координат эллипсоида
+ var localClosestX:Number = k * normalX * _scaleX2;
+ var localClosestY:Number = k * normalY * _scaleY2;
+ var localClosestZ:Number = k * normalZ * _scaleZ2;
+ // Глобальные координаты точки
+ var px:Number = collisionSource.x + localClosestX;
+ var py:Number = collisionSource.y + localClosestY;
+ var pz:Number = collisionSource.z + localClosestZ;
+ // Растояние от найденной точки эллипсоида до плоскости
+ var closestPointDistance:Number = px * normalX + py * normalY + pz * normalZ - plane.node.offset;
+ if (!plane.infront) {
+ closestPointDistance = -closestPointDistance;
+ }
+ if (closestPointDistance > plane.sourceOffset) {
+ // Найдена наиболее удалённая точка, расчитываем вторую
+ px = collisionSource.x - localClosestX;
+ py = collisionSource.y - localClosestY;
+ pz = collisionSource.z - localClosestZ;
+ closestPointDistance = px * normalX + py * normalY + pz * normalZ - plane.node.offset;
+ if (!plane.infront) {
+ closestPointDistance = -closestPointDistance;
+ }
+ }
+ // Если расстояние от ближайшей точки эллипсоида до плоскости больше, чем смещение эллипсоида вдоль нормали плоскости,
+ // то столкновения не произошло и нужно завершить выполнение функции
+ if (closestPointDistance > displacementAlongNormal) {
+ return;
+ }
+ // Если добрались до этого места, значит произошло столкновение с плоскостью. Требуется определить точку столкновения
+ // с ближайшим полигоном, лежащим в этой плоскости
+ if (closestPointDistance <= 0 ) {
+ // Эллипсоид пересекается с плоскостью, ищем проекцию ближайшей точки эллипсоида на плоскость
+ if (plane.infront) {
+ collisionPlanePoint.x = px - normalX * closestPointDistance;
+ collisionPlanePoint.y = py - normalY * closestPointDistance;
+ collisionPlanePoint.z = pz - normalZ * closestPointDistance;
+ } else {
+ collisionPlanePoint.x = px + normalX * closestPointDistance;
+ collisionPlanePoint.y = py + normalY * closestPointDistance;
+ collisionPlanePoint.z = pz + normalZ * closestPointDistance;
+ }
+ } else {
+ // Эллипсоид не пересекается с плоскостью, ищем точку контакта
+ var t:Number = closestPointDistance / displacementAlongNormal;
+ collisionPlanePoint.x = px + currentDisplacement.x * t;
+ collisionPlanePoint.y = py + currentDisplacement.y * t;
+ collisionPlanePoint.z = pz + currentDisplacement.z * t;
+ }
+ // Проверяем примитивы плоскости
+ var primitive:*;
+ collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE;
+ collisionPrimitiveNearest = null;
+ if (plane.infront) {
+ if ((primitive = plane.node.primitive) != null) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveE(primitive);
+ }
+ } else {
+ for (primitive in plane.node.frontPrimitives) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveE(primitive);
+ if (collisionPrimitive != null) {
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ for (primitive in plane.node.backPrimitives) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveE(primitive);
+ if (collisionPrimitive != null) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (collisionPrimitive != null) {
+ // Если точка пересечения попала в примитив
+ // Нормаль плоскости при столкновении - нормаль плоскости примитива
+ if (plane.infront) {
+ collisionNormal.x = normalX;
+ collisionNormal.y = normalY;
+ collisionNormal.z = normalZ;
+ collisionOffset = plane.node.offset;
+ } else {
+ collisionNormal.x = -normalX;
+ collisionNormal.y = -normalY;
+ collisionNormal.z = -normalZ;
+ collisionOffset = -plane.node.offset;
+ }
+ // Радиус эллипсоида в точке столкновения
+ collisionRadius = localClosestX * collisionNormal.x + localClosestY * collisionNormal.y + localClosestZ * collisionNormal.z;
+ if (collisionRadius < 0) {
+ collisionRadius = -collisionRadius;
+ }
+ radiusVector.x = px - collisionSource.x;
+ radiusVector.y = py - collisionSource.y;
+ radiusVector.z = pz - collisionSource.z;
+ // Точка столкновения совпадает с точкой столкновения с плоскостью примитива
+ collisionPoint.x = collisionPlanePoint.x;
+ collisionPoint.y = collisionPlanePoint.y;
+ collisionPoint.z = collisionPlanePoint.z;
+ } else {
+ // Если точка пересечения не попала внутрь примитива, находим пересечение с ближайшей точкой ближайшего примитива
+ // Трансформированная в пространство эллипсоида ближайшая точка на примитиве
+ px = collisionPrimitivePoint.x;
+ py = collisionPrimitivePoint.y;
+ pz = collisionPrimitivePoint.z;
+
+ var collisionExists:Boolean;
+ // Квадрат расстояния из центра эллипсоида до точки примитива
+ var r2:Number = px*px + py*py + pz*pz;
+ if (r2 < _radius2) {
+ // Точка оказалась внутри эллипсоида, находим точку на поверхности эллипсоида, лежащую на том же радиусе
+ k = _radius / Math.sqrt(r2);
+ px *= k * _scaleX;
+ py *= k * _scaleY;
+ pz *= k * _scaleZ;
+
+ collisionExists = true;
+ } else {
+ // Точка вне эллипсоида, находим пересечение луча, направленного противоположно скорости эллипсоида из точки
+ // примитива, с поверхностью эллипсоида
+ // Трансформированный в пространство эллипсоида противоположный вектор скорости
+ var vx:Number = - currentDisplacement.x / _scaleX;
+ var vy:Number = - currentDisplacement.y / _scaleY;
+ var vz:Number = - currentDisplacement.z / _scaleZ;
+ // Нахождение точки пересечения сферы и луча, направленного вдоль вектора скорости
+ var a:Number = vx*vx + vy*vy + vz*vz;
+ var b:Number = 2 * (px*vx + py*vy + pz*vz);
+ var c:Number = r2 - _radius2;
+ var d:Number = b*b - 4*a*c;
+ // Решение есть только при действительном дискриминанте квадратного уравнения
+ if (d >=0) {
+ // Выбирается минимальное время, т.к. нужна первая точка пересечения
+ t = -0.5 * (b + Math.sqrt(d)) / a;
+ // Точка лежит на луче только если время положительное
+ if (t >= 0 && t <= 1) {
+ // Координаты точки пересечения луча с эллипсоидом, переведённые обратно в нормальное пространство
+ px = (px + t * vx) * _scaleX;
+ py = (py + t * vy) * _scaleY;
+ pz = (pz + t * vz) * _scaleZ;
+
+ collisionExists = true;
+ }
+ }
+ }
+ if (collisionExists) {
+ // Противоположная нормаль к эллипсоиду в точке пересечения
+ collisionNormal.x = - px / _scaleX2;
+ collisionNormal.y = - py / _scaleY2;
+ collisionNormal.z = - pz / _scaleZ2;
+ collisionNormal.normalize();
+ // Радиус эллипсоида в точке столкновения
+ collisionRadius = px * collisionNormal.x + py * collisionNormal.y + pz * collisionNormal.z;
+ if (collisionRadius < 0) {
+ collisionRadius = -collisionRadius;
+ }
+ radiusVector.x = px;
+ radiusVector.y = py;
+ radiusVector.z = pz;
+ // Точка столкновения в ближайшей точке
+ collisionPoint.x = collisionPrimitivePoint.x * _scaleX + currentCoords.x;
+ collisionPoint.y = collisionPrimitivePoint.y * _scaleY + currentCoords.y;
+ collisionPoint.z = collisionPrimitivePoint.z * _scaleZ + currentCoords.z;
+ // Смещение плоскости столкновения
+ collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z;
+ collisionPrimitive = collisionPrimitiveNearest;
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Определение наличия столкновения эллипсоида с примитивом. Все расчёты выполняются в пространстве эллипсоида, где он выглядит
+ * как сфера. По окончании работы может быть установлена переменная collisionPrimitive в случае попадания точки
+ * столкновения внутрь примитива или collisionPrimitiveNearest в случае столкновения с ребром примитива через
+ * минимальное время.
+ *
+ * @param primitive примитив, столкновение с которым проверяется
+ */
+ private function calculateCollisionWithPrimitiveE(primitive:PolyPrimitive):void {
+ var length:uint = primitive.num;
+ var points:Array = primitive.points;
+ var normal:Point3D = primitive.face.globalNormal;
+ var inside:Boolean = true;
+
+ var point1:Point3D;
+ var point2:Point3D = points[length - 1];
+ p2.x = (point2.x - currentCoords.x) / _scaleX;
+ p2.y = (point2.y - currentCoords.y) / _scaleY;
+ p2.z = (point2.z - currentCoords.z) / _scaleZ;
+
+ localCollisionPlanePoint.x = (collisionPlanePoint.x - currentCoords.x) / _scaleX;
+ localCollisionPlanePoint.y = (collisionPlanePoint.y - currentCoords.y) / _scaleY;
+ localCollisionPlanePoint.z = (collisionPlanePoint.z - currentCoords.z) / _scaleZ;
+ // Обход всех рёбер примитива
+ for (var i:uint = 0; i < length; i++) {
+ point1 = point2;
+ point2 = points[i];
+
+ p1.x = p2.x;
+ p1.y = p2.y;
+ p1.z = p2.z;
+
+ p2.x = (point2.x - currentCoords.x) / _scaleX;
+ p2.y = (point2.y - currentCoords.y) / _scaleY;
+ p2.z = (point2.z - currentCoords.z) / _scaleZ;
+
+ // Расчёт векторного произведения вектора ребра на радиус-вектор точки столкновения относительно начала ребра
+ // с целью определения положения точки столкновения относительно полигона
+ var edgeX:Number = p2.x - p1.x;
+ var edgeY:Number = p2.y - p1.y;
+ var edgeZ:Number = p2.z - p1.z;
+
+ var vectorX:Number = localCollisionPlanePoint.x - p1.x;
+ var vectorY:Number = localCollisionPlanePoint.y - p1.y;
+ var vectorZ:Number = localCollisionPlanePoint.z - p1.z;
+
+ var crossX:Number = vectorY * edgeZ - vectorZ * edgeY;
+ var crossY:Number = vectorZ * edgeX - vectorX * edgeZ;
+ var crossZ:Number = vectorX * edgeY - vectorY * edgeX;
+
+ if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) {
+ // Точка за пределами полигона
+ inside = false;
+
+ var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ;
+ var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr;
+
+ // Если расстояние до прямой меньше текущего ближайшего
+ if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) {
+ // Ищем нормализованный вектор ребра
+ var edgeLength:Number = Math.sqrt(edgeLengthSqr);
+ var edgeNormX:Number = edgeX / edgeLength;
+ var edgeNormY:Number = edgeY / edgeLength;
+ var edgeNormZ:Number = edgeZ / edgeLength;
+ // Находим расстояние до точки перпендикуляра вдоль ребра
+ var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ;
+ var vectorLengthSqr:Number;
+ if (t < 0) {
+ // Ближайшая точка - первая
+ vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
+ if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
+ collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
+ collisionPrimitivePoint.x = p1.x;
+ collisionPrimitivePoint.y = p1.y;
+ collisionPrimitivePoint.z = p1.z;
+ collisionPrimitiveNearest = primitive;
+ }
+ } else {
+ if (t > edgeLength) {
+ // Ближайшая точка - вторая
+ vectorX = localCollisionPlanePoint.x - p2.x;
+ vectorY = localCollisionPlanePoint.y - p2.y;
+ vectorZ = localCollisionPlanePoint.z - p2.z;
+ vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
+ if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
+ collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
+ collisionPrimitivePoint.x = p2.x;
+ collisionPrimitivePoint.y = p2.y;
+ collisionPrimitivePoint.z = p2.z;
+ collisionPrimitiveNearest = primitive;
+ }
+ } else {
+ // Ближайшая точка на ребре
+ collisionPrimitiveNearestLengthSqr = edgeDistanceSqr;
+ collisionPrimitivePoint.x = p1.x + edgeNormX * t;
+ collisionPrimitivePoint.y = p1.y + edgeNormY * t;
+ collisionPrimitivePoint.z = p1.z + edgeNormZ * t;
+ collisionPrimitiveNearest = primitive;
+ }
+ }
+ }
+ }
+ }
+
+ // Если попали в примитив
+ if (inside) {
+ collisionPrimitive = primitive;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/primitives/Box.as b/Alternativa3D5/5.4/alternativa/engine3d/primitives/Box.as
new file mode 100644
index 0000000..5e730b5
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/primitives/Box.as
@@ -0,0 +1,316 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Прямоугольный параллелепипед.
+ */
+ public class Box extends Mesh {
+
+ /**
+ * Создание нового параллелепипеда.
+ * "front", "back", "left", "right", "top", "bottom"
+ * на каждую из которых может быть установлен свой материал.true, то нормали будут направлены внутрь фигуры.
+ * @param triangulate флаг триангуляции. Если указано значение true, четырехугольники в параллелепипеде будут триангулированы.
+ */
+ public function Box(width:Number = 100, length:Number = 100, height:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false, triangulate:Boolean = false) {
+ super();
+
+ if ((widthSegments == 0) || (lengthSegments == 0) || (heightSegments == 0)) {
+ return;
+ }
+ width = (width < 0)? 0 : width;
+ length = (length < 0)? 0 : length;
+ height = (height < 0)? 0 : height;
+
+ var wh:Number = width/2;
+ var lh:Number = length/2;
+ var hh:Number = height/2;
+ var ws:Number = width/widthSegments;
+ var ls:Number = length/lengthSegments;
+ var hs:Number = height/heightSegments;
+ var x:int;
+ var y:int;
+ var z:int;
+
+ // Создание точек
+ for (x = 0; x <= widthSegments; x++) {
+ for (y = 0; y <= lengthSegments; y++) {
+ for (z = 0; z <= heightSegments; z++) {
+ if (x == 0 || x == widthSegments || y == 0 || y == lengthSegments || z == 0 || z == heightSegments) {
+ createVertex(x*ws - wh, y*ls - lh, z*hs - hh, x + "_" + y + "_" + z);
+ }
+ }
+ }
+ }
+
+ // Создание поверхностей
+ var front:Surface = createSurface(null, "front");
+ var back:Surface = createSurface(null, "back");
+ var left:Surface = createSurface(null, "left");
+ var right:Surface = createSurface(null, "right");
+ var top:Surface = createSurface(null, "top");
+ var bottom:Surface = createSurface(null, "bottom");
+
+ // Создание граней
+ var wd:Number = 1/widthSegments;
+ var ld:Number = 1/lengthSegments;
+ var hd:Number = 1/heightSegments;
+ var faceId:String;
+
+ // Для оптимизаций UV при триангуляции
+ var aUV:Point;
+ var cUV:Point;
+
+ // Построение верхней грани
+ for (y = 0; y < lengthSegments; y++) {
+ for (x = 0; x < widthSegments; x++) {
+ faceId = "top_"+x+"_"+y;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point(x*wd, (lengthSegments - y)*ld);
+ cUV = new Point((x + 1)*wd, (lengthSegments - y - 1)*ld);
+ createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":1");
+ setUVsToFace(aUV, new Point(x*wd, (lengthSegments - y - 1)*ld), cUV, faceId + ":1");
+ createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":0");
+ setUVsToFace(cUV, new Point((x + 1)*wd, (lengthSegments - y)*ld), aUV, faceId + ":0");
+ } else {
+ createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments], faceId);
+ setUVsToFace(new Point(x*wd, (lengthSegments - y)*ld), new Point(x*wd, (lengthSegments - y - 1)*ld), new Point((x + 1)*wd, (lengthSegments - y - 1)*ld), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point(x*wd, y*ld);
+ cUV = new Point((x + 1)*wd, (y + 1)*ld);
+ createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":0");
+ setUVsToFace(aUV, new Point((x + 1)*wd, y*ld), cUV, faceId + ":0");
+ createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":1");
+ setUVsToFace(cUV, new Point(x*wd, (y + 1)*ld), aUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments], faceId);
+ setUVsToFace(new Point(x*wd, y*ld), new Point((x + 1)*wd, y*ld), new Point((x + 1)*wd, (y + 1)*ld), faceId);
+ }
+ }
+ if (triangulate) {
+ top.addFace(faceId + ":0");
+ top.addFace(faceId + ":1");
+ } else {
+ top.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение нижней грани
+ for (y = 0; y < lengthSegments; y++) {
+ for (x = 0; x < widthSegments; x++) {
+ faceId = "bottom_" + x + "_" + y;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point((widthSegments - x)*wd, (lengthSegments - y)*ld);
+ cUV = new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld);
+ createFace([x + "_" + y + "_" + 0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":0");
+ setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), cUV, faceId + ":0");
+ createFace([(x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0, x + "_" + y + "_" + 0], faceId + ":1");
+ setUVsToFace(cUV, new Point((widthSegments - x)*wd, (lengthSegments - y - 1)*ld), aUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + y + "_"+0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0], faceId);
+ setUVsToFace(new Point((widthSegments - x)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point((widthSegments - x)*wd, y*ld);
+ cUV = new Point((widthSegments - x - 1)*wd, (y + 1)*ld);
+ createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":1");
+ setUVsToFace(aUV, new Point((widthSegments - x)*wd, (y + 1)*ld), cUV, faceId + ":1");
+ createFace([(x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0, x + "_" + y + "_" + 0], faceId + ":0");
+ setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, y*ld), aUV, faceId + ":0");
+ } else {
+ createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) +"_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0], faceId);
+ setUVsToFace(new Point((widthSegments - x)*wd, y*ld), new Point((widthSegments - x)*wd, (y + 1)*ld), new Point((widthSegments - x - 1)*wd, (y + 1)*ld), faceId);
+ }
+ }
+ if (triangulate) {
+ bottom.addFace(faceId + ":0");
+ bottom.addFace(faceId + ":1");
+ } else {
+ bottom.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение фронтальной грани
+ for (z = 0; z < heightSegments; z++) {
+ for (x = 0; x < widthSegments; x++) {
+ faceId = "front_"+x+"_"+z;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point((widthSegments - x)*wd, z*hd);
+ cUV = new Point((widthSegments - x - 1)*wd, (z + 1)*hd);
+ createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":1");
+ setUVsToFace(aUV, new Point((widthSegments - x)*wd, (z + 1)*hd), cUV, faceId + ":1");
+ createFace([(x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z, x + "_" + 0 + "_" + z], faceId + ":0");
+ setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, z*hd), aUV, faceId + ":0");
+ } else {
+ createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z], faceId);
+ setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point(x*wd, z*hd);
+ cUV = new Point((x + 1)*wd, (z + 1)*hd);
+ createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":0");
+ setUVsToFace(aUV, new Point((x + 1)*wd, z*hd), cUV, faceId + ":0");
+ createFace([(x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + z], faceId + ":1");
+ setUVsToFace(cUV, new Point(x*wd, (z + 1)*hd), aUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1)], faceId);
+ setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId);
+ }
+ }
+ if (triangulate) {
+ front.addFace(faceId + ":0");
+ front.addFace(faceId + ":1");
+ } else {
+ front.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение задней грани
+ for (z = 0; z < heightSegments; z++) {
+ for (x = 0; x < widthSegments; x++) {
+ faceId = "back_"+x+"_"+z;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point(x * wd, (z + 1) * hd);
+ cUV = new Point((x + 1) * wd, z * hd);
+ createFace([x + "_" + lengthSegments+"_" + (z + 1), x + "_"+lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0");
+ setUVsToFace(aUV, new Point(x * wd, z * hd), cUV, faceId + ":0");
+ createFace([(x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId + ":1");
+ setUVsToFace(cUV, new Point((x + 1) * wd, (z + 1) * hd), aUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId);
+ setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point((widthSegments - x)*wd, (z + 1)*hd);
+ cUV = new Point((widthSegments - x - 1)*wd, z*hd);
+ createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0");
+ setUVsToFace(new Point((widthSegments - x)*wd, z*hd), aUV, cUV, faceId + ":0");
+ createFace([x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":1");
+ setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (z + 1)*hd), cUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId);
+ setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId);
+ }
+ }
+ if (triangulate) {
+ back.addFace(faceId + ":0");
+ back.addFace(faceId + ":1");
+ } else {
+ back.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение левой грани
+ for (y = 0; y < lengthSegments; y++) {
+ for (z = 0; z < heightSegments; z++) {
+ faceId = "left_" + y + "_" + z;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point(y*ld, (z + 1)*hd);
+ cUV = new Point((y + 1)*ld, z*hd);
+ createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z], faceId + ":0");
+ setUVsToFace(aUV, new Point(y*ld, z*hd), cUV, faceId + ":0");
+ createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId + ":1");
+ setUVsToFace(cUV, new Point((y + 1)*ld, (z + 1)*hd), aUV, faceId + ":1");
+ } else {
+ createFace([0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId);
+ setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point((lengthSegments - y - 1)*ld, z*hd);
+ cUV = new Point((lengthSegments - y)*ld, (z + 1)*hd);
+ createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1)], faceId + ":0");
+ setUVsToFace(aUV, new Point((lengthSegments - y)*ld, z*hd), cUV, faceId + ":0");
+ createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + (y + 1) + "_" + z], faceId + ":1");
+ setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), aUV, faceId + ":1");
+ } else {
+ createFace([0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + z], faceId);
+ setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId);
+ }
+ }
+ if (triangulate) {
+ left.addFace(faceId + ":0");
+ left.addFace(faceId + ":1");
+ } else {
+ left.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение правой грани
+ for (y = 0; y < lengthSegments; y++) {
+ for (z = 0; z < heightSegments; z++) {
+ faceId = "right_" + y + "_" + z;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point((lengthSegments - y)*ld, z*hd);
+ cUV = new Point((lengthSegments - y - 1)*ld, (z + 1)*hd);
+ createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":1");
+ setUVsToFace(aUV, new Point((lengthSegments - y)*ld, (z + 1)*hd), cUV, faceId + ":1");
+ createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + y + "_" + z], faceId + ":0");
+ setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, z*hd), aUV, faceId + ":0");
+ } else {
+ createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z], faceId);
+ setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point(y*ld, z*hd);
+ cUV = new Point((y + 1)*ld, (z + 1)*hd);
+ createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":0");
+ setUVsToFace(aUV, new Point((y + 1)*ld, z*hd), cUV, faceId + ":0");
+ createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + y + "_" + z], faceId + ":1");
+ setUVsToFace(cUV, new Point(y*ld, (z + 1)*hd), aUV, faceId + ":1");
+ } else {
+ createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1)], faceId);
+ setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId);
+ }
+ }
+ if (triangulate) {
+ right.addFace(faceId + ":0");
+ right.addFace(faceId + ":1");
+ } else {
+ right.addFace(faceId);
+ }
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Box(0, 0, 0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/primitives/Cone.as b/Alternativa3D5/5.4/alternativa/engine3d/primitives/Cone.as
new file mode 100644
index 0000000..f98c012
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/primitives/Cone.as
@@ -0,0 +1,264 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.utils.MathUtils;
+ import alternativa.engine3d.core.Face;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Усеченный конус или цилиндр.
+ */
+ public class Cone extends Mesh {
+
+ /**
+ * Создает примитив усеченный конус или цилиндр.
+ * topRadius = 0 или bottomRadius = 0 будет построен конус. При установленном triangulate установлен в false и на примитив не может быть наложена текстура.
+ * Только при установленном параметре triangulate в true это возможно."side".
+ * При установленном параметре bottomRadius не равном нулю в примитиве создается поверхность "bottom",
+ * при установленном параметре topRadius в примитиве создается поверхность "top".
+ * На каждую из поверхностей может быть наложен свой материалtrue нормли будут направлены внутрь примитива.
+ * @param triangulate флаг триангуляции. При значении true все четырехугольные грани примитива будут триангулированы
+ * и появится возможность наложить на примитив текстуру.
+ */
+ public function Cone(height:Number = 100, bottomRadius:Number = 100, topRadius:Number = 0, heightSegments:uint = 1, radialSegments:uint = 12, reverse:Boolean = false, triangulate:Boolean = false) {
+
+ if ((radialSegments < 3) || (heightSegments < 1) || (heightSegments == 1 && topRadius == 0 && bottomRadius == 0)) {
+ return;
+ }
+ height = (height < 0)? 0 : height;
+ bottomRadius = (bottomRadius < 0)? 0 : bottomRadius;
+ topRadius = (topRadius < 0)? 0 : topRadius;
+
+ const radialSegment:Number = MathUtils.DEG360/radialSegments;
+ const radiusSegment:Number = (bottomRadius - topRadius)/heightSegments;
+ const heightSegment:Number = height/heightSegments;
+ const halfHeight:Number = height*0.5
+ const uSegment:Number = 1/radialSegments;
+ const vSegment:Number = 1/heightSegments;
+
+ // Создание вершин
+ if (topRadius == 0 || triangulate) {
+ var poleUp:Vertex = createVertex(0, 0, halfHeight, "poleUp");
+ }
+ if (bottomRadius == 0 || triangulate) {
+ var poleDown:Vertex = createVertex(0, 0, -halfHeight, "poleDown");
+ }
+
+ var radial:uint;
+ var segment:uint;
+
+ var topSegment:uint = heightSegments - int(topRadius == 0);
+ var bottomSegment:uint = int(bottomRadius == 0) ;
+ for (segment = bottomSegment; segment <= topSegment; segment++) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ var currentAngle:Number = radialSegment*radial;
+ var currentRadius:Number = bottomRadius - (radiusSegment*segment);
+ createVertex(Math.cos(currentAngle)*currentRadius, Math.sin(currentAngle)*currentRadius, heightSegment*segment - halfHeight, radial + "_" + segment);
+ }
+ }
+
+ // Создание граней и поверхности
+ var face:Face;
+
+ var points:Array;
+
+ var side:Surface = createSurface(null, "side");
+
+ if (topRadius == 0) {
+ // Создание граней у верхнего полюса
+ var prevRadial:uint = radialSegments - 1;
+ var centerUV:Point = new Point(0.5, 1);
+ var v:Number = topSegment*vSegment;
+ if (reverse) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], prevRadial + "_" + topSegment);
+ if (triangulate) {
+ setUVsToFace(centerUV, new Point(1 - (prevRadial + 1)*uSegment, v) , new Point(1 - prevRadial*uSegment, v), face);
+ }
+ side.addFace(face);
+ prevRadial = radial;
+ }
+ } else {
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], prevRadial + "_" + topSegment);
+ if (triangulate) {
+ setUVsToFace(centerUV, new Point(prevRadial*uSegment, v), new Point((prevRadial + 1)*uSegment, v), face);
+ }
+ side.addFace(face);
+ prevRadial = radial;
+ }
+ }
+ } else {
+ // Создание граней верхней крышки
+ var top:Surface = createSurface(null, "top");
+ if (triangulate) {
+ prevRadial = radialSegments - 1;
+ centerUV = new Point(0.5, 0.5);
+ var UV:Point;
+ var prevUV:Point;
+ if (reverse) {
+ prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5);
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], "top_" + prevRadial);
+ currentAngle = radial * radialSegment;
+ UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5);
+ setUVsToFace(centerUV, UV, prevUV, face);
+ top.addFace(face);
+ prevUV = UV;
+ prevRadial = radial;
+ }
+ } else {
+ prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5);
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], "top_" + prevRadial);
+ currentAngle = radial*radialSegment;
+ UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5);
+ setUVsToFace(centerUV, prevUV, UV, face);
+ top.addFace(face);
+ prevUV = UV;
+ prevRadial = radial;
+ }
+ }
+ } else {
+ points = new Array();
+ if (reverse) {
+ for (radial = (radialSegments - 1); radial < uint(-1); radial--) {
+ points.push(radial + "_" + topSegment);
+ }
+ } else {
+ for (radial = 0; radial < radialSegments; radial++) {
+ points.push(radial + "_" + topSegment);
+ }
+ }
+ top.addFace(createFace(points, "top"));
+ }
+ }
+ // Создание боковых граней
+ var face2:Face;
+ var aUV:Point;
+ var cUV:Point;
+ for (segment = bottomSegment; segment < topSegment; segment++) {
+ prevRadial = radialSegments - 1;
+ v = segment * vSegment;
+ for (radial = 0; radial < radialSegments; radial++) {
+ if (triangulate) {
+ if (reverse) {
+ face = createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":0");
+ face2 = createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1");
+ aUV = new Point(1 - (prevRadial + 1)*uSegment, v + vSegment)
+ cUV = new Point(1 - prevRadial*uSegment, v);
+ setUVsToFace(aUV, new Point(1 - (prevRadial + 1)*uSegment, v), cUV, face);
+ setUVsToFace(cUV, new Point(1 - prevRadial*uSegment, v + vSegment), aUV, face2);
+ } else {
+ face = createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0");
+ face2 = createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":1");
+ aUV = new Point(prevRadial*uSegment, v)
+ cUV = new Point((prevRadial + 1)*uSegment, v + vSegment);
+ setUVsToFace(aUV, new Point((prevRadial + 1)*uSegment, v), cUV, face);
+ setUVsToFace(cUV, new Point(prevRadial*uSegment, v + vSegment), aUV, face2);
+ }
+ side.addFace(face);
+ side.addFace(face2);
+ } else {
+ if (reverse) {
+ side.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment));
+ } else {
+ side.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1)], prevRadial + "_" + segment));
+ }
+ }
+ prevRadial = radial;
+ }
+ }
+
+ if (bottomRadius == 0) {
+ // Создание граней у нижнего полюса
+ prevRadial = radialSegments - 1;
+ centerUV = new Point(0.5, 0);
+ v = bottomSegment*vSegment;
+ if (reverse) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], prevRadial + "_0");
+ if (triangulate) {
+ setUVsToFace(centerUV, new Point(1 - prevRadial*uSegment, v), new Point(1 - (prevRadial + 1)*uSegment, v), face);
+ }
+ side.addFace(face);
+ prevRadial = radial;
+ }
+ } else {
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], prevRadial + "_0");
+ if (triangulate) {
+ setUVsToFace(centerUV, new Point((prevRadial + 1)*uSegment, v), new Point(prevRadial*uSegment, v), face);
+ }
+ side.addFace(face);
+ prevRadial = radial;
+ }
+ }
+ } else {
+ // Создание граней нижней крышки
+ var bottom:Surface = createSurface(null, "bottom");
+ if (triangulate) {
+ prevRadial = radialSegments - 1;
+ centerUV = new Point(0.5, 0.5);
+ if (reverse) {
+ prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5);
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], "bottom_" + prevRadial);
+ currentAngle = radial*radialSegment;
+ UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5);
+ setUVsToFace(centerUV, prevUV, UV, face);
+ bottom.addFace(face);
+ prevUV = UV;
+ prevRadial = radial;
+ }
+ } else {
+ prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5);
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], "bottom_" + prevRadial);
+ currentAngle = radial * radialSegment;
+ UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5);
+ setUVsToFace(centerUV, UV, prevUV, face);
+ bottom.addFace(face);
+ prevUV = UV;
+ prevRadial = radial;
+ }
+ }
+ } else {
+ points = new Array();
+ if (reverse) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ points.push(radial + "_" + bottomSegment);
+ }
+ } else {
+ for (radial = (radialSegments - 1); radial < uint(-1); radial--) {
+ points.push(radial + "_" + bottomSegment);
+ }
+ }
+ bottom.addFace(createFace(points, "bottom"));
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Cone(0, 0, 0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/primitives/GeoPlane.as b/Alternativa3D5/5.4/alternativa/engine3d/primitives/GeoPlane.as
new file mode 100644
index 0000000..ea681b4
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/primitives/GeoPlane.as
@@ -0,0 +1,197 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Геоплоскость.
+ */
+ public class GeoPlane extends Mesh {
+
+ /**
+ * Создает геоплоскость.
+ * reverse установленном в true примитив будет содержать грань - "back".
+ * При значении reverse установленном в false примитив будет содержать грань - "front".
+ * Параметр twoSided указывает методу создать обе поверхности.true, то создаётся двусторонняя поверхность
+ * @param reverse флаг инвертирования нормалей
+ */
+ public function GeoPlane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false) {
+ super();
+
+ if ((widthSegments == 0) || (lengthSegments == 0)) {
+ return;
+ }
+ width = (width < 0)? 0 : width;
+ length = (length < 0)? 0 : length;
+
+ // Середина
+ var wh:Number = width/2;
+ var hh:Number = length/2;
+ // Размеры сегмента
+ var ws:Number = width/widthSegments;
+ var hs:Number = length/lengthSegments;
+
+ // Размеры UV-сегмента
+ var us:Number = 1/widthSegments;
+ var vs:Number = 1/lengthSegments;
+
+ // Создание точек
+ var x:uint;
+ var y:uint;
+ var frontUV:Array = new Array();
+ var backUV:Array = ((lengthSegments & 1) == 0) ? null : new Array();
+ for (y = 0; y <= lengthSegments; y++) {
+ frontUV[y] = new Array();
+ if (backUV != null) {
+ backUV[y] = new Array();
+ }
+ for (x = 0; x <= widthSegments; x++) {
+ if ((y & 1) == 0) {
+ // Если чётный ряд
+ createVertex(x*ws - wh, y*hs - hh, 0, y + "_" + x);
+ frontUV[y][x] = new Point(x*us, y*vs);
+ if (backUV != null) {
+ backUV[y][x] = new Point(x*us, 1 - y*vs);
+ }
+ } else {
+ // Если нечётный ряд
+ if (x == 0) {
+ // Первая точка
+ createVertex(-wh, y*hs - hh, 0, y + "_" + x);
+ frontUV[y][x] = new Point(0, y*vs);
+ if (backUV != null) {
+ backUV[y][x] = new Point(0, 1 - y*vs);
+ }
+ } else {
+ createVertex(x*ws - wh - ws/2, y*hs - hh, 0, y + "_" + x);
+ frontUV[y][x] = new Point((x - 0.5)*us, y*vs);
+ if (backUV != null) {
+ backUV[y][x] = new Point((x - 0.5)*us, 1 - y*vs);
+ }
+ if (x == widthSegments) {
+ // Последняя точка
+ createVertex(wh, y*hs - hh, 0, y + "_" + (x + 1));
+ frontUV[y][x + 1] = new Point(1, y*vs);
+ if (backUV != null) {
+ backUV[y][x + 1] = new Point(1, 1 - y*vs);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Создание поверхностей
+ var front:Surface;
+ var back:Surface;
+
+ if (twoSided || !reverse) {
+ front = createSurface(null, "front");
+ }
+ if (twoSided || reverse) {
+ back = createSurface(null, "back");
+ }
+
+ // Создание полигонов
+ var face:Face;
+ for (y = 0; y < lengthSegments; y++) {
+ for (var n:uint = 0; n <= (widthSegments << 1); n++) {
+ x = n >> 1;
+ if ((y & 1) == 0) {
+ // Если чётный ряд
+ if ((n & 1) == 0) {
+ // Если остриём вверх
+ if (twoSided || !reverse) {
+ face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]);
+ setUVsToFace(frontUV[y][x], frontUV[y + 1][x + 1], frontUV[y + 1][x], face);
+ front.addFace(face);
+ }
+ if (twoSided || reverse) {
+ face = createFace([y + "_" + x, (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]);
+ if (backUV != null) {
+ setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y + 1][x + 1], face);
+ } else {
+ setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face);
+ }
+ back.addFace(face);
+ }
+ } else {
+ // Если остриём вниз
+ if (twoSided || !reverse) {
+ face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + (x + 1)]);
+ setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x + 1], face);
+ front.addFace(face);
+ }
+ if (twoSided || reverse) {
+ face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), y + "_" + (x + 1)]);
+ if (backUV != null) {
+ setUVsToFace(backUV[y][x], backUV[y + 1][x + 1], backUV[y][x + 1], face);
+ } else {
+ setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x + 1], frontUV[lengthSegments - y][x + 1], face);
+ }
+ back.addFace(face);
+ }
+ }
+ } else {
+ // Если нечётный ряд
+ if ((n & 1) == 0) {
+ // Если остриём вниз
+ if (twoSided || !reverse) {
+ face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + x]);
+ setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x], face);
+ front.addFace(face);
+ }
+ if (twoSided || reverse) {
+ face = createFace([y + "_" + x, (y + 1) + "_" + x, y + "_" + (x + 1)]);
+ if (backUV != null) {
+ setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y][x + 1], face);
+ } else {
+ setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y][x + 1], face);
+ }
+ back.addFace(face);
+ }
+ } else {
+ // Если остриём вверх
+ if (twoSided || !reverse) {
+ face = createFace([y + "_" + (x + 1), (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]);
+ setUVsToFace(frontUV[y][x+1], frontUV[y + 1][x + 1], frontUV[y + 1][x], face);
+ front.addFace(face);
+ }
+ if (twoSided || reverse) {
+ face = createFace([y + "_" + (x + 1), (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]);
+ if (backUV != null) {
+ setUVsToFace(backUV[y][x + 1], backUV[y + 1][x], backUV[y + 1][x + 1], face);
+ } else {
+ setUVsToFace(frontUV[lengthSegments - y][x + 1], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face);
+ }
+ back.addFace(face);
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new GeoPlane(0, 0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/primitives/GeoSphere.as b/Alternativa3D5/5.4/alternativa/engine3d/primitives/GeoSphere.as
new file mode 100644
index 0000000..7ba3bf2
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/primitives/GeoSphere.as
@@ -0,0 +1,321 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.utils.MathUtils;
+ import alternativa.engine3d.core.Face;
+ import flash.geom.Point;
+ import alternativa.types.Point3D;
+
+ use namespace alternativa3d;
+
+ /**
+ * Геосфера.
+ */
+ public class GeoSphere extends Mesh {
+
+ /**
+ * Создает геосферу.
+ * [0, 1],
+ * поэтому для материала с текстурой необходимо устанавливать флаг repeat.
+ *
+ * @param radius радиус геосферы. Не может быть меньше нуля.
+ * @param segments количество сегментов геосферы
+ * @param reverse флаг направления нормалей. При значении true нормали направлены внуть геосферы.
+ */
+ public function GeoSphere(radius:Number = 100, segments:uint = 2, reverse:Boolean = false) {
+ if (segments == 0) {
+ return;
+ }
+ radius = (radius < 0)? 0 : radius;
+
+ const sections:uint = 20;
+
+ //var nfaces:uint = sections*segments*segments;
+ //var nverts:Number = nfaces/2 + 2;
+ var points:Array = new Array();
+
+ var i:uint;
+ var f:uint;
+
+ var theta:Number;
+ var sin:Number;
+ var cos:Number;
+ // z расстояние до нижней и верхней крышки полюса
+ var subz:Number = 4.472136E-001*radius;
+ // радиус на расстоянии subz
+ var subrad:Number = 2*subz;
+ points.push(createVertex(0, 0, radius, "poleUp"));
+ // Создание вершин верхней крышки
+ for (i = 0; i < 5; i++) {
+ theta = MathUtils.DEG360*i/5;
+ sin = Math.sin(theta);
+ cos = Math.cos(theta);
+ points.push(createVertex(subrad*cos, subrad*sin, subz));
+ }
+ // Создание вершин нижней крышки
+ for (i = 0; i < 5; i++) {
+ theta = MathUtils.DEG180*((i << 1) + 1)/5;
+ sin = Math.sin(theta);
+ cos = Math.cos(theta);
+ points.push(createVertex(subrad*cos, subrad*sin, -subz));
+ }
+ points.push(createVertex(0, 0, -radius, "poleDown"));
+
+ for (i = 1; i < 6; i++) {
+ interpolate(0, i, segments, points);
+ }
+ for (i = 1; i < 6; i++) {
+ interpolate(i, i % 5 + 1, segments, points);
+ }
+ for (i = 1; i < 6; i++) {
+ interpolate(i, i + 5, segments, points);
+ }
+ for (i = 1; i < 6; i++) {
+ interpolate(i, (i + 3) % 5 + 6, segments, points);
+ }
+ for (i = 1; i < 6; i++) {
+ interpolate(i + 5, i % 5 + 6, segments, points);
+ }
+ for (i = 6; i < 11; i++) {
+ interpolate(11, i, segments, points);
+ }
+ for (f = 0; f < 5; f++) {
+ for (i = 1; i <= segments - 2; i++) {
+ interpolate(12 + f*(segments - 1) + i, 12 + (f + 1) % 5*(segments - 1) + i, i + 1, points);
+ }
+ }
+ for (f = 0; f < 5; f++) {
+ for (i = 1; i <= segments - 2; i++) {
+ interpolate(12 + (f + 15)*(segments - 1) + i, 12 + (f + 10)*(segments - 1) + i, i + 1, points);
+ }
+ }
+ for (f = 0; f < 5; f++) {
+ for (i = 1; i <= segments - 2; i++) {
+ interpolate(12 + ((f + 1) % 5 + 15)*(segments - 1) + segments - 2 - i, 12 + (f + 10)*(segments - 1) + segments - 2 - i, i + 1, points);
+ }
+ }
+ for (f = 0; f < 5; f++) {
+ for (i = 1; i <= segments - 2; i++) {
+ interpolate(12 + ((f + 1) % 5 + 25)*(segments - 1) + i, 12 + (f + 25)*(segments - 1) + i, i + 1, points);
+ }
+ }
+ // Создание граней
+ var face:Face;
+ var surface:Surface = createSurface();
+ for (f = 0; f < sections; f++) {
+ for (var row:uint = 0; row < segments; row++) {
+ for (var column:uint = 0; column <= row; column++) {
+ var a:uint = findVertices(segments, f, row, column);
+ var b:uint = findVertices(segments, f, row + 1, column);
+ var c:uint = findVertices(segments, f, row + 1, column + 1);
+ var va:Vertex = points[a];
+ var vb:Vertex = points[b];
+ var vc:Vertex = points[c];
+ var aUV:Point;
+ var bUV:Point;
+ var cUV:Point;
+ var coordA:Point3D = va._coords;
+ var coordB:Point3D = vb._coords;
+ var coordC:Point3D = vc._coords;
+
+ if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) {
+ aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) {
+ bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) {
+ cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ // полюс
+ if (a == 0 || a == 11) {
+ aUV.x = bUV.x + (cUV.x - bUV.x)*0.5;
+ }
+ if (b == 0 || b == 11) {
+ bUV.x = aUV.x + (cUV.x - aUV.x)*0.5;
+ }
+ if (c == 0 || c == 11) {
+ cUV.x = aUV.x + (bUV.x - aUV.x)*0.5;
+ }
+
+ if (reverse) {
+ face = createFace([va, vc, vb], (column << 1) + "_" + row + "_" + f);
+ aUV.x = 1 - aUV.x;
+ bUV.x = 1 - bUV.x;
+ cUV.x = 1 - cUV.x;
+ setUVsToFace(aUV, cUV, bUV, face);
+ } else {
+ face = createFace([va, vb, vc], (column << 1) + "_" + row + "_" + f);
+ setUVsToFace(aUV, bUV, cUV, face);
+ }
+ surface.addFace(face);
+ //trace(a + "_" + b + "_" + c);
+ if (column < row) {
+ b = findVertices(segments, f, row, column + 1);
+ var vd:Vertex = points[b];
+ coordB = vd._coords;
+
+ if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) {
+ aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) {
+ bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) {
+ cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (a == 0 || a == 11) {
+ aUV.x = bUV.x + (cUV.x - bUV.x)*0.5;
+ }
+ if (b == 0 || b == 11) {
+ bUV.x = aUV.x + (cUV.x - aUV.x)*0.5;
+ }
+ if (c == 0 || c == 11) {
+ cUV.x = aUV.x + (bUV.x - aUV.x)*0.5;
+ }
+
+ if (reverse) {
+ face = createFace([va, vd, vc], ((column << 1) + 1) + "_" + row + "_" + f);
+ aUV.x = 1 - aUV.x;
+ bUV.x = 1 - bUV.x;
+ cUV.x = 1 - cUV.x;
+ setUVsToFace(aUV, bUV, cUV, face);
+ } else {
+ face = createFace([va, vc, vd], ((column << 1) + 1) + "_" + row + "_" + f);
+ setUVsToFace(aUV, cUV, bUV, face);
+ }
+ surface.addFace(face);
+ }
+ }
+ }
+ }
+ }
+
+/* private function getUVSpherical(point:Point3D, radius:Number = 0, reverse:Boolean = false):Point {
+ if (radius == 0) {
+ radius = point.length;
+ }
+ if (reverse) {
+ var u:Number = 0.5 - Math.atan2(point.y, point.x)/MathUtils.DEG360;
+ } else {
+ u = Math.atan2(point.y, point.x)/MathUtils.DEG360 + 0.5;
+ }
+ return new Point(u, Math.asin(point.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ */
+ private function interpolate(v1:uint, v2:uint, num:uint, points:Array):void {
+ if (num < 2) {
+ return;
+ }
+ var a:Vertex = Vertex(points[v1]);
+ var b:Vertex = Vertex(points[v2]);
+ var cos:Number = (a.x*b.x + a.y*b.y + a.z*b.z)/(a.x*a.x + a.y*a.y + a.z*a.z);
+ cos = (cos < -1) ? -1 : ((cos > 1) ? 1 : cos);
+ var theta:Number = Math.acos(cos);
+ var sin:Number = Math.sin(theta);
+ for (var e:uint = 1; e < num; e++) {
+ var theta1:Number = theta*e/num;
+ var theta2:Number = theta*(num - e)/num;
+ var st1:Number = Math.sin(theta1);
+ var st2:Number = Math.sin(theta2);
+ points.push(createVertex((a.x*st2 + b.x*st1)/sin, (a.y*st2 + b.y*st1)/sin, (a.z*st2 + b.z*st1)/sin));
+ }
+ }
+
+ private function findVertices(segments:uint, section:uint, row:uint, column:uint):uint {
+ if (row == 0) {
+ if (section < 5) {
+ return (0);
+ }
+ if (section > 14) {
+ return (11);
+ }
+ return (section - 4);
+ }
+ if (row == segments && column == 0) {
+ if (section < 5) {
+ return (section + 1);
+ }
+ if (section < 10) {
+ return ((section + 4) % 5 + 6);
+ }
+ if (section < 15) {
+ return ((section + 1) % 5 + 1);
+ }
+ return ((section + 1) % 5 + 6);
+ }
+ if (row == segments && column == segments) {
+ if (section < 5) {
+ return ((section + 1) % 5 + 1);
+ }
+ if (section < 10) {
+ return (section + 1);
+ }
+ if (section < 15) {
+ return (section - 9);
+ }
+ return (section - 9);
+ }
+ if (row == segments) {
+ if (section < 5) {
+ return (12 + (5 + section)*(segments - 1) + column - 1);
+ }
+ if (section < 10) {
+ return (12 + (20 + (section + 4) % 5)*(segments - 1) + column - 1);
+ }
+ if (section < 15) {
+ return (12 + (section - 5)*(segments - 1) + segments - 1 - column);
+ }
+ return (12 + (5 + section)*(segments - 1) + segments - 1 - column);
+ }
+ if (column == 0) {
+ if (section < 5) {
+ return (12 + section*(segments - 1) + row - 1);
+ }
+ if (section < 10) {
+ return (12 + (section % 5 + 15)*(segments - 1) + row - 1);
+ }
+ if (section < 15) {
+ return (12 + ((section + 1) % 5 + 15)*(segments - 1) + segments - 1 - row);
+ }
+ return (12 + ((section + 1) % 5 + 25)*(segments - 1) + row - 1);
+ }
+ if (column == row) {
+ if (section < 5) {
+ return (12 + (section + 1) % 5*(segments - 1) + row - 1);
+ }
+ if (section < 10) {
+ return (12 + (section % 5 + 10)*(segments - 1) + row - 1);
+ }
+ if (section < 15) {
+ return (12 + (section % 5 + 10)*(segments - 1) + segments - row - 1);
+ }
+ return (12 + (section % 5 + 25)*(segments - 1) + row - 1);
+ }
+ return (12 + 30*(segments - 1) + section*(segments - 1)*(segments - 2)/2 + (row - 1)*(row - 2)/2 + column - 1);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new GeoSphere(0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/primitives/Plane.as b/Alternativa3D5/5.4/alternativa/engine3d/primitives/Plane.as
new file mode 100644
index 0000000..d346048
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/primitives/Plane.as
@@ -0,0 +1,117 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Плоскость.
+ */
+ public class Plane extends Mesh {
+
+ /**
+ * Создает плоскость.
+ * reverse установленном в true примитив будет содержать грань - "back".
+ * При значении reverse установленном в false примитив будет содержать грань - "front".
+ * Параметр twoSided указывает методу создать обе поверхности.true, то формируется двусторонняя плоскость
+ * @param reverse инвертирование нормалей
+ * @param triangulate флаг триангуляции. Если указано значение true, четырехугольники в плоскости будут триангулированы.
+ */
+ public function Plane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false, triangulate:Boolean = false) {
+ super();
+
+ if ((widthSegments == 0) || (lengthSegments == 0)) {
+ return;
+ }
+ width = (width < 0)? 0 : width;
+ length = (length < 0)? 0 : length;
+
+ // Середина
+ var wh:Number = width/2;
+ var lh:Number = length/2;
+
+ // Размеры сегмента
+ var ws:Number = width/widthSegments;
+ var ls:Number = length/lengthSegments;
+
+ // Размеры UV-сегмента
+ var wd:Number = 1/widthSegments;
+ var ld:Number = 1/lengthSegments;
+
+ // Создание точек и UV
+ var x:int;
+ var y:int;
+ var uv:Array = new Array();
+ for (y = 0; y <= lengthSegments; y++) {
+ uv[y] = new Array();
+ for (x = 0; x <= widthSegments; x++) {
+ uv[y][x] = new Point(x*wd, y*ld);
+ createVertex(x*ws - wh, y*ls - lh, 0, x+"_"+y);
+ }
+ }
+
+ // Создание поверхностей
+ var front:Surface;
+ var back:Surface;
+
+ if (twoSided || !reverse) {
+ front = createSurface(null, "front");
+ }
+ if (twoSided || reverse) {
+ back = createSurface(null, "back");
+ }
+
+ // Создание полигонов
+ for (y = 0; y < lengthSegments; y++) {
+ for (x = 0; x < widthSegments; x++) {
+ if (twoSided || !reverse) {
+ if (triangulate) {
+ createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1)], "front" + x + "_" + y + ":0");
+ setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y + ":0");
+ createFace([(x + 1) + "_" + (y + 1), x + "_" + (y + 1), x + "_" + y], "front" + x + "_" + y + ":1");
+ setUVsToFace(uv[y + 1][x + 1], uv[y + 1][x], uv[y][x], "front" + x + "_" + y + ":1");
+ front.addFace("front" + x + "_" + y + ":0");
+ front.addFace("front" + x + "_" + y + ":1");
+ } else {
+ createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1), x + "_" + (y + 1)], "front" + x + "_" + y);
+ setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y);
+ front.addFace("front" + x + "_" + y);
+ }
+ }
+ if (twoSided || reverse) {
+ if (triangulate) {
+ createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1)], "back" + x + "_" + y + ":0");
+ setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y + ":0");
+ createFace([(x + 1) + "_" + (y + 1), (x + 1) + "_" + y, x + "_" + y], "back" + x + "_" + y + ":1");
+ setUVsToFace(uv[lengthSegments - y - 1][x + 1], uv[lengthSegments - y][x + 1], uv[lengthSegments - y][x], "back" + x + "_" + y + ":1");
+ back.addFace("back" + x + "_" + y + ":0");
+ back.addFace("back"+x+"_"+y + ":1");
+ } else {
+ createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1), (x + 1) + "_" + y], "back" + x + "_" + y);
+ setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y);
+ back.addFace("back" + x + "_" + y);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Plane(0, 0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/engine3d/primitives/Sphere.as b/Alternativa3D5/5.4/alternativa/engine3d/primitives/Sphere.as
new file mode 100644
index 0000000..994d7f7
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/engine3d/primitives/Sphere.as
@@ -0,0 +1,144 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.utils.MathUtils;
+ import alternativa.engine3d.core.Face;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Сфера.
+ */
+ public class Sphere extends Mesh {
+
+ /**
+ * Создает сферу.
+ * triangulate установлен в false и на сферу нельзя наложить текстуру.
+ * Только при установленном triangulate в true это возможно.true нормали направлены внутрь сферы.
+ * @param triangulate флаг триангуляции. Если указано значение true, грани будут триангулированы,
+ * и будет возможно наложить на примитив текстуру.
+ */
+ public function Sphere(radius:Number = 100, radialSegments:uint = 8, heightSegments:uint = 8, reverse:Boolean = false, triangulate:Boolean = false) {
+ if ((radialSegments < 3) || (heightSegments < 2)) {
+ return;
+ }
+ radius = (radius < 0)? 0 : radius;
+
+ var poleUp:Vertex = createVertex(0, 0, radius, "poleUp");
+ var poleDown:Vertex = createVertex(0, 0, -radius, "poleDown");
+
+ const radialAngle:Number = MathUtils.DEG360/radialSegments;
+ const heightAngle:Number = MathUtils.DEG360/(heightSegments << 1);
+
+ var radial:uint;
+ var segment:uint;
+
+ // Создание вершин
+ for (segment = 1; segment < heightSegments; segment++) {
+ var currentHeightAngle:Number = heightAngle*segment;
+ var segmentRadius:Number = Math.sin(currentHeightAngle)*radius;
+ var segmentZ:Number = Math.cos(currentHeightAngle)*radius;
+ for (radial = 0; radial < radialSegments; radial++) {
+ var currentRadialAngle:Number = radialAngle*radial;
+ createVertex(-Math.sin(currentRadialAngle)*segmentRadius, Math.cos(currentRadialAngle)*segmentRadius, segmentZ, radial + "_" + segment);
+ }
+ }
+
+ // Создание граней и поверхности
+ var surface:Surface = createSurface();
+
+ var prevRadial:uint = radialSegments - 1;
+ var lastSegmentString:String = "_" + (heightSegments - 1);
+
+ var uStep:Number = 1/radialSegments;
+ var vStep:Number = 1/heightSegments;
+
+ var face:Face;
+
+ // Для триангуляции
+ var aUV:Point;
+ var cUV:Point;
+
+ var u:Number;
+
+ if (reverse) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ // Грани верхнего полюса
+ surface.addFace(createFace([poleUp, radial + "_1", prevRadial + "_1"], prevRadial + "_0"));
+ // Грани нижнего полюса
+ surface.addFace(createFace([radial + lastSegmentString, poleDown, prevRadial + lastSegmentString], prevRadial + lastSegmentString));
+
+ // Если включена триангуляция
+ if (triangulate) {
+ // Триангулируем середину и просчитываем маппинг
+ u = uStep*prevRadial;
+ setUVsToFace(new Point(1 - u, 1), new Point(1 - u - uStep, 1 - vStep), new Point(1 - u, 1 - vStep), prevRadial + "_0");
+ // Грани середки
+ for (segment = 1; segment < (heightSegments - 1); segment++) {
+ aUV = new Point(1 - u - uStep, 1 - (vStep*(segment + 1)));
+ cUV = new Point(1 - u, 1 - vStep*segment);
+ surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":0"));
+ surface.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1"));
+ setUVsToFace(aUV, new Point(1 - u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0");
+ setUVsToFace(cUV, new Point(1 - u - uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1");
+ }
+ setUVsToFace(new Point(1 - u - uStep, vStep), new Point(1 - u, 0), new Point(1 - u, vStep), prevRadial + lastSegmentString);
+
+ } else {
+ // Просто создаем середину
+ // Грани середки
+ for (segment = 1; segment < (heightSegments - 1); segment++) {
+ surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment, radial + "_" + segment], prevRadial + "_" + segment));
+ }
+ }
+ prevRadial = (radial == 0) ? 0 : prevRadial + 1;
+ }
+ } else {
+ for (radial = 0; radial < radialSegments; radial++) {
+ // Грани верхнего полюса
+ surface.addFace(createFace([poleUp, prevRadial + "_1", radial + "_1"], prevRadial + "_0"));
+ // Грани нижнего полюса
+ surface.addFace(createFace([prevRadial + lastSegmentString, poleDown, radial + lastSegmentString], prevRadial + lastSegmentString));
+
+ if (triangulate) {
+ u = uStep*prevRadial;
+ setUVsToFace(new Point(u, 1), new Point(u, 1 - vStep), new Point(u + uStep, 1 - vStep), prevRadial + "_0");
+ // Грани середки
+ for (segment = 1; segment < (heightSegments - 1); segment++) {
+ aUV = new Point(u, 1 - (vStep*segment));
+ cUV = new Point(u + uStep, 1 - vStep * (segment + 1));
+ surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0"));
+ surface.addFace(createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":1"));
+ setUVsToFace(aUV, new Point(u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0");
+ setUVsToFace(cUV, new Point(u + uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1");
+ }
+ setUVsToFace(new Point(u, vStep), new Point(u, 0), new Point(u + uStep, vStep), prevRadial + lastSegmentString);
+ } else {
+ // Грани середки
+ for (segment = 1; segment < (heightSegments - 1); segment++) {
+ surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment));
+ }
+ }
+ prevRadial = (radial == 0) ? 0 : prevRadial + 1;
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Sphere(0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.4/alternativa/utils/MeshUtils.as b/Alternativa3D5/5.4/alternativa/utils/MeshUtils.as
new file mode 100644
index 0000000..5def643
--- /dev/null
+++ b/Alternativa3D5/5.4/alternativa/utils/MeshUtils.as
@@ -0,0 +1,834 @@
+package alternativa.utils {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.engine3d.materials.TextureMaterialPrecision;
+ import alternativa.engine3d.materials.WireMaterial;
+ import alternativa.types.*;
+
+ import flash.display.BlendMode;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+ use namespace alternativatypes;
+
+ /**
+ * Утилиты для работы с Mesh-объектами.
+ */
+ public class MeshUtils {
+
+ static private var verticesSort:Array = ["x", "y", "z"];
+ static private var verticesSortOptions:Array = [Array.NUMERIC, Array.NUMERIC, Array.NUMERIC];
+
+ /**
+ * Объединение нескольких Mesh-объектов. Объекты, переданные как аргументы метода, не изменяются.
+ *
+ * @param meshes объединяемые объекты класса alternativa.engine3d.core.Mesh
+ *
+ * @return новый Mesh-объект, содержащий результат объединения переданных Mesh-объектов
+ */
+ static public function uniteMeshes(... meshes):Mesh {
+ var res:Mesh = new Mesh();
+
+ var length:uint = meshes.length;
+ var key:*;
+ var vertex:Vertex;
+ var face:Face;
+ var j:uint;
+ for (var i:uint = 0; i < length; i++) {
+ var mesh:Mesh = meshes[i];
+ var vertices:Map = mesh._vertices.clone();
+ for (key in vertices) {
+ vertex = vertices[key];
+ vertices[key] = res.createVertex(vertex.x, vertex.y, vertex.z);
+ }
+ var faces:Map = mesh._faces.clone();
+ for (key in faces) {
+ face = faces[key];
+ var faceVertices:Array = new Array().concat(face._vertices);
+ for (j = 0; j < face._verticesCount; j++) {
+ vertex = faceVertices[j];
+ faceVertices[j] = vertices[vertex.id];
+ }
+ faces[key] = res.createFace(faceVertices);
+ res.setUVsToFace(face._aUV, face._bUV, face._cUV, faces[key]);
+ }
+ for (key in mesh._surfaces) {
+ var surface:Surface = mesh._surfaces[key];
+ var surfaceFaces:Array = surface._faces.toArray();
+ var numFaces:uint = surfaceFaces.length;
+ for (j = 0; j < numFaces; j++) {
+ face = surfaceFaces[j];
+ surfaceFaces[j] = faces[face.id];
+ }
+ var newSurface:Surface = res.createSurface(surfaceFaces);
+ newSurface.material = SurfaceMaterial(surface.material.clone());
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Слияние вершин Mesh-объекта с одинаковыми координатами. Равенство координат проверяется с учётом погрешности.
+ *
+ * @param mesh объект, вершины которого объединяются
+ * @param threshold погрешность измерения расстояний
+ */
+ static public function autoWeldVertices(mesh:Mesh, threshold:Number = 0):void {
+ // Получаем список вершин меша и сортируем по координатам
+ var vertices:Array = mesh._vertices.toArray(true);
+ vertices.sortOn(verticesSort, verticesSortOptions);
+
+ // Поиск вершин с одинаковыми координатами
+ var weld:Map = new Map(true);
+ var vertex:Vertex;
+ var currentVertex:Vertex = vertices[0];
+ var length:uint = vertices.length;
+ var i:uint;
+ for (i = 1; i < length; i++) {
+ vertex = vertices[i];
+ if ((currentVertex.x - vertex.x <= threshold) && (currentVertex.x - vertex.x >= -threshold) && (currentVertex.y - vertex.y <= threshold) && (currentVertex.y - vertex.y >= -threshold) && (currentVertex.z - vertex.z <= threshold) && (currentVertex.z - vertex.z >= -threshold)) {
+ weld[vertex] = currentVertex;
+ } else {
+ currentVertex = vertex;
+ }
+ }
+
+ // Собираем грани объединяемых вершин
+ var faces:Set = new Set(true);
+ var keyVertex:*;
+ var keyFace:*;
+ for (keyVertex in weld) {
+ vertex = keyVertex;
+ for (keyFace in vertex._faces) {
+ faces[keyFace] = true;
+ }
+ }
+
+ // Заменяем грани
+ for (keyFace in faces) {
+ var face:Face = keyFace;
+ var id:Object = mesh.getFaceId(face);
+ var surface:Surface = face._surface;
+ var aUV:Point = face._aUV;
+ var bUV:Point = face._bUV;
+ var cUV:Point = face._cUV;
+ vertices = new Array().concat(face._vertices);
+ length = vertices.length;
+ for (i = 0; i < length; i++) {
+ vertex = weld[vertices[i]];
+ if (vertex != null) {
+ vertices[i] = vertex;
+ }
+ }
+ mesh.removeFace(face);
+ face = mesh.createFace(vertices, id);
+ if (surface != null) {
+ surface.addFace(face);
+ }
+ face.aUV = aUV;
+ face.bUV = bUV;
+ face.cUV = cUV;
+ }
+
+ // Удаляем вершины
+ for (keyVertex in weld) {
+ mesh.removeVertex(keyVertex);
+ }
+ }
+
+ /**
+ * Объединение соседних граней, образующих плоский выпуклый многоугольник.
+ *
+ * @param mesh объект, грани которого объединяются
+ * @param angleThreshold погрешность измерения углов
+ * @param uvThreshold погрешность измерения UV-координат
+ */
+ static public function autoWeldFaces(mesh:Mesh, angleThreshold:Number = 0, uvThreshold:Number = 0):void {
+ angleThreshold = Math.cos(angleThreshold);
+
+ var face:Face;
+ var sibling:Face;
+ var key:*;
+ var i:uint;
+
+ // Формируем списки граней
+ var faces1:Set = new Set(true);
+ var faces2:Set = new Set(true);
+
+ // Формируем список нормалей
+ var normals:Map = new Map(true);
+ for each (face in mesh._faces.clone()) {
+ var faceNormal:Point3D = face.normal;
+ if (faceNormal.x != 0 || faceNormal.y != 0 || faceNormal.z != 0) {
+ faces1[face] = true;
+ normals[face] = faceNormal;
+ } else {
+ mesh.removeFace(face);
+ }
+ }
+
+ // Объединение
+ do {
+ // Флаг объединения
+ var weld:Boolean = false;
+ // Объединяем грани
+ while ((face = faces1.take()) != null) {
+ //var num:uint = face.num;
+ //var vertices:Array = face.vertices;
+ var currentWeld:Boolean = false;
+
+ // Проверка общих граней по точкам
+
+
+ // Проверка общих граней по рёбрам
+
+ // Перебираем точки грани
+ for (i = 0; (i < face._verticesCount) && !currentWeld; i++) {
+ var faceIndex1:uint = i;
+ var faceIndex2:uint;
+ var siblingIndex1:int;
+ var siblingIndex2:uint;
+
+ // Перебираем грани текущей точки
+ var vertex:Vertex = face._vertices[faceIndex1];
+ var vertexFaces:Set = vertex.faces;
+ for (key in vertexFaces) {
+ sibling = key;
+ // Если грань в списке на объединение и в одной поверхности
+ if (faces1[sibling] && face._surface == sibling._surface) {
+ faceIndex2 = (faceIndex1 < face._verticesCount - 1) ? (faceIndex1 + 1) : 0;
+ siblingIndex1 = sibling._vertices.indexOf(face._vertices[faceIndex2]);
+ // Если общее ребро
+ if (siblingIndex1 >= 0) {
+ // Если грани сонаправлены
+ var normal:Point3D = normals[face];
+ if (Point3D.dot(normal, normals[sibling]) >= angleThreshold) {
+ // Если в точках объединения нет перегибов
+ siblingIndex2 = (siblingIndex1 < sibling._verticesCount - 1) ? (siblingIndex1 + 1) : 0;
+
+ // Расширяем грани объединения
+ var i1:uint;
+ var i2:uint;
+ while (true) {
+ i1 = (faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1);
+ i2 = (siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0;
+ if (face._vertices[i1] == sibling._vertices[i2]) {
+ faceIndex1 = i1;
+ siblingIndex2 = i2;
+ } else {
+ break;
+ }
+ }
+
+ while (true) {
+ i1 = (faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0;
+ i2 = (siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1);
+ if (face._vertices[i1] == sibling._vertices[i2]) {
+ faceIndex2 = i1;
+ siblingIndex1 = i2;
+ } else {
+ break;
+ }
+ }
+
+ vertex = face._vertices[faceIndex1];
+ var a:Point3D = vertex.coords;
+ vertex = face._vertices[faceIndex2];
+ var b:Point3D = vertex.coords;
+
+ // Считаем первый перегиб
+ vertex = sibling._vertices[(siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0];
+ var c:Point3D = vertex.coords;
+ vertex = face._vertices[(faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1)];
+ var d:Point3D = vertex.coords;
+
+ var cx:Number = c.x - a.x;
+ var cy:Number = c.y - a.y;
+ var cz:Number = c.z - a.z;
+ var dx:Number = d.x - a.x;
+ var dy:Number = d.y - a.y;
+ var dz:Number = d.z - a.z;
+
+ var crossX:Number = cy*dz - cz*dy;
+ var crossY:Number = cz*dx - cx*dz;
+ var crossZ:Number = cx*dy - cy*dx;
+
+ if (crossX == 0 && crossY == 0 && crossZ == 0) {
+ if (cx*dx + cy*dy + cz*dz > 0) {
+ break;
+ }
+ }
+
+ var dot:Number = crossX*normal.x + crossY*normal.y + crossZ*normal.z;
+
+ // Если в первой точке перегиба нет
+ if (dot >= 0) {
+
+ // Считаем второй перегиб
+ vertex = face._vertices[(faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0];
+ c = vertex.coords;
+ vertex = sibling._vertices[(siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1)];
+ d = vertex.coords;
+
+ cx = c.x - b.x;
+ cy = c.y - b.y;
+ cz = c.z - b.z;
+ dx = d.x - b.x;
+ dy = d.y - b.y;
+ dz = d.z - b.z;
+
+ crossX = cy*dz - cz*dy;
+ crossY = cz*dx - cx*dz;
+ crossZ = cx*dy - cy*dx;
+
+ if (crossX == 0 && crossY == 0 && crossZ == 0) {
+ if (cx*dx + cy*dy + cz*dz > 0) {
+ break;
+ }
+ }
+
+ dot = crossX*normal.x + crossY*normal.y + crossZ*normal.z;
+
+ // Если во второй точке перегиба нет
+ if (dot >= 0) {
+
+ // Флаг наличия UV у обеих граней
+ var hasUV:Boolean = (face._aUV != null && face._bUV != null && face._cUV != null && sibling._aUV != null && sibling._bUV != null && sibling._cUV != null);
+
+ if (hasUV || (face._aUV == null && face._bUV == null && face._cUV == null && sibling._aUV == null && sibling._bUV == null && sibling._cUV == null)) {
+
+ // Если грани имеют UV, проверяем совместимость
+ if (hasUV) {
+ vertex = sibling._vertices[0];
+ var uv:Point = face.getUVFast(vertex.coords, normal);
+ if ((uv.x - sibling._aUV.x > uvThreshold) || (uv.x - sibling._aUV.x < -uvThreshold) || (uv.y - sibling._aUV.y > uvThreshold) || (uv.y - sibling._aUV.y < -uvThreshold)) {
+ break;
+ }
+
+ vertex = sibling._vertices[1];
+ uv = face.getUVFast(vertex.coords, normal);
+ if ((uv.x - sibling._bUV.x > uvThreshold) || (uv.x - sibling._bUV.x < -uvThreshold) || (uv.y - sibling._bUV.y > uvThreshold) || (uv.y - sibling._bUV.y < -uvThreshold)) {
+ break;
+ }
+
+ vertex = sibling._vertices[2];
+ uv = face.getUVFast(vertex.coords, normal);
+ if ((uv.x - sibling._cUV.x > uvThreshold) || (uv.x - sibling._cUV.x < -uvThreshold) || (uv.y - sibling._cUV.y > uvThreshold) || (uv.y - sibling._cUV.y < -uvThreshold)) {
+ break;
+ }
+ }
+
+ // Формируем новую грань
+ var newVertices:Array = new Array();
+ var n:uint = faceIndex2;
+ do {
+ newVertices.push(face._vertices[n]);
+ n = (n < face._verticesCount - 1) ? (n + 1) : 0;
+ } while (n != faceIndex1);
+ n = siblingIndex2;
+ do {
+ newVertices.push(sibling._vertices[n]);
+ n = (n < sibling._verticesCount - 1) ? (n + 1) : 0;
+ } while (n != siblingIndex1);
+
+ // Выбираем начальную точку
+ n = getBestBeginVertexIndex(newVertices);
+ for (var m:uint = 0; m < n; m++) {
+ newVertices.push(newVertices.shift());
+ }
+
+ // Заменяем грани новой
+ var surface:Surface = face._surface;
+ var newFace:Face = mesh.createFace(newVertices);
+ if (hasUV) {
+ newFace.aUV = face.getUVFast(newVertices[0].coords, normal);
+ newFace.bUV = face.getUVFast(newVertices[1].coords, normal);
+ newFace.cUV = face.getUVFast(newVertices[2].coords, normal);
+ }
+ if (surface != null) {
+ surface.addFace(newFace);
+ }
+ mesh.removeFace(face);
+ mesh.removeFace(sibling);
+
+ // Обновляем список нормалей
+ delete normals[sibling];
+ delete normals[face];
+ normals[newFace] = newFace.normal;
+
+ // Обновляем списки расчётов
+ delete faces1[sibling];
+ faces2[newFace] = true;
+
+ // Помечаем объединение
+ weld = true;
+ currentWeld = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+
+ // Если не удалось объединить, переносим грань
+ faces2[face] = true;
+ }
+
+ // Меняем списки
+ var fs:Set = faces1;
+ faces1 = faces2;
+ faces2 = fs;
+ } while (weld);
+
+ removeIsolatedVertices(mesh);
+ removeUselessVertices(mesh);
+ }
+
+ /**
+ * Удаление вершин объекта, не принадлежащим ни одной грани.
+ *
+ * @param mesh объект, вершины которого удаляются
+ */
+ static public function removeIsolatedVertices(mesh:Mesh):void {
+ for each (var vertex:Vertex in mesh._vertices.clone()) {
+ if (vertex._faces.isEmpty()) {
+ mesh.removeVertex(vertex);
+ }
+ }
+ }
+
+ /**
+ * Удаление вершин объекта, которые во всех своих гранях лежат на отрезке между предыдущей и следующей вершиной.
+ *
+ * @param mesh объект, вершины которого удаляются
+ */
+ static public function removeUselessVertices(mesh:Mesh):void {
+ var v:Vertex;
+ var key:*;
+ var face:Face;
+ var index:uint;
+ var length:uint;
+ for each (var vertex:Vertex in mesh._vertices.clone()) {
+ var unless:Boolean = true;
+ var indexes:Map = new Map(true);
+ for (key in vertex._faces) {
+ face = key;
+ length = face._vertices.length;
+ index = face._vertices.indexOf(vertex);
+ v = face._vertices[index];
+ var a:Point3D = v.coords;
+ v = face._vertices[(index < length - 1) ? (index + 1) : 0];
+ var b:Point3D = v.coords;
+ v = face._vertices[(index > 0) ? (index - 1) : (length - 1)];
+ var c:Point3D = v.coords;
+ var abx:Number = b.x - a.x;
+ var aby:Number = b.y - a.y;
+ var abz:Number = b.z - a.z;
+ var acx:Number = c.x - a.x;
+ var acy:Number = c.y - a.y;
+ var acz:Number = c.z - a.z;
+ if (aby*acz - abz*acy == 0 && abz*acx - abx*acz == 0 && abx*acy - aby*acx == 0) {
+ indexes[face] = index;
+ } else {
+ unless = false;
+ break;
+ }
+ }
+ if (unless && !indexes.isEmpty()) {
+ // Удаляем
+ for (key in indexes) {
+ var i:uint;
+ face = key;
+ index = indexes[face];
+ length = face._vertices.length;
+ var newVertices:Array = new Array();
+ for (i = 0; i < length; i++) {
+ if (i != index) {
+ newVertices.push(face._vertices[i]);
+ }
+ }
+ var n:uint = getBestBeginVertexIndex(newVertices);
+ for (i = 0; i < n; i++) {
+ newVertices.push(newVertices.shift());
+ }
+ var surface:Surface = face._surface;
+ var newFace:Face = mesh.createFace(newVertices);
+ if (face._aUV != null && face._bUV != null && face._cUV != null) {
+ var normal:Point3D = face.normal;
+ newFace.aUV = face.getUVFast(newVertices[0].coords, normal);
+ newFace.bUV = face.getUVFast(newVertices[1].coords, normal);
+ newFace.cUV = face.getUVFast(newVertices[2].coords, normal);
+ }
+ if (surface != null) {
+ surface.addFace(newFace);
+ }
+ mesh.removeFace(face);
+ }
+ mesh.removeVertex(vertex);
+ }
+ }
+ }
+
+ /**
+ * Удаление вырожденных граней.
+ *
+ * @param mesh объект, грани которого удаляются
+ */
+ static public function removeSingularFaces(mesh:Mesh):void {
+ for each (var face:Face in mesh._faces.clone()) {
+ var normal:Point3D = face.normal;
+ if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
+ mesh.removeFace(face);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Находит наиболее подходящую первую точку.
+ * @param vertices
+ * @return
+ */
+ static public function getBestBeginVertexIndex(vertices:Array):uint {
+ var bestIndex:uint = 0;
+ var num:uint = vertices.length;
+ if (num > 3) {
+ var maxCrossLength:Number = 0;
+ var v:Vertex = vertices[num - 1];
+ var c1:Point3D = v.coords;
+ v = vertices[0];
+ var c2:Point3D = v.coords;
+
+ var prevX:Number = c2.x - c1.x;
+ var prevY:Number = c2.y - c1.y;
+ var prevZ:Number = c2.z - c1.z;
+
+ for (var i:uint = 0; i < num; i++) {
+ c1 = c2;
+ v = vertices[(i < num - 1) ? (i + 1) : 0];
+ c2 = v.coords;
+
+ var nextX:Number = c2.x - c1.x;
+ var nextY:Number = c2.y - c1.y;
+ var nextZ:Number = c2.z - c1.z;
+
+ var crossX:Number = prevY*nextZ - prevZ*nextY;
+ var crossY:Number = prevZ*nextX - prevX*nextZ;
+ var crossZ:Number = prevX*nextY - prevY*nextX;
+
+
+ var crossLength:Number = crossX*crossX + crossY*crossY + crossZ*crossZ;
+ if (crossLength > maxCrossLength) {
+ maxCrossLength = crossLength;
+ bestIndex = i;
+ }
+
+ prevX = nextX;
+ prevY = nextY;
+ prevZ = nextZ;
+ }
+ // Берём предыдущий
+ bestIndex = (bestIndex > 0) ? (bestIndex - 1) : (num - 1);
+ }
+
+ return bestIndex;
+ }
+
+ /**
+ * Генерация AS-класса.
+ *
+ * @param mesh объект, на базе которого генерируется класс
+ * @param packageName имя пакета для генерируемого класса
+ * @return AS-класс в текстовом виде
+ */
+ static public function generateClass(mesh:Mesh, packageName:String = ""):String {
+
+ var className:String = mesh._name.charAt(0).toUpperCase() + mesh._name.substr(1);
+
+ var header:String = "package" + ((packageName != "") ? (" " + packageName + " ") : " ") + "{\r\r";
+
+ var importSet:Object = new Object();
+ importSet["alternativa.engine3d.core.Mesh"] = true;
+
+ var materialSet:Map = new Map(true);
+ var materialName:String;
+ var materialNum:uint = 1;
+
+ var footer:String = "\t\t}\r\t}\r}";
+
+ var classHeader:String = "\tpublic class "+ className + " extends Mesh {\r\r";
+
+ var constructor:String = "\t\tpublic function " + className + "() {\r";
+ constructor += "\t\t\tsuper(\"" + mesh._name +"\");\r\r";
+
+
+ var newLine:Boolean = false;
+ if (mesh.mobility != 0) {
+ constructor += "\t\t\tmobility = " + mesh.mobility +";\r";
+ newLine = true;
+ }
+
+ if (mesh.x != 0 && mesh.y != 0 && mesh.z != 0) {
+ importSet["alternativa.types.Point3D"] = true;
+ constructor += "\t\t\tcoords = new Point3D(" + mesh.x + ", " + mesh.y + ", " + mesh.z +");\r";
+ newLine = true;
+ } else {
+ if (mesh.x != 0) {
+ constructor += "\t\t\tx = " + mesh.x + ";\r";
+ newLine = true;
+ }
+ if (mesh.y != 0) {
+ constructor += "\t\t\ty = " + mesh.y + ";\r";
+ newLine = true;
+ }
+ if (mesh.z != 0) {
+ constructor += "\t\t\tz = " + mesh.z + ";\r";
+ newLine = true;
+ }
+ }
+ if (mesh.rotationX != 0) {
+ constructor += "\t\t\trotationX = " + mesh.rotationX + ";\r";
+ newLine = true;
+ }
+ if (mesh.rotationY != 0) {
+ constructor += "\t\t\trotationY = " + mesh.rotationY + ";\r";
+ newLine = true;
+ }
+ if (mesh.rotationZ != 0) {
+ constructor += "\t\t\trotationZ = " + mesh.rotationZ + ";\r";
+ newLine = true;
+ }
+ if (mesh.scaleX != 1) {
+ constructor += "\t\t\tscaleX = " + mesh.scaleX + ";\r";
+ newLine = true;
+ }
+ if (mesh.scaleY != 1) {
+ constructor += "\t\t\tscaleY = " + mesh.scaleY + ";\r";
+ newLine = true;
+ }
+ if (mesh.scaleZ != 1) {
+ constructor += "\t\t\tscaleZ = " + mesh.scaleZ + ";\r";
+ newLine = true;
+ }
+
+ constructor += newLine ? "\r" : "";
+
+ function idToString(value:*):String {
+ return isNaN(value) ? ("\"" + value + "\"") : value;
+ }
+
+ function blendModeToString(value:String):String {
+ switch (value) {
+ case BlendMode.ADD: return "BlendMode.ADD";
+ case BlendMode.ALPHA: return "BlendMode.ALPHA";
+ case BlendMode.DARKEN: return "BlendMode.DARKEN";
+ case BlendMode.DIFFERENCE: return "BlendMode.DIFFERENCE";
+ case BlendMode.ERASE: return "BlendMode.ERASE";
+ case BlendMode.HARDLIGHT: return "BlendMode.HARDLIGHT";
+ case BlendMode.INVERT: return "BlendMode.INVERT";
+ case BlendMode.LAYER: return "BlendMode.LAYER";
+ case BlendMode.LIGHTEN: return "BlendMode.LIGHTEN";
+ case BlendMode.MULTIPLY: return "BlendMode.MULTIPLY";
+ case BlendMode.NORMAL: return "BlendMode.NORMAL";
+ case BlendMode.OVERLAY: return "BlendMode.OVERLAY";
+ case BlendMode.SCREEN: return "BlendMode.SCREEN";
+ case BlendMode.SUBTRACT: return "BlendMode.SUBTRACT";
+ default: return "BlendMode.NORMAL";
+ }
+ }
+
+ function colorToString(value:uint):String {
+ var hex:String = value.toString(16).toUpperCase();
+ var res:String = "0x";
+ var len:uint = 6 - hex.length;
+ for (var j:uint = 0; j < len; j++) {
+ res += "0";
+ }
+ res += hex;
+ return res;
+ }
+
+ var i:uint;
+ var length:uint;
+ var key:*;
+ var id:String;
+ var face:Face;
+ var surface:Surface;
+
+ newLine = false;
+ for (id in mesh._vertices) {
+ var vertex:Vertex = mesh._vertices[id];
+ var coords:Point3D = vertex.coords;
+ constructor += "\t\t\tcreateVertex(" + coords.x + ", " + coords.y + ", " + coords.z + ", " + idToString(id) + ");\r";
+ newLine = true;
+ }
+
+ constructor += newLine ? "\r" : "";
+
+ newLine = false;
+ for (id in mesh._faces) {
+ face = mesh._faces[id];
+ length = face._verticesCount;
+ constructor += "\t\t\tcreateFace(["
+ for (i = 0; i < length - 1; i++) {
+ constructor += idToString(mesh.getVertexId(face._vertices[i])) + ", ";
+ }
+ constructor += idToString(mesh.getVertexId(face._vertices[i])) + "], " + idToString(id) + ");\r";
+
+ if (face._aUV != null || face._bUV != null || face._cUV != null) {
+ importSet["flash.geom.Point"] = true;
+ constructor += "\t\t\tsetUVsToFace(new Point(" + face._aUV.x + ", " + face._aUV.y + "), new Point(" + face._bUV.x + ", " + face._bUV.y + "), new Point(" + face._cUV.x + ", " + face._cUV.y + "), " + idToString(id) + ");\r";
+ }
+ newLine = true;
+ }
+
+ constructor += newLine ? "\r" : "";
+
+ for (id in mesh._surfaces) {
+ surface = mesh._surfaces[id];
+ var facesStr:String = "";
+ for (key in surface._faces) {
+ facesStr += idToString(mesh.getFaceId(key)) + ", ";
+ }
+ constructor += "\t\t\tcreateSurface([" + facesStr.substr(0, facesStr.length - 2) + "], " + idToString(id) + ");\r";
+
+ if (surface.material != null) {
+ var material:String;
+ var defaultAlpha:Boolean = surface.material.alpha == 1;
+ var defaultBlendMode:Boolean = surface.material.blendMode == BlendMode.NORMAL;
+ if (surface.material is WireMaterial) {
+ importSet["alternativa.engine3d.materials.WireMaterial"] = true;
+ var defaultThickness:Boolean = WireMaterial(surface.material).thickness == 0;
+ var defaultColor:Boolean = WireMaterial(surface.material).color == 0;
+ material = "new WireMaterial(";
+ if (!defaultThickness || !defaultColor || !defaultAlpha || !defaultBlendMode) {
+ material += WireMaterial(surface.material).thickness;
+ if (!defaultColor || !defaultAlpha || !defaultBlendMode) {
+ material += ", " + colorToString(WireMaterial(surface.material).color);
+ if (!defaultAlpha || !defaultBlendMode) {
+ material += ", " + surface.material.alpha ;
+ if (!defaultBlendMode) {
+ importSet["flash.display.BlendMode"] = true;
+ material += ", " + blendModeToString(surface.material.blendMode);
+ }
+ }
+ }
+ }
+ }
+ var defaultWireThickness:Boolean;
+ var defaultWireColor:Boolean;
+ if (surface.material is FillMaterial) {
+ importSet["alternativa.engine3d.materials.FillMaterial"] = true;
+ defaultWireThickness = FillMaterial(surface.material).wireThickness < 0;
+ defaultWireColor = FillMaterial(surface.material).wireColor == 0;
+ material = "new FillMaterial(" + colorToString(FillMaterial(surface.material).color);
+ if (!defaultAlpha || !defaultBlendMode || !defaultWireThickness || !defaultWireColor) {
+ material += ", " + surface.material.alpha;
+ if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor) {
+ importSet["flash.display.BlendMode"] = true;
+ material += ", " + blendModeToString(surface.material.blendMode);
+ if (!defaultWireThickness || !defaultWireColor) {
+ material += ", " + FillMaterial(surface.material).wireThickness;
+ if (!defaultWireColor) {
+ material += ", " + colorToString(FillMaterial(surface.material).wireColor);
+ }
+ }
+ }
+ }
+ }
+ if (surface.material is TextureMaterial) {
+ importSet["alternativa.engine3d.materials.TextureMaterial"] = true;
+ var defaultRepeat:Boolean = TextureMaterial(surface.material).repeat;
+ var defaultSmooth:Boolean = !TextureMaterial(surface.material).smooth;
+ defaultWireThickness = TextureMaterial(surface.material).wireThickness < 0;
+ defaultWireColor = TextureMaterial(surface.material).wireColor == 0;
+ var defaultPrecision:Boolean = TextureMaterial(surface.material).precision == TextureMaterialPrecision.MEDIUM;
+
+ if (TextureMaterial(surface.material).texture == null) {
+ materialName = "null";
+ } else {
+ importSet["alternativa.types.Texture"] = true;
+ if (materialSet[TextureMaterial(surface.material).texture] == undefined) {
+ materialName = (TextureMaterial(surface.material).texture._name != null) ? TextureMaterial(surface.material).texture._name : "texture" + materialNum++;
+ materialSet[TextureMaterial(surface.material).texture] = materialName;
+ } else {
+ materialName = materialSet[TextureMaterial(surface.material).texture];
+ }
+ materialName = materialName.split(".")[0];
+ }
+ material = "new TextureMaterial(" + materialName;
+ if (!defaultAlpha || !defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).alpha;
+ if (!defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).repeat;
+ if (!defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).smooth;
+ if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ importSet["flash.display.BlendMode"] = true;
+ material += ", " + blendModeToString(surface.material.blendMode);
+ if (!defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).wireThickness;
+ if (!defaultWireColor || !defaultPrecision) {
+ material += ", " + colorToString(TextureMaterial(surface.material).wireColor);
+ if (!defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).precision;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ constructor += "\t\t\tsetMaterialToSurface(" + material + "), " + idToString(id) + ");\r";
+ }
+ }
+
+ var imports:String = "";
+ newLine = false;
+
+ var importArray:Array = new Array();
+ for (key in importSet) {
+ importArray.push(key);
+ }
+ importArray.sort();
+
+ length = importArray.length;
+ for (i = 0; i < length; i++) {
+ var pack:String = importArray[i];
+ var current:String = pack.substr(0, pack.indexOf("."));
+ imports += (current != prev && prev != null) ? "\r" : "";
+ imports += "\timport " + pack + ";\r";
+ var prev:String = current;
+ newLine = true;
+ }
+ imports += newLine ? "\r" : "";
+
+ var embeds:String = "";
+ newLine = false;
+ for each (materialName in materialSet) {
+ var materialClassName:String = materialName.split(".")[0];
+ var materialBmpName:String = materialClassName.charAt(0).toUpperCase() + materialClassName.substr(1);
+ embeds += "\t\t[Embed(source=\"" + materialName + "\")] private static const bmp" + materialBmpName + ":Class;\r";
+ embeds += "\t\tprivate static const " + materialClassName + ":Texture = new Texture(new bmp" + materialBmpName + "().bitmapData, \"" + materialName + "\");\r";
+ newLine = true;
+ }
+ embeds += newLine ? "\r" : "";
+
+ return header + imports + classHeader + embeds + constructor + footer;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/Alternativa3D.as b/Alternativa3D5/5.5/alternativa/Alternativa3D.as
new file mode 100644
index 0000000..a8d2ba3
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/Alternativa3D.as
@@ -0,0 +1,14 @@
+package alternativa {
+
+ /**
+ * Класс содержит информацию о версии библиотеки.
+ * Также используется для интеграции библиотеки в среду разработки Adobe Flash.
+ */
+ public class Alternativa3D {
+
+ /**
+ * Версия библиотеки в формате: поколение.feature-версия.fix-версия
+ */
+ public static const version:String = "5.5.0";
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/alternativa3d.as b/Alternativa3D5/5.5/alternativa/engine3d/alternativa3d.as
new file mode 100644
index 0000000..8bce40e
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/alternativa3d.as
@@ -0,0 +1,3 @@
+package alternativa.engine3d {
+ public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d";
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/controllers/CameraController.as b/Alternativa3D5/5.5/alternativa/engine3d/controllers/CameraController.as
new file mode 100644
index 0000000..44102e2
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/controllers/CameraController.as
@@ -0,0 +1,889 @@
+package alternativa.engine3d.controllers {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.physics.EllipsoidCollider;
+ import alternativa.types.Map;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+ import alternativa.utils.KeyboardUtils;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.DisplayObject;
+ import flash.events.KeyboardEvent;
+ import flash.events.MouseEvent;
+ import flash.geom.Point;
+ import flash.utils.getTimer;
+
+ use namespace alternativa3d;
+
+ /**
+ * Контроллер камеры. Контроллер обеспечивает управление движением и поворотами камеры с использованием
+ * клавиатуры и мыши, а также предоставляет простую проверку столкновений камеры с объектами сцены.
+ */
+ public class CameraController {
+ /**
+ * Имя действия для привязки клавиш движения вперёд.
+ */
+ public static const ACTION_FORWARD:String = "ACTION_FORWARD";
+ /**
+ * Имя действия для привязки клавиш движения назад.
+ */
+ public static const ACTION_BACK:String = "ACTION_BACK";
+ /**
+ * Имя действия для привязки клавиш движения влево.
+ */
+ public static const ACTION_LEFT:String = "ACTION_LEFT";
+ /**
+ * Имя действия для привязки клавиш движения вправо.
+ */
+ public static const ACTION_RIGHT:String = "ACTION_RIGHT";
+ /**
+ * Имя действия для привязки клавиш движения вверх.
+ */
+ public static const ACTION_UP:String = "ACTION_UP";
+ /**
+ * Имя действия для привязки клавиш движения вниз.
+ */
+ public static const ACTION_DOWN:String = "ACTION_DOWN";
+// public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT";
+// public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT";
+ /**
+ * Имя действия для привязки клавиш увеличения угла тангажа.
+ */
+ public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP";
+ /**
+ * Имя действия для привязки клавиш уменьшения угла тангажа.
+ */
+ public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN";
+ /**
+ * Имя действия для привязки клавиш уменьшения угла рысканья (поворота налево).
+ */
+ public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT";
+ /**
+ * Имя действия для привязки клавиш увеличения угла рысканья (поворота направо).
+ */
+ public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT";
+ /**
+ * Имя действия для привязки клавиш увеличения скорости.
+ */
+ public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE";
+
+ // флаги действий
+ private var _forward:Boolean;
+ private var _back:Boolean;
+ private var _left:Boolean;
+ private var _right:Boolean;
+ private var _up:Boolean;
+ private var _down:Boolean;
+ private var _pitchUp:Boolean;
+ private var _pitchDown:Boolean;
+ private var _yawLeft:Boolean;
+ private var _yawRight:Boolean;
+ private var _accelerate:Boolean;
+
+ private var _moveLocal:Boolean = true;
+
+ // Флаг включения управления камерой
+ private var _controlsEnabled:Boolean = false;
+
+ // Значение таймера в начале прошлого кадра
+ private var lastFrameTime:uint;
+
+ // Чувствительность обзора мышью. Коэффициент умножения базовых коэффициентов поворотов.
+ private var _mouseSensitivity:Number = 1;
+ // Коэффициент поворота камеры по тангажу. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
+ private var _mousePitch:Number = Math.PI / 360;
+ // Результирующий коэффициент поворота камеры мышью по тангажу
+ private var _mousePitchCoeff:Number = _mouseSensitivity * _mousePitch;
+ // Коэффициент поворота камеры по рысканью. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
+ private var _mouseYaw:Number = Math.PI / 360;
+ // Результирующий коэффициент поворота камеры мышью по расканью
+ private var _mouseYawCoeff:Number = _mouseSensitivity * _mouseYaw;
+
+ // Вспомогательные переменные для обзора мышью
+ private var mouseLookActive:Boolean;
+ private var startDragCoords:Point = new Point();
+ private var currentDragCoords:Point = new Point();
+ private var prevDragCoords:Point = new Point();
+ private var startRotX:Number;
+ private var startRotZ:Number;
+
+ // Скорость изменения тангажа в радианах за секунду при управлении с клавиатуры
+ private var _pitchSpeed:Number = 1;
+ // Скорость изменения рысканья в радианах за секунду при управлении с клавиатуры
+ private var _yawSpeed:Number = 1;
+ // Скорость изменения крена в радианах за секунду при управлении с клавиатуры
+// public var bankSpeed:Number = 2;
+// private var bankMatrix:Matrix3D = new Matrix3D();
+
+ // Скорость поступательного движения в единицах за секунду
+ private var _speed:Number = 100;
+ // Коэффициент увеличения скорости при соответствующей нажатой клавише
+ private var _speedMultiplier:Number = 2;
+
+ private var velocity:Point3D = new Point3D();
+ private var destination:Point3D = new Point3D();
+
+ private var _fovStep:Number = Math.PI / 180;
+ private var _zoomMultiplier:Number = 0.1;
+
+ // Привязка клавиш к действиям
+ private var keyBindings:Map = new Map();
+ // Привязка действий к обработчикам
+ private var actionBindings:Map = new Map();
+
+ // Источник событий клавиатуры и мыши
+ private var _eventsSource:DisplayObject;
+ // Управляемая камера
+ private var _camera:Camera3D;
+
+ // Класс реализации определния столкновений
+ private var _collider:EllipsoidCollider;
+ // Флаг необходимости проверки столкновений
+ private var _checkCollisions:Boolean;
+ // Радиус сферы для определения столкновений
+ private var _collisionRadius:Number = 0;
+ // Набор исключаемых из проверки столкновений объектов
+ private var _collisionIgnoreSet:Set = new Set(true);
+ // Флаг движения
+ private var _isMoving:Boolean;
+
+ private var _onStartMoving:Function;
+ private var _onStopMoving:Function;
+
+ /**
+ * Создание экземпляра контроллера.
+ *
+ * @param eventsSourceObject объект, используемый для получения событий мыши и клавиатуры
+ */
+ public function CameraController(eventsSourceObject:DisplayObject) {
+ if (eventsSourceObject == null) {
+ throw new ArgumentError("CameraController: eventsSource is null");
+ }
+ _eventsSource = eventsSourceObject;
+
+ actionBindings[ACTION_FORWARD] = forward;
+ actionBindings[ACTION_BACK] = back;
+ actionBindings[ACTION_LEFT] = left;
+ actionBindings[ACTION_RIGHT] = right;
+ actionBindings[ACTION_UP] = up;
+ actionBindings[ACTION_DOWN] = down;
+ actionBindings[ACTION_PITCH_UP] = pitchUp;
+ actionBindings[ACTION_PITCH_DOWN] = pitchDown;
+ actionBindings[ACTION_YAW_LEFT] = yawLeft;
+ actionBindings[ACTION_YAW_RIGHT] = yawRight;
+ actionBindings[ACTION_ACCELERATE] = accelerate;
+ }
+
+ /**
+ * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
+ *
+ *
+ */
+ public function setDefaultBindings():void {
+ unbindAll();
+ bindKey(KeyboardUtils.W, ACTION_FORWARD);
+ bindKey(KeyboardUtils.S, ACTION_BACK);
+ bindKey(KeyboardUtils.A, ACTION_LEFT);
+ bindKey(KeyboardUtils.D, ACTION_RIGHT);
+ bindKey(KeyboardUtils.SPACE, ACTION_UP);
+ bindKey(KeyboardUtils.Z, ACTION_DOWN);
+ bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
+ bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
+ bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT);
+ bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT);
+ bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE);
+ }
+
+ /**
+ * Направление камеры на точку.
+ *
+ * @param point координаты точки направления камеры
+ */
+ public function lookAt(point:Point3D):void {
+ if (_camera == null) {
+ return;
+ }
+ var dx:Number = point.x - _camera.x;
+ var dy:Number = point.y - _camera.y;
+ var dz:Number = point.z - _camera.z;
+ _camera.rotationZ = -Math.atan2(dx, dy);
+ _camera.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - MathUtils.DEG90;
+ }
+
+ /**
+ * Callback-функция, вызываемая при начале движения камеры.
+ */
+ public function get onStartMoving():Function {
+ return _onStartMoving;
+ }
+
+ /**
+ * @private
+ */
+ public function set onStartMoving(value:Function):void {
+ _onStartMoving = value;
+ }
+
+ /**
+ * Callback-функция, вызываемая при прекращении движения камеры.
+ */
+ public function get onStopMoving():Function {
+ return _onStopMoving;
+ }
+
+ /**
+ * @private
+ */
+ public function set onStopMoving(value:Function):void {
+ _onStopMoving = value;
+ }
+
+ /**
+ * Набор объектов, исключаемых из проверки столкновений.
+ */
+ public function get collisionIgnoreSet():Set {
+ return _collisionIgnoreSet;
+ }
+
+ /**
+ * Источник событий клавиатуры и мыши.
+ */
+ public function get eventsSource():DisplayObject {
+ return _eventsSource;
+ }
+
+ /**
+ * @private
+ */
+ public function set eventsSource(value:DisplayObject):void {
+ if (_eventsSource != value) {
+ if (value == null) {
+ throw new ArgumentError("CameraController: eventsSource is null");
+ }
+ if (_controlsEnabled) {
+ unregisterEventsListeners();
+ }
+ _eventsSource = value;
+ if (_controlsEnabled) {
+ registerEventListeners();
+ }
+ }
+ }
+
+ /**
+ * Ассоциированная камера.
+ */
+ public function get camera():Camera3D {
+ return _camera;
+ }
+
+ /**
+ * @private
+ */
+ public function set camera(value:Camera3D):void {
+ if (_camera != value) {
+ _camera = value;
+ if (value == null) {
+ controlsEnabled = false;
+ } else {
+ createCollider();
+ }
+ }
+ }
+
+ /**
+ * Режим движения камеры. Если значение равно
+ * Клавиша Действие
+ * W ACTION_FORWARD
+ * S ACTION_BACK
+ * A ACTION_LEFT
+ * D ACTION_RIGHT
+ * SPACE ACTION_UP
+ * Z ACTION_DOWN
+ * SHIFT ACTION_ACCELERATE
+ * UP ACTION_PITCH_UP
+ * DOWN ACTION_PITCH_DOWN
+ * LEFT ACTION_YAW_LEFT
+ * RIGHT ACTION_YAW_RIGHT true, то перемещения камеры происходят относительно
+ * локальной системы координат, иначе относительно глобальной.
+ *
+ * @default true
+ */
+ public function get moveLocal():Boolean {
+ return _moveLocal;
+ }
+
+ /**
+ * @private
+ */
+ public function set moveLocal(value:Boolean):void {
+ _moveLocal = value;
+ }
+
+ /**
+ * Включение режима проверки столкновений.
+ */
+ public function get checkCollisions():Boolean {
+ return _checkCollisions;
+ }
+
+ /**
+ * @private
+ */
+ public function set checkCollisions(value:Boolean):void {
+ _checkCollisions = value;
+ }
+
+ /**
+ * Радиус сферы для определения столкновений.
+ *
+ * @default 0
+ */
+ public function get collisionRadius():Number {
+ return _collisionRadius;
+ }
+
+ /**
+ * @private
+ */
+ public function set collisionRadius(value:Number):void {
+ _collisionRadius = value;
+ if (_collider != null) {
+ _collider.radiusX = _collisionRadius;
+ _collider.radiusY = _collisionRadius;
+ _collider.radiusZ = _collisionRadius;
+ }
+ }
+
+ /**
+ * Привязка клавиши к действию.
+ *
+ * @param keyCode код клавиши
+ * @param action наименование действия
+ */
+ public function bindKey(keyCode:uint, action:String):void {
+ var method:Function = actionBindings[action];
+ if (method != null) {
+ keyBindings[keyCode] = method;
+ }
+ }
+
+ /**
+ * Очистка привязки клавиши.
+ *
+ * @param keyCode код клавиши
+ */
+ public function unbindKey(keyCode:uint):void {
+ keyBindings.remove(keyCode);
+ }
+
+ /**
+ * Очистка привязки всех клавиш.
+ */
+ public function unbindAll():void {
+ keyBindings.clear();
+ }
+
+ /**
+ * Активация движения камеры вперёд.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function forward(value:Boolean):void {
+ _forward = value;
+ }
+
+ /**
+ * Активация движения камеры назад.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function back(value:Boolean):void {
+ _back = value;
+ }
+
+ /**
+ * Активация движения камеры влево.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function left(value:Boolean):void {
+ _left = value;
+ }
+
+ /**
+ * Активация движения камеры вправо.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function right(value:Boolean):void {
+ _right = value;
+ }
+
+ /**
+ * Активация движения камеры вверх.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function up(value:Boolean):void {
+ _up = value;
+ }
+
+ /**
+ * Активация движения камеры вниз.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function down(value:Boolean):void {
+ _down = value;
+ }
+
+ /**
+ * Активация поворота камеры вверх.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function pitchUp(value:Boolean):void {
+ _pitchUp = value;
+ }
+
+ /**
+ * Активация поворота камеры вниз.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function pitchDown(value:Boolean):void {
+ _pitchDown = value;
+ }
+
+ /**
+ * Активация поворота камеры влево.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function yawLeft(value:Boolean):void {
+ _yawLeft = value;
+ }
+
+ /**
+ * Активация поворота камеры вправо.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function yawRight(value:Boolean):void {
+ _yawRight = value;
+ }
+
+
+ /**
+ * Активация режима увеличенной скорости.
+ *
+ * @param value true для включения ускорения, false для выключения
+ */
+ public function accelerate(value:Boolean):void {
+ _accelerate = value;
+ }
+
+ /**
+ * @private
+ */
+ private function createCollider():void {
+ _collider = new EllipsoidCollider(_camera.scene, _collisionRadius);
+ _collider.offsetThreshold = 0.01;
+ _collider.collisionSet = _collisionIgnoreSet;
+ }
+
+ /**
+ * Чувствительность мыши — коэффициент умножения mousePitch и mouseYaw.
+ *
+ * @default 1
+ *
+ * @see #mousePitch()
+ * @see #mouseYaw()
+ */
+ public function get mouseSensitivity():Number {
+ return _mouseSensitivity;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseSensitivity(sensitivity:Number):void {
+ _mouseSensitivity = sensitivity;
+ _mousePitchCoeff = _mouseSensitivity * _mousePitch;
+ _mouseYawCoeff = _mouseSensitivity * _mouseYaw;
+ }
+
+ /**
+ * Скорость изменения угла тангажа при управлении мышью (радианы на пиксель).
+ *
+ * @default Math.PI / 360
+ */
+ public function get mousePitch():Number {
+ return _mousePitch;
+ }
+
+ /**
+ * @private
+ */
+ public function set mousePitch(pitch:Number):void {
+ _mousePitch = pitch;
+ _mousePitchCoeff = _mouseSensitivity * _mousePitch;
+ }
+
+ /**
+ * Скорость изменения угла рысканья при управлении мышью (радианы на пиксель).
+ *
+ * @default Math.PI / 360
+ */
+ public function get mouseYaw():Number {
+ return _mouseYaw;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseYaw(yaw:Number):void {
+ _mouseYaw = yaw;
+ _mouseYawCoeff = _mouseSensitivity * _mouseYaw;
+ }
+
+ /**
+ * Угловая скорость по тангажу при управлении с клавиатуры (радианы в секунду).
+ *
+ * @default 1
+ */
+ public function get pitchSpeed():Number {
+ return _pitchSpeed;
+ }
+
+ /**
+ * @private
+ */
+ public function set pitchSpeed(spd:Number):void {
+ _pitchSpeed = spd;
+ }
+
+ /**
+ * Угловая скорость по рысканью при управлении с клавиатуры (радианы в секунду).
+ *
+ * @default 1
+ */
+ public function get yawSpeed():Number {
+ return _yawSpeed;
+ }
+
+ /**
+ * @private
+ */
+ public function set yawSpeed(spd:Number):void {
+ _yawSpeed = spd;
+ }
+
+ /**
+ * Скорость поступательного движения (единицы в секунду).
+ */
+ public function get speed():Number {
+ return _speed;
+ }
+
+ /**
+ * @private
+ */
+ public function set speed(spd:Number):void {
+ _speed = spd;
+ }
+
+ /**
+ * Коэффициент увеличения скорости при активном действии ACTION_ACCELERATE.
+ *
+ * @default 2
+ */
+ public function get speedMultiplier():Number {
+ return _speedMultiplier;
+ }
+
+ /**
+ * @private
+ */
+ public function set speedMultiplier(value:Number):void {
+ _speedMultiplier = value;
+ }
+
+ /**
+ * Активность управления камеры.
+ *
+ * @default false
+ */
+ public function get controlsEnabled():Boolean {
+ return _controlsEnabled;
+ }
+
+ /**
+ * @private
+ */
+ public function set controlsEnabled(value:Boolean):void {
+ if (_camera == null || _controlsEnabled == value) return;
+ if (value) {
+ lastFrameTime = getTimer();
+ registerEventListeners();
+ }
+ else {
+ unregisterEventsListeners();
+ }
+ _controlsEnabled = value;
+ }
+
+ /**
+ * Базовый шаг изменения угла зрения в радианах. Реальный шаг получаеся умножением этого значения на величину
+ * MouseEvent.delta.
+ *
+ * @default Math.PI / 180
+ */
+ public function get fovStep():Number {
+ return _fovStep;
+ }
+
+ /**
+ * @private
+ */
+ public function set fovStep(value:Number):void {
+ _fovStep = value;
+ }
+
+ /**
+ * Множитель при изменении коэффициента увеличения. Закон изменения коэффициента увеличения описывается формулой:
+ * zoom (1 + MouseEvent.delta zoomMultiplier).
+ *
+ * @default 0.1
+ */
+ public function get zoomMultiplier():Number {
+ return _zoomMultiplier;
+ }
+
+ /**
+ * @private
+ */
+ public function set zoomMultiplier(value:Number):void {
+ _zoomMultiplier = value;
+ }
+
+ /**
+ * @private
+ */
+ private function registerEventListeners():void {
+ _eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey);
+ _eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKey);
+ _eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
+ _eventsSource.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
+ }
+
+ /**
+ * @private
+ */
+ private function unregisterEventsListeners():void {
+ _eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey);
+ _eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKey);
+ _eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
+ _eventsSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ _eventsSource.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
+ }
+
+ /**
+ * @private
+ * @param e
+ */
+ private function onKey(e:KeyboardEvent):void {
+ var method:Function = keyBindings[e.keyCode];
+ if (method != null) {
+ method.call(this, e.type == KeyboardEvent.KEY_DOWN);
+ }
+ }
+
+ /**
+ * @private
+ * @param e
+ */
+ private function onMouseDown(e:MouseEvent):void {
+ mouseLookActive = true;
+ currentDragCoords.x = startDragCoords.x = _eventsSource.stage.mouseX;
+ currentDragCoords.y = startDragCoords.y = _eventsSource.stage.mouseY;
+ startRotX = _camera.rotationX;
+ startRotZ = _camera.rotationZ;
+ _eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * @private
+ * @param e
+ */
+ private function onMouseUp(e:MouseEvent):void {
+ mouseLookActive = false;
+ _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * @private
+ * @param e
+ */
+ private function onMouseWheel(e:MouseEvent):void {
+ if (_camera.orthographic) {
+ _camera.zoom = _camera.zoom * (1 + e.delta * _zoomMultiplier);
+ } else {
+ _camera.fov -= _fovStep * e.delta;
+ }
+ }
+
+ /**
+ * Обработка управляющих воздействий.
+ * Метод должен вызываться каждый кадр перед вызовом Scene3D.calculate().
+ *
+ * @see alternativa.engine3d.core.Scene3D#calculate()
+ */
+ public function processInput(): void {
+ if (!_controlsEnabled || _camera == null) return;
+
+ // Время в секундах от начала предыдущего кадра
+ var frameTime:Number = getTimer() - lastFrameTime;
+ lastFrameTime += frameTime;
+ frameTime /= 1000;
+
+ // Обработка mouselook
+ if (mouseLookActive) {
+ prevDragCoords.x = currentDragCoords.x;
+ prevDragCoords.y = currentDragCoords.y;
+ currentDragCoords.x = _eventsSource.stage.mouseX;
+ currentDragCoords.y = _eventsSource.stage.mouseY;
+ if (!prevDragCoords.equals(currentDragCoords)) {
+ _camera.rotationZ = startRotZ + (startDragCoords.x - currentDragCoords.x) * _mouseYawCoeff;
+ var rotX:Number = startRotX + (startDragCoords.y - currentDragCoords.y) * _mousePitchCoeff;
+ _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
+ }
+ }
+
+ // Поворот относительно вертикальной оси (рысканье, yaw)
+ if (_yawLeft) {
+ _camera.rotationZ += _yawSpeed * frameTime;
+ } else if (_yawRight) {
+ _camera.rotationZ -= _yawSpeed * frameTime;
+ }
+
+ // Поворот относительно поперечной оси (тангаж, pitch)
+ if (_pitchUp) {
+ rotX = _camera.rotationX + _pitchSpeed * frameTime;
+ _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
+ } else if (_pitchDown) {
+ rotX = _camera.rotationX - _pitchSpeed * frameTime;
+ _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
+ }
+
+ // TODO: Поворот относительно продольной оси (крен, roll)
+
+ var frameDistance:Number = _speed * frameTime;
+ if (_accelerate) {
+ frameDistance *= _speedMultiplier;
+ }
+ velocity.x = 0;
+ velocity.y = 0;
+ velocity.z = 0;
+ var transformation:Matrix3D = _camera._transformation;
+
+ if (_moveLocal) {
+ // Режим относительных пермещений
+ // Движение вперед-назад
+ if (_forward) {
+ velocity.x += frameDistance * transformation.c;
+ velocity.y += frameDistance * transformation.g;
+ velocity.z += frameDistance * transformation.k;
+ } else if (_back) {
+ velocity.x -= frameDistance * transformation.c;
+ velocity.y -= frameDistance * transformation.g;
+ velocity.z -= frameDistance * transformation.k;
+ }
+ // Движение влево-вправо
+ if (_left) {
+ velocity.x -= frameDistance * transformation.a;
+ velocity.y -= frameDistance * transformation.e;
+ velocity.z -= frameDistance * transformation.i;
+ } else if (_right) {
+ velocity.x += frameDistance * transformation.a;
+ velocity.y += frameDistance * transformation.e;
+ velocity.z += frameDistance * transformation.i;
+ }
+ // Движение вверх-вниз
+ if (_up) {
+ velocity.x -= frameDistance * transformation.b;
+ velocity.y -= frameDistance * transformation.f;
+ velocity.z -= frameDistance * transformation.j;
+ } else if (_down) {
+ velocity.x += frameDistance * transformation.b;
+ velocity.y += frameDistance * transformation.f;
+ velocity.z += frameDistance * transformation.j;
+ }
+ }
+ else {
+ // Режим глобальных перемещений
+ var cosZ:Number = Math.cos(_camera.rotationZ);
+ var sinZ:Number = Math.sin(_camera.rotationZ);
+ // Движение вперед-назад
+ if (_forward) {
+ velocity.x -= frameDistance * sinZ;
+ velocity.y += frameDistance * cosZ;
+ } else if (_back) {
+ velocity.x += frameDistance * sinZ;
+ velocity.y -= frameDistance * cosZ;
+ }
+ // Движение влево-вправо
+ if (_left) {
+ velocity.x -= frameDistance * cosZ;
+ velocity.y -= frameDistance * sinZ;
+ } else if (_right) {
+ velocity.x += frameDistance * cosZ;
+ velocity.y += frameDistance * sinZ;
+ }
+ // Движение вверх-вниз
+ if (_up) {
+ velocity.z += frameDistance;
+ } else if (_down) {
+ velocity.z -= frameDistance;
+ }
+ }
+
+ // Коррекция модуля вектора скорости
+ if (velocity.x != 0 || velocity.y != 0 || velocity.z != 0) {
+ velocity.length = frameDistance;
+ }
+
+ // Проверка столкновений
+ if (_checkCollisions) {
+ _collider.calculateDestination(_camera.coords, velocity, destination);
+ _camera.x = destination.x;
+ _camera.y = destination.y;
+ _camera.z = destination.z;
+ } else {
+ _camera.x += velocity.x;
+ _camera.y += velocity.y;
+ _camera.z += velocity.z;
+ }
+
+ // Обработка начала/окончания движения
+ if (_camera.changeRotationOrScaleOperation.queued || _camera.changeCoordsOperation.queued) {
+ if (!_isMoving) {
+ _isMoving = true;
+ if (_onStartMoving != null) {
+ _onStartMoving.call(this);
+ }
+ }
+ } else {
+ if (_isMoving) {
+ _isMoving = false;
+ if (_onStopMoving != null) {
+ _onStopMoving.call(this);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/controllers/FlyController.as b/Alternativa3D5/5.5/alternativa/engine3d/controllers/FlyController.as
new file mode 100644
index 0000000..3c325d6
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/controllers/FlyController.as
@@ -0,0 +1,469 @@
+package alternativa.engine3d.controllers {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.utils.KeyboardUtils;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.DisplayObject;
+
+ use namespace alternativa3d;
+
+ /**
+ * Контроллер, реализующий управление объектом, находящимся в корневом объекте сцены, подобно управлению летательным аппаратом.
+ * Повороты выполняются вокруг локальных осей объекта, собственные ускорения действуют вдоль локальных осей.
+ *
+ *
+ *
+ *
+ *
+ *
+ * Ось Направление Поворот
+ *
+ *
+ * X Вправо Тангаж
+ *
+ *
+ * Y Вперёд Крен
+ *
+ *
+ * Z Вверх Рысканье
+ *
+ *
+ *
+ *
+ * Ось Направление Поворот
+ *
+ *
+ * X Вправо Тангаж
+ *
+ *
+ * Y Вниз Рысканье
+ *
+ *
+ * Z Вперёд Крен
+ *
+ *
+ */
+ override public function setDefaultBindings():void {
+ unbindAll();
+ bindKey(KeyboardUtils.W, ACTION_FORWARD);
+ bindKey(KeyboardUtils.S, ACTION_BACK);
+ bindKey(KeyboardUtils.A, ACTION_LEFT);
+ bindKey(KeyboardUtils.D, ACTION_RIGHT);
+ bindKey(KeyboardUtils.SPACE, ACTION_UP);
+ bindKey(KeyboardUtils.Z, ACTION_DOWN);
+ bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
+ bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
+ bindKey(KeyboardUtils.LEFT, ACTION_ROLL_LEFT);
+ bindKey(KeyboardUtils.RIGHT, ACTION_ROLL_RIGHT);
+ bindKey(KeyboardUtils.Q, ACTION_YAW_LEFT);
+ bindKey(KeyboardUtils.E, ACTION_YAW_RIGHT);
+ bindKey(KeyboardUtils.M, ACTION_MOUSE_LOOK);
+ }
+
+ /**
+ * Метод выполняет поворот объекта относительно локальных осей в соответствии с имеющимися воздействиями.
+ *
+ * @param frameTime длительность текущего кадра в секундах
+ */
+ override protected function rotateObject(frameTime:Number):void {
+ var transformation:Matrix3D = _object._transformation;
+ if (_mouseLookActive) {
+ currentMouseCoords.x = _eventsSource.stage.mouseX;
+ currentMouseCoords.y = _eventsSource.stage.mouseY;
+ if (!currentMouseCoords.equals(startMouseCoords)) {
+ var deltaYaw:Number = (currentMouseCoords.x - startMouseCoords.x) * _mouseCoefficientX;
+ if (_object is Camera3D) {
+ axis.x = transformation.c;
+ axis.y = transformation.g;
+ axis.z = transformation.k;
+ } else {
+ axis.x = transformation.b;
+ axis.y = transformation.f;
+ axis.z = transformation.j;
+ }
+
+ rotateObjectAroundAxis(axis, deltaYaw * frameTime);
+
+ currentTransform.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1);
+ var deltaPitch:Number = (startMouseCoords.y - currentMouseCoords.y) * _mouseCoefficientY;
+ axis.x = currentTransform.a;
+ axis.y = currentTransform.e;
+ axis.z = currentTransform.i;
+
+ rotateObjectAroundAxis(axis, deltaPitch * frameTime);
+ }
+ }
+
+ // Поворот относительно продольной оси (крен, roll)
+ if (_rollLeft) {
+ if (_object is Camera3D) {
+ axis.x = transformation.c;
+ axis.y = transformation.g;
+ axis.z = transformation.k;
+ } else {
+ axis.x = transformation.b;
+ axis.y = transformation.f;
+ axis.z = transformation.j;
+ }
+ rotateObjectAroundAxis(axis, -_rollSpeed * frameTime);
+ } else if (_rollRight) {
+ if (_object is Camera3D) {
+ axis.x = transformation.c;
+ axis.y = transformation.g;
+ axis.z = transformation.k;
+ } else {
+ axis.x = transformation.b;
+ axis.y = transformation.f;
+ axis.z = transformation.j;
+ }
+ rotateObjectAroundAxis(axis, _rollSpeed * frameTime);
+ }
+
+ // Поворот относительно поперечной оси (тангаж, pitch)
+ if (_pitchUp) {
+ axis.x = transformation.a;
+ axis.y = transformation.e;
+ axis.z = transformation.i;
+ rotateObjectAroundAxis(axis, _pitchSpeed * frameTime);
+ } else if (_pitchDown) {
+ axis.x = transformation.a;
+ axis.y = transformation.e;
+ axis.z = transformation.i;
+ rotateObjectAroundAxis(axis, -_pitchSpeed * frameTime);
+ }
+
+ // Поворот относительно вертикальной оси (рысканье, yaw)
+ if (_yawRight) {
+ if (_object is Camera3D) {
+ axis.x = transformation.b;
+ axis.y = transformation.f;
+ axis.z = transformation.j;
+ rotateObjectAroundAxis(axis, _yawSpeed * frameTime);
+ } else {
+ axis.x = transformation.c;
+ axis.y = transformation.g;
+ axis.z = transformation.k;
+ rotateObjectAroundAxis(axis, -_yawSpeed * frameTime);
+ }
+ } else if (_yawLeft) {
+ if (_object is Camera3D) {
+ axis.x = transformation.b;
+ axis.y = transformation.f;
+ axis.z = transformation.j;
+ rotateObjectAroundAxis(axis, -_yawSpeed * frameTime);
+ } else {
+ axis.x = transformation.c;
+ axis.y = transformation.g;
+ axis.z = transformation.k;
+ rotateObjectAroundAxis(axis, _yawSpeed * frameTime);
+ }
+ }
+ }
+
+ /**
+ * Метод вычисляет вектор потенциального смещения эллипсоида.
+ *
+ * @param frameTime длительность текущего кадра в секундах
+ * @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта
+ */
+ override protected function getDisplacement(frameTime:Number, displacement:Point3D):void {
+ // Движение вперед-назад
+ accelerationVector.x = 0;
+ accelerationVector.y = 0;
+ accelerationVector.z = 0;
+ if (_forward) {
+ accelerationVector.y = 1;
+ } else if (_back) {
+ accelerationVector.y = -1;
+ }
+ // Движение влево-вправо
+ if (_right) {
+ accelerationVector.x = 1;
+ } else if (_left) {
+ accelerationVector.x = -1;
+ }
+ // Движение ввверх-вниз
+ if (_up) {
+ accelerationVector.z = 1;
+ } else if (_down) {
+ accelerationVector.z = -1;
+ }
+
+ var speedLoss:Number;
+ var len:Number;
+
+ if (accelerationVector.x != 0 || accelerationVector.y != 0 || accelerationVector.z != 0) {
+ // Управление активно
+ if (_object is Camera3D) {
+ var tmp:Number = accelerationVector.z;
+ accelerationVector.z = accelerationVector.y;
+ accelerationVector.y = -tmp;
+ }
+ accelerationVector.normalize();
+ accelerationVector.x *= acceleration;
+ accelerationVector.y *= acceleration;
+ accelerationVector.z *= acceleration;
+ currentTransform.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1);
+ accelerationVector.transform(currentTransform);
+ deltaVelocity.x = accelerationVector.x;
+ deltaVelocity.y = accelerationVector.y;
+ deltaVelocity.z = accelerationVector.z;
+ deltaVelocity.x *= frameTime;
+ deltaVelocity.y *= frameTime;
+ deltaVelocity.z *= frameTime;
+
+ if (!inertialMode) {
+ speedLoss = deceleration * frameTime;
+ var dot:Number = Point3D.dot(velocity, accelerationVector);
+ if (dot > 0) {
+ len = accelerationVector.length;
+ var x:Number = accelerationVector.x / len;
+ var y:Number = accelerationVector.y / len;
+ var z:Number = accelerationVector.z / len;
+ len = dot / len;
+ x = velocity.x - len * x;
+ y = velocity.y - len * y;
+ z = velocity.z - len * z;
+ len = Math.sqrt(x*x + y*y + z*z);
+ if (len > speedLoss) {
+ x *= speedLoss / len;
+ y *= speedLoss / len;
+ z *= speedLoss / len;
+ }
+ velocity.x -= x;
+ velocity.y -= y;
+ velocity.z -= z;
+ } else {
+ len = velocity.length;
+ velocity.length = (len > speedLoss) ? (len - speedLoss) : 0;
+ }
+ }
+
+ velocity.x += deltaVelocity.x;
+ velocity.y += deltaVelocity.y;
+ velocity.z += deltaVelocity.z;
+
+ if (velocity.length > _speed) {
+ velocity.length = _speed;
+ }
+ } else {
+ // Управление неактивно
+ if (!inertialMode) {
+ speedLoss = deceleration * frameTime;
+ len = velocity.length;
+ velocity.length = (len > speedLoss) ? (len - speedLoss) : 0;
+ }
+ }
+
+ displacement.x = velocity.x * frameTime;
+ displacement.y = velocity.y * frameTime;
+ displacement.z = velocity.z * frameTime;
+ }
+
+ /**
+ * Метод применяет потенциальный вектор смещения к эллипсоиду с учётом столкновений с геометрией сцены, если включён
+ * соотвествующий режим.
+ *
+ * @param frameTime время кадра в секундах
+ * @param displacement векотр потенциального смещения эллипсоида
+ */
+ override protected function applyDisplacement(frameTime:Number, displacement:Point3D):void {
+ if (checkCollisions) {
+ _collider.calculateDestination(_coords, displacement, destination);
+
+ displacement.x = destination.x - _coords.x;
+ displacement.y = destination.y - _coords.y;
+ displacement.z = destination.z - _coords.z;
+ } else {
+ destination.x = _coords.x + displacement.x;
+ destination.y = _coords.y + displacement.y;
+ destination.z = _coords.z + displacement.z;
+ }
+
+ velocity.x = displacement.x / frameTime;
+ velocity.y = displacement.y / frameTime;
+ velocity.z = displacement.z / frameTime;
+
+ _coords.x = destination.x;
+ _coords.y = destination.y;
+ _coords.z = destination.z;
+ setObjectCoords();
+
+ var len:Number = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
+ if (len < speedThreshold) {
+ velocity.x = 0;
+ velocity.y = 0;
+ velocity.z = 0;
+ _currentSpeed = 0;
+ } else {
+ _currentSpeed = len;
+ }
+ }
+
+ /**
+ * Поворот объекта вокруг заданной оси.
+ *
+ * @param axis
+ * @param angle
+ */
+ private function rotateObjectAroundAxis(axis:Point3D, angle:Number):void {
+ transformation.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1);
+ rollMatrix.fromAxisAngle(axis, angle);
+ rollMatrix.inverseCombine(transformation);
+ rotations = rollMatrix.getRotations(rotations);
+ _object.rotationX = rotations.x;
+ _object.rotationY = rotations.y;
+ _object.rotationZ = rotations.z;
+ }
+
+ /**
+ * Направление объекта на точку. В результате работы метода локальная ось объекта, соответствующая направлению "вперёд"
+ * будет направлена на указанную точку, а угол поворота вокруг этой оси будет равен нулю.
+ *
+ * @param point координаты точки, на которую должен быть направлен объект
+ */
+ public function lookAt(point:Point3D):void {
+ if (_object == null) {
+ return;
+ }
+ var dx:Number = point.x - _object.x;
+ var dy:Number = point.y - _object.y;
+ var dz:Number = point.z - _object.z;
+ _object.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - (_object is Camera3D ? MathUtils.DEG90 : 0);
+ _object.rotationY = 0;
+ _object.rotationZ = -Math.atan2(dx, dy);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function set enabled(value:Boolean):void {
+ super.enabled = value;
+ if (!value) {
+ velocity.reset();
+ _currentSpeed = 0;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function clearCommandFlags():void {
+ super.clearCommandFlags();
+ _rollLeft = false;
+ _rollRight = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/controllers/ObjectController.as b/Alternativa3D5/5.5/alternativa/engine3d/controllers/ObjectController.as
new file mode 100644
index 0000000..343df38
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/controllers/ObjectController.as
@@ -0,0 +1,886 @@
+package alternativa.engine3d.controllers {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.physics.EllipsoidCollider;
+ import alternativa.types.Map;
+ import alternativa.types.Point3D;
+ import alternativa.utils.MathUtils;
+ import alternativa.utils.ObjectUtils;
+
+ import flash.display.DisplayObject;
+ import flash.events.KeyboardEvent;
+ import flash.events.MouseEvent;
+ import flash.utils.getTimer;
+
+ use namespace alternativa3d;
+
+ /**
+ * Базовый контроллер для изменения ориентации и положения объекта в сцене с помощью клавиатуры и мыши. В классе
+ * реализована поддержка назначения обработчиков клавиатурных команд, а также обработчики для основных команд
+ * перемещения.
+ */
+ public class ObjectController {
+ /**
+ * Имя действия для привязки клавиш движения вперёд.
+ */
+ public static const ACTION_FORWARD:String = "ACTION_FORWARD";
+ /**
+ * Имя действия для привязки клавиш движения назад.
+ */
+ public static const ACTION_BACK:String = "ACTION_BACK";
+ /**
+ * Имя действия для привязки клавиш движения влево.
+ */
+ public static const ACTION_LEFT:String = "ACTION_LEFT";
+ /**
+ * Имя действия для привязки клавиш движения вправо.
+ */
+ public static const ACTION_RIGHT:String = "ACTION_RIGHT";
+ /**
+ * Имя действия для привязки клавиш движения вверх.
+ */
+ public static const ACTION_UP:String = "ACTION_UP";
+ /**
+ * Имя действия для привязки клавиш движения вниз.
+ */
+ public static const ACTION_DOWN:String = "ACTION_DOWN";
+ /**
+ * Имя действия для привязки клавиш поворота вверх.
+ */
+ public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP";
+ /**
+ * Имя действия для привязки клавиш поворота вниз.
+ */
+ public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN";
+ /**
+ * Имя действия для привязки клавиш поворота налево.
+ */
+ public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT";
+ /**
+ * Имя действия для привязки клавиш поворота направо.
+ */
+ public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT";
+ /**
+ * Имя действия для привязки клавиш увеличения скорости.
+ */
+ public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE";
+ /**
+ * Имя действия для привязки клавиш активации обзора мышью.
+ */
+ public static const ACTION_MOUSE_LOOK:String = "ACTION_MOUSE_LOOK";
+
+ /**
+ * Флаг движения вперёд.
+ */
+ protected var _forward:Boolean;
+ /**
+ * Флаг движения назад.
+ */
+ protected var _back:Boolean;
+ /**
+ * Флаг движения влево.
+ */
+ protected var _left:Boolean;
+ /**
+ * Флаг движения вправо.
+ */
+ protected var _right:Boolean;
+ /**
+ * Флаг движения вверх.
+ */
+ protected var _up:Boolean;
+ /**
+ * Флаг движения вниз.
+ */
+ protected var _down:Boolean;
+ /**
+ * Флаг поворота относительно оси X в положительном направлении (взгляд вверх).
+ */
+ protected var _pitchUp:Boolean;
+ /**
+ * Флаг поворота относительно оси X в отрицательном направлении (взгляд вниз).
+ */
+ protected var _pitchDown:Boolean;
+ /**
+ * Флаг поворота относительно оси Z в положительном направлении (взгляд налево).
+ */
+ protected var _yawLeft:Boolean;
+ /**
+ * Флаг активности поворота относительно оси Z в отрицательном направлении (взгляд направо).
+ */
+ protected var _yawRight:Boolean;
+ /**
+ * Флаг активности режима ускорения.
+ */
+ protected var _accelerate:Boolean;
+ /**
+ * Флаг активности режима поворотов мышью.
+ */
+ protected var _mouseLookActive:Boolean;
+ /**
+ * Начальные координаты мышиного курсора в режиме mouse look.
+ */
+ protected var startMouseCoords:Point3D = new Point3D();
+ /**
+ * Флаг активности контроллера.
+ */
+ protected var _enabled:Boolean = true;
+ /**
+ * Источник событий клавиатуры и мыши
+ */
+ protected var _eventsSource:DisplayObject;
+ /**
+ * Ассоциативный массив, связывающий коды клавиатурных клавиш с именами команд.
+ */
+ protected var keyBindings:Map = new Map();
+ /**
+ * Ассоциативный массив, связывающий имена команд с реализующими их функциями. Функции должны иметь вид
+ * function(value:Boolean):void. Значение параметра
+ * Клавиша Действие
+ * W ACTION_FORWARD
+ * S ACTION_BACK
+ * A ACTION_LEFT
+ * D ACTION_RIGHT
+ * SPACE ACTION_UP
+ * Z ACTION_DOWN
+ * UP ACTION_PITCH_UP
+ * DOWN ACTION_PITCH_DOWN
+ * LEFT ACTION_ROLL_LEFT
+ * RIGHT ACTION_ROLL_RIGHT
+ * Q ACTION_YAW_LEFT
+ * E ACTION_YAW_RIGHT
+ * M ACTION_MOUSE_LOOK value указывает, нажата или отпущена соответсвующая команде
+ * клавиша.
+ */
+ protected var actionBindings:Map = new Map();
+ /**
+ * Флаг активности клавиатуры.
+ */
+ protected var _keyboardEnabled:Boolean;
+ /**
+ * Флаг активности мыши.
+ */
+ protected var _mouseEnabled:Boolean;
+ /**
+ * Общая чувствительность мыши. Коэффициент умножения чувствительности по вертикали и горизонтали.
+ */
+ protected var _mouseSensitivity:Number = 1;
+ /**
+ * Коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
+ */
+ protected var _mouseSensitivityY:Number = Math.PI / 360;
+ /**
+ * Коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
+ */
+ protected var _mouseSensitivityX:Number = Math.PI / 360;
+ /**
+ * Результирующий коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
+ */
+ protected var _mouseCoefficientY:Number = _mouseSensitivity * _mouseSensitivityY;
+ /**
+ * Результирующий коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
+ */
+ protected var _mouseCoefficientX:Number = _mouseSensitivity * _mouseSensitivityX;
+ /**
+ * Угловая скорость поворота вокруг поперечной оси в радианах за секунду.
+ */
+ protected var _pitchSpeed:Number = 1;
+ /**
+ * Угловая скорость поворота вокруг вертикальной оси в радианах за секунду.
+ */
+ protected var _yawSpeed:Number = 1;
+ /**
+ * Скорость поступательного движения в единицах за секунду.
+ */
+ protected var _speed:Number = 100;
+ /**
+ * Коэффициент увеличения скорости при соответствующей активной команде.
+ */
+ protected var _speedMultiplier:Number = 2;
+ /**
+ * Управляемый объект.
+ */
+ protected var _object:Object3D;
+ /**
+ * Время в секундах, прошедшее с последнего вызова метода processInput (обычно с последнего кадра).
+ */
+ protected var lastFrameTime:uint;
+ /**
+ * Текущие координаты контроллера.
+ */
+ protected var _coords:Point3D = new Point3D();
+ /**
+ * Индикатор движения объекта (перемещения или поворота) в текущем кадре.
+ */
+ protected var _isMoving:Boolean;
+ /**
+ * Объект для определения столкновений.
+ */
+ protected var _collider:EllipsoidCollider = new EllipsoidCollider();
+
+ /**
+ * Включение и выключение режима проверки столкновений.
+ */
+ public var checkCollisions:Boolean;
+ /**
+ * Функция вида function():void, вызываемая при начале движения объекта. Под движением
+ * понимается изменение координат или ориентации.
+ */
+ public var onStartMoving:Function;
+ /**
+ * Функция вида function():void, вызываемая при прекращении движения объекта. Под движением
+ * понимается изменение координат или ориентации.
+ */
+ public var onStopMoving:Function;
+
+ // Вектор смещения
+ private var _displacement:Point3D = new Point3D();
+
+ /**
+ * Создаёт новый экземпляр контролллера.
+ *
+ * @param eventsSourceObject источник событий клавиатуры и мыши
+ */
+ public function ObjectController(eventsSourceObject:DisplayObject) {
+ if (eventsSourceObject == null) {
+ throw new ArgumentError(ObjectUtils.getClassName(this) + ": eventsSourceObject is null");
+ }
+ _eventsSource = eventsSourceObject;
+
+ actionBindings[ACTION_FORWARD] = moveForward;
+ actionBindings[ACTION_BACK] = moveBack;
+ actionBindings[ACTION_LEFT] = moveLeft;
+ actionBindings[ACTION_RIGHT] = moveRight;
+ actionBindings[ACTION_UP] = moveUp;
+ actionBindings[ACTION_DOWN] = moveDown;
+ actionBindings[ACTION_PITCH_UP] = pitchUp;
+ actionBindings[ACTION_PITCH_DOWN] = pitchDown;
+ actionBindings[ACTION_YAW_LEFT] = yawLeft;
+ actionBindings[ACTION_YAW_RIGHT] = yawRight;
+ actionBindings[ACTION_ACCELERATE] = accelerate;
+ actionBindings[ACTION_MOUSE_LOOK] = setMouseLook;
+
+ keyboardEnabled = true;
+ mouseEnabled = true;
+ }
+
+ /**
+ * Включение и выключение контроллера. Выключенный контроллер пропускает выполнение метода processInput().
+ *
+ * @default true
+ *
+ * @see #processInput()
+ */
+ public function get enabled():Boolean {
+ return _enabled;
+ }
+
+ /**
+ * @private
+ */
+ public function set enabled(value:Boolean):void {
+ _enabled = value;
+ if (_enabled) {
+ if (_mouseEnabled) {
+ registerMouseListeners();
+ }
+ if (_keyboardEnabled) {
+ registerKeyboardListeners();
+ }
+ } else {
+ if (_mouseEnabled) {
+ unregisterMouseListeners();
+ setMouseLook(false);
+ }
+ if (_keyboardEnabled) {
+ unregisterKeyboardListeners();
+ }
+ }
+ }
+
+ /**
+ * Координаты контроллера. Координаты совпадают с координатами центра эллипсоида, используемого для определения
+ * столкновений. Координаты управляемого объекта могут не совпадать с координатами контроллера.
+ *
+ * @see #setObjectCoords()
+ */
+ public function get coords():Point3D {
+ return _coords.clone();
+ }
+
+ /**
+ * @private
+ */
+ public function set coords(value:Point3D):void {
+ _coords.copy(value);
+ setObjectCoords();
+ }
+
+ /**
+ * Чтение координат контроллера в заданную переменную.
+ *
+ * @param point переменная, в которую записываются координаты контроллера
+ */
+ public function readCoords(point:Point3D):void {
+ point.copy(_coords);
+ }
+
+ /**
+ * Управляемый объект.
+ */
+ public function get object():Object3D {
+ return _object;
+ }
+
+ /**
+ * @private
+ * При установке объекта устанавливается сцена для коллайдера.
+ */
+ public function set object(value:Object3D):void {
+ _object = value;
+ _collider.scene = _object == null ? null : _object.scene;
+ setObjectCoords();
+ }
+
+ /**
+ * Объект, реализующий проверку столкновений для эллипсоида.
+ */
+ public function get collider():EllipsoidCollider {
+ return _collider;
+ }
+
+ /**
+ * Активация движения вперёд.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveForward(value:Boolean):void {
+ _forward = value;
+ }
+
+ /**
+ * Активация движения назад.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveBack(value:Boolean):void {
+ _back = value;
+ }
+
+ /**
+ * Активация движения влево.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveLeft(value:Boolean):void {
+ _left = value;
+ }
+
+ /**
+ * Активация движения вправо.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveRight(value:Boolean):void {
+ _right = value;
+ }
+
+ /**
+ * Активация движения вверх.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveUp(value:Boolean):void {
+ _up = value;
+ }
+
+ /**
+ * Активация движения вниз.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveDown(value:Boolean):void {
+ _down = value;
+ }
+
+ /**
+ * Активация поворота вверх.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function pitchUp(value:Boolean):void {
+ _pitchUp = value;
+ }
+
+ /**
+ * Активация поворота вниз.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function pitchDown(value:Boolean):void {
+ _pitchDown = value;
+ }
+
+ /**
+ * Активация поворота влево.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function yawLeft(value:Boolean):void {
+ _yawLeft = value;
+ }
+
+ /**
+ * Активация поворота вправо.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function yawRight(value:Boolean):void {
+ _yawRight = value;
+ }
+
+ /**
+ * Активация режима увеличенной скорости.
+ *
+ * @param value true для включения ускорения, false для выключения
+ */
+ public function accelerate(value:Boolean):void {
+ _accelerate = value;
+ }
+
+ /**
+ * Угловая скорость поворота вокруг поперечной оси (радианы в секунду).
+ *
+ * @default 1
+ */
+ public function get pitchSpeed():Number {
+ return _pitchSpeed;
+ }
+
+ /**
+ * @private
+ */
+ public function set pitchSpeed(spd:Number):void {
+ _pitchSpeed = spd;
+ }
+
+ /**
+ * Угловая скорость поворота вокруг вертикальной оси (радианы в секунду).
+ *
+ * @default 1
+ */
+ public function get yawSpeed():Number {
+ return _yawSpeed;
+ }
+
+ /**
+ * @private
+ */
+ public function set yawSpeed(spd:Number):void {
+ _yawSpeed = spd;
+ }
+
+ /**
+ * Скорость движения в единицах за секунду. При установке отрицательного значения берётся модуль.
+ *
+ * @default 100
+ */
+ public function get speed():Number {
+ return _speed;
+ }
+
+ /**
+ * @private
+ */
+ public function set speed(value:Number):void {
+ _speed = value < 0 ? -value : value;
+ }
+
+ /**
+ * Коэффициент увеличения скорости при активном действии ACTION_ACCELERATE.
+ *
+ * @default 2
+ */
+ public function get speedMultiplier():Number {
+ return _speedMultiplier;
+ }
+
+ /**
+ * @private
+ */
+ public function set speedMultiplier(value:Number):void {
+ _speedMultiplier = value;
+ }
+
+ /**
+ * Чувствительность мыши — коэффициент умножения mouseSensitivityX и mouseSensitivityY.
+ *
+ * @default 1
+ *
+ * @see #mouseSensitivityY()
+ * @see #mouseSensitivityX()
+ */
+ public function get mouseSensitivity():Number {
+ return _mouseSensitivity;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseSensitivity(sensitivity:Number):void {
+ _mouseSensitivity = sensitivity;
+ _mouseCoefficientY = _mouseSensitivity * _mouseSensitivityY;
+ _mouseCoefficientX = _mouseSensitivity * _mouseSensitivityX;
+ }
+
+ /**
+ * Чувтсвительность мыши по вертикали.
+ *
+ * @default Math.PI / 360
+ *
+ * @see #mouseSensitivity()
+ * @see #mouseSensitivityX()
+ */
+ public function get mouseSensitivityY():Number {
+ return _mouseSensitivityY;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseSensitivityY(value:Number):void {
+ _mouseSensitivityY = value;
+ _mouseCoefficientY = _mouseSensitivity * _mouseSensitivityY;
+ }
+
+ /**
+ * Чувтсвительность мыши по горизонтали.
+ *
+ * @default Math.PI / 360
+ *
+ * @see #mouseSensitivity()
+ * @see #mouseSensitivityY()
+ */
+ public function get mouseSensitivityX():Number {
+ return _mouseSensitivityX;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseSensitivityX(value:Number):void {
+ _mouseSensitivityX = value;
+ _mouseCoefficientX = _mouseSensitivity * _mouseSensitivityX;
+ }
+
+ /**
+ * Включение/выключение режима вращения объекта мышью. При включении режима вполняется метод startMouseLook(),
+ * при выключении — stoptMouseLook().
+ *
+ * @see #startMouseLook()
+ * @see #stopMouseLook()
+ */
+ public function setMouseLook(value:Boolean):void {
+ if (_mouseLookActive != value) {
+ _mouseLookActive = value;
+ if (_mouseLookActive) {
+ startMouseLook();
+ } else {
+ stopMouseLook();
+ }
+ }
+ }
+
+ /**
+ * Метод выполняет необходимые действия при включении режима вращения объекта мышью.
+ * Реализация по умолчанию записывает начальные глобальные координаты курсора мыши в переменную startMouseCoords.
+ *
+ * @see #startMouseCoords
+ * @see #setMouseLook()
+ * @see #stopMouseLook()
+ */
+ protected function startMouseLook():void {
+ startMouseCoords.x = _eventsSource.stage.mouseX;
+ startMouseCoords.y = _eventsSource.stage.mouseY;
+ }
+
+ /**
+ * Метод выполняет необходимые действия при выключении вращения объекта мышью. Реализация по умолчанию не делает
+ * ничего.
+ *
+ * @see #setMouseLook()
+ * @see #startMouseLook()
+ */
+ protected function stopMouseLook():void {
+ }
+
+ /**
+ * Метод выполняет обработку всех воздействий на объект. Если объект не установлен или свойство enabled
+ * равно false, метод не выполняется.
+ *
+ *
+ */
+ public function processInput():void {
+ if (!_enabled || _object == null) {
+ return;
+ }
+ var frameTime:Number = getTimer() - lastFrameTime;
+ // Проверка в связи с возможным багом десятого плеера, ну и вообще на всякий случай
+ if (frameTime == 0) {
+ return;
+ }
+
+ lastFrameTime += frameTime;
+ if (frameTime > 100) {
+ frameTime = 100;
+ }
+ frameTime /= 1000;
+
+ rotateObject(frameTime);
+ getDisplacement(frameTime, _displacement);
+ applyDisplacement(frameTime, _displacement);
+
+ // Обработка начала/окончания движения
+ if (_object.changeRotationOrScaleOperation.queued || _object.changeCoordsOperation.queued) {
+ if (!_isMoving) {
+ _isMoving = true;
+ if (onStartMoving != null) {
+ onStartMoving.call(this);
+ }
+ }
+ } else {
+ if (_isMoving) {
+ _isMoving = false;
+ if (onStopMoving != null) {
+ onStopMoving.call(this);
+ }
+ }
+ }
+ }
+
+ /**
+ * Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Реализация по умолчанию не делает ничего.
+ *
+ * @param frameTime длительность текущего кадра в секундах
+ */
+ protected function rotateObject(frameTime:Number):void {
+ }
+
+ /**
+ * Метод вычисляет потенциальное смещение объекта за кадр. Реализация по умолчанию не делает ничего.
+ *
+ * @param frameTime длительность текущего кадра в секундах
+ * @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта
+ */
+ protected function getDisplacement(frameTime:Number, displacement:Point3D):void {
+ }
+
+ /**
+ * Метод применяет потенциальное смещение объекта. Реализация по умолчанию не делает ничего.
+ *
+ * @param frameTime длительность текущего кадра в секундах
+ * @param displacement смещение объекта, которое нужно обработать
+ */
+ protected function applyDisplacement(frameTime:Number, displacement:Point3D):void {
+ }
+
+ /**
+ * Метод выполняет привязку клавиши к действию. Одной клавише может быть назначено только одно действие.
+ *
+ * @param keyCode код клавиши
+ * @param action наименование действия
+ *
+ * @see #unbindKey()
+ * @see #unbindAll()
+ */
+ public function bindKey(keyCode:uint, action:String):void {
+ var method:Function = actionBindings[action];
+ if (method != null) {
+ keyBindings[keyCode] = method;
+ }
+ }
+
+ /**
+ * Очистка привязки клавиши.
+ *
+ * @param keyCode код клавиши
+ *
+ * @see #bindKey()
+ * @see #unbindAll()
+ */
+ public function unbindKey(keyCode:uint):void {
+ keyBindings.remove(keyCode);
+ }
+
+ /**
+ * Очистка привязки всех клавиш.
+ *
+ * @see #bindKey()
+ * @see #unbindKey()
+ */
+ public function unbindAll():void {
+ keyBindings.clear();
+ }
+
+ /**
+ * Метод устанавливает привязки клавиш по умолчанию. Реализация по умолчанию не делает ничего.
+ *
+ * @see #bindKey()
+ * @see #unbindKey()
+ * @see #unbindAll()
+ */
+ public function setDefaultBindings():void {
+ }
+
+ /**
+ * Включение и выключение обработки клавиатурных событий. При включении выполняется метод registerKeyboardListeners,
+ * при выключении — unregisterKeyboardListeners.
+ *
+ * @see #registerKeyboardListeners()
+ * @see #unregisterKeyboardListeners()
+ */
+ public function get keyboardEnabled():Boolean {
+ return _keyboardEnabled;
+ }
+
+ /**
+ * @private
+ */
+ public function set keyboardEnabled(value:Boolean):void {
+ if (_keyboardEnabled != value) {
+ _keyboardEnabled = value;
+ if (_keyboardEnabled) {
+ if (_enabled) {
+ registerKeyboardListeners();
+ }
+ } else {
+ unregisterKeyboardListeners();
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Запуск обработчиков клавиатурных команд.
+ */
+ private function onKeyboardEvent(e:KeyboardEvent):void {
+ var method:Function = keyBindings[e.keyCode];
+ if (method != null) {
+ method.call(this, e.type == KeyboardEvent.KEY_DOWN);
+ }
+ }
+
+ /**
+ * Регистрация необходимых обработчиков при включении обработки клавиатурных событий.
+ *
+ * @see #unregisterKeyboardListeners()
+ */
+ protected function registerKeyboardListeners():void {
+ _eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent);
+ _eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent);
+ }
+
+ /**
+ * Удаление обработчиков при выключении обработки клавиатурных событий.
+ *
+ * @see #registerKeyboardListeners()
+ */
+ protected function unregisterKeyboardListeners():void {
+ clearCommandFlags();
+ _eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent);
+ _eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent);
+ }
+
+ /**
+ * Включение и выключение обработки мышиных событий. При включении выполняется метод registerMouseListeners,
+ * при выключении — unregisterMouseListeners.
+ *
+ * @see #registerMouseListeners()
+ * @see #unregisterMouseListeners()
+ */
+ public function get mouseEnabled():Boolean {
+ return _mouseEnabled;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseEnabled(value:Boolean):void {
+ if (_mouseEnabled != value) {
+ _mouseEnabled = value;
+ if (_mouseEnabled) {
+ if (_enabled) {
+ registerMouseListeners();
+ }
+ } else {
+ unregisterMouseListeners();
+ }
+ }
+ }
+
+ /**
+ * Регистрация необходимых обработчиков при включении обработки мышиных событий.
+ *
+ * @see #unregisterMouseListeners()
+ */
+ protected function registerMouseListeners():void {
+ _eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
+ }
+
+ /**
+ * Удаление используемых обработчиков при выключении обработки мышиных событий.
+ *
+ * @see #registerMouseListeners()
+ */
+ protected function unregisterMouseListeners():void {
+ _eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
+ _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * Активация mouselook
+ */
+ private function onMouseDown(e:MouseEvent):void {
+ setMouseLook(true);
+ _eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * Отключение mouselook
+ */
+ private function onMouseUp(e:MouseEvent):void {
+ setMouseLook(false);
+ _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * Установка координат управляемого объекта в зависимости от текущих координат контроллера.
+ */
+ protected function setObjectCoords():void {
+ if (_object != null) {
+ _object.coords = _coords;
+ }
+ }
+
+ /**
+ * Индикатор режима увеличенной скорости.
+ */
+ public function get accelerated():Boolean {
+ return _accelerate;
+ }
+
+ /**
+ * Метод сбрасывает флаги активных команд.
+ */
+ protected function clearCommandFlags():void {
+ _forward = false;
+ _back = false;
+ _left = false;
+ _right = false;
+ _up = false;
+ _down = false;
+ _pitchUp = false;
+ _pitchDown = false;
+ _yawLeft = false;
+ _yawRight = false;
+ _accelerate = false;
+ _mouseLookActive = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/controllers/WalkController.as b/Alternativa3D5/5.5/alternativa/engine3d/controllers/WalkController.as
new file mode 100644
index 0000000..edc7a0a
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/controllers/WalkController.as
@@ -0,0 +1,520 @@
+package alternativa.engine3d.controllers {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.physics.Collision;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.utils.KeyboardUtils;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.DisplayObject;
+
+ use namespace alternativa3d;
+
+ /**
+ * Контроллер, реализующий управление движением объекта, находящегося в системе координат корневого объекта сцены.
+ *
+ * Camera3D, направлением "вперёд" считается направление его оси
+ * Y, направлением "вверх" — направление оси Z. Для объектов класса
+ * Camera3D направление "вперёд" совпадает с направлением локальной оси Z, а направление
+ * "вверх" противоположно направлению локальной оси Y.
+ *
+ * collider. Координаты управляемого
+ * объекта вычисляются исходя из положения центра эллипсоида и положения объекта на вертикальной оси эллипсоида,
+ * задаваемого параметром objectZPosition.
+ *
+ * ACTION_UP в режиме ходьбы при ненулевой гравитации вызывает прыжок, в остальных случаях
+ * происходит движение вверх.
+ */
+ public class WalkController extends ObjectController {
+ /**
+ * Величина ускорения свободного падения. При положительном значении сила тяжести направлена против оси Z,
+ * при отрицательном — по оси Z.
+ */
+ public var gravity:Number = 0;
+ /**
+ * Вертикальная скорость прыжка.
+ */
+ public var jumpSpeed:Number = 0;
+ /**
+ * Объект, на котором стоит эллипсоид при ненулевой гравитации.
+ */
+ private var _groundMesh:Mesh;
+ /**
+ * Погрешность определения скорости. В режиме полёта или в режиме ходьбы при нахождении на поверхности
+ * скорость приравнивается к нулю, если её модуль не превышает заданного значения.
+ */
+ public var speedThreshold:Number = 1;
+
+ // Коэффициент эффективности управления перемещением при нахождении в воздухе в режиме ходьбы и нулевой гравитации.
+ private var _airControlCoefficient:Number = 1;
+
+ private var _currentSpeed:Number = 0;
+
+ private var minGroundCos:Number = Math.cos(MathUtils.toRadian(70));
+
+ private var destination:Point3D = new Point3D();
+ private var collision:Collision = new Collision();
+
+ private var _objectZPosition:Number = 0.5;
+ private var _flyMode:Boolean;
+ private var _onGround:Boolean;
+
+ private var velocity:Point3D = new Point3D();
+ private var tmpVelocity:Point3D = new Point3D();
+
+ private var controlsActive:Boolean;
+
+ private var inJump:Boolean;
+ private var startRotX:Number;
+ private var startRotZ:Number;
+
+ // Координаты мышиного курсора в режиме mouse look в предыдущем кадре.
+ private var prevMouseCoords:Point3D = new Point3D();
+ // Текущие координаты мышиного курсора в режиме mouse look.
+ private var currentMouseCoords:Point3D = new Point3D();
+
+ /**
+ * @inheritDoc
+ */
+ public function WalkController(eventSourceObject:DisplayObject) {
+ super(eventSourceObject);
+ }
+
+ /**
+ * Объект, на котором стоит эллипсоид при ненулевой гравитации.
+ */
+ public function get groundMesh():Mesh {
+ return _groundMesh;
+ }
+
+ /**
+ * Направление объекта на точку. В результате работы метода локальная ось объекта, соответствующая направлению "вперёд"
+ * будет направлена на указанную точку, а угол поворота вокруг этой оси будет равен нулю.
+ *
+ * @param point координаты точки, на которую должен быть направлен объект
+ */
+ public function lookAt(point:Point3D):void {
+ if (_object == null) {
+ return;
+ }
+ var dx:Number = point.x - _object.x;
+ var dy:Number = point.y - _object.y;
+ var dz:Number = point.z - _object.z;
+ _object.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - (_object is Camera3D ? MathUtils.DEG90 : 0);
+ _object.rotationY = 0;
+ _object.rotationZ = -Math.atan2(dx, dy);
+ }
+
+ /**
+ * Коэффициент эффективности управления перемещением в режиме ходьбы при нахождении в воздухе и ненулевой гравитации.
+ * Значение 0 обозначает полное отсутствие контроля, значение 1 указывает, что управление так же эффективно, как при
+ * нахождении на поверхности.
+ *
+ * @default 1
+ */
+ public function get airControlCoefficient():Number {
+ return _airControlCoefficient;
+ }
+
+ /**
+ * @private
+ */
+ public function set airControlCoefficient(value:Number):void {
+ _airControlCoefficient = value > 0 ? value : -value;
+ }
+
+ /**
+ * Максимальный угол наклона поверхности в радианах, на которой возможен прыжок и на которой объект стоит на месте
+ * в отсутствие управляющих воздействий. Если угол наклона поверхности превышает заданное значение, свойство
+ * onGround будет иметь значение false.
+ *
+ * @see #onGround
+ */
+ public function get maxGroundAngle():Number {
+ return Math.acos(minGroundCos);
+ }
+
+ /**
+ * @private
+ */
+ public function set maxGroundAngle(value:Number):void {
+ minGroundCos = Math.cos(value);
+ }
+
+ /**
+ * Положение управляемого объекта на оси Z эллипсоида. Значение 0 указывает положение в нижней точке эллипсоида,
+ * значение 1 -- положение в верхней точке эллипсоида.
+ */
+ public function get objectZPosition():Number {
+ return _objectZPosition;
+ }
+
+ /**
+ * @private
+ */
+ public function set objectZPosition(value:Number):void {
+ _objectZPosition = value;
+ setObjectCoords();
+ }
+
+ /**
+ * Включене и выключение режима полёта.
+ *
+ * @default false
+ */
+ public function get flyMode():Boolean {
+ return _flyMode;
+ }
+
+ /**
+ * @private
+ */
+ public function set flyMode(value:Boolean):void {
+ _flyMode = value;
+ }
+
+ /**
+ * Индикатор положения эллипсоида на поверхности в режиме ходьбы. Считается, что эллипсоид находится на поверхности,
+ * если угол наклона поверхности под ним не превышает заданного свойством maxGroundAngle значения.
+ *
+ * @see #maxGroundAngle
+ */
+ public function get onGround():Boolean {
+ return _onGround;
+ }
+
+ /**
+ * Модуль текущей скорости.
+ */
+ public function get currentSpeed():Number {
+ return _currentSpeed;
+ }
+
+ /**
+ * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
+ *
+ *
+ */
+ override public function setDefaultBindings():void {
+ unbindAll();
+ bindKey(KeyboardUtils.W, ACTION_FORWARD);
+ bindKey(KeyboardUtils.S, ACTION_BACK);
+ bindKey(KeyboardUtils.A, ACTION_LEFT);
+ bindKey(KeyboardUtils.D, ACTION_RIGHT);
+ bindKey(KeyboardUtils.SPACE, ACTION_UP);
+ bindKey(KeyboardUtils.Z, ACTION_DOWN);
+ bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
+ bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
+ bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT);
+ bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT);
+ bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE);
+ bindKey(KeyboardUtils.M, ACTION_MOUSE_LOOK);
+ }
+
+ /**
+ * Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Взгляд вверх и вниз ограничен
+ * отклонением на 90 градусов от горизонтали.
+ *
+ * @param frameTime длительность текущего кадра в секундах
+ */
+ override protected function rotateObject(frameTime:Number):void {
+ // Mouse look
+ var rotX:Number;
+ if (_mouseLookActive) {
+ prevMouseCoords.copy(currentMouseCoords);
+ currentMouseCoords.x = _eventsSource.stage.mouseX;
+ currentMouseCoords.y = _eventsSource.stage.mouseY;
+ if (!prevMouseCoords.equals(currentMouseCoords)) {
+ _object.rotationZ = startRotZ + (startMouseCoords.x - currentMouseCoords.x) * _mouseCoefficientX;
+ rotX = startRotX + (startMouseCoords.y - currentMouseCoords.y) * _mouseCoefficientY;
+ if (_object is Camera3D) {
+ // Коррекция поворота для камеры
+ _object.rotationX = (rotX > MathUtils.DEG90) ? 0 : (rotX < -MathUtils.DEG90) ? -Math.PI : rotX - MathUtils.DEG90;
+ } else {
+ _object.rotationX = (rotX > MathUtils.DEG90) ? MathUtils.DEG90 : (rotX < -MathUtils.DEG90) ? -MathUtils.DEG90 : rotX;
+ }
+ }
+ }
+
+ // Повороты влево-вправо
+ if (_yawLeft) {
+ _object.rotationZ += _yawSpeed * frameTime;
+ } else if (_yawRight) {
+ _object.rotationZ -= _yawSpeed * frameTime;
+ }
+ // Взгляд вверх-вниз
+ rotX = NaN;
+ if (_pitchUp) {
+ rotX = _object.rotationX + _pitchSpeed * frameTime;
+ } else if (_pitchDown) {
+ rotX = _object.rotationX - _pitchSpeed * frameTime;
+ }
+ if (!isNaN(rotX)) {
+ if (_object is Camera3D) {
+ // Коррекция поворота для камеры
+ _object.rotationX = (rotX > 0) ? 0 : (rotX < -Math.PI) ? -Math.PI : rotX;
+ } else {
+ _object.rotationX = (rotX > MathUtils.DEG90) ? MathUtils.DEG90 : (rotX < -MathUtils.DEG90) ? -MathUtils.DEG90 : rotX;
+ }
+ }
+ }
+
+ /**
+ * Метод вычисляет вектор потенциального смещения эллипсоида, учитывая режим перемещения, команды перемещения и силу тяжести.
+ *
+ * @param frameTime длительность текущего кадра в секундах
+ * @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта
+ */
+ override protected function getDisplacement(frameTime:Number, displacement:Point3D):void {
+ var cos:Number = 0;
+ if (checkCollisions && !_flyMode) {
+ // Проверка наличия под ногами поверхности
+ displacement.x = 0;
+ displacement.y = 0;
+ displacement.z = - 0.5 * gravity * frameTime * frameTime;
+ if (_collider.getCollision(_coords, displacement, collision)) {
+ cos = collision.normal.z;
+ _groundMesh = collision.face._mesh;
+ } else {
+ _groundMesh = null;
+ }
+ }
+ _onGround = cos > minGroundCos;
+
+ if (_onGround && inJump) {
+ inJump = false;
+ }
+
+ var len:Number;
+ var x:Number;
+ var y:Number;
+ var z:Number;
+
+ // При наличии управляющих воздействий расчитывается приращение скорости
+ controlsActive = _forward || _back || _right || _left || _up || _down;
+ if (controlsActive) {
+ if (_flyMode) {
+ tmpVelocity.x = 0;
+ tmpVelocity.y = 0;
+ tmpVelocity.z = 0;
+ // Режим полёта, ускорения направлены вдоль локальных осей
+ // Ускорение вперёд-назад
+ if (_forward) {
+ tmpVelocity.y = 1;
+ } else if (_back) {
+ tmpVelocity.y = -1;
+ }
+ // Ускорение влево-вправо
+ if (_right) {
+ tmpVelocity.x = 1;
+ } else if (_left) {
+ tmpVelocity.x = -1;
+ }
+ // Ускорение вверх-вниз
+ if (_up) {
+ tmpVelocity.z = 1;
+ } else if (_down) {
+ tmpVelocity.z = -1;
+ }
+ var matrix:Matrix3D = _object._transformation;
+ x = tmpVelocity.x;
+ if (_object is Camera3D) {
+ y = -tmpVelocity.z;
+ z = tmpVelocity.y;
+ } else {
+ y = tmpVelocity.y;
+ z = tmpVelocity.z;
+ }
+ // Поворот вектора из локальной системы координат объекта в глобальную
+ velocity.x += (x * matrix.a + y * matrix.b + z * matrix.c) * _speed;
+ velocity.y += (x * matrix.e + y * matrix.f + z * matrix.g) * _speed;
+ velocity.z += (x * matrix.i + y * matrix.j + z * matrix.k) * _speed;
+ } else {
+
+ // Режим хождения, ускорения вперёд-назад-влево-вправо лежат в глобальной плоскости XY, вверх-вниз направлены вдоль глобальной оси Z
+ var heading:Number = _object.rotationZ;
+ var headingCos:Number = Math.cos(heading);
+ var headingSin:Number = Math.sin(heading);
+
+ var spd:Number = _speed;
+ if (gravity != 0 && !_onGround) {
+ spd *= _airControlCoefficient;
+ }
+
+ // Вперёд-назад
+ if (_forward) {
+ velocity.x -= spd * headingSin;
+ velocity.y += spd * headingCos;
+ } else if (_back) {
+ velocity.x += spd * headingSin;
+ velocity.y -= spd * headingCos;
+ }
+ // Влево-вправо
+ if (_right) {
+ velocity.x += spd * headingCos;
+ velocity.y += spd * headingSin;
+ } else if (_left) {
+ velocity.x -= spd * headingCos;
+ velocity.y -= spd * headingSin;
+ }
+ if (gravity == 0) {
+ // Ускорение вверх-вниз
+ if (_up) {
+ velocity.z += _speed;
+ } else if (_down) {
+ velocity.z -= _speed;
+ }
+ }
+ }
+ } else {
+ // Управление неактивно, замедление движения
+ len = 1 / Math.pow(3, frameTime * 10);
+ if (_flyMode || gravity == 0) {
+ velocity.x *= len;
+ velocity.y *= len;
+ velocity.z *= len;
+ } else {
+ if (_onGround) {
+ velocity.x *= len;
+ velocity.y *= len;
+ if (velocity.z < 0) {
+ velocity.z *= len;
+ }
+ } else {
+ if (cos > 0 && velocity.z > 0) {
+ velocity.z = 0;
+ }
+ }
+ }
+ }
+ // Прыжок
+ if (_onGround && _up && !inJump) {
+ velocity.z = jumpSpeed;
+ inJump = true;
+ _onGround = false;
+ cos = 0;
+ }
+ // В режиме ходьбы добавляется ускорение свободного падения, если находимся не на ровной поверхности
+ if (!(_flyMode || _onGround)) {
+ velocity.z -= gravity * frameTime;
+ }
+
+ // Ограничение скорости
+ var max:Number = _accelerate ? _speed * _speedMultiplier : _speed;
+ if (_flyMode || gravity == 0) {
+ len = velocity.length;
+ if (len > max) {
+ velocity.length = max;
+ }
+ } else {
+ len = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y);
+ if (len > max) {
+ velocity.x *= max / len;
+ velocity.y *= max / len;
+ }
+ if (cos > 0 && velocity.z > 0) {
+ velocity.z = 0;
+ }
+ }
+
+ // Cмещение за кадр
+ displacement.x = velocity.x * frameTime;
+ displacement.y = velocity.y * frameTime;
+ displacement.z = velocity.z * frameTime;
+ }
+
+ /**
+ * Метод применяет потенциальный вектор смещения к эллипсоиду с учётом столкновений с геометрией сцены, если включён
+ * соотвествующий режим.
+ *
+ * @param frameTime время кадра в секундах
+ * @param displacement векотр потенциального смещения эллипсоида
+ */
+ override protected function applyDisplacement(frameTime:Number, displacement:Point3D):void {
+ if (checkCollisions) {
+ _collider.calculateDestination(_coords, displacement, destination);
+
+ displacement.x = destination.x - _coords.x;
+ displacement.y = destination.y - _coords.y;
+ displacement.z = destination.z - _coords.z;
+ } else {
+ destination.x = _coords.x + displacement.x;
+ destination.y = _coords.y + displacement.y;
+ destination.z = _coords.z + displacement.z;
+ }
+
+ velocity.x = displacement.x / frameTime;
+ velocity.y = displacement.y / frameTime;
+ velocity.z = displacement.z / frameTime;
+
+ _coords.x = destination.x;
+ _coords.y = destination.y;
+ _coords.z = destination.z;
+ setObjectCoords();
+
+ var len:Number = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
+ if (len < speedThreshold) {
+ velocity.x = 0;
+ velocity.y = 0;
+ velocity.z = 0;
+ _currentSpeed = 0;
+ } else {
+ _currentSpeed = len;
+ }
+ }
+
+ /**
+ * Метод устанавливает координаты управляемого объекта в зависимости от параметра
+ * Клавиша Действие
+ * W ACTION_FORWARD
+ * S ACTION_BACK
+ * A ACTION_LEFT
+ * D ACTION_RIGHT
+ * SPACE ACTION_UP
+ * Z ACTION_DOWN
+ * SHIFT ACTION_ACCELERATE
+ * UP ACTION_PITCH_UP
+ * DOWN ACTION_PITCH_DOWN
+ * LEFT ACTION_YAW_LEFT
+ * RIGHT ACTION_YAW_RIGHT
+ * M ACTION_MOUSE_LOOK objectZPosition.
+ *
+ * @see #objectZPosition
+ */
+ override protected function setObjectCoords():void {
+ if (_object != null) {
+ _object.x = _coords.x;
+ _object.y = _coords.y;
+ _object.z = _coords.z + (2 * _objectZPosition - 1) * _collider.radiusZ;
+ }
+ }
+
+ /**
+ * Метод выполняет необходимые действия при включении вращения объекта мышью.
+ */
+ override protected function startMouseLook():void {
+ super.startMouseLook();
+ startRotX = _object is Camera3D ? _object.rotationX + MathUtils.DEG90 : _object.rotationX;
+ startRotZ = _object.rotationZ;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function set enabled(value:Boolean):void {
+ super.enabled = value;
+ if (!value) {
+ velocity.reset();
+ _currentSpeed = 0;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/BSPNode.as b/Alternativa3D5/5.5/alternativa/engine3d/core/BSPNode.as
new file mode 100644
index 0000000..1032a99
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/core/BSPNode.as
@@ -0,0 +1,77 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.*;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ */
+ public final class BSPNode {
+
+ // Тип примитива
+ alternativa3d var sprited:Boolean;
+
+ // Родительская нода
+ alternativa3d var parent:BSPNode;
+
+ // Дочерние ветки
+ alternativa3d var front:BSPNode;
+ alternativa3d var back:BSPNode;
+
+ // Нормаль плоскости ноды
+ alternativa3d var normal:Point3D = new Point3D();
+
+ // Смещение плоскости примитива
+ alternativa3d var offset:Number;
+
+ // Минимальная мобильность ноды
+ alternativa3d var mobility:int = int.MAX_VALUE;
+
+ // Набор примитивов в ноде
+ alternativa3d var primitive:PolyPrimitive;
+ alternativa3d var backPrimitives:Set;
+ alternativa3d var frontPrimitives:Set;
+
+ // Хранилище неиспользуемых нод
+ static private var collector:Array = new Array();
+
+ // Создать ноду на основе примитива
+ static alternativa3d function createBSPNode(primitive:PolyPrimitive):BSPNode {
+ var node:BSPNode;
+ if ((node = collector.pop()) == null) {
+ node = new BSPNode();
+ }
+
+ // Добавляем примитив в ноду
+ node.primitive = primitive;
+ // Сохраняем ноду
+ primitive.node = node;
+ // Если это спрайтовый примитив
+ if (primitive.face == null) {
+ node.normal.x = 0;
+ node.normal.y = 0;
+ node.normal.z = 0;
+ node.offset = 0;
+ node.sprited = true;
+ } else {
+ // Сохраняем плоскость
+ node.normal.copy(primitive.face.globalNormal);
+ node.offset = primitive.face.globalOffset;
+ node.sprited = false;
+ }
+ // Сохраняем мобильность
+ node.mobility = primitive.mobility;
+ return node;
+ }
+
+ // Удалить ноду, все ссылки должны быть почищены
+ static alternativa3d function destroyBSPNode(node:BSPNode):void {
+ //trace(node.back, node.front, node.parent, node.primitive, node.backPrimitives, node.frontPrimitives);
+ collector.push(node);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Camera3D.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Camera3D.as
new file mode 100644
index 0000000..65fac09
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Camera3D.as
@@ -0,0 +1,1269 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.display.Skin;
+ import alternativa.engine3d.display.View;
+ import alternativa.engine3d.materials.DrawPoint;
+ import alternativa.engine3d.materials.SpriteMaterial;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ import flash.geom.Matrix;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Камера для отображения 3D-сцены на экране.
+ *
+ * alternativa.engine3d.display.View.
+ *
+ * @see alternativa.engine3d.display.View
+ */
+ public class Camera3D extends Object3D {
+
+ /**
+ * @private
+ * Расчёт матрицы пространства камеры
+ */
+ alternativa3d var calculateMatrixOperation:Operation = new Operation("calculateMatrix", this, calculateMatrix, Operation.CAMERA_CALCULATE_MATRIX);
+ /**
+ * @private
+ * Расчёт плоскостей отсечения
+ */
+ alternativa3d var calculatePlanesOperation:Operation = new Operation("calculatePlanes", this, calculatePlanes, Operation.CAMERA_CALCULATE_PLANES);
+ /**
+ * @private
+ * Отрисовка
+ */
+ alternativa3d var renderOperation:Operation = new Operation("render", this, render, Operation.CAMERA_RENDER);
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ /**
+ * @private
+ * Поле зрения
+ */
+ alternativa3d var _fov:Number = Math.PI/2;
+ /**
+ * @private
+ * Фокусное расстояние
+ */
+ alternativa3d var focalLength:Number;
+ /**
+ * @private
+ * Перспективное искажение
+ */
+ alternativa3d var focalDistortion:Number;
+
+ /**
+ * @private
+ * Флаги рассчитанности UV-матриц
+ */
+ alternativa3d var uvMatricesCalculated:Set = new Set(true);
+
+ // Всмомогательные точки для расчёта UV-матриц
+ private var textureA:Point3D = new Point3D();
+ private var textureB:Point3D = new Point3D();
+ private var textureC:Point3D = new Point3D();
+
+ /**
+ * @private
+ * Вид из камеры
+ */
+ alternativa3d var _view:View;
+
+ /**
+ * @private
+ * Режим отрисовки
+ */
+ alternativa3d var _orthographic:Boolean = false;
+ private var fullDraw:Boolean;
+
+ /**
+ * @private
+ * Масштаб
+ */
+ alternativa3d var _zoom:Number = 1;
+
+ // Синус половинчатого угла обзора камеры
+ private var viewAngle:Number;
+
+ /**
+ * @private
+ * Направление камеры
+ */
+ private var direction:Point3D = new Point3D(0, 0, 1);
+
+ /**
+ * @private
+ * Обратная трансформация камеры
+ */
+ alternativa3d var cameraMatrix:Matrix3D = new Matrix3D();
+
+ // Скины
+ private var firstSkin:Skin;
+ private var prevSkin:Skin;
+ private var currentSkin:Skin;
+
+ // Плоскости отсечения
+ private var leftPlane:Point3D = new Point3D();
+ private var rightPlane:Point3D = new Point3D();
+ private var topPlane:Point3D = new Point3D();
+ private var bottomPlane:Point3D = new Point3D();
+ private var farPlane:Point3D = new Point3D();
+ private var leftOffset:Number;
+ private var rightOffset:Number;
+ private var topOffset:Number;
+ private var bottomOffset:Number;
+ private var nearOffset:Number;
+ private var farOffset:Number;
+
+ // Вспомогательные массивы точек для отрисовки
+ private var points1:Array = new Array();
+ private var points2:Array = new Array();
+ private var spritePoint:Point3D = new Point3D();
+ // Массив для сортировки спрайтов
+ private var spritePrimitives:Array = new Array();
+
+ /**
+ * @private
+ */
+ alternativa3d var _nearClippingDistance:Number = 10;
+ /**
+ * @private
+ */
+ alternativa3d var _farClippingDistance:Number = 1000;
+ /**
+ * @private
+ */
+ alternativa3d var _nearClipping:Boolean = false;
+ /**
+ * @private
+ */
+ alternativa3d var _farClipping:Boolean = false;
+ /**
+ * @private
+ */
+ alternativa3d var _viewClipping:Boolean = true;
+
+ /**
+ * Создание нового экземпляра камеры.
+ *
+ * @param name имя экземпляра
+ */
+ public function Camera3D(name:String = null) {
+ super(name);
+ }
+
+ /**
+ * @private
+ */
+ private function calculateMatrix():void {
+ // Расчёт матрицы пространства камеры
+ cameraMatrix.copy(_transformation);
+ cameraMatrix.invert();
+ if (_orthographic) {
+ cameraMatrix.scale(_zoom, _zoom, _zoom);
+ }
+ // Направление камеры
+ direction.x = _transformation.c;
+ direction.y = _transformation.g;
+ direction.z = _transformation.k;
+ direction.normalize();
+ }
+
+ /**
+ * @private
+ * Расчёт плоскостей отсечения
+ */
+ private function calculatePlanes():void {
+ var halfWidth:Number = _view._width*0.5;
+ var halfHeight:Number = _view._height*0.5;
+
+ var aw:Number = _transformation.a*halfWidth;
+ var ew:Number = _transformation.e*halfWidth;
+ var iw:Number = _transformation.i*halfWidth;
+ var bh:Number = _transformation.b*halfHeight;
+ var fh:Number = _transformation.f*halfHeight;
+ var jh:Number = _transformation.j*halfHeight;
+ if (_orthographic) {
+ if (_viewClipping) {
+ // Расчёт плоскостей отсечения в изометрии
+ aw /= _zoom;
+ ew /= _zoom;
+ iw /= _zoom;
+ bh /= _zoom;
+ fh /= _zoom;
+ jh /= _zoom;
+
+ // Левая плоскость
+ leftPlane.x = _transformation.f*_transformation.k - _transformation.j*_transformation.g;
+ leftPlane.y = _transformation.j*_transformation.c - _transformation.b*_transformation.k;
+ leftPlane.z = _transformation.b*_transformation.g - _transformation.f*_transformation.c;
+ leftOffset = (_transformation.d - aw)*leftPlane.x + (_transformation.h - ew)*leftPlane.y + (_transformation.l - iw)*leftPlane.z;
+
+ // Правая плоскость
+ rightPlane.x = -leftPlane.x;
+ rightPlane.y = -leftPlane.y;
+ rightPlane.z = -leftPlane.z;
+ rightOffset = (_transformation.d + aw)*rightPlane.x + (_transformation.h + ew)*rightPlane.y + (_transformation.l + iw)*rightPlane.z;
+
+ // Верхняя плоскость
+ topPlane.x = _transformation.g*_transformation.i - _transformation.k*_transformation.e;
+ topPlane.y = _transformation.k*_transformation.a - _transformation.c*_transformation.i;
+ topPlane.z = _transformation.c*_transformation.e - _transformation.g*_transformation.a;
+ topOffset = (_transformation.d - bh)*topPlane.x + (_transformation.h - fh)*topPlane.y + (_transformation.l - jh)*topPlane.z;
+
+ // Нижняя плоскость
+ bottomPlane.x = -topPlane.x;
+ bottomPlane.y = -topPlane.y;
+ bottomPlane.z = -topPlane.z;
+ bottomOffset = (_transformation.d + bh)*bottomPlane.x + (_transformation.h + fh)*bottomPlane.y + (_transformation.l + jh)*bottomPlane.z;
+ }
+ } else {
+ // Вычисляем расстояние фокуса
+ focalLength = Math.sqrt(_view._width*_view._width + _view._height*_view._height)*0.5/Math.tan(0.5*_fov);
+ // Вычисляем минимальное (однопиксельное) искажение перспективной коррекции
+ focalDistortion = 1/(focalLength*focalLength);
+
+ if (_viewClipping) {
+ // Расчёт плоскостей отсечения в перспективе
+ var cl:Number = _transformation.c*focalLength;
+ var gl:Number = _transformation.g*focalLength;
+ var kl:Number = _transformation.k*focalLength;
+
+ // Угловые вектора пирамиды видимости
+ var leftTopX:Number = -aw - bh + cl;
+ var leftTopY:Number = -ew - fh + gl;
+ var leftTopZ:Number = -iw - jh + kl;
+ var rightTopX:Number = aw - bh + cl;
+ var rightTopY:Number = ew - fh + gl;
+ var rightTopZ:Number = iw - jh + kl;
+ var leftBottomX:Number = -aw + bh + cl;
+ var leftBottomY:Number = -ew + fh + gl;
+ var leftBottomZ:Number = -iw + jh + kl;
+ var rightBottomX:Number = aw + bh + cl;
+ var rightBottomY:Number = ew + fh + gl;
+ var rightBottomZ:Number = iw + jh + kl;
+
+ // Левая плоскость
+ leftPlane.x = leftBottomY*leftTopZ - leftBottomZ*leftTopY;
+ leftPlane.y = leftBottomZ*leftTopX - leftBottomX*leftTopZ;
+ leftPlane.z = leftBottomX*leftTopY - leftBottomY*leftTopX;
+ leftOffset = _transformation.d*leftPlane.x + _transformation.h*leftPlane.y + _transformation.l*leftPlane.z;
+
+ // Правая плоскость
+ rightPlane.x = rightTopY*rightBottomZ - rightTopZ*rightBottomY;
+ rightPlane.y = rightTopZ*rightBottomX - rightTopX*rightBottomZ;
+ rightPlane.z = rightTopX*rightBottomY - rightTopY*rightBottomX;
+ rightOffset = _transformation.d*rightPlane.x + _transformation.h*rightPlane.y + _transformation.l*rightPlane.z;
+
+ // Верхняя плоскость
+ topPlane.x = leftTopY*rightTopZ - leftTopZ*rightTopY;
+ topPlane.y = leftTopZ*rightTopX - leftTopX*rightTopZ;
+ topPlane.z = leftTopX*rightTopY - leftTopY*rightTopX;
+ topOffset = _transformation.d*topPlane.x + _transformation.h*topPlane.y + _transformation.l*topPlane.z;
+
+ // Нижняя плоскость
+ bottomPlane.x = rightBottomY*leftBottomZ - rightBottomZ*leftBottomY;
+ bottomPlane.y = rightBottomZ*leftBottomX - rightBottomX*leftBottomZ;
+ bottomPlane.z = rightBottomX*leftBottomY - rightBottomY*leftBottomX;
+ bottomOffset = _transformation.d*bottomPlane.x + _transformation.h*bottomPlane.y + _transformation.l*bottomPlane.z;
+
+
+ // Расчёт угла конуса
+ var length:Number = Math.sqrt(leftTopX*leftTopX + leftTopY*leftTopY + leftTopZ*leftTopZ);
+ leftTopX /= length;
+ leftTopY /= length;
+ leftTopZ /= length;
+ length = Math.sqrt(rightTopX*rightTopX + rightTopY*rightTopY + rightTopZ*rightTopZ);
+ rightTopX /= length;
+ rightTopY /= length;
+ rightTopZ /= length;
+ length = Math.sqrt(leftBottomX*leftBottomX + leftBottomY*leftBottomY + leftBottomZ*leftBottomZ);
+ leftBottomX /= length;
+ leftBottomY /= length;
+ leftBottomZ /= length;
+ length = Math.sqrt(rightBottomX*rightBottomX + rightBottomY*rightBottomY + rightBottomZ*rightBottomZ);
+ rightBottomX /= length;
+ rightBottomY /= length;
+ rightBottomZ /= length;
+
+ viewAngle = leftTopX*direction.x + leftTopY*direction.y + leftTopZ*direction.z;
+ var dot:Number = rightTopX*direction.x + rightTopY*direction.y + rightTopZ*direction.z;
+ viewAngle = (dot < viewAngle) ? dot : viewAngle;
+ dot = leftBottomX*direction.x + leftBottomY*direction.y + leftBottomZ*direction.z;
+ viewAngle = (dot < viewAngle) ? dot : viewAngle;
+ dot = rightBottomX*direction.x + rightBottomY*direction.y + rightBottomZ*direction.z;
+ viewAngle = (dot < viewAngle) ? dot : viewAngle;
+
+ viewAngle = Math.sin(Math.acos(viewAngle));
+ } else {
+ viewAngle = 1;
+ }
+ }
+
+ var x:Number;
+ var y:Number;
+ var z:Number;
+ var k:Number;
+
+ if (_nearClipping) {
+ if (_orthographic) {
+ k = _nearClippingDistance / _zoom;
+ x = _transformation.c*k + _transformation.d;
+ y = _transformation.g*k + _transformation.h;
+ z = _transformation.k*k + _transformation.l;
+ } else {
+ x = _transformation.c*_nearClippingDistance + _transformation.d;
+ y = _transformation.g*_nearClippingDistance + _transformation.h;
+ z = _transformation.k*_nearClippingDistance + _transformation.l;
+ }
+ nearOffset = direction.x*x + direction.y*y + direction.z*z;
+ }
+
+ if (_farClipping) {
+ if (_orthographic) {
+ k = _farClippingDistance / _zoom;
+ x = _transformation.c*k + _transformation.d;
+ y = _transformation.g*k + _transformation.h;
+ z = _transformation.k*k + _transformation.l;
+ } else {
+ x = _transformation.c*_farClippingDistance + _transformation.d;
+ y = _transformation.g*_farClippingDistance + _transformation.h;
+ z = _transformation.k*_farClippingDistance + _transformation.l;
+ }
+
+ farPlane.x = -direction.x;
+ farPlane.y = -direction.y;
+ farPlane.z = -direction.z;
+ farOffset = farPlane.x*x + farPlane.y*y + farPlane.z*z;
+ }
+ }
+
+ /**
+ * @private
+ */
+ private function render():void {
+ // Режим отрисовки
+ fullDraw = (calculateMatrixOperation.queued || calculatePlanesOperation.queued);
+
+ // Очистка рассчитанных текстурных матриц
+ uvMatricesCalculated.clear();
+
+ // Отрисовка
+ prevSkin = null;
+ currentSkin = firstSkin;
+ renderBSPNode(_scene.bsp);
+
+ // Удаление ненужных скинов
+ while (currentSkin != null) {
+ removeCurrentSkin();
+ }
+
+ if (_view._interactive) {
+ _view.checkMouseOverOut(true);
+ }
+ }
+
+ /**
+ * @private
+ */
+ private function renderBSPNode(node:BSPNode):void {
+ if (node != null) {
+ if (node.sprited) {
+ // Спрайтовая нода
+ if (node.primitive != null) {
+ drawSpriteSkin(node.primitive as SpritePrimitive);
+ } else {
+ drawSpritePrimitives(node.frontPrimitives);
+ }
+ } else {
+ var primitive:*;
+ var normal:Point3D = node.normal;
+ var cameraAngle:Number = direction.x*normal.x + direction.y*normal.y + direction.z*normal.z;
+ var cameraOffset:Number;
+ if (!_orthographic) {
+ cameraOffset = globalCoords.x*normal.x + globalCoords.y*normal.y + globalCoords.z*normal.z - node.offset;
+ }
+ if (node.primitive != null) {
+ // В ноде только базовый примитив
+ if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) {
+ // Камера спереди ноды
+ if (_orthographic || cameraAngle < viewAngle) {
+ renderBSPNode(node.back);
+ drawSkin(node.primitive);
+ }
+ renderBSPNode(node.front);
+ } else {
+ // Камера сзади ноды
+ if (_orthographic || cameraAngle > -viewAngle) {
+ renderBSPNode(node.front);
+ }
+ renderBSPNode(node.back);
+ }
+ } else {
+ // В ноде несколько примитивов
+ if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) {
+ // Камера спереди ноды
+ if (_orthographic || cameraAngle < viewAngle) {
+ renderBSPNode(node.back);
+ for (primitive in node.frontPrimitives) {
+ drawSkin(primitive);
+ }
+ }
+ renderBSPNode(node.front);
+ } else {
+ if (_orthographic || cameraAngle > -viewAngle) {
+ renderBSPNode(node.front);
+ for (primitive in node.backPrimitives) {
+ drawSkin(primitive);
+ }
+ }
+ renderBSPNode(node.back);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Функция сортирует список спрайтовых примитивов по удаленности и отправляет на отрисовку.
+ *
+ * @param primitives список примитивов для отрисовки
+ */
+ private function drawSpritePrimitives(primitives:Set):void {
+ var primitive:SpritePrimitive;
+ var counter:int = -1;
+ for (var p:* in primitives) {
+ primitive = p;
+ var point:Point3D = primitive.sprite.globalCoords;
+ var z:Number = cameraMatrix.i*point.x + cameraMatrix.j*point.y + cameraMatrix.k*point.z + cameraMatrix.l;
+ primitive.screenDepth = z;
+ //if (z > 0) {
+ spritePrimitives[++counter] = primitive;
+ //}
+ }
+ if (counter > 0) {
+ sortSpritePrimitives(0, counter);
+ }
+ for (var i:int = counter; i >= 0; i--) {
+ drawSpriteSkin(spritePrimitives[i]);
+ }
+ }
+
+ /**
+ * @private
+ * Сортировка примитивов по удаленности.
+ *
+ * @param l начальный индекс
+ * @param r конечный индекс
+ */
+ private function sortSpritePrimitives(l:int, r:int):void {
+ var i:int = l;
+ var j:int = r;
+ var left:SpritePrimitive;
+ var mid:Number = spritePrimitives[(r + l) >> 1].screenDepth;
+ var right:SpritePrimitive;
+ do {
+ while ((left = spritePrimitives[i]).screenDepth < mid) {i++};
+ while (mid < (right = spritePrimitives[j]).screenDepth) {j--};
+ if (i <= j) {
+ spritePrimitives[i++] = right;
+ spritePrimitives[j--] = left;
+ }
+ } while (i <= j)
+ if (l < j) {
+ sortSpritePrimitives(l, j);
+ }
+ if (i < r) {
+ sortSpritePrimitives(i, r);
+ }
+ }
+
+ /**
+ * @private
+ * Отрисовка скина спрайтового примитива.
+ *
+ * @param primitive спрайтовый примитив
+ */
+ private function drawSpriteSkin(primitive:SpritePrimitive):void {
+ if (!fullDraw && currentSkin != null && currentSkin.primitive == primitive && !_scene.changedPrimitives[primitive]) {
+ // Пропуск скина
+ prevSkin = currentSkin;
+ currentSkin = currentSkin.nextSkin;
+ } else {
+ var sprite:Sprite3D = primitive.sprite;
+
+ var material:SpriteMaterial = sprite._material;
+ if (material == null) {
+ return;
+ }
+
+ if (!material.canDraw(this)) {
+ return;
+ }
+
+ if (fullDraw || _scene.changedPrimitives[primitive]) {
+
+ // Если конец списка скинов
+ if (currentSkin == null) {
+ // Добавляем скин в конец
+ addCurrentSkin();
+ } else {
+ if (fullDraw || _scene.changedPrimitives[currentSkin.primitive]) {
+ // Очистка скина
+ currentSkin.material.clear(currentSkin);
+ } else {
+ // Вставка скина перед текущим
+ insertCurrentSkin();
+ }
+ }
+
+ // Назначаем скину примитив и материал
+ currentSkin.primitive = primitive;
+ currentSkin.material = material;
+ material.draw(this, currentSkin);
+ prevSkin = currentSkin;
+ currentSkin = currentSkin.nextSkin;
+ } else {
+ // Скин текущего примитива дальше по списку скинов
+
+ // Удаление ненужных скинов
+ while (currentSkin != null && _scene.changedPrimitives[currentSkin.primitive]) {
+ removeCurrentSkin();
+ }
+
+ // Переключение на следующий скин
+ if (currentSkin != null) {
+ prevSkin = currentSkin;
+ currentSkin = currentSkin.nextSkin;
+ }
+
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Отрисовка скина примитива
+ */
+ private function drawSkin(primitive:PolyPrimitive):void {
+ if (!fullDraw && currentSkin != null && currentSkin.primitive == primitive && !_scene.changedPrimitives[primitive]) {
+ // Пропуск скина
+ prevSkin = currentSkin;
+ currentSkin = currentSkin.nextSkin;
+ } else {
+ // Проверка поверхности
+ var surface:Surface = primitive.face._surface;
+ if (surface == null) {
+ return;
+ }
+ // Проверка материала
+ var material:SurfaceMaterial = surface._material;
+ if (material == null || !material.canDraw(primitive)) {
+ return;
+ }
+ // Отсечение выходящих за окно просмотра частей
+ var i:uint;
+ var length:uint = primitive.num;
+ var primitivePoint:Point3D;
+ var primitiveUV:Point;
+ var point:DrawPoint;
+ var useUV:Boolean = !_orthographic && material.useUV && primitive.face.uvMatrixBase;
+ if (useUV) {
+ // Формируем список точек и UV-координат полигона
+ for (i = 0; i < length; i++) {
+ primitivePoint = primitive.points[i];
+ primitiveUV = primitive.uvs[i];
+ point = points1[i];
+ if (point == null) {
+ points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z, primitiveUV.x, primitiveUV.y);
+ } else {
+ point.x = primitivePoint.x;
+ point.y = primitivePoint.y;
+ point.z = primitivePoint.z;
+ point.u = primitiveUV.x;
+ point.v = primitiveUV.y;
+ }
+ }
+ } else {
+ // Формируем список точек полигона
+ for (i = 0; i < length; i++) {
+ primitivePoint = primitive.points[i];
+ point = points1[i];
+ if (point == null) {
+ points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z);
+ } else {
+ point.x = primitivePoint.x;
+ point.y = primitivePoint.y;
+ point.z = primitivePoint.z;
+ }
+ }
+ }
+
+ if (_viewClipping) {
+ // Отсечение по левой стороне
+ length = clip(length, points1, points2, leftPlane, leftOffset, useUV);
+ if (length < 3) {
+ return;
+ }
+ // Отсечение по правой стороне
+ length = clip(length, points2, points1, rightPlane, rightOffset, useUV);
+ if (length < 3) {
+ return;
+ }
+ // Отсечение по верхней стороне
+ length = clip(length, points1, points2, topPlane, topOffset, useUV);
+ if (length < 3) {
+ return;
+ }
+ // Отсечение по нижней стороне
+ length = clip(length, points2, points1, bottomPlane, bottomOffset, useUV);
+ if (length < 3) {
+ return;
+ }
+ }
+
+ var tmp:Array;
+ if (_farClipping && _nearClipping) {
+ // Отсечение по дальней плоскости
+ length = clip(length, points1, points2, farPlane, farOffset, useUV);
+ if (length < 3) {
+ return;
+ }
+ // Отсечение по ближней плоскости
+ length = clip(length, points2, points1, direction, nearOffset, useUV);
+ if (length < 3) {
+ return;
+ }
+ } else if (_farClipping) {
+ // Отсечение по дальней плоскости
+ length = clip(length, points1, points2, farPlane, farOffset, useUV);
+ if (length < 3) {
+ return;
+ }
+ tmp = points1;
+ points1 = points2;
+ points2 = tmp;
+ } else if (_nearClipping) {
+ // Отсечение по ближней плоскости
+ length = clip(length, points1, points2, direction, nearOffset, useUV);
+ if (length < 3) {
+ return;
+ }
+ tmp = points1;
+ points1 = points2;
+ points2 = tmp;
+ }
+
+ if (fullDraw || _scene.changedPrimitives[primitive]) {
+
+ // Если конец списка скинов
+ if (currentSkin == null) {
+ // Добавляем скин в конец
+ addCurrentSkin();
+ } else {
+ if (fullDraw || _scene.changedPrimitives[currentSkin.primitive]) {
+ // Очистка скина
+ currentSkin.material.clear(currentSkin);
+ } else {
+ // Вставка скина перед текущим
+ insertCurrentSkin();
+ }
+ }
+
+ // Переводим координаты в систему камеры
+ var x:Number;
+ var y:Number;
+ var z:Number;
+ for (i = 0; i < length; i++) {
+ point = points1[i];
+ x = point.x;
+ y = point.y;
+ z = point.z;
+ point.x = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d;
+ point.y = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h;
+ point.z = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l;
+ }
+
+ // Назначаем скину примитив и материал
+ currentSkin.primitive = primitive;
+ currentSkin.material = material;
+ material.draw(this, currentSkin, length, points1);
+
+ // Переключаемся на следующий скин
+ prevSkin = currentSkin;
+ currentSkin = currentSkin.nextSkin;
+
+ } else {
+ // Скин текущего примитива дальше по списку скинов
+
+ // Удаление ненужных скинов
+ while (currentSkin != null && _scene.changedPrimitives[currentSkin.primitive]) {
+ removeCurrentSkin();
+ }
+
+ // Переключение на следующий скин
+ if (currentSkin != null) {
+ prevSkin = currentSkin;
+ currentSkin = currentSkin.nextSkin;
+ }
+
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Отсечение полигона плоскостью.
+ */
+ private function clip(length:uint, points1:Array, points2:Array, plane:Point3D, offset:Number, calculateUV:Boolean):uint {
+ var i:uint;
+ var k:Number;
+ var index:uint = 0;
+ var point:DrawPoint;
+ var point1:DrawPoint;
+ var point2:DrawPoint;
+ var offset1:Number;
+ var offset2:Number;
+
+ point1 = points1[length - 1];
+ offset1 = plane.x*point1.x + plane.y*point1.y + plane.z*point1.z - offset;
+
+ if (calculateUV) {
+
+ for (i = 0; i < length; i++) {
+
+ point2 = points1[i];
+ offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset;
+
+ if (offset2 > 0) {
+ if (offset1 <= 0) {
+ k = offset2/(offset2 - offset1);
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k);
+ points2[index] = point;
+ } else {
+ point.x = point2.x - (point2.x - point1.x)*k;
+ point.y = point2.y - (point2.y - point1.y)*k;
+ point.z = point2.z - (point2.z - point1.z)*k;
+ point.u = point2.u - (point2.u - point1.u)*k;
+ point.v = point2.v - (point2.v - point1.v)*k;
+ }
+ index++;
+ }
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x, point2.y, point2.z, point2.u, point2.v);
+ points2[index] = point;
+ } else {
+ point.x = point2.x;
+ point.y = point2.y;
+ point.z = point2.z;
+ point.u = point2.u;
+ point.v = point2.v;
+ }
+ index++;
+ } else {
+ if (offset1 > 0) {
+ k = offset2/(offset2 - offset1);
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k);
+ points2[index] = point;
+ } else {
+ point.x = point2.x - (point2.x - point1.x)*k;
+ point.y = point2.y - (point2.y - point1.y)*k;
+ point.z = point2.z - (point2.z - point1.z)*k;
+ point.u = point2.u - (point2.u - point1.u)*k;
+ point.v = point2.v - (point2.v - point1.v)*k;
+ }
+ index++;
+ }
+ }
+ offset1 = offset2;
+ point1 = point2;
+ }
+
+ } else {
+
+ for (i = 0; i < length; i++) {
+
+ point2 = points1[i];
+ offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset;
+
+ if (offset2 > 0) {
+ if (offset1 <= 0) {
+ k = offset2/(offset2 - offset1);
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k);
+ points2[index] = point;
+ } else {
+ point.x = point2.x - (point2.x - point1.x)*k;
+ point.y = point2.y - (point2.y - point1.y)*k;
+ point.z = point2.z - (point2.z - point1.z)*k;
+ }
+ index++;
+ }
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x, point2.y, point2.z);
+ points2[index] = point;
+ } else {
+ point.x = point2.x;
+ point.y = point2.y;
+ point.z = point2.z;
+ }
+ index++;
+ } else {
+ if (offset1 > 0) {
+ k = offset2/(offset2 - offset1);
+ point = points2[index];
+ if (point == null) {
+ point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k);
+ points2[index] = point;
+ } else {
+ point.x = point2.x - (point2.x - point1.x)*k;
+ point.y = point2.y - (point2.y - point1.y)*k;
+ point.z = point2.z - (point2.z - point1.z)*k;
+ }
+ index++;
+ }
+ }
+ offset1 = offset2;
+ point1 = point2;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * @private
+ * Добавление текущего скина.
+ */
+ private function addCurrentSkin():void {
+ currentSkin = Skin.createSkin();
+ _view.canvas.addChild(currentSkin);
+ if (prevSkin == null) {
+ firstSkin = currentSkin;
+ } else {
+ prevSkin.nextSkin = currentSkin;
+ }
+ }
+
+ /**
+ * @private
+ * Вставляем под текущий скин.
+ */
+ private function insertCurrentSkin():void {
+ var skin:Skin = Skin.createSkin();
+ _view.canvas.addChildAt(skin, _view.canvas.getChildIndex(currentSkin));
+ skin.nextSkin = currentSkin;
+ if (prevSkin == null) {
+ firstSkin = skin;
+ } else {
+ prevSkin.nextSkin = skin;
+ }
+ currentSkin = skin;
+ }
+
+ /**
+ * @private
+ * Удаляет текущий скин.
+ */
+ private function removeCurrentSkin():void {
+ // Сохраняем следующий
+ var next:Skin = currentSkin.nextSkin;
+ // Удаляем из канваса
+ _view.canvas.removeChild(currentSkin);
+ // Очистка скина
+ if (currentSkin.material != null) {
+ currentSkin.material.clear(currentSkin);
+ }
+ // Зачищаем ссылки
+ currentSkin.nextSkin = null;
+ currentSkin.primitive = null;
+ currentSkin.material = null;
+ // Удаляем
+ Skin.destroySkin(currentSkin);
+ // Следующий устанавливаем текущим
+ currentSkin = next;
+ // Устанавливаем связь от предыдущего скина
+ if (prevSkin == null) {
+ firstSkin = currentSkin;
+ } else {
+ prevSkin.nextSkin = currentSkin;
+ }
+ }
+
+ /**
+ * @private
+ */
+ alternativa3d function calculateUVMatrix(face:Face, width:uint, height:uint):void {
+
+ // Расчёт точек базового примитива в координатах камеры
+ var point:Point3D = face.primitive.points[0];
+ textureA.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
+ textureA.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
+ point = face.primitive.points[1];
+ textureB.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
+ textureB.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
+ point = face.primitive.points[2];
+ textureC.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
+ textureC.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
+
+ // Находим AB и AC
+ var abx:Number = textureB.x - textureA.x;
+ var aby:Number = textureB.y - textureA.y;
+ var acx:Number = textureC.x - textureA.x;
+ var acy:Number = textureC.y - textureA.y;
+
+ // Расчёт текстурной матрицы
+ var uvMatrixBase:Matrix = face.uvMatrixBase;
+ var uvMatrix:Matrix = face.uvMatrix;
+ uvMatrix.a = (uvMatrixBase.a*abx + uvMatrixBase.b*acx)/width;
+ uvMatrix.b = (uvMatrixBase.a*aby + uvMatrixBase.b*acy)/width;
+ uvMatrix.c = -(uvMatrixBase.c*abx + uvMatrixBase.d*acx)/height;
+ uvMatrix.d = -(uvMatrixBase.c*aby + uvMatrixBase.d*acy)/height;
+ uvMatrix.tx = (uvMatrixBase.tx + uvMatrixBase.c)*abx + (uvMatrixBase.ty + uvMatrixBase.d)*acx + textureA.x + cameraMatrix.d;
+ uvMatrix.ty = (uvMatrixBase.tx + uvMatrixBase.c)*aby + (uvMatrixBase.ty + uvMatrixBase.d)*acy + textureA.y + cameraMatrix.h;
+
+ // Помечаем, как рассчитанную
+ uvMatricesCalculated[face] = true;
+ }
+
+ /**
+ * Поле вывода, в котором происходит отрисовка камеры.
+ */
+ public function get view():View {
+ return _view;
+ }
+
+ /**
+ * @private
+ */
+ public function set view(value:View):void {
+ if (value != _view) {
+ if (_view != null) {
+ _view.camera = null;
+ }
+ if (value != null) {
+ value.camera = this;
+ }
+ }
+ }
+
+ /**
+ * Включение режима аксонометрической проекции.
+ *
+ * @default false
+ */
+ public function get orthographic():Boolean {
+ return _orthographic;
+ }
+
+ /**
+ * @private
+ */
+ public function set orthographic(value:Boolean):void {
+ if (_orthographic != value) {
+ // Отправляем сигнал об изменении типа камеры
+ addOperationToScene(calculateMatrixOperation);
+ // Сохраняем новое значение
+ _orthographic = value;
+ }
+ }
+
+ /**
+ * Угол поля зрения в радианах в режиме перспективной проекции. При изменении FOV изменяется фокусное расстояние
+ * камеры по формуле f = d/tan(fov/2), где d является половиной диагонали поля вывода.
+ * Угол зрения ограничен диапазоном 0-180 градусов.
+ */
+ public function get fov():Number {
+ return _fov;
+ }
+
+ /**
+ * @private
+ */
+ public function set fov(value:Number):void {
+ value = (value < 0) ? 0 : ((value > (Math.PI - 0.0001)) ? (Math.PI - 0.0001) : value);
+ if (_fov != value) {
+ // Если перспектива
+ if (!_orthographic) {
+ // Отправляем сигнал об изменении плоскостей отсечения
+ addOperationToScene(calculatePlanesOperation);
+ }
+ // Сохраняем новое значение
+ _fov = value;
+ }
+ }
+
+ /**
+ * Коэффициент увеличения изображения в режиме аксонометрической проекции.
+ */
+ public function get zoom():Number {
+ return _zoom;
+ }
+
+ /**
+ * @private
+ */
+ public function set zoom(value:Number):void {
+ value = (value < 0) ? 0 : value;
+ if (_zoom != value) {
+ // Если изометрия
+ if (_orthographic) {
+ // Отправляем сигнал об изменении zoom
+ addOperationToScene(calculateMatrixOperation);
+ }
+ // Сохраняем новое значение
+ _zoom = value;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function addToScene(scene:Scene3D):void {
+ super.addToScene(scene);
+ if (_view != null) {
+ // Отправляем операцию расчёта плоскостей отсечения
+ scene.addOperation(calculatePlanesOperation);
+ // Подписываемся на сигналы сцены
+ scene.changePrimitivesOperation.addSequel(renderOperation);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function removeFromScene(scene:Scene3D):void {
+ super.removeFromScene(scene);
+
+ // Удаляем все операции из очереди
+ scene.removeOperation(calculateMatrixOperation);
+ scene.removeOperation(calculatePlanesOperation);
+ scene.removeOperation(renderOperation);
+
+ if (_view != null) {
+ // Отписываемся от сигналов сцены
+ scene.changePrimitivesOperation.removeSequel(renderOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ alternativa3d function addToView(view:View):void {
+ // Сохраняем первый скин
+ firstSkin = (view.canvas.numChildren > 0) ? Skin(view.canvas.getChildAt(0)) : null;
+
+ // Подписка на свои операции
+
+ // При изменении камеры пересчёт матрицы
+ calculateTransformationOperation.addSequel(calculateMatrixOperation);
+ // При изменении матрицы или FOV пересчёт плоскостей отсечения
+ calculateMatrixOperation.addSequel(calculatePlanesOperation);
+ // При изменении плоскостей перерисовка
+ calculatePlanesOperation.addSequel(renderOperation);
+
+ if (_scene != null) {
+ // Отправляем сигнал перерисовки
+ _scene.addOperation(calculateMatrixOperation);
+ // Подписываемся на сигналы сцены
+ _scene.changePrimitivesOperation.addSequel(renderOperation);
+ }
+
+ // Сохраняем вид
+ _view = view;
+ }
+
+ /**
+ * @private
+ */
+ alternativa3d function removeFromView(view:View):void {
+ // Сброс ссылки на первый скин
+ firstSkin = null;
+
+ // Отписка от своих операций
+
+ // При изменении камеры пересчёт матрицы
+ calculateTransformationOperation.removeSequel(calculateMatrixOperation);
+ // При изменении матрицы или FOV пересчёт плоскостей отсечения
+ calculateMatrixOperation.removeSequel(calculatePlanesOperation);
+ // При изменении плоскостей перерисовка
+ calculatePlanesOperation.removeSequel(renderOperation);
+
+ if (_scene != null) {
+ // Удаляем все операции из очереди
+ _scene.removeOperation(calculateMatrixOperation);
+ _scene.removeOperation(calculatePlanesOperation);
+ _scene.removeOperation(renderOperation);
+ // Отписываемся от сигналов сцены
+ _scene.changePrimitivesOperation.removeSequel(renderOperation);
+ }
+ // Удаляем ссылку на вид
+ _view = null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function defaultName():String {
+ return "camera" + ++counter;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Camera3D();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function clonePropertiesFrom(source:Object3D):void {
+ super.clonePropertiesFrom(source);
+
+ var src:Camera3D = Camera3D(source);
+ orthographic = src._orthographic;
+ zoom = src._zoom;
+ fov = src._fov;
+ }
+
+ /**
+ * Определяет, выполняется ли отсечение по ближней плоскости. Если задано значение true, объекты или их части, имеющие в камере
+ * координату Z меньше заданного значения, не отрисовываются. Если задано значение false, отсечение не выполняется.
+ *
+ * @see #farClipping
+ * @see #viewClipping
+ * @see #nearClippingDistance
+ *
+ * @default false
+ */
+ public function get nearClipping():Boolean {
+ return _nearClipping;
+ }
+
+ /**
+ * @private
+ */
+ public function set nearClipping(value:Boolean):void {
+ if (_nearClipping != value) {
+ _nearClipping = value;
+ addOperationToScene(calculatePlanesOperation);
+ }
+ }
+
+ /**
+ * Расстояние до ближней плоскости отсечения в системе координат камеры. При задании отрицательного значения свойству присваивается ноль.
+ *
+ * @see #nearClipping
+ *
+ * @default 10
+ */
+ public function get nearClippingDistance():Number {
+ return _nearClippingDistance;
+ }
+
+ /**
+ * @private
+ */
+ public function set nearClippingDistance(value:Number):void {
+ if (value < 0) {
+ value = 0;
+ }
+ if (_nearClippingDistance != value) {
+ _nearClippingDistance = value;
+ addOperationToScene(calculatePlanesOperation);
+ }
+ }
+
+ /**
+ * Определяет, выполняется ли отсечение по дальней плоскости. Если задано значение true, объекты или их части, имеющие в камере
+ * координату Z больше заданного значения, не отрисовываются. Если задано значение false, отсечение не выполняется.
+ *
+ * @see #nearClipping
+ * @see #viewClipping
+ * @see #farClippingDistance
+ *
+ * @default false
+ */
+ public function get farClipping():Boolean {
+ return _farClipping;
+ }
+
+ /**
+ * @private
+ */
+ public function set farClipping(value:Boolean):void {
+ if (_farClipping != value) {
+ _farClipping = value;
+ addOperationToScene(calculatePlanesOperation);
+ }
+ }
+
+ /**
+ * Расстояние до дальней плоскости отсечения в системе координат камеры. При задании отрицательного значения свойству присваивается ноль.
+ *
+ * @see #farClipping
+ *
+ * @default 1000
+ */
+ public function get farClippingDistance():Number {
+ return _farClippingDistance;
+ }
+
+ /**
+ * @private
+ */
+ public function set farClippingDistance(value:Number):void {
+ if (value < 0) {
+ value = 0;
+ }
+ if (_farClippingDistance != value) {
+ _farClippingDistance = value;
+ addOperationToScene(calculatePlanesOperation);
+ }
+ }
+
+ /**
+ * Определяет, выполняется ли отсечение по пирамиде видимости. Если задано значение true, выполняется отсечение объектов сцены по
+ * пирамиде видимости. Если задано значение false, отсечение не выполняется, тем самым ускоряя отрисовку. Такой режим полезен, когда
+ * заранее известно, что вся сцена находится в пирамиде видимости. Не следует отключать отсечение по пирамиде видимости одновременно с
+ * отсечением по ближней плоскости, если позади камеры находятся объекты или части объектов, т.к. это приведёт к артефактам отрисовки,
+ * а при использовании текстурных материалов и к зацикливанию адаптивной триангуляции.
+ *
+ * @see #nearClipping
+ * @see #farClipping
+ *
+ * @default true
+ */
+ public function get viewClipping():Boolean {
+ return _viewClipping;
+ }
+
+ /**
+ * @private
+ */
+ public function set viewClipping(value:Boolean):void {
+ if (_viewClipping != value) {
+ _viewClipping = value;
+ addOperationToScene(calculatePlanesOperation);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Face.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Face.as
new file mode 100644
index 0000000..de8516d
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Face.as
@@ -0,0 +1,1078 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IEventDispatcher;
+ import flash.geom.Matrix;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одной и той же гранью.
+ * Между нажатием и отпусканием кнопки могут происходить любые другие события.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.CLICK
+ */
+ [Event (name="click", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь нажимает левую кнопку мыши над гранью.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN
+ */
+ [Event (name="mouseDown", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь отпускает левую кнопку мыши над гранью.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP
+ */
+ [Event (name="mouseUp", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь наводит курсор мыши на грань.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER
+ */
+ [Event (name="mouseOver", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь уводит курсор мыши с грани.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT
+ */
+ [Event (name="mouseOut", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь перемещает курсор мыши над гранью.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE
+ */
+ [Event (name="mouseMove", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь вращает колесо мыши над гранью.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL
+ */
+ [Event (name="mouseWheel", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Грань, образованная тремя или более вершинами. Грани являются составными частями полигональных объектов. Каждая грань
+ * содержит информацию об объекте и поверхности, которым она принадлежит. Для обеспечения возможности наложения
+ * текстуры на грань, первым трём её вершинам могут быть заданы UV-координаты, на основании которых расчитывается
+ * матрица трансформации текстуры.
+ *
+ * flash.events.IEventDispatcher и может рассылать мышиные события, содержащие информацию
+ * о точке в трёхмерном пространстве, в которой произошло событие.
+ */
+ final public class Face implements IEventDispatcher {
+ // Погрешность определения вырожденной UV матрицы
+ private static const uvThreshold:Number = 1.0/2880;
+ // Операции
+ /**
+ * @private
+ * Расчёт глобальной нормали плоскости грани.
+ */
+ alternativa3d var calculateNormalOperation:Operation = new Operation("calculateNormal", this, calculateNormal, Operation.FACE_CALCULATE_NORMAL);
+ /**
+ * @private
+ * Расчёт UV-координат (выполняется до трансформации, чтобы UV корректно разбились при построении BSP).
+ */
+ alternativa3d var calculateUVOperation:Operation = new Operation("calculateUV", this, calculateUV, Operation.FACE_CALCULATE_UV);
+ /**
+ * @private
+ * Обновление примитива в сцене.
+ */
+ alternativa3d var updatePrimitiveOperation:Operation = new Operation("updatePrimitive", this, updatePrimitive, Operation.FACE_UPDATE_PRIMITIVE);
+ /**
+ * @private
+ * Обновление материала.
+ */
+ alternativa3d var updateMaterialOperation:Operation = new Operation("updateMaterial", this, updateMaterial, Operation.FACE_UPDATE_MATERIAL);
+ /**
+ * @private
+ * Расчёт UV для фрагментов (выполняется после трансформации, если её не было).
+ */
+ alternativa3d var calculateFragmentsUVOperation:Operation = new Operation("calculateFragmentsUV", this, calculateFragmentsUV, Operation.FACE_CALCULATE_FRAGMENTS_UV);
+
+ /**
+ * @private
+ * Меш
+ */
+ alternativa3d var _mesh:Mesh;
+ /**
+ * @private
+ * Поверхность
+ */
+ alternativa3d var _surface:Surface;
+ /**
+ * @private
+ * Вершины грани
+ */
+ alternativa3d var _vertices:Array;
+ /**
+ * @private
+ * Количество вершин
+ */
+ alternativa3d var _verticesCount:uint;
+ /**
+ * @private
+ * Примитив
+ */
+ alternativa3d var primitive:PolyPrimitive;
+
+ // UV-координаты
+ /**
+ * @private
+ */
+ alternativa3d var _aUV:Point;
+ /**
+ * @private
+ */
+ alternativa3d var _bUV:Point;
+ /**
+ * @private
+ */
+ alternativa3d var _cUV:Point;
+
+ /**
+ * @private
+ * Коэффициенты базовой UV-матрицы
+ */
+ alternativa3d var uvMatrixBase:Matrix;
+
+ /**
+ * @private
+ * UV Матрица перевода текстурных координат в изометрическую камеру.
+ */
+ alternativa3d var uvMatrix:Matrix;
+ /**
+ * @private
+ * Нормаль плоскости
+ */
+ alternativa3d var globalNormal:Point3D = new Point3D();
+ /**
+ * @private
+ * Смещение плоскости
+ */
+ alternativa3d var globalOffset:Number;
+
+ /**
+ * Флаг указывает, будет ли объект принимать мышиные события.
+ */
+ public var mouseEnabled:Boolean = true;
+ /**
+ * Диспетчер событий.
+ */
+ private var dispatcher:EventDispatcher;
+
+ /**
+ * Создание экземпляра грани.
+ *
+ * @param vertices массив объектов типа alternativa.engine3d.core.Vertex, задающий вершины грани в
+ * порядке обхода лицевой стороны грани против часовой стрелки.
+ *
+ * @see Vertex
+ */
+ public function Face(vertices:Array) {
+ // Сохраняем вершины
+ _vertices = vertices;
+ _verticesCount = vertices.length;
+
+ // Создаём оригинальный примитив
+ primitive = PolyPrimitive.createPolyPrimitive();
+ primitive.face = this;
+ primitive.num = _verticesCount;
+
+ // Обрабатываем вершины
+ for (var i:uint = 0; i < _verticesCount; i++) {
+ var vertex:Vertex = vertices[i];
+ // Добавляем координаты вершины в примитив
+ primitive.points.push(vertex.globalCoords);
+ // Добавляем пустые UV-координаты в примитив
+ primitive.uvs.push(null);
+ // Добавляем вершину в грань
+ vertex.addToFace(this);
+ }
+
+ // Расчёт нормали
+ calculateNormalOperation.addSequel(updatePrimitiveOperation);
+
+ // Расчёт UV грани инициирует расчёт UV фрагментов и перерисовку
+ calculateUVOperation.addSequel(calculateFragmentsUVOperation);
+ calculateUVOperation.addSequel(updateMaterialOperation);
+ }
+
+ /**
+ * @private
+ * Расчёт нормали в глобальных координатах
+ */
+ private function calculateNormal():void {
+ // Вектор AB
+ var vertex:Vertex = _vertices[0];
+ var av:Point3D = vertex.globalCoords;
+ vertex = _vertices[1];
+ var bv:Point3D = vertex.globalCoords;
+ var abx:Number = bv.x - av.x;
+ var aby:Number = bv.y - av.y;
+ var abz:Number = bv.z - av.z;
+ // Вектор AC
+ vertex = _vertices[2];
+ var cv:Point3D = vertex.globalCoords;
+ var acx:Number = cv.x - av.x;
+ var acy:Number = cv.y - av.y;
+ var acz:Number = cv.z - av.z;
+ // Перпендикуляр к плоскости
+ globalNormal.x = acz*aby - acy*abz;
+ globalNormal.y = acx*abz - acz*abx;
+ globalNormal.z = acy*abx - acx*aby;
+ // Нормализация перпендикуляра
+ globalNormal.normalize();
+ }
+
+ /**
+ * @private
+ * Расчитывает глобальное смещение плоскости грани.
+ * Помечает конечные примитивы на удаление, а базовый на добавление в сцене.
+ */
+ private function updatePrimitive():void {
+ // Расчёт смещения
+ var vertex:Vertex = _vertices[0];
+ globalOffset = vertex.globalCoords.x*globalNormal.x + vertex.globalCoords.y*globalNormal.y + vertex.globalCoords.z*globalNormal.z;
+
+ removePrimitive(primitive);
+ primitive.mobility = _mesh.inheritedMobility;
+ _mesh._scene.addPrimitives.push(primitive);
+ }
+
+ /**
+ * @private
+ * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на удаление из сцены
+ */
+ private function removePrimitive(primitive:PolyPrimitive):void {
+ if (primitive.backFragment != null) {
+ removePrimitive(primitive.backFragment);
+ removePrimitive(primitive.frontFragment);
+ primitive.backFragment = null;
+ primitive.frontFragment = null;
+ if (primitive != this.primitive) {
+ primitive.parent = null;
+ primitive.sibling = null;
+ PolyPrimitive.destroyPolyPrimitive(primitive);
+ }
+ } else {
+ // Если примитив в BSP-дереве
+ if (primitive.node != null) {
+ // Удаление примитива
+ _mesh._scene.removeBSPPrimitive(primitive);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Пометка на перерисовку фрагментов грани.
+ */
+ private function updateMaterial():void {
+ if (!updatePrimitiveOperation.queued) {
+ changePrimitive(primitive);
+ }
+ }
+
+ /**
+ * @private
+ * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на перерисовку
+ */
+ private function changePrimitive(primitive:PolyPrimitive):void {
+ if (primitive.backFragment != null) {
+ changePrimitive(primitive.backFragment);
+ changePrimitive(primitive.frontFragment);
+ } else {
+ _mesh._scene.changedPrimitives[primitive] = true;
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт UV-матрицы на основании первых трёх UV-координат.
+ * Расчёт UV-координат для оставшихся точек.
+ */
+ private function calculateUV():void {
+ var i:uint;
+ var uv:Point;
+ // Расчёт UV-матрицы
+ if (_aUV != null && _bUV != null && _cUV != null) {
+ var abu:Number = _bUV.x - _aUV.x;
+ var abv:Number = _bUV.y - _aUV.y;
+ var acu:Number = _cUV.x - _aUV.x;
+ var acv:Number = _cUV.y - _aUV.y;
+ var det:Number = abu*acv - abv*acu;
+
+ // Проверка на нулевой определитель
+ if (det < uvThreshold && det > -uvThreshold) {
+ var len:Number;
+ if (abu < uvThreshold && abu > -uvThreshold && abv < uvThreshold && abv > -uvThreshold) {
+ if (acu < uvThreshold && acu > -uvThreshold && acv < uvThreshold && acv > -uvThreshold) {
+ // Оба вырождены
+ abu = uvThreshold;
+ acv = uvThreshold;
+ } else {
+ // Вырожден AB
+ len = Math.sqrt(acu*acu + acv*acv);
+ abu = uvThreshold*acv/len;
+ abv = -uvThreshold*acu/len;
+ }
+ } else {
+ if (acu < uvThreshold && acu > -uvThreshold && acv < uvThreshold && acv > -uvThreshold) {
+ //Вырожден AC
+ len = Math.sqrt(abu*abu + abv*abv);
+ acu = -uvThreshold*abv/len;
+ acv = uvThreshold*abu/len;
+ } else {
+ // Сонаправлены
+ len = Math.sqrt(abu*abu + abv*abv);
+ acu += uvThreshold*abv/len;
+ acv -= uvThreshold*abu/len;
+ }
+ }
+
+ // Пересчитываем определитель
+ det = abu*acv - abv*acu;
+
+ // Заполняем UV в базовом примитиве
+ primitive.uvs[0] = _aUV;
+ primitive.uvs[1] = new Point(_aUV.x + abu, _aUV.y + abv);
+ primitive.uvs[2] = new Point(_aUV.x + acu, _aUV.y + acv);
+ } else {
+ // Заполняем UV в базовом примитиве
+ primitive.uvs[0] = _aUV;
+ primitive.uvs[1] = _bUV;
+ primitive.uvs[2] = _cUV;
+ }
+
+ // Создаём матрицу
+ if (uvMatrixBase == null) {
+ uvMatrixBase = new Matrix();
+ uvMatrix = new Matrix();
+ }
+ uvMatrixBase.a = acv/det;
+ uvMatrixBase.b = -abv/det;
+ uvMatrixBase.c = -acu/det;
+ uvMatrixBase.d = abu/det;
+ uvMatrixBase.tx = -(uvMatrixBase.a*_aUV.x + uvMatrixBase.c*_aUV.y);
+ uvMatrixBase.ty = -(uvMatrixBase.b*_aUV.x + uvMatrixBase.d*_aUV.y);
+
+ // Расчёт недостающих UV
+ if (_verticesCount > 3) {
+ var a:Point3D = primitive.points[0];
+ var b:Point3D = primitive.points[1];
+ var c:Point3D = primitive.points[2];
+
+ var ab1:Number;
+ var ab2:Number;
+ var ac1:Number;
+ var ac2:Number;
+ var ad1:Number;
+ var ad2:Number;
+ var abk:Number;
+ var ack:Number;
+
+ var point:Point3D;
+
+ // Выбор наиболее подходящих осей для расчёта
+ if (((globalNormal.x < 0) ? -globalNormal.x : globalNormal.x) > ((globalNormal.y < 0) ? -globalNormal.y : globalNormal.y)) {
+ if (((globalNormal.x < 0) ? -globalNormal.x : globalNormal.x) > ((globalNormal.z < 0) ? -globalNormal.z : globalNormal.z)) {
+ // Ось X
+ ab1 = b.y - a.y;
+ ab2 = b.z - a.z;
+ ac1 = c.y - a.y;
+ ac2 = c.z - a.z;
+ det = ab1*ac2 - ac1*ab2;
+ for (i = 3; i < _verticesCount; i++) {
+ point = primitive.points[i];
+ ad1 = point.y - a.y;
+ ad2 = point.z - a.z;
+ abk = (ad1*ac2 - ac1*ad2)/det;
+ ack = (ab1*ad2 - ad1*ab2)/det;
+ uv = primitive.uvs[i];
+ if (uv == null) {
+ uv = new Point();
+ primitive.uvs[i] = uv;
+ }
+ uv.x = _aUV.x + abu*abk + acu*ack;
+ uv.y = _aUV.y + abv*abk + acv*ack;
+ }
+ } else {
+ // Ось Z
+ ab1 = b.x - a.x;
+ ab2 = b.y - a.y;
+ ac1 = c.x - a.x;
+ ac2 = c.y - a.y;
+ det = ab1*ac2 - ac1*ab2;
+ for (i = 3; i < _verticesCount; i++) {
+ point = primitive.points[i];
+ ad1 = point.x - a.x;
+ ad2 = point.y - a.y;
+ abk = (ad1*ac2 - ac1*ad2)/det;
+ ack = (ab1*ad2 - ad1*ab2)/det;
+ uv = primitive.uvs[i];
+ if (uv == null) {
+ uv = new Point();
+ primitive.uvs[i] = uv;
+ }
+ uv.x = _aUV.x + abu*abk + acu*ack;
+ uv.y = _aUV.y + abv*abk + acv*ack;
+ }
+ }
+ } else {
+ if (((globalNormal.y < 0) ? -globalNormal.y : globalNormal.y) > ((globalNormal.z < 0) ? -globalNormal.z : globalNormal.z)) {
+ // Ось Y
+ ab1 = b.x - a.x;
+ ab2 = b.z - a.z;
+ ac1 = c.x - a.x;
+ ac2 = c.z - a.z;
+ det = ab1*ac2 - ac1*ab2;
+ for (i = 3; i < _verticesCount; i++) {
+ point = primitive.points[i];
+ ad1 = point.x - a.x;
+ ad2 = point.z - a.z;
+ abk = (ad1*ac2 - ac1*ad2)/det;
+ ack = (ab1*ad2 - ad1*ab2)/det;
+ uv = primitive.uvs[i];
+ if (uv == null) {
+ uv = new Point();
+ primitive.uvs[i] = uv;
+ }
+ uv.x = _aUV.x + abu*abk + acu*ack;
+ uv.y = _aUV.y + abv*abk + acv*ack;
+ }
+ } else {
+ // Ось Z
+ ab1 = b.x - a.x;
+ ab2 = b.y - a.y;
+ ac1 = c.x - a.x;
+ ac2 = c.y - a.y;
+ det = ab1*ac2 - ac1*ab2;
+ for (i = 3; i < _verticesCount; i++) {
+ point = primitive.points[i];
+ ad1 = point.x - a.x;
+ ad2 = point.y - a.y;
+ abk = (ad1*ac2 - ac1*ad2)/det;
+ ack = (ab1*ad2 - ad1*ab2)/det;
+ uv = primitive.uvs[i];
+ if (uv == null) {
+ uv = new Point();
+ primitive.uvs[i] = uv;
+ }
+ uv.x = _aUV.x + abu*abk + acu*ack;
+ uv.y = _aUV.y + abv*abk + acv*ack;
+ }
+ }
+ }
+ }
+ } else {
+ // Удаляем UV-матрицу
+ uvMatrixBase = null;
+ uvMatrix = null;
+ // Удаляем UV-координаты из базового примитива
+ for (i = 0; i < _verticesCount; i++) {
+ primitive.uvs[i] = null;
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт UV-координат для фрагментов примитива, если не было трансформации
+ */
+ private function calculateFragmentsUV():void {
+ // Если в этом цикле не было трансформации
+ if (!updatePrimitiveOperation.queued) {
+ if (uvMatrixBase != null) {
+ // Рассчитываем UV в примитиве
+ calculatePrimitiveUV(primitive);
+ } else {
+ // Удаляем UV в примитиве
+ removePrimitiveUV(primitive);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт UV для точек базового примитива.
+ *
+ * @param primitive
+ */
+ private function calculatePrimitiveUV(primitive:PolyPrimitive):void {
+ if (primitive.backFragment != null) {
+ var points:Array = primitive.points;
+ var backPoints:Array = primitive.backFragment.points;
+ var frontPoints:Array = primitive.frontFragment.points;
+ var uvs:Array = primitive.uvs;
+ var backUVs:Array = primitive.backFragment.uvs;
+ var frontUVs:Array = primitive.frontFragment.uvs;
+ var index1:uint = 0;
+ var index2:uint = 0;
+ var point:Point3D;
+ var uv:Point;
+ var uv1:Point;
+ var uv2:Point;
+ var t:Number;
+ var firstSplit:Boolean = true;
+ for (var i:uint = 0; i < primitive.num; i++) {
+ var split:Boolean = true;
+ point = points[i];
+ if (point == frontPoints[index2]) {
+ if (frontUVs[index2] == null) {
+ frontUVs[index2] = uvs[i];
+ }
+ split = false;
+ index2++;
+ }
+ if (point == backPoints[index1]) {
+ if (backUVs[index1] == null) {
+ backUVs[index1] = uvs[i];
+ }
+ split = false;
+ index1++;
+ }
+
+ if (split) {
+ uv1 = uvs[(i == 0) ? (primitive.num - 1) : (i - 1)];
+ uv2 = uvs[i];
+ t = (firstSplit) ? primitive.splitTime1 : primitive.splitTime2;
+ uv = frontUVs[index2];
+ if (uv == null) {
+ uv = new Point(uv1.x + (uv2.x - uv1.x)*t, uv1.y + (uv2.y - uv1.y)*t);
+ frontUVs[index2] = uv;
+ backUVs[index1] = uv;
+ } else {
+ uv.x = uv1.x + (uv2.x - uv1.x)*t;
+ uv.y = uv1.y + (uv2.y - uv1.y)*t;
+ }
+ firstSplit = false;
+ index2++;
+ index1++;
+ if (point == frontPoints[index2]) {
+ if (frontUVs[index2] == null) {
+ frontUVs[index2] = uvs[i];
+ }
+ index2++;
+ }
+ if (point == backPoints[index1]) {
+ if (backUVs[index1] == null) {
+ backUVs[index1] = uvs[i];
+ }
+ index1++;
+ }
+ }
+ }
+ // Проверяем рассечение последнего ребра
+ if (index2 < primitive.frontFragment.num) {
+ uv1 = uvs[primitive.num - 1];
+ uv2 = uvs[0];
+ t = (firstSplit) ? primitive.splitTime1 : primitive.splitTime2;
+ uv = frontUVs[index2];
+ if (uv == null) {
+ uv = new Point(uv1.x + (uv2.x - uv1.x)*t, uv1.y + (uv2.y - uv1.y)*t);
+ frontUVs[index2] = uv;
+ backUVs[index1] = uv;
+ } else {
+ uv.x = uv1.x + (uv2.x - uv1.x)*t;
+ uv.y = uv1.y + (uv2.y - uv1.y)*t;
+ }
+ }
+
+ calculatePrimitiveUV(primitive.backFragment);
+ calculatePrimitiveUV(primitive.frontFragment);
+ }
+ }
+
+ /**
+ * @private
+ * Удаление UV в примитиве и его фрагментах
+ * @param primitive
+ */
+ private function removePrimitiveUV(primitive:PolyPrimitive):void {
+ // Очищаем список UV
+ for (var i:uint = 0; i < primitive.num; i++) {
+ primitive.uvs[i] = null;
+ }
+ // Если есть фрагменты, удаляем UV в них
+ if (primitive.backFragment != null) {
+ removePrimitiveUV(primitive.backFragment);
+ removePrimitiveUV(primitive.frontFragment);
+ }
+ }
+
+ /**
+ * Массив вершин грани, представленных объектами класса alternativa.engine3d.core.Vertex.
+ *
+ * @see Vertex
+ */
+ public function get vertices():Array {
+ return new Array().concat(_vertices);
+ }
+
+ /**
+ * Количество вершин грани.
+ */
+ public function get verticesCount():uint {
+ return _verticesCount;
+ }
+
+ /**
+ * Полигональный объект, которому принадлежит грань.
+ */
+ public function get mesh():Mesh {
+ return _mesh;
+ }
+
+ /**
+ * Поверхность, которой принадлежит грань.
+ */
+ public function get surface():Surface {
+ return _surface;
+ }
+
+ /**
+ * Идентификатор грани в полигональном объекте. В случае, если грань не принадлежит ни одному объекту, идентификатор
+ * имеет значение null.
+ */
+ public function get id():Object {
+ return (_mesh != null) ? _mesh.getFaceId(this) : null;
+ }
+
+ /**
+ * UV-координаты, соответствующие первой вершине грани.
+ */
+ public function get aUV():Point {
+ return (_aUV != null) ? _aUV.clone() : null;
+ }
+
+ /**
+ * UV-координаты, соответствующие второй вершине грани.
+ */
+ public function get bUV():Point {
+ return (_bUV != null) ? _bUV.clone() : null;
+ }
+
+ /**
+ * UV-координаты, соответствующие третьей вершине грани.
+ */
+ public function get cUV():Point {
+ return (_cUV != null) ? _cUV.clone() : null;
+ }
+
+ /**
+ * @private
+ */
+ public function set aUV(value:Point):void {
+ if (_aUV != null) {
+ if (value != null) {
+ if (!_aUV.equals(value)) {
+ _aUV.x = value.x;
+ _aUV.y = value.y;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ _aUV = null;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ if (value != null) {
+ _aUV = value.clone();
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set bUV(value:Point):void {
+ if (_bUV != null) {
+ if (value != null) {
+ if (!_bUV.equals(value)) {
+ _bUV.x = value.x;
+ _bUV.y = value.y;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ _bUV = null;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ if (value != null) {
+ _bUV = value.clone();
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set cUV(value:Point):void {
+ if (_cUV != null) {
+ if (value != null) {
+ if (!_cUV.equals(value)) {
+ _cUV.x = value.x;
+ _cUV.y = value.y;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ _cUV = null;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ } else {
+ if (value != null) {
+ _cUV = value.clone();
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateUVOperation);
+ }
+ }
+ }
+ }
+
+ /**
+ * Нормаль в локальной системе координат.
+ */
+ public function get normal():Point3D {
+ var res:Point3D = new Point3D();
+ var vertex:Vertex = _vertices[0];
+ var av:Point3D = vertex.coords;
+ vertex = _vertices[1];
+ var bv:Point3D = vertex.coords;
+ var abx:Number = bv.x - av.x;
+ var aby:Number = bv.y - av.y;
+ var abz:Number = bv.z - av.z;
+ vertex = _vertices[2];
+ var cv:Point3D = vertex.coords;
+ var acx:Number = cv.x - av.x;
+ var acy:Number = cv.y - av.y;
+ var acz:Number = cv.z - av.z;
+ res.x = acz*aby - acy*abz;
+ res.y = acx*abz - acz*abx;
+ res.z = acy*abx - acx*aby;
+ if (res.x != 0 || res.y != 0 || res.z != 0) {
+ var k:Number = Math.sqrt(res.x*res.x + res.y*res.y + res.z*res.z);
+ res.x /= k;
+ res.y /= k;
+ res.z /= k;
+ }
+ return res;
+ }
+
+ /**
+ * Расчёт UV-координат для произвольной точки в системе координат объекта, которому принадлежит грань.
+ *
+ * @param point точка в плоскости грани, для которой производится расчёт UV-координат
+ * @return UV-координаты заданной точки
+ */
+ public function getUV(point:Point3D):Point {
+ return getUVFast(point, normal);
+ }
+
+ /**
+ * @private
+ * Расчёт UV-координат для произвольной точки в локальной системе координат без расчёта
+ * локальной нормали грани. Используется для оптимизации.
+ *
+ * @param point точка в плоскости грани, для которой производится расчёт UV-координат
+ * @param normal нормаль плоскости грани в локальной системе координат
+ * @return UV-координаты заданной точки
+ */
+ alternativa3d function getUVFast(point:Point3D, normal:Point3D):Point {
+ if (_aUV == null || _bUV == null || _cUV == null) {
+ return null;
+ }
+
+ // Выбор наиболее длинной оси нормали
+ var dir:uint;
+ if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.y < 0) ? -normal.y : normal.y)) {
+ if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.z < 0) ? -normal.z : normal.z)) {
+ dir = 0;
+ } else {
+ dir = 2;
+ }
+ } else {
+ if (((normal.y < 0) ? -normal.y : normal.y) > ((normal.z < 0) ? -normal.z : normal.z)) {
+ dir = 1;
+ } else {
+ dir = 2;
+ }
+ }
+
+ // Расчёт соотношения по векторам AB и AC
+ var v:Vertex = _vertices[0];
+ var a:Point3D = v._coords;
+ v = _vertices[1];
+ var b:Point3D = v._coords;
+ v = _vertices[2];
+ var c:Point3D = v._coords;
+
+ var ab1:Number = (dir == 0) ? (b.y - a.y) : (b.x - a.x);
+ var ab2:Number = (dir == 2) ? (b.y - a.y) : (b.z - a.z);
+ var ac1:Number = (dir == 0) ? (c.y - a.y) : (c.x - a.x);
+ var ac2:Number = (dir == 2) ? (c.y - a.y) : (c.z - a.z);
+ var det:Number = ab1*ac2 - ac1*ab2;
+
+ var ad1:Number = (dir == 0) ? (point.y - a.y) : (point.x - a.x);
+ var ad2:Number = (dir == 2) ? (point.y - a.y) : (point.z - a.z);
+ var abk:Number = (ad1*ac2 - ac1*ad2)/det;
+ var ack:Number = (ab1*ad2 - ad1*ab2)/det;
+
+ // Интерполяция по UV первых точек
+ var abu:Number = _bUV.x - _aUV.x;
+ var abv:Number = _bUV.y - _aUV.y;
+ var acu:Number = _cUV.x - _aUV.x;
+ var acv:Number = _cUV.y - _aUV.y;
+
+ return new Point(_aUV.x + abu*abk + acu*ack, _aUV.y + abv*abk + acv*ack);
+ }
+
+ /**
+ * Множество граней, имеющих общие рёбра с текущей гранью.
+ */
+ public function get edgeJoinedFaces():Set {
+ var res:Set = new Set(true);
+ // Перебираем точки грани
+ for (var i:uint = 0; i < _verticesCount; i++) {
+ var a:Vertex = _vertices[i];
+ var b:Vertex = _vertices[(i < _verticesCount - 1) ? (i + 1) : 0];
+
+ // Перебираем грани текущей точки
+ for (var key:* in a._faces) {
+ var face:Face = key;
+ // Если это другая грань и у неё также есть следующая точка
+ if (face != this && face._vertices.indexOf(b) >= 0) {
+ // Значит у граней общее ребро
+ res[face] = true;
+ }
+ }
+ }
+ return res;
+ }
+
+ /**
+ * @private
+ * Удаление всех вершин из грани.
+ * Очистка базового примитива.
+ */
+ alternativa3d function removeVertices():void {
+ // Удалить вершины
+ for (var i:uint = 0; i < _verticesCount; i++) {
+ // Удаляем из списка
+ var vertex:Vertex = _vertices.pop();
+ // Удаляем координаты вершины из примитива
+ primitive.points.pop();
+ // Удаляем вершину из грани
+ vertex.removeFromFace(this);
+ }
+ // Обнуляем количество вершин
+ _verticesCount = 0;
+ }
+
+ /**
+ * @private
+ * Добавление грани на сцену
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {
+ // При добавлении на сцену рассчитываем плоскость и UV
+ scene.addOperation(calculateNormalOperation);
+ scene.addOperation(calculateUVOperation);
+
+ // Подписываем сцену на операции
+ updatePrimitiveOperation.addSequel(scene.calculateBSPOperation);
+ updateMaterialOperation.addSequel(scene.changePrimitivesOperation);
+ }
+
+ /**
+ * @private
+ * Удаление грани из сцены
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(calculateUVOperation);
+ scene.removeOperation(calculateFragmentsUVOperation);
+ scene.removeOperation(calculateNormalOperation);
+ scene.removeOperation(updatePrimitiveOperation);
+ scene.removeOperation(updateMaterialOperation);
+
+ // Удаляем примитивы из сцены
+ removePrimitive(primitive);
+
+ // Посылаем операцию сцены на расчёт BSP
+ scene.addOperation(scene.calculateBSPOperation);
+
+ // Отписываем сцену от операций
+ updatePrimitiveOperation.removeSequel(scene.calculateBSPOperation);
+ updateMaterialOperation.removeSequel(scene.changePrimitivesOperation);
+ }
+
+ /**
+ * @private
+ * Добавление грани в меш
+ * @param mesh
+ */
+ alternativa3d function addToMesh(mesh:Mesh):void {
+ // Подписка на операции меша
+ mesh.changeCoordsOperation.addSequel(updatePrimitiveOperation);
+ mesh.changeRotationOrScaleOperation.addSequel(calculateNormalOperation);
+ mesh.calculateMobilityOperation.addSequel(updatePrimitiveOperation);
+ // Сохранить меш
+ _mesh = mesh;
+ }
+
+ /**
+ * @private
+ * Удаление грани из меша
+ * @param mesh
+ */
+ alternativa3d function removeFromMesh(mesh:Mesh):void {
+ // Отписка от операций меша
+ mesh.changeCoordsOperation.removeSequel(updatePrimitiveOperation);
+ mesh.changeRotationOrScaleOperation.removeSequel(calculateNormalOperation);
+ mesh.calculateMobilityOperation.removeSequel(updatePrimitiveOperation);
+ // Удалить ссылку на меш
+ _mesh = null;
+ }
+
+ /**
+ * @private
+ * Добавление к поверхности
+ *
+ * @param surface
+ */
+ alternativa3d function addToSurface(surface:Surface):void {
+ // Подписка поверхности на операции
+ surface.changeMaterialOperation.addSequel(updateMaterialOperation);
+ // Если при смене поверхности изменился материал
+ if (_mesh != null && (_surface != null && _surface._material != surface._material || _surface == null && surface._material != null)) {
+ // Отправляем сигнал смены материала
+ _mesh.addOperationToScene(updateMaterialOperation);
+ }
+ // Сохранить поверхность
+ _surface = surface;
+ }
+
+ /**
+ * @private
+ * Удаление из поверхности
+ *
+ * @param surface
+ */
+ alternativa3d function removeFromSurface(surface:Surface):void {
+ // Отписка поверхности от операций
+ surface.changeMaterialOperation.removeSequel(updateMaterialOperation);
+ // Если был материал
+ if (surface._material != null) {
+ // Отправляем сигнал смены материала
+ _mesh.addOperationToScene(updateMaterialOperation);
+ }
+ // Удалить ссылку на поверхность
+ _surface = null;
+ }
+
+ /**
+ * Строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ var res:String = "[Face ID:" + id + ((_verticesCount > 0) ? " vertices:" : "");
+ for (var i:uint = 0; i < _verticesCount; i++) {
+ var vertex:Vertex = _vertices[i];
+ res += vertex.id + ((i < _verticesCount - 1) ? ", " : "");
+ }
+ res += "]";
+ return res;
+ }
+
+ /**
+ * Добавление обработчика события.
+ *
+ * @param type тип события
+ * @param listener обработчик события
+ * @param useCapture не используется,
+ * @param priority приоритет обработчика. Обработчики с большим приоритетом выполняются раньше. Обработчики с одинаковым приоритетом
+ * выполняются в порядке их добавления.
+ * @param useWeakReference флаг использования слабой ссылки для обработчика
+ */
+ public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
+ if (dispatcher == null) {
+ dispatcher = new EventDispatcher(this);
+ }
+ useCapture = false;
+ dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
+ }
+
+ /**
+ * Рассылка события.
+ *
+ * @param event посылаемое событие
+ * @return false
+ */
+ public function dispatchEvent(event:Event):Boolean {
+ if (dispatcher != null) {
+ dispatcher.dispatchEvent(event);
+ }
+ return false;
+ }
+
+ /**
+ * Проверка наличия зарегистрированных обработчиков события указанного типа.
+ *
+ * @param type тип события
+ * @return true если есть обработчики события указанного типа, иначе false
+ */
+ public function hasEventListener(type:String):Boolean {
+ if (dispatcher != null) {
+ return dispatcher.hasEventListener(type);
+ }
+ return false;
+ }
+
+ /**
+ * Удаление обработчика события.
+ *
+ * @param type тип события
+ * @param listener обработчик события
+ * @param useCapture не используется
+ */
+ public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void {
+ if (dispatcher != null) {
+ useCapture = false;
+ dispatcher.removeEventListener(type, listener, useCapture);
+ }
+ }
+
+ /**
+ *
+ */
+ public function willTrigger(type:String):Boolean {
+ if (dispatcher != null) {
+ return dispatcher.willTrigger(type);
+ }
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Mesh.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Mesh.as
new file mode 100644
index 0000000..b17d63d
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Mesh.as
@@ -0,0 +1,986 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.errors.FaceExistsError;
+ import alternativa.engine3d.errors.FaceNeedMoreVerticesError;
+ import alternativa.engine3d.errors.FaceNotFoundError;
+ import alternativa.engine3d.errors.InvalidIDError;
+ import alternativa.engine3d.errors.SurfaceExistsError;
+ import alternativa.engine3d.errors.SurfaceNotFoundError;
+ import alternativa.engine3d.errors.VertexExistsError;
+ import alternativa.engine3d.errors.VertexNotFoundError;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.types.Map;
+ import alternativa.utils.ObjectUtils;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Полигональный объект — базовый класс для трёхмерных объектов, состоящих из граней-полигонов. Объект
+ * содержит в себе наборы вершин, граней и поверхностей.
+ */
+ public class Mesh extends Object3D {
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ // Инкременты для идентификаторов вершин, граней и поверхностей
+ private var vertexIDCounter:uint = 0;
+ private var faceIDCounter:uint = 0;
+ private var surfaceIDCounter:uint = 0;
+
+ /**
+ * @private
+ * Список вершин
+ */
+ alternativa3d var _vertices:Map = new Map();
+ /**
+ * @private
+ * Список граней
+ */
+ alternativa3d var _faces:Map = new Map();
+ /**
+ * @private
+ * Список поверхностей
+ */
+ alternativa3d var _surfaces:Map = new Map();
+
+ /**
+ * Создание экземпляра полигонального объекта.
+ *
+ * @param name имя экземпляра
+ */
+ public function Mesh(name:String = null) {
+ super(name);
+ }
+
+ /**
+ * Добавление новой вершины к объекту.
+ *
+ * @param x координата X в локальной системе координат объекта
+ * @param y координата Y в локальной системе координат объекта
+ * @param z координата Z в локальной системе координат объекта
+ * @param id идентификатор вершины. Если указано значение null, идентификатор будет
+ * сформирован автоматически.
+ *
+ * @return экземпляр добавленной вершины
+ *
+ * @throws alternativa.engine3d.errors.VertexExistsError объект уже содержит вершину с указанным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function createVertex(x:Number = 0, y:Number = 0, z:Number = 0, id:Object = null):Vertex {
+ // Проверяем ID
+ if (id != null) {
+ // Если уже есть вершина с таким ID
+ if (_vertices[id] != undefined) {
+ if (_vertices[id] is Vertex) {
+ throw new VertexExistsError(id, this);
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ } else {
+ // Ищем первый свободный
+ while (_vertices[vertexIDCounter] != undefined) {
+ vertexIDCounter++;
+ }
+ id = vertexIDCounter;
+ }
+
+ // Создаём вершину
+ var v:Vertex = new Vertex(x, y, z);
+
+ // Добавляем вершину на сцену
+ if (_scene != null) {
+ v.addToScene(_scene);
+ }
+
+ // Добавляем вершину в меш
+ v.addToMesh(this);
+ _vertices[id] = v;
+
+ return v;
+ }
+
+ /**
+ * Удаление вершины из объекта. При удалении вершины из объекта также удаляются все грани, которым принадлежит данная вершина.
+ *
+ * @param vertex экземпляр класса alternativa.engine3d.core.Vertex или идентификатор удаляемой вершины
+ *
+ * @return экземпляр удалённой вершины
+ *
+ * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function removeVertex(vertex:Object):Vertex {
+ var byLink:Boolean = vertex is Vertex;
+
+ // Проверяем на null
+ if (vertex == null) {
+ throw new VertexNotFoundError(null, this);
+ }
+
+ // Проверяем наличие вершины в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Vertex(vertex)._mesh != this) {
+ // Если вершина не в меше
+ throw new VertexNotFoundError(vertex, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_vertices[vertex] == undefined) {
+ // Если нет вершины с таким ID
+ throw new VertexNotFoundError(vertex, this);
+ } else if (!(_vertices[vertex] is Vertex)) {
+ // По этому id не вершина
+ throw new InvalidIDError(vertex, this);
+ }
+ }
+
+ // Находим вершину и её ID
+ var v:Vertex = byLink ? Vertex(vertex) : _vertices[vertex];
+ var id:Object = byLink ? getVertexId(Vertex(vertex)) : vertex;
+
+ // Удаляем вершину из сцены
+ if (_scene != null) {
+ v.removeFromScene(_scene);
+ }
+
+ // Удаляем вершину из меша
+ v.removeFromMesh(this);
+ delete _vertices[id];
+
+ return v;
+ }
+
+ /**
+ * Добавление грани к объекту. В результате выполнения метода в объекте появляется новая грань, не привязанная
+ * ни к одной поверхности.
+ *
+ * @param vertices массив вершин грани, указанных в порядке обхода лицевой стороны грани против часовой
+ * стрелки. Каждый элемент массива может быть либо экземпляром класса alternativa.engine3d.core.Vertex,
+ * либо идентификатором в наборе вершин объекта. В обоих случаях объект должен содержать указанную вершину.
+ * @param id идентификатор грани. Если указано значение null, идентификатор будет
+ * сформирован автоматически.
+ *
+ * @return экземпляр добавленной грани
+ *
+ * @throws alternativa.engine3d.errors.FaceNeedMoreVerticesError в качестве массива вершин был передан
+ * null, либо количество вершин в массиве меньше трёх
+ * @throws alternativa.engine3d.errors.FaceExistsError объект уже содержит грань с заданным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит какую-либо вершину из входного массива
+ *
+ * @see Vertex
+ */
+ public function createFace(vertices:Array, id:Object = null):Face {
+
+ // Проверяем на null
+ if (vertices == null) {
+ throw new FaceNeedMoreVerticesError(this);
+ }
+
+ // Проверяем ID
+ if (id != null) {
+ // Если уже есть грань с таким ID
+ if (_faces[id] != undefined) {
+ if (_faces[id] is Face) {
+ throw new FaceExistsError(id, this);
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ } else {
+ // Ищем первый свободный ID
+ while (_faces[faceIDCounter] != undefined) {
+ faceIDCounter++;
+ }
+ id = faceIDCounter;
+ }
+
+ // Проверяем количество точек
+ var length:uint = vertices.length;
+ if (length < 3) {
+ throw new FaceNeedMoreVerticesError(this, length);
+ }
+
+ // Проверяем и формируем список вершин
+ var v:Array = new Array();
+ var vertex:Vertex;
+ for (var i:uint = 0; i < length; i++) {
+ if (vertices[i] is Vertex) {
+ // Если работаем со ссылками
+ vertex = vertices[i];
+ if (vertex._mesh != this) {
+ // Если вершина не в меше
+ throw new VertexNotFoundError(vertices[i], this);
+ }
+ } else {
+ // Если работаем с ID
+ if (_vertices[vertices[i]] == null) {
+ // Если нет вершины с таким ID
+ throw new VertexNotFoundError(vertices[i], this);
+ } else if (!(_vertices[vertices[i]] is Vertex)) {
+ // Если id зарезервировано
+ throw new InvalidIDError(vertices[i],this);
+ }
+ vertex = _vertices[vertices[i]];
+ }
+ v.push(vertex);
+ }
+
+ // Создаём грань
+ var f:Face = new Face(v);
+
+ // Добавляем грань на сцену
+ if (_scene != null) {
+ f.addToScene(_scene);
+ }
+
+ // Добавляем грань в меш
+ f.addToMesh(this);
+ _faces[id] = f;
+
+ return f;
+ }
+
+ /**
+ * Удаление грани из объекта. Грань также удаляется из поверхности объекта, которой она принадлежит.
+ *
+ * @param экземпляр класса alternativa.engine3d.core.Face или идентификатор удаляемой грани
+ *
+ * @return экземпляр удалённой грани
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function removeFace(face:Object):Face {
+ var byLink:Boolean = face is Face;
+
+ // Проверяем на null
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие грани в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Face(face)._mesh != this) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_faces[face] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(face, this);
+ } else if (!(_faces[face] is Face)) {
+ throw new InvalidIDError(face, this);
+ }
+ }
+
+ // Находим грань и её ID
+ var f:Face = byLink ? Face(face) : _faces[face] ;
+ var id:Object = byLink ? getFaceId(Face(face)) : face;
+
+ // Удаляем вершины из грани
+ f.removeVertices();
+
+ // Удаляем грань из поверхности
+ if (f._surface != null) {
+ f._surface._faces.remove(f);
+ f.removeFromSurface(f._surface);
+ }
+
+ // Удаляем грань из сцены
+ if (_scene != null) {
+ f.removeFromScene(_scene);
+ }
+
+ // Удаляем грань из меша
+ f.removeFromMesh(this);
+
+ delete _faces[id];
+
+ return f;
+ }
+
+ /**
+ * Добавление новой поверхности к объекту.
+ *
+ * @param faces набор граней, составляющих поверхность. Каждый элемент массива должен быть либо экземпляром класса
+ * alternativa.engine3d.core.Face, либо идентификатором грани. В обоих случаях объект должен содержать
+ * указанную грань. Если значение параметра равно null, то будет создана пустая поверхность. Если
+ * какая-либо грань содержится в другой поверхности, она будет перенесена в новую поверхность.
+ * @param id идентификатор новой поверхности. Если указано значение null, идентификатор будет
+ * сформирован автоматически.
+ *
+ * @return экземпляр добавленной поверхности
+ *
+ * @throws alternativa.engine3d.errors.SurfaceExistsError объект уже содержит поверхность с заданным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Face
+ */
+ public function createSurface(faces:Array = null, id:Object = null):Surface {
+ // Проверяем ID
+ if (id != null) {
+ // Если уже есть поверхность с таким ID
+ if (_surfaces[id] != undefined) {
+ if (_surfaces[id] is Surface) {
+ throw new SurfaceExistsError(id, this);
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ } else {
+ // Ищем первый свободный ID
+ while (_surfaces[surfaceIDCounter] != undefined) {
+ surfaceIDCounter++;
+ }
+ id = surfaceIDCounter;
+ }
+
+ // Создаём поверхность
+ var s:Surface = new Surface();
+
+ // Добавляем поверхность на сцену
+ if (_scene != null) {
+ s.addToScene(_scene);
+ }
+
+ // Добавляем поверхность в меш
+ s.addToMesh(this);
+ _surfaces[id] = s;
+
+ // Добавляем грани, если есть
+ if (faces != null) {
+ var length:uint = faces.length;
+ for (var i:uint = 0; i < length; i++) {
+ s.addFace(faces[i]);
+ }
+ }
+
+ return s;
+ }
+
+ /**
+ * Удаление поверхности объекта. Из удаляемой поверхности также удаляются все содержащиеся в ней грани.
+ *
+ * @param surface экземпляр класса alternativa.engine3d.core.Face или идентификатор удаляемой поверхности
+ *
+ * @return экземпляр удалённой поверхности
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function removeSurface(surface:Object):Surface {
+ var byLink:Boolean = surface is Surface;
+
+ // Проверяем на null
+ if (surface == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие поверхности в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Surface(surface)._mesh != this) {
+ // Если поверхность не в меше
+ throw new SurfaceNotFoundError(surface, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_surfaces[surface] == undefined) {
+ // Если нет поверхности с таким ID
+ throw new SurfaceNotFoundError(surface, this);
+ } else if (!(_surfaces[surface] is Surface)) {
+ throw new InvalidIDError(surface, this);
+ }
+ }
+
+ // Находим поверхность и её ID
+ var s:Surface = byLink ? Surface(surface) : _surfaces[surface];
+ var id:Object = byLink ? getSurfaceId(Surface(surface)) : surface;
+
+ // Удаляем поверхность из сцены
+ if (_scene != null) {
+ s.removeFromScene(_scene);
+ }
+
+ // Удаляем грани из поверхности
+ s.removeFaces();
+
+ // Удаляем поверхность из меша
+ s.removeFromMesh(this);
+ delete _surfaces[id];
+
+ return s;
+ }
+
+ /**
+ * Добавление всех граней объекта в указанную поверхность.
+ *
+ * @param surface экземпляр класса alternativa.engine3d.core.Surface или идентификатор поверхности, в
+ * которую добавляются грани. Если задан идентификатор, и объект не содержит поверхность с таким идентификатором,
+ * будет создана новая поверхность.
+ *
+ * @param removeSurfaces удалять или нет пустые поверхности после переноса граней
+ *
+ * @return экземпляр поверхности, в которую перенесены грани
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанный экземпляр поверхности
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function moveAllFacesToSurface(surface:Object = null, removeSurfaces:Boolean = false):Surface {
+ var returnSurface:Surface;
+ var returnSurfaceId:Object;
+ if (surface is Surface) {
+ // Работаем с экземпляром Surface
+ if (surface._mesh == this) {
+ returnSurface = Surface(surface);
+ } else {
+ throw new SurfaceNotFoundError(surface, this);
+ }
+ } else {
+ // Работаем с идентификатором
+ if (_surfaces[surface] == undefined) {
+ // Поверхности еще нет
+ returnSurface = createSurface(null, surface);
+ returnSurfaceId = surface;
+ } else {
+ if (_surfaces[surface] is Surface) {
+ returnSurface = _surfaces[surface];
+ } else {
+ // _surfaces[surface] по идентификатору возвращает не Surface
+ throw new InvalidIDError(surface, this);
+ }
+ }
+ }
+ // Перемещаем все грани
+ for each (var face:Face in _faces) {
+ if (face._surface != returnSurface) {
+ returnSurface.addFace(face);
+ }
+ }
+ if (removeSurfaces) {
+ // Удаляем старые, теперь вручную - меньше проверок, но рискованно
+ if (returnSurfaceId == null) {
+ returnSurfaceId = getSurfaceId(returnSurface);
+ }
+ var newSurfaces:Map = new Map();
+ newSurfaces[returnSurfaceId] = returnSurface;
+ delete _surfaces[returnSurfaceId];
+ // Удаляем оставшиеся
+ for (var currentSurfaceId:* in _surfaces) {
+ // Удаляем поверхность из сцены
+ var currentSurface:Surface = _surfaces[currentSurfaceId];
+ if (_scene != null) {
+ currentSurface.removeFromScene(_scene);
+ }
+ // Удаляем поверхность из меша
+ currentSurface.removeFromMesh(this);
+ delete _surfaces[currentSurfaceId];
+ }
+ // Новый список граней
+ _surfaces = newSurfaces;
+ }
+ return returnSurface;
+ }
+
+ /**
+ * Установка материала для указанной поверхности.
+ *
+ * @param material материал, назначаемый поверхности. Один экземпляр SurfaceMaterial можно назначить только одной поверхности.
+ * @param surface экземпляр класса alternativa.engine3d.core.Surface или идентификатор поверхности
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Surface
+ */
+ public function setMaterialToSurface(material:SurfaceMaterial, surface:Object):void {
+ var byLink:Boolean = surface is Surface;
+
+ // Проверяем на null
+ if (surface == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие поверхности в меше
+ if (byLink) {
+ // Если назначаем по ссылке
+ if (Surface(surface)._mesh != this) {
+ // Если поверхность не в меше
+ throw new SurfaceNotFoundError(surface, this);
+ }
+ } else {
+ // Если назначаем по ID
+ if (_surfaces[surface] == undefined) {
+ // Если нет поверхности с таким ID
+ throw new SurfaceNotFoundError(surface, this);
+ } else if (!(_surfaces[surface] is Surface)) {
+ throw new InvalidIDError(surface, this);
+ }
+ }
+
+ // Находим поверхность
+ var s:Surface = byLink ? Surface(surface) : _surfaces[surface];
+
+ // Назначаем материал
+ s.material = material;
+ }
+
+ /**
+ * Установка материала для всех поверхностей объекта. Для каждой поверхности устанавливается копия материала.
+ * При передаче null в качестве параметра происходит сброс материалов у всех поверхностей.
+ *
+ * @param material устанавливаемый материал
+ */
+ public function cloneMaterialToAllSurfaces(material:SurfaceMaterial):void {
+ for each (var surface:Surface in _surfaces) {
+ surface.material = (material != null) ? SurfaceMaterial(material.clone()) : null;
+ }
+ }
+
+ /**
+ * Установка UV-координат для указанной грани объекта. Матрица преобразования UV-координат расчитывается по
+ * UV-координатам первых трёх вершин грани, поэтому для корректного текстурирования эти вершины должны образовывать
+ * невырожденный треугольник в UV-пространстве.
+ *
+ * @param aUV UV-координаты, соответствующие первой вершине грани
+ * @param bUV UV-координаты, соответствующие второй вершине грани
+ * @param cUV UV-координаты, соответствующие третьей вершине грани
+ * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Face
+ */
+ public function setUVsToFace(aUV:Point, bUV:Point, cUV:Point, face:Object):void {
+ var byLink:Boolean = face is Face;
+
+ // Проверяем на null
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие грани в меше
+ if (byLink) {
+ // Если назначаем по ссылке
+ if (Face(face)._mesh != this) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ } else {
+ // Если назначаем по ID
+ if (_faces[face] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(face, this);
+ } else if (!(_faces[face] is Face)) {
+ throw new InvalidIDError(face, this);
+ }
+ }
+
+ // Находим грань
+ var f:Face = byLink ? Face(face) : _faces[face];
+
+ // Назначаем UV-координаты
+ f.aUV = aUV;
+ f.bUV = bUV;
+ f.cUV = cUV;
+ }
+
+ /**
+ * Набор вершин объекта. Ключами ассоциативного массива являются идентификаторы вершин, значениями - экземпляры вершин.
+ */
+ public function get vertices():Map {
+ return _vertices.clone();
+ }
+
+ /**
+ * Набор граней объекта. Ключами ассоциативного массива являются идентификаторы граней, значениями - экземпляры граней.
+ */
+ public function get faces():Map {
+ return _faces.clone();
+ }
+
+ /**
+ * Набор поверхностей объекта. Ключами ассоциативного массива являются идентификаторы поверхностей, значениями - экземпляры поверхностей.
+ */
+ public function get surfaces():Map {
+ return _surfaces.clone();
+ }
+
+ /**
+ * Получение вершины объекта по её идентификатору.
+ *
+ * @param id идентификатор вершины
+ *
+ * @return экземпляр вершины с указанным идентификатором
+ *
+ * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит вершину с указанным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function getVertexById(id:Object):Vertex {
+ if (id == null) {
+ throw new VertexNotFoundError(null, this);
+ }
+ if (_vertices[id] == undefined) {
+ // Если нет вершины с таким ID
+ throw new VertexNotFoundError(id, this);
+ } else {
+ if (_vertices[id] is Vertex) {
+ return _vertices[id];
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ }
+
+ /**
+ * Получение идентификатора вершины объекта.
+ *
+ * @param экземпляр вершины
+ *
+ * @return идентификатор указанной вершины
+ *
+ * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину
+ */
+ public function getVertexId(vertex:Vertex):Object {
+ if (vertex == null) {
+ throw new VertexNotFoundError(null, this);
+ }
+ if (vertex._mesh != this) {
+ // Если вершина не в меше
+ throw new VertexNotFoundError(vertex, this);
+ }
+ for (var i:Object in _vertices) {
+ if (_vertices[i] == vertex) {
+ return i;
+ }
+ }
+ throw new VertexNotFoundError(vertex, this);
+ }
+
+ /**
+ * Проверка наличия вершины в объекте.
+ *
+ * @param vertex экземпляр класса alternativa.engine3d.core.Vertex или идентификатор вершины
+ *
+ * @return true, если объект содержит указанную вершину, иначе false
+ *
+ * @throws alternativa.engine3d.errors.VertexNotFoundError в качестве vertex был передан null
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Vertex
+ */
+ public function hasVertex(vertex:Object):Boolean {
+ if (vertex == null) {
+ throw new VertexNotFoundError(null, this);
+ }
+ if (vertex is Vertex) {
+ // Проверка вершины
+ return vertex._mesh == this;
+ } else {
+ // Проверка ID вершины
+ if (_vertices[vertex] != undefined) {
+ // По этому ID есть объект
+ if (_vertices[vertex] is Vertex) {
+ // Объект является вершиной
+ return true;
+ } else {
+ // ID некорректный
+ throw new InvalidIDError(vertex, this);
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Получение грани объекта по ее идентификатору.
+ *
+ * @param id идентификатор грани
+ *
+ * @return экземпляр грани с указанным идентификатором
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит грань с указанным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function getFaceById(id:Object):Face {
+ if (id == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+ if (_faces[id] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(id, this);
+ } else {
+ if (_faces[id] is Face) {
+ return _faces[id];
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ }
+
+ /**
+ * Получение идентификатора грани объекта.
+ *
+ * @param face экземпляр грани
+ *
+ * @return идентификатор указанной грани
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
+ */
+ public function getFaceId(face:Face):Object {
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+ if (face._mesh != this) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ for (var i:Object in _faces) {
+ if (_faces[i] == face) {
+ return i;
+ }
+ }
+ throw new FaceNotFoundError(face, this);
+ }
+
+ /**
+ * Проверка наличия грани в объекте.
+ *
+ * @param face экземпляр класса Face или идентификатор грани
+ *
+ * @return true, если объект содержит указанную грань, иначе false
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError в качестве face был указан null
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function hasFace(face:Object):Boolean {
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+ if (face is Face) {
+ // Проверка грани
+ return face._mesh == this;
+ } else {
+ // Проверка ID грани
+ if (_faces[face] != undefined) {
+ // По этому ID есть объект
+ if (_faces[face] is Face) {
+ // Объект является гранью
+ return true;
+ } else {
+ // ID некорректный
+ throw new InvalidIDError(face, this);
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Получение поверхности объекта по ее идентификатору
+ *
+ * @param id идентификатор поверхности
+ *
+ * @return экземпляр поверхности с указанным идентификатором
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит поверхность с указанным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function getSurfaceById(id:Object):Surface {
+ if (id == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+ if (_surfaces[id] == undefined) {
+ // Если нет поверхности с таким ID
+ throw new SurfaceNotFoundError(id, this);
+ } else {
+ if (_surfaces[id] is Surface) {
+ return _surfaces[id];
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ }
+
+ /**
+ * Получение идентификатора поверхности объекта.
+ *
+ * @param surface экземпляр поверхности
+ *
+ * @return идентификатор указанной поверхности
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
+ */
+ public function getSurfaceId(surface:Surface):Object {
+ if (surface == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+ if (surface._mesh != this) {
+ // Если поверхность не в меше
+ throw new SurfaceNotFoundError(surface, this);
+ }
+ for (var i:Object in _surfaces) {
+ if (_surfaces[i] == surface) {
+ return i;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Проверка наличия поверхности в объекте.
+ *
+ * @param surface экземпляр класса Surface или идентификатор поверхности
+ *
+ * @return true, если объект содержит указанную поверхность, иначе будет построен цилиндр.false
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError в качестве surface был передан null
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function hasSurface(surface:Object):Boolean {
+ if (surface == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+ if (surface is Surface) {
+ // Проверка поверхности
+ return surface._mesh == this;
+ } else {
+ // Проверка ID поверхности
+ if (_surfaces[surface] != undefined) {
+ // По этому ID есть объект
+ if (_surfaces[surface] is Surface) {
+ // Объект является поверхностью
+ return true;
+ } else {
+ // ID некорректный
+ throw new InvalidIDError(surface, this);
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function setScene(value:Scene3D):void {
+ if (_scene != value) {
+ var vertex:Vertex;
+ var face:Face;
+ var surface:Surface;
+ if (value != null) {
+ // Добавить вершины на сцену
+ for each (vertex in _vertices) {
+ vertex.addToScene(value);
+ }
+ // Добавить грани на сцену
+ for each (face in _faces) {
+ face.addToScene(value);
+ }
+ // Добавить поверхности на сцену
+ for each (surface in _surfaces) {
+ surface.addToScene(value);
+ }
+ } else {
+ // Удалить вершины из сцены
+ for each (vertex in _vertices) {
+ vertex.removeFromScene(_scene);
+ }
+ // Удалить грани из сцены
+ for each (face in _faces) {
+ face.removeFromScene(_scene);
+ }
+ // Удалить поверхности из сцены
+ for each (surface in _surfaces) {
+ surface.removeFromScene(_scene);
+ }
+ }
+ }
+ super.setScene(value);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function defaultName():String {
+ return "mesh" + ++counter;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function toString():String {
+ return "[" + ObjectUtils.getClassName(this) + " " + _name + " vertices: " + _vertices.length + " faces: " + _faces.length + "]";
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Mesh();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function clonePropertiesFrom(source:Object3D):void {
+ super.clonePropertiesFrom(source);
+
+ var src:Mesh = Mesh(source);
+
+ var id:*;
+ var len:int;
+ var i:int;
+ // Копирование вершин
+ var vertexMap:Map = new Map(true);
+ for (id in src._vertices) {
+ var sourceVertex:Vertex = src._vertices[id];
+ vertexMap[sourceVertex] = createVertex(sourceVertex.x, sourceVertex.y, sourceVertex.z, id);
+ }
+
+ // Копирование граней
+ var faceMap:Map = new Map(true);
+ for (id in src._faces) {
+ var sourceFace:Face = src._faces[id];
+ len = sourceFace._vertices.length;
+ var faceVertices:Array = new Array(len);
+ for (i = 0; i < len; i++) {
+ faceVertices[i] = vertexMap[sourceFace._vertices[i]];
+ }
+ var newFace:Face = createFace(faceVertices, id);
+ newFace.aUV = sourceFace._aUV;
+ newFace.bUV = sourceFace._bUV;
+ newFace.cUV = sourceFace._cUV;
+ faceMap[sourceFace] = newFace;
+ }
+
+ // Копирование поверхностей
+ for (id in src._surfaces) {
+ var sourceSurface:Surface = src._surfaces[id];
+ var surfaceFaces:Array = sourceSurface._faces.toArray();
+ len = surfaceFaces.length;
+ for (i = 0; i < len; i++) {
+ surfaceFaces[i] = faceMap[surfaceFaces[i]];
+ }
+ var surface:Surface = createSurface(surfaceFaces, id);
+ var sourceMaterial:SurfaceMaterial = sourceSurface.material;
+ if (sourceMaterial != null) {
+ surface.material = SurfaceMaterial(sourceMaterial.clone());
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Object3D.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Object3D.as
new file mode 100644
index 0000000..bd6ad51
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Object3D.as
@@ -0,0 +1,957 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.errors.Object3DHierarchyError;
+ import alternativa.engine3d.errors.Object3DNotFoundError;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+ import alternativa.utils.ObjectUtils;
+
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IEventDispatcher;
+
+ use namespace alternativa3d;
+
+ /**
+ * Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одним и тем же объектом.
+ * Между нажатием и отпусканием кнопки могут происходить любые другие события.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.CLICK
+ */
+ [Event (name="click", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь нажимает левую кнопку мыши над объектом.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN
+ */
+ [Event (name="mouseDown", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь отпускает левую кнопку мыши над объектом.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP
+ */
+ [Event (name="mouseUp", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь наводит курсор мыши на объект.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER
+ */
+ [Event (name="mouseOver", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь уводит курсор мыши с объекта.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT
+ */
+ [Event (name="mouseOut", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь перемещает курсор мыши над объектом.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE
+ */
+ [Event (name="mouseMove", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь вращает колесо мыши над объектом.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL
+ */
+ [Event (name="mouseWheel", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Базовый класс для объектов, находящихся в сцене. Класс реализует иерархию объектов сцены, а также содержит сведения
+ * о трансформации объекта как единого целого.
+ *
+ * X, Y, Z и параллельного переноса центра объекта из начала координат.
+ * Операции применяются в порядке их перечисления.
+ *
+ * flash.events.IEventDispatcher и может рассылать мышиные события, содержащие информацию
+ * о точке в трёхмерном пространстве, в которой произошло событие. На данный момент не реализованы capture и bubbling фазы рассылки
+ * событий.
+ */
+ public class Object3D implements IEventDispatcher {
+ /**
+ * @private
+ * Вспомогательная матрица
+ */
+ alternativa3d static var matrix1:Matrix3D = new Matrix3D();
+ /**
+ * @private
+ * Вспомогательная матрица
+ */
+ alternativa3d static var matrix2:Matrix3D = new Matrix3D();
+
+ /**
+ * @private
+ * Поворот или масштабирование
+ */
+ alternativa3d var changeRotationOrScaleOperation:Operation = new Operation("changeRotationOrScale", this);
+ /**
+ * @private
+ * Перемещение
+ */
+ alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this);
+ /**
+ * @private
+ * Расчёт матрицы трансформации
+ */
+ alternativa3d var calculateTransformationOperation:Operation = new Operation("calculateTransformation", this, calculateTransformation, Operation.OBJECT_CALCULATE_TRANSFORMATION);
+ /**
+ * @private
+ * Изменение уровеня мобильности
+ */
+ alternativa3d var calculateMobilityOperation:Operation = new Operation("calculateMobility", this, calculateMobility, Operation.OBJECT_CALCULATE_MOBILITY);
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ /**
+ * @private
+ * Наименование
+ */
+ alternativa3d var _name:String;
+ /**
+ * @private
+ * Сцена
+ */
+ alternativa3d var _scene:Scene3D;
+ /**
+ * @private
+ * Родительский объект
+ */
+ alternativa3d var _parent:Object3D;
+ /**
+ * @private
+ * Дочерние объекты
+ */
+ alternativa3d var _children:Set = new Set();
+ /**
+ * @private
+ * Уровень мобильности
+ */
+ alternativa3d var _mobility:int = 0;
+ /**
+ * @private
+ */
+ alternativa3d var inheritedMobility:int;
+ /**
+ * @private
+ * Координаты объекта относительно родителя
+ */
+ alternativa3d var _coords:Point3D = new Point3D();
+ /**
+ * @private
+ * Поворот объекта по оси X относительно родителя. Угол измеряется в радианах.
+ */
+ alternativa3d var _rotationX:Number = 0;
+ /**
+ * @private
+ * Поворот объекта по оси Y относительно родителя. Угол измеряется в радианах.
+ */
+ alternativa3d var _rotationY:Number = 0;
+ /**
+ * @private
+ * Поворот объекта по оси Z относительно родителя. Угол измеряется в радианах.
+ */
+ alternativa3d var _rotationZ:Number = 0;
+ /**
+ * @private
+ * Мастшаб объекта по оси X относительно родителя
+ */
+ alternativa3d var _scaleX:Number = 1;
+ /**
+ * @private
+ * Мастшаб объекта по оси Y относительно родителя
+ */
+ alternativa3d var _scaleY:Number = 1;
+ /**
+ * @private
+ * Мастшаб объекта по оси Z относительно родителя
+ */
+ alternativa3d var _scaleZ:Number = 1;
+ /**
+ * @private
+ * Полная матрица трансформации, переводящая координаты из локальной системы координат объекта в систему координат сцены
+ */
+ alternativa3d var _transformation:Matrix3D = new Matrix3D();
+ /**
+ * @private
+ * Координаты в сцене
+ */
+ alternativa3d var globalCoords:Point3D = new Point3D();
+
+ /**
+ * Флаг указывает, будет ли объект принимать мышиные события.
+ */
+ public var mouseEnabled:Boolean = true;
+ /**
+ * Диспетчер событий.
+ */
+ private var dispatcher:EventDispatcher;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param name имя экземпляра
+ */
+ public function Object3D(name:String = null) {
+ // Имя по-умолчанию
+ _name = (name != null) ? name : defaultName();
+
+ // Последствия операций
+ changeRotationOrScaleOperation.addSequel(calculateTransformationOperation);
+ changeCoordsOperation.addSequel(calculateTransformationOperation);
+ }
+
+ /**
+ * @private
+ * Расчёт трансформации
+ */
+ alternativa3d function calculateTransformation():void {
+ if (changeRotationOrScaleOperation.queued) {
+ // Если полная трансформация
+ _transformation.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ);
+ if (_parent != null) {
+ _transformation.combine(_parent._transformation);
+ }
+ // Сохраняем глобальные координаты объекта
+ globalCoords.x = _transformation.d;
+ globalCoords.y = _transformation.h;
+ globalCoords.z = _transformation.l;
+ } else {
+ // Если только перемещение
+ globalCoords.copy(_coords);
+ if (_parent != null) {
+ globalCoords.transform(_parent._transformation);
+ }
+ _transformation.offset(globalCoords.x, globalCoords.y, globalCoords.z);
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт общей мобильности
+ */
+ private function calculateMobility():void {
+ inheritedMobility = ((_parent != null) ? _parent.inheritedMobility : 0) + _mobility;
+ }
+
+ /**
+ * Добавление дочернего объекта. Добавляемый объект удаляется из списка детей предыдущего родителя.
+ * Новой сценой дочернего объекта становится сцена родителя.
+ *
+ * @param child добавляемый объект
+ *
+ * @return добавленный объект
+ *
+ * @throws alternativa.engine3d.errors.Object3DHierarchyError нарушение иерархии объектов сцены
+ */
+ public function addChild(child:Object3D):Object3D {
+
+ // Проверка на null
+ if (child == null) {
+ throw new Object3DHierarchyError(null, this);
+ }
+
+ // Проверка на наличие
+ if (child._parent == this) {
+ return child;
+ }
+
+ // Проверка на добавление к самому себе
+ if (child == this) {
+ throw new Object3DHierarchyError(this, this);
+ }
+
+ // Проверка на добавление родительского объекта
+ if (child._scene == _scene) {
+ // Если объект был в той же сцене, либо оба не были в сцене
+ var parentObject:Object3D = _parent;
+ while (parentObject != null) {
+ if (child == parentObject) {
+ throw new Object3DHierarchyError(child, this);
+ return;
+ }
+ parentObject = parentObject._parent;
+ }
+ }
+
+ // Если объект был в другом объекте
+ if (child._parent != null) {
+ // Удалить его оттуда
+ child._parent._children.remove(child);
+ } else {
+ // Если объект был корневым в сцене
+ if (child._scene != null && child._scene._root == child) {
+ child._scene.root = null;
+ }
+ }
+
+ // Добавляем в список
+ _children.add(child);
+ // Указываем себя как родителя
+ child.setParent(this);
+ // Устанавливаем уровни
+ child.setLevel((calculateTransformationOperation.priority & 0xFFFFFF) + 1);
+ // Указываем сцену
+ child.setScene(_scene);
+
+ return child;
+ }
+
+ /**
+ * Удаление дочернего объекта.
+ *
+ * @param child удаляемый дочерний объект
+ *
+ * @return удаленный объект
+ *
+ * @throws alternativa.engine3d.errors.Object3DNotFoundError указанный объект не содержится в списке детей текущего объекта
+ */
+ public function removeChild(child:Object3D):Object3D {
+ // Проверка на null
+ if (child == null) {
+ throw new Object3DNotFoundError(null, this);
+ }
+ // Проверка на наличие
+ if (child._parent != this) {
+ throw new Object3DNotFoundError(child, this);
+ }
+ // Убираем из списка
+ _children.remove(child);
+ // Удаляем ссылку на родителя
+ child.setParent(null);
+ // Удаляем ссылку на сцену
+ child.setScene(null);
+
+ return child;
+ }
+
+ /**
+ * @private
+ * Установка родительского объекта.
+ *
+ * @param value родительский объект
+ */
+ alternativa3d function setParent(value:Object3D):void {
+ // Отписываемся от сигналов старого родителя
+ if (_parent != null) {
+ removeParentSequels();
+ }
+ // Сохранить родителя
+ _parent = value;
+ // Если устанавливаем родителя
+ if (value != null) {
+ // Подписка на сигналы родителя
+ addParentSequels();
+ }
+ }
+
+ /**
+ * @private
+ * Установка новой сцены для объекта.
+ *
+ * @param value сцена
+ */
+ alternativa3d function setScene(value:Scene3D):void {
+ if (_scene != value) {
+ // Если была сцена
+ if (_scene != null) {
+ // Удалиться из сцены
+ removeFromScene(_scene);
+ }
+ // Если новая сцена
+ if (value != null) {
+ // Добавиться на сцену
+ addToScene(value);
+ }
+ // Сохранить сцену
+ _scene = value;
+ } else {
+ // Посылаем операцию трансформации
+ addOperationToScene(changeRotationOrScaleOperation);
+ // Посылаем операцию пересчёта мобильности
+ addOperationToScene(calculateMobilityOperation);
+ }
+ // Установить эту сцену у дочерних объектов
+ for (var key:* in _children) {
+ var object:Object3D = key;
+ object.setScene(_scene);
+ }
+ }
+
+ /**
+ * @private
+ * Установка уровня операции трансформации.
+ *
+ * @param value уровень операции трансформации
+ */
+ alternativa3d function setLevel(value:uint):void {
+ // Установить уровень операции трансформации и расчёта мобильности
+ calculateTransformationOperation.priority = (calculateTransformationOperation.priority & 0xFF000000) | value;
+ calculateMobilityOperation.priority = (calculateMobilityOperation.priority & 0xFF000000) | value;
+ // Установить уровни у дочерних объектов
+ for (var key:* in _children) {
+ var object:Object3D = key;
+ object.setLevel(value + 1);
+ }
+ }
+
+ /**
+ * @private
+ * Подписка на сигналы родителя.
+ */
+ private function addParentSequels():void {
+ _parent.changeCoordsOperation.addSequel(changeCoordsOperation);
+ _parent.changeRotationOrScaleOperation.addSequel(changeRotationOrScaleOperation);
+ _parent.calculateMobilityOperation.addSequel(calculateMobilityOperation);
+ }
+
+ /**
+ * @private
+ * Удаление подписки на сигналы родителя.
+ */
+ private function removeParentSequels():void {
+ _parent.changeCoordsOperation.removeSequel(changeCoordsOperation);
+ _parent.changeRotationOrScaleOperation.removeSequel(changeRotationOrScaleOperation);
+ _parent.calculateMobilityOperation.removeSequel(calculateMobilityOperation);
+ }
+
+ /**
+ * Метод вызывается при добавлении объекта на сцену. Наследники могут переопределять метод для выполнения
+ * специфических действий.
+ *
+ * @param scene сцена, в которую добавляется объект
+ */
+ protected function addToScene(scene:Scene3D):void {
+ // При добавлении на сцену полная трансформация и расчёт мобильности
+ scene.addOperation(changeRotationOrScaleOperation);
+ scene.addOperation(calculateMobilityOperation);
+ }
+
+ /**
+ * Метод вызывается при удалении объекта со сцены. Наследники могут переопределять метод для выполнения
+ * специфических действий.
+ *
+ * @param scene сцена, из которой удаляется объект
+ */
+ protected function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(changeRotationOrScaleOperation);
+ scene.removeOperation(changeCoordsOperation);
+ scene.removeOperation(calculateMobilityOperation);
+ }
+
+ /**
+ * Имя объекта.
+ */
+ public function get name():String {
+ return _name;
+ }
+
+ /**
+ * @private
+ */
+ public function set name(value:String):void {
+ _name = value;
+ }
+
+ /**
+ * Сцена, которой принадлежит объект.
+ */
+ public function get scene():Scene3D {
+ return _scene;
+ }
+
+ /**
+ * Родительский объект.
+ */
+ public function get parent():Object3D {
+ return _parent;
+ }
+
+ /**
+ * Набор дочерних объектов.
+ */
+ public function get children():Set {
+ return _children.clone();
+ }
+
+ /**
+ * Уровень мобильности. Результирующая мобильность объекта является суммой мобильностей объекта и всех его предков
+ * по иерархии объектов в сцене. Результирующая мобильность влияет на положение объекта в BSP-дереве. Менее мобильные
+ * объекты находятся ближе к корню дерева, чем более мобильные.
+ */
+ public function get mobility():int {
+ return _mobility;
+ }
+
+ /**
+ * @private
+ */
+ public function set mobility(value:int):void {
+ if (_mobility != value) {
+ _mobility = value;
+ addOperationToScene(calculateMobilityOperation);
+ }
+ }
+
+ /**
+ * Координата X.
+ */
+ public function get x():Number {
+ return _coords.x;
+ }
+
+ /**
+ * Координата Y.
+ */
+ public function get y():Number {
+ return _coords.y;
+ }
+
+ /**
+ * Координата Z.
+ */
+ public function get z():Number {
+ return _coords.z;
+ }
+
+ /**
+ * @private
+ */
+ public function set x(value:Number):void {
+ if (_coords.x != value) {
+ _coords.x = value;
+ addOperationToScene(changeCoordsOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set y(value:Number):void {
+ if (_coords.y != value) {
+ _coords.y = value;
+ addOperationToScene(changeCoordsOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set z(value:Number):void {
+ if (_coords.z != value) {
+ _coords.z = value;
+ addOperationToScene(changeCoordsOperation);
+ }
+ }
+
+ /**
+ * Координаты объекта.
+ */
+ public function get coords():Point3D {
+ return _coords.clone();
+ }
+
+ /**
+ * @private
+ */
+ public function set coords(value:Point3D):void {
+ if (!_coords.equals(value)) {
+ _coords.copy(value);
+ addOperationToScene(changeCoordsOperation);
+ }
+ }
+
+ /**
+ * Угол поворота вокруг оси X, заданный в радианах.
+ */
+ public function get rotationX():Number {
+ return _rotationX;
+ }
+
+ /**
+ * Угол поворота вокруг оси Y, заданный в радианах.
+ */
+ public function get rotationY():Number {
+ return _rotationY;
+ }
+
+ /**
+ * Угол поворота вокруг оси Z, заданный в радианах.
+ */
+ public function get rotationZ():Number {
+ return _rotationZ;
+ }
+
+ /**
+ * @private
+ */
+ public function set rotationX(value:Number):void {
+ if (_rotationX != value) {
+ _rotationX = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set rotationY(value:Number):void {
+ if (_rotationY != value) {
+ _rotationY = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set rotationZ(value:Number):void {
+ if (_rotationZ != value) {
+ _rotationZ = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * Коэффициент масштабирования вдоль оси X.
+ */
+ public function get scaleX():Number {
+ return _scaleX;
+ }
+
+ /**
+ * Коэффициент масштабирования вдоль оси Y.
+ */
+ public function get scaleY():Number {
+ return _scaleY;
+ }
+
+ /**
+ * Коэффициент масштабирования вдоль оси Z.
+ */
+ public function get scaleZ():Number {
+ return _scaleZ;
+ }
+
+ /**
+ * @private
+ */
+ public function set scaleX(value:Number):void {
+ if (_scaleX != value) {
+ _scaleX = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set scaleY(value:Number):void {
+ if (_scaleY != value) {
+ _scaleY = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set scaleZ(value:Number):void {
+ if (_scaleZ != value) {
+ _scaleZ = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * Строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ return "[" + ObjectUtils.getClassName(this) + " " + _name + "]";
+ }
+
+ /**
+ * Имя объекта по умолчанию.
+ *
+ * @return имя объекта по умолчанию
+ */
+ protected function defaultName():String {
+ return "object" + ++counter;
+ }
+
+ /**
+ * @private
+ * Добавление операции в очередь.
+ *
+ * @param operation добавляемая операция
+ */
+ alternativa3d function addOperationToScene(operation:Operation):void {
+ if (_scene != null) {
+ _scene.addOperation(operation);
+ }
+ }
+
+ /**
+ * @private
+ * Удаление операции из очереди.
+ *
+ * @param operation удаляемая операция
+ */
+ alternativa3d function removeOperationFromScene(operation:Operation):void {
+ if (_scene != null) {
+ _scene.removeOperation(operation);
+ }
+ }
+
+ /**
+ * Создание пустого объекта без какой-либо внутренней структуры. Например, если некоторый геометрический примитив при
+ * своём создании формирует набор вершин, граней и поверхностей, то этот метод не должен создавать вершины, грани и
+ * поверхности. Данный метод используется в методе clone() и должен быть переопределён в потомках для получения
+ * правильного объекта.
+ *
+ * @return новый пустой объект
+ */
+ protected function createEmptyObject():Object3D {
+ return new Object3D();
+ }
+
+ /**
+ * Копирование свойств объекта-источника. Данный метод используется в методе clone() и должен быть переопределён в
+ * потомках для получения правильного объекта. Каждый потомок должен в переопределённом методе копировать только те
+ * свойства, которые добавлены к базовому классу именно в нём. Копирование унаследованных свойств выполняется
+ * вызовом super.clonePropertiesFrom(source).
+ *
+ * @param source объект, свойства которого копируются
+ */
+ protected function clonePropertiesFrom(source:Object3D):void {
+ _name = source._name;
+ _mobility = source._mobility;
+ _coords.x = source._coords.x;
+ _coords.y = source._coords.y;
+ _coords.z = source._coords.z;
+ _rotationX = source._rotationX;
+ _rotationY = source._rotationY;
+ _rotationZ = source._rotationZ;
+ _scaleX = source._scaleX;
+ _scaleY = source._scaleY;
+ _scaleZ = source._scaleZ;
+ }
+
+ /**
+ * Клонирование объекта. Для реализации собственного клонирования наследники должны переопределять методы
+ * createEmptyObject() и clonePropertiesFrom().
+ *
+ * @return клонированный экземпляр объекта
+ *
+ * @see #createEmptyObject()
+ * @see #clonePropertiesFrom()
+ */
+ public function clone():Object3D {
+ var copy:Object3D = createEmptyObject();
+ copy.clonePropertiesFrom(this);
+
+ // Клонирование детей
+ for (var key:* in _children) {
+ var child:Object3D = key;
+ copy.addChild(child.clone());
+ }
+
+ return copy;
+ }
+
+ /**
+ * Получение дочернего объекта с заданным именем.
+ *
+ * @param name имя дочернего объекта
+ * @param deep флаг углублённого поиска. Если задано значение false, поиск будет осуществляться только среди непосредственных
+ * дочерних объектов, иначе поиск будет выполняться по всему дереву дочерних объектов.
+ *
+ * @return любой дочерний объект с заданным именем или null в случае отсутствия таких объектов
+ */
+ public function getChildByName(name:String, deep:Boolean = false):Object3D {
+ var key:*;
+ var child:Object3D;
+ for (key in _children) {
+ child = key;
+ if (child._name == name) {
+ return child;
+ }
+ }
+ if (deep) {
+ for (key in _children) {
+ child = key;
+ child = child.getChildByName(name, true);
+ if (child != null) {
+ return child;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Трансформация точки из локальной системы координат объекта в систему координат сцены. Матрица трансформации корневого объекта сцены
+ * не учитывается, т.к. его система координат является системой координат сцены.
+ *
+ * @param point локальные координаты точки
+ *
+ * @return координаты точки в сцене или null в случае, если объект не находится в сцене
+ */
+ public function localToGlobal(point:Point3D):Point3D {
+ if (_scene == null) {
+ return null;
+ }
+ var globalPoint:Point3D = point.clone();
+
+ if (_parent == null) {
+ // Для корневого объекта трансформация единичная вне зависимости от матрицы
+ return globalPoint;
+ }
+
+ getTransformation(matrix2);
+ globalPoint.transform(matrix2);
+
+ return globalPoint;
+ }
+
+ /**
+ * Преобразование точки из глобальной в локальную систему координат.
+ *
+ * @param point точка, заданная в глобальной системе координат.
+ *
+ * @return точка, трансформированная в локальную систему координат объекта.
+ */
+ public function globalToLocal(point:Point3D):Point3D {
+ var result:Point3D = point.clone();
+ getTransformation(matrix2);
+ matrix2.invert();
+ result.transform(matrix2);
+ return result;
+ }
+
+ /**
+ * Получение матрицы полной трансформации объекта.
+ *
+ * @return матрица полной трансформации, переводящая координаты из локальной системы объекта в систему координат сцены
+ */
+ public function get transformation():Matrix3D {
+ var result:Matrix3D = new Matrix3D();
+ getTransformation(result);
+ return result;
+ }
+
+ /**
+ * Добавление обработчика события.
+ *
+ * @param type тип события
+ * @param listener обработчик события
+ * @param useCapture не используется,
+ * @param priority приоритет обработчика. Обработчики с большим приоритетом выполняются раньше. Обработчики с одинаковым приоритетом
+ * выполняются в порядке их добавления.
+ * @param useWeakReference флаг использования слабой ссылки для обработчика
+ */
+ public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
+ if (dispatcher == null) {
+ dispatcher = new EventDispatcher(this);
+ }
+ useCapture = false;
+ dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
+ }
+
+ /**
+ * Рассылка события.
+ *
+ * @param event посылаемое событие
+ * @return false
+ */
+ public function dispatchEvent(event:Event):Boolean {
+ if (dispatcher != null) {
+ dispatcher.dispatchEvent(event);
+ }
+ return false;
+ }
+
+ /**
+ * Проверка наличия зарегистрированных обработчиков события указанного типа.
+ *
+ * @param type тип события
+ * @return true если есть обработчики события указанного типа, иначе false
+ */
+ public function hasEventListener(type:String):Boolean {
+ if (dispatcher != null) {
+ return dispatcher.hasEventListener(type);
+ }
+ return false;
+ }
+
+ /**
+ * Удаление обработчика события.
+ *
+ * @param type тип события
+ * @param listener обработчик события
+ * @param useCapture не используется
+ */
+ public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void {
+ if (dispatcher != null) {
+ useCapture = false;
+ dispatcher.removeEventListener(type, listener, useCapture);
+ }
+ }
+
+ /**
+ *
+ */
+ public function willTrigger(type:String):Boolean {
+ if (dispatcher != null) {
+ return dispatcher.willTrigger(type);
+ }
+ return false;
+ }
+
+ /**
+ * @private
+ * Получение матрицы трансформации объекта. Перед расчётом выполняется проверка необходимости пересчёта матрицы.
+ *
+ * @param matrix матрица, в которую записывается результат
+ *
+ * @return true, если был произведён пересчёт матрицы трансформации, false, если пересчёт не понадобился
+ */
+ alternativa3d function getTransformation(matrix:Matrix3D):Boolean {
+ var rootObject:Object3D = _scene._root;
+ var topNonTransformedObject:Object3D;
+ var currentObject:Object3D = this;
+
+ // Поиск первого нетрансформированного объекта в дереве объектов
+ do {
+ if (currentObject.changeCoordsOperation.queued || currentObject.changeRotationOrScaleOperation.queued) {
+ topNonTransformedObject = currentObject._parent;
+ }
+ } while ((currentObject = currentObject._parent) != rootObject);
+
+ if (topNonTransformedObject != null) {
+ // Расчёт матрицы трансформации
+ matrix.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ);
+ currentObject = this;
+ while ((currentObject = currentObject._parent) != topNonTransformedObject) {
+ matrix1.toTransform(currentObject._coords.x, currentObject._coords.y, currentObject._coords.z, currentObject._rotationX, currentObject._rotationY, currentObject._rotationZ, currentObject._scaleX, currentObject._scaleY, currentObject._scaleZ);
+ matrix.combine(matrix1);
+ }
+ if (topNonTransformedObject != rootObject) {
+ matrix.combine(topNonTransformedObject._transformation);
+ }
+ return true;
+ }
+ matrix.copy(_transformation);
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Operation.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Operation.as
new file mode 100644
index 0000000..ae0236c
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Operation.as
@@ -0,0 +1,126 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.types.Set;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ */
+ public class Operation {
+
+ alternativa3d static const OBJECT_CALCULATE_TRANSFORMATION:uint = 0x01000000;
+ alternativa3d static const OBJECT_CALCULATE_MOBILITY:uint = 0x02000000;
+ alternativa3d static const VERTEX_CALCULATE_COORDS:uint = 0x03000000;
+ alternativa3d static const FACE_CALCULATE_NORMAL:uint = 0x04000000;
+ alternativa3d static const FACE_CALCULATE_UV:uint = 0x05000000;
+ alternativa3d static const FACE_UPDATE_PRIMITIVE:uint = 0x06000000;
+ alternativa3d static const SCENE_CALCULATE_BSP:uint = 0x07000000;
+ alternativa3d static const FACE_UPDATE_MATERIAL:uint = 0x08000000;
+ alternativa3d static const SPRITE_UPDATE_MATERIAL:uint = 0x09000000;
+ alternativa3d static const FACE_CALCULATE_FRAGMENTS_UV:uint = 0x0A000000;
+ alternativa3d static const CAMERA_CALCULATE_MATRIX:uint = 0x0B000000;
+ alternativa3d static const CAMERA_CALCULATE_PLANES:uint = 0x0C000000;
+ alternativa3d static const CAMERA_RENDER:uint = 0x0D000000;
+ alternativa3d static const SCENE_CLEAR_PRIMITIVES:uint = 0x0E000000;
+
+ // Объект
+ alternativa3d var object:Object;
+
+ // Метод
+ alternativa3d var method:Function;
+
+ // Название метода
+ alternativa3d var name:String;
+
+ // Последствия
+ private var sequel:Operation;
+ private var sequels:Set;
+
+ // Приоритет операции
+ alternativa3d var priority:uint;
+
+ // Находится ли операция в очереди
+ alternativa3d var queued:Boolean = false;
+
+ public function Operation(name:String, object:Object = null, method:Function = null, priority:uint = 0) {
+ this.object = object;
+ this.method = method;
+ this.name = name;
+ this.priority = priority;
+ }
+
+ // Добавить последствие
+ alternativa3d function addSequel(operation:Operation):void {
+ if (sequel == null) {
+ if (sequels == null) {
+ sequel = operation;
+ } else {
+ sequels[operation] = true;
+ }
+ } else {
+ if (sequel != operation) {
+ sequels = new Set(true);
+ sequels[sequel] = true;
+ sequels[operation] = true;
+ sequel = null;
+ }
+ }
+ }
+
+ // Удалить последствие
+ alternativa3d function removeSequel(operation:Operation):void {
+ if (sequel == null) {
+ if (sequels != null) {
+ delete sequels[operation];
+ var key:*;
+ var single:Boolean = false;
+ for (key in sequels) {
+ if (single) {
+ single = false;
+ break;
+ }
+ single = true;
+ }
+ if (single) {
+ sequel = key;
+ sequels = null;
+ }
+ }
+ } else {
+ if (sequel == operation) {
+ sequel = null;
+ }
+ }
+ }
+
+ alternativa3d function collectSequels(collector:Array):void {
+ if (sequel == null) {
+ // Проверяем последствия
+ for (var key:* in sequels) {
+ var operation:Operation = key;
+ // Если операция ещё не в очереди
+ if (!operation.queued) {
+ // Добавляем её в очередь
+ collector.push(operation);
+ // Устанавливаем флаг очереди
+ operation.queued = true;
+ // Вызываем добавление в очередь её последствий
+ operation.collectSequels(collector);
+ }
+ }
+ } else {
+ if (!sequel.queued) {
+ collector.push(sequel);
+ sequel.queued = true;
+ sequel.collectSequels(collector);
+ }
+ }
+ }
+
+ public function toString():String {
+ return "[Operation " + (priority >>> 24) + "/" + (priority & 0xFFFFFF) + " " + object + "." + name + "]";
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/PolyPrimitive.as b/Alternativa3D5/5.5/alternativa/engine3d/core/PolyPrimitive.as
new file mode 100644
index 0000000..6e5d645
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/core/PolyPrimitive.as
@@ -0,0 +1,128 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.*;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ * Примитивный полигон (примитив), хранящийся в узле BSP-дерева.
+ */
+ public class PolyPrimitive {
+
+ /**
+ * @private
+ * Количество точек
+ */
+ alternativa3d var num:uint;
+ /**
+ * @private
+ * Точки
+ */
+ alternativa3d var points:Array = new Array();
+ /**
+ * @private
+ * UV-координаты
+ */
+ alternativa3d var uvs:Array = new Array();
+ /**
+ * @private
+ * Грань
+ */
+ alternativa3d var face:Face;
+ /**
+ * @private
+ * Родительский примитив
+ */
+ alternativa3d var parent:PolyPrimitive;
+ /**
+ * @private
+ * Соседний примитив (при наличии родительского)
+ */
+ alternativa3d var sibling:PolyPrimitive;
+ /**
+ * @private
+ * Фрагменты
+ */
+ alternativa3d var backFragment:PolyPrimitive;
+ /**
+ * @private
+ */
+ alternativa3d var frontFragment:PolyPrimitive;
+ /**
+ * @private
+ * Рассечения
+ */
+ alternativa3d var splitTime1:Number;
+ /**
+ * @private
+ */
+ alternativa3d var splitTime2:Number;
+ /**
+ * @private
+ * BSP-нода, в которой находится примитив
+ */
+ alternativa3d var node:BSPNode;
+ /**
+ * @private
+ * Значения для расчёта качества сплиттера
+ */
+ alternativa3d var splits:uint;
+ /**
+ * @private
+ */
+ alternativa3d var disbalance:int;
+ /**
+ * @private
+ * Качество примитива как сплиттера (меньше - лучше)
+ */
+ public var splitQuality:Number;
+ /**
+ * @private
+ * Приоритет в BSP-дереве. Чем ниже мобильность, тем примитив выше в дереве.
+ */
+ public var mobility:int;
+
+ // Хранилище неиспользуемых примитивов
+ static private var collector:Array = new Array();
+
+ /**
+ * @private
+ * Создать примитив
+ */
+ static alternativa3d function createPolyPrimitive():PolyPrimitive {
+ var primitive:PolyPrimitive;
+ if ((primitive = collector.pop()) != null) {
+ return primitive;
+ }
+ return new PolyPrimitive();
+ }
+
+ /**
+ * @private
+ * Кладёт примитив в коллектор для последующего реиспользования.
+ * Ссылка на грань и массивы точек зачищаются в этом методе.
+ * Ссылки на фрагменты (parent, sibling, back, front) должны быть зачищены перед запуском метода.
+ *
+ * Исключение:
+ * при сборке примитивов в сцене ссылки на back и front зачищаются после запуска метода.
+ *
+ * @param primitive примитив на реиспользование
+ */
+ static alternativa3d function destroyPolyPrimitive(primitive:PolyPrimitive):void {
+ primitive.face = null;
+ for (var i:uint = 0; i < primitive.num; i++) {
+ primitive.points.pop();
+ primitive.uvs.pop();
+ }
+ collector.push(primitive);
+ }
+
+ /**
+ * Строковое представление объекта.
+ */
+ public function toString():String {
+ return "[Primitive " + face._mesh._name + "]";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Scene3D.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Scene3D.as
new file mode 100644
index 0000000..bed9480
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Scene3D.as
@@ -0,0 +1,1287 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.SpriteTextureMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.engine3d.materials.WireMaterial;
+ import alternativa.types.*;
+
+ import flash.display.Shape;
+ import flash.display.Sprite;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+ use namespace alternativatypes;
+
+ /**
+ * Сцена является контейнером 3D-объектов, с которыми ведётся работа. Все взаимодействия объектов
+ * происходят в пределах одной сцены. Класс обеспечивает работу системы сигналов и реализует алгоритм построения
+ * BSP-дерева для содержимого сцены.
+ */
+ public class Scene3D {
+ // Операции
+ /**
+ * @private
+ * Полное обновление BSP-дерева
+ */
+ alternativa3d var updateBSPOperation:Operation = new Operation("updateBSP", this);
+ /**
+ * @private
+ * Изменение примитивов
+ */
+ alternativa3d var changePrimitivesOperation:Operation = new Operation("changePrimitives", this);
+ /**
+ * @private
+ * Расчёт BSP-дерева
+ */
+ alternativa3d var calculateBSPOperation:Operation = new Operation("calculateBSP", this, calculateBSP, Operation.SCENE_CALCULATE_BSP);
+ /**
+ * @private
+ * Очистка списков изменений
+ */
+ alternativa3d var clearPrimitivesOperation:Operation = new Operation("clearPrimitives", this, clearPrimitives, Operation.SCENE_CLEAR_PRIMITIVES);
+
+ /**
+ * @private
+ * Корневой объект
+ */
+ alternativa3d var _root:Object3D;
+
+ /**
+ * @private
+ * Список операций на выполнение
+ */
+ alternativa3d var operations:Array = new Array();
+ //protected var operationSort:Array = ["priority"];
+ //protected var operationSortOptions:Array = [Array.NUMERIC];
+ /**
+ * @private
+ * Вспомогательная пустая операция, используется при удалении операций из списка
+ */
+ alternativa3d var dummyOperation:Operation = new Operation("removed", this);
+
+ /**
+ * @private
+ * Флаг анализа сплиттеров
+ */
+ alternativa3d var _splitAnalysis:Boolean = true;
+ /**
+ * @private
+ * Cбалансированность дерева
+ */
+ alternativa3d var _splitBalance:Number = 0;
+ /**
+ * @private
+ * Список изменённых примитивов
+ */
+ alternativa3d var changedPrimitives:Set = new Set();
+
+ // Вспомогательный список для сборки дочерних примитивов
+ private var childPrimitives:Set = new Set();
+ /**
+ * @private
+ * Список примитивов на добавление/удаление
+ */
+ alternativa3d var addPrimitives:Array = new Array();
+
+ /**
+ * @private
+ * Погрешность при определении точек на плоскости
+ */
+ private var _planeOffsetThreshold:Number = 0.01;
+ /**
+ * @private
+ * BSP-дерево
+ */
+ alternativa3d var bsp:BSPNode;
+
+ /**
+ * @private
+ * Список нод на удаление
+ */
+ alternativa3d var removeNodes:Set = new Set();
+ /**
+ * @private
+ * Вспомогательная пустая нода, используется при удалении нод из дерева
+ */
+ alternativa3d var dummyNode:BSPNode = new BSPNode();
+
+ /**
+ * Создание экземпляра сцены.
+ */
+ public function Scene3D() {
+ // Обновление BSP-дерева требует его пересчёта
+ updateBSPOperation.addSequel(calculateBSPOperation);
+ // Изменение примитивов в случае пересчёта дерева
+ calculateBSPOperation.addSequel(changePrimitivesOperation);
+ // При изменении примитивов необходимо очистить списки изменений
+ changePrimitivesOperation.addSequel(clearPrimitivesOperation);
+ }
+
+ /**
+ * Расчёт сцены. Метод анализирует все изменения, произошедшие с момента предыдущего расчёта, формирует список
+ * команд и исполняет их в необходимой последовательности. В результате расчёта происходит перерисовка во всех
+ * областях вывода, к которым подключены находящиеся в сцене камеры.
+ */
+ public function calculate():void {
+ if (operations[0] != undefined) {
+ // Формируем последствия
+ var operation:Operation;
+ var length:uint = operations.length;
+ var i:uint;
+ for (i = 0; i < length; i++) {
+ operation = operations[i];
+ operation.collectSequels(operations);
+ }
+ // Сортируем операции
+ length = operations.length;
+ sortOperations(0, length - 1);
+ // Запускаем операции
+ //trace("----------------------------------------");
+ for (i = 0; i < length; i++) {
+ operation = operations[i];
+ if (operation.method != null) {
+ //trace("EXECUTE:", operation);
+ operation.method();
+ } else {
+ /*if (operation == dummyOperation) {
+ trace("REMOVED");
+ } else {
+ trace(operation);
+ }*/
+ }
+ }
+ // Очищаем список операций
+ for (i = 0; i < length; i++) {
+ operation = operations.pop();
+ operation.queued = false;
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Сортировка операций, если массив operations пуст будет ошибка
+ *
+ * @param l начальный элемент
+ * @param r конечный элемент
+ */
+ alternativa3d function sortOperations(l:int, r:int):void {
+ var i:int = l;
+ var j:int = r;
+ var left:Operation;
+ var mid:uint = operations[(r + l) >> 1].priority;
+ var right:Operation;
+ do {
+ while ((left = operations[i]).priority < mid) {i++};
+ while (mid < (right = operations[j]).priority) {j--};
+ if (i <= j) {
+ operations[i++] = right;
+ operations[j--] = left;
+ }
+ } while (i <= j)
+ if (l < j) {
+ sortOperations(l, j);
+ }
+ if (i < r) {
+ sortOperations(i, r);
+ }
+ }
+
+ /**
+ * @private
+ * Добавление операции в список
+ *
+ * @param operation добавляемая операция
+ */
+ alternativa3d function addOperation(operation:Operation):void {
+ if (!operation.queued) {
+ operations.push(operation);
+ operation.queued = true;
+ }
+ }
+
+ /**
+ * @private
+ * удаление операции из списка
+ *
+ * @param operation удаляемая операция
+ */
+ alternativa3d function removeOperation(operation:Operation):void {
+ if (operation.queued) {
+ operations[operations.indexOf(operation)] = dummyOperation;
+ operation.queued = false;
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт изменений в BSP-дереве.
+ * Обработка удалённых и добавленных примитивов.
+ */
+ protected function calculateBSP():void {
+ if (updateBSPOperation.queued) {
+
+ // Удаление списка нод, помеченных на удаление
+ removeNodes.clear();
+
+ // Удаление BSP-дерева, перенос примитивов в список дочерних
+ childBSP(bsp);
+ bsp = null;
+
+ // Собираем дочерние примитивы в список нижних
+ assembleChildPrimitives();
+
+ } else {
+
+ var key:*;
+ var primitive:PolyPrimitive;
+
+ // Удаляем ноды из дерева
+ if (!removeNodes.isEmpty()) {
+ var node:BSPNode;
+ while ((node = removeNodes.peek()) != null) {
+
+ // Ищем верхнюю удаляемую ноду
+ var removeNode:BSPNode = node;
+ while ((node = node.parent) != null) {
+ if (removeNodes[node]) {
+ removeNode = node;
+ }
+ }
+
+ // Удаляем ветку
+ var parent:BSPNode = removeNode.parent;
+ var replace:BSPNode = removeBSPNode(removeNode);
+
+ // Если вернулась вспомогательная нода, игнорируем её
+ if (replace == dummyNode) {
+ replace = null;
+ }
+
+ // Если есть родительская нода
+ if (parent != null) {
+ // Заменяем себя на указанную ноду
+ if (parent.front == removeNode) {
+ parent.front = replace;
+ } else {
+ parent.back = replace;
+ }
+ } else {
+ // Если нет родительской ноды, значит заменяем корень на указанную ноду
+ bsp = replace;
+ }
+
+ // Устанавливаем связь с родителем для заменённой ноды
+ if (replace != null) {
+ replace.parent = parent;
+ }
+ }
+
+ // Собираем дочерние примитивы в список на добавление
+ assembleChildPrimitives();
+ }
+ }
+
+ // Если есть примитивы на добавление
+ if (addPrimitives[0] != undefined) {
+ // Если включен анализ сплиттеров
+ if (_splitAnalysis) {
+ // Рассчитываем качество рассечения примитивов
+ analyseSplitQuality();
+ // Сортируем массив примитивов c учётом качества
+ sortPrimitives(0, addPrimitives.length - 1);
+ } else {
+ // Сортируем массив по мобильности
+ sortPrimitivesByMobility(0, addPrimitives.length - 1);
+ }
+
+ // Если корневого нода ещё нет, создаём
+ if (bsp == null) {
+ primitive = addPrimitives.pop();
+ bsp = BSPNode.createBSPNode(primitive);
+ changedPrimitives[primitive] = true;
+ }
+
+ // Встраиваем примитивы в дерево
+ while ((primitive = addPrimitives.pop()) != null) {
+ addBSP(bsp, primitive);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Сортировка граней, если массив addPrimitives пуст будет ошибка
+ *
+ * @param l начальный элемент
+ * @param r конечный элемент
+ */
+ alternativa3d function sortPrimitives(l:int, r:int):void {
+ var i:int = l;
+ var j:int = r;
+ var left:PolyPrimitive;
+ var mid:PolyPrimitive = addPrimitives[(r + l) >> 1];
+ var midMobility:int = mid.mobility;
+ var midSplitQuality:Number = mid.splitQuality;
+ var right:PolyPrimitive;
+ do {
+ while (((left = addPrimitives[i]).mobility > midMobility) || ((left.mobility == midMobility) && (left.splitQuality > midSplitQuality))) {i++};
+ while ((midMobility > (right = addPrimitives[j]).mobility) || ((midMobility == right.mobility) && (midSplitQuality > right.splitQuality))) {j--};
+ if (i <= j) {
+ addPrimitives[i++] = right;
+ addPrimitives[j--] = left;
+ }
+ } while (i <= j)
+ if (l < j) {
+ sortPrimitives(l, j);
+ }
+ if (i < r) {
+ sortPrimitives(i, r);
+ }
+ }
+
+ /**
+ * @private
+ * Сортировка только по мобильности
+ *
+ * @param l начальный элемент
+ * @param r конечный элемент
+ */
+ alternativa3d function sortPrimitivesByMobility(l:int, r:int):void {
+ var i:int = l;
+ var j:int = r;
+ var left:PolyPrimitive;
+ var mid:int = addPrimitives[(r + l) >> 1].mobility;
+ var right:PolyPrimitive;
+ do {
+ while ((left = addPrimitives[i]).mobility > mid) {i++};
+ while (mid > (right = addPrimitives[j]).mobility) {j--};
+ if (i <= j) {
+ addPrimitives[i++] = right;
+ addPrimitives[j--] = left;
+ }
+ } while (i <= j)
+ if (l < j) {
+ sortPrimitivesByMobility(l, j);
+ }
+ if (i < r) {
+ sortPrimitivesByMobility(i, r);
+ }
+ }
+
+
+ /**
+ * @private
+ * Анализ качества сплиттеров
+ */
+ private function analyseSplitQuality():void {
+ // Перебираем примитивы на добавление
+ var i:uint;
+ var length:uint = addPrimitives.length;
+ var maxSplits:uint = 0;
+ var maxDisbalance:uint = 0;
+ var splitter:PolyPrimitive;
+ for (i = 0; i < length; i++) {
+ splitter = addPrimitives[i];
+ if (splitter.face == null) {
+ // Пропускаем спрайтовые примитивы
+ continue;
+ }
+ splitter.splits = 0;
+ splitter.disbalance = 0;
+ var normal:Point3D = splitter.face.globalNormal;
+ var offset:Number = splitter.face.globalOffset;
+ // Проверяем соотношение с другими примитивами не меньшей мобильности на добавление
+ for (var j:uint = 0; j < length; j++) {
+ if (i != j) {
+ var primitive:PolyPrimitive = addPrimitives[j];
+ if (primitive.face == null) {
+ // Пропускаем спрайтовые примитивы
+ continue;
+ }
+ if (splitter.mobility <= primitive.mobility) {
+ // Проверяем наличие точек спереди и сзади сплиттера
+ var pointsFront:Boolean = false;
+ var pointsBack:Boolean = false;
+ for (var k:uint = 0; k < primitive.num; k++) {
+ var point:Point3D = primitive.points[k];
+ var pointOffset:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - offset;
+ if (pointOffset > _planeOffsetThreshold) {
+ if (!pointsFront) {
+ splitter.disbalance++;
+ pointsFront = true;
+ }
+ if (pointsBack) {
+ splitter.splits++;
+ break;
+ }
+ } else {
+ if (pointOffset < -_planeOffsetThreshold) {
+ if (!pointsBack) {
+ splitter.disbalance--;
+ pointsBack = true;
+ }
+ if (pointsFront) {
+ splitter.splits++;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ // Абсолютное значение дисбаланса
+ splitter.disbalance = (splitter.disbalance > 0) ? splitter.disbalance : -splitter.disbalance;
+ // Ищем максимальное количество рассечений и значение дисбаланса
+ maxSplits = (maxSplits > splitter.splits) ? maxSplits : splitter.splits;
+ maxDisbalance = (maxDisbalance > splitter.disbalance) ? maxDisbalance : splitter.disbalance;
+ }
+ // Расчитываем качество сплиттеров
+ for (i = 0; i < length; i++) {
+ splitter = addPrimitives[i];
+ splitter.splitQuality = (1 - _splitBalance)*splitter.splits/maxSplits + _splitBalance*splitter.disbalance/maxDisbalance;
+ }
+ }
+
+ /**
+ * @private
+ * Добавление примитива в BSP-дерево
+ *
+ * @param node текущий узел дерева, в который добавляется примитив
+ * @param primitive добавляемый примитив
+ */
+ protected function addBSP(node:BSPNode, primitive:PolyPrimitive):void {
+ var point:Point3D;
+ var normal:Point3D;
+ var key:*;
+
+ // Сравниваем мобильности ноды и примитива
+ if (primitive.mobility < node.mobility || (node.sprited && primitive.face != null)) {
+ // Мобильность примитива ниже мобильности ноды
+ // Или мобильность примитива равна мобильности спрайтовой ноды
+
+ // Формируем список содержимого ноды и всех примитивов ниже
+ if (node.primitive != null) {
+ childPrimitives[node.primitive] = true;
+ changedPrimitives[node.primitive] = true;
+ node.primitive.node = null;
+ } else {
+ var p:PolyPrimitive;
+ for (key in node.backPrimitives) {
+ p = key;
+ childPrimitives[p] = true;
+ changedPrimitives[p] = true;
+ p.node = null;
+ }
+ for (key in node.frontPrimitives) {
+ p = key;
+ childPrimitives[p] = true;
+ changedPrimitives[p] = true;
+ p.node = null;
+ }
+ }
+ childBSP(node.back);
+ childBSP(node.front);
+
+ // Собираем дочерние примитивы в список нижних
+ assembleChildPrimitives();
+
+ // Если включен анализ сплиттеров
+ if (_splitAnalysis) {
+ // Рассчитываем качество рассечения примитивов
+ analyseSplitQuality();
+ // Сортируем массив примитивов c учётом качества
+ sortPrimitives(0, addPrimitives.length - 1);
+ } else {
+ // Сортируем массив по мобильности
+ sortPrimitivesByMobility(0, addPrimitives.length - 1);
+ }
+
+ // Добавляем примитив в ноду
+ node.primitive = primitive;
+
+ // Пометка об изменении примитива
+ changedPrimitives[primitive] = true;
+
+ // Сохраняем ноду
+ primitive.node = node;
+
+ // Сохраняем плоскость
+ node.normal.copy(primitive.face.globalNormal);
+ node.offset = primitive.face.globalOffset;
+ node.sprited = false;
+
+ // Сохраняем мобильность
+ node.mobility = primitive.mobility;
+
+ // Чистим списки примитивов
+ node.backPrimitives = null;
+ node.frontPrimitives = null;
+
+ // Удаляем дочерние ноды
+ node.back = null;
+ node.front = null;
+
+ } else {
+ // Получаем нормаль из ноды
+ normal = node.normal;
+
+ var points:Array = primitive.points;
+ var uvs:Array = primitive.uvs;
+
+ // Собирательные флаги
+ var pointsFront:Boolean = false;
+ var pointsBack:Boolean = false;
+
+ // Собираем расстояния точек до плоскости
+ for (var i:uint = 0; i < primitive.num; i++) {
+ point = points[i];
+ var pointOffset:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset;
+ if (pointOffset > _planeOffsetThreshold) {
+ pointsFront = true;
+ if (pointsBack) {
+ break;
+ }
+ } else {
+ if (pointOffset < -_planeOffsetThreshold) {
+ pointsBack = true;
+ if (pointsFront) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Если все точки в плоскости или это добавление спрайтовой точки в спрайтовую ноду
+ if (!pointsFront && !pointsBack && (primitive.face != null || node.sprited)) {
+ // Сохраняем ноду
+ primitive.node = node;
+
+ // Если был только базовый примитив, переносим его в список
+ if (node.primitive != null) {
+ node.frontPrimitives = new Set(true);
+ node.frontPrimitives[node.primitive] = true;
+ node.primitive = null;
+ }
+
+ // Если примитив спрайтовый или нормаль полигона сонаправлена с нормалью ноды
+ if (primitive.face == null || Point3D.dot(primitive.face.globalNormal, normal) > 0) {
+ node.frontPrimitives[primitive] = true;
+ } else {
+ if (node.backPrimitives == null) {
+ node.backPrimitives = new Set(true);
+ }
+ node.backPrimitives[primitive] = true;
+ }
+
+ // Пометка об изменении примитива
+ changedPrimitives[primitive] = true;
+ } else {
+ if (!pointsBack) {
+ // Примитив спереди плоскости ноды
+ if (node.front == null) {
+ // Создаём переднюю ноду
+ node.front = BSPNode.createBSPNode(primitive);
+ node.front.parent = node;
+ changedPrimitives[primitive] = true;
+ } else {
+ // Добавляем примитив в переднюю ноду
+ addBSP(node.front, primitive);
+ }
+ } else {
+ if (!pointsFront) {
+ // Примитив сзади плоскости ноды
+ if (node.back == null) {
+ // Создаём заднюю ноду
+ node.back = BSPNode.createBSPNode(primitive);
+ node.back.parent = node;
+ changedPrimitives[primitive] = true;
+ } else {
+ // Добавляем примитив в заднюю ноду
+ addBSP(node.back, primitive);
+ }
+ } else {
+ // Рассечение
+ var backFragment:PolyPrimitive = PolyPrimitive.createPolyPrimitive();
+ var frontFragment:PolyPrimitive = PolyPrimitive.createPolyPrimitive();
+
+ var firstSplit:Boolean = true;
+
+ point = points[0];
+ var offset0:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset;
+ var offset1:Number = offset0;
+ var offset2:Number;
+ for (i = 0; i < primitive.num; i++) {
+ var j:uint;
+ if (i < primitive.num - 1) {
+ j = i + 1;
+ point = points[j];
+ offset2 = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset;
+ } else {
+ j = 0;
+ offset2 = offset0;
+ }
+
+ if (offset1 > _planeOffsetThreshold) {
+ // Точка спереди плоскости ноды
+ frontFragment.points.push(points[i]);
+ frontFragment.uvs.push(primitive.uvs[i]);
+ } else {
+ if (offset1 < -_planeOffsetThreshold) {
+ // Точка сзади плоскости ноды
+ backFragment.points.push(points[i]);
+ backFragment.uvs.push(primitive.uvs[i]);
+ } else {
+ // Рассечение по точке примитива
+ backFragment.points.push(points[i]);
+ backFragment.uvs.push(primitive.uvs[i]);
+ frontFragment.points.push(points[i]);
+ frontFragment.uvs.push(primitive.uvs[i]);
+ }
+ }
+
+ // Рассечение ребра
+ if (offset1 > _planeOffsetThreshold && offset2 < -_planeOffsetThreshold || offset1 < -_planeOffsetThreshold && offset2 > _planeOffsetThreshold) {
+ // Находим точку рассечения
+ var t:Number = offset1/(offset1 - offset2);
+ point = Point3D.interpolate(points[i], points[j], t);
+ backFragment.points.push(point);
+ frontFragment.points.push(point);
+ // Находим UV в точке рассечения
+ var uv:Point;
+ if (primitive.face.uvMatrixBase != null) {
+ uv = Point.interpolate(uvs[j], uvs[i], t);
+ } else {
+ uv = null;
+ }
+ backFragment.uvs.push(uv);
+ frontFragment.uvs.push(uv);
+ // Отмечаем рассечённое ребро
+ if (firstSplit) {
+ primitive.splitTime1 = t;
+ firstSplit = false;
+ } else {
+ primitive.splitTime2 = t;
+ }
+ }
+
+ offset1 = offset2;
+ }
+ backFragment.num = backFragment.points.length;
+ frontFragment.num = frontFragment.points.length;
+
+ // Назначаем мобильность
+ backFragment.mobility = primitive.mobility;
+ frontFragment.mobility = primitive.mobility;
+
+ // Устанавливаем связи рассечённых примитивов
+ backFragment.face = primitive.face;
+ frontFragment.face = primitive.face;
+ backFragment.parent = primitive;
+ frontFragment.parent = primitive;
+ backFragment.sibling = frontFragment;
+ frontFragment.sibling = backFragment;
+ primitive.backFragment = backFragment;
+ primitive.frontFragment = frontFragment;
+
+ // Добавляем фрагменты в дочерние ноды
+ if (node.back == null) {
+ node.back = BSPNode.createBSPNode(backFragment);
+ node.back.parent = node;
+ changedPrimitives[backFragment] = true;
+ } else {
+ addBSP(node.back, backFragment);
+ }
+ if (node.front == null) {
+ node.front = BSPNode.createBSPNode(frontFragment);
+ node.front.parent = node;
+ changedPrimitives[frontFragment] = true;
+ } else {
+ addBSP(node.front, frontFragment);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Удаление узла BSP-дерева, включая все дочерние узлы, помеченные для удаления.
+ *
+ * @param node удаляемый узел
+ * @return корневой узел поддерева, оставшегося после операции удаления
+ */
+ protected function removeBSPNode(node:BSPNode):BSPNode {
+ var replaceNode:BSPNode;
+ if (node != null) {
+ // Удаляем дочерние
+ node.back = removeBSPNode(node.back);
+ node.front = removeBSPNode(node.front);
+
+ if (!removeNodes[node]) {
+ // Если нода не удаляется, возвращает себя
+ replaceNode = node;
+
+ // Проверяем дочерние ноды
+ if (node.back != null) {
+ if (node.back != dummyNode) {
+ node.back.parent = node;
+ } else {
+ node.back = null;
+ }
+ }
+ if (node.front != null) {
+ if (node.front != dummyNode) {
+ node.front.parent = node;
+ } else {
+ node.front = null;
+ }
+ }
+ } else {
+ // Проверяем дочерние ветки
+ if (node.back == null) {
+ if (node.front != null) {
+ // Есть только передняя ветка
+ replaceNode = node.front;
+ node.front = null;
+ }
+ } else {
+ if (node.front == null) {
+ // Есть только задняя ветка
+ replaceNode = node.back;
+ node.back = null;
+ } else {
+ // Есть обе ветки - собираем дочерние примитивы
+ childBSP(node.back);
+ childBSP(node.front);
+ // Используем вспомогательную ноду
+ replaceNode = dummyNode;
+ // Удаляем связи с дочерними нодами
+ node.back = null;
+ node.front = null;
+ }
+ }
+
+ // Удаляем ноду из списка на удаление
+ delete removeNodes[node];
+ // Удаляем ноду
+ node.parent = null;
+ BSPNode.destroyBSPNode(node);
+ }
+ }
+ return replaceNode;
+ }
+
+ /**
+ * @private
+ * Удаление примитива из узла дерева
+ *
+ * @param primitive удаляемый примитив
+ */
+ alternativa3d function removeBSPPrimitive(primitive:PolyPrimitive):void {
+ var node:BSPNode = primitive.node;
+ primitive.node = null;
+
+ var single:Boolean = false;
+ var key:*;
+
+ // Пометка об изменении примитива
+ changedPrimitives[primitive] = true;
+
+ // Если нода единичная
+ if (node.primitive == primitive) {
+ removeNodes[node] = true;
+ node.primitive = null;
+ } else {
+ // Есть передние примитивы
+ if (node.frontPrimitives[primitive]) {
+ // Удаляем примитив спереди
+ delete node.frontPrimitives[primitive];
+
+ // Проверяем количество примитивов спереди
+ for (key in node.frontPrimitives) {
+ if (single) {
+ single = false;
+ break;
+ }
+ single = true;
+ }
+
+ if (key == null) {
+ // Передняя пуста или не спрайтовая нода, значит сзади кто-то есть
+
+ // Переворачиваем дочерние ноды
+ var t:BSPNode = node.back;
+ node.back = node.front;
+ node.front = t;
+
+ // Переворачиваем плоскость ноды
+ node.normal.invert();
+ node.offset = -node.offset;
+
+ // Проверяем количество примитивов сзади
+ for (key in node.backPrimitives) {
+ if (single) {
+ single = false;
+ break;
+ }
+ single = true;
+ }
+
+ // Если сзади один примитив
+ if (single) {
+ // Устанавливаем базовый примитив ноды
+ node.primitive = key;
+ // Устанавливаем мобильность
+ node.mobility = node.primitive.mobility;
+ // Стираем список передних примитивов
+ node.frontPrimitives = null;
+ } else {
+ // Если сзади несколько примитивов, переносим их в передние
+ node.frontPrimitives = node.backPrimitives;
+ // Пересчитываем мобильность ноды по передним примитивам
+ // Присваивается наименьшая мобильность из всех примитивов
+ if (primitive.mobility == node.mobility) {
+ node.mobility = int.MAX_VALUE;
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ }
+ }
+
+ // Стираем список задних примитивов
+ node.backPrimitives = null;
+
+ } else {
+ // Если остался один примитив и сзади примитивов нет
+ if (single && node.backPrimitives == null) {
+ // Устанавливаем базовый примитив ноды
+ node.primitive = key;
+ // Устанавливаем мобильность
+ node.mobility = node.primitive.mobility;
+ // Стираем список передних примитивов
+ node.frontPrimitives = null;
+ } else {
+ // Пересчитываем мобильность ноды
+ if (primitive.mobility == node.mobility) {
+ node.mobility = int.MAX_VALUE;
+ for (key in node.backPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ }
+ }
+ }
+ } else {
+ // Удаляем примитив сзади
+ delete node.backPrimitives[primitive];
+
+ // Проверяем количество примитивов сзади
+ for (key in node.backPrimitives) {
+ break;
+ }
+
+ // Если сзади примитивов больше нет
+ if (key == null) {
+ // Проверяем количество примитивов спереди
+ for (key in node.frontPrimitives) {
+ if (single) {
+ single = false;
+ break;
+ }
+ single = true;
+ }
+
+ // Если спереди один примитив
+ if (single) {
+ // Устанавливаем базовый примитив ноды
+ node.primitive = key;
+ // Устанавливаем мобильность
+ node.mobility = node.primitive.mobility;
+ // Стираем список передних примитивов
+ node.frontPrimitives = null;
+ } else {
+ // Пересчитываем мобильность ноды по передним примитивам
+ if (primitive.mobility == node.mobility) {
+ node.mobility = int.MAX_VALUE;
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ }
+ }
+
+ // Стираем список задних примитивов
+ node.backPrimitives = null;
+ } else {
+ // Пересчитываем мобильность ноды
+ if (primitive.mobility == node.mobility) {
+ node.mobility = int.MAX_VALUE;
+ for (key in node.backPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Удаление и перевставка ветки
+ *
+ * @param node
+ */
+ protected function childBSP(node:BSPNode):void {
+ if (node != null && node != dummyNode) {
+ var primitive:PolyPrimitive = node.primitive;
+ if (primitive != null) {
+ childPrimitives[primitive] = true;
+ changedPrimitives[primitive] = true;
+ node.primitive = null;
+ primitive.node = null;
+ } else {
+ for (var key:* in node.backPrimitives) {
+ primitive = key;
+ childPrimitives[primitive] = true;
+ changedPrimitives[primitive] = true;
+ primitive.node = null;
+ }
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ childPrimitives[primitive] = true;
+ changedPrimitives[primitive] = true;
+ primitive.node = null;
+ }
+ node.backPrimitives = null;
+ node.frontPrimitives = null;
+ }
+ childBSP(node.back);
+ childBSP(node.front);
+ // Удаляем ноду
+ node.parent = null;
+ node.back = null;
+ node.front = null;
+ BSPNode.destroyBSPNode(node);
+ }
+ }
+
+ /**
+ * @private
+ * Сборка списка дочерних примитивов в коллектор
+ */
+ protected function assembleChildPrimitives():void {
+ var primitive:PolyPrimitive;
+ while ((primitive = childPrimitives.take()) != null) {
+ assemblePrimitive(primitive);
+ }
+ }
+
+ /**
+ * @private
+ * Сборка примитивов и разделение на добавленные и удалённые
+ *
+ * @param primitive
+ */
+ private function assemblePrimitive(primitive:PolyPrimitive):void {
+ // Если есть соседний примитив и он может быть собран
+ if (primitive.sibling != null && canAssemble(primitive.sibling)) {
+ // Собираем их в родительский
+ assemblePrimitive(primitive.parent);
+ // Зачищаем связи между примитивами
+ primitive.sibling.sibling = null;
+ primitive.sibling.parent = null;
+ PolyPrimitive.destroyPolyPrimitive(primitive.sibling);
+ primitive.sibling = null;
+ primitive.parent.backFragment = null;
+ primitive.parent.frontFragment = null;
+ primitive.parent = null;
+ PolyPrimitive.destroyPolyPrimitive(primitive);
+ } else {
+ // Если собраться не получилось или родительский
+ addPrimitives.push(primitive);
+ }
+ }
+
+ /**
+ * @private
+ * Проверка, может ли примитив в списке дочерних быть собран
+ *
+ * @param primitive
+ * @return
+ */
+ private function canAssemble(primitive:PolyPrimitive):Boolean {
+ if (childPrimitives[primitive]) {
+ delete childPrimitives[primitive];
+ return true;
+ } else {
+ var backFragment:PolyPrimitive = primitive.backFragment;
+ var frontFragment:PolyPrimitive = primitive.frontFragment;
+ if (backFragment != null) {
+ var assembleBack:Boolean = canAssemble(backFragment);
+ var assembleFront:Boolean = canAssemble(frontFragment);
+ if (assembleBack && assembleFront) {
+ backFragment.parent = null;
+ frontFragment.parent = null;
+ backFragment.sibling = null;
+ frontFragment.sibling = null;
+ primitive.backFragment = null;
+ primitive.frontFragment = null;
+ PolyPrimitive.destroyPolyPrimitive(backFragment);
+ PolyPrimitive.destroyPolyPrimitive(frontFragment);
+ return true;
+ } else {
+ if (assembleBack) {
+ addPrimitives.push(backFragment);
+ }
+ if (assembleFront) {
+ addPrimitives.push(frontFragment);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @private
+ * Очистка списков
+ */
+ private function clearPrimitives():void {
+ changedPrimitives.clear();
+ }
+
+ /**
+ * Проверка наличия изменений в сцене.
+ *
+ * @return true, если в сцене были изменения с предыдущего вызова метода calculate
+ *
+ * @see #calculate()
+ */
+ public function hasChanges():Boolean {
+ var len:int = operations.length;
+ for (var i:int = 0; i < len; i++) {
+ if (operations[i] != dummyOperation) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Корневой объект сцены.
+ */
+ public function get root():Object3D {
+ return _root;
+ }
+
+ /**
+ * @private
+ */
+ public function set root(value:Object3D):void {
+ // Если ещё не является корневым объектом
+ if (_root != value) {
+ // Если устанавливаем не пустой объект
+ if (value != null) {
+ // Если объект был в другом объекте
+ if (value._parent != null) {
+ // Удалить его оттуда
+ value._parent._children.remove(value);
+ } else {
+ // Если объект был корневым в сцене
+ if (value._scene != null && value._scene._root == value) {
+ value._scene.root = null;
+ }
+ }
+ // Удаляем ссылку на родителя
+ value.setParent(null);
+ // Указываем сцену
+ value.setScene(this);
+ // Устанавливаем уровни
+ value.setLevel(0);
+ }
+
+ // Если был корневой объект
+ if (_root != null) {
+ // Удаляем ссылку на родителя
+ _root.setParent(null);
+ // Удаляем ссылку на камеру
+ _root.setScene(null);
+ }
+
+ // Сохраняем корневой объект
+ _root = value;
+ }
+ }
+
+ /**
+ * Флаг активности анализа сплиттеров.
+ * В режиме анализа для каждого добавляемого в BSP-дерево полигона выполняется его оценка в качестве разделяющей
+ * плоскости (сплиттера). Наиболее качественные сплиттеры добавляются в BSP-дерево первыми.
+ *
+ * bottomRadius = topRadiussplitBalance можно влиять на конечный вид BSP-дерева.
+ *
+ * @see #splitBalance
+ * @default true
+ */
+ public function get splitAnalysis():Boolean {
+ return _splitAnalysis;
+ }
+
+ /**
+ * @private
+ */
+ public function set splitAnalysis(value:Boolean):void {
+ if (_splitAnalysis != value) {
+ _splitAnalysis = value;
+ addOperation(updateBSPOperation);
+ }
+ }
+
+ /**
+ * Параметр балансировки BSP-дерева при влюченном режиме анализа сплиттеров.
+ * Может принимать значения от 0 (минимизация фрагментирования полигонов) до 1 (максимальный баланс BSP-дерева).
+ *
+ * @see #splitAnalysis
+ * @default 0
+ */
+ public function get splitBalance():Number {
+ return _splitBalance;
+ }
+
+ /**
+ * @private
+ */
+ public function set splitBalance(value:Number):void {
+ value = (value < 0) ? 0 : ((value > 1) ? 1 : value);
+ if (_splitBalance != value) {
+ _splitBalance = value;
+ if (_splitAnalysis) {
+ addOperation(updateBSPOperation);
+ }
+ }
+ }
+
+ /**
+ * Погрешность определения расстояний и координат. При построении BSP-дерева точка считается попавшей в плоскость сплиттера, если расстояние от точки до плоскости меньше planeOffsetThreshold.
+ *
+ * @default 0.01
+ */
+ public function get planeOffsetThreshold():Number {
+ return _planeOffsetThreshold;
+ }
+
+ /**
+ * @private
+ */
+ public function set planeOffsetThreshold(value:Number):void {
+ value = (value < 0) ? 0 : value;
+ if (_planeOffsetThreshold != value) {
+ _planeOffsetThreshold = value;
+ addOperation(updateBSPOperation);
+ }
+ }
+
+ /**
+ * Визуализация BSP-дерева. Дерево рисуется в заданном контейнере. Каждый узел дерева обозначается точкой, имеющей
+ * цвет материала (в случае текстурного материала показывается цвет первой точки текстуры) первого полигона из этого
+ * узла. Задние узлы рисуются слева-снизу от родителя, передние справа-снизу.
+ *
+ * @param container контейнер для отрисовки дерева
+ */
+ public function drawBSP(container:Sprite):void {
+
+ container.graphics.clear();
+ while (container.numChildren > 0) {
+ container.removeChildAt(0);
+ }
+ if (bsp != null) {
+ drawBSPNode(bsp, container, 0, 0, 1);
+ }
+ }
+
+ /**
+ * @private
+ * Отрисовка узла BSP-дерева при визуализации
+ *
+ * @param node
+ * @param container
+ * @param x
+ * @param y
+ * @param size
+ */
+ private function drawBSPNode(node:BSPNode, container:Sprite, x:Number, y:Number, size:Number):void {
+ var s:Shape = new Shape();
+ container.addChild(s);
+ s.x = x;
+ s.y = y;
+ var color:uint = 0xFF0000;
+ var primitive:PolyPrimitive;
+ if (node.primitive != null) {
+ primitive = node.primitive;
+ } else {
+ if (node.frontPrimitives != null) {
+ primitive = node.frontPrimitives.peek();
+ }
+ }
+ if (primitive != null) {
+ if (primitive.face == null) {
+ // Спрайтовый примитив
+ var material:SpriteTextureMaterial = (primitive as SpritePrimitive).sprite._material as SpriteTextureMaterial;
+ if (material != null && material._texture != null) {
+ color = material._texture._bitmapData.getPixel(0, 0);
+ }
+ } else {
+ if (primitive.face._surface != null && primitive.face._surface._material != null) {
+ if (primitive.face._surface._material is FillMaterial) {
+ color = FillMaterial(primitive.face._surface._material)._color;
+ }
+ if (primitive.face._surface._material is WireMaterial) {
+ color = WireMaterial(primitive.face._surface._material)._color;
+ }
+ if ((primitive.face._surface._material is TextureMaterial) && TextureMaterial(primitive.face._surface._material)._texture != null) {
+ color = TextureMaterial(primitive.face._surface._material).texture._bitmapData.getPixel(0, 0);
+ }
+ }
+ }
+ }
+
+ if (node == dummyNode) {
+ color = 0xFF00FF;
+ }
+
+ s.graphics.beginFill(color);
+ s.graphics.drawCircle(0, 0, 3);
+ s.graphics.endFill();
+
+ var xOffset:Number = 100;
+ var yOffset:Number = 20;
+ if (node.back != null) {
+ container.graphics.lineStyle(0, 0x660000);
+ container.graphics.moveTo(x, y);
+ container.graphics.lineTo(x - xOffset*size, y + yOffset);
+ drawBSPNode(node.back, container, x - xOffset*size, y + yOffset, size*0.8);
+ }
+ if (node.front != null) {
+ container.graphics.lineStyle(0, 0x006600);
+ container.graphics.moveTo(x, y);
+ container.graphics.lineTo(x + xOffset*size, y + yOffset);
+ drawBSPNode(node.front, container, x + xOffset*size, y + yOffset, size*0.8);
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Sprite3D.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Sprite3D.as
new file mode 100644
index 0000000..e9ebba0
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Sprite3D.as
@@ -0,0 +1,207 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.materials.SpriteMaterial;
+
+ use namespace alternativa3d;
+
+ /**
+ * Объект представляет собой точку в трёхмерном пространстве. Объекту может быть назначен материал для вывода различных изображений в
+ * месте его нахождения.
+ */
+ public class Sprite3D extends Object3D {
+ /**
+ * Счетчик имен объектов
+ */
+ private static var counter:uint = 0;
+
+ /**
+ * @private
+ * Обновление материала.
+ */
+ alternativa3d var updateMaterialOperation:Operation = new Operation("updateSpriteMaterial", this, updateMaterial, Operation.SPRITE_UPDATE_MATERIAL);
+ /**
+ * @private
+ * Примитив.
+ */
+ alternativa3d var primitive:SpritePrimitive;
+ /**
+ * @private
+ * Материал.
+ */
+ alternativa3d var _material:SpriteMaterial;
+ /**
+ * @private
+ * Размер материала.
+ */
+ alternativa3d var _materialScale:Number;
+
+ /**
+ * Создание экземпляра спрайта.
+ *
+ * @param name имя спрайта
+ */
+ public function Sprite3D(name:String = null) {
+ super(name);
+ // Создаем примитив спрайта
+ primitive = new SpritePrimitive();
+ primitive.sprite = this;
+ // В примитиве одна точка - координата спрайта
+ primitive.points = [this.globalCoords];
+ primitive.num = 1;
+ primitive.mobility = int.MAX_VALUE;
+ }
+
+ /**
+ * @private
+ * Расчет перемещения точки спрайта.
+ */
+ override alternativa3d function calculateTransformation():void {
+ super.calculateTransformation();
+ // Произошло перемещение спрайта, необходимо перевставить точку в БСП
+ updatePrimitive();
+ if (changeRotationOrScaleOperation.queued) {
+ // Считаем размер материала
+ // Считается для любого материала, без отдельных операций
+ var a:Number = _transformation.a;
+ var b:Number = _transformation.b;
+ var c:Number = _transformation.c;
+ var e:Number = _transformation.e;
+ var f:Number = _transformation.f;
+ var g:Number = _transformation.g;
+ var i:Number = _transformation.i;
+ var j:Number = _transformation.j;
+ var k:Number = _transformation.k;
+ _materialScale = (Math.sqrt(a*a + e*e + i*i) + Math.sqrt(b*b + f*f + j*j) + Math.sqrt(c*c + g*g + k*k))/3;
+ }
+ }
+
+ /**
+ * Перевставка точки спрайта в БСП дереве.
+ */
+ private function updatePrimitive():void {
+ // Если примитив в BSP-дереве
+ if (primitive.node != null) {
+ // Удаление примитива
+ _scene.removeBSPPrimitive(primitive);
+ }
+ _scene.addPrimitives.push(primitive);
+ }
+
+ /**
+ * Перерисовка скинов спрайта.
+ */
+ private function updateMaterial():void {
+ if (!calculateTransformationOperation.queued) {
+ _scene.changedPrimitives[primitive] = true;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function addToScene(scene:Scene3D):void {
+ super.addToScene(scene);
+ // Подписываем сцену на операции
+ calculateTransformationOperation.addSequel(scene.calculateBSPOperation);
+ updateMaterialOperation.addSequel(scene.changePrimitivesOperation);
+ // Добавляем на сцену материал
+ if (_material != null) {
+ _material.addToScene(scene);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(updateMaterialOperation);
+
+ // Если примитив в BSP-дереве
+ if (primitive.node != null) {
+ // Удаляем примитив из сцены
+ scene.removeBSPPrimitive(primitive);
+ }
+
+ // Посылаем операцию сцены на расчёт BSP
+ scene.addOperation(scene.calculateBSPOperation);
+
+ // Отписываем сцену от операций
+ calculateTransformationOperation.removeSequel(scene.calculateBSPOperation);
+ updateMaterialOperation.removeSequel(scene.changePrimitivesOperation);
+ // Удаляем из сцены материал
+ if (_material != null) {
+ _material.removeFromScene(scene);
+ }
+ super.removeFromScene(scene);
+ }
+
+ /**
+ * @private
+ * Изменение материала.
+ */
+ alternativa3d function addMaterialChangedOperationToScene():void {
+ if (_scene != null) {
+ _scene.addOperation(updateMaterialOperation);
+ }
+ }
+
+ /**
+ * Материал для отображения спрайта.
+ */
+ public function get material():SpriteMaterial {
+ return _material;
+ }
+
+ /**
+ * @private
+ */
+ public function set material(value:SpriteMaterial):void {
+ if (_material != value) {
+ if (_material != null) {
+ _material.removeFromSprite(this);
+ if (_scene != null) {
+ _material.removeFromScene(_scene);
+ }
+ }
+ if (value != null) {
+ if (value._sprite != null) {
+ value._sprite.material = null;
+ }
+ value.addToSprite(this);
+ if (_scene != null) {
+ value.addToScene(_scene);
+ }
+ }
+ _material = value;
+ // Отправляем операцию изменения материала
+ addMaterialChangedOperationToScene();
+ }
+ }
+
+ /**
+ * Имя объекта по умолчанию.
+ *
+ * @return имя объекта по умолчанию
+ */
+ override protected function defaultName():String {
+ return "sprite" + ++counter;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Sprite3D();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function clonePropertiesFrom(source:Object3D):void {
+ super.clonePropertiesFrom(source);
+ material = (source as Sprite3D).material.clone() as SpriteMaterial;
+ }
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/SpritePrimitive.as b/Alternativa3D5/5.5/alternativa/engine3d/core/SpritePrimitive.as
new file mode 100644
index 0000000..52c315b
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/core/SpritePrimitive.as
@@ -0,0 +1,42 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.*;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ * Спрайтовый примитив.
+ */
+ public class SpritePrimitive extends PolyPrimitive {
+
+ /**
+ * @private
+ * Спрайт, которому принадлежит примитив.
+ */
+ alternativa3d var sprite:Sprite3D;
+ /**
+ * @private
+ * Параметр используется для сортировки примитивов в камере.
+ */
+ alternativa3d var screenDepth:Number;
+
+ /**
+ * @private
+ * Создает экземпляр класса.
+ */
+ public function SpritePrimitive() {
+ super();
+ uvs = null;
+ }
+
+ /**
+ * @private
+ * Строковое представление объекта.
+ */
+ override public function toString():String {
+ return "[SpritePrimitive " + sprite.toString() + "]";
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Surface.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Surface.as
new file mode 100644
index 0000000..d0a8501
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Surface.as
@@ -0,0 +1,477 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.errors.FaceExistsError;
+ import alternativa.engine3d.errors.FaceNotFoundError;
+ import alternativa.engine3d.errors.InvalidIDError;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.types.Set;
+
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IEventDispatcher;
+
+ use namespace alternativa3d;
+
+ /**
+ * Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одной и той же поверхностью.
+ * Между нажатием и отпусканием кнопки могут происходить любые другие события.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.CLICK
+ */
+ [Event (name="click", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь нажимает левую кнопку мыши над поверхностью.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN
+ */
+ [Event (name="mouseDown", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь отпускает левую кнопку мыши над поверхностью.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP
+ */
+ [Event (name="mouseUp", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь наводит курсор мыши на поверхность.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER
+ */
+ [Event (name="mouseOver", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь уводит курсор мыши с поверхности.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT
+ */
+ [Event (name="mouseOut", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь перемещает курсор мыши над поверхностью.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE
+ */
+ [Event (name="mouseMove", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь вращает колесо мыши над поверхностью.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL
+ */
+ [Event (name="mouseWheel", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Поверхность — набор граней, объединённых в группу. Поверхности используются для установки материалов,
+ * визуализирующих грани объекта.
+ *
+ * flash.events.IEventDispatcher и может рассылать мышиные события, содержащие информацию
+ * о точке в трёхмерном пространстве, в которой произошло событие.
+ */
+ public class Surface implements IEventDispatcher {
+ // Операции
+ /**
+ * @private
+ * Изменение набора граней
+ */
+ alternativa3d var changeFacesOperation:Operation = new Operation("changeFaces", this);
+ /**
+ * @private
+ * Изменение материала
+ */
+ alternativa3d var changeMaterialOperation:Operation = new Operation("changeMaterial", this);
+
+ /**
+ * @private
+ * Меш
+ */
+ alternativa3d var _mesh:Mesh;
+ /**
+ * @private
+ * Материал
+ */
+ alternativa3d var _material:SurfaceMaterial;
+ /**
+ * @private
+ * Грани
+ */
+ alternativa3d var _faces:Set = new Set();
+
+ /**
+ * Флаг указывает, будет ли объект принимать мышиные события.
+ */
+ public var mouseEnabled:Boolean = true;
+ /**
+ * Диспетчер событий.
+ */
+ private var dispatcher:EventDispatcher;
+
+ /**
+ * Создание экземпляра поверхности.
+ */
+ public function Surface() {}
+
+ /**
+ * Добавление грани в поверхность.
+ *
+ * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани полигонального объекта
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError грань не найдена в полигональном объекте содержащем поверхность
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ * @throws alternativa.engine3d.errors.FaceExistsError поверхность уже содержит указанную грань
+ *
+ * @see Face
+ */
+ public function addFace(face:Object):void {
+ var byLink:Boolean = face is Face;
+
+ // Проверяем на нахождение поверхности в меше
+ if (_mesh == null) {
+ throw new FaceNotFoundError(face, this);
+ }
+
+ // Проверяем на null
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие грани в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Face(face)._mesh != _mesh) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_mesh._faces[face] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(face, this);
+ } else {
+ if (!(_mesh._faces[face] is Face)) {
+ throw new InvalidIDError(face, this);
+ }
+ }
+ }
+
+ // Находим грань
+ var f:Face = byLink ? Face(face) : _mesh._faces[face];
+
+ // Проверяем наличие грани в поверхности
+ if (_faces.has(f)) {
+ // Если грань уже в поверхности
+ throw new FaceExistsError(f, this);
+ }
+
+ // Проверяем грань на нахождение в другой поверхности
+ if (f._surface != null) {
+ // Удаляем её из той поверхности
+ f._surface._faces.remove(f);
+ f.removeFromSurface(f._surface);
+ }
+
+ // Добавляем грань в поверхность
+ _faces.add(f);
+ f.addToSurface(this);
+
+ // Отправляем операцию изменения набора граней
+ _mesh.addOperationToScene(changeFacesOperation);
+ }
+
+ /**
+ * Удаление грани из поверхности.
+ *
+ * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани полигонального объекта
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError поверхность не содержит указанную грань
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Face
+ */
+ public function removeFace(face:Object):void {
+ var byLink:Boolean = face is Face;
+
+ // Проверяем на нахождение поверхности в меше
+ if (_mesh == null) {
+ throw new FaceNotFoundError(face, this);
+ }
+
+ // Проверяем на null
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие грани в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Face(face)._mesh != _mesh) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_mesh._faces[face] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(face, this);
+ } else {
+ if (!(_mesh._faces[face] is Face)) {
+ throw new InvalidIDError(face, this);
+ }
+ }
+
+ }
+
+ // Находим грань
+ var f:Face = byLink ? Face(face) : _mesh._faces[face];
+
+ // Проверяем наличие грани в поверхности
+ if (!_faces.has(f)) {
+ // Если грань не в поверхности
+ throw new FaceNotFoundError(f, this);
+ }
+
+ // Удаляем грань из поверхности
+ _faces.remove(f);
+ f.removeFromSurface(this);
+
+ // Отправляем операцию изменения набора граней
+ _mesh.addOperationToScene(changeFacesOperation);
+ }
+
+ /**
+ * Материал поверхности. При установке нового значения, устанавливаемый материал будет удалён из старой поверхности.
+ */
+ public function get material():SurfaceMaterial {
+ return _material;
+ }
+
+ /**
+ * @private
+ */
+ public function set material(value:SurfaceMaterial):void {
+ if (_material != value) {
+ // Если был материал
+ if (_material != null) {
+ // Удалить материал из поверхности
+ _material.removeFromSurface(this);
+ // Удалить материал из меша
+ if (_mesh != null) {
+ _material.removeFromMesh(_mesh);
+ // Удалить материал из сцены
+ if (_mesh._scene != null) {
+ _material.removeFromScene(_mesh._scene);
+ }
+ }
+ }
+ // Если новый материал
+ if (value != null) {
+ // Если материал был в другой поверхности
+ if (value._surface != null) {
+ // Удалить его оттуда
+ value._surface.material = null;
+ }
+ // Добавить материал в поверхность
+ value.addToSurface(this);
+ // Добавить материал в меш
+ if (_mesh != null) {
+ value.addToMesh(_mesh);
+ // Добавить материал в сцену
+ if (_mesh._scene != null) {
+ value.addToScene(_mesh._scene);
+ }
+ }
+ }
+ // Сохраняем материал
+ _material = value;
+ // Отправляем операцию изменения материала
+ addMaterialChangedOperationToScene();
+ }
+ }
+
+ /**
+ * Набор граней поверхности.
+ */
+ public function get faces():Set {
+ return _faces.clone();
+ }
+
+ /**
+ * Полигональный объект, которому принадлежит поверхность.
+ */
+ public function get mesh():Mesh {
+ return _mesh;
+ }
+
+ /**
+ * Идентификатор поверхности в полигональном объекте. Если поверхность не принадлежит ни одному объекту,
+ * значение идентификатора равно null.
+ */
+ public function get id():Object {
+ return (_mesh != null) ? _mesh.getSurfaceId(this) : null;
+ }
+
+ /**
+ * @private
+ * Добавление в сцену.
+ *
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {
+ // Добавляем на сцену материал
+ if (_material != null) {
+ _material.addToScene(scene);
+ }
+ }
+
+ /**
+ * @private
+ * Удаление из сцены.
+ *
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(changeFacesOperation);
+ scene.removeOperation(changeMaterialOperation);
+ // Удаляем из сцены материал
+ if (_material != null) {
+ _material.removeFromScene(scene);
+ }
+ }
+
+ /**
+ * @private
+ * Добавление к мешу
+ * @param mesh
+ */
+ alternativa3d function addToMesh(mesh:Mesh):void {
+ // Подписка на операции меша
+
+ // Добавляем в меш материал
+ if (_material != null) {
+ _material.addToMesh(mesh);
+ }
+ // Сохранить меш
+ _mesh = mesh;
+ }
+
+ /**
+ * @private
+ * Удаление из меша
+ *
+ * @param mesh
+ */
+ alternativa3d function removeFromMesh(mesh:Mesh):void {
+ // Отписка от операций меша
+
+ // Удаляем из меша материал
+ if (_material != null) {
+ _material.removeFromMesh(mesh);
+ }
+ // Удалить ссылку на меш
+ _mesh = null;
+ }
+
+ /**
+ * @private
+ * Удаление граней
+ */
+ alternativa3d function removeFaces():void {
+ for (var key:* in _faces) {
+ var face:Face = key;
+ _faces.remove(face);
+ face.removeFromSurface(this);
+ }
+ }
+
+ /**
+ * @private
+ * Изменение материала
+ */
+ alternativa3d function addMaterialChangedOperationToScene():void {
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeMaterialOperation);
+ }
+ }
+
+ /**
+ * Строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ var length:uint = _faces.length;
+ var res:String = "[Surface ID:" + id + ((length > 0) ? " faces:" : "");
+ var i:uint = 0;
+ for (var key:* in _faces) {
+ var face:Face = key;
+ res += face.id + ((i < length - 1) ? ", " : "");
+ i++;
+ }
+ res += "]";
+ return res;
+ }
+
+ /**
+ * Добавление обработчика события.
+ *
+ * @param type тип события
+ * @param listener обработчик события
+ * @param useCapture не используется,
+ * @param priority приоритет обработчика. Обработчики с большим приоритетом выполняются раньше. Обработчики с одинаковым приоритетом
+ * выполняются в порядке их добавления.
+ * @param useWeakReference флаг использования слабой ссылки для обработчика
+ */
+ public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
+ if (dispatcher == null) {
+ dispatcher = new EventDispatcher(this);
+ }
+ useCapture = false;
+ dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
+ }
+
+ /**
+ * Рассылка события.
+ *
+ * @param event посылаемое событие
+ * @return false
+ */
+ public function dispatchEvent(event:Event):Boolean {
+ if (dispatcher != null) {
+ dispatcher.dispatchEvent(event);
+ }
+ return false;
+ }
+
+ /**
+ * Проверка наличия зарегистрированных обработчиков события указанного типа.
+ *
+ * @param type тип события
+ * @return true если есть обработчики события указанного типа, иначе false
+ */
+ public function hasEventListener(type:String):Boolean {
+ if (dispatcher != null) {
+ return dispatcher.hasEventListener(type);
+ }
+ return false;
+ }
+
+ /**
+ * Удаление обработчика события.
+ *
+ * @param type тип события
+ * @param listener обработчик события
+ * @param useCapture не используется
+ */
+ public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void {
+ if (dispatcher != null) {
+ useCapture = false;
+ dispatcher.removeEventListener(type, listener, useCapture);
+ }
+ }
+
+ /**
+ *
+ */
+ public function willTrigger(type:String):Boolean {
+ if (dispatcher != null) {
+ return dispatcher.willTrigger(type);
+ }
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/core/Vertex.as b/Alternativa3D5/5.5/alternativa/engine3d/core/Vertex.as
new file mode 100644
index 0000000..d7f5be5
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/core/Vertex.as
@@ -0,0 +1,250 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ use namespace alternativa3d;
+
+ /**
+ * Вершина полигона в трёхмерном пространстве. Вершина хранит свои координаты, а также ссылки на
+ * полигональный объект и грани этого объекта, которым она принадлежит.
+ */
+ final public class Vertex {
+ // Операции
+ /**
+ * @private
+ * Изменение локальных координат
+ */
+ alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this);
+ /**
+ * @private
+ * Расчёт глобальных координат
+ */
+ alternativa3d var calculateCoordsOperation:Operation = new Operation("calculateCoords", this, calculateCoords, Operation.VERTEX_CALCULATE_COORDS);
+
+ /**
+ * @private
+ * Меш
+ */
+ alternativa3d var _mesh:Mesh;
+ /**
+ * @private
+ * Координаты точки
+ */
+ alternativa3d var _coords:Point3D;
+ /**
+ * @private
+ * Грани
+ */
+ alternativa3d var _faces:Set = new Set();
+ /**
+ * @private
+ * Координаты в сцене
+ */
+ alternativa3d var globalCoords:Point3D = new Point3D();
+
+ /**
+ * Создание экземпляра вершины.
+ *
+ * @param x координата вершины по оси X
+ * @param y координата вершины по оси Y
+ * @param z координата вершины по оси Z
+ */
+ public function Vertex(x:Number = 0, y:Number = 0, z:Number = 0) {
+ _coords = new Point3D(x, y, z);
+
+ // Изменение координат инициирует пересчёт глобальных координат
+ changeCoordsOperation.addSequel(calculateCoordsOperation);
+ }
+
+ /**
+ * Вызывается из операции calculateCoordsOperation для расчета глобальных координат вершины
+ */
+ private function calculateCoords():void {
+ globalCoords.copy(_coords);
+ globalCoords.transform(_mesh._transformation);
+ }
+
+ /**
+ * @private
+ */
+ public function set x(value:Number):void {
+ if (_coords.x != value) {
+ _coords.x = value;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeCoordsOperation);
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set y(value:Number):void {
+ if (_coords.y != value) {
+ _coords.y = value;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeCoordsOperation);
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set z(value:Number):void {
+ if (_coords.z != value) {
+ _coords.z = value;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeCoordsOperation);
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set coords(value:Point3D):void {
+ if (!_coords.equals(value)) {
+ _coords.copy(value);
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeCoordsOperation);
+ }
+ }
+ }
+
+ /**
+ * координата вершины по оси X.
+ */
+ public function get x():Number {
+ return _coords.x;
+ }
+
+ /**
+ * координата вершины по оси Y.
+ */
+ public function get y():Number {
+ return _coords.y;
+ }
+
+ /**
+ * координата вершины по оси Z.
+ */
+ public function get z():Number {
+ return _coords.z;
+ }
+
+ /**
+ * Координаты вершины.
+ */
+ public function get coords():Point3D {
+ return _coords.clone();
+ }
+
+ /**
+ * Полигональный объект, которому принадлежит вершина.
+ */
+ public function get mesh():Mesh {
+ return _mesh;
+ }
+
+ /**
+ * Множество граней, которым принадлежит вершина. Каждый элемент множества является объектом класса
+ * altertnativa.engine3d.core.Face.
+ *
+ * @see Face
+ */
+ public function get faces():Set {
+ return _faces.clone();
+ }
+
+ /**
+ * Идентификатор вершины в полигональном объекте. Если вершина не принадлежит полигональному объекту, возвращается null.
+ */
+ public function get id():Object {
+ return (_mesh != null) ? _mesh.getVertexId(this) : null;
+ }
+
+ /**
+ * @private
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {
+ // При добавлении на сцену расчитать глобальные координаты
+ scene.addOperation(calculateCoordsOperation);
+ }
+
+ /**
+ * @private
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(calculateCoordsOperation);
+ scene.removeOperation(changeCoordsOperation);
+ }
+
+ /**
+ * @private
+ * @param mesh
+ */
+ alternativa3d function addToMesh(mesh:Mesh):void {
+ // Подписка на операции меша
+ mesh.changeCoordsOperation.addSequel(calculateCoordsOperation);
+ mesh.changeRotationOrScaleOperation.addSequel(calculateCoordsOperation);
+ // Сохранить меш
+ _mesh = mesh;
+ }
+
+ /**
+ * @private
+ * @param mesh
+ */
+ alternativa3d function removeFromMesh(mesh:Mesh):void {
+ // Отписка от операций меша
+ mesh.changeCoordsOperation.removeSequel(calculateCoordsOperation);
+ mesh.changeRotationOrScaleOperation.removeSequel(calculateCoordsOperation);
+ // Удалить зависимые грани
+ for (var key:* in _faces) {
+ var face:Face = key;
+ mesh.removeFace(face);
+ }
+ // Удалить ссылку на меш
+ _mesh = null;
+ }
+
+ /**
+ * @private
+ * @param face
+ */
+ alternativa3d function addToFace(face:Face):void {
+ // Подписка грани на операции
+ changeCoordsOperation.addSequel(face.calculateUVOperation);
+ changeCoordsOperation.addSequel(face.calculateNormalOperation);
+ // Добавить грань в список
+ _faces.add(face);
+ }
+
+ /**
+ * @private
+ * @param face
+ */
+ alternativa3d function removeFromFace(face:Face):void {
+ // Отписка грани от операций
+ changeCoordsOperation.removeSequel(face.calculateUVOperation);
+ changeCoordsOperation.removeSequel(face.calculateNormalOperation);
+ // Удалить грань из списка
+ _faces.remove(face);
+ }
+
+ /**
+ * Строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ return "[Vertex ID:" + id + " " + _coords.x.toFixed(2) + ", " + _coords.y.toFixed(2) + ", " + _coords.z.toFixed(2) + "]";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/display/Skin.as b/Alternativa3D5/5.5/alternativa/engine3d/display/Skin.as
new file mode 100644
index 0000000..0bb7f21
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/display/Skin.as
@@ -0,0 +1,65 @@
+package alternativa.engine3d.display {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.core.SpritePrimitive;
+ import alternativa.engine3d.materials.Material;
+
+ import flash.display.Graphics;
+ import flash.display.Sprite;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ * Контейнер, используемый материалами для отрисовки примитивов. Каждый примитив BSP-дерева рисуется в своём контейнере.
+ */
+ public class Skin extends Sprite {
+
+ /**
+ * @private
+ * Графика скина (для быстрого доступа)
+ */
+ alternativa3d var gfx:Graphics = graphics;
+
+ /**
+ * @private
+ * Ссылка на следующий скин
+ */
+ alternativa3d var nextSkin:Skin;
+
+ /**
+ * @private
+ * Примитив
+ */
+ alternativa3d var primitive:PolyPrimitive;
+
+ /**
+ * @private
+ * Материал, связанный со скином.
+ */
+ alternativa3d var material:Material;
+
+ // Хранилище неиспользуемых скинов
+ static private var collector:Array = new Array();
+
+ /**
+ * @private
+ * Создание скина.
+ */
+ static alternativa3d function createSkin():Skin {
+ var skin:Skin;
+ if ((skin = collector.pop()) != null) {
+ return skin;
+ }
+ return new Skin();
+ }
+
+ /**
+ * @private
+ * Удаление скина, все ссылки должны быть почищены.
+ */
+ static alternativa3d function destroySkin(skin:Skin):void {
+ collector.push(skin);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/display/View.as b/Alternativa3D5/5.5/alternativa/engine3d/display/View.as
new file mode 100644
index 0000000..1566aac
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/display/View.as
@@ -0,0 +1,820 @@
+package alternativa.engine3d.display {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Sprite3D;
+ import alternativa.engine3d.core.SpritePrimitive;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.events.MouseEvent3D;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+
+ import flash.display.Sprite;
+ import flash.events.MouseEvent;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Область для вывода изображения с камеры.
+ */
+ public class View extends Sprite {
+
+ /**
+ * @private
+ * Область отрисовки спрайтов
+ */
+ alternativa3d var canvas:Sprite;
+
+ private var _camera:Camera3D;
+
+ /**
+ * @private
+ * Ширина области вывода
+ */
+ alternativa3d var _width:Number;
+ /**
+ * @private
+ * Высота области вывода
+ */
+ alternativa3d var _height:Number;
+
+ /**
+ * @private
+ * Флаг интерактивности вьюпорта. В интерактивном режиме вьюпорт принимает события мыши и транслирует их
+ * в подсистему трёхмерных событий, которая, в свою очередь, преобразует двумерный клик в трёхмерный и рассылает
+ * события всем подписчикам.
+ */
+ alternativa3d var _interactive:Boolean;
+
+ // Грань под курсором
+ private var faceUnderPoint:Face;
+ private var objectUnderPoint:Object3D;
+
+ private var lastMouseEvent:MouseEvent;
+ private var stagePoint:Point = new Point();
+
+ // Текущая грань
+ private var currentFace:Face;
+ // Текущая поверхность
+ private var currentSurface:Surface;
+ // Текущий объект
+ private var currentObject:Object3D;
+ // Грань, на которой было событие MOUSE_DOWN
+ private var pressedFace:Face;
+ // Поверхность, на которой было событие MOUSE_DOWN
+ private var pressedSurface:Surface;
+ // Объект, на которой было событие MOUSE_DOWN
+ private var pressedObject:Object3D;
+
+ // Направляющий вектор проецирующей прямой в камере
+ private var lineVector:Point3D = new Point3D();
+ // Вспомогательная переменная для хранения точки проецирующей прямой в ортографической камере
+ private var linePoint:Point3D = new Point3D();
+ // Точка на объекте под курсором в глобальной системе координат.
+ private var globalCursor3DCoords:Point3D = new Point3D();
+ // Координаты курсора в системе координат объекта
+ private var localCursor3DCoords:Point3D = new Point3D();
+ // UV-координаты в грани под курсором
+ private var uvPoint:Point = new Point();
+ // Вспомогательная матрица
+ private var inverseMatrix:Matrix3D = new Matrix3D();
+
+ /**
+ * Создание экземпляра области вывода.
+ *
+ * @param camera камера, изображение с которой должно выводиться
+ * @param width ширина области вывода
+ * @param height высота области вывода
+ */
+ public function View(camera:Camera3D = null, width:Number = 0, height:Number = 0) {
+ canvas = new Sprite();
+ canvas.mouseEnabled = false;
+ canvas.mouseChildren = false;
+ canvas.tabEnabled = false;
+ canvas.tabChildren = false;
+ addChild(canvas);
+
+ this.camera = camera;
+ this.width = width;
+ this.height = height;
+ }
+
+ /**
+ * Камера с которой ведётся отображение.
+ */
+ public function get camera():Camera3D {
+ return _camera;
+ }
+
+ /**
+ * @private
+ */
+ public function set camera(value:Camera3D):void {
+ if (_camera != value) {
+ // Если была камера
+ if (_camera != null) {
+ // Удалить камеру
+ _camera.removeFromView(this);
+ }
+ // Если новая камера
+ if (value != null) {
+ // Если камера была в другом вьюпорте
+ if (value._view != null) {
+ // Удалить её оттуда
+ value._view.camera = null;
+ }
+ // Добавить камеру
+ value.addToView(this);
+ } else {
+ // Зачистка скинов
+ if (canvas.numChildren > 0) {
+ var skin:Skin = Skin(canvas.getChildAt(0));
+ while (skin != null) {
+ // Сохраняем следующий
+ var next:Skin = skin.nextSkin;
+ // Удаляем из канваса
+ canvas.removeChild(skin);
+ // Очистка скина
+ if (skin.material != null) {
+ skin.material.clear(skin);
+ }
+ // Зачищаем ссылки
+ skin.nextSkin = null;
+ skin.primitive = null;
+ skin.material = null;
+ // Удаляем
+ Skin.destroySkin(skin);
+ // Следующий устанавливаем текущим
+ skin = next;
+ }
+ }
+ }
+ // Сохраняем камеру
+ _camera = value;
+ }
+ }
+
+ /**
+ * Ширина области вывода в пикселях.
+ */
+ override public function get width():Number {
+ return _width;
+ }
+
+ /**
+ * @private
+ */
+ override public function set width(value:Number):void {
+ if (_width != value) {
+ _width = value;
+ canvas.x = _width*0.5;
+ if (_camera != null) {
+ camera.addOperationToScene(camera.calculatePlanesOperation);
+ }
+ }
+ }
+
+ /**
+ * Высота области вывода в пикселях.
+ */
+ override public function get height():Number {
+ return _height;
+ }
+
+ /**
+ * @private
+ */
+ override public function set height(value:Number):void {
+ if (_height != value) {
+ _height = value;
+ canvas.y = _height*0.5;
+ if (_camera != null) {
+ camera.addOperationToScene(camera.calculatePlanesOperation);
+ }
+ }
+ }
+
+ /**
+ * Метод возвращает объект, находящийся под указанной точкой в области вывода.
+ *
+ * @param viewPoint координаты точки относительно области вывода
+ *
+ * @return ближайший к камере объект под заданной точкой области вывода. Объект может быть гранью (Face) или спрайтом (Sprite3D).
+ */
+ public function getObjectUnderPoint(viewPoint:Point):Object {
+ var stagePoint:Point = localToGlobal(viewPoint);
+ var objects:Array = stage.getObjectsUnderPoint(stagePoint);
+ var skin:Skin;
+ for (var i:int = objects.length - 1; i >= 0; i--) {
+ skin = objects[i] as Skin;
+ if (skin != null && skin.parent.parent == this) {
+ return skin.primitive.face != null ? skin.primitive.face : (skin.primitive as SpritePrimitive).sprite;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Метод возвращает объекты, находящиеся под указанной точкой в области вывода.
+ *
+ * @param viewPoint координаты точки относительно области вывода
+ *
+ * @return массив объектов, расположенных под заданной точкой области вывода. Первым элементом массива является самый дальний объект.
+ * Объектами могут быть грани (Face) или спрайты (Sprite3D).
+ */
+ override public function getObjectsUnderPoint(viewPoint:Point):Array {
+ var stagePoint:Point = localToGlobal(viewPoint);
+ var objects:Array = stage.getObjectsUnderPoint(stagePoint);
+ var res:Array = new Array();
+ var length:uint = objects.length;
+ for (var i:uint = 0; i < length; i++) {
+ var skin:Skin = objects[i] as Skin;
+ if (skin != null && skin.parent.parent == this) {
+ if (skin.primitive.face != null) {
+ res.push(skin.primitive.face);
+ } else {
+ res.push((skin.primitive as SpritePrimitive).sprite);
+ }
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Проецирование точки, заданной глобальными координатами, на плоскость области вывода.
+ *
+ * @param point глобальные координаты проецируемой точки
+ *
+ * @return объект Point3D, содержащий координаты проекции точки относительно левого верхнего угла области вывода и z-координату
+ * точки в системе камеры. В случае, если области вывода не назначена камера или камера не находится в сцене, возвращается null.
+ */
+ public function projectPoint(point:Point3D):Point3D {
+ if (_camera == null || _camera._scene == null) {
+ return null;
+ }
+
+ var cameraMatrix:Matrix3D = Object3D.matrix2;
+ var focalLength:Number = _camera.focalLength;;
+ var zoom:Number;
+
+ // Вычисление матрицы трансформации камеры
+ if (camera.getTransformation(cameraMatrix)) {
+ // Матрица была пересчитана заново
+ cameraMatrix.invert();
+ if (_camera._orthographic) {
+ // Учёт масштабирования в ортографической камере
+ zoom = _camera.zoom;
+ cameraMatrix.scale(zoom, zoom, zoom);
+ }
+ } else {
+ // Пересчёта не потребовалось, проверяем изменение зума
+ if (_camera._orthographic && _camera.calculateMatrixOperation.queued) {
+ cameraMatrix.invert();
+ zoom = _camera.zoom;
+ cameraMatrix.scale(zoom, zoom, zoom);
+ } else {
+ // Зум не менялся или перспективный режим, просто копируем обратную матрицу
+ cameraMatrix = _camera.cameraMatrix;
+ }
+ }
+ // Расчёт фокусного расстояния
+ if (!_camera._orthographic && _camera.calculatePlanesOperation.queued) {
+ focalLength = 0.5 * Math.sqrt(_height * _height + _width * _width) / Math.tan(0.5 * _camera._fov);
+ }
+ // Координаты точки в системе координат камеры
+ var x:Number = cameraMatrix.a * point.x + cameraMatrix.b * point.y + cameraMatrix.c * point.z + cameraMatrix.d;
+ var y:Number = cameraMatrix.e * point.x + cameraMatrix.f * point.y + cameraMatrix.g * point.z + cameraMatrix.h;
+ var z:Number = cameraMatrix.i * point.x + cameraMatrix.j * point.y + cameraMatrix.k * point.z + cameraMatrix.l;
+ // Проекция точки на вьюпорт
+ if (_camera._orthographic) {
+ return new Point3D(x + (_width >> 1), y + (_height >> 1), z);
+ } else {
+ return new Point3D(x * focalLength / z + (_width >> 1), y * focalLength / z + (_height >> 1), z);
+ }
+ }
+
+ /**
+ * Интерактивность области вывода. При включённой интерактивности возможно взаимодействие мыши с отрисованными объектами.
+ */
+ public function get interactive():Boolean {
+ return _interactive;
+ }
+
+ /**
+ * @private
+ */
+ public function set interactive(value:Boolean):void {
+ if (_interactive == value) {
+ return;
+ }
+ _interactive = value;
+
+ if (_interactive) {
+ addEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent);
+ addEventListener(MouseEvent.MOUSE_UP, onMouseEvent);
+ addEventListener(MouseEvent.MOUSE_MOVE, onMouseEvent);
+ addEventListener(MouseEvent.MOUSE_WHEEL, onMouseEvent);
+ addEventListener(MouseEvent.MOUSE_OUT, onMouseEvent);
+ } else {
+ removeEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent);
+ removeEventListener(MouseEvent.MOUSE_UP, onMouseEvent);
+ removeEventListener(MouseEvent.MOUSE_MOVE, onMouseEvent);
+ removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseEvent);
+ removeEventListener(MouseEvent.MOUSE_OUT, onMouseEvent);
+ pressedFace = currentFace = null;
+ pressedSurface = currentSurface = null;
+ pressedObject = currentObject = null;
+ }
+ }
+
+ /**
+ * Сброс нажатых объектов при отпускании кнопки мыши вне вьюпорта.
+ */
+ private function stageMouseUp(e:MouseEvent):void {
+ pressedFace = null;
+ pressedSurface = null;
+ pressedObject = null;
+ stage.removeEventListener(MouseEvent.MOUSE_UP, stageMouseUp);
+ }
+
+ /**
+ * Метод находит интерактивный объект (Object3D) и интерактивную грань, если возможно, находящиеся под указанной точкой в области вывода.
+ *
+ * @param pointX X-координата точки относительно области вывода
+ * @param pointY Y-координата точки относительно области вывода
+ */
+ private function getInteractiveObjectUnderPoint(pointX:Number, pointY:Number):void {
+ faceUnderPoint = null;
+ objectUnderPoint = null;
+ stagePoint.x = pointX;
+ stagePoint.y = pointY;
+ var objects:Array = stage.getObjectsUnderPoint(stagePoint);
+ var skin:Skin;
+ for (var i:int = objects.length - 1; i >= 0; i--) {
+ skin = objects[i] as Skin;
+ if (skin != null && skin.parent.parent == this) {
+ if (skin.primitive.face != null) {
+ // Скин, содержащий PolyPrimitive
+ if (skin.primitive.face._mesh.mouseEnabled) {
+ faceUnderPoint = skin.primitive.face;
+ objectUnderPoint = faceUnderPoint._mesh;
+ return;
+ }
+ } else {
+ // Скин, содержащий SpritePrimitive
+ var sprite:Sprite3D = (skin.primitive as SpritePrimitive).sprite;
+ if (sprite.mouseEnabled) {
+ objectUnderPoint = sprite;
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Вычисление свойств точки объекта, находящегося под указанной точкой фокусной плоскости камеры. Метод расчитывает глобальные и локальные
+ * 3D-координаты точки, а также её UV-координаты.
+ *
+ * @param canvasX
+ * @param canvasY
+ */
+ private function getInteractiveObjectPointProperties(canvasX:Number, canvasY:Number):void {
+ if (objectUnderPoint == null) {
+ return;
+ }
+ // Координаты локального направляющего вектора
+ var x:Number;
+ var y:Number;
+ var z:Number;
+ // Вычисление направляющего вектора и точки проецирующей прямой в глобальном пространстве
+ var m:Matrix3D = _camera._transformation;
+ if (_camera._orthographic) {
+ x = y = 0;
+ z = 1;
+ linePoint.x = canvasX / _camera.zoom;
+ linePoint.y = canvasY / _camera.zoom;
+ linePoint.z = 0;
+ linePoint.transform(m);
+ } else {
+ x = canvasX;
+ y = canvasY;
+ z = _camera.focalLength;
+ linePoint.copy(_camera.globalCoords);
+ }
+ // Направляющий вектор в глобальном пространстве
+ lineVector.x = x * m.a + y * m.b + z * m.c;
+ lineVector.y = x * m.e + y * m.f + z * m.g;
+ lineVector.z = x * m.i + y * m.j + z * m.k;
+ // Вычисление глобальных координат точки пересечения проецирующей прямой и плоскости объекта
+ var normal:Point3D;
+ var offset:Number;
+ if (faceUnderPoint != null) {
+ // Работаем с гранью
+ normal = faceUnderPoint.globalNormal;
+ offset = faceUnderPoint.globalOffset;
+ } else {
+ // Работаем со спрайтом
+ normal = lineVector.clone();
+ normal.multiply(-1);
+ globalCursor3DCoords.copy(objectUnderPoint._coords);
+ globalCursor3DCoords.transform(objectUnderPoint._transformation);
+ offset = Point3D.dot(globalCursor3DCoords, normal);
+ }
+ getLineAndPlaneIntersection(linePoint, lineVector, normal, offset, globalCursor3DCoords);
+ // Вычисление локальных координат точки пересечения
+ inverseMatrix.copy((faceUnderPoint != null ? faceUnderPoint._mesh : objectUnderPoint)._transformation);
+ inverseMatrix.invert();
+ localCursor3DCoords.copy(globalCursor3DCoords);
+ localCursor3DCoords.transform(inverseMatrix);
+ // Вычисление UV-координат
+ if (faceUnderPoint != null) {
+ var uv:Point = faceUnderPoint.getUV(localCursor3DCoords);
+ if (uv != null) {
+ uvPoint.x = uv.x;
+ uvPoint.y = uv.y;
+ } else {
+ uvPoint.x = NaN;
+ uvPoint.y = NaN;
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private function createSimpleMouseEvent3D(type:String, object:Object3D, surface:Surface, face:Face):MouseEvent3D {
+ var altKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.altKey;
+ var ctrlKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.ctrlKey;
+ var shiftKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.shiftKey;
+ var delta:int = lastMouseEvent == null ? 0 : lastMouseEvent.delta;
+ return new MouseEvent3D(type, this, object, surface, face,
+ NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN,
+ altKey, ctrlKey, shiftKey, delta);
+ }
+
+ /**
+ *
+ */
+ private function createFullMouseEvent3D(type:String, object:Object3D, surface:Surface, face:Face):MouseEvent3D {
+ var altKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.altKey;
+ var ctrlKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.ctrlKey;
+ var shiftKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.shiftKey;
+ var delta:int = lastMouseEvent == null ? 0 : lastMouseEvent.delta;
+ return new MouseEvent3D(type, this, object, surface, face,
+ globalCursor3DCoords.x, globalCursor3DCoords.y, globalCursor3DCoords.z, localCursor3DCoords.x, localCursor3DCoords.y, localCursor3DCoords.z, uvPoint.x, uvPoint.y,
+ altKey, ctrlKey, shiftKey, delta);
+ }
+
+ /**
+ * Обработка мышиного события на вьюпорте и передача его в систему трёхмерных событий.
+ */
+ private function onMouseEvent(e:MouseEvent):void {
+ // Сохранение события для использования в функциях создания MouseEvent3D
+ lastMouseEvent = e;
+ // Получение объекта под курсором и свойств точки на этом объекте
+ getInteractiveObjectUnderPoint(stage.mouseX, stage.mouseY);
+ getInteractiveObjectPointProperties(mouseX - (_width >> 1), mouseY - (_height >> 1));
+ // Обработка события
+ switch (e.type) {
+ case MouseEvent.MOUSE_MOVE:
+ processMouseMove();
+ break;
+ case MouseEvent.MOUSE_OUT:
+ stage.addEventListener(MouseEvent.MOUSE_UP, stageMouseUp);
+ checkMouseOverOut();
+ break;
+ case MouseEvent.MOUSE_DOWN:
+ processMouseDown();
+ break;
+ case MouseEvent.MOUSE_UP:
+ processMouseUp();
+ break;
+ case MouseEvent.MOUSE_WHEEL:
+ processMouseWheel();
+ break;
+ }
+ lastMouseEvent = null;
+ }
+
+ /**
+ * Обработка нажатия кнопки мыши во вьюпорте. Генерируются события: MouseEvent3D.MOUSE_DOWN.
+ */
+ private function processMouseDown():void {
+ if (objectUnderPoint == null) {
+ return;
+ }
+ if (faceUnderPoint != null) {
+ currentFace = faceUnderPoint;
+ currentSurface = faceUnderPoint._surface;
+ } else {
+ currentFace = null;
+ currentSurface = null;
+ }
+ currentObject = pressedObject = objectUnderPoint;
+
+ var evt:MouseEvent3D;
+ if (currentFace != null && currentFace.mouseEnabled) {
+ pressedFace = currentFace;
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_DOWN, currentObject, currentSurface, currentFace);
+ currentFace.dispatchEvent(evt);
+ }
+
+ if (currentSurface != null && currentSurface.mouseEnabled) {
+ pressedSurface = currentSurface;
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_DOWN, currentObject, currentSurface, currentFace);
+ currentSurface.dispatchEvent(evt);
+ }
+
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_DOWN, currentObject, currentSurface, currentFace);
+ currentObject.dispatchEvent(evt);
+ }
+
+ /**
+ * Обработка отжатия кнопки мыши во вьюпорте. Генерируются события: MouseEvent3D.MOUSE_UP, MouseEvent3D.CLICK.
+ */
+ private function processMouseUp():void {
+ if (objectUnderPoint == null) {
+ pressedFace = null;
+ pressedSurface = null;
+ pressedObject = null;
+ return;
+ }
+
+ if (faceUnderPoint != null) {
+ currentFace = faceUnderPoint;
+ currentSurface = faceUnderPoint._surface;
+ } else {
+ currentFace = null;
+ currentSurface = null;
+ }
+ currentObject = objectUnderPoint;
+
+ var evt:MouseEvent3D;
+ // MouseEvent3D.MOUSE_UP
+ if (currentFace != null && currentFace.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_UP, currentObject, currentSurface, currentFace);
+ currentFace.dispatchEvent(evt);
+ }
+
+ if (currentSurface != null && currentSurface.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_UP, currentObject, currentSurface, currentFace);
+ currentSurface.dispatchEvent(evt);
+ }
+
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_UP, currentObject, currentSurface, currentFace);
+ currentObject.dispatchEvent(evt);
+
+ // MouseEvent3D.CLICK
+ if (currentFace != null && currentFace == pressedFace && currentFace.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.CLICK, currentObject, currentSurface, currentFace);
+ currentFace.dispatchEvent(evt);
+ }
+
+ if (currentSurface != null && currentSurface == pressedSurface && currentSurface.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.CLICK, currentObject, currentSurface, currentFace);
+ currentSurface.dispatchEvent(evt);
+ }
+
+ if (currentObject == pressedObject) {
+ evt = createFullMouseEvent3D(MouseEvent3D.CLICK, currentObject, currentSurface, currentFace);
+ currentObject.dispatchEvent(evt);
+ }
+
+ pressedFace = null;
+ pressedSurface = null;
+ pressedObject = null;
+ }
+
+ /**
+ * Обработка вращения колеса мыши во вьюпорте. Генерируются события: MouseEvent3D.MOUSE_WHEEL.
+ */
+ private function processMouseWheel():void {
+ if (objectUnderPoint == null) {
+ return;
+ }
+
+ var evt:MouseEvent3D;
+ if (faceUnderPoint != null) {
+ currentFace = faceUnderPoint;
+ currentSurface = faceUnderPoint._surface;
+
+ if (currentFace.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_WHEEL, currentObject, currentSurface, currentFace);
+ currentFace.dispatchEvent(evt);
+ }
+
+ if (currentSurface.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_WHEEL, currentObject, currentSurface, currentFace);
+ currentSurface.dispatchEvent(evt);
+ }
+ } else {
+ currentFace = null;
+ currentSurface = null;
+ }
+ currentObject = objectUnderPoint;
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_WHEEL, currentObject, currentSurface, currentFace);
+ currentObject.dispatchEvent(evt);
+ }
+
+ /**
+ * @private
+ * Метод проверяет наличиче событий MOUSE_OVER, MOUSE_OUT для объектов сцены, их поверхностей и граней.
+ *
+ * @param checkObject флаг необходимости предварительно получить объект под курсором. Используется при вызове метода из функции отрисовки камеры.
+ */
+ alternativa3d function checkMouseOverOut(checkObject:Boolean = false):void {
+ if (checkObject) {
+ getInteractiveObjectUnderPoint(stage.mouseX, stage.mouseY);
+ getInteractiveObjectPointProperties(mouseX - (_width >> 1), mouseY - (_height >> 1));
+ }
+ var evt:MouseEvent3D;
+ if (objectUnderPoint == null) {
+ // Мышь ушла с объекта, генерируются события MOUSE_OUT
+ if (currentFace != null) {
+ // MOUSE_OUT для грани
+ if (currentFace.mouseEnabled) {
+ evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
+ currentFace.dispatchEvent(evt);
+ }
+ // MOUSE_OUT для поверхности
+ if (currentSurface.mouseEnabled) {
+ evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
+ currentSurface.dispatchEvent(evt);
+ }
+ }
+
+ if (currentObject != null) {
+ // MOUSE_OUT для объекта
+ evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
+ currentObject.dispatchEvent(evt);
+ }
+
+ currentFace = null;
+ currentSurface = null;
+ currentObject = null;
+ } else {
+ // Мышь на каком-то объекте
+ var surface:Surface;
+ var faceChanged:Boolean;
+ var surfaceChanged:Boolean;
+ var objectChanged:Boolean;
+
+ if (faceUnderPoint != null) {
+ surface = faceUnderPoint._surface;
+ }
+ //
+ if (faceUnderPoint != currentFace) {
+ // MOUSE_OUT для грани
+ if (currentFace != null && currentFace.mouseEnabled) {
+ evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
+ currentFace.dispatchEvent(evt);
+ }
+ faceChanged = true;
+ // MOUSE_OUT для поверхности
+ if (surface != currentSurface) {
+ if (currentSurface != null && currentSurface.mouseEnabled) {
+ evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
+ currentSurface.dispatchEvent(evt);
+ }
+ surfaceChanged = true;
+ }
+ }
+ // MOUSE_OUT для объекта
+ if (objectUnderPoint != currentObject) {
+ if (currentObject != null) {
+ evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
+ currentObject.dispatchEvent(evt);
+ }
+ objectChanged = true;
+ }
+
+ currentFace = faceUnderPoint;
+ currentSurface = surface;
+ currentObject = objectUnderPoint;
+ if (currentFace != null) {
+ // MOUSE_OVER для грани
+ if (faceChanged && currentFace.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_OVER, currentObject, currentSurface, currentFace);
+ currentFace.dispatchEvent(evt);
+ }
+ // MOUSE_OVER для поверхности
+ if (surfaceChanged && currentSurface.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_OVER, currentObject, currentSurface, currentFace);
+ currentSurface.dispatchEvent(evt);
+ }
+ }
+ // MOUSE_OVER для объекта
+ if (objectChanged) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_OVER, currentObject, currentSurface, currentFace);
+ currentObject.dispatchEvent(evt);
+ }
+ }
+ }
+
+ /**
+ * Обработчик движения мыши.
+ */
+ private function processMouseMove():void {
+ // Запуск проверки на наличие событий MOUSE_OVER и MOUSE_OUT
+ checkMouseOverOut();
+ // Генерация событий MOUSE_MOVE
+ var evt:MouseEvent3D;
+ if (currentFace != null) {
+ // Мышь на каком-то объекте
+ if (currentFace.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_MOVE, currentObject, currentSurface, currentFace);
+ currentFace.dispatchEvent(evt);
+ }
+
+ if (currentSurface.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_MOVE, currentObject, currentSurface, currentFace);
+ currentSurface.dispatchEvent(evt);
+ }
+ }
+
+ if (currentObject != null) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_MOVE, currentObject, currentSurface, currentFace);
+ currentObject.dispatchEvent(evt);
+ }
+ }
+
+ /**
+ * Вычисление точки пересечения прямой и плоскости.
+ *
+ * @param linePoint точка, лежащая на прямой
+ * @param lineVector направляющий вектор прямой
+ * @param planeNormal нормаль плоскости
+ * @param planeOffset смещение плоскости
+ * @param result переменная для сохранения координат точки пересечения. Если прямая и плоскость параллельны, то будут записаны значения NaN.
+ */
+ private function getLineAndPlaneIntersection(linePoint:Point3D, lineVector:Point3D, planeNormal:Point3D, planeOffset:Number, result:Point3D):void {
+ var dot:Number = planeNormal.x * lineVector.x + planeNormal.y * lineVector.y + planeNormal.z * lineVector.z;
+ if (dot == 0) {
+ // Прямая и плосоксть параллельны
+ result.x = NaN;
+ result.y = NaN;
+ result.z = NaN;
+ return;
+ }
+ // Выбор оси с максимальной по модулю координатой
+ var axis:int = 0;
+ var max:Number = lineVector.x;
+ if (max < lineVector.y && max > -lineVector.y) {
+ max = lineVector.y;
+ axis = 1;
+ }
+ if (max < lineVector.z && max > -lineVector.z) {
+ axis = 2;
+ }
+
+ var x:Number;
+ var y:Number;
+ var z:Number;
+ switch (axis) {
+ case 0:
+ x = (planeOffset * lineVector.x + planeNormal.y * (linePoint.x * lineVector.y - linePoint.y * lineVector.x) + planeNormal.z * (linePoint.x * lineVector.z - linePoint.z * lineVector.x)) / dot;
+ result.x = x;
+ result.y = (x - linePoint.x) * lineVector.y / lineVector.x + linePoint.y;
+ result.z = (x - linePoint.x) * lineVector.z / lineVector.x + linePoint.z;
+ break;
+ case 1:
+ y = (planeOffset * lineVector.y + planeNormal.x * (linePoint.y * lineVector.x - linePoint.x * lineVector.y) + planeNormal.z * (linePoint.y * lineVector.z - linePoint.z * lineVector.y)) / dot;
+ result.x = (y - linePoint.y) * lineVector.x / lineVector.y + linePoint.x;
+ result.y = y;
+ result.z = (y - linePoint.y) * lineVector.z / lineVector.y + linePoint.z;
+ break;
+ case 2:
+ z = (planeOffset * lineVector.z + planeNormal.x * (linePoint.z * lineVector.x - linePoint.x * lineVector.z) + planeNormal.y * (linePoint.z * lineVector.y - linePoint.y * lineVector.z)) / dot;
+ result.x = (z - linePoint.z) * lineVector.x / lineVector.z + linePoint.x;
+ result.y = (z - linePoint.z) * lineVector.y / lineVector.z + linePoint.y;
+ result.z = z;
+ break;
+ }
+ }
+
+ /**
+ * Получение координат точки в системе координат камеры, связанной с областью вывода. Если камера в режиме перспективной
+ * проекции, то метод вернёт координаты точки, лежащей на прямой, проходящей через начало координат камеры и указанную точку
+ * области вывода. Если камера в режиме ортографической проекции, то метод вернёт координаты точки, лежащей на прямой,
+ * перпендикулярной фокальной плоскости камеры и проходящей через указанную точку области вывода.
+ *
+ * @param viewPoint координаты точки в области вывода
+ * @param depth глубина точки в камере — координата Z в системе координат камеры
+ * @return координаты точки в системе координат камеры или null, если с областью вывода не связана камера
+ */
+ public function get3DCoords(viewPoint:Point, depth:Number):Point3D {
+ if (_camera == null) {
+ return null;
+ }
+ if (_camera._orthographic) {
+ return new Point3D((viewPoint.x - (_width >> 1)), (viewPoint.y - (_height >> 1)), depth);
+ } else {
+ var k:Number = depth / _camera.focalLength;
+ return new Point3D((viewPoint.x - (_width >> 1))* k, (viewPoint.y - (_height >> 1)) * k, depth);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/Engine3DError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/Engine3DError.as
new file mode 100644
index 0000000..7c3edd0
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/Engine3DError.as
@@ -0,0 +1,25 @@
+package alternativa.engine3d.errors {
+
+ /**
+ * Базовый класс для ошибок 3d-engine.
+ */
+ public class Engine3DError extends Error {
+
+ /**
+ * Источник ошибки - объект в котором произошла ошибка.
+ */
+ public var source:Object;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param message описание ошибки
+ * @param source источник ошибки
+ */
+ public function Engine3DError(message:String = "", source:Object = null) {
+ super(message);
+ this.source = source;
+ this.name = "Engine3DError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceExistsError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceExistsError.as
new file mode 100644
index 0000000..ecc7f59
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceExistsError.as
@@ -0,0 +1,35 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+ import alternativa.engine3d.core.Surface;
+
+ /**
+ * Ошибка, возникающая при попытке добавить в какой-либо объект грань, уже содержащуюся в данном объекте.
+ */
+ public class FaceExistsError extends ObjectExistsError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param face экземпляр или идентификатор грани, которая уже содержится в объекте
+ * @param source источник ошибки
+ */
+ public function FaceExistsError(face:Object = null, source:Object = null) {
+ var message:String;
+ if (source is Mesh) {
+ message = "Mesh ";
+ } else if (source is Surface) {
+ message = "Surface ";
+ }
+ if (face is Face) {
+ message += "%1. Face %2 already exists.";
+ } else {
+ message += "%1. Face with ID '%2' already exists.";
+ }
+ super(TextUtils.insertVars(message, source, face), face, source);
+ this.name = "FaceExistsError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as
new file mode 100644
index 0000000..3341f63
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as
@@ -0,0 +1,29 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая недостаточное количество вершин для создания грани.
+ * Для создания грани должно быть указано не менее трех вершин.
+ */
+ public class FaceNeedMoreVerticesError extends Engine3DError {
+
+ /**
+ * Количество переданных для создания грани вершин
+ */
+ public var count:uint;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param mesh объект, в котором произошла ошибка
+ * @param count количество вершин, переданное для создания грани
+ */
+ public function FaceNeedMoreVerticesError(mesh:Mesh = null, count:uint = 0) {
+ super(TextUtils.insertVars("Mesh %1. %2 vertices not enough for face creation.", mesh, count), mesh);
+ this.count = count;
+ this.name = "FaceNeedMoreVerticesError";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceNotFoundError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceNotFoundError.as
new file mode 100644
index 0000000..912d6a4
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/FaceNotFoundError.as
@@ -0,0 +1,34 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, возникающая, если грань не найдена в объекте.
+ */
+ public class FaceNotFoundError extends ObjectNotFoundError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param face экземпляр или идентификатор грани
+ * @param source объект, в котором произошла ошибка
+ */
+ public function FaceNotFoundError(face:Object = null, source:Object = null) {
+ var message:String;
+ if (source is Mesh) {
+ message = "Mesh ";
+ } else {
+ message = "Surface ";
+ }
+ if (face is Face) {
+ message += "%1. Face %2 not found.";
+ } else {
+ message += "%1. Face with ID '%2' not found.";
+ }
+ super(TextUtils.insertVars(message, source, face), face, source);
+ this.name = "FaceNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/InvalidIDError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/InvalidIDError.as
new file mode 100644
index 0000000..fbf4666
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/InvalidIDError.as
@@ -0,0 +1,34 @@
+package alternativa.engine3d.errors {
+ import alternativa.utils.TextUtils;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Surface;
+
+
+ /**
+ * Ошибка, обозначающая, что идентификатор зарезервирован и не может быть использован.
+ */
+ public class InvalidIDError extends Engine3DError {
+ /**
+ * Зарезервированный идентификатор
+ */
+ public var id:Object;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param id идентификатор
+ * @param source объект, в котором произошла ошибка
+ */
+ public function InvalidIDError(id:Object = null, source:Object = null) {
+ var message:String;
+ if (source is Mesh) {
+ message = "Mesh %2. ";
+ } else if (source is Surface) {
+ message = "Surface %2. ";
+ }
+ super(TextUtils.insertVars(message + "ID %1 is reserved and cannot be used", [id, source]), source);
+ this.id = id;
+ this.name = "InvalidIDError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/Object3DHierarchyError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/Object3DHierarchyError.as
new file mode 100644
index 0000000..8d61bd0
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/Object3DHierarchyError.as
@@ -0,0 +1,29 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, связанная с нарушением иерархии объектов сцены.
+ */
+ public class Object3DHierarchyError extends Engine3DError
+ {
+
+ /**
+ * Объект сцены, нарушающий иерархию
+ */
+ public var object:Object3D;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param object объект, нарушающий иерархию
+ * @param source источник ошибки
+ */
+ public function Object3DHierarchyError(object:Object3D = null, source:Object3D = null) {
+ super(TextUtils.insertVars("Object3D %1. Object %2 cannot be added", source, object), source);
+ this.object = object;
+ this.name = "Object3DHierarchyError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/Object3DNotFoundError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/Object3DNotFoundError.as
new file mode 100644
index 0000000..d1ddd1e
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/Object3DNotFoundError.as
@@ -0,0 +1,22 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, возникающая, когда объект сцены не был найден в списке связанных с необходимым объектом сцены.
+ */
+ public class Object3DNotFoundError extends ObjectNotFoundError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param object ненайденный объект сцены
+ * @param source объект сцены, в котором произошла ошибка
+ */
+ public function Object3DNotFoundError(object:Object3D = null, source:Object3D = null) {
+ super(TextUtils.insertVars("Object3D %1. Object %2 not in child list", source, object), object, source);
+ this.name = "Object3DNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/ObjectExistsError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/ObjectExistsError.as
new file mode 100644
index 0000000..7743a53
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/ObjectExistsError.as
@@ -0,0 +1,26 @@
+package alternativa.engine3d.errors {
+
+ /**
+ * Ошибка, обозначающая, что объект уже присутствует в контейнере.
+ */
+ public class ObjectExistsError extends Engine3DError {
+
+ /**
+ * Экземпляр или идентификатор объекта, который уже присутствует в контейнере
+ */
+ public var object:Object;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param message описание ошибки
+ * @param object объект, который уже присутствует в контейнере
+ * @param source объект, вызвавший ошибку
+ */
+ public function ObjectExistsError(message:String = "", object:Object = null, source:Object = null) {
+ super(message, source);
+ this.object = object;
+ this.name = "ObjectExistsError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/ObjectNotFoundError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/ObjectNotFoundError.as
new file mode 100644
index 0000000..87b3d14
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/ObjectNotFoundError.as
@@ -0,0 +1,26 @@
+package alternativa.engine3d.errors {
+
+ /**
+ * Необходимый объект не был найден в контейнере.
+ */
+ public class ObjectNotFoundError extends Engine3DError {
+
+ /**
+ * Объект, который отсутствует в контейнере.
+ */
+ public var object:Object;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param message описание ошибки
+ * @param object отсутствующий объект
+ * @param source объект, вызвавший ошибку
+ */
+ public function ObjectNotFoundError(message:String = "", object:Object = null, source:Object = null) {
+ super(message, source);
+ this.object = object;
+ this.name = "ObjectNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/SurfaceExistsError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/SurfaceExistsError.as
new file mode 100644
index 0000000..e13bee7
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/SurfaceExistsError.as
@@ -0,0 +1,22 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая, что поверхность уже присутствует в контейнере.
+ */
+ public class SurfaceExistsError extends ObjectExistsError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param surface поверхность, которая уже присутствует в контейнере
+ * @param mesh объект, вызвавший ошибку
+ */
+ public function SurfaceExistsError(surface:Object = null, mesh:Mesh = null) {
+ super(TextUtils.insertVars("Mesh %1. Surface with ID '%2' already exists.", mesh, surface), surface, mesh);
+ this.name = "SurfaceExistsError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/SurfaceNotFoundError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/SurfaceNotFoundError.as
new file mode 100644
index 0000000..05457bf
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/SurfaceNotFoundError.as
@@ -0,0 +1,31 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая, что поверхность не найдена в контейнере.
+ */
+ public class SurfaceNotFoundError extends ObjectNotFoundError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param surface поверхность, которая отсутствует в объекте
+ * @param mesh объект, который вызвал ошибку
+ */
+ public function SurfaceNotFoundError(surface:Object = null, mesh:Mesh = null) {
+ if (mesh == null) {
+
+ }
+ if (surface is Surface) {
+ message = "Mesh %1. Surface %2 not found.";
+ } else {
+ message = "Mesh %1. Surface with ID '%2' not found.";
+ }
+ super(TextUtils.insertVars(message, mesh, surface), surface, mesh);
+ this.name = "SurfaceNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/VertexExistsError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/VertexExistsError.as
new file mode 100644
index 0000000..9f0fff5
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/VertexExistsError.as
@@ -0,0 +1,22 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая, что вершина уже содержится в объекте.
+ */
+ public class VertexExistsError extends ObjectExistsError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param vertex вершина, которая уже есть в объекте
+ * @param mesh объект, вызвавший ошибку
+ */
+ public function VertexExistsError(vertex:Object = null, mesh:Mesh = null) {
+ super(TextUtils.insertVars("Mesh %1. Vertex with ID '%2' already exists.", mesh, vertex), vertex, mesh);
+ this.name = "VertexExistsError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/errors/VertexNotFoundError.as b/Alternativa3D5/5.5/alternativa/engine3d/errors/VertexNotFoundError.as
new file mode 100644
index 0000000..edf80aa
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/errors/VertexNotFoundError.as
@@ -0,0 +1,28 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая, что вершина не найдена в объекте.
+ */
+ public class VertexNotFoundError extends ObjectNotFoundError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param vertex вершина, которая не найдена в объекте
+ * @param mesh объект, вызвавший ошибку
+ */
+ public function VertexNotFoundError(vertex:Object = null, mesh:Mesh = null) {
+ if (vertex is Vertex) {
+ message = "Mesh %1. Vertex %2 not found.";
+ } else {
+ message = "Mesh %1. Vertex with ID '%2' not found.";
+ }
+ super(TextUtils.insertVars(message, mesh, vertex), vertex, mesh);
+ this.name = "VertexNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/events/MouseEvent3D.as b/Alternativa3D5/5.5/alternativa/engine3d/events/MouseEvent3D.as
new file mode 100644
index 0000000..283b83d
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/events/MouseEvent3D.as
@@ -0,0 +1,171 @@
+package alternativa.engine3d.events {
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.display.View;
+
+ import flash.events.Event;
+
+ /**
+ * Событие, возникающее при взаимодействии мыши с объектами сцены.
+ */
+ public class MouseEvent3D extends Event {
+ /**
+ * Значение свойства type для объекта события click.
+ * @eventType click
+ */
+ public static const CLICK:String = "click";
+ /**
+ * Значение свойства type для объекта события mouseDown.
+ * @eventType mouseDown
+ */
+ public static const MOUSE_DOWN:String = "mouseDown";
+ /**
+ * Значение свойства type для объекта события mouseUp.
+ * @eventType mouseUp
+ */
+ public static const MOUSE_UP:String = "mouseUp";
+ /**
+ * Значение свойства type для объекта события mouseOver.
+ * @eventType mouseOver
+ */
+ public static const MOUSE_OVER:String = "mouseOver";
+ /**
+ * Значение свойства type для объекта события mouseOut.
+ * @eventType mouseOut
+ */
+ public static const MOUSE_OUT:String = "mouseOut";
+ /**
+ * Значение свойства type для объекта события mouseMove.
+ * @eventType mouseMove
+ */
+ public static const MOUSE_MOVE:String = "mouseMove";
+ /**
+ * Значение свойства type для объекта события mouseWheel.
+ * @eventType mouseWheel
+ */
+ public static const MOUSE_WHEEL:String = "mouseWheel";
+
+ /**
+ * Объект сцены, с которым связано событие.
+ */
+ public var object:Object3D;
+ /**
+ * Поверхность объекта сцены, с которой связано событие.
+ */
+ public var surface:Surface;
+ /**
+ * Грань объекта сцены, с которой связано событие.
+ */
+ public var face:Face;
+ /**
+ * Область вывода, в которой произошло событие.
+ */
+ public var view:View;
+
+ /**
+ * X-координата мышиного курсора в сцене.
+ */
+ public var globalX:Number;
+ /**
+ * Y-координата мышиного курсора в сцене.
+ */
+ public var globalY:Number;
+ /**
+ * Z-координата мышиного курсора в сцене.
+ */
+ public var globalZ:Number;
+
+ /**
+ * X-координата мышиного курсора в системе координат объекта.
+ */
+ public var localX:Number;
+ /**
+ * Y-координата мышиного курсора в системе координат объекта.
+ */
+ public var localY:Number;
+ /**
+ * Z-координата мышиного курсора в системе координат объекта.
+ */
+ public var localZ:Number;
+
+ /**
+ * Текстурная координата U в точке нахождения мышиного курсора. При отсутствии текстурных координат у грани, поле содержит значение NaN.
+ */
+ public var u:Number;
+ /**
+ * Текстурная координата V в точке нахождения мышиного курсора. При отсутствии текстурных координат у грани, поле содержит значение NaN.
+ */
+ public var v:Number;
+ /**
+ * Индикатор нажатой (true) или отпущенной (false) клавиши Alt.
+ */
+ public var altKey:Boolean;
+ /**
+ * Индикатор нажатой (true) или отпущенной (false) клавиши Control.
+ */
+ public var ctrlKey:Boolean;
+ /**
+ * Индикатор нажатой (true) или отпущенной (false) клавиши Shift.
+ */
+ public var shiftKey:Boolean;
+ /**
+ * Количество линий прокрутки при вращении колеса мыши.
+ */
+ public var delta:int;
+
+ /**
+ * Создаёт новый экземпляр события.
+ *
+ * @param type тип события
+ * @param view область вывода, в которой произошло событие
+ * @param object объект сцены, с которым связано событие
+ * @param surface поверхность, с которой связано событие
+ * @param face грань, с которой связано событие
+ * @param globalX X-координата мышиного курсора в сцене
+ * @param globalY Y-координата мышиного курсора в сцене
+ * @param globalZ Z-координата мышиного курсора в сцене
+ * @param localX X-координата мышиного курсора в системе координат объекта
+ * @param localY Y-координата мышиного курсора в системе координат объекта
+ * @param localZ Z-координата мышиного курсора в системе координат объекта
+ * @param u текстурная координата U в точке нахождения мышиного курсора
+ * @param v текстурная координата V в точке нахождения мышиного курсора
+ */
+ public function MouseEvent3D(type:String, view:View, object:Object3D, surface:Surface, face:Face, globalX:Number = NaN, globalY:Number = NaN, globalZ:Number = NaN, localX:Number = NaN, localY:Number = NaN, localZ:Number = NaN, u:Number = NaN, v:Number = NaN, altKey:Boolean = false, ctrlKey:Boolean = false, shiftKey:Boolean = false, delta:int = 0) {
+ super(type);
+ this.object = object;
+ this.surface = surface;
+ this.face = face;
+ this.globalX = globalX;
+ this.globalY = globalY;
+ this.globalZ = globalZ;
+ this.localX = localX;
+ this.localY = localY;
+ this.localZ = localZ;
+ this.u = u;
+ this.v = v;
+ this.altKey = altKey;
+ this.ctrlKey = ctrlKey;
+ this.shiftKey = shiftKey;
+ this.delta = delta;
+ }
+
+ /**
+ * Получение строкового представления объекта.
+ *
+ * @return строковое представление объекта
+ */
+ override public function toString():String {
+ return formatToString("MouseEvent3D", "object", "surface", "face", "globalX", "globalY", "globalZ", "localX", "localY", "localZ", "u", "v", "delta", "altKey", "ctrlKey", "shiftKey");
+ }
+
+ /**
+ * Возвращает клон объекта.
+ *
+ * @return клон события
+ */
+ override public function clone():Event {
+ return new MouseEvent3D(type, view, object, surface, face, globalX, globalY, globalZ, localX, localY, localZ, u, v, altKey, ctrlKey, shiftKey, delta);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/loaders/Loader3DS.as b/Alternativa3D5/5.5/alternativa/engine3d/loaders/Loader3DS.as
new file mode 100644
index 0000000..197ed5d
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/loaders/Loader3DS.as
@@ -0,0 +1,1114 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.engine3d.materials.TextureMaterialPrecision;
+ import alternativa.engine3d.materials.WireMaterial;
+ import alternativa.types.Map;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Texture;
+ import alternativa.utils.ColorUtils;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.BitmapData;
+ import flash.display.BlendMode;
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IOErrorEvent;
+ import flash.events.SecurityErrorEvent;
+ import flash.geom.Matrix;
+ import flash.geom.Point;
+ import flash.net.URLLoader;
+ import flash.net.URLLoaderDataFormat;
+ import flash.net.URLRequest;
+ import flash.system.LoaderContext;
+ import flash.utils.ByteArray;
+ import flash.utils.Endian;
+
+ use namespace alternativa3d;
+
+ /**
+ * Событие посылается после завершения загрузки модели.
+ *
+ * @eventType flash.events.Event.COMPLETE
+ */
+ [Event (name="complete", type="flash.events.Event")]
+ /**
+ * Событие посылается при возникновении ошибки загрузки, связанной с вводом-выводом.
+ *
+ * @eventType flash.events.IOErrorEvent.IO_ERROR
+ */
+ [Event (name="ioError", type="flash.events.IOErrorEvent")]
+ /**
+ * Событие посылается при нарушении правил безопасности при загрузке файла модели.
+ *
+ * @eventType flash.events.SecurityErrorEvent.SECURITY_ERROR
+ */
+ [Event (name="securityError", type="flash.events.SecurityErrorEvent")]
+ /**
+ * Загрузчик моделей в формате 3DS.
+ *
+ *
+ *
+ *
+ * Порядок загрузки каждого объекта из 3DS-файла:
+ * Object3D;
+ * TextureMaterial,
+ * иначе в виде FillMaterial с указанным цветом. Если произошла ошибка при загрузке файла текстуры
+ * (например, файл отсутствует), то создаётся текстура-заглушка. Для текстурных материалов может быть указана
+ * карта прозрачности, на основе которой формируется канал прозрачности диффузной текстуры. Если размеры карты прозрачности отличаются от
+ * размеров диффузной текстуры, то карта прозрачности сжимается или растягивается до размеров диффузной текстуры.
+ *
+ *
+ *
+ * Перед загрузкой файла можно установить ряд свойств, влияющих на создаваемые текстурные материалы.
+ */
+ public class Loader3DS extends EventDispatcher {
+
+ /**
+ * Коэффициент пересчёта в дюймы.
+ */
+ public static const INCHES:Number = 1;
+
+ /**
+ * Коэффициент пересчёта в футы.
+ */
+ public static const FEET:Number = 0.0833333334;
+ /**
+ * Коэффициент пересчёта в мили.
+ */
+ public static const MILES:Number = 0.0000157828;
+ /**
+ * Коэффициент пересчёта в миллиметры.
+ */
+ public static const MILLIMETERS:Number = 25.4000000259;
+ /**
+ * Коэффициент пересчёта в сантиметры.
+ */
+ public static const CENTIMETERS:Number = 2.5400000025;
+ /**
+ * Коэффициент пересчёта в метры.
+ */
+ public static const METERS:Number = 0.0254;
+ /**
+ * Коэффициент пересчёта в километры.
+ */
+ public static const KILOMETERS:Number = 0.0000254;
+
+ private static const STATE_IDLE:int = -1;
+ private static const STATE_LOADING_MODEL:int = 0;
+ private static const STATE_LOADING_TEXTURES:int = 1;
+
+ private static var stubBitmapData:BitmapData;
+
+ private var _content:Object3D;
+ private var version:uint;
+ private var objectDatas:Map;
+ private var animationDatas:Array;
+ private var materialDatas:Array;
+ private var bitmaps:Array;
+
+ private var modelLoader:URLLoader;
+ private var textureLoader:TextureMapsLoader;
+ private var data:ByteArray;
+
+ private var counter:int;
+ private var textureMaterialNames:Array;
+ private var context:LoaderContext;
+ private var path:String;
+
+ private var loaderState:int = STATE_IDLE;
+
+ /**
+ * Повтор текстуры при заливке для создаваемых текстурных материалов.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var repeat:Boolean = true;
+ /**
+ * Сглаживание текстур при увеличении масштаба.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var smooth:Boolean = false;
+ /**
+ * Режим наложения цвета для создаваемых текстурных материалов.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var blendMode:String = BlendMode.NORMAL;
+ /**
+ * Точность перспективной коррекции для создаваемых текстурных материалов.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var precision:Number = TextureMaterialPrecision.MEDIUM;
+
+ /**
+ * Коэффициент пересчёта единиц измерения модели.
+ */
+ public var units:Number = INCHES;
+ /**
+ * Устанавливаемый уровень мобильности загруженных объектов.
+ */
+ public var mobility:int = 0;
+
+ /**
+ * Прекращение текущей загрузки.
+ */
+ public function close():void {
+ if (loaderState == STATE_LOADING_MODEL) {
+ modelLoader.close();
+ }
+ if (loaderState == STATE_LOADING_TEXTURES) {
+ textureLoader.close();
+ }
+ loaderState = STATE_IDLE;
+ }
+
+ /**
+ * Метод очищает внутренние ссылки на загруженные данные чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает
+ * во время загрузки.
+ */
+ public function unload():void {
+ if (loaderState == STATE_IDLE) {
+ clean();
+ }
+ }
+
+ /**
+ * Очистка сылок.
+ */
+ private function clean():void {
+ _content = null;
+ objectDatas = null;
+ animationDatas = null;
+ materialDatas = null;
+ bitmaps = null;
+ textureMaterialNames = null;
+ }
+
+ /**
+ * Загрузка сцены из 3DS-файла по указанному адресу. По окончании загрузки посылается сообщение Event.COMPLETE,
+ * после чего контейнер с загруженными объектами становится доступным через свойство content.
+ * IOErrorEvent.IO_ERROR и
+ * SecurityErrorEvent.SECURITY_ERROR соответственно.
+ * alternativa.engine3d.loaders.MaterialInfo.
+ * @see alternativa.engine3d.loaders.MaterialInfo
+ */
+ public function get library():Map {
+ return _library;
+ }
+
+ /**
+ * Метод выполняет загрузку файла материалов, разбор его содержимого, загрузку текстур при необходимости и
+ * формирование библиотеки материалов. После окончания работы метода посылается сообщение
+ * Event.COMPLETE и становится доступна библиотека материалов через свойство library.
+ * IOErrorEvent.IO_ERROR и
+ * SecurityErrorEvent.SECURITY_ERROR соответственно.
+ * Object3D. Отдельные модели в файле должны определяться командой
+ * o object_name.
+ *
+ *
+ */
+ public class LoaderOBJ extends EventDispatcher {
+
+ private static const COMMENT_CHAR:String = "#";
+
+ private static const CMD_OBJECT_NAME:String = "o";
+ private static const CMD_GROUP_NAME:String = "g";
+ private static const CMD_VERTEX:String = "v";
+ private static const CMD_TEXTURE_VERTEX:String = "vt";
+ private static const CMD_FACE:String = "f";
+ private static const CMD_MATERIAL_LIB:String = "mtllib";
+ private static const CMD_USE_MATERIAL:String = "usemtl";
+
+ private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/;
+ private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/;
+ private static const REGEXP_SPLIT_LINE:RegExp = /\s+/;
+
+ private static const STATE_IDLE:int = -1;
+ private static const STATE_LOADING_MODEL:int = 0;
+ private static const STATE_LOADING_LIBRARY:int = 1;
+
+ // Текущее состояние загрузчика
+ private var loaderState:int = STATE_IDLE;
+
+ private var basePath:String;
+ private var objLoader:URLLoader;
+ private var mtlLoader:LoaderMTL;
+ private var loaderContext:LoaderContext;
+ private var loadMaterials:Boolean;
+ // Объект, содержащий все определённые в obj файле объекты
+ private var _content:Object3D;
+ // Текущий конструируемый объект
+ private var currentObject:Mesh;
+ // Стартовый индекс вершины в глобальном массиве вершин для текущего объекта
+ private var vIndexStart:int = 0;
+ // Стартовый индекс текстурной вершины в глобальном массиве текстурных вершин для текущего объекта
+ private var vtIndexStart:int = 0;
+ // Глобальный массив вершин, определённых во входном файле
+ private var globalVertices:Array;
+ // Глобальный массив текстурных вершин, определённых во входном файле
+ private var globalTextureVertices:Array;
+ // Имя текущего активного материала. Если значение равно null, то активного материала нет.
+ private var currentMaterialName:String;
+ // Массив граней текущего объекта, которым назначен текущий материал
+ private var materialFaces:Array;
+ // Массив имён файлов, содержащих определения материалов
+ private var materialFileNames:Array;
+ private var currentMaterialFileIndex:int;
+ private var materialLibrary:Map;
+ private var uv:Point = new Point();
+
+ /**
+ * Сглаживание текстур при увеличении масштаба.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var smooth:Boolean = false;
+ /**
+ * Режим наложения цвета для создаваемых текстурных материалов.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var blendMode:String = BlendMode.NORMAL;
+ /**
+ * Точность перспективной коррекции для создаваемых текстурных материалов.
+ *
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public var precision:Number = TextureMaterialPrecision.MEDIUM;
+
+ /**
+ * Устанавливаемый уровень мобильности загруженных объектов.
+ */
+ public var mobility:int = 0;
+
+ /**
+ * При установленном значении
+ *
+ * Команда
+ * Описание
+ * Действие
+ *
+ * o object_name
+ * Объявление нового объекта с именем object_name
+ * Если для текущего объекта были определены грани, то команда создаёт новый текущий объект с указанным именем,
+ * иначе у текущего объекта просто меняется имя на указанное.
+ *
+ *
+ * v x y z
+ * Объявление вершины с координатами x y z
+ * Вершина помещается в общий список вершин сцены для дальнейшего использования
+ *
+ *
+ * vt u [v]
+ * Объявление текстурной вершины с координатами u v
+ * Вершина помещается в общий список текстурных вершин сцены для дальнейшего использования
+ *
+ *
+ * f v0[/vt0] v1[/vt1] ... vN[/vtN]
+ * Объявление грани, состоящей из указанных вершин и опционально имеющую заданные текстурные координаты для вершин.
+ * Грань добавляется к текущему активному объекту. Если есть активный материал, то грань также добавляется в поверхность
+ * текущего объекта, соответствующую текущему материалу.
+ *
+ *
+ * usemtl material_name
+ * Установка текущего материала с именем material_name
+ * С момента установки текущего материала все грани, создаваемые в текущем объекте будут помещаться в поверхность,
+ * соотвествующую этому материалу и имеющую идентификатор, совпадающий с его именем.
+ *
+ *
+ * mtllib file1 file2 ...
+ * Объявление файлов, содержащих определения материалов
+ * Выполняется загрузка файлов и формирование библиотеки материалов. Для текстурных материалов может быть указана
+ * карта прозрачности, на основе которой формируется канал прозрачности диффузной текстуры. Если размеры карты прозрачности отличаются от
+ * размеров диффузной текстуры, то карта прозрачности сжимается или растягивается до размеров диффузной текстуры.
+ * true выполняется преобразование координат геометрических вершин посредством
+ * поворота на 90 градусов относительно оси X. Смысл флага в преобразовании системы координат, в которой вверх направлена
+ * ось Y, в систему координат, использующуюся в Alternativa3D (вверх направлена ось Z).
+ */
+ public var rotateModel:Boolean;
+
+ /**
+ * Создаёт новый экземпляр загрузчика.
+ */
+ public function LoaderOBJ() {
+ }
+
+ /**
+ * Контейнер, содержащий все загруженные из OBJ-файла модели.
+ */
+ public function get content():Object3D {
+ return _content;
+ }
+
+ /**
+ * Прекращение текущей загрузки.
+ */
+ public function close():void {
+ if (loaderState == STATE_LOADING_MODEL) {
+ objLoader.close();
+ }
+ if (loaderState == STATE_LOADING_LIBRARY) {
+ mtlLoader.close();
+ }
+ loaderState = STATE_IDLE;
+ }
+
+ /**
+ * Метод очищает внутренние ссылки на загруженные данные чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает
+ * во время загрузки.
+ */
+ public function unload():void {
+ if (loaderState == STATE_IDLE) {
+ clean();
+ _content = null;
+ }
+ }
+
+ /**
+ * Загрузка сцены из OBJ-файла по указанному адресу. По окончании загрузки посылается сообщение Event.COMPLETE,
+ * после чего контейнер с загруженными объектами становится доступным через свойство content. При вызове метода текущая
+ * активная загрузка прекращается.
+ * IOErrorEvent.IO_ERROR и
+ * SecurityErrorEvent.SECURITY_ERROR соответственно.
+ * true, будут обработаны все файлы
+ * материалов, указанные в исходном OBJ-файле.
+ * @param context LoaderContext для загрузки файлов текстур
+ *
+ * @see #content
+ */
+ public function load(url:String, loadMaterials:Boolean = true, context:LoaderContext = null):void {
+ if (objLoader == null) {
+ objLoader = new URLLoader();
+ objLoader.addEventListener(Event.COMPLETE, onObjLoadComplete);
+ objLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError);
+ objLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError);
+ } else {
+ close();
+ }
+ loaderState = STATE_LOADING_MODEL;
+ _content = null;
+ this.loadMaterials = loadMaterials;
+ this.loaderContext = context;
+ basePath = url.substring(0, url.lastIndexOf("/") + 1);
+ objLoader.load(new URLRequest(url));
+ }
+
+ /**
+ * Обработка окончания загрузки obj файла.
+ *
+ * @param e
+ */
+ private function onObjLoadComplete(e:Event):void {
+ parse(objLoader.data);
+ }
+
+ /**
+ * Обработка ошибки при загрузке.
+ *
+ * @param e
+ */
+ private function onObjLoadError(e:ErrorEvent):void {
+ close();
+ dispatchEvent(e);
+ }
+
+ /**
+ *
+ */
+ private function complete():void {
+ loaderState = STATE_IDLE;
+ clean();
+ dispatchEvent(new Event(Event.COMPLETE));
+ }
+
+ /**
+ *
+ */
+ private function clean():void {
+ globalVertices = null;
+ globalTextureVertices = null;
+ materialFileNames = null;
+ currentObject = null;
+ loaderContext = null;
+ }
+
+ /**
+ * Метод выполняет разбор данных, полученных из obj файла.
+ *
+ * @param s содержимое obj файла
+ * @param materialLibrary библиотека материалов
+ * @return объект, содержащий все трёхмерные объекты, определённые в obj файле
+ */
+ private function parse(data:String):void {
+ _content = new Object3D();
+ currentObject = new Mesh();
+ currentObject.mobility = mobility;
+ _content.addChild(currentObject);
+
+ globalVertices = new Array();
+ globalTextureVertices = new Array();
+ materialFileNames = new Array();
+
+ var lines:Array = data.split(REGEXP_SPLIT_FILE);
+ lines.forEach(parseLine);
+ moveFacesToSurface();
+ // Вся геометрия загружена и сформирована. Выполняется загрузка информации о материалах.
+ if (loadMaterials && materialFileNames.length > 0) {
+ loadMaterialsLibrary();
+ } else {
+ complete();
+ }
+ }
+
+ /**
+ *
+ */
+ private function parseLine(line:String, index:int, lines:Array):void {
+ line = line.replace(REGEXP_TRIM,"$1");
+ if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) {
+ return;
+ }
+ var parts:Array = line.split(REGEXP_SPLIT_LINE);
+ switch (parts[0]) {
+ // Объявление нового объекта
+ case CMD_OBJECT_NAME:
+ defineObject(parts[1]);
+ break;
+ // Объявление вершины
+ case CMD_VERTEX:
+ globalVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3])));
+ break;
+ // Объявление текстурной вершины
+ case CMD_TEXTURE_VERTEX:
+ globalTextureVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3])));
+ break;
+ // Объявление грани
+ case CMD_FACE:
+ createFace(parts);
+ break;
+ case CMD_MATERIAL_LIB:
+ storeMaterialFileNames(parts);
+ break;
+ case CMD_USE_MATERIAL:
+ setNewMaterial(parts);
+ break;
+ }
+ }
+
+ /**
+ * Объявление нового объекта.
+ *
+ * @param objectName имя объекта
+ */
+ private function defineObject(objectName:String):void {
+ if (currentObject._faces.length == 0) {
+ // Если у текущего объекта нет граней, то он остаётся текущим, но меняется имя
+ currentObject.name = objectName;
+ } else {
+ // Если у текущего объекта есть грани, то обявление нового имени создаёт новый объект
+ moveFacesToSurface();
+ currentObject = new Mesh(objectName);
+ currentObject.mobility = mobility;
+ _content.addChild(currentObject);
+ }
+ vIndexStart = globalVertices.length;
+ vtIndexStart = globalTextureVertices.length;
+ }
+
+ /**
+ * Создание грани в текущем объекте.
+ *
+ * @param parts массив, содержащий индексы вершин грани, начиная с элемента с индексом 1
+ */
+ private function createFace(parts:Array):void {
+ // Стартовый индекс вершины в объекте для добавляемой грани
+ var startVertexIndex:int = currentObject._vertices.length;
+ // Создание вершин в объекте
+ var faceVertexCount:int = parts.length - 1;
+ var vtIndices:Array = new Array(3);
+ // Массив идентификаторов вершин грани
+ var faceVertices:Array = new Array(faceVertexCount);
+ for (var i:int = 0; i < faceVertexCount; i++) {
+ var indices:Array = parts[i + 1].split("/");
+ // Создание вершины
+ var vIdx:int = int(indices[0]);
+ // Если индекс положительный, то его значение уменьшается на единицу, т.к. в obj формате индексация начинается с 1.
+ // Если индекс отрицательный, то выполняется смещение на его значение назад от стартового глобального индекса вершин для текущего объекта.
+ var actualIndex:int = vIdx > 0 ? vIdx - 1 : vIndexStart + vIdx;
+
+ var vertex:Vertex = currentObject._vertices[actualIndex];
+ // Если вершины нет в объекте, она добавляется
+ if (vertex == null) {
+ var p:Point3D = globalVertices[actualIndex];
+ if (rotateModel) {
+ // В формате obj направление "вверх" совпадает с осью Y, поэтому выполняется поворот координат на 90 градусов по оси X
+ vertex = currentObject.createVertex(p.x, -p.z, p.y, actualIndex);
+ } else {
+ vertex = currentObject.createVertex(p.x, p.y, p.z, actualIndex);
+ }
+ }
+ faceVertices[i] = vertex;
+
+ // Запись индекса текстурной вершины
+ if (i < 3) {
+ vtIndices[i] = int(indices[1]);
+ }
+ }
+ // Создание грани
+ var face:Face = currentObject.createFace(faceVertices, currentObject._faces.length);
+ // Установка uv координат
+ if (vtIndices[0] != 0) {
+ p = globalTextureVertices[vtIndices[0] - 1];
+ face.aUV = new Point(p.x, p.y);
+ p = globalTextureVertices[vtIndices[1] - 1];
+ face.bUV = new Point(p.x, p.y);
+ p = globalTextureVertices[vtIndices[2] - 1];
+ face.cUV = new Point(p.x, p.y);
+ }
+ // Если есть активный материал, то грань заносится в массив для последующего формирования поверхности в объекте
+ if (currentMaterialName != null) {
+ materialFaces.push(face);
+ }
+ }
+
+ /**
+ * Загрузка библиотек материалов.
+ *
+ * @param parts массив, содержащий имена файлов материалов, начиная с элемента с индексом 1
+ */
+ private function storeMaterialFileNames(parts:Array):void {
+ for (var i:int = 1; i < parts.length; i++) {
+ materialFileNames.push(parts[i]);
+ }
+ }
+
+ /**
+ * Установка нового текущего материала.
+ *
+ * @param parts массив, во втором элементе которого содержится имя материала
+ */
+ private function setNewMaterial(parts:Array):void {
+ // Все сохранённые грани добавляются в соответствующую поверхность текущего объекта
+ moveFacesToSurface();
+ // Установка нового текущего материала
+ currentMaterialName = parts[1];
+ }
+
+ /**
+ * Добавление всех граней с текущим материалом в поверхность с идентификатором, совпадающим с именем материала.
+ */
+ private function moveFacesToSurface():void {
+ if (currentMaterialName != null && materialFaces.length > 0) {
+ if (currentObject.hasSurface(currentMaterialName)) {
+ // При наличии поверхности с таким идентификатором, грани добавляются в неё
+ var surface:Surface = currentObject.getSurfaceById(currentMaterialName);
+ for each (var face:* in materialFaces) {
+ surface.addFace(face);
+ }
+ } else {
+ // При отсутствии поверхности с таким идентификатором, создатся новая поверхность
+ currentObject.createSurface(materialFaces, currentMaterialName);
+ }
+ }
+ materialFaces = [];
+ }
+
+ /**
+ * Загрузка материалов.
+ */
+ private function loadMaterialsLibrary():void {
+ loaderState = STATE_LOADING_LIBRARY;
+ if (mtlLoader == null) {
+ mtlLoader = new LoaderMTL();
+ mtlLoader.addEventListener(Event.COMPLETE, onMaterialFileLoadComplete);
+ mtlLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError);
+ mtlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError);
+ }
+ materialLibrary = new Map();
+
+ currentMaterialFileIndex = -1;
+ loadNextMaterialFile();
+ }
+
+ /**
+ * Обработка успешной загрузки библиотеки материалов.
+ */
+ private function onMaterialFileLoadComplete(e:Event):void {
+ materialLibrary.concat(mtlLoader.library);
+ // Загрузка следующего файла материалов
+ loadNextMaterialFile();
+ }
+
+ /**
+ * Загрузка и разбор очередного файла материалов.
+ */
+ private function loadNextMaterialFile():void {
+ currentMaterialFileIndex++;
+ if (currentMaterialFileIndex == materialFileNames.length) {
+ // Все материалы загружены
+ setMaterials();
+ complete();
+ } else {
+ mtlLoader.load(basePath + materialFileNames[currentMaterialFileIndex], loaderContext);
+ }
+ }
+
+ /**
+ * Установка материалов.
+ */
+ private function setMaterials():void {
+ if (materialLibrary != null) {
+ for (var objectKey:* in _content.children) {
+ var object:Mesh = objectKey;
+ for (var surfaceKey:* in object._surfaces) {
+ var surface:Surface = object._surfaces[surfaceKey];
+ // Поверхности имеют идентификаторы, соответствующие именам материалов
+ var materialInfo:MaterialInfo = materialLibrary[surfaceKey];
+ if (materialInfo != null) {
+ if (materialInfo.bitmapData == null) {
+ surface.material = new FillMaterial(materialInfo.color, materialInfo.alpha, blendMode);
+ } else {
+ surface.material = new TextureMaterial(new Texture(materialInfo.bitmapData, materialInfo.textureFileName), materialInfo.alpha, materialInfo.repeat, (materialInfo.bitmapData != LoaderMTL.stubBitmapData) ? smooth : false, blendMode, -1, 0, precision);
+ transformUVs(surface, materialInfo.mapOffset, materialInfo.mapSize);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Метод выполняет преобразование UV-координат текстурированных граней. В связи с тем, что в формате MTL предусмотрено
+ * масштабирование и смещение текстурной карты в UV-пространстве, а в движке такой фунциональности нет, необходимо
+ * эмулировать преобразования текстуры преобразованием UV-координат граней. Преобразования выполняются исходя из предположения,
+ * что текстурное пространство сначала масштабируется относительно центра, а затем сдвигается на указанную величину
+ * смещения.
+ *
+ * @param surface поверхность, грани которой обрабатываюся
+ * @param mapOffset смещение текстурной карты. Значение mapOffset.x указывает смещение по U, значение mapOffset.y
+ * указывает смещение по V.
+ * @param mapSize коэффициенты масштабирования текстурной карты. Значение mapSize.x указывает коэффициент масштабирования
+ * по оси U, значение mapSize.y указывает коэффициент масштабирования по оси V.
+ */
+ private function transformUVs(surface:Surface, mapOffset:Point, mapSize:Point):void {
+ for (var key:* in surface._faces) {
+ var face:Face = key;
+ if (face._aUV) {
+ uv.x = face._aUV.x;
+ uv.y = face._aUV.y;
+ uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
+ uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
+ face.aUV = uv;
+
+ uv.x = face._bUV.x;
+ uv.y = face._bUV.y;
+ uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
+ uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
+ face.bUV = uv;
+
+ uv.x = face._cUV.x;
+ uv.y = face._cUV.y;
+ uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
+ uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
+ face.cUV = uv;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/loaders/MTLTextureMapInfo.as b/Alternativa3D5/5.5/alternativa/engine3d/loaders/MTLTextureMapInfo.as
new file mode 100644
index 0000000..4bf9e57
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/loaders/MTLTextureMapInfo.as
@@ -0,0 +1,120 @@
+package alternativa.engine3d.loaders {
+ /**
+ * @private
+ * Класс содержит информацию о текстуре в формате MTL material format (Lightwave, OBJ) и функционал для разбора
+ * описания текстуры.
+ * Описание формата можно посмотреть по адресу: http://local.wasp.uwa.edu.au/~pbourke/dataformats/mtl/
+ */
+ internal class MTLTextureMapInfo {
+
+ // Ассоциация параметров команды объявления текстуры и методов для их чтения
+ private static const optionReaders:Object = {
+ "-clamp": clampReader,
+ "-o": offsetReader,
+ "-s": sizeReader,
+
+ "-blendu": stubReader,
+ "-blendv": stubReader,
+ "-bm": stubReader,
+ "-boost": stubReader,
+ "-cc": stubReader,
+ "-imfchan": stubReader,
+ "-mm": stubReader,
+ "-t": stubReader,
+ "-texres": stubReader
+ };
+
+ // Смещение в текстурном пространстве
+ public var offsetU:Number = 0;
+ public var offsetV:Number = 0;
+ public var offsetW:Number = 0;
+
+ // Масштабирование текстурного пространства
+ public var sizeU:Number = 1;
+ public var sizeV:Number = 1;
+ public var sizeW:Number = 1;
+
+ // Флаг повторения текстуры
+ public var repeat:Boolean = true;
+ // Имя файла текстуры
+ public var fileName:String;
+
+ /**
+ * Метод выполняет разбор данных о текстуре.
+ *
+ * @param parts Данные о текстуре. Массив должен содержать части разделённой по пробелам входной строки MTL-файла.
+ * @return объект, содержащий данные о текстуре
+ */
+ public static function parse(parts:Array):MTLTextureMapInfo {
+ var info:MTLTextureMapInfo = new MTLTextureMapInfo();
+ // Начальное значение индекса единица, т.к. первый элемент массива содержит тип текстуры
+ var index:int = 1;
+ var reader:Function;
+ // Чтение параметров текстуры
+ while ((reader = optionReaders[parts[index]]) != null) {
+ index = reader(index, parts, info);
+ }
+ // Если не было ошибок, последний элемент массива должен содержать имя файла текстуры
+ info.fileName = parts[index];
+ return info;
+ }
+
+ /**
+ * Читатель-заглушка. Пропускает все неподдерживаемые параметры.
+ */
+ private static function stubReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
+ index++;
+ var maxIndex:int = parts.length - 1;
+ while ((MTLTextureMapInfo.optionReaders[parts[index]] == null) && (index < maxIndex)) {
+ index++;
+ }
+ return index;
+ }
+
+ /**
+ * Метод чтения параметров масштабирования текстурного пространства.
+ */
+ private static function sizeReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
+ info.sizeU = Number(parts[index + 1]);
+ index += 2;
+ var value:Number = Number(parts[index]);
+ if (!isNaN(value)) {
+ info.sizeV = value;
+ index++;
+ value = Number(parts[index]);
+ if (!isNaN(value)) {
+ info.sizeW = value;
+ index++;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Метод чтения параметров смещения текстуры.
+ */
+ private static function offsetReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
+ info.offsetU = Number(parts[index + 1]);
+ index += 2;
+ var value:Number = Number(parts[index]);
+ if (!isNaN(value)) {
+ info.offsetV = value;
+ index++;
+ value = Number(parts[index]);
+ if (!isNaN(value)) {
+ info.offsetW = value;
+ index++;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Метод чтения параметра повторения текстуры.
+ */
+ private static function clampReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
+ info.repeat = parts[index + 1] == "off";
+ return index + 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/loaders/MaterialInfo.as b/Alternativa3D5/5.5/alternativa/engine3d/loaders/MaterialInfo.as
new file mode 100644
index 0000000..0a9a9ee
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/loaders/MaterialInfo.as
@@ -0,0 +1,20 @@
+package alternativa.engine3d.loaders {
+ import flash.display.BitmapData;
+ import flash.geom.Point;
+
+ /**
+ * @private
+ * Класс содержит обобщённую информацию о материале.
+ */
+ internal class MaterialInfo {
+ public var color:uint;
+ public var alpha:Number;
+
+ public var textureFileName:String;
+ public var bitmapData:BitmapData;
+ public var repeat:Boolean;
+
+ public var mapOffset:Point;
+ public var mapSize:Point;
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/loaders/TextureMapsLoader.as b/Alternativa3D5/5.5/alternativa/engine3d/loaders/TextureMapsLoader.as
new file mode 100644
index 0000000..6fc4865
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/loaders/TextureMapsLoader.as
@@ -0,0 +1,157 @@
+package alternativa.engine3d.loaders {
+ import flash.display.Bitmap;
+ import flash.display.BitmapData;
+ import flash.display.BitmapDataChannel;
+ import flash.display.BlendMode;
+ import flash.display.Loader;
+ import flash.display.Stage;
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IOErrorEvent;
+ import flash.geom.Matrix;
+ import flash.geom.Point;
+ import flash.net.URLRequest;
+ import flash.system.LoaderContext;
+
+ /**
+ * Тип события, возникающего при завершении загрузки.
+ */
+ [Event (name="complete", type="flash.events.Event")]
+ /**
+ * Тип события, возникающего при ошибке загрузки, связанной с вводом-выводом.
+ */
+ [Event (name="ioError", type="flash.events.IOErrorEvent")]
+ /**
+ * @private
+ * Класс для загрузки битмапов диффузной текустуры и карты прозрачности.
+ */
+ public class TextureMapsLoader extends EventDispatcher {
+
+ private static const STATE_IDLE:int = -1;
+ private static const STATE_LOADING_DIFFUSE_MAP:int = 0;
+ private static const STATE_LOADING_ALPHA_MAP:int = 1;
+
+ private var bitmapLoader:Loader;
+ private var _bitmapData:BitmapData;
+ private var alphaTextureUrl:String;
+ private var loaderContext:LoaderContext;
+
+ private var loaderState:int = STATE_IDLE;
+
+ /**
+ *
+ */
+ public function TextureMapsLoader() {
+ }
+
+ /**
+ * Загрузка текстурных карт. При успешной загрузке посылается сообщение Event.COMPLETE.
+ *
+ * @param diffuseTextureUrl URL файла диффузной карты
+ * @param alphaTextureUrl URL файла карты прозрачности
+ * @param loaderContext
+ */
+ public function load(diffuseTextureUrl:String, alphaTextureUrl:String = null, loaderContext:LoaderContext = null):void {
+ this.alphaTextureUrl = alphaTextureUrl;
+ this.loaderContext = loaderContext;
+ if (bitmapLoader == null) {
+ bitmapLoader = new Loader();
+ bitmapLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
+ bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoadError);
+ } else {
+ close();
+ }
+ startLoading(STATE_LOADING_DIFFUSE_MAP, diffuseTextureUrl);
+ }
+
+ /**
+ * Запуск загрузки файла текстуры.
+ *
+ * @param state фаза загрузки
+ * @param url URL загружаемого файла
+ */
+ private function startLoading(state:int, url:String):void {
+ loaderState = state;
+ bitmapLoader.load(new URLRequest(url), loaderContext);
+ }
+
+ /**
+ *
+ */
+ private function onLoadComplete(e:Event):void {
+ switch (loaderState) {
+ case STATE_LOADING_DIFFUSE_MAP:
+ // Загрузилась диффузная текстура. При необходимости загружается карта прозрачности.
+ _bitmapData = Bitmap(bitmapLoader.content).bitmapData;
+ if (alphaTextureUrl != null) {
+ startLoading(STATE_LOADING_ALPHA_MAP, alphaTextureUrl);
+ } else {
+ complete();
+ }
+ break;
+ case STATE_LOADING_ALPHA_MAP:
+ // Загрузилась карта прозрачности. Выполняется копирование прозрачности в альфа-канал диффузной текстуры.
+ var tmpBmp:BitmapData = _bitmapData;
+ _bitmapData = new BitmapData(_bitmapData.width, _bitmapData.height);
+ _bitmapData.copyPixels(tmpBmp, tmpBmp.rect, new Point());
+
+ var alpha:BitmapData = Bitmap(bitmapLoader.content).bitmapData;
+ if (_bitmapData.width != alpha.width || _bitmapData.height != alpha.height) {
+ tmpBmp.draw(alpha, new Matrix(_bitmapData.width / alpha.width, 0, 0, _bitmapData.height / alpha.height), null, BlendMode.NORMAL, null, true);
+ alpha.dispose();
+ alpha = tmpBmp;
+ }
+ _bitmapData.copyChannel(alpha, alpha.rect, new Point(), BitmapDataChannel.RED, BitmapDataChannel.ALPHA);
+ alpha.dispose();
+ complete();
+ break;
+ }
+ }
+
+ /**
+ *
+ */
+ private function onLoadError(e:Event):void {
+ loaderState = STATE_IDLE;
+ dispatchEvent(e);
+ }
+
+ /**
+ *
+ */
+ private function complete():void {
+ loaderState = STATE_IDLE;
+ loaderContext = null;
+ bitmapLoader.unload();
+ dispatchEvent(new Event(Event.COMPLETE));
+ }
+
+ /**
+ * Загруженная текстура.
+ */
+ public function get bitmapData():BitmapData {
+ return _bitmapData;
+ }
+
+ /**
+ * Прекращение загрузки.
+ */
+ public function close():void {
+ if (loaderState != STATE_IDLE) {
+ loaderState = STATE_IDLE;
+ bitmapLoader.close();
+ }
+ }
+
+ /**
+ * Очищает внутренние ссылки на загруженные объекты, чтобы сборщик мусора смог их удалить.
+ */
+ public function unload():void {
+ if (bitmapLoader != null && loaderState == STATE_IDLE) {
+ bitmapLoader.unload();
+ loaderContext = null;
+ _bitmapData = null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/DevMaterial.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/DevMaterial.as
new file mode 100644
index 0000000..2d44686
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/DevMaterial.as
@@ -0,0 +1,420 @@
+package alternativa.engine3d.materials {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.BSPNode;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.display.Skin;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.utils.ColorUtils;
+
+ import flash.display.BlendMode;
+ import flash.display.Graphics;
+
+ use namespace alternativa3d;
+
+ /**
+ * Материал, раскрашивающий грани оттенками заданного цвета в зависимости от значения свойства parameterType.
+ */
+ public class DevMaterial extends SurfaceMaterial {
+ /**
+ * Значение свойства parameterType для отображения глубины полигона в BSP-дереве. Глубина кодируется оттенком базового цвета материала. Оттенок
+ * получается покомпонентным умножением базового цвета на отношение значения уровня полигона в дереве к максимальному значению, задаваемому
+ * свойством maxParameterValue. Уровни нумеруются начиная от корня дерева, таким образом, чем глубже в дереве расположен
+ * полигон, тем он будет светлее.
+ */
+ public static const BSP_DEPTH:int = 0;
+ /**
+ * Значение свойства parameterType для отображения мобильности полигона. Мобильность кодируется оттенком базового цвета материала. Оттенок
+ * получается покомпонентным умножением базового цвета на коэффициент, характеризующий положение мобильности полигона на отрезке,
+ * задаваемом свойствами minMobility и maxMobility. Более мобильные полигоны имеют более светлый оттенок.
+ */
+ public static const MOBILITY:int = 1;
+ /**
+ * Значение свойства parameterType для отображения количества фрагментов грани, которой принадлежит отрисовываемый полигон. Количество фрагментов кодируется
+ * оттенком базового цвета материала. Оттенок получается покомпонентным умножением базового цвета на отношние количества фрагментов текущей грани
+ * к максимальному значению, задаваемому свойством maxParameterValue. Чем больше грань фрагментирована, тем она светлее.
+ */
+ public static const FRAGMENTATION:int = 2;
+ /**
+ * Значение свойства parameterType для отображения граней с отсутствующими UV-координатами. Такие грани отображаются красной заливкой.
+ */
+ public static const NO_UV_MAPPING:int = 3;
+ /**
+ * Значение свойства parameterType для отображения вырожденных полигонов. Вырожденные полигоны отображаются красной заливкой с красной обводкой толщиной пять
+ * пикселей. Для лучшей видимости таких полигонов можно сделать материал полупрозрачным.
+ */
+ public static const DEGENERATE_POLY:int = 4;
+
+ private static const point1:Point3D = new Point3D();
+ private static const point2:Point3D = new Point3D();
+
+ private var _parameterType:int = BSP_DEPTH;
+
+ private var _showNormals:Boolean;
+ private var _normalsColor:uint = 0x00FFFF;
+ private var _minMobility:int = 0;
+ private var _maxMobility:int = 255;
+ private var _maxParameterValue:Number = 20;
+ private var currentColor:int;
+ private var currentWireThickness:Number;
+ private var currentWireColor:uint;
+
+ /**
+ * @private
+ * Цвет
+ */
+ alternativa3d var _color:uint;
+
+ /**
+ * @private
+ * Толщина линий обводки
+ */
+ alternativa3d var _wireThickness:Number;
+
+ /**
+ * @private
+ * Цвет линий обводки
+ */
+ alternativa3d var _wireColor:uint;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param parameterType тип отображаемого параметра
+ * @param color цвет заливки
+ * @param maxParameterValue максимальное значение отображаемого параметра
+ * @param showNormals включение режима отображения нормалей
+ * @param normalsColor цвет нормалей
+ * @param minMobility начало интервала мобильности
+ * @param maxMobility окончание интервала мобильности
+ * @param alpha прозрачность
+ * @param blendMode режим наложения цвета
+ * @param wireThickness толщина линии обводки
+ * @param wireColor цвет линии обводки
+ */
+ public function DevMaterial(parameterType:uint = 0, color:uint = 0xFFFFFF, maxParameterValue:Number = 20, showNormals:Boolean = false, normalsColor:uint = 0x00FFFF, minMobility:int = 0, maxMobility:int = 255, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) {
+ super(alpha, blendMode);
+ _parameterType = parameterType;
+ _color = color;
+ _maxParameterValue = maxParameterValue;
+ _showNormals = showNormals;
+ _normalsColor = normalsColor;
+ _minMobility = minMobility;
+ _maxMobility = maxMobility;
+ _wireThickness = wireThickness;
+ _wireColor = wireColor;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+
+ var i:uint;
+ var point:DrawPoint;
+ var gfx:Graphics = skin.gfx;
+ var perspective:Number;
+
+ setDrawingParameters(skin);
+
+ if (currentColor > -1) {
+ gfx.beginFill(currentColor);
+ }
+ if (currentWireThickness >= 0) {
+ gfx.lineStyle(currentWireThickness, currentWireColor);
+ }
+ point = points[0];
+
+ if (camera._orthographic) {
+ gfx.moveTo(point.x, point.y);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ if (currentWireThickness >= 0) {
+ point = points[0];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ perspective = camera.focalLength/point.z;
+ gfx.moveTo(point.x*perspective, point.y*perspective);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ if (currentWireThickness >= 0) {
+ point = points[0];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ }
+
+ // Отрисовка нормали
+ if (_showNormals) {
+ point1.reset();
+ for (i = 0; i < length; i++) {
+ point = points[i];
+ point1.x += point.x;
+ point1.y += point.y;
+ point1.z += point.z;
+ }
+ point1.multiply(1 / length);
+
+ var multiplyer:Number = 10;
+ var normal:Point3D = skin.primitive.face.globalNormal;
+ var m:Matrix3D = camera.cameraMatrix;
+ point2.x = (normal.x * m.a + normal.y * m.b + normal.z * m.c) * multiplyer + point1.x;
+ point2.y = (normal.x * m.e + normal.y * m.f + normal.z * m.g) * multiplyer + point1.y;
+ point2.z = (normal.x * m.i + normal.y * m.j + normal.z * m.k) * multiplyer + point1.z;
+
+ if (camera._orthographic) {
+ gfx.moveTo(point1.x, point1.y);
+ gfx.lineStyle(0, _normalsColor);
+ gfx.lineTo(point2.x, point2.y);
+ } else {
+ perspective = camera.focalLength / point1.z;
+ gfx.moveTo(point1.x * perspective, point1.y * perspective);
+ gfx.lineStyle(0, _normalsColor);
+ perspective = camera.focalLength / point2.z;
+ gfx.lineTo(point2.x * perspective, point2.y * perspective);
+ }
+ gfx.lineStyle();
+ }
+ }
+
+ /**
+ * Установка параметров отрисовки.
+ */
+ private function setDrawingParameters(skin:Skin):void {
+ currentColor = _color;
+ currentWireColor = _wireColor;
+ currentWireThickness = _wireThickness;
+
+ var param:int = 0;
+ switch (_parameterType) {
+ case BSP_DEPTH:
+ // Глубина вложенности в BSP-tree
+ var node:BSPNode = skin.primitive.node;
+ while (node != null) {
+ node = node.parent;
+ param++;
+ }
+ currentColor = ColorUtils.multiply(_color, param / _maxParameterValue);
+ break;
+ case MOBILITY:
+ // Мобильность
+ var value:Number = (skin.primitive.mobility - _minMobility) / (_maxMobility - _minMobility);
+ if (value < 0) {
+ value = 0;
+ }
+ currentColor = ColorUtils.multiply(_color, value);
+ break;
+ case FRAGMENTATION:
+ // Степень фрагментирования
+ currentColor = ColorUtils.multiply(_color, calculateFragments(skin.primitive.face.primitive) / _maxParameterValue);
+ break;
+ case NO_UV_MAPPING:
+ // Отсутствие UV
+ if (skin.primitive.face.uvMatrix == null) {
+ currentColor = 0xFF0000;
+ }
+ break;
+ case DEGENERATE_POLY:
+ // Вырожденные полигоны
+ var face:Face = skin.primitive.face;
+ point1.copy(face._vertices[1]._coords);
+ point2.copy(face._vertices[2]._coords);
+ point2.subtract(point1);
+ point1.subtract(face._vertices[0]._coords);
+ var crossX:Number = point1.y * point2.z - point1.z * point2.y;
+ var crossY:Number = point1.z * point2.x - point1.x * point2.z;
+ var crossZ:Number = point1.x * point2.y - point1.y * point2.x;
+ if (crossX * crossX + crossY * crossY + crossZ * crossZ < 0.001) {
+ currentColor = 0xFF0000;
+ currentWireColor = 0xFF0000;
+ currentWireThickness = 5;
+ }
+ break;
+ }
+ }
+
+ /**
+ * Расчёт количества фрагментов грани примитива.
+ */
+ private function calculateFragments(primitive:PolyPrimitive):int {
+ if (primitive.frontFragment == null) {
+ return 1;
+ }
+ return calculateFragments(primitive.frontFragment) + calculateFragments(primitive.backFragment);
+ }
+
+ /**
+ * Цвет заливки.
+ */
+ public function get color():uint {
+ return _color;
+ }
+
+ /**
+ * @private
+ */
+ public function set color(value:uint):void {
+ if (_color != value) {
+ _color = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Толщина линии обводки. Если значение отрицательное, то отрисовка линии не выполняется.
+ */
+ public function get wireThickness():Number {
+ return _wireThickness;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireThickness(value:Number):void {
+ if (_wireThickness != value) {
+ _wireThickness = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Цвет линии обводки.
+ */
+ public function get wireColor():uint {
+ return _wireColor;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireColor(value:uint):void {
+ if (_wireColor != value) {
+ _wireColor = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ var res:DevMaterial = new DevMaterial(_parameterType, _color, _maxParameterValue, _showNormals, _normalsColor, _minMobility, _maxMobility, _alpha, _blendMode, _wireThickness, _wireColor);
+ return res;
+ }
+
+ /**
+ * Тип отображаемого параметра.
+ */
+ public function set parameterType(value:int):void {
+ if (_parameterType != value) {
+ _parameterType = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function get parameterType():int {
+ return _parameterType;
+ }
+
+ /**
+ * Включение режима отображения нормалей.
+ */
+ public function set showNormals(value:Boolean):void {
+ if (_showNormals != value) {
+ _showNormals = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function get showNormals():Boolean {
+ return _showNormals;
+ }
+
+ /**
+ * Цвет нормалей.
+ *
+ * @default 0x00FFFF
+ */
+ public function set normalsColor(value:uint):void {
+ if (_normalsColor != value) {
+ _normalsColor = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function get normalsColor():uint {
+ return _normalsColor;
+ }
+
+ /**
+ * Начало интервала мобильности.
+ */
+ public function set minMobility(value:int):void {
+ if (_minMobility != value) {
+ _minMobility = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function get minMobility():int {
+ return _minMobility;
+ }
+
+ /**
+ * Окончание интервала мобильности.
+ */
+ public function set maxMobility(value:int):void {
+ if (_maxMobility != value) {
+ _maxMobility = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function get maxMobility():int {
+ return _maxMobility;
+ }
+
+ /**
+ * Максимальное значение отображаемого параметра.
+ */
+ public function set maxParameterValue(value:Number):void {
+ if (_maxParameterValue != value) {
+ _maxParameterValue = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function get maxParameterValue():Number {
+ return _maxParameterValue;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/DrawPoint.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/DrawPoint.as
new file mode 100644
index 0000000..2447283
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/DrawPoint.as
@@ -0,0 +1,47 @@
+package alternativa.engine3d.materials {
+ /**
+ * @private
+ * Точка, подготовленная к отрисовке.
+ */
+ public final class DrawPoint {
+
+ /**
+ * Координата X в системе координат камеры.
+ */
+ public var x:Number;
+ /**
+ * Координата Y в системе координат камеры.
+ */
+ public var y:Number;
+ /**
+ * Координата Z в системе координат камеры.
+ */
+ public var z:Number;
+ /**
+ * Координата U в текстурном пространстве.
+ */
+ public var u:Number;
+ /**
+ * Координата V в текстурном пространстве.
+ */
+ public var v:Number;
+
+ /**
+ * Создаёт новый экземпляр класса.
+ *
+ * @param x координата X в системе координат камеры
+ * @param y координата Y в системе координат камеры
+ * @param z координата Z в системе координат камеры
+ * @param u координата U в текстурном пространстве
+ * @param v координата V в текстурном пространстве
+ */
+ public function DrawPoint(x:Number, y:Number, z:Number, u:Number = 0, v:Number = 0) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.u = u;
+ this.v = v;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/FillMaterial.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/FillMaterial.as
new file mode 100644
index 0000000..9eb21c1
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/FillMaterial.as
@@ -0,0 +1,160 @@
+package alternativa.engine3d.materials {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.display.Skin;
+
+ import flash.display.BlendMode;
+ import flash.display.Graphics;
+
+ use namespace alternativa3d;
+
+ /**
+ * Материал, заполняющий полигон сплошной одноцветной заливкой. Помимо заливки цветом, материал может рисовать границу
+ * полигона линией заданной толщины и цвета.
+ */
+ public class FillMaterial extends SurfaceMaterial {
+
+ /**
+ * @private
+ * Цвет
+ */
+ alternativa3d var _color:uint;
+
+ /**
+ * @private
+ * Толщина линий обводки
+ */
+ alternativa3d var _wireThickness:Number;
+
+ /**
+ * @private
+ * Цвет линий обводки
+ */
+ alternativa3d var _wireColor:uint;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param color цвет заливки
+ * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ * @param blendMode режим наложения цвета
+ * @param wireThickness толщина линии обводки
+ * @param wireColor цвет линии обводки
+ */
+ public function FillMaterial(color:uint, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) {
+ super(alpha, blendMode);
+ _color = color;
+ _wireThickness = wireThickness;
+ _wireColor = wireColor;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+
+ var i:uint;
+ var point:DrawPoint;
+ var gfx:Graphics = skin.gfx;
+
+ if (camera._orthographic) {
+ gfx.beginFill(_color);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ gfx.moveTo(point.x, point.y);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ gfx.beginFill(_color);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ var perspective:Number = camera.focalLength/point.z;
+ gfx.moveTo(point.x*perspective, point.y*perspective);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ }
+ }
+
+ /**
+ * Цвет заливки.
+ */
+ public function get color():uint {
+ return _color;
+ }
+
+ /**
+ * @private
+ */
+ public function set color(value:uint):void {
+ if (_color != value) {
+ _color = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Толщина линии обводки. Если значение отрицательное, то обводка не рисуется.
+ */
+ public function get wireThickness():Number {
+ return _wireThickness;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireThickness(value:Number):void {
+ if (_wireThickness != value) {
+ _wireThickness = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Цвет линии обводки.
+ */
+ public function get wireColor():uint {
+ return _wireColor;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireColor(value:uint):void {
+ if (_wireColor != value) {
+ _wireColor = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ var res:FillMaterial = new FillMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor);
+ return res;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/Material.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/Material.as
new file mode 100644
index 0000000..e7dcedc
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/Material.as
@@ -0,0 +1,94 @@
+package alternativa.engine3d.materials {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.display.Skin;
+
+ use namespace alternativa3d;
+
+ /**
+ * Базовый класс для материалов.
+ */
+ public class Material {
+
+ /**
+ * @private
+ * Альфа.
+ */
+ alternativa3d var _alpha:Number;
+ /**
+ * @private
+ * Режим наложения цвета.
+ */
+ alternativa3d var _blendMode:String;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ * @param blendMode режим наложения цвета
+ */
+ public function Material(alpha:Number, blendMode:String) {
+ _alpha = alpha;
+ _blendMode = blendMode;
+ }
+
+ /**
+ * Коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ */
+ public function get alpha():Number {
+ return _alpha;
+ }
+
+ /**
+ * @private
+ */
+ public function set alpha(value:Number):void {
+ if (_alpha != value) {
+ _alpha = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Режим наложения цвета.
+ */
+ public function get blendMode():String {
+ return _blendMode;
+ }
+
+ /**
+ * @private
+ */
+ public function set blendMode(value:String):void {
+ if (_blendMode != value) {
+ _blendMode = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Отметить материал на перерисовку.
+ */
+ protected function markToChange():void {}
+
+ /**
+ * @private
+ * Метод очищает переданный скин (нарисованную графику, дочерние объекты и т.д.).
+ *
+ * @param skin скин для очистки
+ */
+ alternativa3d function clear(skin:Skin):void {
+ skin.gfx.clear();
+ }
+
+ /**
+ * Создание клона материала.
+ *
+ * @return клон материала
+ */
+ public function clone():Material {
+ return new Material(_alpha, _blendMode);
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/SpriteMaterial.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/SpriteMaterial.as
new file mode 100644
index 0000000..387a0d8
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/SpriteMaterial.as
@@ -0,0 +1,119 @@
+package alternativa.engine3d.materials {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Scene3D;
+ import alternativa.engine3d.core.Sprite3D;
+ import alternativa.engine3d.display.Skin;
+ import alternativa.types.*;
+
+ import flash.display.BlendMode;
+
+ use namespace alternativa3d;
+ use namespace alternativatypes;
+
+ /**
+ * Базовый класс для материалов спрайтов.
+ */
+ public class SpriteMaterial extends Material {
+
+ /**
+ * @private
+ * Спрайт.
+ */
+ alternativa3d var _sprite:Sprite3D;
+
+ /**
+ * @inheritDoc
+ */
+ public function SpriteMaterial(alpha:Number = 1, blendMode:String = BlendMode.NORMAL) {
+ super(alpha, blendMode);
+ }
+
+ /**
+ * Спрайт, которому назначен материал.
+ */
+ public function get sprite():Sprite3D {
+ return _sprite;
+ }
+
+ /**
+ * @private
+ * Добавление на сцену.
+ *
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {}
+
+ /**
+ * @private
+ * Удаление из сцены.
+ *
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {}
+
+ /**
+ * @private
+ * Назначение спрайту.
+ *
+ * @param sprite спрайт
+ */
+ alternativa3d function addToSprite(sprite:Sprite3D):void {
+ _sprite = sprite;
+ }
+
+ /**
+ * @private
+ * Удаление из спрайта.
+ *
+ * @param sprite спрайт
+ */
+ alternativa3d function removeFromSprite(sprite:Sprite3D):void {
+ _sprite = null;
+ }
+
+ /**
+ * @private
+ * Метод определяет, может ли материал нарисовать спрайт. Метод используется в системе отрисовки сцены и должен использоваться
+ * наследниками для указания видимости связанной со спрайтом. Реализация по умолчанию возвращает
+ * true.
+ *
+ * @param camera камера через которую происходит отрисовка.
+ *
+ * @return true, если материал может отрисовать указанный примитив, иначе false
+ */
+ alternativa3d function canDraw(camera:Camera3D):Boolean {
+ return true;
+ }
+
+ /**
+ * @private
+ * Метод выполняет отрисовку в заданный скин.
+ *
+ * @param camera камера, вызвавшая метод
+ * @param skin скин, в котором нужно отрисовать
+ */
+ alternativa3d function draw(camera:Camera3D, skin:Skin):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function markToChange():void {
+ if (_sprite != null) {
+ _sprite.addMaterialChangedOperationToScene();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ return new SpriteMaterial(_alpha, _blendMode);
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/SpriteTextureMaterial.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/SpriteTextureMaterial.as
new file mode 100644
index 0000000..abb711d
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/SpriteTextureMaterial.as
@@ -0,0 +1,243 @@
+package alternativa.engine3d.materials {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.display.Skin;
+ import alternativa.types.*;
+
+ import flash.display.BlendMode;
+ import flash.geom.Matrix;
+ import flash.geom.Rectangle;
+
+ use namespace alternativa3d;
+ use namespace alternativatypes;
+
+ /**
+ * Материал, отображающий заданную текстуру в точке нахождения спрайта. Текстура отображается так, как если бы она находилась в плоскости,
+ * параллельной плоскости области вывода камеры, а верхний край текстуры был параллелен верхнему краю области вывода. При отрисовке изображения
+ * начало координат текстуры в области вывода совпадает с проекцией точки спрайта. По умолчанию начало координат текстуры перенесено в её центр.
+ */
+ public class SpriteTextureMaterial extends SpriteMaterial {
+
+ /**
+ * @private
+ * Вспомогательный прямоугольник, используется при отрисовке для хранения параметров отрисовки.
+ */
+ private static var drawRect:Rectangle = new Rectangle();
+
+ /**
+ * @private
+ * Матрица, используемая для отрисовки текстуры спрайта
+ */
+ private static var textureMatrix:Matrix = new Matrix();
+
+ /**
+ * @private
+ * Текстура
+ */
+ alternativa3d var _texture:Texture;
+
+ /**
+ * @private
+ * Сглаженность текстуры
+ */
+ alternativa3d var _smooth:Boolean;
+
+ /**
+ * @private
+ * Смещение начала координат по оси X
+ */
+ alternativa3d var _originX:Number;
+
+ /**
+ * @private
+ * Смещение начала координат по оси Y
+ */
+ alternativa3d var _originY:Number;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param texture текстура для отображения
+ * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ * @param smooth сглаживание текстуры
+ * @param blendMode режим наложения цвета
+ * @param originX относительное смещение начала координат в текстуре по оси X
+ * @param originY относительное смещение начала координат в текстуре по оси Y
+ */
+ public function SpriteTextureMaterial(texture:Texture, alpha:Number = 1, smooth:Boolean = false, blendMode:String = BlendMode.NORMAL, originX:Number = 0.5, originY:Number = 0.5) {
+ super(alpha, blendMode);
+ _texture = texture;
+ _smooth = smooth;
+ _originX = originX;
+ _originY = originY;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function canDraw(camera:Camera3D):Boolean {
+ if (_texture == null) {
+ return false;
+ }
+
+ // Переводим координаты в систему камеры
+ var cameraMatrix:Matrix3D = camera.cameraMatrix;
+
+ var x:Number = _sprite.globalCoords.x;
+ var y:Number = _sprite.globalCoords.y;
+ var z:Number = _sprite.globalCoords.z;
+ var pointX:Number = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d;
+ var pointY:Number = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h;
+ var pointZ:Number = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l;
+
+ var w:Number;
+ var h:Number;
+
+ if (camera._orthographic) {
+ if ((camera._nearClipping && pointZ < camera._nearClippingDistance) || (camera._farClipping && pointZ > camera._farClippingDistance)) {
+ return false;
+ }
+ w = _texture._width*camera._zoom*_sprite._materialScale;
+ h = _texture._height*camera._zoom*_sprite._materialScale;
+ x = pointX - w*_originX;
+ y = pointY - h*_originY;
+ } else {
+ if ((pointZ <= 0) || (camera._nearClipping && pointZ < camera._nearClippingDistance) || (camera._farClipping && pointZ > camera._farClippingDistance)) {
+ return false;
+ }
+ var perspective:Number = camera.focalLength/pointZ;
+ w = _texture._width*perspective*_sprite._materialScale;
+ h = _texture._height*perspective*_sprite._materialScale;
+ x = pointX*perspective - w*_originX;
+ y = pointY*perspective - h*_originY;
+ }
+ var halfW:Number = camera._view._width*0.5;
+ var halfH:Number = camera._view._height*0.5;
+
+ if (camera._viewClipping && (x >= halfW || y >= halfH || x + w <= -halfW || y + h <= -halfH)) {
+ return false;
+ }
+
+ textureMatrix.a = w/_texture._width;
+ textureMatrix.d = h/_texture._height;
+ textureMatrix.tx = x;
+ textureMatrix.ty = y;
+
+ if (camera._viewClipping) {
+ if (x < -halfW) {
+ w -= -halfW - x;
+ x = -halfW;
+ }
+ if (x + w > halfW) {
+ w = halfW - x;
+ }
+ if (y < -halfH) {
+ h -= -halfH - y;
+ y = -halfH;
+ }
+ if (y + h > halfH) {
+ h = halfH - y;
+ }
+ }
+ drawRect.x = x;
+ drawRect.y = y;
+ drawRect.width = w;
+ drawRect.height = h;
+
+ return true;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+ skin.gfx.beginBitmapFill(_texture._bitmapData, textureMatrix, false, _smooth);
+ skin.gfx.drawRect(drawRect.x, drawRect.y, drawRect.width, drawRect.height);
+ }
+
+ /**
+ * Текстура.
+ */
+ public function get texture():Texture {
+ return _texture;
+ }
+
+ /**
+ * @private
+ */
+ public function set texture(value:Texture):void {
+ if (_texture != value) {
+ _texture = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Сглаживание текстуры.
+ */
+ public function get smooth():Boolean {
+ return _smooth;
+ }
+
+ /**
+ * @private
+ */
+ public function set smooth(value:Boolean):void {
+ if (_smooth != value) {
+ _smooth = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Относительное смещение начала координат в текстуре по оси X.
+ *
+ * @default 0.5
+ */
+ public function get originX():Number {
+ return _originX;
+ }
+
+ /**
+ * @private
+ */
+ public function set originX(value:Number):void {
+ if (_originX != value) {
+ _originX = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Относительное смещение начала координат в текстуре по оси Y.
+ *
+ * @default 0.5
+ */
+ public function get originY():Number {
+ return _originY;
+ }
+
+ /**
+ * @private
+ */
+ public function set originY(value:Number):void {
+ if (_originY != value) {
+ _originY = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ return new SpriteTextureMaterial(_texture, _alpha, _smooth, _blendMode, _originX, _originY);
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/SurfaceMaterial.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/SurfaceMaterial.as
new file mode 100644
index 0000000..51fca54
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/SurfaceMaterial.as
@@ -0,0 +1,147 @@
+package alternativa.engine3d.materials {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.core.Scene3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.display.Skin;
+
+ import flash.display.BlendMode;
+
+ use namespace alternativa3d;
+
+ /**
+ * Базовый класс для материалов полигональных поверхностей.
+ */
+ public class SurfaceMaterial extends Material {
+
+ /**
+ * @private
+ * Поверхность
+ */
+ alternativa3d var _surface:Surface;
+ /**
+ * @private
+ * Флаг, определяет использует ли материал UV-координаты в грани
+ */
+ alternativa3d var useUV:Boolean = false;
+
+ /**
+ * @inheritDoc
+ */
+ public function SurfaceMaterial(alpha:Number = 1, blendMode:String = BlendMode.NORMAL) {
+ super(alpha, blendMode);
+ }
+
+ /**
+ * Поверхность, которой назначен материал.
+ */
+ public function get surface():Surface {
+ return _surface;
+ }
+
+ /**
+ * @private
+ * Добавление на сцену
+ *
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {}
+
+ /**
+ * @private
+ * Удаление из сцены
+ *
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {}
+
+ /**
+ * @private
+ * Добавление к мешу
+ *
+ * @param mesh
+ */
+ alternativa3d function addToMesh(mesh:Mesh):void {}
+
+ /**
+ * @private
+ * Удаление из меша
+ *
+ * @param mesh
+ */
+ alternativa3d function removeFromMesh(mesh:Mesh):void {}
+
+ /**
+ * @private
+ * Добавление на поверхность
+ *
+ * @param surface
+ */
+ alternativa3d function addToSurface(surface:Surface):void {
+ // Сохраняем поверхность
+ _surface = surface;
+ }
+
+ /**
+ * @private
+ * Удаление с поверхности
+ *
+ * @param surface
+ */
+ alternativa3d function removeFromSurface(surface:Surface):void {
+ // Удаляем ссылку на поверхность
+ _surface = null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function markToChange():void {
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+
+ /**
+ * @private
+ * Метод определяет, может ли материал нарисовать указанный примитив. Метод используется в системе отрисовки сцены и должен использоваться
+ * наследниками для указания видимости связанной с материалом поверхности или отдельного примитива. Реализация по умолчанию возвращает
+ * true.
+ *
+ * @param primitive примитив для проверки
+ *
+ * @return true, если материал может отрисовать указанный примитив, иначе false
+ */
+ alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
+ return true;
+ }
+
+ /**
+ * @private
+ * Метод выполняет отрисовку в заданный скин.
+ *
+ * @param camera камера, вызвавшая метод
+ * @param skin скин, в котором нужно рисовать
+ * @param length длина массива points
+ * @param points массив точек, определяющих отрисовываемый полигон. Каждый элемент массива является объектом класса
+ * alternativa.engine3d.materials.DrawPoint
+ *
+ * @see DrawPoint
+ */
+ alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ return new SurfaceMaterial(_alpha, _blendMode);
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/TextureMaterial.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/TextureMaterial.as
new file mode 100644
index 0000000..85d8357
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/TextureMaterial.as
@@ -0,0 +1,361 @@
+package alternativa.engine3d.materials {
+ import __AS3__.vec.Vector;
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.display.Skin;
+ import alternativa.types.*;
+
+ import flash.display.BitmapData;
+ import flash.display.BlendMode;
+ import flash.geom.Matrix;
+ import flash.display.BitmapData;
+ import flash.display.Graphics;
+
+ use namespace alternativa3d;
+ use namespace alternativatypes;
+
+ /**
+ * Материал, заполняющий полигон текстурой. Помимо наложения текстуры, материал может рисовать границу полигона линией
+ * заданной толщины и цвета.
+ */
+ public class TextureMaterial extends SurfaceMaterial {
+
+ private static var stubBitmapData:BitmapData;
+ private static var stubMatrix:Matrix;
+
+ private var gfx:Graphics;
+ private var textureMatrix:Matrix = new Matrix();
+ private var focalLength:Number;
+ private var distortion:Number;
+
+ /**
+ * @private
+ * Текстура
+ */
+ alternativa3d var _texture:Texture;
+ /**
+ * @private
+ * Повтор текстуры
+ */
+ alternativa3d var _repeat:Boolean;
+ /**
+ * @private
+ * Сглаженность текстуры
+ */
+ alternativa3d var _smooth:Boolean;
+ /**
+ * @private
+ * Точность перспективной коррекции
+ */
+ alternativa3d var _precision:Number;
+
+ /**
+ * @private
+ * Толщина линий обводки
+ */
+ alternativa3d var _wireThickness:Number;
+
+ /**
+ * @private
+ * Цвет линий обводки
+ */
+ alternativa3d var _wireColor:uint;
+
+ /**
+ * Создание экземпляра текстурного материала.
+ *
+ * @param texture текстура материала
+ * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ * @param repeat повтор текстуры при заполнении
+ * @param smooth сглаживание текстуры при увеличении масштаба
+ * @param blendMode режим наложения цвета
+ * @param wireThickness толщина линии обводки
+ * @param wireColor цвет линии обводки
+ * @param precision точность перспективной коррекции. Может быть задана одной из констант класса
+ * TextureMaterialPrecision или числом типа Number. Во втором случае, чем ближе заданное значение к единице, тем более
+ * качественная перспективная коррекция будет выполнена, и тем больше времени будет затрачено на расчёт кадра.
+ *
+ * @see TextureMaterialPrecision
+ */
+ public function TextureMaterial(texture:Texture, alpha:Number = 1, repeat:Boolean = true, smooth:Boolean = false, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0, precision:Number = TextureMaterialPrecision.MEDIUM) {
+ super(alpha, blendMode);
+ _texture = texture;
+ _repeat = repeat;
+ _smooth = smooth;
+ _wireThickness = wireThickness;
+ _wireColor = wireColor;
+ _precision = precision;
+ useUV = true;
+ }
+
+ /**
+ * @private
+ * Метод определяет, может ли материал нарисовать указанный примитив. Метод используется в системе отрисовки сцены и должен использоваться
+ * наследниками для указания видимости связанной с материалом поверхности или отдельного примитива.
+ *
+ * @param primitive примитив для проверки
+ *
+ * @return true, если материал может отрисовать указанный примитив, иначе false
+ */
+ override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
+ return _texture != null;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+
+ var i:uint;
+ var point:DrawPoint;
+ gfx = skin.gfx;
+
+ // Проверка на нулевую UV-матрицу
+ if (skin.primitive.face.uvMatrixBase == null) {
+ if (stubBitmapData == null) {
+ // Создание текстуры-заглушки
+ stubBitmapData = new BitmapData(2, 2, false, 0);
+ stubBitmapData.setPixel(0, 0, 0xFF00FF);
+ stubBitmapData.setPixel(1, 1, 0xFF00FF);
+ stubMatrix = new Matrix(10, 0, 0, 10, 0, 0);
+ }
+ gfx.beginBitmapFill(stubBitmapData, stubMatrix);
+ if (camera._orthographic) {
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ gfx.moveTo(point.x, point.y);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ var perspective:Number = camera.focalLength/point.z;
+ gfx.moveTo(point.x*perspective, point.y*perspective);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ }
+ return;
+ }
+
+ if (camera._orthographic) {
+ // Расчитываем матрицу наложения текстуры
+ var face:Face = skin.primitive.face;
+ // Если матрица не расчитана, считаем
+ if (!camera.uvMatricesCalculated[face]) {
+ camera.calculateUVMatrix(face, _texture._width, _texture._height);
+ }
+ gfx.beginBitmapFill(_texture._bitmapData, face.uvMatrix, _repeat, _smooth);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ gfx.moveTo(point.x, point.y);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ // Отрисовка
+ focalLength = camera.focalLength;
+ //distortion = camera.focalDistortion*_precision;
+
+ var front:int = 0;
+ var back:int = length - 1;
+
+ var newFront:int = 1;
+ var newBack:int = (back > 0) ? (back - 1) : (length - 1);
+ var direction:Boolean = true;
+
+ var a:DrawPoint = points[back];
+ var b:DrawPoint;
+ var c:DrawPoint = points[front];
+
+ var drawVertices:Vector.flash.display.Graphics#beginBitmapFill().
+ */
+ public function get repeat():Boolean {
+ return _repeat;
+ }
+
+ /**
+ * @private
+ */
+ public function set repeat(value:Boolean):void {
+ if (_repeat != value) {
+ _repeat = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Сглаживание текстуры при увеличении масштаба. Более подробную информацию можно найти в описании метода
+ * flash.display.Graphics#beginBitmapFill().
+ */
+ public function get smooth():Boolean {
+ return _smooth;
+ }
+
+ /**
+ * @private
+ */
+ public function set smooth(value:Boolean):void {
+ if (_smooth != value) {
+ _smooth = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Толщина линии обводки полигона. Если значение отрицательное, то обводка не рисуется.
+ */
+ public function get wireThickness():Number {
+ return _wireThickness;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireThickness(value:Number):void {
+ if (_wireThickness != value) {
+ _wireThickness = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Цвет линии обводки полигона.
+ */
+ public function get wireColor():uint {
+ return _wireColor;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireColor(value:uint):void {
+ if (_wireColor != value) {
+ _wireColor = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Точность перспективной коррекции.
+ */
+ public function get precision():Number {
+ return _precision;
+ }
+
+ /**
+ * @private
+ */
+ public function set precision(value:Number):void {
+ if (_precision != value) {
+ _precision = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ var res:TextureMaterial = new TextureMaterial(_texture, _alpha, _repeat, _smooth, _blendMode, _wireThickness, _wireColor, _precision);
+ return res;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/TextureMaterialPrecision.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/TextureMaterialPrecision.as
new file mode 100644
index 0000000..ca955e5
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/TextureMaterialPrecision.as
@@ -0,0 +1,39 @@
+package alternativa.engine3d.materials {
+
+ /**
+ * Класс содержит константы точности перспективной коррекции текстурного материала.
+ *
+ * @see TextureMaterial
+ */
+ public class TextureMaterialPrecision {
+
+ /**
+ * Адаптивная триангуляция не будет выполняться, только простая триангуляция.
+ */
+ public static const NONE:Number = -1;
+ /**
+ * Очень низкое качество адаптивной триангуляции.
+ */
+ public static const VERY_LOW:Number = 50;
+ /**
+ * Низкое качество адаптивной триангуляции.
+ */
+ public static const LOW:Number = 25;
+ /**
+ * Среднее качество адаптивной триангуляции.
+ */
+ public static const MEDIUM:Number = 10;
+ /**
+ * Высокое качество адаптивной триангуляции.
+ */
+ public static const HIGH:Number = 6;
+ /**
+ * Очень высокое качество адаптивной триангуляции.
+ */
+ public static const VERY_HIGH:Number = 3;
+ /**
+ * Максимальное качество адаптивной триангуляции.
+ */
+ public static const BEST:Number = 1;
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/materials/WireMaterial.as b/Alternativa3D5/5.5/alternativa/engine3d/materials/WireMaterial.as
new file mode 100644
index 0000000..6b112b1
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/materials/WireMaterial.as
@@ -0,0 +1,127 @@
+package alternativa.engine3d.materials {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.display.Skin;
+
+ import flash.display.BlendMode;
+ import flash.display.Graphics;
+
+ use namespace alternativa3d;
+
+ /**
+ * Материал для рисования рёбер полигонов.
+ */
+ public class WireMaterial extends SurfaceMaterial {
+
+ /**
+ * @private
+ * Цвет
+ */
+ alternativa3d var _color:uint;
+ /**
+ * @private
+ * Толщина линий
+ */
+ alternativa3d var _thickness:Number;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param thickness толщина линий
+ * @param color цвет линий
+ * @param alpha коэффициент непрозрачности линий. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ * @param blendMode режим наложения цвета
+ */
+ public function WireMaterial(thickness:Number = 0, color:uint = 0, alpha:Number = 1, blendMode:String = BlendMode.NORMAL) {
+ super(alpha, blendMode);
+ _color = color;
+ _thickness = thickness;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
+ return _thickness >= 0;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+
+ var i:uint;
+ var point:DrawPoint;
+ var gfx:Graphics = skin.gfx;
+
+ if (camera._orthographic) {
+ gfx.lineStyle(_thickness, _color);
+ point = points[length - 1];
+ gfx.moveTo(point.x, point.y);
+ for (i = 0; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ // Отрисовка
+ gfx.lineStyle(_thickness, _color);
+ point = points[length - 1];
+ var perspective:Number = camera.focalLength/point.z;
+ gfx.moveTo(point.x*perspective, point.y*perspective);
+ for (i = 0; i < length; i++) {
+ point = points[i];
+ perspective = camera.focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ }
+ }
+
+ /**
+ * Цвет линий.
+ */
+ public function get color():uint {
+ return _color;
+ }
+
+ /**
+ * @private
+ */
+ public function set color(value:uint):void {
+ if (_color != value) {
+ _color = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Толщина линий. Если толщина отрицательная, то отрисовка не выполняется.
+ */
+ public function get thickness():Number {
+ return _thickness;
+ }
+
+ /**
+ * @private
+ */
+ public function set thickness(value:Number):void {
+ if (_thickness != value) {
+ _thickness = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ return new WireMaterial(_thickness, _color, _alpha, _blendMode);
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/physics/Collision.as b/Alternativa3D5/5.5/alternativa/engine3d/physics/Collision.as
new file mode 100644
index 0000000..5e0e560
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/physics/Collision.as
@@ -0,0 +1,30 @@
+package alternativa.engine3d.physics {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.types.Point3D;
+
+ use namespace alternativa3d;
+
+ /**
+ * Параметры столкновения эллипсоида с гранью объекта. Плоскостью столкновения является касательная к
+ * эллипсоиду плоскость, проходящая через точку столкновения с гранью.
+ */
+ public class Collision {
+ /**
+ * Грань, с которой произошло столкновение.
+ */
+ public var face:Face;
+ /**
+ * Нормаль плоскости столкновения.
+ */
+ public var normal:Point3D;
+ /**
+ * Смещение плоскости столкновения.
+ */
+ public var offset:Number;
+ /**
+ * Координаты точки столкновения.
+ */
+ public var point:Point3D;
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/physics/CollisionPlane.as b/Alternativa3D5/5.5/alternativa/engine3d/physics/CollisionPlane.as
new file mode 100644
index 0000000..65c3634
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/physics/CollisionPlane.as
@@ -0,0 +1,60 @@
+package alternativa.engine3d.physics {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.BSPNode;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ */
+ public class CollisionPlane {
+ // Узел BSP дерева, который содержит плоскость
+ public var node:BSPNode;
+ // Индикатор положения объекта относительно плоскости (спереди или сзади)
+ public var infront:Boolean;
+ // Расстояние до плоскости в начальной точке (всегда положительное)
+ public var sourceOffset:Number;
+ // Расстояние до плоскости в конечной точке
+ public var destinationOffset:Number;
+
+ // Хранилище неиспользуемых плоскостей
+ static private var collector:Array = new Array();
+
+
+ /**
+ * Создание плоскости
+ *
+ * @param node
+ * @param infront
+ * @param sourceOffset
+ * @param destinationOffset
+ * @return
+ */
+ static alternativa3d function createCollisionPlane(node:BSPNode, infront:Boolean, sourceOffset:Number, destinationOffset:Number):CollisionPlane {
+
+ // Достаём плоскость из коллектора
+ var plane:CollisionPlane = collector.pop();
+ // Если коллектор пуст, создаём новую плоскость
+ if (plane == null) {
+ plane = new CollisionPlane();
+ }
+
+ plane.node = node;
+ plane.infront = infront;
+ plane.sourceOffset = sourceOffset;
+ plane.destinationOffset = destinationOffset;
+
+ return plane;
+ }
+
+ /**
+ * Удаление плоскости, все ссылки должны быть почищены
+ *
+ * @param plane
+ */
+ static alternativa3d function destroyCollisionPlane(plane:CollisionPlane):void {
+ plane.node = null;
+ collector.push(plane);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/physics/CollisionSetMode.as b/Alternativa3D5/5.5/alternativa/engine3d/physics/CollisionSetMode.as
new file mode 100644
index 0000000..7dd776e
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/physics/CollisionSetMode.as
@@ -0,0 +1,18 @@
+package alternativa.engine3d.physics {
+ /**
+ * Константы, определяющие режим учёта объектов сцены, заданных в множестве EllipsoidCollider.collisionSet
+ * при определении столкновений.
+ *
+ * @see EllipsoidCollider#collisionSet
+ */
+ public class CollisionSetMode {
+ /**
+ * Грани объектов игнорируются при определении столкновений.
+ */
+ static public const EXCLUDE:int = 1;
+ /**
+ * Учитываются только столкновения с гранями, принадлежащим перечисленным в множестве объектам.
+ */
+ static public const INCLUDE:int = 2;
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/physics/EllipsoidCollider.as b/Alternativa3D5/5.5/alternativa/engine3d/physics/EllipsoidCollider.as
new file mode 100644
index 0000000..bcf933c
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/physics/EllipsoidCollider.as
@@ -0,0 +1,994 @@
+package alternativa.engine3d.physics {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.BSPNode;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.core.Scene3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+ import alternativa.utils.ObjectUtils;
+
+ use namespace alternativa3d;
+
+ /**
+ * Класс реализует алгоритм непрерывного определения столкновений эллипсоида с плоскими выпуклыми многоугольниками.
+ */
+ public class EllipsoidCollider {
+ // Максимальное количество попыток найти свободное от столкновения со сценой направление
+ private static const MAX_COLLISIONS:uint = 50;
+ // Радиус наибольшей сферы
+ private var _radius:Number = 100;
+ private var _radius2:Number = _radius * _radius;
+ private var _radiusX:Number = _radius;
+ private var _radiusY:Number = _radius;
+ private var _radiusZ:Number = _radius;
+ private var _radiusX2:Number = _radiusX * _radiusX;
+ private var _radiusY2:Number = _radiusY * _radiusY;
+ private var _radiusZ2:Number = _radiusZ * _radiusZ;
+ // Коэффициенты масштабирования осей
+ private var _scaleX:Number = 1;
+ private var _scaleY:Number = 1;
+ private var _scaleZ:Number = 1;
+ // Квадраты коэффициентов масштабирования осей
+ private var _scaleX2:Number = 1;
+ private var _scaleY2:Number = 1;
+ private var _scaleZ2:Number = 1;
+
+ private var collisionSource:Point3D;
+ private var currentDisplacement:Point3D = new Point3D();
+ private var collisionDestination:Point3D = new Point3D();
+
+ private var collisionPlanes:Array = new Array();
+ private var collisionPrimitive:PolyPrimitive;
+ private var collisionPrimitiveNearest:PolyPrimitive;
+ private var collisionPlanePoint:Point3D = new Point3D();
+ private var collisionPrimitiveNearestLengthSqr:Number;
+ private var collisionPrimitivePoint:Point3D = new Point3D();
+
+ private var collisionNormal:Point3D = new Point3D();
+ private var collisionPoint:Point3D = new Point3D();
+ private var collisionOffset:Number;
+
+ private var currentCoords:Point3D = new Point3D();
+ private var collision:Collision = new Collision();
+ private var collisionRadius:Number;
+ private var radiusVector:Point3D = new Point3D();
+ private var p1:Point3D = new Point3D();
+ private var p2:Point3D = new Point3D();;
+ private var localCollisionPlanePoint:Point3D = new Point3D();
+
+ // Флаг использования упорщённого алгоритма. Включается когда эллипсоид представляет собой сферу.
+ private var useSimpleAlgorithm:Boolean = true;
+
+ /**
+ * Сцена, в которой определяются столкновения.
+ */
+ public var scene:Scene3D;
+ /**
+ * Погрешность определения расстояний и координат. Две точки совпадают, если модуль разности любых соответствующих
+ * координат меньше указанной погрешности.
+ */
+ public var offsetThreshold:Number = 0.0001;
+ /**
+ * Множество объектов, учитываемых в процессе определения столкновений. В качестве объектов могут выступать экземпляры
+ * классов Mesh и Surface. Каким образом учитываются перечисленные в множестве объекты зависит
+ * от значения поля collisionSetMode. Значение null эквивалентно заданию пустого множества.
+ *
+ * @see #collisionSetMode
+ * @see alternativa.engine3d.core.Mesh
+ * @see alternativa.engine3d.core.Surface
+ */
+ public var collisionSet:Set;
+ /**
+ * Параметр определяет, каким образом учитываются объекты, перечисленные в множестве collisionSet. Если
+ * значение параметра равно true, то грани объектов из множества игнорируются при определении столкновений.
+ * При значении параметра false учитываются только столкновения с гранями, принадлежащим перечисленным
+ * в множестве объектам.
+ *
+ * @default true
+ * @see #collisionSet
+ */
+ private var _collisionSetMode:int = CollisionSetMode.EXCLUDE;
+
+ /**
+ * Создаёт новый экземпляр класса.
+ *
+ * @param scene сцена, в которой определяются столкновения
+ * @param scaleX радиус эллипсоида по оси X
+ * @param scaleY радиус эллипсоида по оси Y
+ * @param scaleZ радиус эллипсоида по оси Z
+ */
+ public function EllipsoidCollider(scene:Scene3D = null, radiusX:Number = 100, radiusY:Number = 100, radiusZ:Number = 100) {
+ this.scene = scene;
+ this.radiusX = radiusX;
+ this.radiusY = radiusY;
+ this.radiusZ = radiusZ;
+ }
+
+ /**
+ * @private
+ */
+ public function get collisionSetMode():int {
+ return _collisionSetMode;
+ }
+
+ /**
+ * Параметр определяет, каким образом учитываются объекты, перечисленные в множестве collisionSet.
+ *
+ * @default CollisionSetMode.EXCLUDE
+ * @see #collisionSet
+ * @see CollisionSetMode
+ */
+ public function set collisionSetMode(value:int):void {
+ if (value != CollisionSetMode.EXCLUDE && value != CollisionSetMode.INCLUDE) {
+ throw ArgumentError(ObjectUtils.getClassName(this) + ".collisionSetMode invalid value");
+ }
+ _collisionSetMode = value;
+ }
+
+ /**
+ * Величина радиуса (полуоси) эллипсоида по оси X. При установке отрицательного значения берётся модуль.
+ *
+ * @default 100
+ */
+ public function get radiusX():Number {
+ return _radiusX;
+ }
+
+ /**
+ * @private
+ */
+ public function set radiusX(value:Number):void {
+ _radiusX = value >= 0 ? value : -value;
+ _radiusX2 = _radiusX * _radiusX;
+ calculateScales();
+ }
+
+ /**
+ * Величина радиуса (полуоси) эллипсоида по оси Y. При установке отрицательного значения берётся модуль.
+ *
+ * @default 100
+ */
+ public function get radiusY():Number {
+ return _radiusY;
+ }
+
+ /**
+ * @private
+ */
+ public function set radiusY(value:Number):void {
+ _radiusY = value >= 0 ? value : -value;
+ _radiusY2 = _radiusY * _radiusY;
+ calculateScales();
+ }
+
+ /**
+ * Величина радиуса (полуоси) эллипсоида по оси Z. При установке отрицательного значения берётся модуль.
+ *
+ * @default 100
+ */
+ public function get radiusZ():Number {
+ return _radiusZ;
+ }
+
+ /**
+ * @private
+ */
+ public function set radiusZ(value:Number):void {
+ _radiusZ = value >= 0 ? value : -value;
+ _radiusZ2 = _radiusZ * _radiusZ;
+ calculateScales();
+ }
+
+ /**
+ * Расчёт коэффициентов масштабирования осей.
+ */
+ private function calculateScales():void {
+ _radius = _radiusX;
+ if (_radiusY > _radius) {
+ _radius = _radiusY;
+ }
+ if (_radiusZ > _radius) {
+ _radius = _radiusZ;
+ }
+ _radius2 = _radius * _radius;
+ _scaleX = _radiusX / _radius;
+ _scaleY = _radiusY / _radius;
+ _scaleZ = _radiusZ / _radius;
+ _scaleX2 = _scaleX * _scaleX;
+ _scaleY2 = _scaleY * _scaleY;
+ _scaleZ2 = _scaleZ * _scaleZ;
+
+ useSimpleAlgorithm = (_radiusX == _radiusY) && (_radiusX == _radiusZ);
+ }
+
+ /**
+ * Расчёт конечного положения эллипсоида по заданному начальному положению и вектору смещения. Если задано значение
+ * поля scene, то при вычислении конечного положения учитываются столкновения с объектами сцены,
+ * принимая во внимание множество collisionSet и режим работы collisionSetMode. Если
+ * значение поля scene равно null, то результат работы метода будет простой суммой двух
+ * входных векторов.
+ *
+ * @param sourcePoint начальное положение центра эллипсоида в системе координат корневого объекта сцены
+ * @param displacementVector вектор перемещения эллипсоида в системе координат корневого объекта сцены. Если модуль
+ * каждого компонента вектора не превышает значения offsetThreshold, эллипсоид остаётся в начальной точке.
+ * @param destinationPoint в эту переменную записывается расчётное положение центра эллипсоида в системе координат
+ * корневого объекта сцены
+ *
+ * @see #scene
+ * @see #collisionSet
+ * @see #collisionSetMode
+ * @see #offsetThreshold
+ */
+ public function calculateDestination(sourcePoint:Point3D, displacementVector:Point3D, destinationPoint:Point3D):void {
+ // Расчеты не производятся, если перемещение мало
+ if (displacementVector.x < offsetThreshold && displacementVector.x > -offsetThreshold &&
+ displacementVector.y < offsetThreshold && displacementVector.y > -offsetThreshold &&
+ displacementVector.z < offsetThreshold && displacementVector.z > -offsetThreshold) {
+ destinationPoint.x = sourcePoint.x;
+ destinationPoint.y = sourcePoint.y;
+ destinationPoint.z = sourcePoint.z;
+ return;
+ }
+
+ // Начальные координаты
+ currentCoords.x = sourcePoint.x;
+ currentCoords.y = sourcePoint.y;
+ currentCoords.z = sourcePoint.z;
+ // Начальный вектор перемещения
+ currentDisplacement.x = displacementVector.x;
+ currentDisplacement.y = displacementVector.y;
+ currentDisplacement.z = displacementVector.z;
+ // Начальная точка назначения
+ destinationPoint.x = sourcePoint.x + currentDisplacement.x;
+ destinationPoint.y = sourcePoint.y + currentDisplacement.y;
+ destinationPoint.z = sourcePoint.z + currentDisplacement.z;
+
+ if (useSimpleAlgorithm) {
+ calculateDestinationS(sourcePoint, destinationPoint);
+ } else {
+ calculateDestinationE(sourcePoint, destinationPoint);
+ }
+ }
+
+ /**
+ * Вычисление точки назначения для сферы.
+ * @param sourcePoint
+ * @param destinationPoint
+ */
+ private function calculateDestinationS(sourcePoint:Point3D, destinationPoint:Point3D):void {
+ var collisionCount:uint = 0;
+ var hasCollision:Boolean;
+ do {
+ hasCollision = getCollision(currentCoords, currentDisplacement, collision);
+ if (hasCollision ) {
+ // Вынос точки назначения из-за плоскости столкновения на высоту радиуса сферы над плоскостью по направлению нормали
+ var offset:Number = _radius + offsetThreshold + collision.offset - destinationPoint.x*collision.normal.x - destinationPoint.y*collision.normal.y - destinationPoint.z*collision.normal.z;
+ destinationPoint.x += collision.normal.x * offset;
+ destinationPoint.y += collision.normal.y * offset;
+ destinationPoint.z += collision.normal.z * offset;
+ // Коррекция текущих кординат центра сферы для следующей итерации
+ currentCoords.x = collision.point.x + collision.normal.x * (_radius + offsetThreshold);
+ currentCoords.y = collision.point.y + collision.normal.y * (_radius + offsetThreshold);
+ currentCoords.z = collision.point.z + collision.normal.z * (_radius + offsetThreshold);
+ // Коррекция вектора скорости. Результирующий вектор направлен вдоль плоскости столкновения.
+ currentDisplacement.x = destinationPoint.x - currentCoords.x;
+ currentDisplacement.y = destinationPoint.y - currentCoords.y;
+ currentDisplacement.z = destinationPoint.z - currentCoords.z;
+
+ // Если смещение слишком мало, останавливаемся
+ if (currentDisplacement.x < offsetThreshold && currentDisplacement.x > -offsetThreshold &&
+ currentDisplacement.y < offsetThreshold && currentDisplacement.y > -offsetThreshold &&
+ currentDisplacement.z < offsetThreshold && currentDisplacement.z > -offsetThreshold) {
+ break;
+ }
+ }
+ } while (hasCollision && (++collisionCount < MAX_COLLISIONS));
+ // Если количество итераций достигло максимально возможного значения, то остаемся на старом месте
+ if (collisionCount == MAX_COLLISIONS) {
+ destinationPoint.x = sourcePoint.x;
+ destinationPoint.y = sourcePoint.y;
+ destinationPoint.z = sourcePoint.z;
+ }
+ }
+
+ /**
+ * Вычисление точки назначения для эллипсоида.
+ * @param destinationPoint
+ * @return
+ */
+ private function calculateDestinationE(sourcePoint:Point3D, destinationPoint:Point3D):void {
+ var collisionCount:uint = 0;
+ var hasCollision:Boolean;
+ // Цикл выполняется до тех пор, пока не будет найдено ни одного столкновения на очередной итерации или пока не
+ // будет достигнуто максимально допустимое количество столкновений, что означает зацикливание алгоритма и
+ // необходимость принудительного выхода.
+ do {
+ hasCollision = getCollision(currentCoords, currentDisplacement, collision);
+ if (hasCollision) {
+ // Вынос точки назначения из-за плоскости столкновения на высоту эффективного радиуса эллипсоида над плоскостью по направлению нормали
+ var offset:Number = collisionRadius + offsetThreshold + collision.offset - destinationPoint.x * collision.normal.x - destinationPoint.y * collision.normal.y - destinationPoint.z * collision.normal.z;
+ destinationPoint.x += collision.normal.x * offset;
+ destinationPoint.y += collision.normal.y * offset;
+ destinationPoint.z += collision.normal.z * offset;
+ // Коррекция текущих кординат центра эллипсоида для следующей итерации
+ collisionRadius = (collisionRadius + offsetThreshold) / collisionRadius;
+ currentCoords.x = collision.point.x - collisionRadius * radiusVector.x;
+ currentCoords.y = collision.point.y - collisionRadius * radiusVector.y;
+ currentCoords.z = collision.point.z - collisionRadius * radiusVector.z;
+ // Коррекция вектора смещения. Результирующий вектор направлен параллельно плоскости столкновения.
+ currentDisplacement.x = destinationPoint.x - currentCoords.x;
+ currentDisplacement.y = destinationPoint.y - currentCoords.y;
+ currentDisplacement.z = destinationPoint.z - currentCoords.z;
+ // Если смещение слишком мало, останавливаемся
+ if (currentDisplacement.x < offsetThreshold && currentDisplacement.x > -offsetThreshold &&
+ currentDisplacement.y < offsetThreshold && currentDisplacement.y > -offsetThreshold &&
+ currentDisplacement.z < offsetThreshold && currentDisplacement.z > -offsetThreshold) {
+ destinationPoint.x = currentCoords.x;
+ destinationPoint.y = currentCoords.y;
+ destinationPoint.z = currentCoords.z;
+ break;
+ }
+ }
+ } while (hasCollision && (++collisionCount < MAX_COLLISIONS));
+ // Если количество итераций достигло максимально возможного значения, то остаемся на старом месте
+ if (collisionCount == MAX_COLLISIONS) {
+ destinationPoint.x = sourcePoint.x;
+ destinationPoint.y = sourcePoint.y;
+ destinationPoint.z = sourcePoint.z;
+ }
+ }
+
+ /**
+ * Метод определяет наличие столкновения при смещении эллипсоида из заданной точки на величину указанного вектора
+ * перемещения, принимая во внимание множество collisionSet и режим работы collisionSetMode.
+ *
+ * @param sourcePoint начальное положение центра эллипсоида в системе координат корневого объекта сцены
+ * @param displacementVector вектор перемещения эллипсоида в системе координат корневого объекта сцены
+ * @param collision в эту переменную будут записаны данные о плоскости и точке столкновения в системе координат
+ * корневого объекта сцены
+ *
+ * @return true, если эллипсоид при заданном перемещении столкнётся с каким-либо полигоном сцены,
+ * false если столкновений нет или не задано значение поля scene.
+ *
+ * @see #scene
+ * @see #collisionSet
+ * @see #collisionSetMode
+ */
+ public function getCollision(sourcePoint:Point3D, displacementVector:Point3D, collision:Collision):Boolean {
+ if (scene == null) {
+ return false;
+ }
+
+ collisionSource = sourcePoint;
+
+ currentDisplacement.x = displacementVector.x;
+ currentDisplacement.y = displacementVector.y;
+ currentDisplacement.z = displacementVector.z;
+
+ collisionDestination.x = collisionSource.x + currentDisplacement.x;
+ collisionDestination.y = collisionSource.y + currentDisplacement.y;
+ collisionDestination.z = collisionSource.z + currentDisplacement.z;
+
+ collectPotentialCollisionPlanes(scene.bsp);
+ collisionPlanes.sortOn("sourceOffset", Array.NUMERIC | Array.DESCENDING);
+
+ var plane:CollisionPlane;
+ // Пока не найдём столкновение с примитивом или плоскости не кончатся
+ if (useSimpleAlgorithm) {
+ while ((plane = collisionPlanes.pop()) != null) {
+ if (collisionPrimitive == null) {
+ calculateCollisionWithPlaneS(plane);
+ }
+ CollisionPlane.destroyCollisionPlane(plane);
+ }
+ } else {
+ while ((plane = collisionPlanes.pop()) != null) {
+ if (collisionPrimitive == null) {
+ calculateCollisionWithPlaneE(plane);
+ }
+ CollisionPlane.destroyCollisionPlane(plane);
+ }
+ }
+
+ var collisionFound:Boolean = collisionPrimitive != null;
+ if (collisionFound) {
+ collision.face = collisionPrimitive.face;
+ collision.normal = collisionNormal;
+ collision.offset = collisionOffset;
+ collision.point = collisionPoint;
+ }
+
+ collisionPrimitive = null;
+ collisionSource = null;
+
+ return collisionFound;
+ }
+
+ /**
+ * Сбор потенциальных плоскостей столкновения.
+ *
+ * @param node текущий узел BSP-дерева
+ */
+ private function collectPotentialCollisionPlanes(node:BSPNode):void {
+ if (node == null) {
+ return;
+ }
+
+ var sourceOffset:Number = collisionSource.x * node.normal.x + collisionSource.y * node.normal.y + collisionSource.z * node.normal.z - node.offset;
+ var destinationOffset:Number = collisionDestination.x * node.normal.x + collisionDestination.y * node.normal.y + collisionDestination.z * node.normal.z - node.offset;
+ var plane:CollisionPlane;
+
+ if (sourceOffset >= 0) {
+ // Исходное положение центра перед плоскостью ноды
+ // Проверяем передние ноды
+ collectPotentialCollisionPlanes(node.front);
+ // Грубая оценка пересечения с плоскостью по радиусу ограничивающей сферы эллипсоида
+ // Или мы наткнулись на спрайтовую ноду
+ if (destinationOffset < _radius && !node.sprited) {
+ // Нашли потенциальное пересечение с плоскостью
+ plane = CollisionPlane.createCollisionPlane(node, true, sourceOffset, destinationOffset);
+ collisionPlanes.push(plane);
+ // Проверяем задние ноды
+ collectPotentialCollisionPlanes(node.back);
+ }
+ } else {
+ // Исходное положение центра за плоскостью ноды
+ // Проверяем задние ноды
+ collectPotentialCollisionPlanes(node.back);
+ // Грубая оценка пересечения с плоскостью по радиусу ограничивающей сферы эллипсоида
+ if (destinationOffset > -_radius) {
+ // Столкновение возможно только в случае если в ноде есть примитивы, направленные назад
+ if (node.backPrimitives != null) {
+ // Нашли потенциальное пересечение с плоскостью
+ plane = CollisionPlane.createCollisionPlane(node, false, -sourceOffset, -destinationOffset);
+ collisionPlanes.push(plane);
+ }
+ // Проверяем передние ноды
+ collectPotentialCollisionPlanes(node.front);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Определение пересечения сферы с примитивами, лежащими в заданной плоскости.
+ *
+ * @param plane плоскость, содержащая примитивы для проверки
+ */
+ private function calculateCollisionWithPlaneS(plane:CollisionPlane):void {
+ collisionPlanePoint.copy(collisionSource);
+
+ var normal:Point3D = plane.node.normal;
+ // Если сфера врезана в плоскость
+ if (plane.sourceOffset <= _radius) {
+ if (plane.infront) {
+ collisionPlanePoint.x -= normal.x * plane.sourceOffset;
+ collisionPlanePoint.y -= normal.y * plane.sourceOffset;
+ collisionPlanePoint.z -= normal.z * plane.sourceOffset;
+ } else {
+ collisionPlanePoint.x += normal.x * plane.sourceOffset;
+ collisionPlanePoint.y += normal.y * plane.sourceOffset;
+ collisionPlanePoint.z += normal.z * plane.sourceOffset;
+ }
+ } else {
+ // Находим центр сферы во время столкновения с плоскостью
+ var time:Number = (plane.sourceOffset - _radius) / (plane.sourceOffset - plane.destinationOffset);
+ collisionPlanePoint.x = collisionSource.x + currentDisplacement.x * time;
+ collisionPlanePoint.y = collisionSource.y + currentDisplacement.y * time;
+ collisionPlanePoint.z = collisionSource.z + currentDisplacement.z * time;
+
+ // Устанавливаем точку пересечения cферы с плоскостью
+ if (plane.infront) {
+ collisionPlanePoint.x -= normal.x * _radius;
+ collisionPlanePoint.y -= normal.y * _radius;
+ collisionPlanePoint.z -= normal.z * _radius;
+ } else {
+ collisionPlanePoint.x += normal.x * _radius;
+ collisionPlanePoint.y += normal.y * _radius;
+ collisionPlanePoint.z += normal.z * _radius;
+ }
+ }
+
+ // Проверяем примитивы плоскости
+ var primitive:*;
+ collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE;
+ collisionPrimitiveNearest = null;
+ if (plane.infront) {
+ if ((primitive = plane.node.primitive) != null) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveS(plane.node.primitive);
+ }
+ } else {
+ for (primitive in plane.node.frontPrimitives) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveS(primitive);
+ if (collisionPrimitive != null) break;
+ }
+ }
+ }
+ } else {
+ for (primitive in plane.node.backPrimitives) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveS(primitive);
+ if (collisionPrimitive != null) break;
+ }
+ }
+ }
+
+ if (collisionPrimitive != null) {
+ // Если точка пересечения попала в примитив
+
+ // Нормаль плоскости при столкновении - нормаль плоскости
+ if (plane.infront) {
+ collisionNormal.x = normal.x;
+ collisionNormal.y = normal.y;
+ collisionNormal.z = normal.z;
+ collisionOffset = plane.node.offset;
+ } else {
+ collisionNormal.x = -normal.x;
+ collisionNormal.y = -normal.y;
+ collisionNormal.z = -normal.z;
+ collisionOffset = -plane.node.offset;
+ }
+
+ // Точка столкновения в точке столкновения с плоскостью
+ collisionPoint.x = collisionPlanePoint.x;
+ collisionPoint.y = collisionPlanePoint.y;
+ collisionPoint.z = collisionPlanePoint.z;
+
+ } else {
+ // Если точка пересечения не попала ни в один примитив, проверяем столкновение с ближайшей
+
+ // Вектор из ближайшей точки в центр сферы
+ var nearestPointToSourceX:Number = collisionSource.x - collisionPrimitivePoint.x;
+ var nearestPointToSourceY:Number = collisionSource.y - collisionPrimitivePoint.y;
+ var nearestPointToSourceZ:Number = collisionSource.z - collisionPrimitivePoint.z;
+
+ // Если движение в сторону точки
+ if (nearestPointToSourceX * currentDisplacement.x + nearestPointToSourceY * currentDisplacement.y + nearestPointToSourceZ * currentDisplacement.z <= 0) {
+
+ // Ищем нормализованный вектор обратного направления
+ var vectorLength:Number = Math.sqrt(currentDisplacement.x * currentDisplacement.x + currentDisplacement.y * currentDisplacement.y + currentDisplacement.z * currentDisplacement.z);
+ var vectorX:Number = -currentDisplacement.x / vectorLength;
+ var vectorY:Number = -currentDisplacement.y / vectorLength;
+ var vectorZ:Number = -currentDisplacement.z / vectorLength;
+
+ // Длина вектора из ближайшей точки в центр сферы
+ var nearestPointToSourceLengthSqr:Number = nearestPointToSourceX * nearestPointToSourceX + nearestPointToSourceY * nearestPointToSourceY + nearestPointToSourceZ * nearestPointToSourceZ;
+
+ // Проекция вектора из ближайшей точки в центр сферы на нормализованный вектор обратного направления
+ var projectionLength:Number = nearestPointToSourceX * vectorX + nearestPointToSourceY * vectorY + nearestPointToSourceZ * vectorZ;
+
+ var projectionInsideSphereLengthSqr:Number = _radius2 - nearestPointToSourceLengthSqr + projectionLength * projectionLength;
+
+ if (projectionInsideSphereLengthSqr > 0) {
+ // Находим расстояние из ближайшей точки до сферы
+ var distance:Number = projectionLength - Math.sqrt(projectionInsideSphereLengthSqr);
+
+ if (distance < vectorLength) {
+ // Столкновение сферы с ближайшей точкой произошло
+
+ // Точка столкновения в ближайшей точке
+ collisionPoint.x = collisionPrimitivePoint.x;
+ collisionPoint.y = collisionPrimitivePoint.y;
+ collisionPoint.z = collisionPrimitivePoint.z;
+
+ // Находим нормаль плоскости столкновения
+ var nearestPointToSourceLength:Number = Math.sqrt(nearestPointToSourceLengthSqr);
+ collisionNormal.x = nearestPointToSourceX / nearestPointToSourceLength;
+ collisionNormal.y = nearestPointToSourceY / nearestPointToSourceLength;
+ collisionNormal.z = nearestPointToSourceZ / nearestPointToSourceLength;
+
+ // Смещение плоскости столкновения
+ collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z;
+ collisionPrimitive = collisionPrimitiveNearest;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Определение столкновения сферы с примитивом.
+ *
+ * @param primitive примитив, столкновение с которым проверяется
+ */
+ private function calculateCollisionWithPrimitiveS(primitive:PolyPrimitive):void {
+
+ var length:uint = primitive.num;
+ var points:Array = primitive.points;
+ var normal:Point3D = primitive.face.globalNormal;
+ var inside:Boolean = true;
+
+ for (var i:uint = 0; i < length; i++) {
+
+ var p1:Point3D = points[i];
+ var p2:Point3D = points[(i < length - 1) ? (i + 1) : 0];
+
+ var edgeX:Number = p2.x - p1.x;
+ var edgeY:Number = p2.y - p1.y;
+ var edgeZ:Number = p2.z - p1.z;
+
+ var vectorX:Number = collisionPlanePoint.x - p1.x;
+ var vectorY:Number = collisionPlanePoint.y - p1.y;
+ var vectorZ:Number = collisionPlanePoint.z - p1.z;
+
+ var crossX:Number = vectorY * edgeZ - vectorZ * edgeY;
+ var crossY:Number = vectorZ * edgeX - vectorX * edgeZ;
+ var crossZ:Number = vectorX * edgeY - vectorY * edgeX;
+
+ if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) {
+ // Точка за пределами полигона
+ inside = false;
+
+ var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ;
+ var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr;
+
+ // Если расстояние до прямой меньше текущего ближайшего
+ if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) {
+
+ // Ищем нормализованный вектор ребра
+ var edgeLength:Number = Math.sqrt(edgeLengthSqr);
+ var edgeNormX:Number = edgeX / edgeLength;
+ var edgeNormY:Number = edgeY / edgeLength;
+ var edgeNormZ:Number = edgeZ / edgeLength;
+
+ // Находим расстояние до точки перпендикуляра вдоль ребра
+ var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ;
+
+ var vectorLengthSqr:Number;
+ if (t < 0) {
+ // Ближайшая точка - первая
+ vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
+ if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
+ collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
+ collisionPrimitivePoint.x = p1.x;
+ collisionPrimitivePoint.y = p1.y;
+ collisionPrimitivePoint.z = p1.z;
+ collisionPrimitiveNearest = primitive;
+ }
+ } else {
+ if (t > edgeLength) {
+ // Ближайшая точка - вторая
+ vectorX = collisionPlanePoint.x - p2.x;
+ vectorY = collisionPlanePoint.y - p2.y;
+ vectorZ = collisionPlanePoint.z - p2.z;
+ vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
+ if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
+ collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
+ collisionPrimitivePoint.x = p2.x;
+ collisionPrimitivePoint.y = p2.y;
+ collisionPrimitivePoint.z = p2.z;
+ collisionPrimitiveNearest = primitive;
+ }
+ } else {
+ // Ближайшая точка на ребре
+ collisionPrimitiveNearestLengthSqr = edgeDistanceSqr;
+ collisionPrimitivePoint.x = p1.x + edgeNormX * t;
+ collisionPrimitivePoint.y = p1.y + edgeNormY * t;
+ collisionPrimitivePoint.z = p1.z + edgeNormZ * t;
+ collisionPrimitiveNearest = primitive;
+ }
+ }
+ }
+ }
+ }
+
+ // Если попали в примитив
+ if (inside) {
+ collisionPrimitive = primitive;
+ }
+ }
+
+ /**
+ * Проверка на действительное столкновение эллипсоида с плоскостью.
+ */
+ private function calculateCollisionWithPlaneE(plane:CollisionPlane):void {
+ var normalX:Number = plane.node.normal.x;
+ var normalY:Number = plane.node.normal.y;
+ var normalZ:Number = plane.node.normal.z;
+ // Смещение по направлению к плоскости вдоль нормали. Положительное смещение означает приближение к плоскости, отрицательное -- удаление
+ // от плоскости, в этом случае столкновения не происходит.
+ var displacementAlongNormal:Number = currentDisplacement.x * normalX + currentDisplacement.y * normalY + currentDisplacement.z * normalZ;
+ if (plane.infront) {
+ displacementAlongNormal = -displacementAlongNormal;
+ }
+ // Выходим из функции в случае удаления от плоскости
+ if (displacementAlongNormal < 0) {
+ return;
+ }
+ // Определение ближайшей к плоскости точки эллипсоида
+ var k:Number = _radius / Math.sqrt(normalX * normalX * _scaleX2 + normalY * normalY * _scaleY2 + normalZ * normalZ * _scaleZ2);
+ // Положение точки в локальной системе координат эллипсоида
+ var localClosestX:Number = k * normalX * _scaleX2;
+ var localClosestY:Number = k * normalY * _scaleY2;
+ var localClosestZ:Number = k * normalZ * _scaleZ2;
+ // Глобальные координаты точки
+ var px:Number = collisionSource.x + localClosestX;
+ var py:Number = collisionSource.y + localClosestY;
+ var pz:Number = collisionSource.z + localClosestZ;
+ // Растояние от найденной точки эллипсоида до плоскости
+ var closestPointDistance:Number = px * normalX + py * normalY + pz * normalZ - plane.node.offset;
+ if (!plane.infront) {
+ closestPointDistance = -closestPointDistance;
+ }
+ if (closestPointDistance > plane.sourceOffset) {
+ // Найдена наиболее удалённая точка, расчитываем вторую
+ px = collisionSource.x - localClosestX;
+ py = collisionSource.y - localClosestY;
+ pz = collisionSource.z - localClosestZ;
+ closestPointDistance = px * normalX + py * normalY + pz * normalZ - plane.node.offset;
+ if (!plane.infront) {
+ closestPointDistance = -closestPointDistance;
+ }
+ }
+ // Если расстояние от ближайшей точки эллипсоида до плоскости больше, чем смещение эллипсоида вдоль нормали плоскости,
+ // то столкновения не произошло и нужно завершить выполнение функции
+ if (closestPointDistance > displacementAlongNormal) {
+ return;
+ }
+ // Если добрались до этого места, значит произошло столкновение с плоскостью. Требуется определить точку столкновения
+ // с ближайшим полигоном, лежащим в этой плоскости
+ if (closestPointDistance <= 0 ) {
+ // Эллипсоид пересекается с плоскостью, ищем проекцию ближайшей точки эллипсоида на плоскость
+ if (plane.infront) {
+ collisionPlanePoint.x = px - normalX * closestPointDistance;
+ collisionPlanePoint.y = py - normalY * closestPointDistance;
+ collisionPlanePoint.z = pz - normalZ * closestPointDistance;
+ } else {
+ collisionPlanePoint.x = px + normalX * closestPointDistance;
+ collisionPlanePoint.y = py + normalY * closestPointDistance;
+ collisionPlanePoint.z = pz + normalZ * closestPointDistance;
+ }
+ } else {
+ // Эллипсоид не пересекается с плоскостью, ищем точку контакта
+ var t:Number = closestPointDistance / displacementAlongNormal;
+ collisionPlanePoint.x = px + currentDisplacement.x * t;
+ collisionPlanePoint.y = py + currentDisplacement.y * t;
+ collisionPlanePoint.z = pz + currentDisplacement.z * t;
+ }
+ // Проверяем примитивы плоскости
+ var primitive:*;
+ collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE;
+ collisionPrimitiveNearest = null;
+ if (plane.infront) {
+ if ((primitive = plane.node.primitive) != null) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveE(primitive);
+ }
+ } else {
+ for (primitive in plane.node.frontPrimitives) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveE(primitive);
+ if (collisionPrimitive != null) {
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ for (primitive in plane.node.backPrimitives) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveE(primitive);
+ if (collisionPrimitive != null) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (collisionPrimitive != null) {
+ // Если точка пересечения попала в примитив
+ // Нормаль плоскости при столкновении - нормаль плоскости примитива
+ if (plane.infront) {
+ collisionNormal.x = normalX;
+ collisionNormal.y = normalY;
+ collisionNormal.z = normalZ;
+ collisionOffset = plane.node.offset;
+ } else {
+ collisionNormal.x = -normalX;
+ collisionNormal.y = -normalY;
+ collisionNormal.z = -normalZ;
+ collisionOffset = -plane.node.offset;
+ }
+ // Радиус эллипсоида в точке столкновения
+ collisionRadius = localClosestX * collisionNormal.x + localClosestY * collisionNormal.y + localClosestZ * collisionNormal.z;
+ if (collisionRadius < 0) {
+ collisionRadius = -collisionRadius;
+ }
+ radiusVector.x = px - collisionSource.x;
+ radiusVector.y = py - collisionSource.y;
+ radiusVector.z = pz - collisionSource.z;
+ // Точка столкновения совпадает с точкой столкновения с плоскостью примитива
+ collisionPoint.x = collisionPlanePoint.x;
+ collisionPoint.y = collisionPlanePoint.y;
+ collisionPoint.z = collisionPlanePoint.z;
+ } else {
+ // Если точка пересечения не попала внутрь примитива, находим пересечение с ближайшей точкой ближайшего примитива
+ // Трансформированная в пространство эллипсоида ближайшая точка на примитиве
+ px = collisionPrimitivePoint.x;
+ py = collisionPrimitivePoint.y;
+ pz = collisionPrimitivePoint.z;
+
+ var collisionExists:Boolean;
+ // Квадрат расстояния из центра эллипсоида до точки примитива
+ var r2:Number = px*px + py*py + pz*pz;
+ if (r2 < _radius2) {
+ // Точка оказалась внутри эллипсоида, находим точку на поверхности эллипсоида, лежащую на том же радиусе
+ k = _radius / Math.sqrt(r2);
+ px *= k * _scaleX;
+ py *= k * _scaleY;
+ pz *= k * _scaleZ;
+
+ collisionExists = true;
+ } else {
+ // Точка вне эллипсоида, находим пересечение луча, направленного противоположно скорости эллипсоида из точки
+ // примитива, с поверхностью эллипсоида
+ // Трансформированный в пространство эллипсоида противоположный вектор скорости
+ var vx:Number = - currentDisplacement.x / _scaleX;
+ var vy:Number = - currentDisplacement.y / _scaleY;
+ var vz:Number = - currentDisplacement.z / _scaleZ;
+ // Нахождение точки пересечения сферы и луча, направленного вдоль вектора скорости
+ var a:Number = vx*vx + vy*vy + vz*vz;
+ var b:Number = 2 * (px*vx + py*vy + pz*vz);
+ var c:Number = r2 - _radius2;
+ var d:Number = b*b - 4*a*c;
+ // Решение есть только при действительном дискриминанте квадратного уравнения
+ if (d >=0) {
+ // Выбирается минимальное время, т.к. нужна первая точка пересечения
+ t = -0.5 * (b + Math.sqrt(d)) / a;
+ // Точка лежит на луче только если время положительное
+ if (t >= 0 && t <= 1) {
+ // Координаты точки пересечения луча с эллипсоидом, переведённые обратно в нормальное пространство
+ px = (px + t * vx) * _scaleX;
+ py = (py + t * vy) * _scaleY;
+ pz = (pz + t * vz) * _scaleZ;
+
+ collisionExists = true;
+ }
+ }
+ }
+ if (collisionExists) {
+ // Противоположная нормаль к эллипсоиду в точке пересечения
+ collisionNormal.x = - px / _scaleX2;
+ collisionNormal.y = - py / _scaleY2;
+ collisionNormal.z = - pz / _scaleZ2;
+ collisionNormal.normalize();
+ // Радиус эллипсоида в точке столкновения
+ collisionRadius = px * collisionNormal.x + py * collisionNormal.y + pz * collisionNormal.z;
+ if (collisionRadius < 0) {
+ collisionRadius = -collisionRadius;
+ }
+ radiusVector.x = px;
+ radiusVector.y = py;
+ radiusVector.z = pz;
+ // Точка столкновения в ближайшей точке
+ collisionPoint.x = collisionPrimitivePoint.x * _scaleX + currentCoords.x;
+ collisionPoint.y = collisionPrimitivePoint.y * _scaleY + currentCoords.y;
+ collisionPoint.z = collisionPrimitivePoint.z * _scaleZ + currentCoords.z;
+ // Смещение плоскости столкновения
+ collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z;
+ collisionPrimitive = collisionPrimitiveNearest;
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Определение наличия столкновения эллипсоида с примитивом. Все расчёты выполняются в пространстве эллипсоида, где он выглядит
+ * как сфера. По окончании работы может быть установлена переменная collisionPrimitive в случае попадания точки
+ * столкновения внутрь примитива или collisionPrimitiveNearest в случае столкновения с ребром примитива через
+ * минимальное время.
+ *
+ * @param primitive примитив, столкновение с которым проверяется
+ */
+ private function calculateCollisionWithPrimitiveE(primitive:PolyPrimitive):void {
+ var length:uint = primitive.num;
+ var points:Array = primitive.points;
+ var normal:Point3D = primitive.face.globalNormal;
+ var inside:Boolean = true;
+
+ var point1:Point3D;
+ var point2:Point3D = points[length - 1];
+ p2.x = (point2.x - currentCoords.x) / _scaleX;
+ p2.y = (point2.y - currentCoords.y) / _scaleY;
+ p2.z = (point2.z - currentCoords.z) / _scaleZ;
+
+ localCollisionPlanePoint.x = (collisionPlanePoint.x - currentCoords.x) / _scaleX;
+ localCollisionPlanePoint.y = (collisionPlanePoint.y - currentCoords.y) / _scaleY;
+ localCollisionPlanePoint.z = (collisionPlanePoint.z - currentCoords.z) / _scaleZ;
+ // Обход всех рёбер примитива
+ for (var i:uint = 0; i < length; i++) {
+ point1 = point2;
+ point2 = points[i];
+
+ p1.x = p2.x;
+ p1.y = p2.y;
+ p1.z = p2.z;
+
+ p2.x = (point2.x - currentCoords.x) / _scaleX;
+ p2.y = (point2.y - currentCoords.y) / _scaleY;
+ p2.z = (point2.z - currentCoords.z) / _scaleZ;
+
+ // Расчёт векторного произведения вектора ребра на радиус-вектор точки столкновения относительно начала ребра
+ // с целью определения положения точки столкновения относительно полигона
+ var edgeX:Number = p2.x - p1.x;
+ var edgeY:Number = p2.y - p1.y;
+ var edgeZ:Number = p2.z - p1.z;
+
+ var vectorX:Number = localCollisionPlanePoint.x - p1.x;
+ var vectorY:Number = localCollisionPlanePoint.y - p1.y;
+ var vectorZ:Number = localCollisionPlanePoint.z - p1.z;
+
+ var crossX:Number = vectorY * edgeZ - vectorZ * edgeY;
+ var crossY:Number = vectorZ * edgeX - vectorX * edgeZ;
+ var crossZ:Number = vectorX * edgeY - vectorY * edgeX;
+
+ if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) {
+ // Точка за пределами полигона
+ inside = false;
+
+ var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ;
+ var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr;
+
+ // Если расстояние до прямой меньше текущего ближайшего
+ if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) {
+ // Ищем нормализованный вектор ребра
+ var edgeLength:Number = Math.sqrt(edgeLengthSqr);
+ var edgeNormX:Number = edgeX / edgeLength;
+ var edgeNormY:Number = edgeY / edgeLength;
+ var edgeNormZ:Number = edgeZ / edgeLength;
+ // Находим расстояние до точки перпендикуляра вдоль ребра
+ var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ;
+ var vectorLengthSqr:Number;
+ if (t < 0) {
+ // Ближайшая точка - первая
+ vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
+ if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
+ collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
+ collisionPrimitivePoint.x = p1.x;
+ collisionPrimitivePoint.y = p1.y;
+ collisionPrimitivePoint.z = p1.z;
+ collisionPrimitiveNearest = primitive;
+ }
+ } else {
+ if (t > edgeLength) {
+ // Ближайшая точка - вторая
+ vectorX = localCollisionPlanePoint.x - p2.x;
+ vectorY = localCollisionPlanePoint.y - p2.y;
+ vectorZ = localCollisionPlanePoint.z - p2.z;
+ vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
+ if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
+ collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
+ collisionPrimitivePoint.x = p2.x;
+ collisionPrimitivePoint.y = p2.y;
+ collisionPrimitivePoint.z = p2.z;
+ collisionPrimitiveNearest = primitive;
+ }
+ } else {
+ // Ближайшая точка на ребре
+ collisionPrimitiveNearestLengthSqr = edgeDistanceSqr;
+ collisionPrimitivePoint.x = p1.x + edgeNormX * t;
+ collisionPrimitivePoint.y = p1.y + edgeNormY * t;
+ collisionPrimitivePoint.z = p1.z + edgeNormZ * t;
+ collisionPrimitiveNearest = primitive;
+ }
+ }
+ }
+ }
+ }
+
+ // Если попали в примитив
+ if (inside) {
+ collisionPrimitive = primitive;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/primitives/Box.as b/Alternativa3D5/5.5/alternativa/engine3d/primitives/Box.as
new file mode 100644
index 0000000..ec4459b
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/primitives/Box.as
@@ -0,0 +1,316 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Прямоугольный параллелепипед.
+ * "front", "back", "left",
+ * "right", "top", "bottom", на каждую из которых может быть установлен свой материал.
+ * true, то нормали будут направлены внутрь фигуры.
+ * @param triangulate флаг триангуляции. Если указано значение true, четырехугольники в параллелепипеде будут триангулированы.
+ */
+ public function Box(width:Number = 100, length:Number = 100, height:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false, triangulate:Boolean = false) {
+ super();
+
+ if ((widthSegments == 0) || (lengthSegments == 0) || (heightSegments == 0)) {
+ return;
+ }
+ width = (width < 0)? 0 : width;
+ length = (length < 0)? 0 : length;
+ height = (height < 0)? 0 : height;
+
+ var wh:Number = width/2;
+ var lh:Number = length/2;
+ var hh:Number = height/2;
+ var ws:Number = width/widthSegments;
+ var ls:Number = length/lengthSegments;
+ var hs:Number = height/heightSegments;
+ var x:int;
+ var y:int;
+ var z:int;
+
+ // Создание точек
+ for (x = 0; x <= widthSegments; x++) {
+ for (y = 0; y <= lengthSegments; y++) {
+ for (z = 0; z <= heightSegments; z++) {
+ if (x == 0 || x == widthSegments || y == 0 || y == lengthSegments || z == 0 || z == heightSegments) {
+ createVertex(x*ws - wh, y*ls - lh, z*hs - hh, x + "_" + y + "_" + z);
+ }
+ }
+ }
+ }
+
+ // Создание поверхностей
+ var front:Surface = createSurface(null, "front");
+ var back:Surface = createSurface(null, "back");
+ var left:Surface = createSurface(null, "left");
+ var right:Surface = createSurface(null, "right");
+ var top:Surface = createSurface(null, "top");
+ var bottom:Surface = createSurface(null, "bottom");
+
+ // Создание граней
+ var wd:Number = 1/widthSegments;
+ var ld:Number = 1/lengthSegments;
+ var hd:Number = 1/heightSegments;
+ var faceId:String;
+
+ // Для оптимизаций UV при триангуляции
+ var aUV:Point;
+ var cUV:Point;
+
+ // Построение верхней грани
+ for (y = 0; y < lengthSegments; y++) {
+ for (x = 0; x < widthSegments; x++) {
+ faceId = "top_"+x+"_"+y;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point(x*wd, (lengthSegments - y)*ld);
+ cUV = new Point((x + 1)*wd, (lengthSegments - y - 1)*ld);
+ createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":1");
+ setUVsToFace(aUV, new Point(x*wd, (lengthSegments - y - 1)*ld), cUV, faceId + ":1");
+ createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":0");
+ setUVsToFace(cUV, new Point((x + 1)*wd, (lengthSegments - y)*ld), aUV, faceId + ":0");
+ } else {
+ createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments], faceId);
+ setUVsToFace(new Point(x*wd, (lengthSegments - y)*ld), new Point(x*wd, (lengthSegments - y - 1)*ld), new Point((x + 1)*wd, (lengthSegments - y - 1)*ld), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point(x*wd, y*ld);
+ cUV = new Point((x + 1)*wd, (y + 1)*ld);
+ createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":0");
+ setUVsToFace(aUV, new Point((x + 1)*wd, y*ld), cUV, faceId + ":0");
+ createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":1");
+ setUVsToFace(cUV, new Point(x*wd, (y + 1)*ld), aUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments], faceId);
+ setUVsToFace(new Point(x*wd, y*ld), new Point((x + 1)*wd, y*ld), new Point((x + 1)*wd, (y + 1)*ld), faceId);
+ }
+ }
+ if (triangulate) {
+ top.addFace(faceId + ":0");
+ top.addFace(faceId + ":1");
+ } else {
+ top.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение нижней грани
+ for (y = 0; y < lengthSegments; y++) {
+ for (x = 0; x < widthSegments; x++) {
+ faceId = "bottom_" + x + "_" + y;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point((widthSegments - x)*wd, (lengthSegments - y)*ld);
+ cUV = new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld);
+ createFace([x + "_" + y + "_" + 0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":0");
+ setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), cUV, faceId + ":0");
+ createFace([(x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0, x + "_" + y + "_" + 0], faceId + ":1");
+ setUVsToFace(cUV, new Point((widthSegments - x)*wd, (lengthSegments - y - 1)*ld), aUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + y + "_"+0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0], faceId);
+ setUVsToFace(new Point((widthSegments - x)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point((widthSegments - x)*wd, y*ld);
+ cUV = new Point((widthSegments - x - 1)*wd, (y + 1)*ld);
+ createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":1");
+ setUVsToFace(aUV, new Point((widthSegments - x)*wd, (y + 1)*ld), cUV, faceId + ":1");
+ createFace([(x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0, x + "_" + y + "_" + 0], faceId + ":0");
+ setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, y*ld), aUV, faceId + ":0");
+ } else {
+ createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) +"_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0], faceId);
+ setUVsToFace(new Point((widthSegments - x)*wd, y*ld), new Point((widthSegments - x)*wd, (y + 1)*ld), new Point((widthSegments - x - 1)*wd, (y + 1)*ld), faceId);
+ }
+ }
+ if (triangulate) {
+ bottom.addFace(faceId + ":0");
+ bottom.addFace(faceId + ":1");
+ } else {
+ bottom.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение фронтальной грани
+ for (z = 0; z < heightSegments; z++) {
+ for (x = 0; x < widthSegments; x++) {
+ faceId = "front_"+x+"_"+z;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point((widthSegments - x)*wd, z*hd);
+ cUV = new Point((widthSegments - x - 1)*wd, (z + 1)*hd);
+ createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":1");
+ setUVsToFace(aUV, new Point((widthSegments - x)*wd, (z + 1)*hd), cUV, faceId + ":1");
+ createFace([(x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z, x + "_" + 0 + "_" + z], faceId + ":0");
+ setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, z*hd), aUV, faceId + ":0");
+ } else {
+ createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z], faceId);
+ setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point(x*wd, z*hd);
+ cUV = new Point((x + 1)*wd, (z + 1)*hd);
+ createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":0");
+ setUVsToFace(aUV, new Point((x + 1)*wd, z*hd), cUV, faceId + ":0");
+ createFace([(x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + z], faceId + ":1");
+ setUVsToFace(cUV, new Point(x*wd, (z + 1)*hd), aUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1)], faceId);
+ setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId);
+ }
+ }
+ if (triangulate) {
+ front.addFace(faceId + ":0");
+ front.addFace(faceId + ":1");
+ } else {
+ front.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение задней грани
+ for (z = 0; z < heightSegments; z++) {
+ for (x = 0; x < widthSegments; x++) {
+ faceId = "back_"+x+"_"+z;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point(x * wd, (z + 1) * hd);
+ cUV = new Point((x + 1) * wd, z * hd);
+ createFace([x + "_" + lengthSegments+"_" + (z + 1), x + "_"+lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0");
+ setUVsToFace(aUV, new Point(x * wd, z * hd), cUV, faceId + ":0");
+ createFace([(x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId + ":1");
+ setUVsToFace(cUV, new Point((x + 1) * wd, (z + 1) * hd), aUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId);
+ setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point((widthSegments - x)*wd, (z + 1)*hd);
+ cUV = new Point((widthSegments - x - 1)*wd, z*hd);
+ createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0");
+ setUVsToFace(new Point((widthSegments - x)*wd, z*hd), aUV, cUV, faceId + ":0");
+ createFace([x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":1");
+ setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (z + 1)*hd), cUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId);
+ setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId);
+ }
+ }
+ if (triangulate) {
+ back.addFace(faceId + ":0");
+ back.addFace(faceId + ":1");
+ } else {
+ back.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение левой грани
+ for (y = 0; y < lengthSegments; y++) {
+ for (z = 0; z < heightSegments; z++) {
+ faceId = "left_" + y + "_" + z;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point(y*ld, (z + 1)*hd);
+ cUV = new Point((y + 1)*ld, z*hd);
+ createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z], faceId + ":0");
+ setUVsToFace(aUV, new Point(y*ld, z*hd), cUV, faceId + ":0");
+ createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId + ":1");
+ setUVsToFace(cUV, new Point((y + 1)*ld, (z + 1)*hd), aUV, faceId + ":1");
+ } else {
+ createFace([0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId);
+ setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point((lengthSegments - y - 1)*ld, z*hd);
+ cUV = new Point((lengthSegments - y)*ld, (z + 1)*hd);
+ createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1)], faceId + ":0");
+ setUVsToFace(aUV, new Point((lengthSegments - y)*ld, z*hd), cUV, faceId + ":0");
+ createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + (y + 1) + "_" + z], faceId + ":1");
+ setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), aUV, faceId + ":1");
+ } else {
+ createFace([0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + z], faceId);
+ setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId);
+ }
+ }
+ if (triangulate) {
+ left.addFace(faceId + ":0");
+ left.addFace(faceId + ":1");
+ } else {
+ left.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение правой грани
+ for (y = 0; y < lengthSegments; y++) {
+ for (z = 0; z < heightSegments; z++) {
+ faceId = "right_" + y + "_" + z;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point((lengthSegments - y)*ld, z*hd);
+ cUV = new Point((lengthSegments - y - 1)*ld, (z + 1)*hd);
+ createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":1");
+ setUVsToFace(aUV, new Point((lengthSegments - y)*ld, (z + 1)*hd), cUV, faceId + ":1");
+ createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + y + "_" + z], faceId + ":0");
+ setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, z*hd), aUV, faceId + ":0");
+ } else {
+ createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z], faceId);
+ setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point(y*ld, z*hd);
+ cUV = new Point((y + 1)*ld, (z + 1)*hd);
+ createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":0");
+ setUVsToFace(aUV, new Point((y + 1)*ld, z*hd), cUV, faceId + ":0");
+ createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + y + "_" + z], faceId + ":1");
+ setUVsToFace(cUV, new Point(y*ld, (z + 1)*hd), aUV, faceId + ":1");
+ } else {
+ createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1)], faceId);
+ setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId);
+ }
+ }
+ if (triangulate) {
+ right.addFace(faceId + ":0");
+ right.addFace(faceId + ":1");
+ } else {
+ right.addFace(faceId);
+ }
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Box(0, 0, 0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/primitives/Cone.as b/Alternativa3D5/5.5/alternativa/engine3d/primitives/Cone.as
new file mode 100644
index 0000000..44fe0c2
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/primitives/Cone.as
@@ -0,0 +1,264 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.utils.MathUtils;
+ import alternativa.engine3d.core.Face;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Усеченный конус или цилиндр.
+ */
+ public class Cone extends Mesh {
+
+ /**
+ * Создает усеченный конус или цилиндр.
+ * topRadius = 0 или bottomRadius = 0 будет построен конус. При установленном triangulate установлен в false и на примитив не может быть наложена текстура.
+ * Только при установленном параметре triangulate в true это возможно."side".
+ * При установленном параметре bottomRadius не равном нулю в примитиве создается поверхность "bottom",
+ * при установленном параметре topRadius в примитиве создается поверхность "top".
+ * На каждую из поверхностей может быть наложен свой материалtrue нормли будут направлены внутрь примитива.
+ * @param triangulate флаг триангуляции. При значении true все четырехугольные грани примитива будут триангулированы
+ * и появится возможность наложить на примитив текстуру.
+ */
+ public function Cone(height:Number = 100, bottomRadius:Number = 100, topRadius:Number = 0, heightSegments:uint = 1, radialSegments:uint = 12, reverse:Boolean = false, triangulate:Boolean = false) {
+
+ if ((radialSegments < 3) || (heightSegments < 1) || (heightSegments == 1 && topRadius == 0 && bottomRadius == 0)) {
+ return;
+ }
+ height = (height < 0)? 0 : height;
+ bottomRadius = (bottomRadius < 0)? 0 : bottomRadius;
+ topRadius = (topRadius < 0)? 0 : topRadius;
+
+ const radialSegment:Number = MathUtils.DEG360/radialSegments;
+ const radiusSegment:Number = (bottomRadius - topRadius)/heightSegments;
+ const heightSegment:Number = height/heightSegments;
+ const halfHeight:Number = height*0.5
+ const uSegment:Number = 1/radialSegments;
+ const vSegment:Number = 1/heightSegments;
+
+ // Создание вершин
+ if (topRadius == 0 || triangulate) {
+ var poleUp:Vertex = createVertex(0, 0, halfHeight, "poleUp");
+ }
+ if (bottomRadius == 0 || triangulate) {
+ var poleDown:Vertex = createVertex(0, 0, -halfHeight, "poleDown");
+ }
+
+ var radial:uint;
+ var segment:uint;
+
+ var topSegment:uint = heightSegments - int(topRadius == 0);
+ var bottomSegment:uint = int(bottomRadius == 0) ;
+ for (segment = bottomSegment; segment <= topSegment; segment++) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ var currentAngle:Number = radialSegment*radial;
+ var currentRadius:Number = bottomRadius - (radiusSegment*segment);
+ createVertex(Math.cos(currentAngle)*currentRadius, Math.sin(currentAngle)*currentRadius, heightSegment*segment - halfHeight, radial + "_" + segment);
+ }
+ }
+
+ // Создание граней и поверхности
+ var face:Face;
+
+ var points:Array;
+
+ var side:Surface = createSurface(null, "side");
+
+ if (topRadius == 0) {
+ // Создание граней у верхнего полюса
+ var prevRadial:uint = radialSegments - 1;
+ var centerUV:Point = new Point(0.5, 1);
+ var v:Number = topSegment*vSegment;
+ if (reverse) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], prevRadial + "_" + topSegment);
+ if (triangulate) {
+ setUVsToFace(centerUV, new Point(1 - (prevRadial + 1)*uSegment, v) , new Point(1 - prevRadial*uSegment, v), face);
+ }
+ side.addFace(face);
+ prevRadial = radial;
+ }
+ } else {
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], prevRadial + "_" + topSegment);
+ if (triangulate) {
+ setUVsToFace(centerUV, new Point(prevRadial*uSegment, v), new Point((prevRadial + 1)*uSegment, v), face);
+ }
+ side.addFace(face);
+ prevRadial = radial;
+ }
+ }
+ } else {
+ // Создание граней верхней крышки
+ var top:Surface = createSurface(null, "top");
+ if (triangulate) {
+ prevRadial = radialSegments - 1;
+ centerUV = new Point(0.5, 0.5);
+ var UV:Point;
+ var prevUV:Point;
+ if (reverse) {
+ prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5);
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], "top_" + prevRadial);
+ currentAngle = radial * radialSegment;
+ UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5);
+ setUVsToFace(centerUV, UV, prevUV, face);
+ top.addFace(face);
+ prevUV = UV;
+ prevRadial = radial;
+ }
+ } else {
+ prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5);
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], "top_" + prevRadial);
+ currentAngle = radial*radialSegment;
+ UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5);
+ setUVsToFace(centerUV, prevUV, UV, face);
+ top.addFace(face);
+ prevUV = UV;
+ prevRadial = radial;
+ }
+ }
+ } else {
+ points = new Array();
+ if (reverse) {
+ for (radial = (radialSegments - 1); radial < uint(-1); radial--) {
+ points.push(radial + "_" + topSegment);
+ }
+ } else {
+ for (radial = 0; radial < radialSegments; radial++) {
+ points.push(radial + "_" + topSegment);
+ }
+ }
+ top.addFace(createFace(points, "top"));
+ }
+ }
+ // Создание боковых граней
+ var face2:Face;
+ var aUV:Point;
+ var cUV:Point;
+ for (segment = bottomSegment; segment < topSegment; segment++) {
+ prevRadial = radialSegments - 1;
+ v = segment * vSegment;
+ for (radial = 0; radial < radialSegments; radial++) {
+ if (triangulate) {
+ if (reverse) {
+ face = createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":0");
+ face2 = createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1");
+ aUV = new Point(1 - (prevRadial + 1)*uSegment, v + vSegment)
+ cUV = new Point(1 - prevRadial*uSegment, v);
+ setUVsToFace(aUV, new Point(1 - (prevRadial + 1)*uSegment, v), cUV, face);
+ setUVsToFace(cUV, new Point(1 - prevRadial*uSegment, v + vSegment), aUV, face2);
+ } else {
+ face = createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0");
+ face2 = createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":1");
+ aUV = new Point(prevRadial*uSegment, v)
+ cUV = new Point((prevRadial + 1)*uSegment, v + vSegment);
+ setUVsToFace(aUV, new Point((prevRadial + 1)*uSegment, v), cUV, face);
+ setUVsToFace(cUV, new Point(prevRadial*uSegment, v + vSegment), aUV, face2);
+ }
+ side.addFace(face);
+ side.addFace(face2);
+ } else {
+ if (reverse) {
+ side.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment));
+ } else {
+ side.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1)], prevRadial + "_" + segment));
+ }
+ }
+ prevRadial = radial;
+ }
+ }
+
+ if (bottomRadius == 0) {
+ // Создание граней у нижнего полюса
+ prevRadial = radialSegments - 1;
+ centerUV = new Point(0.5, 0);
+ v = bottomSegment*vSegment;
+ if (reverse) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], prevRadial + "_0");
+ if (triangulate) {
+ setUVsToFace(centerUV, new Point(1 - prevRadial*uSegment, v), new Point(1 - (prevRadial + 1)*uSegment, v), face);
+ }
+ side.addFace(face);
+ prevRadial = radial;
+ }
+ } else {
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], prevRadial + "_0");
+ if (triangulate) {
+ setUVsToFace(centerUV, new Point((prevRadial + 1)*uSegment, v), new Point(prevRadial*uSegment, v), face);
+ }
+ side.addFace(face);
+ prevRadial = radial;
+ }
+ }
+ } else {
+ // Создание граней нижней крышки
+ var bottom:Surface = createSurface(null, "bottom");
+ if (triangulate) {
+ prevRadial = radialSegments - 1;
+ centerUV = new Point(0.5, 0.5);
+ if (reverse) {
+ prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5);
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], "bottom_" + prevRadial);
+ currentAngle = radial*radialSegment;
+ UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5);
+ setUVsToFace(centerUV, prevUV, UV, face);
+ bottom.addFace(face);
+ prevUV = UV;
+ prevRadial = radial;
+ }
+ } else {
+ prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5);
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], "bottom_" + prevRadial);
+ currentAngle = radial * radialSegment;
+ UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5);
+ setUVsToFace(centerUV, UV, prevUV, face);
+ bottom.addFace(face);
+ prevUV = UV;
+ prevRadial = radial;
+ }
+ }
+ } else {
+ points = new Array();
+ if (reverse) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ points.push(radial + "_" + bottomSegment);
+ }
+ } else {
+ for (radial = (radialSegments - 1); radial < uint(-1); radial--) {
+ points.push(radial + "_" + bottomSegment);
+ }
+ }
+ bottom.addFace(createFace(points, "bottom"));
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Cone(0, 0, 0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/primitives/GeoPlane.as b/Alternativa3D5/5.5/alternativa/engine3d/primitives/GeoPlane.as
new file mode 100644
index 0000000..ea681b4
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/primitives/GeoPlane.as
@@ -0,0 +1,197 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Геоплоскость.
+ */
+ public class GeoPlane extends Mesh {
+
+ /**
+ * Создает геоплоскость.
+ * reverse установленном в true примитив будет содержать грань - "back".
+ * При значении reverse установленном в false примитив будет содержать грань - "front".
+ * Параметр twoSided указывает методу создать обе поверхности.true, то создаётся двусторонняя поверхность
+ * @param reverse флаг инвертирования нормалей
+ */
+ public function GeoPlane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false) {
+ super();
+
+ if ((widthSegments == 0) || (lengthSegments == 0)) {
+ return;
+ }
+ width = (width < 0)? 0 : width;
+ length = (length < 0)? 0 : length;
+
+ // Середина
+ var wh:Number = width/2;
+ var hh:Number = length/2;
+ // Размеры сегмента
+ var ws:Number = width/widthSegments;
+ var hs:Number = length/lengthSegments;
+
+ // Размеры UV-сегмента
+ var us:Number = 1/widthSegments;
+ var vs:Number = 1/lengthSegments;
+
+ // Создание точек
+ var x:uint;
+ var y:uint;
+ var frontUV:Array = new Array();
+ var backUV:Array = ((lengthSegments & 1) == 0) ? null : new Array();
+ for (y = 0; y <= lengthSegments; y++) {
+ frontUV[y] = new Array();
+ if (backUV != null) {
+ backUV[y] = new Array();
+ }
+ for (x = 0; x <= widthSegments; x++) {
+ if ((y & 1) == 0) {
+ // Если чётный ряд
+ createVertex(x*ws - wh, y*hs - hh, 0, y + "_" + x);
+ frontUV[y][x] = new Point(x*us, y*vs);
+ if (backUV != null) {
+ backUV[y][x] = new Point(x*us, 1 - y*vs);
+ }
+ } else {
+ // Если нечётный ряд
+ if (x == 0) {
+ // Первая точка
+ createVertex(-wh, y*hs - hh, 0, y + "_" + x);
+ frontUV[y][x] = new Point(0, y*vs);
+ if (backUV != null) {
+ backUV[y][x] = new Point(0, 1 - y*vs);
+ }
+ } else {
+ createVertex(x*ws - wh - ws/2, y*hs - hh, 0, y + "_" + x);
+ frontUV[y][x] = new Point((x - 0.5)*us, y*vs);
+ if (backUV != null) {
+ backUV[y][x] = new Point((x - 0.5)*us, 1 - y*vs);
+ }
+ if (x == widthSegments) {
+ // Последняя точка
+ createVertex(wh, y*hs - hh, 0, y + "_" + (x + 1));
+ frontUV[y][x + 1] = new Point(1, y*vs);
+ if (backUV != null) {
+ backUV[y][x + 1] = new Point(1, 1 - y*vs);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Создание поверхностей
+ var front:Surface;
+ var back:Surface;
+
+ if (twoSided || !reverse) {
+ front = createSurface(null, "front");
+ }
+ if (twoSided || reverse) {
+ back = createSurface(null, "back");
+ }
+
+ // Создание полигонов
+ var face:Face;
+ for (y = 0; y < lengthSegments; y++) {
+ for (var n:uint = 0; n <= (widthSegments << 1); n++) {
+ x = n >> 1;
+ if ((y & 1) == 0) {
+ // Если чётный ряд
+ if ((n & 1) == 0) {
+ // Если остриём вверх
+ if (twoSided || !reverse) {
+ face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]);
+ setUVsToFace(frontUV[y][x], frontUV[y + 1][x + 1], frontUV[y + 1][x], face);
+ front.addFace(face);
+ }
+ if (twoSided || reverse) {
+ face = createFace([y + "_" + x, (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]);
+ if (backUV != null) {
+ setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y + 1][x + 1], face);
+ } else {
+ setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face);
+ }
+ back.addFace(face);
+ }
+ } else {
+ // Если остриём вниз
+ if (twoSided || !reverse) {
+ face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + (x + 1)]);
+ setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x + 1], face);
+ front.addFace(face);
+ }
+ if (twoSided || reverse) {
+ face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), y + "_" + (x + 1)]);
+ if (backUV != null) {
+ setUVsToFace(backUV[y][x], backUV[y + 1][x + 1], backUV[y][x + 1], face);
+ } else {
+ setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x + 1], frontUV[lengthSegments - y][x + 1], face);
+ }
+ back.addFace(face);
+ }
+ }
+ } else {
+ // Если нечётный ряд
+ if ((n & 1) == 0) {
+ // Если остриём вниз
+ if (twoSided || !reverse) {
+ face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + x]);
+ setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x], face);
+ front.addFace(face);
+ }
+ if (twoSided || reverse) {
+ face = createFace([y + "_" + x, (y + 1) + "_" + x, y + "_" + (x + 1)]);
+ if (backUV != null) {
+ setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y][x + 1], face);
+ } else {
+ setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y][x + 1], face);
+ }
+ back.addFace(face);
+ }
+ } else {
+ // Если остриём вверх
+ if (twoSided || !reverse) {
+ face = createFace([y + "_" + (x + 1), (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]);
+ setUVsToFace(frontUV[y][x+1], frontUV[y + 1][x + 1], frontUV[y + 1][x], face);
+ front.addFace(face);
+ }
+ if (twoSided || reverse) {
+ face = createFace([y + "_" + (x + 1), (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]);
+ if (backUV != null) {
+ setUVsToFace(backUV[y][x + 1], backUV[y + 1][x], backUV[y + 1][x + 1], face);
+ } else {
+ setUVsToFace(frontUV[lengthSegments - y][x + 1], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face);
+ }
+ back.addFace(face);
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new GeoPlane(0, 0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/primitives/GeoSphere.as b/Alternativa3D5/5.5/alternativa/engine3d/primitives/GeoSphere.as
new file mode 100644
index 0000000..7ba3bf2
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/primitives/GeoSphere.as
@@ -0,0 +1,321 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.utils.MathUtils;
+ import alternativa.engine3d.core.Face;
+ import flash.geom.Point;
+ import alternativa.types.Point3D;
+
+ use namespace alternativa3d;
+
+ /**
+ * Геосфера.
+ */
+ public class GeoSphere extends Mesh {
+
+ /**
+ * Создает геосферу.
+ * [0, 1],
+ * поэтому для материала с текстурой необходимо устанавливать флаг repeat.
+ *
+ * @param radius радиус геосферы. Не может быть меньше нуля.
+ * @param segments количество сегментов геосферы
+ * @param reverse флаг направления нормалей. При значении true нормали направлены внуть геосферы.
+ */
+ public function GeoSphere(radius:Number = 100, segments:uint = 2, reverse:Boolean = false) {
+ if (segments == 0) {
+ return;
+ }
+ radius = (radius < 0)? 0 : radius;
+
+ const sections:uint = 20;
+
+ //var nfaces:uint = sections*segments*segments;
+ //var nverts:Number = nfaces/2 + 2;
+ var points:Array = new Array();
+
+ var i:uint;
+ var f:uint;
+
+ var theta:Number;
+ var sin:Number;
+ var cos:Number;
+ // z расстояние до нижней и верхней крышки полюса
+ var subz:Number = 4.472136E-001*radius;
+ // радиус на расстоянии subz
+ var subrad:Number = 2*subz;
+ points.push(createVertex(0, 0, radius, "poleUp"));
+ // Создание вершин верхней крышки
+ for (i = 0; i < 5; i++) {
+ theta = MathUtils.DEG360*i/5;
+ sin = Math.sin(theta);
+ cos = Math.cos(theta);
+ points.push(createVertex(subrad*cos, subrad*sin, subz));
+ }
+ // Создание вершин нижней крышки
+ for (i = 0; i < 5; i++) {
+ theta = MathUtils.DEG180*((i << 1) + 1)/5;
+ sin = Math.sin(theta);
+ cos = Math.cos(theta);
+ points.push(createVertex(subrad*cos, subrad*sin, -subz));
+ }
+ points.push(createVertex(0, 0, -radius, "poleDown"));
+
+ for (i = 1; i < 6; i++) {
+ interpolate(0, i, segments, points);
+ }
+ for (i = 1; i < 6; i++) {
+ interpolate(i, i % 5 + 1, segments, points);
+ }
+ for (i = 1; i < 6; i++) {
+ interpolate(i, i + 5, segments, points);
+ }
+ for (i = 1; i < 6; i++) {
+ interpolate(i, (i + 3) % 5 + 6, segments, points);
+ }
+ for (i = 1; i < 6; i++) {
+ interpolate(i + 5, i % 5 + 6, segments, points);
+ }
+ for (i = 6; i < 11; i++) {
+ interpolate(11, i, segments, points);
+ }
+ for (f = 0; f < 5; f++) {
+ for (i = 1; i <= segments - 2; i++) {
+ interpolate(12 + f*(segments - 1) + i, 12 + (f + 1) % 5*(segments - 1) + i, i + 1, points);
+ }
+ }
+ for (f = 0; f < 5; f++) {
+ for (i = 1; i <= segments - 2; i++) {
+ interpolate(12 + (f + 15)*(segments - 1) + i, 12 + (f + 10)*(segments - 1) + i, i + 1, points);
+ }
+ }
+ for (f = 0; f < 5; f++) {
+ for (i = 1; i <= segments - 2; i++) {
+ interpolate(12 + ((f + 1) % 5 + 15)*(segments - 1) + segments - 2 - i, 12 + (f + 10)*(segments - 1) + segments - 2 - i, i + 1, points);
+ }
+ }
+ for (f = 0; f < 5; f++) {
+ for (i = 1; i <= segments - 2; i++) {
+ interpolate(12 + ((f + 1) % 5 + 25)*(segments - 1) + i, 12 + (f + 25)*(segments - 1) + i, i + 1, points);
+ }
+ }
+ // Создание граней
+ var face:Face;
+ var surface:Surface = createSurface();
+ for (f = 0; f < sections; f++) {
+ for (var row:uint = 0; row < segments; row++) {
+ for (var column:uint = 0; column <= row; column++) {
+ var a:uint = findVertices(segments, f, row, column);
+ var b:uint = findVertices(segments, f, row + 1, column);
+ var c:uint = findVertices(segments, f, row + 1, column + 1);
+ var va:Vertex = points[a];
+ var vb:Vertex = points[b];
+ var vc:Vertex = points[c];
+ var aUV:Point;
+ var bUV:Point;
+ var cUV:Point;
+ var coordA:Point3D = va._coords;
+ var coordB:Point3D = vb._coords;
+ var coordC:Point3D = vc._coords;
+
+ if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) {
+ aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) {
+ bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) {
+ cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ // полюс
+ if (a == 0 || a == 11) {
+ aUV.x = bUV.x + (cUV.x - bUV.x)*0.5;
+ }
+ if (b == 0 || b == 11) {
+ bUV.x = aUV.x + (cUV.x - aUV.x)*0.5;
+ }
+ if (c == 0 || c == 11) {
+ cUV.x = aUV.x + (bUV.x - aUV.x)*0.5;
+ }
+
+ if (reverse) {
+ face = createFace([va, vc, vb], (column << 1) + "_" + row + "_" + f);
+ aUV.x = 1 - aUV.x;
+ bUV.x = 1 - bUV.x;
+ cUV.x = 1 - cUV.x;
+ setUVsToFace(aUV, cUV, bUV, face);
+ } else {
+ face = createFace([va, vb, vc], (column << 1) + "_" + row + "_" + f);
+ setUVsToFace(aUV, bUV, cUV, face);
+ }
+ surface.addFace(face);
+ //trace(a + "_" + b + "_" + c);
+ if (column < row) {
+ b = findVertices(segments, f, row, column + 1);
+ var vd:Vertex = points[b];
+ coordB = vd._coords;
+
+ if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) {
+ aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) {
+ bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) {
+ cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (a == 0 || a == 11) {
+ aUV.x = bUV.x + (cUV.x - bUV.x)*0.5;
+ }
+ if (b == 0 || b == 11) {
+ bUV.x = aUV.x + (cUV.x - aUV.x)*0.5;
+ }
+ if (c == 0 || c == 11) {
+ cUV.x = aUV.x + (bUV.x - aUV.x)*0.5;
+ }
+
+ if (reverse) {
+ face = createFace([va, vd, vc], ((column << 1) + 1) + "_" + row + "_" + f);
+ aUV.x = 1 - aUV.x;
+ bUV.x = 1 - bUV.x;
+ cUV.x = 1 - cUV.x;
+ setUVsToFace(aUV, bUV, cUV, face);
+ } else {
+ face = createFace([va, vc, vd], ((column << 1) + 1) + "_" + row + "_" + f);
+ setUVsToFace(aUV, cUV, bUV, face);
+ }
+ surface.addFace(face);
+ }
+ }
+ }
+ }
+ }
+
+/* private function getUVSpherical(point:Point3D, radius:Number = 0, reverse:Boolean = false):Point {
+ if (radius == 0) {
+ radius = point.length;
+ }
+ if (reverse) {
+ var u:Number = 0.5 - Math.atan2(point.y, point.x)/MathUtils.DEG360;
+ } else {
+ u = Math.atan2(point.y, point.x)/MathUtils.DEG360 + 0.5;
+ }
+ return new Point(u, Math.asin(point.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ */
+ private function interpolate(v1:uint, v2:uint, num:uint, points:Array):void {
+ if (num < 2) {
+ return;
+ }
+ var a:Vertex = Vertex(points[v1]);
+ var b:Vertex = Vertex(points[v2]);
+ var cos:Number = (a.x*b.x + a.y*b.y + a.z*b.z)/(a.x*a.x + a.y*a.y + a.z*a.z);
+ cos = (cos < -1) ? -1 : ((cos > 1) ? 1 : cos);
+ var theta:Number = Math.acos(cos);
+ var sin:Number = Math.sin(theta);
+ for (var e:uint = 1; e < num; e++) {
+ var theta1:Number = theta*e/num;
+ var theta2:Number = theta*(num - e)/num;
+ var st1:Number = Math.sin(theta1);
+ var st2:Number = Math.sin(theta2);
+ points.push(createVertex((a.x*st2 + b.x*st1)/sin, (a.y*st2 + b.y*st1)/sin, (a.z*st2 + b.z*st1)/sin));
+ }
+ }
+
+ private function findVertices(segments:uint, section:uint, row:uint, column:uint):uint {
+ if (row == 0) {
+ if (section < 5) {
+ return (0);
+ }
+ if (section > 14) {
+ return (11);
+ }
+ return (section - 4);
+ }
+ if (row == segments && column == 0) {
+ if (section < 5) {
+ return (section + 1);
+ }
+ if (section < 10) {
+ return ((section + 4) % 5 + 6);
+ }
+ if (section < 15) {
+ return ((section + 1) % 5 + 1);
+ }
+ return ((section + 1) % 5 + 6);
+ }
+ if (row == segments && column == segments) {
+ if (section < 5) {
+ return ((section + 1) % 5 + 1);
+ }
+ if (section < 10) {
+ return (section + 1);
+ }
+ if (section < 15) {
+ return (section - 9);
+ }
+ return (section - 9);
+ }
+ if (row == segments) {
+ if (section < 5) {
+ return (12 + (5 + section)*(segments - 1) + column - 1);
+ }
+ if (section < 10) {
+ return (12 + (20 + (section + 4) % 5)*(segments - 1) + column - 1);
+ }
+ if (section < 15) {
+ return (12 + (section - 5)*(segments - 1) + segments - 1 - column);
+ }
+ return (12 + (5 + section)*(segments - 1) + segments - 1 - column);
+ }
+ if (column == 0) {
+ if (section < 5) {
+ return (12 + section*(segments - 1) + row - 1);
+ }
+ if (section < 10) {
+ return (12 + (section % 5 + 15)*(segments - 1) + row - 1);
+ }
+ if (section < 15) {
+ return (12 + ((section + 1) % 5 + 15)*(segments - 1) + segments - 1 - row);
+ }
+ return (12 + ((section + 1) % 5 + 25)*(segments - 1) + row - 1);
+ }
+ if (column == row) {
+ if (section < 5) {
+ return (12 + (section + 1) % 5*(segments - 1) + row - 1);
+ }
+ if (section < 10) {
+ return (12 + (section % 5 + 10)*(segments - 1) + row - 1);
+ }
+ if (section < 15) {
+ return (12 + (section % 5 + 10)*(segments - 1) + segments - row - 1);
+ }
+ return (12 + (section % 5 + 25)*(segments - 1) + row - 1);
+ }
+ return (12 + 30*(segments - 1) + section*(segments - 1)*(segments - 2)/2 + (row - 1)*(row - 2)/2 + column - 1);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new GeoSphere(0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/primitives/Plane.as b/Alternativa3D5/5.5/alternativa/engine3d/primitives/Plane.as
new file mode 100644
index 0000000..d346048
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/primitives/Plane.as
@@ -0,0 +1,117 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Плоскость.
+ */
+ public class Plane extends Mesh {
+
+ /**
+ * Создает плоскость.
+ * reverse установленном в true примитив будет содержать грань - "back".
+ * При значении reverse установленном в false примитив будет содержать грань - "front".
+ * Параметр twoSided указывает методу создать обе поверхности.true, то формируется двусторонняя плоскость
+ * @param reverse инвертирование нормалей
+ * @param triangulate флаг триангуляции. Если указано значение true, четырехугольники в плоскости будут триангулированы.
+ */
+ public function Plane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false, triangulate:Boolean = false) {
+ super();
+
+ if ((widthSegments == 0) || (lengthSegments == 0)) {
+ return;
+ }
+ width = (width < 0)? 0 : width;
+ length = (length < 0)? 0 : length;
+
+ // Середина
+ var wh:Number = width/2;
+ var lh:Number = length/2;
+
+ // Размеры сегмента
+ var ws:Number = width/widthSegments;
+ var ls:Number = length/lengthSegments;
+
+ // Размеры UV-сегмента
+ var wd:Number = 1/widthSegments;
+ var ld:Number = 1/lengthSegments;
+
+ // Создание точек и UV
+ var x:int;
+ var y:int;
+ var uv:Array = new Array();
+ for (y = 0; y <= lengthSegments; y++) {
+ uv[y] = new Array();
+ for (x = 0; x <= widthSegments; x++) {
+ uv[y][x] = new Point(x*wd, y*ld);
+ createVertex(x*ws - wh, y*ls - lh, 0, x+"_"+y);
+ }
+ }
+
+ // Создание поверхностей
+ var front:Surface;
+ var back:Surface;
+
+ if (twoSided || !reverse) {
+ front = createSurface(null, "front");
+ }
+ if (twoSided || reverse) {
+ back = createSurface(null, "back");
+ }
+
+ // Создание полигонов
+ for (y = 0; y < lengthSegments; y++) {
+ for (x = 0; x < widthSegments; x++) {
+ if (twoSided || !reverse) {
+ if (triangulate) {
+ createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1)], "front" + x + "_" + y + ":0");
+ setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y + ":0");
+ createFace([(x + 1) + "_" + (y + 1), x + "_" + (y + 1), x + "_" + y], "front" + x + "_" + y + ":1");
+ setUVsToFace(uv[y + 1][x + 1], uv[y + 1][x], uv[y][x], "front" + x + "_" + y + ":1");
+ front.addFace("front" + x + "_" + y + ":0");
+ front.addFace("front" + x + "_" + y + ":1");
+ } else {
+ createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1), x + "_" + (y + 1)], "front" + x + "_" + y);
+ setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y);
+ front.addFace("front" + x + "_" + y);
+ }
+ }
+ if (twoSided || reverse) {
+ if (triangulate) {
+ createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1)], "back" + x + "_" + y + ":0");
+ setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y + ":0");
+ createFace([(x + 1) + "_" + (y + 1), (x + 1) + "_" + y, x + "_" + y], "back" + x + "_" + y + ":1");
+ setUVsToFace(uv[lengthSegments - y - 1][x + 1], uv[lengthSegments - y][x + 1], uv[lengthSegments - y][x], "back" + x + "_" + y + ":1");
+ back.addFace("back" + x + "_" + y + ":0");
+ back.addFace("back"+x+"_"+y + ":1");
+ } else {
+ createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1), (x + 1) + "_" + y], "back" + x + "_" + y);
+ setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y);
+ back.addFace("back" + x + "_" + y);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Plane(0, 0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/engine3d/primitives/Sphere.as b/Alternativa3D5/5.5/alternativa/engine3d/primitives/Sphere.as
new file mode 100644
index 0000000..994d7f7
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/engine3d/primitives/Sphere.as
@@ -0,0 +1,144 @@
+package alternativa.engine3d.primitives {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.utils.MathUtils;
+ import alternativa.engine3d.core.Face;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Сфера.
+ */
+ public class Sphere extends Mesh {
+
+ /**
+ * Создает сферу.
+ * triangulate установлен в false и на сферу нельзя наложить текстуру.
+ * Только при установленном triangulate в true это возможно.true нормали направлены внутрь сферы.
+ * @param triangulate флаг триангуляции. Если указано значение true, грани будут триангулированы,
+ * и будет возможно наложить на примитив текстуру.
+ */
+ public function Sphere(radius:Number = 100, radialSegments:uint = 8, heightSegments:uint = 8, reverse:Boolean = false, triangulate:Boolean = false) {
+ if ((radialSegments < 3) || (heightSegments < 2)) {
+ return;
+ }
+ radius = (radius < 0)? 0 : radius;
+
+ var poleUp:Vertex = createVertex(0, 0, radius, "poleUp");
+ var poleDown:Vertex = createVertex(0, 0, -radius, "poleDown");
+
+ const radialAngle:Number = MathUtils.DEG360/radialSegments;
+ const heightAngle:Number = MathUtils.DEG360/(heightSegments << 1);
+
+ var radial:uint;
+ var segment:uint;
+
+ // Создание вершин
+ for (segment = 1; segment < heightSegments; segment++) {
+ var currentHeightAngle:Number = heightAngle*segment;
+ var segmentRadius:Number = Math.sin(currentHeightAngle)*radius;
+ var segmentZ:Number = Math.cos(currentHeightAngle)*radius;
+ for (radial = 0; radial < radialSegments; radial++) {
+ var currentRadialAngle:Number = radialAngle*radial;
+ createVertex(-Math.sin(currentRadialAngle)*segmentRadius, Math.cos(currentRadialAngle)*segmentRadius, segmentZ, radial + "_" + segment);
+ }
+ }
+
+ // Создание граней и поверхности
+ var surface:Surface = createSurface();
+
+ var prevRadial:uint = radialSegments - 1;
+ var lastSegmentString:String = "_" + (heightSegments - 1);
+
+ var uStep:Number = 1/radialSegments;
+ var vStep:Number = 1/heightSegments;
+
+ var face:Face;
+
+ // Для триангуляции
+ var aUV:Point;
+ var cUV:Point;
+
+ var u:Number;
+
+ if (reverse) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ // Грани верхнего полюса
+ surface.addFace(createFace([poleUp, radial + "_1", prevRadial + "_1"], prevRadial + "_0"));
+ // Грани нижнего полюса
+ surface.addFace(createFace([radial + lastSegmentString, poleDown, prevRadial + lastSegmentString], prevRadial + lastSegmentString));
+
+ // Если включена триангуляция
+ if (triangulate) {
+ // Триангулируем середину и просчитываем маппинг
+ u = uStep*prevRadial;
+ setUVsToFace(new Point(1 - u, 1), new Point(1 - u - uStep, 1 - vStep), new Point(1 - u, 1 - vStep), prevRadial + "_0");
+ // Грани середки
+ for (segment = 1; segment < (heightSegments - 1); segment++) {
+ aUV = new Point(1 - u - uStep, 1 - (vStep*(segment + 1)));
+ cUV = new Point(1 - u, 1 - vStep*segment);
+ surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":0"));
+ surface.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1"));
+ setUVsToFace(aUV, new Point(1 - u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0");
+ setUVsToFace(cUV, new Point(1 - u - uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1");
+ }
+ setUVsToFace(new Point(1 - u - uStep, vStep), new Point(1 - u, 0), new Point(1 - u, vStep), prevRadial + lastSegmentString);
+
+ } else {
+ // Просто создаем середину
+ // Грани середки
+ for (segment = 1; segment < (heightSegments - 1); segment++) {
+ surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment, radial + "_" + segment], prevRadial + "_" + segment));
+ }
+ }
+ prevRadial = (radial == 0) ? 0 : prevRadial + 1;
+ }
+ } else {
+ for (radial = 0; radial < radialSegments; radial++) {
+ // Грани верхнего полюса
+ surface.addFace(createFace([poleUp, prevRadial + "_1", radial + "_1"], prevRadial + "_0"));
+ // Грани нижнего полюса
+ surface.addFace(createFace([prevRadial + lastSegmentString, poleDown, radial + lastSegmentString], prevRadial + lastSegmentString));
+
+ if (triangulate) {
+ u = uStep*prevRadial;
+ setUVsToFace(new Point(u, 1), new Point(u, 1 - vStep), new Point(u + uStep, 1 - vStep), prevRadial + "_0");
+ // Грани середки
+ for (segment = 1; segment < (heightSegments - 1); segment++) {
+ aUV = new Point(u, 1 - (vStep*segment));
+ cUV = new Point(u + uStep, 1 - vStep * (segment + 1));
+ surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0"));
+ surface.addFace(createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":1"));
+ setUVsToFace(aUV, new Point(u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0");
+ setUVsToFace(cUV, new Point(u + uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1");
+ }
+ setUVsToFace(new Point(u, vStep), new Point(u, 0), new Point(u + uStep, vStep), prevRadial + lastSegmentString);
+ } else {
+ // Грани середки
+ for (segment = 1; segment < (heightSegments - 1); segment++) {
+ surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment));
+ }
+ }
+ prevRadial = (radial == 0) ? 0 : prevRadial + 1;
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Sphere(0, 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.5/alternativa/utils/MeshUtils.as b/Alternativa3D5/5.5/alternativa/utils/MeshUtils.as
new file mode 100644
index 0000000..5def643
--- /dev/null
+++ b/Alternativa3D5/5.5/alternativa/utils/MeshUtils.as
@@ -0,0 +1,834 @@
+package alternativa.utils {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.engine3d.materials.TextureMaterialPrecision;
+ import alternativa.engine3d.materials.WireMaterial;
+ import alternativa.types.*;
+
+ import flash.display.BlendMode;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+ use namespace alternativatypes;
+
+ /**
+ * Утилиты для работы с Mesh-объектами.
+ */
+ public class MeshUtils {
+
+ static private var verticesSort:Array = ["x", "y", "z"];
+ static private var verticesSortOptions:Array = [Array.NUMERIC, Array.NUMERIC, Array.NUMERIC];
+
+ /**
+ * Объединение нескольких Mesh-объектов. Объекты, переданные как аргументы метода, не изменяются.
+ *
+ * @param meshes объединяемые объекты класса alternativa.engine3d.core.Mesh
+ *
+ * @return новый Mesh-объект, содержащий результат объединения переданных Mesh-объектов
+ */
+ static public function uniteMeshes(... meshes):Mesh {
+ var res:Mesh = new Mesh();
+
+ var length:uint = meshes.length;
+ var key:*;
+ var vertex:Vertex;
+ var face:Face;
+ var j:uint;
+ for (var i:uint = 0; i < length; i++) {
+ var mesh:Mesh = meshes[i];
+ var vertices:Map = mesh._vertices.clone();
+ for (key in vertices) {
+ vertex = vertices[key];
+ vertices[key] = res.createVertex(vertex.x, vertex.y, vertex.z);
+ }
+ var faces:Map = mesh._faces.clone();
+ for (key in faces) {
+ face = faces[key];
+ var faceVertices:Array = new Array().concat(face._vertices);
+ for (j = 0; j < face._verticesCount; j++) {
+ vertex = faceVertices[j];
+ faceVertices[j] = vertices[vertex.id];
+ }
+ faces[key] = res.createFace(faceVertices);
+ res.setUVsToFace(face._aUV, face._bUV, face._cUV, faces[key]);
+ }
+ for (key in mesh._surfaces) {
+ var surface:Surface = mesh._surfaces[key];
+ var surfaceFaces:Array = surface._faces.toArray();
+ var numFaces:uint = surfaceFaces.length;
+ for (j = 0; j < numFaces; j++) {
+ face = surfaceFaces[j];
+ surfaceFaces[j] = faces[face.id];
+ }
+ var newSurface:Surface = res.createSurface(surfaceFaces);
+ newSurface.material = SurfaceMaterial(surface.material.clone());
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Слияние вершин Mesh-объекта с одинаковыми координатами. Равенство координат проверяется с учётом погрешности.
+ *
+ * @param mesh объект, вершины которого объединяются
+ * @param threshold погрешность измерения расстояний
+ */
+ static public function autoWeldVertices(mesh:Mesh, threshold:Number = 0):void {
+ // Получаем список вершин меша и сортируем по координатам
+ var vertices:Array = mesh._vertices.toArray(true);
+ vertices.sortOn(verticesSort, verticesSortOptions);
+
+ // Поиск вершин с одинаковыми координатами
+ var weld:Map = new Map(true);
+ var vertex:Vertex;
+ var currentVertex:Vertex = vertices[0];
+ var length:uint = vertices.length;
+ var i:uint;
+ for (i = 1; i < length; i++) {
+ vertex = vertices[i];
+ if ((currentVertex.x - vertex.x <= threshold) && (currentVertex.x - vertex.x >= -threshold) && (currentVertex.y - vertex.y <= threshold) && (currentVertex.y - vertex.y >= -threshold) && (currentVertex.z - vertex.z <= threshold) && (currentVertex.z - vertex.z >= -threshold)) {
+ weld[vertex] = currentVertex;
+ } else {
+ currentVertex = vertex;
+ }
+ }
+
+ // Собираем грани объединяемых вершин
+ var faces:Set = new Set(true);
+ var keyVertex:*;
+ var keyFace:*;
+ for (keyVertex in weld) {
+ vertex = keyVertex;
+ for (keyFace in vertex._faces) {
+ faces[keyFace] = true;
+ }
+ }
+
+ // Заменяем грани
+ for (keyFace in faces) {
+ var face:Face = keyFace;
+ var id:Object = mesh.getFaceId(face);
+ var surface:Surface = face._surface;
+ var aUV:Point = face._aUV;
+ var bUV:Point = face._bUV;
+ var cUV:Point = face._cUV;
+ vertices = new Array().concat(face._vertices);
+ length = vertices.length;
+ for (i = 0; i < length; i++) {
+ vertex = weld[vertices[i]];
+ if (vertex != null) {
+ vertices[i] = vertex;
+ }
+ }
+ mesh.removeFace(face);
+ face = mesh.createFace(vertices, id);
+ if (surface != null) {
+ surface.addFace(face);
+ }
+ face.aUV = aUV;
+ face.bUV = bUV;
+ face.cUV = cUV;
+ }
+
+ // Удаляем вершины
+ for (keyVertex in weld) {
+ mesh.removeVertex(keyVertex);
+ }
+ }
+
+ /**
+ * Объединение соседних граней, образующих плоский выпуклый многоугольник.
+ *
+ * @param mesh объект, грани которого объединяются
+ * @param angleThreshold погрешность измерения углов
+ * @param uvThreshold погрешность измерения UV-координат
+ */
+ static public function autoWeldFaces(mesh:Mesh, angleThreshold:Number = 0, uvThreshold:Number = 0):void {
+ angleThreshold = Math.cos(angleThreshold);
+
+ var face:Face;
+ var sibling:Face;
+ var key:*;
+ var i:uint;
+
+ // Формируем списки граней
+ var faces1:Set = new Set(true);
+ var faces2:Set = new Set(true);
+
+ // Формируем список нормалей
+ var normals:Map = new Map(true);
+ for each (face in mesh._faces.clone()) {
+ var faceNormal:Point3D = face.normal;
+ if (faceNormal.x != 0 || faceNormal.y != 0 || faceNormal.z != 0) {
+ faces1[face] = true;
+ normals[face] = faceNormal;
+ } else {
+ mesh.removeFace(face);
+ }
+ }
+
+ // Объединение
+ do {
+ // Флаг объединения
+ var weld:Boolean = false;
+ // Объединяем грани
+ while ((face = faces1.take()) != null) {
+ //var num:uint = face.num;
+ //var vertices:Array = face.vertices;
+ var currentWeld:Boolean = false;
+
+ // Проверка общих граней по точкам
+
+
+ // Проверка общих граней по рёбрам
+
+ // Перебираем точки грани
+ for (i = 0; (i < face._verticesCount) && !currentWeld; i++) {
+ var faceIndex1:uint = i;
+ var faceIndex2:uint;
+ var siblingIndex1:int;
+ var siblingIndex2:uint;
+
+ // Перебираем грани текущей точки
+ var vertex:Vertex = face._vertices[faceIndex1];
+ var vertexFaces:Set = vertex.faces;
+ for (key in vertexFaces) {
+ sibling = key;
+ // Если грань в списке на объединение и в одной поверхности
+ if (faces1[sibling] && face._surface == sibling._surface) {
+ faceIndex2 = (faceIndex1 < face._verticesCount - 1) ? (faceIndex1 + 1) : 0;
+ siblingIndex1 = sibling._vertices.indexOf(face._vertices[faceIndex2]);
+ // Если общее ребро
+ if (siblingIndex1 >= 0) {
+ // Если грани сонаправлены
+ var normal:Point3D = normals[face];
+ if (Point3D.dot(normal, normals[sibling]) >= angleThreshold) {
+ // Если в точках объединения нет перегибов
+ siblingIndex2 = (siblingIndex1 < sibling._verticesCount - 1) ? (siblingIndex1 + 1) : 0;
+
+ // Расширяем грани объединения
+ var i1:uint;
+ var i2:uint;
+ while (true) {
+ i1 = (faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1);
+ i2 = (siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0;
+ if (face._vertices[i1] == sibling._vertices[i2]) {
+ faceIndex1 = i1;
+ siblingIndex2 = i2;
+ } else {
+ break;
+ }
+ }
+
+ while (true) {
+ i1 = (faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0;
+ i2 = (siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1);
+ if (face._vertices[i1] == sibling._vertices[i2]) {
+ faceIndex2 = i1;
+ siblingIndex1 = i2;
+ } else {
+ break;
+ }
+ }
+
+ vertex = face._vertices[faceIndex1];
+ var a:Point3D = vertex.coords;
+ vertex = face._vertices[faceIndex2];
+ var b:Point3D = vertex.coords;
+
+ // Считаем первый перегиб
+ vertex = sibling._vertices[(siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0];
+ var c:Point3D = vertex.coords;
+ vertex = face._vertices[(faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1)];
+ var d:Point3D = vertex.coords;
+
+ var cx:Number = c.x - a.x;
+ var cy:Number = c.y - a.y;
+ var cz:Number = c.z - a.z;
+ var dx:Number = d.x - a.x;
+ var dy:Number = d.y - a.y;
+ var dz:Number = d.z - a.z;
+
+ var crossX:Number = cy*dz - cz*dy;
+ var crossY:Number = cz*dx - cx*dz;
+ var crossZ:Number = cx*dy - cy*dx;
+
+ if (crossX == 0 && crossY == 0 && crossZ == 0) {
+ if (cx*dx + cy*dy + cz*dz > 0) {
+ break;
+ }
+ }
+
+ var dot:Number = crossX*normal.x + crossY*normal.y + crossZ*normal.z;
+
+ // Если в первой точке перегиба нет
+ if (dot >= 0) {
+
+ // Считаем второй перегиб
+ vertex = face._vertices[(faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0];
+ c = vertex.coords;
+ vertex = sibling._vertices[(siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1)];
+ d = vertex.coords;
+
+ cx = c.x - b.x;
+ cy = c.y - b.y;
+ cz = c.z - b.z;
+ dx = d.x - b.x;
+ dy = d.y - b.y;
+ dz = d.z - b.z;
+
+ crossX = cy*dz - cz*dy;
+ crossY = cz*dx - cx*dz;
+ crossZ = cx*dy - cy*dx;
+
+ if (crossX == 0 && crossY == 0 && crossZ == 0) {
+ if (cx*dx + cy*dy + cz*dz > 0) {
+ break;
+ }
+ }
+
+ dot = crossX*normal.x + crossY*normal.y + crossZ*normal.z;
+
+ // Если во второй точке перегиба нет
+ if (dot >= 0) {
+
+ // Флаг наличия UV у обеих граней
+ var hasUV:Boolean = (face._aUV != null && face._bUV != null && face._cUV != null && sibling._aUV != null && sibling._bUV != null && sibling._cUV != null);
+
+ if (hasUV || (face._aUV == null && face._bUV == null && face._cUV == null && sibling._aUV == null && sibling._bUV == null && sibling._cUV == null)) {
+
+ // Если грани имеют UV, проверяем совместимость
+ if (hasUV) {
+ vertex = sibling._vertices[0];
+ var uv:Point = face.getUVFast(vertex.coords, normal);
+ if ((uv.x - sibling._aUV.x > uvThreshold) || (uv.x - sibling._aUV.x < -uvThreshold) || (uv.y - sibling._aUV.y > uvThreshold) || (uv.y - sibling._aUV.y < -uvThreshold)) {
+ break;
+ }
+
+ vertex = sibling._vertices[1];
+ uv = face.getUVFast(vertex.coords, normal);
+ if ((uv.x - sibling._bUV.x > uvThreshold) || (uv.x - sibling._bUV.x < -uvThreshold) || (uv.y - sibling._bUV.y > uvThreshold) || (uv.y - sibling._bUV.y < -uvThreshold)) {
+ break;
+ }
+
+ vertex = sibling._vertices[2];
+ uv = face.getUVFast(vertex.coords, normal);
+ if ((uv.x - sibling._cUV.x > uvThreshold) || (uv.x - sibling._cUV.x < -uvThreshold) || (uv.y - sibling._cUV.y > uvThreshold) || (uv.y - sibling._cUV.y < -uvThreshold)) {
+ break;
+ }
+ }
+
+ // Формируем новую грань
+ var newVertices:Array = new Array();
+ var n:uint = faceIndex2;
+ do {
+ newVertices.push(face._vertices[n]);
+ n = (n < face._verticesCount - 1) ? (n + 1) : 0;
+ } while (n != faceIndex1);
+ n = siblingIndex2;
+ do {
+ newVertices.push(sibling._vertices[n]);
+ n = (n < sibling._verticesCount - 1) ? (n + 1) : 0;
+ } while (n != siblingIndex1);
+
+ // Выбираем начальную точку
+ n = getBestBeginVertexIndex(newVertices);
+ for (var m:uint = 0; m < n; m++) {
+ newVertices.push(newVertices.shift());
+ }
+
+ // Заменяем грани новой
+ var surface:Surface = face._surface;
+ var newFace:Face = mesh.createFace(newVertices);
+ if (hasUV) {
+ newFace.aUV = face.getUVFast(newVertices[0].coords, normal);
+ newFace.bUV = face.getUVFast(newVertices[1].coords, normal);
+ newFace.cUV = face.getUVFast(newVertices[2].coords, normal);
+ }
+ if (surface != null) {
+ surface.addFace(newFace);
+ }
+ mesh.removeFace(face);
+ mesh.removeFace(sibling);
+
+ // Обновляем список нормалей
+ delete normals[sibling];
+ delete normals[face];
+ normals[newFace] = newFace.normal;
+
+ // Обновляем списки расчётов
+ delete faces1[sibling];
+ faces2[newFace] = true;
+
+ // Помечаем объединение
+ weld = true;
+ currentWeld = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+
+ // Если не удалось объединить, переносим грань
+ faces2[face] = true;
+ }
+
+ // Меняем списки
+ var fs:Set = faces1;
+ faces1 = faces2;
+ faces2 = fs;
+ } while (weld);
+
+ removeIsolatedVertices(mesh);
+ removeUselessVertices(mesh);
+ }
+
+ /**
+ * Удаление вершин объекта, не принадлежащим ни одной грани.
+ *
+ * @param mesh объект, вершины которого удаляются
+ */
+ static public function removeIsolatedVertices(mesh:Mesh):void {
+ for each (var vertex:Vertex in mesh._vertices.clone()) {
+ if (vertex._faces.isEmpty()) {
+ mesh.removeVertex(vertex);
+ }
+ }
+ }
+
+ /**
+ * Удаление вершин объекта, которые во всех своих гранях лежат на отрезке между предыдущей и следующей вершиной.
+ *
+ * @param mesh объект, вершины которого удаляются
+ */
+ static public function removeUselessVertices(mesh:Mesh):void {
+ var v:Vertex;
+ var key:*;
+ var face:Face;
+ var index:uint;
+ var length:uint;
+ for each (var vertex:Vertex in mesh._vertices.clone()) {
+ var unless:Boolean = true;
+ var indexes:Map = new Map(true);
+ for (key in vertex._faces) {
+ face = key;
+ length = face._vertices.length;
+ index = face._vertices.indexOf(vertex);
+ v = face._vertices[index];
+ var a:Point3D = v.coords;
+ v = face._vertices[(index < length - 1) ? (index + 1) : 0];
+ var b:Point3D = v.coords;
+ v = face._vertices[(index > 0) ? (index - 1) : (length - 1)];
+ var c:Point3D = v.coords;
+ var abx:Number = b.x - a.x;
+ var aby:Number = b.y - a.y;
+ var abz:Number = b.z - a.z;
+ var acx:Number = c.x - a.x;
+ var acy:Number = c.y - a.y;
+ var acz:Number = c.z - a.z;
+ if (aby*acz - abz*acy == 0 && abz*acx - abx*acz == 0 && abx*acy - aby*acx == 0) {
+ indexes[face] = index;
+ } else {
+ unless = false;
+ break;
+ }
+ }
+ if (unless && !indexes.isEmpty()) {
+ // Удаляем
+ for (key in indexes) {
+ var i:uint;
+ face = key;
+ index = indexes[face];
+ length = face._vertices.length;
+ var newVertices:Array = new Array();
+ for (i = 0; i < length; i++) {
+ if (i != index) {
+ newVertices.push(face._vertices[i]);
+ }
+ }
+ var n:uint = getBestBeginVertexIndex(newVertices);
+ for (i = 0; i < n; i++) {
+ newVertices.push(newVertices.shift());
+ }
+ var surface:Surface = face._surface;
+ var newFace:Face = mesh.createFace(newVertices);
+ if (face._aUV != null && face._bUV != null && face._cUV != null) {
+ var normal:Point3D = face.normal;
+ newFace.aUV = face.getUVFast(newVertices[0].coords, normal);
+ newFace.bUV = face.getUVFast(newVertices[1].coords, normal);
+ newFace.cUV = face.getUVFast(newVertices[2].coords, normal);
+ }
+ if (surface != null) {
+ surface.addFace(newFace);
+ }
+ mesh.removeFace(face);
+ }
+ mesh.removeVertex(vertex);
+ }
+ }
+ }
+
+ /**
+ * Удаление вырожденных граней.
+ *
+ * @param mesh объект, грани которого удаляются
+ */
+ static public function removeSingularFaces(mesh:Mesh):void {
+ for each (var face:Face in mesh._faces.clone()) {
+ var normal:Point3D = face.normal;
+ if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
+ mesh.removeFace(face);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Находит наиболее подходящую первую точку.
+ * @param vertices
+ * @return
+ */
+ static public function getBestBeginVertexIndex(vertices:Array):uint {
+ var bestIndex:uint = 0;
+ var num:uint = vertices.length;
+ if (num > 3) {
+ var maxCrossLength:Number = 0;
+ var v:Vertex = vertices[num - 1];
+ var c1:Point3D = v.coords;
+ v = vertices[0];
+ var c2:Point3D = v.coords;
+
+ var prevX:Number = c2.x - c1.x;
+ var prevY:Number = c2.y - c1.y;
+ var prevZ:Number = c2.z - c1.z;
+
+ for (var i:uint = 0; i < num; i++) {
+ c1 = c2;
+ v = vertices[(i < num - 1) ? (i + 1) : 0];
+ c2 = v.coords;
+
+ var nextX:Number = c2.x - c1.x;
+ var nextY:Number = c2.y - c1.y;
+ var nextZ:Number = c2.z - c1.z;
+
+ var crossX:Number = prevY*nextZ - prevZ*nextY;
+ var crossY:Number = prevZ*nextX - prevX*nextZ;
+ var crossZ:Number = prevX*nextY - prevY*nextX;
+
+
+ var crossLength:Number = crossX*crossX + crossY*crossY + crossZ*crossZ;
+ if (crossLength > maxCrossLength) {
+ maxCrossLength = crossLength;
+ bestIndex = i;
+ }
+
+ prevX = nextX;
+ prevY = nextY;
+ prevZ = nextZ;
+ }
+ // Берём предыдущий
+ bestIndex = (bestIndex > 0) ? (bestIndex - 1) : (num - 1);
+ }
+
+ return bestIndex;
+ }
+
+ /**
+ * Генерация AS-класса.
+ *
+ * @param mesh объект, на базе которого генерируется класс
+ * @param packageName имя пакета для генерируемого класса
+ * @return AS-класс в текстовом виде
+ */
+ static public function generateClass(mesh:Mesh, packageName:String = ""):String {
+
+ var className:String = mesh._name.charAt(0).toUpperCase() + mesh._name.substr(1);
+
+ var header:String = "package" + ((packageName != "") ? (" " + packageName + " ") : " ") + "{\r\r";
+
+ var importSet:Object = new Object();
+ importSet["alternativa.engine3d.core.Mesh"] = true;
+
+ var materialSet:Map = new Map(true);
+ var materialName:String;
+ var materialNum:uint = 1;
+
+ var footer:String = "\t\t}\r\t}\r}";
+
+ var classHeader:String = "\tpublic class "+ className + " extends Mesh {\r\r";
+
+ var constructor:String = "\t\tpublic function " + className + "() {\r";
+ constructor += "\t\t\tsuper(\"" + mesh._name +"\");\r\r";
+
+
+ var newLine:Boolean = false;
+ if (mesh.mobility != 0) {
+ constructor += "\t\t\tmobility = " + mesh.mobility +";\r";
+ newLine = true;
+ }
+
+ if (mesh.x != 0 && mesh.y != 0 && mesh.z != 0) {
+ importSet["alternativa.types.Point3D"] = true;
+ constructor += "\t\t\tcoords = new Point3D(" + mesh.x + ", " + mesh.y + ", " + mesh.z +");\r";
+ newLine = true;
+ } else {
+ if (mesh.x != 0) {
+ constructor += "\t\t\tx = " + mesh.x + ";\r";
+ newLine = true;
+ }
+ if (mesh.y != 0) {
+ constructor += "\t\t\ty = " + mesh.y + ";\r";
+ newLine = true;
+ }
+ if (mesh.z != 0) {
+ constructor += "\t\t\tz = " + mesh.z + ";\r";
+ newLine = true;
+ }
+ }
+ if (mesh.rotationX != 0) {
+ constructor += "\t\t\trotationX = " + mesh.rotationX + ";\r";
+ newLine = true;
+ }
+ if (mesh.rotationY != 0) {
+ constructor += "\t\t\trotationY = " + mesh.rotationY + ";\r";
+ newLine = true;
+ }
+ if (mesh.rotationZ != 0) {
+ constructor += "\t\t\trotationZ = " + mesh.rotationZ + ";\r";
+ newLine = true;
+ }
+ if (mesh.scaleX != 1) {
+ constructor += "\t\t\tscaleX = " + mesh.scaleX + ";\r";
+ newLine = true;
+ }
+ if (mesh.scaleY != 1) {
+ constructor += "\t\t\tscaleY = " + mesh.scaleY + ";\r";
+ newLine = true;
+ }
+ if (mesh.scaleZ != 1) {
+ constructor += "\t\t\tscaleZ = " + mesh.scaleZ + ";\r";
+ newLine = true;
+ }
+
+ constructor += newLine ? "\r" : "";
+
+ function idToString(value:*):String {
+ return isNaN(value) ? ("\"" + value + "\"") : value;
+ }
+
+ function blendModeToString(value:String):String {
+ switch (value) {
+ case BlendMode.ADD: return "BlendMode.ADD";
+ case BlendMode.ALPHA: return "BlendMode.ALPHA";
+ case BlendMode.DARKEN: return "BlendMode.DARKEN";
+ case BlendMode.DIFFERENCE: return "BlendMode.DIFFERENCE";
+ case BlendMode.ERASE: return "BlendMode.ERASE";
+ case BlendMode.HARDLIGHT: return "BlendMode.HARDLIGHT";
+ case BlendMode.INVERT: return "BlendMode.INVERT";
+ case BlendMode.LAYER: return "BlendMode.LAYER";
+ case BlendMode.LIGHTEN: return "BlendMode.LIGHTEN";
+ case BlendMode.MULTIPLY: return "BlendMode.MULTIPLY";
+ case BlendMode.NORMAL: return "BlendMode.NORMAL";
+ case BlendMode.OVERLAY: return "BlendMode.OVERLAY";
+ case BlendMode.SCREEN: return "BlendMode.SCREEN";
+ case BlendMode.SUBTRACT: return "BlendMode.SUBTRACT";
+ default: return "BlendMode.NORMAL";
+ }
+ }
+
+ function colorToString(value:uint):String {
+ var hex:String = value.toString(16).toUpperCase();
+ var res:String = "0x";
+ var len:uint = 6 - hex.length;
+ for (var j:uint = 0; j < len; j++) {
+ res += "0";
+ }
+ res += hex;
+ return res;
+ }
+
+ var i:uint;
+ var length:uint;
+ var key:*;
+ var id:String;
+ var face:Face;
+ var surface:Surface;
+
+ newLine = false;
+ for (id in mesh._vertices) {
+ var vertex:Vertex = mesh._vertices[id];
+ var coords:Point3D = vertex.coords;
+ constructor += "\t\t\tcreateVertex(" + coords.x + ", " + coords.y + ", " + coords.z + ", " + idToString(id) + ");\r";
+ newLine = true;
+ }
+
+ constructor += newLine ? "\r" : "";
+
+ newLine = false;
+ for (id in mesh._faces) {
+ face = mesh._faces[id];
+ length = face._verticesCount;
+ constructor += "\t\t\tcreateFace(["
+ for (i = 0; i < length - 1; i++) {
+ constructor += idToString(mesh.getVertexId(face._vertices[i])) + ", ";
+ }
+ constructor += idToString(mesh.getVertexId(face._vertices[i])) + "], " + idToString(id) + ");\r";
+
+ if (face._aUV != null || face._bUV != null || face._cUV != null) {
+ importSet["flash.geom.Point"] = true;
+ constructor += "\t\t\tsetUVsToFace(new Point(" + face._aUV.x + ", " + face._aUV.y + "), new Point(" + face._bUV.x + ", " + face._bUV.y + "), new Point(" + face._cUV.x + ", " + face._cUV.y + "), " + idToString(id) + ");\r";
+ }
+ newLine = true;
+ }
+
+ constructor += newLine ? "\r" : "";
+
+ for (id in mesh._surfaces) {
+ surface = mesh._surfaces[id];
+ var facesStr:String = "";
+ for (key in surface._faces) {
+ facesStr += idToString(mesh.getFaceId(key)) + ", ";
+ }
+ constructor += "\t\t\tcreateSurface([" + facesStr.substr(0, facesStr.length - 2) + "], " + idToString(id) + ");\r";
+
+ if (surface.material != null) {
+ var material:String;
+ var defaultAlpha:Boolean = surface.material.alpha == 1;
+ var defaultBlendMode:Boolean = surface.material.blendMode == BlendMode.NORMAL;
+ if (surface.material is WireMaterial) {
+ importSet["alternativa.engine3d.materials.WireMaterial"] = true;
+ var defaultThickness:Boolean = WireMaterial(surface.material).thickness == 0;
+ var defaultColor:Boolean = WireMaterial(surface.material).color == 0;
+ material = "new WireMaterial(";
+ if (!defaultThickness || !defaultColor || !defaultAlpha || !defaultBlendMode) {
+ material += WireMaterial(surface.material).thickness;
+ if (!defaultColor || !defaultAlpha || !defaultBlendMode) {
+ material += ", " + colorToString(WireMaterial(surface.material).color);
+ if (!defaultAlpha || !defaultBlendMode) {
+ material += ", " + surface.material.alpha ;
+ if (!defaultBlendMode) {
+ importSet["flash.display.BlendMode"] = true;
+ material += ", " + blendModeToString(surface.material.blendMode);
+ }
+ }
+ }
+ }
+ }
+ var defaultWireThickness:Boolean;
+ var defaultWireColor:Boolean;
+ if (surface.material is FillMaterial) {
+ importSet["alternativa.engine3d.materials.FillMaterial"] = true;
+ defaultWireThickness = FillMaterial(surface.material).wireThickness < 0;
+ defaultWireColor = FillMaterial(surface.material).wireColor == 0;
+ material = "new FillMaterial(" + colorToString(FillMaterial(surface.material).color);
+ if (!defaultAlpha || !defaultBlendMode || !defaultWireThickness || !defaultWireColor) {
+ material += ", " + surface.material.alpha;
+ if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor) {
+ importSet["flash.display.BlendMode"] = true;
+ material += ", " + blendModeToString(surface.material.blendMode);
+ if (!defaultWireThickness || !defaultWireColor) {
+ material += ", " + FillMaterial(surface.material).wireThickness;
+ if (!defaultWireColor) {
+ material += ", " + colorToString(FillMaterial(surface.material).wireColor);
+ }
+ }
+ }
+ }
+ }
+ if (surface.material is TextureMaterial) {
+ importSet["alternativa.engine3d.materials.TextureMaterial"] = true;
+ var defaultRepeat:Boolean = TextureMaterial(surface.material).repeat;
+ var defaultSmooth:Boolean = !TextureMaterial(surface.material).smooth;
+ defaultWireThickness = TextureMaterial(surface.material).wireThickness < 0;
+ defaultWireColor = TextureMaterial(surface.material).wireColor == 0;
+ var defaultPrecision:Boolean = TextureMaterial(surface.material).precision == TextureMaterialPrecision.MEDIUM;
+
+ if (TextureMaterial(surface.material).texture == null) {
+ materialName = "null";
+ } else {
+ importSet["alternativa.types.Texture"] = true;
+ if (materialSet[TextureMaterial(surface.material).texture] == undefined) {
+ materialName = (TextureMaterial(surface.material).texture._name != null) ? TextureMaterial(surface.material).texture._name : "texture" + materialNum++;
+ materialSet[TextureMaterial(surface.material).texture] = materialName;
+ } else {
+ materialName = materialSet[TextureMaterial(surface.material).texture];
+ }
+ materialName = materialName.split(".")[0];
+ }
+ material = "new TextureMaterial(" + materialName;
+ if (!defaultAlpha || !defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).alpha;
+ if (!defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).repeat;
+ if (!defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).smooth;
+ if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ importSet["flash.display.BlendMode"] = true;
+ material += ", " + blendModeToString(surface.material.blendMode);
+ if (!defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).wireThickness;
+ if (!defaultWireColor || !defaultPrecision) {
+ material += ", " + colorToString(TextureMaterial(surface.material).wireColor);
+ if (!defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).precision;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ constructor += "\t\t\tsetMaterialToSurface(" + material + "), " + idToString(id) + ");\r";
+ }
+ }
+
+ var imports:String = "";
+ newLine = false;
+
+ var importArray:Array = new Array();
+ for (key in importSet) {
+ importArray.push(key);
+ }
+ importArray.sort();
+
+ length = importArray.length;
+ for (i = 0; i < length; i++) {
+ var pack:String = importArray[i];
+ var current:String = pack.substr(0, pack.indexOf("."));
+ imports += (current != prev && prev != null) ? "\r" : "";
+ imports += "\timport " + pack + ";\r";
+ var prev:String = current;
+ newLine = true;
+ }
+ imports += newLine ? "\r" : "";
+
+ var embeds:String = "";
+ newLine = false;
+ for each (materialName in materialSet) {
+ var materialClassName:String = materialName.split(".")[0];
+ var materialBmpName:String = materialClassName.charAt(0).toUpperCase() + materialClassName.substr(1);
+ embeds += "\t\t[Embed(source=\"" + materialName + "\")] private static const bmp" + materialBmpName + ":Class;\r";
+ embeds += "\t\tprivate static const " + materialClassName + ":Texture = new Texture(new bmp" + materialBmpName + "().bitmapData, \"" + materialName + "\");\r";
+ newLine = true;
+ }
+ embeds += newLine ? "\r" : "";
+
+ return header + imports + classHeader + embeds + constructor + footer;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/Alternativa3D.as b/Alternativa3D5/5.6/alternativa/Alternativa3D.as
new file mode 100644
index 0000000..398627f
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/Alternativa3D.as
@@ -0,0 +1,14 @@
+package alternativa {
+
+ /**
+ * Класс содержит информацию о версии библиотеки.
+ * Также используется для интеграции библиотеки в среду разработки Adobe Flash.
+ */
+ public class Alternativa3D {
+
+ /**
+ * Версия библиотеки в формате: поколение.feature-версия.fix-версия
+ */
+ public static const version:String = "5.6.0";
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/alternativa3d.as b/Alternativa3D5/5.6/alternativa/engine3d/alternativa3d.as
new file mode 100644
index 0000000..8bce40e
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/alternativa3d.as
@@ -0,0 +1,3 @@
+package alternativa.engine3d {
+ public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d";
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/controllers/CameraController.as b/Alternativa3D5/5.6/alternativa/engine3d/controllers/CameraController.as
new file mode 100644
index 0000000..3d7aceb
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/controllers/CameraController.as
@@ -0,0 +1,894 @@
+package alternativa.engine3d.controllers {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.physics.EllipsoidCollider;
+ import alternativa.types.Map;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+ import alternativa.utils.KeyboardUtils;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.DisplayObject;
+ import flash.events.KeyboardEvent;
+ import flash.events.MouseEvent;
+ import flash.geom.Point;
+ import flash.utils.getTimer;
+
+ use namespace alternativa3d;
+
+ /**
+ * Контроллер камеры. Контроллер обеспечивает управление движением и поворотами камеры с использованием
+ * клавиатуры и мыши, а также предоставляет простую проверку столкновений камеры с объектами сцены.
+ */
+ public class CameraController {
+ /**
+ * Имя действия для привязки клавиш движения вперёд.
+ */
+ public static const ACTION_FORWARD:String = "ACTION_FORWARD";
+ /**
+ * Имя действия для привязки клавиш движения назад.
+ */
+ public static const ACTION_BACK:String = "ACTION_BACK";
+ /**
+ * Имя действия для привязки клавиш движения влево.
+ */
+ public static const ACTION_LEFT:String = "ACTION_LEFT";
+ /**
+ * Имя действия для привязки клавиш движения вправо.
+ */
+ public static const ACTION_RIGHT:String = "ACTION_RIGHT";
+ /**
+ * Имя действия для привязки клавиш движения вверх.
+ */
+ public static const ACTION_UP:String = "ACTION_UP";
+ /**
+ * Имя действия для привязки клавиш движения вниз.
+ */
+ public static const ACTION_DOWN:String = "ACTION_DOWN";
+// public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT";
+// public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT";
+ /**
+ * Имя действия для привязки клавиш увеличения угла тангажа.
+ */
+ public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP";
+ /**
+ * Имя действия для привязки клавиш уменьшения угла тангажа.
+ */
+ public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN";
+ /**
+ * Имя действия для привязки клавиш уменьшения угла рысканья (поворота налево).
+ */
+ public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT";
+ /**
+ * Имя действия для привязки клавиш увеличения угла рысканья (поворота направо).
+ */
+ public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT";
+ /**
+ * Имя действия для привязки клавиш увеличения скорости.
+ */
+ public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE";
+
+ // флаги действий
+ private var _forward:Boolean;
+ private var _back:Boolean;
+ private var _left:Boolean;
+ private var _right:Boolean;
+ private var _up:Boolean;
+ private var _down:Boolean;
+ private var _pitchUp:Boolean;
+ private var _pitchDown:Boolean;
+ private var _yawLeft:Boolean;
+ private var _yawRight:Boolean;
+ private var _accelerate:Boolean;
+
+ private var _moveLocal:Boolean = true;
+
+ // Флаг включения управления камерой
+ private var _controlsEnabled:Boolean = false;
+
+ // Значение таймера в начале прошлого кадра
+ private var lastFrameTime:uint;
+
+ // Чувствительность обзора мышью. Коэффициент умножения базовых коэффициентов поворотов.
+ private var _mouseSensitivity:Number = 1;
+ // Коэффициент поворота камеры по тангажу. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
+ private var _mousePitch:Number = Math.PI / 360;
+ // Результирующий коэффициент поворота камеры мышью по тангажу
+ private var _mousePitchCoeff:Number = _mouseSensitivity * _mousePitch;
+ // Коэффициент поворота камеры по рысканью. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
+ private var _mouseYaw:Number = Math.PI / 360;
+ // Результирующий коэффициент поворота камеры мышью по расканью
+ private var _mouseYawCoeff:Number = _mouseSensitivity * _mouseYaw;
+
+ // Вспомогательные переменные для обзора мышью
+ private var mouseLookActive:Boolean;
+ private var startDragCoords:Point = new Point();
+ private var currentDragCoords:Point = new Point();
+ private var prevDragCoords:Point = new Point();
+ private var startRotX:Number;
+ private var startRotZ:Number;
+
+ // Скорость изменения тангажа в радианах за секунду при управлении с клавиатуры
+ private var _pitchSpeed:Number = 1;
+ // Скорость изменения рысканья в радианах за секунду при управлении с клавиатуры
+ private var _yawSpeed:Number = 1;
+ // Скорость изменения крена в радианах за секунду при управлении с клавиатуры
+// public var bankSpeed:Number = 2;
+// private var bankMatrix:Matrix3D = new Matrix3D();
+
+ // Скорость поступательного движения в единицах за секунду
+ private var _speed:Number = 100;
+ // Коэффициент увеличения скорости при соответствующей нажатой клавише
+ private var _speedMultiplier:Number = 2;
+
+ private var velocity:Point3D = new Point3D();
+ private var destination:Point3D = new Point3D();
+
+ private var _fovStep:Number = Math.PI / 180;
+ private var _zoomMultiplier:Number = 0.1;
+
+ // Привязка клавиш к действиям
+ private var keyBindings:Map = new Map();
+ // Привязка действий к обработчикам
+ private var actionBindings:Map = new Map();
+
+ // Источник событий клавиатуры и мыши
+ private var _eventsSource:DisplayObject;
+ // Управляемая камера
+ private var _camera:Camera3D;
+
+ // Класс реализации определния столкновений
+ private var _collider:EllipsoidCollider;
+ // Флаг необходимости проверки столкновений
+ private var _checkCollisions:Boolean;
+ // Радиус сферы для определения столкновений
+ private var _collisionRadius:Number = 0;
+ // Набор исключаемых из проверки столкновений объектов
+ private var _collisionIgnoreSet:Set = new Set(true);
+ // Флаг движения
+ private var _isMoving:Boolean;
+
+ private var _onStartMoving:Function;
+ private var _onStopMoving:Function;
+
+ /**
+ * Создание экземпляра контроллера.
+ *
+ * @param eventsSourceObject объект, используемый для получения событий мыши и клавиатуры
+ *
+ * @throws ArgumentError в качестве eventsSourceObject не может быть указан null
+ */
+ public function CameraController(eventsSourceObject:DisplayObject) {
+ if (eventsSourceObject == null) {
+ throw new ArgumentError("CameraController: eventsSource is null");
+ }
+ _eventsSource = eventsSourceObject;
+
+ actionBindings[ACTION_FORWARD] = forward;
+ actionBindings[ACTION_BACK] = back;
+ actionBindings[ACTION_LEFT] = left;
+ actionBindings[ACTION_RIGHT] = right;
+ actionBindings[ACTION_UP] = up;
+ actionBindings[ACTION_DOWN] = down;
+ actionBindings[ACTION_PITCH_UP] = pitchUp;
+ actionBindings[ACTION_PITCH_DOWN] = pitchDown;
+ actionBindings[ACTION_YAW_LEFT] = yawLeft;
+ actionBindings[ACTION_YAW_RIGHT] = yawRight;
+ actionBindings[ACTION_ACCELERATE] = accelerate;
+ }
+
+ /**
+ * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
+ *
+ *
+ */
+ public function setDefaultBindings():void {
+ unbindAll();
+ bindKey(KeyboardUtils.W, ACTION_FORWARD);
+ bindKey(KeyboardUtils.S, ACTION_BACK);
+ bindKey(KeyboardUtils.A, ACTION_LEFT);
+ bindKey(KeyboardUtils.D, ACTION_RIGHT);
+ bindKey(KeyboardUtils.SPACE, ACTION_UP);
+ bindKey(KeyboardUtils.Z, ACTION_DOWN);
+ bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
+ bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
+ bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT);
+ bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT);
+ bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE);
+ }
+
+ /**
+ * Направление камеры на точку.
+ *
+ * @param point координаты точки направления камеры
+ */
+ public function lookAt(point:Point3D):void {
+ if (_camera == null) {
+ return;
+ }
+ var dx:Number = point.x - _camera.x;
+ var dy:Number = point.y - _camera.y;
+ var dz:Number = point.z - _camera.z;
+ _camera.rotationZ = -Math.atan2(dx, dy);
+ _camera.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - MathUtils.DEG90;
+ }
+
+ /**
+ * Callback-функция, вызываемая при начале движения камеры.
+ */
+ public function get onStartMoving():Function {
+ return _onStartMoving;
+ }
+
+ /**
+ * @private
+ */
+ public function set onStartMoving(value:Function):void {
+ _onStartMoving = value;
+ }
+
+ /**
+ * Callback-функция, вызываемая при прекращении движения камеры.
+ */
+ public function get onStopMoving():Function {
+ return _onStopMoving;
+ }
+
+ /**
+ * @private
+ */
+ public function set onStopMoving(value:Function):void {
+ _onStopMoving = value;
+ }
+
+ /**
+ * Набор объектов, исключаемых из проверки столкновений.
+ */
+ public function get collisionIgnoreSet():Set {
+ return _collisionIgnoreSet;
+ }
+
+ /**
+ * Источник событий клавиатуры и мыши.
+ *
+ * @throws ArgumentError в качестве eventsSource не может быть указан null
+ */
+ public function get eventsSource():DisplayObject {
+ return _eventsSource;
+ }
+
+ /**
+ * @private
+ */
+ public function set eventsSource(value:DisplayObject):void {
+ if (_eventsSource != value) {
+ if (value == null) {
+ throw new ArgumentError("CameraController: eventsSource is null");
+ }
+ if (_controlsEnabled) {
+ unregisterEventsListeners();
+ }
+ _eventsSource = value;
+ if (_controlsEnabled) {
+ registerEventListeners();
+ }
+ }
+ }
+
+ /**
+ * Ассоциированная камера.
+ */
+ public function get camera():Camera3D {
+ return _camera;
+ }
+
+ /**
+ * @private
+ */
+ public function set camera(value:Camera3D):void {
+ if (_camera != value) {
+ _camera = value;
+ if (value == null) {
+ controlsEnabled = false;
+ } else {
+ createCollider();
+ }
+ }
+ }
+
+ /**
+ * Режим движения камеры. Если значение равно
+ * Клавиша Действие
+ * W ACTION_FORWARD
+ * S ACTION_BACK
+ * A ACTION_LEFT
+ * D ACTION_RIGHT
+ * SPACE ACTION_UP
+ * Z ACTION_DOWN
+ * SHIFT ACTION_ACCELERATE
+ * UP ACTION_PITCH_UP
+ * DOWN ACTION_PITCH_DOWN
+ * LEFT ACTION_YAW_LEFT
+ * RIGHT ACTION_YAW_RIGHT true, то перемещения камеры происходят относительно
+ * локальной системы координат, иначе относительно глобальной.
+ *
+ * @default true
+ */
+ public function get moveLocal():Boolean {
+ return _moveLocal;
+ }
+
+ /**
+ * @private
+ */
+ public function set moveLocal(value:Boolean):void {
+ _moveLocal = value;
+ }
+
+ /**
+ * Включение режима проверки столкновений.
+ */
+ public function get checkCollisions():Boolean {
+ return _checkCollisions;
+ }
+
+ /**
+ * @private
+ */
+ public function set checkCollisions(value:Boolean):void {
+ _checkCollisions = value;
+ }
+
+ /**
+ * Радиус сферы для определения столкновений.
+ *
+ * @default 0
+ */
+ public function get collisionRadius():Number {
+ return _collisionRadius;
+ }
+
+ /**
+ * @private
+ */
+ public function set collisionRadius(value:Number):void {
+ _collisionRadius = value;
+ if (_collider != null) {
+ _collider.radiusX = _collisionRadius;
+ _collider.radiusY = _collisionRadius;
+ _collider.radiusZ = _collisionRadius;
+ }
+ }
+
+ /**
+ * Привязка клавиши к действию.
+ *
+ * @param keyCode код клавиши
+ * @param action наименование действия
+ */
+ public function bindKey(keyCode:uint, action:String):void {
+ var method:Function = actionBindings[action];
+ if (method != null) {
+ keyBindings[keyCode] = method;
+ }
+ }
+
+ /**
+ * Очистка привязки клавиши.
+ *
+ * @param keyCode код клавиши
+ */
+ public function unbindKey(keyCode:uint):void {
+ keyBindings.remove(keyCode);
+ }
+
+ /**
+ * Очистка привязки всех клавиш.
+ */
+ public function unbindAll():void {
+ keyBindings.clear();
+ }
+
+ /**
+ * Активация движения камеры вперёд.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function forward(value:Boolean):void {
+ _forward = value;
+ }
+
+ /**
+ * Активация движения камеры назад.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function back(value:Boolean):void {
+ _back = value;
+ }
+
+ /**
+ * Активация движения камеры влево.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function left(value:Boolean):void {
+ _left = value;
+ }
+
+ /**
+ * Активация движения камеры вправо.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function right(value:Boolean):void {
+ _right = value;
+ }
+
+ /**
+ * Активация движения камеры вверх.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function up(value:Boolean):void {
+ _up = value;
+ }
+
+ /**
+ * Активация движения камеры вниз.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function down(value:Boolean):void {
+ _down = value;
+ }
+
+ /**
+ * Активация поворота камеры вверх.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function pitchUp(value:Boolean):void {
+ _pitchUp = value;
+ }
+
+ /**
+ * Активация поворота камеры вниз.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function pitchDown(value:Boolean):void {
+ _pitchDown = value;
+ }
+
+ /**
+ * Активация поворота камеры влево.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function yawLeft(value:Boolean):void {
+ _yawLeft = value;
+ }
+
+ /**
+ * Активация поворота камеры вправо.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function yawRight(value:Boolean):void {
+ _yawRight = value;
+ }
+
+ /**
+ * Активация режима увеличенной скорости.
+ *
+ * @param value true для включения ускорения, false для выключения
+ */
+ public function accelerate(value:Boolean):void {
+ _accelerate = value;
+ }
+
+ /**
+ * @private
+ */
+ private function createCollider():void {
+ _collider = new EllipsoidCollider(_camera.scene, _collisionRadius);
+ _collider.offsetThreshold = 0.01;
+ _collider.collisionSet = _collisionIgnoreSet;
+ }
+
+ /**
+ * Чувствительность мыши — коэффициент умножения mousePitch и mouseYaw.
+ *
+ * @default 1
+ *
+ * @see #mousePitch()
+ * @see #mouseYaw()
+ */
+ public function get mouseSensitivity():Number {
+ return _mouseSensitivity;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseSensitivity(sensitivity:Number):void {
+ _mouseSensitivity = sensitivity;
+ _mousePitchCoeff = _mouseSensitivity * _mousePitch;
+ _mouseYawCoeff = _mouseSensitivity * _mouseYaw;
+ }
+
+ /**
+ * Скорость изменения угла тангажа при управлении мышью (радианы на пиксель).
+ *
+ * @default Math.PI / 360
+ */
+ public function get mousePitch():Number {
+ return _mousePitch;
+ }
+
+ /**
+ * @private
+ */
+ public function set mousePitch(pitch:Number):void {
+ _mousePitch = pitch;
+ _mousePitchCoeff = _mouseSensitivity * _mousePitch;
+ }
+
+ /**
+ * Скорость изменения угла рысканья при управлении мышью (радианы на пиксель).
+ *
+ * @default Math.PI / 360
+ */
+ public function get mouseYaw():Number {
+ return _mouseYaw;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseYaw(yaw:Number):void {
+ _mouseYaw = yaw;
+ _mouseYawCoeff = _mouseSensitivity * _mouseYaw;
+ }
+
+ /**
+ * Угловая скорость по тангажу при управлении с клавиатуры (радианы в секунду).
+ *
+ * @default 1
+ */
+ public function get pitchSpeed():Number {
+ return _pitchSpeed;
+ }
+
+ /**
+ * @private
+ */
+ public function set pitchSpeed(spd:Number):void {
+ _pitchSpeed = spd;
+ }
+
+ /**
+ * Угловая скорость по рысканью при управлении с клавиатуры (радианы в секунду).
+ *
+ * @default 1
+ */
+ public function get yawSpeed():Number {
+ return _yawSpeed;
+ }
+
+ /**
+ * @private
+ */
+ public function set yawSpeed(spd:Number):void {
+ _yawSpeed = spd;
+ }
+
+ /**
+ * Скорость поступательного движения (единицы в секунду).
+ */
+ public function get speed():Number {
+ return _speed;
+ }
+
+ /**
+ * @private
+ */
+ public function set speed(spd:Number):void {
+ _speed = spd;
+ }
+
+ /**
+ * Коэффициент увеличения скорости при активном действии ACTION_ACCELERATE.
+ *
+ * @default 2
+ */
+ public function get speedMultiplier():Number {
+ return _speedMultiplier;
+ }
+
+ /**
+ * @private
+ */
+ public function set speedMultiplier(value:Number):void {
+ _speedMultiplier = value;
+ }
+
+ /**
+ * Активность управления камеры.
+ *
+ * @default false
+ */
+ public function get controlsEnabled():Boolean {
+ return _controlsEnabled;
+ }
+
+ /**
+ * @private
+ */
+ public function set controlsEnabled(value:Boolean):void {
+ if (_camera == null || _controlsEnabled == value) return;
+ if (value) {
+ lastFrameTime = getTimer();
+ registerEventListeners();
+ }
+ else {
+ unregisterEventsListeners();
+ }
+ _controlsEnabled = value;
+ }
+
+ /**
+ * Базовый шаг изменения угла зрения в радианах. Реальный шаг получаеся умножением этого значения на величину
+ * MouseEvent.delta.
+ *
+ * @default Math.PI / 180
+ */
+ public function get fovStep():Number {
+ return _fovStep;
+ }
+
+ /**
+ * @private
+ */
+ public function set fovStep(value:Number):void {
+ _fovStep = value;
+ }
+
+ /**
+ * Множитель при изменении коэффициента увеличения. Закон изменения коэффициента увеличения описывается формулой:
+ * zoom (1 + MouseEvent.delta zoomMultiplier).
+ *
+ * @default 0.1
+ */
+ public function get zoomMultiplier():Number {
+ return _zoomMultiplier;
+ }
+
+ /**
+ * @private
+ */
+ public function set zoomMultiplier(value:Number):void {
+ _zoomMultiplier = value;
+ }
+
+ /**
+ * @private
+ */
+ private function registerEventListeners():void {
+ _eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey);
+ _eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKey);
+ _eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
+ _eventsSource.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
+ }
+
+ /**
+ * @private
+ */
+ private function unregisterEventsListeners():void {
+ _eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey);
+ _eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKey);
+ _eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
+ _eventsSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ _eventsSource.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
+ }
+
+ /**
+ * @private
+ * @param e
+ */
+ private function onKey(e:KeyboardEvent):void {
+ var method:Function = keyBindings[e.keyCode];
+ if (method != null) {
+ method.call(this, e.type == KeyboardEvent.KEY_DOWN);
+ }
+ }
+
+ /**
+ * @private
+ * @param e
+ */
+ private function onMouseDown(e:MouseEvent):void {
+ mouseLookActive = true;
+ currentDragCoords.x = startDragCoords.x = _eventsSource.stage.mouseX;
+ currentDragCoords.y = startDragCoords.y = _eventsSource.stage.mouseY;
+ startRotX = _camera.rotationX;
+ startRotZ = _camera.rotationZ;
+ _eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * @private
+ * @param e
+ */
+ private function onMouseUp(e:MouseEvent):void {
+ mouseLookActive = false;
+ _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * @private
+ * @param e
+ */
+ private function onMouseWheel(e:MouseEvent):void {
+ if (_camera.orthographic) {
+ _camera.zoom = _camera.zoom * (1 + e.delta * _zoomMultiplier);
+ } else {
+ _camera.fov -= _fovStep * e.delta;
+ }
+ }
+
+ /**
+ * Обработка управляющих воздействий.
+ * Метод должен вызываться каждый кадр перед вызовом Scene3D.calculate().
+ *
+ * @see alternativa.engine3d.core.Scene3D#calculate()
+ */
+ public function processInput(): void {
+ if (!_controlsEnabled || _camera == null) return;
+
+ // Время в секундах от начала предыдущего кадра
+ var frameTime:Number = getTimer() - lastFrameTime;
+ lastFrameTime += frameTime;
+ frameTime /= 1000;
+
+ // Обработка mouselook
+ if (mouseLookActive) {
+ prevDragCoords.x = currentDragCoords.x;
+ prevDragCoords.y = currentDragCoords.y;
+ currentDragCoords.x = _eventsSource.stage.mouseX;
+ currentDragCoords.y = _eventsSource.stage.mouseY;
+ if (!prevDragCoords.equals(currentDragCoords)) {
+ _camera.rotationZ = startRotZ + (startDragCoords.x - currentDragCoords.x) * _mouseYawCoeff;
+ var rotX:Number = startRotX + (startDragCoords.y - currentDragCoords.y) * _mousePitchCoeff;
+ _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
+ }
+ }
+
+ // Поворот относительно вертикальной оси (рысканье, yaw)
+ if (_yawLeft) {
+ _camera.rotationZ += _yawSpeed * frameTime;
+ } else if (_yawRight) {
+ _camera.rotationZ -= _yawSpeed * frameTime;
+ }
+
+ // Поворот относительно поперечной оси (тангаж, pitch)
+ if (_pitchUp) {
+ rotX = _camera.rotationX + _pitchSpeed * frameTime;
+ _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
+ } else if (_pitchDown) {
+ rotX = _camera.rotationX - _pitchSpeed * frameTime;
+ _camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
+ }
+
+ // TODO: Поворот относительно продольной оси (крен, roll)
+
+ var frameDistance:Number = _speed * frameTime;
+ if (_accelerate) {
+ frameDistance *= _speedMultiplier;
+ }
+ velocity.x = 0;
+ velocity.y = 0;
+ velocity.z = 0;
+ var transformation:Matrix3D = _camera._transformation;
+
+ if (_moveLocal) {
+ // Режим относительных пермещений
+ // Движение вперед-назад
+ if (_forward) {
+ velocity.x += frameDistance * transformation.c;
+ velocity.y += frameDistance * transformation.g;
+ velocity.z += frameDistance * transformation.k;
+ } else if (_back) {
+ velocity.x -= frameDistance * transformation.c;
+ velocity.y -= frameDistance * transformation.g;
+ velocity.z -= frameDistance * transformation.k;
+ }
+ // Движение влево-вправо
+ if (_left) {
+ velocity.x -= frameDistance * transformation.a;
+ velocity.y -= frameDistance * transformation.e;
+ velocity.z -= frameDistance * transformation.i;
+ } else if (_right) {
+ velocity.x += frameDistance * transformation.a;
+ velocity.y += frameDistance * transformation.e;
+ velocity.z += frameDistance * transformation.i;
+ }
+ // Движение вверх-вниз
+ if (_up) {
+ velocity.x -= frameDistance * transformation.b;
+ velocity.y -= frameDistance * transformation.f;
+ velocity.z -= frameDistance * transformation.j;
+ } else if (_down) {
+ velocity.x += frameDistance * transformation.b;
+ velocity.y += frameDistance * transformation.f;
+ velocity.z += frameDistance * transformation.j;
+ }
+ }
+ else {
+ // Режим глобальных перемещений
+ var cosZ:Number = Math.cos(_camera.rotationZ);
+ var sinZ:Number = Math.sin(_camera.rotationZ);
+ // Движение вперед-назад
+ if (_forward) {
+ velocity.x -= frameDistance * sinZ;
+ velocity.y += frameDistance * cosZ;
+ } else if (_back) {
+ velocity.x += frameDistance * sinZ;
+ velocity.y -= frameDistance * cosZ;
+ }
+ // Движение влево-вправо
+ if (_left) {
+ velocity.x -= frameDistance * cosZ;
+ velocity.y -= frameDistance * sinZ;
+ } else if (_right) {
+ velocity.x += frameDistance * cosZ;
+ velocity.y += frameDistance * sinZ;
+ }
+ // Движение вверх-вниз
+ if (_up) {
+ velocity.z += frameDistance;
+ } else if (_down) {
+ velocity.z -= frameDistance;
+ }
+ }
+
+ // Коррекция модуля вектора скорости
+ if (velocity.x != 0 || velocity.y != 0 || velocity.z != 0) {
+ velocity.length = frameDistance;
+ }
+
+ // Проверка столкновений
+ if (_checkCollisions) {
+ _collider.calculateDestination(_camera.coords, velocity, destination);
+ _camera.x = destination.x;
+ _camera.y = destination.y;
+ _camera.z = destination.z;
+ } else {
+ _camera.x += velocity.x;
+ _camera.y += velocity.y;
+ _camera.z += velocity.z;
+ }
+
+ // Обработка начала/окончания движения
+ if (_camera.changeRotationOrScaleOperation.queued || _camera.changeCoordsOperation.queued) {
+ if (!_isMoving) {
+ _isMoving = true;
+ if (_onStartMoving != null) {
+ _onStartMoving.call(this);
+ }
+ }
+ } else {
+ if (_isMoving) {
+ _isMoving = false;
+ if (_onStopMoving != null) {
+ _onStopMoving.call(this);
+ }
+ }
+ }
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/controllers/FlyController.as b/Alternativa3D5/5.6/alternativa/engine3d/controllers/FlyController.as
new file mode 100644
index 0000000..6199401
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/controllers/FlyController.as
@@ -0,0 +1,475 @@
+package alternativa.engine3d.controllers {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.utils.KeyboardUtils;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.DisplayObject;
+
+ use namespace alternativa3d;
+
+ /**
+ * Контроллер, реализующий управление объектом, находящимся в корневом объекте сцены, подобно управлению летательным аппаратом.
+ * Повороты выполняются вокруг локальных осей объекта, собственные ускорения действуют вдоль локальных осей.
+ *
+ *
+ *
+ *
+ * Ось Направление Поворот
+ *
+ *
+ * X Вправо Тангаж
+ *
+ *
+ * Y Вперёд Крен
+ *
+ *
+ * Z Вверх Рысканье
+ *
Соответствия локальных осей для объектов, являющихся камерой: + *
| Ось | Направление | Поворот | + *
|---|---|---|
| X | Вправо | Тангаж | + *
| Y | Вниз | Рысканье | + *
| Z | Вперёд | Крен | + *
+ * Поворот мышью реализован следующим образом: в момент активации режима поворота (нажата левая кнопка мыши или + * соответствующая кнопка на клавиатуре) текущее положение курсора становится точкой, относительно которой определяются + * дальнейшие отклонения. Отклонение курсора по вертикали в пикселях, умноженное на коэффициент чувствительности мыши + * по вертикали даёт угловую скорость по тангажу. Отклонение курсора по горизонтали в пикселях, умноженное на коэффициент + * чувствительности мыши по горизонтали даёт угловую скорость по крену. + *
+ */ + public class FlyController extends ObjectController { + /** + * Имя действия для привязки клавиш поворота по крену влево. + */ + public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT"; + /** + * Имя действия для привязки клавиш поворота по крену вправо. + */ + public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT"; + + private var _rollLeft:Boolean; + private var _rollRight:Boolean; + + private var _rollSpeed:Number = 1; + + private var rotations:Point3D; + private var rollMatrix:Matrix3D = new Matrix3D(); + private var transformation:Matrix3D = new Matrix3D(); + private var axis:Point3D = new Point3D(); + + private var velocity:Point3D = new Point3D(); + private var displacement:Point3D = new Point3D(); + private var destination:Point3D = new Point3D(); + private var deltaVelocity:Point3D = new Point3D(); + private var accelerationVector:Point3D = new Point3D(); + private var currentTransform:Matrix3D = new Matrix3D(); + + private var _currentSpeed:Number = 1; + /** + * Текущие координаты мышиного курсора в режиме mouse look. + */ + private var currentMouseCoords:Point3D = new Point3D(); + + /** + * Модуль вектора ускорния, получаемого от команд движения. + */ + public var acceleration:Number = 1000; + /** + * Модуль вектора замедляющего ускорения. + */ + public var deceleration:Number = 50; + /** + * Погрешность определения скорости. Скорость приравнивается к нулю, если её модуль не превышает заданного значения. + */ + public var speedThreshold:Number = 1; + /** + * Переключение инерционного режима. В инерционном режиме отсутствует замедляющее ускорение, в результате чего вектор + * скорости объекта остаётся постоянным, если нет управляющих воздействий. При выключенном инерционном режиме к объекту + * прикладывается замедляющее ускорение. + */ + public var inertialMode:Boolean; + + /** + * Создаёт новый экземпляр контролллера. + * + * @param eventsSourceObject источник событий клавиатуры и мыши + * + * @throws ArgumentError в качестве eventsSourceObject не может быть указан null + */ + public function FlyController(eventsSourceObject:DisplayObject) { + super(eventsSourceObject); + + actionBindings[ACTION_ROLL_LEFT] = rollLeft; + actionBindings[ACTION_ROLL_RIGHT] = rollRight; + } + + /** + * Текущая скорость движения. + */ + public function get currentSpeed():Number { + return _currentSpeed; + } + + /** + * Активация вращения по крену влево. + */ + public function rollLeft(value:Boolean):void { + _rollLeft = value; + } + + /** + * Активация вращения по крену вправо. + */ + public function rollRight(value:Boolean):void { + _rollRight = value; + } + + /** + * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие: + *| Клавиша | Действие |
|---|---|
| W | ACTION_FORWARD |
| S | ACTION_BACK |
| A | ACTION_LEFT |
| D | ACTION_RIGHT |
| SPACE | ACTION_UP |
| Z | ACTION_DOWN |
| UP | ACTION_PITCH_UP |
| DOWN | ACTION_PITCH_DOWN |
| LEFT | ACTION_ROLL_LEFT |
| RIGHT | ACTION_ROLL_RIGHT |
| Q | ACTION_YAW_LEFT |
| E | ACTION_YAW_RIGHT |
| M | ACTION_MOUSE_LOOK |
value указывает, нажата или отпущена соответсвующая команде
+ * клавиша.
+ */
+ protected var actionBindings:Map = new Map();
+ /**
+ * Флаг активности клавиатуры.
+ */
+ protected var _keyboardEnabled:Boolean;
+ /**
+ * Флаг активности мыши.
+ */
+ protected var _mouseEnabled:Boolean;
+ /**
+ * Общая чувствительность мыши. Коэффициент умножения чувствительности по вертикали и горизонтали.
+ */
+ protected var _mouseSensitivity:Number = 1;
+ /**
+ * Коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
+ */
+ protected var _mouseSensitivityY:Number = Math.PI / 360;
+ /**
+ * Коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
+ */
+ protected var _mouseSensitivityX:Number = Math.PI / 360;
+ /**
+ * Результирующий коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
+ */
+ protected var _mouseCoefficientY:Number = _mouseSensitivity * _mouseSensitivityY;
+ /**
+ * Результирующий коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
+ */
+ protected var _mouseCoefficientX:Number = _mouseSensitivity * _mouseSensitivityX;
+ /**
+ * Угловая скорость поворота вокруг поперечной оси в радианах за секунду.
+ */
+ protected var _pitchSpeed:Number = 1;
+ /**
+ * Угловая скорость поворота вокруг вертикальной оси в радианах за секунду.
+ */
+ protected var _yawSpeed:Number = 1;
+ /**
+ * Скорость поступательного движения в единицах за секунду.
+ */
+ protected var _speed:Number = 100;
+ /**
+ * Коэффициент увеличения скорости при соответствующей активной команде.
+ */
+ protected var _speedMultiplier:Number = 2;
+ /**
+ * Управляемый объект.
+ */
+ protected var _object:Object3D;
+ /**
+ * Время в секундах, прошедшее с последнего вызова метода processInput (обычно с последнего кадра).
+ */
+ protected var lastFrameTime:uint;
+ /**
+ * Текущие координаты контроллера.
+ */
+ protected var _coords:Point3D = new Point3D();
+ /**
+ * Индикатор движения объекта (перемещения или поворота) в текущем кадре.
+ */
+ protected var _isMoving:Boolean;
+ /**
+ * Объект для определения столкновений.
+ */
+ protected var _collider:EllipsoidCollider = new EllipsoidCollider();
+
+ /**
+ * Включение и выключение режима проверки столкновений.
+ */
+ public var checkCollisions:Boolean;
+ /**
+ * Функция вида function():void, вызываемая при начале движения объекта. Под движением
+ * понимается изменение координат или ориентации.
+ */
+ public var onStartMoving:Function;
+ /**
+ * Функция вида function():void, вызываемая при прекращении движения объекта. Под движением
+ * понимается изменение координат или ориентации.
+ */
+ public var onStopMoving:Function;
+
+ // Вектор смещения
+ private var _displacement:Point3D = new Point3D();
+
+ /**
+ * Создаёт новый экземпляр контролллера.
+ *
+ * @param eventsSourceObject источник событий клавиатуры и мыши
+ *
+ * @throws ArgumentError в качестве eventsSourceObject не может быть указан null
+ */
+ public function ObjectController(eventsSourceObject:DisplayObject) {
+ if (eventsSourceObject == null) {
+ throw new ArgumentError(ObjectUtils.getClassName(this) + ": eventsSourceObject is null");
+ }
+ _eventsSource = eventsSourceObject;
+
+ actionBindings[ACTION_FORWARD] = moveForward;
+ actionBindings[ACTION_BACK] = moveBack;
+ actionBindings[ACTION_LEFT] = moveLeft;
+ actionBindings[ACTION_RIGHT] = moveRight;
+ actionBindings[ACTION_UP] = moveUp;
+ actionBindings[ACTION_DOWN] = moveDown;
+ actionBindings[ACTION_PITCH_UP] = pitchUp;
+ actionBindings[ACTION_PITCH_DOWN] = pitchDown;
+ actionBindings[ACTION_YAW_LEFT] = yawLeft;
+ actionBindings[ACTION_YAW_RIGHT] = yawRight;
+ actionBindings[ACTION_ACCELERATE] = accelerate;
+ actionBindings[ACTION_MOUSE_LOOK] = setMouseLook;
+
+ keyboardEnabled = true;
+ mouseEnabled = true;
+ }
+
+ /**
+ * Включение и выключение контроллера. Выключенный контроллер пропускает выполнение метода processInput().
+ *
+ * @default true
+ *
+ * @see #processInput()
+ */
+ public function get enabled():Boolean {
+ return _enabled;
+ }
+
+ /**
+ * @private
+ */
+ public function set enabled(value:Boolean):void {
+ _enabled = value;
+ if (_enabled) {
+ if (_mouseEnabled) {
+ registerMouseListeners();
+ }
+ if (_keyboardEnabled) {
+ registerKeyboardListeners();
+ }
+ } else {
+ if (_mouseEnabled) {
+ unregisterMouseListeners();
+ setMouseLook(false);
+ }
+ if (_keyboardEnabled) {
+ unregisterKeyboardListeners();
+ }
+ }
+ }
+
+ /**
+ * Координаты контроллера. Координаты совпадают с координатами центра эллипсоида, используемого для определения
+ * столкновений. Координаты управляемого объекта могут не совпадать с координатами контроллера.
+ *
+ * @see #setObjectCoords()
+ */
+ public function get coords():Point3D {
+ return _coords.clone();
+ }
+
+ /**
+ * @private
+ */
+ public function set coords(value:Point3D):void {
+ _coords.copy(value);
+ setObjectCoords();
+ }
+
+ /**
+ * Чтение координат контроллера в заданную переменную.
+ *
+ * @param point переменная, в которую записываются координаты контроллера
+ */
+ public function readCoords(point:Point3D):void {
+ point.copy(_coords);
+ }
+
+ /**
+ * Управляемый объект.
+ */
+ public function get object():Object3D {
+ return _object;
+ }
+
+ /**
+ * @private
+ * При установке объекта устанавливается сцена для коллайдера.
+ */
+ public function set object(value:Object3D):void {
+ _object = value;
+ _collider.scene = _object == null ? null : _object.scene;
+ setObjectCoords();
+ }
+
+ /**
+ * Объект, реализующий проверку столкновений для эллипсоида.
+ */
+ public function get collider():EllipsoidCollider {
+ return _collider;
+ }
+
+ /**
+ * Активация движения вперёд.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveForward(value:Boolean):void {
+ _forward = value;
+ }
+
+ /**
+ * Активация движения назад.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveBack(value:Boolean):void {
+ _back = value;
+ }
+
+ /**
+ * Активация движения влево.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveLeft(value:Boolean):void {
+ _left = value;
+ }
+
+ /**
+ * Активация движения вправо.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveRight(value:Boolean):void {
+ _right = value;
+ }
+
+ /**
+ * Активация движения вверх.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveUp(value:Boolean):void {
+ _up = value;
+ }
+
+ /**
+ * Активация движения вниз.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function moveDown(value:Boolean):void {
+ _down = value;
+ }
+
+ /**
+ * Активация поворота вверх.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function pitchUp(value:Boolean):void {
+ _pitchUp = value;
+ }
+
+ /**
+ * Активация поворота вниз.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function pitchDown(value:Boolean):void {
+ _pitchDown = value;
+ }
+
+ /**
+ * Активация поворота влево.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function yawLeft(value:Boolean):void {
+ _yawLeft = value;
+ }
+
+ /**
+ * Активация поворота вправо.
+ *
+ * @param value true для начала движения, false для окончания
+ */
+ public function yawRight(value:Boolean):void {
+ _yawRight = value;
+ }
+
+ /**
+ * Активация режима увеличенной скорости.
+ *
+ * @param value true для включения ускорения, false для выключения
+ */
+ public function accelerate(value:Boolean):void {
+ _accelerate = value;
+ }
+
+ /**
+ * Угловая скорость поворота вокруг поперечной оси (радианы в секунду).
+ *
+ * @default 1
+ */
+ public function get pitchSpeed():Number {
+ return _pitchSpeed;
+ }
+
+ /**
+ * @private
+ */
+ public function set pitchSpeed(spd:Number):void {
+ _pitchSpeed = spd;
+ }
+
+ /**
+ * Угловая скорость поворота вокруг вертикальной оси (радианы в секунду).
+ *
+ * @default 1
+ */
+ public function get yawSpeed():Number {
+ return _yawSpeed;
+ }
+
+ /**
+ * @private
+ */
+ public function set yawSpeed(spd:Number):void {
+ _yawSpeed = spd;
+ }
+
+ /**
+ * Скорость движения в единицах за секунду. При установке отрицательного значения берётся модуль.
+ *
+ * @default 100
+ */
+ public function get speed():Number {
+ return _speed;
+ }
+
+ /**
+ * @private
+ */
+ public function set speed(value:Number):void {
+ _speed = value < 0 ? -value : value;
+ }
+
+ /**
+ * Коэффициент увеличения скорости при активном действии ACTION_ACCELERATE.
+ *
+ * @default 2
+ */
+ public function get speedMultiplier():Number {
+ return _speedMultiplier;
+ }
+
+ /**
+ * @private
+ */
+ public function set speedMultiplier(value:Number):void {
+ _speedMultiplier = value;
+ }
+
+ /**
+ * Чувствительность мыши — коэффициент умножения mouseSensitivityX и mouseSensitivityY.
+ *
+ * @default 1
+ *
+ * @see #mouseSensitivityY()
+ * @see #mouseSensitivityX()
+ */
+ public function get mouseSensitivity():Number {
+ return _mouseSensitivity;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseSensitivity(sensitivity:Number):void {
+ _mouseSensitivity = sensitivity;
+ _mouseCoefficientY = _mouseSensitivity * _mouseSensitivityY;
+ _mouseCoefficientX = _mouseSensitivity * _mouseSensitivityX;
+ }
+
+ /**
+ * Чувствительность мыши по вертикали.
+ *
+ * @default Math.PI / 360
+ *
+ * @see #mouseSensitivity()
+ * @see #mouseSensitivityX()
+ */
+ public function get mouseSensitivityY():Number {
+ return _mouseSensitivityY;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseSensitivityY(value:Number):void {
+ _mouseSensitivityY = value;
+ _mouseCoefficientY = _mouseSensitivity * _mouseSensitivityY;
+ }
+
+ /**
+ * Чувствительность мыши по горизонтали.
+ *
+ * @default Math.PI / 360
+ *
+ * @see #mouseSensitivity()
+ * @see #mouseSensitivityY()
+ */
+ public function get mouseSensitivityX():Number {
+ return _mouseSensitivityX;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseSensitivityX(value:Number):void {
+ _mouseSensitivityX = value;
+ _mouseCoefficientX = _mouseSensitivity * _mouseSensitivityX;
+ }
+
+ /**
+ * Включение/выключение режима вращения объекта мышью. При включении режима вполняется метод startMouseLook(),
+ * при выключении — stoptMouseLook().
+ *
+ * @see #startMouseLook()
+ * @see #stopMouseLook()
+ */
+ public function setMouseLook(value:Boolean):void {
+ if (_mouseLookActive != value) {
+ _mouseLookActive = value;
+ if (_mouseLookActive) {
+ startMouseLook();
+ } else {
+ stopMouseLook();
+ }
+ }
+ }
+
+ /**
+ * Метод выполняет необходимые действия при включении режима вращения объекта мышью.
+ * Реализация по умолчанию записывает начальные глобальные координаты курсора мыши в переменную startMouseCoords.
+ *
+ * @see #startMouseCoords
+ * @see #setMouseLook()
+ * @see #stopMouseLook()
+ */
+ protected function startMouseLook():void {
+ startMouseCoords.x = _eventsSource.stage.mouseX;
+ startMouseCoords.y = _eventsSource.stage.mouseY;
+ }
+
+ /**
+ * Метод выполняет необходимые действия при выключении вращения объекта мышью. Реализация по умолчанию не делает
+ * ничего.
+ *
+ * @see #setMouseLook()
+ * @see #startMouseLook()
+ */
+ protected function stopMouseLook():void {
+ }
+
+ /**
+ * Метод выполняет обработку всех воздействий на объект. Если объект не установлен или свойство enabled
+ * равно false, метод не выполняется.
+ * + * Алгоритм работы метода следующий: + *
registerKeyboardListeners,
+ * при выключении — unregisterKeyboardListeners.
+ *
+ * @see #registerKeyboardListeners()
+ * @see #unregisterKeyboardListeners()
+ */
+ public function get keyboardEnabled():Boolean {
+ return _keyboardEnabled;
+ }
+
+ /**
+ * @private
+ */
+ public function set keyboardEnabled(value:Boolean):void {
+ if (_keyboardEnabled != value) {
+ _keyboardEnabled = value;
+ if (_keyboardEnabled) {
+ if (_enabled) {
+ registerKeyboardListeners();
+ }
+ } else {
+ unregisterKeyboardListeners();
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Запуск обработчиков клавиатурных команд.
+ */
+ private function onKeyboardEvent(e:KeyboardEvent):void {
+ var method:Function = keyBindings[e.keyCode];
+ if (method != null) {
+ method.call(this, e.type == KeyboardEvent.KEY_DOWN);
+ }
+ }
+
+ /**
+ * Регистрация необходимых обработчиков при включении обработки клавиатурных событий.
+ *
+ * @see #unregisterKeyboardListeners()
+ */
+ protected function registerKeyboardListeners():void {
+ _eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent);
+ _eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent);
+ }
+
+ /**
+ * Удаление обработчиков при выключении обработки клавиатурных событий.
+ *
+ * @see #registerKeyboardListeners()
+ */
+ protected function unregisterKeyboardListeners():void {
+ clearCommandFlags();
+ _eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent);
+ _eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent);
+ }
+
+ /**
+ * Включение и выключение обработки мышиных событий. При включении выполняется метод registerMouseListeners,
+ * при выключении — unregisterMouseListeners.
+ *
+ * @see #registerMouseListeners()
+ * @see #unregisterMouseListeners()
+ */
+ public function get mouseEnabled():Boolean {
+ return _mouseEnabled;
+ }
+
+ /**
+ * @private
+ */
+ public function set mouseEnabled(value:Boolean):void {
+ if (_mouseEnabled != value) {
+ _mouseEnabled = value;
+ if (_mouseEnabled) {
+ if (_enabled) {
+ registerMouseListeners();
+ }
+ } else {
+ unregisterMouseListeners();
+ }
+ }
+ }
+
+ /**
+ * Регистрация необходимых обработчиков при включении обработки мышиных событий.
+ *
+ * @see #unregisterMouseListeners()
+ */
+ protected function registerMouseListeners():void {
+ _eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
+ }
+
+ /**
+ * Удаление используемых обработчиков при выключении обработки мышиных событий.
+ *
+ * @see #registerMouseListeners()
+ */
+ protected function unregisterMouseListeners():void {
+ _eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
+ _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * Активация mouselook
+ */
+ private function onMouseDown(e:MouseEvent):void {
+ setMouseLook(true);
+ _eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * Отключение mouselook
+ */
+ private function onMouseUp(e:MouseEvent):void {
+ setMouseLook(false);
+ _eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
+ }
+
+ /**
+ * Установка координат управляемого объекта в зависимости от текущих координат контроллера.
+ */
+ protected function setObjectCoords():void {
+ if (_object != null) {
+ _object.coords = _coords;
+ }
+ }
+
+ /**
+ * Индикатор режима увеличенной скорости.
+ */
+ public function get accelerated():Boolean {
+ return _accelerate;
+ }
+
+ /**
+ * Метод сбрасывает флаги активных команд.
+ */
+ protected function clearCommandFlags():void {
+ _forward = false;
+ _back = false;
+ _left = false;
+ _right = false;
+ _up = false;
+ _down = false;
+ _pitchUp = false;
+ _pitchDown = false;
+ _yawLeft = false;
+ _yawRight = false;
+ _accelerate = false;
+ _mouseLookActive = false;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/controllers/WalkController.as b/Alternativa3D5/5.6/alternativa/engine3d/controllers/WalkController.as
new file mode 100644
index 0000000..8b8d46a
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/controllers/WalkController.as
@@ -0,0 +1,570 @@
+package alternativa.engine3d.controllers {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.physics.Collision;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.utils.KeyboardUtils;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.DisplayObject;
+
+ use namespace alternativa3d;
+
+ /**
+ * Контроллер, реализующий управление движением объекта, находящегося в системе координат корневого объекта сцены.
+ *
+ * Контроллер предоставляет два режима движения: режим ходьбы с учётом силы тяжести и режим полёта, в котором сила + * тяжести не учитывается. В обоих режимах может быть включена проверка столкновений с объектами сцены. Если проверка + * столкновений отключена, то в режиме ходьбы сила тяжести также игнорируется и дополнительно появляется возможность + * движения по вертикали.
+ * + *Для всех объектов, за исключением Camera3D, направлением "вперёд" считается направление его оси
+ * Y, направлением "вверх" — направление оси Z. Для объектов класса
+ * Camera3D направление "вперёд" совпадает с направлением локальной оси Z, а направление
+ * "вверх" противоположно направлению локальной оси Y.
Вне зависимости от того, включена проверка столкновений или нет, координаты при перемещении расчитываются для
+ * эллипсоида, параметры которого устанавливаются через свойство collider. Координаты управляемого
+ * объекта вычисляются исходя из положения центра эллипсоида и положения объекта на вертикальной оси эллипсоида,
+ * задаваемого параметром objectZPosition.
Команда ACTION_UP в режиме ходьбы при ненулевой гравитации вызывает прыжок, в остальных случаях
+ * происходит движение вверх.
onGround будет иметь значение false.
+ *
+ * @see #onGround
+ */
+ public function get maxGroundAngle():Number {
+ return Math.acos(minGroundCos);
+ }
+
+ /**
+ * @private
+ */
+ public function set maxGroundAngle(value:Number):void {
+ minGroundCos = Math.cos(value);
+ }
+
+ /**
+ * Положение управляемого объекта на оси Z эллипсоида. Значение 0 указывает положение в нижней точке эллипсоида,
+ * значение 1 — положение в верхней точке эллипсоида.
+ */
+ public function get objectZPosition():Number {
+ return _objectZPosition;
+ }
+
+ /**
+ * @private
+ */
+ public function set objectZPosition(value:Number):void {
+ _objectZPosition = value;
+ setObjectCoords();
+ }
+
+ /**
+ * Включение и выключение режима полёта.
+ *
+ * @default false
+ */
+ public function get flyMode():Boolean {
+ return _flyMode;
+ }
+
+ /**
+ *@private
+ */
+ public function set flyMode(value:Boolean):void {
+ _flyMode = value;
+ }
+
+ /**
+ * Индикатор положения эллипсоида на поверхности в режиме ходьбы. Считается, что эллипсоид находится на поверхности,
+ * если угол наклона поверхности под ним не превышает заданного свойством maxGroundAngle значения.
+ *
+ * @see #maxGroundAngle
+ */
+ public function get onGround():Boolean {
+ return _onGround;
+ }
+
+ /**
+ * Модуль текущей скорости.
+ */
+ public function get currentSpeed():Number {
+ return _currentSpeed;
+ }
+
+ /**
+ * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
+ * | Клавиша | Действие |
|---|---|
| W | ACTION_FORWARD |
| S | ACTION_BACK |
| A | ACTION_LEFT |
| D | ACTION_RIGHT |
| SPACE | ACTION_UP |
| Z | ACTION_DOWN |
| SHIFT | ACTION_ACCELERATE |
| UP | ACTION_PITCH_UP |
| DOWN | ACTION_PITCH_DOWN |
| LEFT | ACTION_YAW_LEFT |
| RIGHT | ACTION_YAW_RIGHT |
| M | ACTION_MOUSE_LOOK |
objectZPosition.
+ *
+ * @see #objectZPosition
+ */
+ override protected function setObjectCoords():void {
+ if (_object != null) {
+ _object.x = _coords.x;
+ _object.y = _coords.y;
+ _object.z = _coords.z + (2*_objectZPosition - 1)*_collider.radiusZ;
+ }
+ }
+
+ /**
+ * Метод выполняет необходимые действия при включении вращения объекта мышью.
+ */
+ override protected function startMouseLook():void {
+ super.startMouseLook();
+ startRotX = _object is Camera3D ? _object.rotationX + MathUtils.DEG90 : _object.rotationX;
+ startRotZ = _object.rotationZ;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function set enabled(value:Boolean):void {
+ super.enabled = value;
+ if (!value) {
+ velocity.reset();
+ _currentSpeed = 0;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function moveUp(value:Boolean):void {
+ super.moveUp(value);
+ if (!inJump && !value) {
+ _jumpLocked = false;
+ }
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/BSPNode.as b/Alternativa3D5/5.6/alternativa/engine3d/core/BSPNode.as
new file mode 100644
index 0000000..c0ec092
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/core/BSPNode.as
@@ -0,0 +1,93 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.*;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ */
+ public final class BSPNode {
+
+ // Сплиттер ноды (если есть)
+ alternativa3d var splitter:Splitter;
+
+ // Передний и задний сектора (если есть сплиттер)
+ alternativa3d var frontSector:Sector;
+ alternativa3d var backSector:Sector;
+
+ // Тип примитива
+ alternativa3d var isSprite:Boolean;
+
+ // Родительская нода
+ alternativa3d var parent:BSPNode;
+
+ // Дочерние ветки
+ alternativa3d var front:BSPNode;
+ alternativa3d var back:BSPNode;
+
+ // Нормаль плоскости ноды
+ alternativa3d var normal:Point3D = new Point3D();
+
+ // Смещение плоскости примитива
+ alternativa3d var offset:Number;
+
+ // Минимальная мобильность ноды
+ alternativa3d var mobility:int = int.MAX_VALUE;
+
+ // Набор примитивов в ноде
+ alternativa3d var primitive:PolyPrimitive;
+ alternativa3d var backPrimitives:Set;
+ alternativa3d var frontPrimitives:Set;
+
+ // Хранилище неиспользуемых нод
+ static private var collector:Array = new Array();
+
+ // Создать ноду на основе примитива
+ static alternativa3d function create(primitive:PolyPrimitive):BSPNode {
+ var node:BSPNode;
+ if ((node = collector.pop()) == null) {
+ node = new BSPNode();
+ }
+
+ // Добавляем примитив в ноду
+ node.primitive = primitive;
+ // Сохраняем ноду
+ primitive.node = node;
+ // Если это спрайтовый примитив или сплиттеровый примитив
+ if (primitive.face == null) {
+ var sprimitive:SplitterPrimitive = primitive as SplitterPrimitive;
+ if (sprimitive == null) {
+ // SpritePrimitive
+ node.normal.x = 0;
+ node.normal.y = 0;
+ node.normal.z = 0;
+ node.offset = 0;
+ node.isSprite = true;
+ } else {
+ node.splitter = sprimitive.splitter;
+ node.normal.copy(sprimitive.splitter.normal);
+ node.offset = sprimitive.splitter.offset;
+ node.isSprite = false;
+ }
+ } else {
+ // Сохраняем плоскость
+ node.normal.copy(primitive.face.globalNormal);
+ node.offset = primitive.face.globalOffset;
+ node.isSprite = false;
+ }
+ // Сохраняем мобильность
+ node.mobility = primitive.mobility;
+ return node;
+ }
+
+ // Удалить ноду, все ссылки должны быть почищены
+ static alternativa3d function destroy(node:BSPNode):void {
+ //trace(node.back, node.front, node.parent, node.primitive, node.backPrimitives, node.frontPrimitives);
+ collector.push(node);
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Camera3D.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Camera3D.as
new file mode 100644
index 0000000..e8cf69d
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Camera3D.as
@@ -0,0 +1,1311 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.display.Skin;
+ import alternativa.engine3d.display.View;
+ import alternativa.engine3d.materials.DrawPoint;
+ import alternativa.engine3d.materials.SpriteMaterial;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ import flash.geom.Matrix;
+
+ use namespace alternativa3d;
+
+ /**
+ * Камера для отображения 3D-сцены на экране.
+ *
+ * Направление камеры совпадает с её локальной осью Z, поэтому только что созданная камера смотрит вверх в системе + * координат родителя.
+ * + * Для отображения видимой через камеру части сцены на экран, к камере должен быть подключён вьюпорт —
+ * экземпляр класса alternativa.engine3d.display.View.
f = d/tan(fov/2), где d является половиной диагонали вьюпорта.
+ * Угол зрения ограничен диапазоном 0-180 градусов.
+ *
+ * @see focalLength
+ */
+ public function get fov():Number {
+ return _fov;
+ }
+
+ /**
+ * @private
+ */
+ public function set fov(value:Number):void {
+ value = (value < 0) ? 0 : ((value > (Math.PI - 0.0001)) ? (Math.PI - 0.0001) : value);
+ if (_fov != value) {
+ // Если перспектива
+ if (!_orthographic) {
+ // Отправляем сигнал об изменении плоскостей отсечения
+ addOperationToScene(calculatePlanesOperation);
+ }
+ // Сохраняем новое значение
+ _fov = value;
+ }
+ }
+
+ /**
+ * Фокусное расстояние камеры.
+ * Вычисляется по формуле f = d/tan(fov/2), где d является половиной диагонали вьюпорта.
+ * Если с камерой не связан вьюпорт, метод вернет NaN.
+ *
+ * @see fov
+ */
+ public function get focalLength():Number {
+ if (_view == null) {
+ return NaN;
+ }
+ if (orthographic || calculatePlanesOperation.queued || scene == null) {
+ // Требуется пересчет
+ return 0.5*Math.sqrt(_view._width*_view._width + _view._height*_view._height)/Math.tan(0.5*_fov);
+ } else {
+ return _focalLength;
+ }
+ }
+
+ /**
+ * Коэффициент увеличения изображения в режиме аксонометрической проекции.
+ */
+ public function get zoom():Number {
+ return _zoom;
+ }
+
+ /**
+ * @private
+ */
+ public function set zoom(value:Number):void {
+ value = (value < 0) ? 0 : value;
+ if (_zoom != value) {
+ // Если изометрия
+ if (_orthographic) {
+ // Отправляем сигнал об изменении zoom
+ addOperationToScene(calculateMatrixOperation);
+ }
+ // Сохраняем новое значение
+ _zoom = value;
+ }
+ }
+
+ /**
+ * Сектор в котором находится камера.
+ *
+ * Для правильной работы метода, сцена должна быть построена вызовом метода + * calculate после изменения следующих свойств сцены: splitters, sectors, planeOffsetThreshold.
+ * + * @see Scene3D#calculate() + */ + public function get currentSector():Sector { + if (_scene == null) { + return null; + } + sector = null; + findSector(_scene.bsp); + return sector; + } + + /** + * @inheritDoc + */ + override protected function addToScene(scene:Scene3D):void { + super.addToScene(scene); + if (_view != null) { + // Отправляем операцию расчёта плоскостей отсечения + scene.addOperation(calculatePlanesOperation); + // Подписываемся на сигналы сцены + scene.changePrimitivesOperation.addSequel(renderOperation); + } + } + + /** + * @inheritDoc + */ + override protected function removeFromScene(scene:Scene3D):void { + super.removeFromScene(scene); + + // Удаляем все операции из очереди + scene.removeOperation(calculateMatrixOperation); + scene.removeOperation(calculatePlanesOperation); + scene.removeOperation(renderOperation); + + if (_view != null) { + // Отписываемся от сигналов сцены + scene.changePrimitivesOperation.removeSequel(renderOperation); + } + } + + /** + * @private + */ + alternativa3d function addToView(view:View):void { + // Сохраняем первый скин + firstSkin = (view.canvas.numChildren > 0) ? Skin(view.canvas.getChildAt(0)) : null; + + // Подписка на свои операции + + // При изменении камеры пересчёт матрицы + calculateTransformationOperation.addSequel(calculateMatrixOperation); + // При изменении матрицы или FOV пересчёт плоскостей отсечения + calculateMatrixOperation.addSequel(calculatePlanesOperation); + // При изменении плоскостей перерисовка + calculatePlanesOperation.addSequel(renderOperation); + + if (_scene != null) { + // Отправляем сигнал перерисовки + _scene.addOperation(calculateMatrixOperation); + // Подписываемся на сигналы сцены + _scene.changePrimitivesOperation.addSequel(renderOperation); + } + + // Сохраняем вид + _view = view; + } + + /** + * @private + */ + alternativa3d function removeFromView(view:View):void { + // Сброс ссылки на первый скин + firstSkin = null; + + // Отписка от своих операций + + // При изменении камеры пересчёт матрицы + calculateTransformationOperation.removeSequel(calculateMatrixOperation); + // При изменении матрицы или FOV пересчёт плоскостей отсечения + calculateMatrixOperation.removeSequel(calculatePlanesOperation); + // При изменении плоскостей перерисовка + calculatePlanesOperation.removeSequel(renderOperation); + + if (_scene != null) { + // Удаляем все операции из очереди + _scene.removeOperation(calculateMatrixOperation); + _scene.removeOperation(calculatePlanesOperation); + _scene.removeOperation(renderOperation); + // Отписываемся от сигналов сцены + _scene.changePrimitivesOperation.removeSequel(renderOperation); + } + // Удаляем ссылку на вид + _view = null; + } + + /** + * @inheritDoc + */ + override protected function defaultName():String { + return "camera" + ++counter; + } + + /** + * @inheritDoc + */ + protected override function createEmptyObject():Object3D { + return new Camera3D(); + } + + /** + * @inheritDoc + */ + protected override function clonePropertiesFrom(source:Object3D):void { + super.clonePropertiesFrom(source); + + var src:Camera3D = Camera3D(source); + orthographic = src._orthographic; + zoom = src._zoom; + fov = src._fov; + } + + /** + * Включает отсечение по ближней плоскости. Если задано значениеtrue, объекты или их части, имеющие в камере
+ * координату Z меньше заданного значения, не отрисовываются. Если задано значение false, отсечение не выполняется.
+ *
+ * @see #farClipping
+ * @see #viewClipping
+ * @see #nearClippingDistance
+ *
+ * @default false
+ */
+ public function get nearClipping():Boolean {
+ return _nearClipping;
+ }
+
+ /**
+ * @private
+ */
+ public function set nearClipping(value:Boolean):void {
+ if (_nearClipping != value) {
+ _nearClipping = value;
+ addOperationToScene(calculatePlanesOperation);
+ }
+ }
+
+ /**
+ * Расстояние до ближней плоскости отсечения в системе координат камеры. Значение ограничено снизу нулём.
+ *
+ * @see #nearClipping
+ *
+ * @default 1
+ */
+ public function get nearClippingDistance():Number {
+ return _nearClippingDistance;
+ }
+
+ /**
+ * @private
+ */
+ public function set nearClippingDistance(value:Number):void {
+ if (value < 0) {
+ value = 0;
+ }
+ if (_nearClippingDistance != value) {
+ _nearClippingDistance = value;
+ addOperationToScene(calculatePlanesOperation);
+ }
+ }
+
+ /**
+ * Включает отсечение по дальней плоскости. Если задано значение true, объекты или их части, имеющие в камере
+ * координату Z больше заданного значения, не отрисовываются. Если задано значение false, отсечение не выполняется.
+ *
+ * @see #nearClipping
+ * @see #viewClipping
+ * @see #farClippingDistance
+ *
+ * @default false
+ */
+ public function get farClipping():Boolean {
+ return _farClipping;
+ }
+
+ /**
+ * @private
+ */
+ public function set farClipping(value:Boolean):void {
+ if (_farClipping != value) {
+ _farClipping = value;
+ addOperationToScene(calculatePlanesOperation);
+ }
+ }
+
+ /**
+ * Расстояние до дальней плоскости отсечения в системе координат камеры. Значение ограничено снизу нулём.
+ *
+ * @see #farClipping
+ *
+ * @default 1
+ */
+ public function get farClippingDistance():Number {
+ return _farClippingDistance;
+ }
+
+ /**
+ * @private
+ */
+ public function set farClippingDistance(value:Number):void {
+ if (value < 0) {
+ value = 0;
+ }
+ if (_farClippingDistance != value) {
+ _farClippingDistance = value;
+ addOperationToScene(calculatePlanesOperation);
+ }
+ }
+
+ /**
+ * Включает отсечение по пирамиде видимости. Если задано значение true, выполняется отсечение объектов сцены по
+ * пирамиде видимости. Если задано значение false, отсечение не выполняется. Такой режим полезен, когда
+ * заранее известно, что вся сцена находится в пирамиде видимости. В этом случае отключение отсечения позволит ускорить отрисовку. Не следует отключать
+ * отсечение по пирамиде видимости одновременно с отсечением по ближней плоскости, если позади камеры находятся объекты или части объектов, т.к. это приведёт к
+ * артефактам отрисовки, а при использовании текстурных материалов и к зацикливанию адаптивной триангуляции.
+ *
+ * @see #nearClipping
+ * @see #farClipping
+ *
+ * @default true
+ */
+ public function get viewClipping():Boolean {
+ return _viewClipping;
+ }
+
+ /**
+ * @private
+ */
+ public function set viewClipping(value:Boolean):void {
+ if (_viewClipping != value) {
+ _viewClipping = value;
+ addOperationToScene(calculatePlanesOperation);
+ }
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Face.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Face.as
new file mode 100644
index 0000000..9e81042
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Face.as
@@ -0,0 +1,904 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.*;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IEventDispatcher;
+ import flash.geom.Matrix;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одной и той же гранью.
+ * Между нажатием и отпусканием кнопки могут происходить любые другие события.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.CLICK
+ */
+ [Event(name="click", type = "alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь нажимает левую кнопку мыши над гранью.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN
+ */
+ [Event(name="mouseDown", type = "alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь отпускает левую кнопку мыши над гранью.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP
+ */
+ [Event(name="mouseUp", type = "alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь наводит курсор мыши на грань.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER
+ */
+ [Event(name="mouseOver", type = "alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь уводит курсор мыши с грани.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT
+ */
+ [Event(name="mouseOut", type = "alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь перемещает курсор мыши над гранью.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE
+ */
+ [Event(name="mouseMove", type = "alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь вращает колесо мыши над гранью.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL
+ */
+ [Event(name="mouseWheel", type = "alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Грань, образованная тремя или более вершинами. Грани являются составными частями полигональных объектов. Каждая грань
+ * содержит информацию об объекте и поверхности, которым она принадлежит. Для обеспечения возможности наложения
+ * текстуры на грань, первым трём её вершинам могут быть заданы UV-координаты, на основании которых расчитывается
+ * матрица трансформации текстуры.
+ *
+ * Класс реализует интерфейс flash.events.IEventDispatcher и может рассылать мышиные события, содержащие информацию
+ * о точке в трёхмерном пространстве, в которой произошло событие.
alternativa.engine3d.core.Vertex, задающий вершины грани в
+ * порядке обхода лицевой стороны грани против часовой стрелки.
+ *
+ * @see Vertex
+ */
+ public function Face(vertices:Array) {
+ // Сохраняем вершины
+ _vertices = vertices;
+ _verticesCount = vertices.length;
+
+ // Создаём оригинальный примитив
+ primitive = PolyPrimitive.create();
+ primitive.face = this;
+ primitive.num = _verticesCount;
+
+ // Обрабатываем вершины
+ for (var i:uint = 0; i < _verticesCount; i++) {
+ var vertex:Vertex = vertices[i];
+ // Добавляем координаты вершины в примитив
+ primitive.points.push(vertex.globalCoords);
+ // Добавляем вершину в грань
+ vertex.addToFace(this);
+ }
+
+ // Расчёт нормали
+ calculateNormalOperation.addSequel(updatePrimitiveOperation);
+
+ // Расчет нормали заставляет пересчитаться UV матрицу
+ calculateNormalOperation.addSequel(calculateUVOperation);
+ // Расчет базововй UV матрицы инициирует расчет UV матрицы грани
+ calculateBaseUVOperation.addSequel(calculateUVOperation);
+ // Расчёт UV матрицы грани инициирует перерисовку
+ calculateUVOperation.addSequel(updateMaterialOperation);
+ }
+
+ /**
+ * @private
+ * Расчёт нормали в глобальных координатах
+ */
+ private function calculateNormal():void {
+ // Вектор AB
+ var vertex:Vertex = _vertices[0];
+ var av:Point3D = vertex.globalCoords;
+ vertex = _vertices[1];
+ var bv:Point3D = vertex.globalCoords;
+ var abx:Number = bv.x - av.x;
+ var aby:Number = bv.y - av.y;
+ var abz:Number = bv.z - av.z;
+ // Вектор AC
+ vertex = _vertices[2];
+ var cv:Point3D = vertex.globalCoords;
+ var acx:Number = cv.x - av.x;
+ var acy:Number = cv.y - av.y;
+ var acz:Number = cv.z - av.z;
+ // Перпендикуляр к плоскости
+ globalNormal.x = acz*aby - acy*abz;
+ globalNormal.y = acx*abz - acz*abx;
+ globalNormal.z = acy*abx - acx*aby;
+ // Нормализация перпендикуляра
+ globalNormal.normalize();
+ }
+
+ /**
+ * @private
+ * Расчитывает глобальное смещение плоскости грани.
+ * Помечает конечные примитивы на удаление, а базовый на добавление в сцене.
+ */
+ private function updatePrimitive():void {
+ // Расчёт смещения
+ var vertex:Vertex = _vertices[0];
+ globalOffset = vertex.globalCoords.x*globalNormal.x + vertex.globalCoords.y*globalNormal.y + vertex.globalCoords.z*globalNormal.z;
+
+ removePrimitive(primitive);
+ primitive.mobility = _mesh.inheritedMobility;
+ _mesh._scene.addPrimitives.push(primitive);
+ }
+
+ /**
+ * @private
+ * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на удаление из сцены
+ */
+ private function removePrimitive(primitive:PolyPrimitive):void {
+ if (primitive.backFragment != null) {
+ removePrimitive(primitive.backFragment);
+ removePrimitive(primitive.frontFragment);
+ primitive.backFragment = null;
+ primitive.frontFragment = null;
+ if (primitive != this.primitive) {
+ primitive.parent = null;
+ primitive.sibling = null;
+ PolyPrimitive.destroy(primitive);
+ }
+ } else {
+ // Если примитив в BSP-дереве
+ if (primitive.node != null) {
+ // Удаление примитива
+ _mesh._scene.removeBSPPrimitive(primitive);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Пометка на перерисовку фрагментов грани.
+ */
+ private function updateMaterial():void {
+ if (!updatePrimitiveOperation.queued) {
+ changePrimitive(primitive);
+ }
+ }
+
+ /**
+ * @private
+ * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на перерисовку
+ */
+ private function changePrimitive(primitive:PolyPrimitive):void {
+ if (primitive.backFragment != null) {
+ changePrimitive(primitive.backFragment);
+ changePrimitive(primitive.frontFragment);
+ } else {
+ _mesh._scene.changedPrimitives[primitive] = true;
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт UV-матрицы на основании первых трёх UV-координат.
+ */
+ private function calculateBaseUV():void {
+ // Расчёт UV-матрицы
+ if (_aUV != null && _bUV != null && _cUV != null) {
+ var abu:Number = _bUV.x - _aUV.x;
+ var abv:Number = _bUV.y - _aUV.y;
+ var acu:Number = _cUV.x - _aUV.x;
+ var acv:Number = _cUV.y - _aUV.y;
+ var det:Number = abu*acv - abv*acu;
+
+ // Проверка на нулевой определитель
+ if (det < uvThreshold && det > -uvThreshold) {
+ var len:Number;
+ if (abu < uvThreshold && abu > -uvThreshold && abv < uvThreshold && abv > -uvThreshold) {
+ if (acu < uvThreshold && acu > -uvThreshold && acv < uvThreshold && acv > -uvThreshold) {
+ // Оба вырождены
+ abu = uvThreshold;
+ acv = uvThreshold;
+ } else {
+ // Вырожден AB
+ len = Math.sqrt(acu*acu + acv*acv);
+ abu = uvThreshold*acv/len;
+ abv = -uvThreshold*acu/len;
+ }
+ } else {
+ if (acu < uvThreshold && acu > -uvThreshold && acv < uvThreshold && acv > -uvThreshold) {
+ //Вырожден AC
+ len = Math.sqrt(abu*abu + abv*abv);
+ acu = -uvThreshold*abv/len;
+ acv = uvThreshold*abu/len;
+ } else {
+ // Сонаправлены
+ len = Math.sqrt(abu*abu + abv*abv);
+ acu += uvThreshold*abv/len;
+ acv -= uvThreshold*abu/len;
+ }
+ }
+ // Пересчитываем определитель
+ det = abu*acv - abv*acu;
+ }
+ // Создаём матрицу
+ if (uvMatrixBase == null) {
+ uvMatrixBase = new Matrix();
+ orthoTextureMatrix = new Matrix();
+ }
+ uvMatrixBase.a = acv / det;
+ uvMatrixBase.b = -abv / det;
+ uvMatrixBase.c = -acu / det;
+ uvMatrixBase.d = abu / det;
+ uvMatrixBase.tx = -(uvMatrixBase.a * _aUV.x + uvMatrixBase.c * _aUV.y);
+ uvMatrixBase.ty = -(uvMatrixBase.b * _aUV.x + uvMatrixBase.d * _aUV.y);
+ } else {
+ // Удаляем UV-матрицу
+ uvMatrixBase = null;
+ orthoTextureMatrix = null;
+ }
+ }
+
+ /**
+ * @private
+ * Расчет UV матрицы грани.
+ */
+ private function calculateUV():void {
+ if (uvMatrixBase != null) {
+ if (uvMatrix == null) {
+ uvMatrix = new Matrix3D();
+ }
+ var a:Point3D = _vertices[0].globalCoords;
+ var b:Point3D = _vertices[1].globalCoords;
+ var c:Point3D = _vertices[2].globalCoords;
+ var abx:Number = b.x - a.x;
+ var aby:Number = b.y - a.y;
+ var abz:Number = b.z - a.z;
+ var acx:Number = c.x - a.x;
+ var acy:Number = c.y - a.y;
+ var acz:Number = c.z - a.z;
+
+ uvMatrix.a = abx*uvMatrixBase.a + acx*uvMatrixBase.b;
+ uvMatrix.b = abx*uvMatrixBase.c + acx*uvMatrixBase.d;
+ uvMatrix.c = globalNormal.x;
+ uvMatrix.d = abx*uvMatrixBase.tx + acx*uvMatrixBase.ty + a.x;
+ uvMatrix.e = aby*uvMatrixBase.a + acy*uvMatrixBase.b;
+ uvMatrix.f = aby*uvMatrixBase.c + acy*uvMatrixBase.d;
+ uvMatrix.g = globalNormal.y;
+ uvMatrix.h = aby*uvMatrixBase.tx + acy*uvMatrixBase.ty + a.y;
+ uvMatrix.i = abz*uvMatrixBase.a + acz*uvMatrixBase.b;
+ uvMatrix.j = abz*uvMatrixBase.c + acz*uvMatrixBase.d;
+ uvMatrix.k = globalNormal.z;
+ uvMatrix.l = abz*uvMatrixBase.tx + acz*uvMatrixBase.ty + a.z;
+
+ // Считаем invert
+ var _a:Number = uvMatrix.a;
+ var _b:Number = uvMatrix.b;
+ var _c:Number = uvMatrix.c;
+ var _d:Number = uvMatrix.d;
+ var _e:Number = uvMatrix.e;
+ var _f:Number = uvMatrix.f;
+ var _g:Number = uvMatrix.g;
+ var _h:Number = uvMatrix.h;
+ var _i:Number = uvMatrix.i;
+ var _j:Number = uvMatrix.j;
+ var _k:Number = uvMatrix.k;
+ var _l:Number = uvMatrix.l;
+
+ var det:Number = -_c*_f*_i + _b*_g*_i + _c*_e*_j - _a*_g*_j - _b*_e*_k + _a*_f*_k;
+ if (det != 0) {
+ uvMatrix.a = (-_g*_j + _f*_k)/det;
+ uvMatrix.b = (_c*_j - _b*_k)/det;
+ uvMatrix.c = (-_c*_f + _b*_g)/det;
+ uvMatrix.d = (_d*_g*_j - _c*_h*_j - _d*_f*_k + _b*_h*_k + _c*_f*_l - _b*_g*_l)/det;
+ uvMatrix.e = (_g*_i - _e*_k)/det;
+ uvMatrix.f = (-_c*_i + _a*_k)/det;
+ uvMatrix.g = (_c*_e - _a*_g)/det;
+ uvMatrix.h = (_c*_h*_i - _d*_g*_i + _d*_e*_k - _a*_h*_k - _c*_e*_l + _a*_g*_l)/det;
+ uvMatrix.i = (-_f*_i + _e*_j)/det;
+ uvMatrix.j = (_b*_i - _a*_j)/det;
+ uvMatrix.k = (-_b*_e + _a*_f)/det;
+ uvMatrix.l = (_d*_f*_i - _b*_h*_i - _d*_e*_j + _a*_h*_j + _b*_e*_l - _a*_f*_l)/det;
+ } else {
+ uvMatrix = null;
+ }
+ } else {
+ uvMatrix = null;
+ }
+ }
+
+ /**
+ * Массив вершин грани, представленных объектами класса alternativa.engine3d.core.Vertex.
+ *
+ * @see Vertex
+ */
+ public function get vertices():Array {
+ return new Array().concat(_vertices);
+ }
+
+ /**
+ * Количество вершин грани.
+ */
+ public function get verticesCount():uint {
+ return _verticesCount;
+ }
+
+ /**
+ * Полигональный объект, которому принадлежит грань.
+ */
+ public function get mesh():Mesh {
+ return _mesh;
+ }
+
+ /**
+ * Поверхность, которой принадлежит грань.
+ */
+ public function get surface():Surface {
+ return _surface;
+ }
+
+ /**
+ * Идентификатор грани в полигональном объекте. В случае, если грань не принадлежит ни одному объекту, идентификатор
+ * имеет значение null.
+ */
+ public function get id():Object {
+ return (_mesh != null) ? _mesh.getFaceId(this) : null;
+ }
+
+ /**
+ * UV-координаты, соответствующие первой вершине грани.
+ */
+ public function get aUV():Point {
+ return (_aUV != null) ? _aUV.clone() : null;
+ }
+
+ /**
+ * UV-координаты, соответствующие второй вершине грани.
+ */
+ public function get bUV():Point {
+ return (_bUV != null) ? _bUV.clone() : null;
+ }
+
+ /**
+ * UV-координаты, соответствующие третьей вершине грани.
+ */
+ public function get cUV():Point {
+ return (_cUV != null) ? _cUV.clone() : null;
+ }
+
+ /**
+ * @private
+ */
+ public function set aUV(value:Point):void {
+ if (_aUV != null) {
+ if (value != null) {
+ if (!_aUV.equals(value)) {
+ _aUV.x = value.x;
+ _aUV.y = value.y;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateBaseUVOperation);
+ }
+ }
+ } else {
+ _aUV = null;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateBaseUVOperation);
+ }
+ }
+ } else {
+ if (value != null) {
+ _aUV = value.clone();
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateBaseUVOperation);
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set bUV(value:Point):void {
+ if (_bUV != null) {
+ if (value != null) {
+ if (!_bUV.equals(value)) {
+ _bUV.x = value.x;
+ _bUV.y = value.y;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateBaseUVOperation);
+ }
+ }
+ } else {
+ _bUV = null;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateBaseUVOperation);
+ }
+ }
+ } else {
+ if (value != null) {
+ _bUV = value.clone();
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateBaseUVOperation);
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set cUV(value:Point):void {
+ if (_cUV != null) {
+ if (value != null) {
+ if (!_cUV.equals(value)) {
+ _cUV.x = value.x;
+ _cUV.y = value.y;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateBaseUVOperation);
+ }
+ }
+ } else {
+ _cUV = null;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateBaseUVOperation);
+ }
+ }
+ } else {
+ if (value != null) {
+ _cUV = value.clone();
+ if (_mesh != null) {
+ _mesh.addOperationToScene(calculateBaseUVOperation);
+ }
+ }
+ }
+ }
+
+ /**
+ * Нормаль в локальной системе координат.
+ */
+ public function get normal():Point3D {
+ var res:Point3D = new Point3D();
+ var vertex:Vertex = _vertices[0];
+ var av:Point3D = vertex.coords;
+ vertex = _vertices[1];
+ var bv:Point3D = vertex.coords;
+ var abx:Number = bv.x - av.x;
+ var aby:Number = bv.y - av.y;
+ var abz:Number = bv.z - av.z;
+ vertex = _vertices[2];
+ var cv:Point3D = vertex.coords;
+ var acx:Number = cv.x - av.x;
+ var acy:Number = cv.y - av.y;
+ var acz:Number = cv.z - av.z;
+ res.x = acz*aby - acy*abz;
+ res.y = acx*abz - acz*abx;
+ res.z = acy*abx - acx*aby;
+ if (res.x != 0 || res.y != 0 || res.z != 0) {
+ var k:Number = Math.sqrt(res.x*res.x + res.y*res.y + res.z*res.z);
+ res.x /= k;
+ res.y /= k;
+ res.z /= k;
+ }
+ return res;
+ }
+
+ /**
+ * Расчёт UV-координат для произвольной точки в системе координат объекта, которому принадлежит грань.
+ *
+ * @param point точка в плоскости грани, для которой производится расчёт UV-координат
+ * @return UV-координаты заданной точки
+ */
+ public function getUV(point:Point3D):Point {
+ return getUVFast(point, normal);
+ }
+
+ /**
+ * @private
+ * Расчёт UV-координат для произвольной точки в локальной системе координат без расчёта
+ * локальной нормали грани. Используется для оптимизации.
+ *
+ * @param point точка в плоскости грани, для которой производится расчёт UV-координат
+ * @param normal нормаль плоскости грани в локальной системе координат
+ * @return UV-координаты заданной точки
+ */
+ alternativa3d function getUVFast(point:Point3D, normal:Point3D):Point {
+ if (_aUV == null || _bUV == null || _cUV == null) {
+ return null;
+ }
+
+ // Выбор наиболее длинной оси нормали
+ var dir:uint;
+ if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.y < 0) ? -normal.y : normal.y)) {
+ if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.z < 0) ? -normal.z : normal.z)) {
+ dir = 0;
+ } else {
+ dir = 2;
+ }
+ } else {
+ if (((normal.y < 0) ? -normal.y : normal.y) > ((normal.z < 0) ? -normal.z : normal.z)) {
+ dir = 1;
+ } else {
+ dir = 2;
+ }
+ }
+
+ // Расчёт соотношения по векторам AB и AC
+ var v:Vertex = _vertices[0];
+ var a:Point3D = v._coords;
+ v = _vertices[1];
+ var b:Point3D = v._coords;
+ v = _vertices[2];
+ var c:Point3D = v._coords;
+
+ var ab1:Number = (dir == 0) ? (b.y - a.y) : (b.x - a.x);
+ var ab2:Number = (dir == 2) ? (b.y - a.y) : (b.z - a.z);
+ var ac1:Number = (dir == 0) ? (c.y - a.y) : (c.x - a.x);
+ var ac2:Number = (dir == 2) ? (c.y - a.y) : (c.z - a.z);
+ var det:Number = ab1*ac2 - ac1*ab2;
+
+ var ad1:Number = (dir == 0) ? (point.y - a.y) : (point.x - a.x);
+ var ad2:Number = (dir == 2) ? (point.y - a.y) : (point.z - a.z);
+ var abk:Number = (ad1*ac2 - ac1*ad2)/det;
+ var ack:Number = (ab1*ad2 - ad1*ab2)/det;
+
+ // Интерполяция по UV первых точек
+ var abu:Number = _bUV.x - _aUV.x;
+ var abv:Number = _bUV.y - _aUV.y;
+ var acu:Number = _cUV.x - _aUV.x;
+ var acv:Number = _cUV.y - _aUV.y;
+
+ return new Point(_aUV.x + abu*abk + acu*ack, _aUV.y + abv*abk + acv*ack);
+ }
+
+ /**
+ * Множество граней, имеющих общие рёбра с текущей гранью.
+ */
+ public function get edgeJoinedFaces():Set {
+ var res:Set = new Set(true);
+ // Перебираем точки грани
+ for (var i:uint = 0; i < _verticesCount; i++) {
+ var a:Vertex = _vertices[i];
+ var b:Vertex = _vertices[(i < _verticesCount - 1) ? (i + 1) : 0];
+
+ // Перебираем грани текущей точки
+ for (var key:* in a._faces) {
+ var face:Face = key;
+ // Если это другая грань и у неё также есть следующая точка
+ if (face != this && face._vertices.indexOf(b) >= 0) {
+ // Значит у граней общее ребро
+ res[face] = true;
+ }
+ }
+ }
+ return res;
+ }
+
+ /**
+ * @private
+ * Удаление всех вершин из грани.
+ * Очистка базового примитива.
+ */
+ alternativa3d function removeVertices():void {
+ // Удалить вершины
+ for (var i:uint = 0; i < _verticesCount; i++) {
+ // Удаляем из списка
+ var vertex:Vertex = _vertices.pop();
+ // Удаляем вершину из грани
+ vertex.removeFromFace(this);
+ }
+ // Очищаем вершины в примитиве
+ primitive.points.length = 0;
+ // Обнуляем количество вершин
+ _verticesCount = 0;
+ }
+
+ /**
+ * @private
+ * Добавление грани на сцену
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {
+ // При добавлении на сцену рассчитываем плоскость и UV
+ scene.addOperation(calculateNormalOperation);
+ scene.addOperation(calculateBaseUVOperation);
+
+ // Подписываем сцену на операции
+ updatePrimitiveOperation.addSequel(scene.calculateBSPOperation);
+ updateMaterialOperation.addSequel(scene.changePrimitivesOperation);
+ }
+
+ /**
+ * @private
+ * Удаление грани из сцены
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(calculateBaseUVOperation);
+ scene.removeOperation(calculateNormalOperation);
+ scene.removeOperation(updatePrimitiveOperation);
+ scene.removeOperation(updateMaterialOperation);
+
+ // Удаляем примитивы из сцены
+ removePrimitive(primitive);
+
+ // Посылаем операцию сцены на расчёт BSP
+ scene.addOperation(scene.calculateBSPOperation);
+
+ // Отписываем сцену от операций
+ updatePrimitiveOperation.removeSequel(scene.calculateBSPOperation);
+ updateMaterialOperation.removeSequel(scene.changePrimitivesOperation);
+ }
+
+ /**
+ * @private
+ * Добавление грани в меш
+ * @param mesh
+ */
+ alternativa3d function addToMesh(mesh:Mesh):void {
+ // Подписка на операции меша
+ mesh.changeCoordsOperation.addSequel(updatePrimitiveOperation);
+ // При перемещении меша, пересчитать UV. При вращении вызовется calculateNormal и UV пересчитаются.
+ mesh.changeCoordsOperation.addSequel(calculateUVOperation);
+ mesh.changeRotationOrScaleOperation.addSequel(calculateNormalOperation);
+ mesh.calculateMobilityOperation.addSequel(updatePrimitiveOperation);
+ // Сохранить меш
+ _mesh = mesh;
+ }
+
+ /**
+ * @private
+ * Удаление грани из меша
+ * @param mesh
+ */
+ alternativa3d function removeFromMesh(mesh:Mesh):void {
+ // Отписка от операций меша
+ mesh.changeCoordsOperation.removeSequel(updatePrimitiveOperation);
+ mesh.changeCoordsOperation.removeSequel(calculateUVOperation);
+ mesh.changeRotationOrScaleOperation.removeSequel(calculateNormalOperation);
+ mesh.calculateMobilityOperation.removeSequel(updatePrimitiveOperation);
+ // Удалить ссылку на меш
+ _mesh = null;
+ }
+
+ /**
+ * @private
+ * Добавление к поверхности
+ *
+ * @param surface
+ */
+ alternativa3d function addToSurface(surface:Surface):void {
+ // Подписка поверхности на операции
+ surface.changeMaterialOperation.addSequel(updateMaterialOperation);
+ // Если при смене поверхности изменился материал
+ if (_mesh != null && (_surface != null && _surface._material != surface._material || _surface == null && surface._material != null)) {
+ // Отправляем сигнал смены материала
+ _mesh.addOperationToScene(updateMaterialOperation);
+ }
+ // Сохранить поверхность
+ _surface = surface;
+ }
+
+ /**
+ * @private
+ * Удаление из поверхности
+ *
+ * @param surface
+ */
+ alternativa3d function removeFromSurface(surface:Surface):void {
+ // Отписка поверхности от операций
+ surface.changeMaterialOperation.removeSequel(updateMaterialOperation);
+ // Если был материал
+ if (surface._material != null) {
+ // Отправляем сигнал смены материала
+ _mesh.addOperationToScene(updateMaterialOperation);
+ }
+ // Удалить ссылку на поверхность
+ _surface = null;
+ }
+
+ /**
+ * Строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ var res:String = "[Face ID:" + id + ((_verticesCount > 0) ? " vertices:" : "");
+ for (var i:uint = 0; i < _verticesCount; i++) {
+ var vertex:Vertex = _vertices[i];
+ res += vertex.id + ((i < _verticesCount - 1) ? ", " : "");
+ }
+ res += "]";
+ return res;
+ }
+
+ /**
+ * Добавление обработчика события.
+ *
+ * @param type тип события
+ * @param listener обработчик события
+ * @param useCapture не используется,
+ * @param priority приоритет обработчика. Обработчики с большим приоритетом выполняются раньше. Обработчики с одинаковым приоритетом
+ * выполняются в порядке их добавления.
+ * @param useWeakReference флаг использования слабой ссылки для обработчика
+ */
+ public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
+ if (dispatcher == null) {
+ dispatcher = new EventDispatcher(this);
+ }
+ useCapture = false;
+ dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
+ }
+
+ /**
+ * Рассылка события.
+ *
+ * @param event посылаемое событие
+ * @return false
+ */
+ public function dispatchEvent(event:Event):Boolean {
+ if (dispatcher != null) {
+ dispatcher.dispatchEvent(event);
+ }
+ return false;
+ }
+
+ /**
+ * Проверка наличия зарегистрированных обработчиков события указанного типа.
+ *
+ * @param type тип события
+ * @return true если есть обработчики события указанного типа, иначе false
+ */
+ public function hasEventListener(type:String):Boolean {
+ if (dispatcher != null) {
+ return dispatcher.hasEventListener(type);
+ }
+ return false;
+ }
+
+ /**
+ * Удаление обработчика события.
+ *
+ * @param type тип события
+ * @param listener обработчик события
+ * @param useCapture не используется
+ */
+ public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void {
+ if (dispatcher != null) {
+ useCapture = false;
+ dispatcher.removeEventListener(type, listener, useCapture);
+ }
+ }
+
+ /**
+ *
+ */
+ public function willTrigger(type:String):Boolean {
+ if (dispatcher != null) {
+ return dispatcher.willTrigger(type);
+ }
+ return false;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Mesh.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Mesh.as
new file mode 100644
index 0000000..e6b6d93
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Mesh.as
@@ -0,0 +1,986 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.errors.FaceExistsError;
+ import alternativa.engine3d.errors.FaceNeedMoreVerticesError;
+ import alternativa.engine3d.errors.FaceNotFoundError;
+ import alternativa.engine3d.errors.InvalidIDError;
+ import alternativa.engine3d.errors.SurfaceExistsError;
+ import alternativa.engine3d.errors.SurfaceNotFoundError;
+ import alternativa.engine3d.errors.VertexExistsError;
+ import alternativa.engine3d.errors.VertexNotFoundError;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.types.Map;
+ import alternativa.utils.ObjectUtils;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Полигональный объект — базовый класс для трёхмерных объектов, состоящих из граней-полигонов. Объект
+ * содержит в себе наборы вершин, граней и поверхностей.
+ */
+ public class Mesh extends Object3D {
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ // Инкременты для идентификаторов вершин, граней и поверхностей
+ private var vertexIDCounter:uint = 0;
+ private var faceIDCounter:uint = 0;
+ private var surfaceIDCounter:uint = 0;
+
+ /**
+ * @private
+ * Список вершин
+ */
+ alternativa3d var _vertices:Map = new Map();
+ /**
+ * @private
+ * Список граней
+ */
+ alternativa3d var _faces:Map = new Map();
+ /**
+ * @private
+ * Список поверхностей
+ */
+ alternativa3d var _surfaces:Map = new Map();
+
+ /**
+ * Создание экземпляра полигонального объекта.
+ *
+ * @param name имя экземпляра
+ */
+ public function Mesh(name:String = null) {
+ super(name);
+ }
+
+ /**
+ * Добавление новой вершины к объекту.
+ *
+ * @param x координата X в локальной системе координат объекта
+ * @param y координата Y в локальной системе координат объекта
+ * @param z координата Z в локальной системе координат объекта
+ * @param id идентификатор вершины. Если указано значение null, идентификатор будет
+ * сформирован автоматически.
+ *
+ * @return экземпляр добавленной вершины
+ *
+ * @throws alternativa.engine3d.errors.VertexExistsError объект уже содержит вершину с указанным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function createVertex(x:Number = 0, y:Number = 0, z:Number = 0, id:Object = null):Vertex {
+ // Проверяем ID
+ if (id != null) {
+ // Если уже есть вершина с таким ID
+ if (_vertices[id] != undefined) {
+ if (_vertices[id] is Vertex) {
+ throw new VertexExistsError(id, this);
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ } else {
+ // Ищем первый свободный
+ while (_vertices[vertexIDCounter] != undefined) {
+ vertexIDCounter++;
+ }
+ id = vertexIDCounter;
+ }
+
+ // Создаём вершину
+ var v:Vertex = new Vertex(x, y, z);
+
+ // Добавляем вершину на сцену
+ if (_scene != null) {
+ v.addToScene(_scene);
+ }
+
+ // Добавляем вершину в меш
+ v.addToMesh(this);
+ _vertices[id] = v;
+
+ return v;
+ }
+
+ /**
+ * Удаление вершины из объекта. При удалении вершины из объекта также удаляются все грани, которым принадлежит данная вершина.
+ *
+ * @param vertex экземпляр класса alternativa.engine3d.core.Vertex или идентификатор удаляемой вершины
+ *
+ * @return экземпляр удалённой вершины
+ *
+ * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function removeVertex(vertex:Object):Vertex {
+ var byLink:Boolean = vertex is Vertex;
+
+ // Проверяем на null
+ if (vertex == null) {
+ throw new VertexNotFoundError(null, this);
+ }
+
+ // Проверяем наличие вершины в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Vertex(vertex)._mesh != this) {
+ // Если вершина не в меше
+ throw new VertexNotFoundError(vertex, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_vertices[vertex] == undefined) {
+ // Если нет вершины с таким ID
+ throw new VertexNotFoundError(vertex, this);
+ } else if (!(_vertices[vertex] is Vertex)) {
+ // По этому id не вершина
+ throw new InvalidIDError(vertex, this);
+ }
+ }
+
+ // Находим вершину и её ID
+ var v:Vertex = byLink ? Vertex(vertex) : _vertices[vertex];
+ var id:Object = byLink ? getVertexId(Vertex(vertex)) : vertex;
+
+ // Удаляем вершину из сцены
+ if (_scene != null) {
+ v.removeFromScene(_scene);
+ }
+
+ // Удаляем вершину из меша
+ v.removeFromMesh(this);
+ delete _vertices[id];
+
+ return v;
+ }
+
+ /**
+ * Добавление грани к объекту. В результате выполнения метода в объекте появляется новая грань, не привязанная
+ * ни к одной поверхности.
+ *
+ * @param vertices массив вершин грани, указанных в порядке обхода лицевой стороны грани против часовой
+ * стрелки. Каждый элемент массива может быть либо экземпляром класса alternativa.engine3d.core.Vertex,
+ * либо идентификатором в наборе вершин объекта. В обоих случаях объект должен содержать указанную вершину.
+ * @param id идентификатор грани. Если указано значение null, идентификатор будет
+ * сформирован автоматически.
+ *
+ * @return экземпляр добавленной грани
+ *
+ * @throws alternativa.engine3d.errors.FaceNeedMoreVerticesError в качестве массива вершин был передан
+ * null, либо количество вершин в массиве меньше трёх
+ * @throws alternativa.engine3d.errors.FaceExistsError объект уже содержит грань с заданным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит какую-либо вершину из входного массива
+ *
+ * @see Vertex
+ */
+ public function createFace(vertices:Array, id:Object = null):Face {
+ // Проверяем на null
+ if (vertices == null) {
+ throw new FaceNeedMoreVerticesError(this);
+ }
+
+ // Проверяем ID
+ if (id != null) {
+ // Если уже есть грань с таким ID
+ if (_faces[id] != undefined) {
+ if (_faces[id] is Face) {
+ throw new FaceExistsError(id, this);
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ } else {
+ // Ищем первый свободный ID
+ while (_faces[faceIDCounter] != undefined) {
+ faceIDCounter++;
+ }
+ id = faceIDCounter;
+ }
+
+ // Проверяем количество точек
+ var length:uint = vertices.length;
+ if (length < 3) {
+ throw new FaceNeedMoreVerticesError(this, length);
+ }
+
+ // Проверяем и формируем список вершин
+ var v:Array = new Array();
+ var vertex:Vertex;
+ for (var i:uint = 0; i < length; i++) {
+ if (vertices[i] is Vertex) {
+ // Если работаем со ссылками
+ vertex = vertices[i];
+ if (vertex._mesh != this) {
+ // Если вершина не в меше
+ throw new VertexNotFoundError(vertices[i], this);
+ }
+ } else {
+ // Если работаем с ID
+ if (_vertices[vertices[i]] == null) {
+ // Если нет вершины с таким ID
+ throw new VertexNotFoundError(vertices[i], this);
+ } else if (!(_vertices[vertices[i]] is Vertex)) {
+ // Если id зарезервировано
+ throw new InvalidIDError(vertices[i],this);
+ }
+ vertex = _vertices[vertices[i]];
+ }
+ v.push(vertex);
+ }
+
+ // Создаём грань
+ var f:Face = new Face(v);
+
+ // Добавляем грань на сцену
+ if (_scene != null) {
+ f.addToScene(_scene);
+ }
+
+ // Добавляем грань в меш
+ f.addToMesh(this);
+ _faces[id] = f;
+
+ return f;
+ }
+
+ /**
+ * Удаление грани из объекта. Грань также удаляется из поверхности объекта, которой она принадлежит.
+ *
+ * @param экземпляр класса alternativa.engine3d.core.Face или идентификатор удаляемой грани
+ *
+ * @return экземпляр удалённой грани
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function removeFace(face:Object):Face {
+ var byLink:Boolean = face is Face;
+
+ // Проверяем на null
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие грани в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Face(face)._mesh != this) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_faces[face] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(face, this);
+ } else if (!(_faces[face] is Face)) {
+ throw new InvalidIDError(face, this);
+ }
+ }
+
+ // Находим грань и её ID
+ var f:Face = byLink ? Face(face) : _faces[face] ;
+ var id:Object = byLink ? getFaceId(Face(face)) : face;
+
+ // Удаляем вершины из грани
+ f.removeVertices();
+
+ // Удаляем грань из поверхности
+ if (f._surface != null) {
+ f._surface._faces.remove(f);
+ f.removeFromSurface(f._surface);
+ }
+
+ // Удаляем грань из сцены
+ if (_scene != null) {
+ f.removeFromScene(_scene);
+ }
+
+ // Удаляем грань из меша
+ f.removeFromMesh(this);
+
+ delete _faces[id];
+
+ return f;
+ }
+
+ /**
+ * Добавление новой поверхности к объекту.
+ *
+ * @param faces набор граней, составляющих поверхность. Каждый элемент массива должен быть либо экземпляром класса
+ * alternativa.engine3d.core.Face, либо идентификатором грани. В обоих случаях объект должен содержать
+ * указанную грань. Если значение параметра равно null, то будет создана пустая поверхность. Если
+ * какая-либо грань содержится в другой поверхности, она будет перенесена в новую поверхность.
+ * @param id идентификатор новой поверхности. Если указано значение null, идентификатор будет
+ * сформирован автоматически.
+ *
+ * @return экземпляр добавленной поверхности
+ *
+ * @throws alternativa.engine3d.errors.SurfaceExistsError объект уже содержит поверхность с заданным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Face
+ */
+ public function createSurface(faces:Array = null, id:Object = null):Surface {
+ // Проверяем ID
+ if (id != null) {
+ // Если уже есть поверхность с таким ID
+ if (_surfaces[id] != undefined) {
+ if (_surfaces[id] is Surface) {
+ throw new SurfaceExistsError(id, this);
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ } else {
+ // Ищем первый свободный ID
+ while (_surfaces[surfaceIDCounter] != undefined) {
+ surfaceIDCounter++;
+ }
+ id = surfaceIDCounter;
+ }
+
+ // Создаём поверхность
+ var s:Surface = new Surface();
+
+ // Добавляем поверхность на сцену
+ if (_scene != null) {
+ s.addToScene(_scene);
+ }
+
+ // Добавляем поверхность в меш
+ s.addToMesh(this);
+ _surfaces[id] = s;
+
+ // Добавляем грани, если есть
+ if (faces != null) {
+ var length:uint = faces.length;
+ for (var i:uint = 0; i < length; i++) {
+ s.addFace(faces[i]);
+ }
+ }
+ return s;
+ }
+
+ /**
+ * Удаление поверхности объекта. Из удаляемой поверхности также удаляются все содержащиеся в ней грани.
+ *
+ * @param surface экземпляр класса alternativa.engine3d.core.Face или идентификатор удаляемой поверхности
+ *
+ * @return экземпляр удалённой поверхности
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function removeSurface(surface:Object):Surface {
+ var byLink:Boolean = surface is Surface;
+
+ // Проверяем на null
+ if (surface == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие поверхности в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Surface(surface)._mesh != this) {
+ // Если поверхность не в меше
+ throw new SurfaceNotFoundError(surface, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_surfaces[surface] == undefined) {
+ // Если нет поверхности с таким ID
+ throw new SurfaceNotFoundError(surface, this);
+ } else if (!(_surfaces[surface] is Surface)) {
+ throw new InvalidIDError(surface, this);
+ }
+ }
+
+ // Находим поверхность и её ID
+ var s:Surface = byLink ? Surface(surface) : _surfaces[surface];
+ var id:Object = byLink ? getSurfaceId(Surface(surface)) : surface;
+
+ // Удаляем поверхность из сцены
+ if (_scene != null) {
+ s.removeFromScene(_scene);
+ }
+
+ // Удаляем грани из поверхности
+ s.removeFaces();
+
+ // Удаляем поверхность из меша
+ s.removeFromMesh(this);
+ delete _surfaces[id];
+
+ return s;
+ }
+
+ /**
+ * Добавление всех граней объекта в указанную поверхность.
+ *
+ * @param surface экземпляр класса alternativa.engine3d.core.Surface или идентификатор поверхности, в
+ * которую добавляются грани. Если задан идентификатор, и объект не содержит поверхность с таким идентификатором,
+ * будет создана новая поверхность.
+ *
+ * @param removeSurfaces удалять или нет пустые поверхности после переноса граней
+ *
+ * @return экземпляр поверхности, в которую перенесены грани
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанный экземпляр поверхности
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function moveAllFacesToSurface(surface:Object = null, removeSurfaces:Boolean = false):Surface {
+ var returnSurface:Surface;
+ var returnSurfaceId:Object;
+ if (surface is Surface) {
+ // Работаем с экземпляром Surface
+ if (surface._mesh == this) {
+ returnSurface = Surface(surface);
+ } else {
+ throw new SurfaceNotFoundError(surface, this);
+ }
+ } else {
+ // Работаем с идентификатором
+ if (_surfaces[surface] == undefined) {
+ // Поверхности еще нет
+ returnSurface = createSurface(null, surface);
+ returnSurfaceId = surface;
+ } else {
+ if (_surfaces[surface] is Surface) {
+ returnSurface = _surfaces[surface];
+ } else {
+ // _surfaces[surface] по идентификатору возвращает не Surface
+ throw new InvalidIDError(surface, this);
+ }
+ }
+ }
+ // Перемещаем все грани
+ for each (var face:Face in _faces) {
+ if (face._surface != returnSurface) {
+ returnSurface.addFace(face);
+ }
+ }
+ if (removeSurfaces) {
+ // Удаляем старые, теперь вручную - меньше проверок, но рискованно
+ if (returnSurfaceId == null) {
+ returnSurfaceId = getSurfaceId(returnSurface);
+ }
+ var newSurfaces:Map = new Map();
+ newSurfaces[returnSurfaceId] = returnSurface;
+ delete _surfaces[returnSurfaceId];
+ // Удаляем оставшиеся
+ for (var currentSurfaceId:* in _surfaces) {
+ // Удаляем поверхность из сцены
+ var currentSurface:Surface = _surfaces[currentSurfaceId];
+ if (_scene != null) {
+ currentSurface.removeFromScene(_scene);
+ }
+ // Удаляем поверхность из меша
+ currentSurface.removeFromMesh(this);
+ delete _surfaces[currentSurfaceId];
+ }
+ // Новый список граней
+ _surfaces = newSurfaces;
+ }
+ return returnSurface;
+ }
+
+ /**
+ * Установка материала для указанной поверхности.
+ *
+ * @param material материал, назначаемый поверхности. Один экземпляр SurfaceMaterial можно назначить только одной поверхности.
+ * @param surface экземпляр класса alternativa.engine3d.core.Surface или идентификатор поверхности
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Surface
+ */
+ public function setMaterialToSurface(material:SurfaceMaterial, surface:Object):void {
+ var byLink:Boolean = surface is Surface;
+
+ // Проверяем на null
+ if (surface == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие поверхности в меше
+ if (byLink) {
+ // Если назначаем по ссылке
+ if (Surface(surface)._mesh != this) {
+ // Если поверхность не в меше
+ throw new SurfaceNotFoundError(surface, this);
+ }
+ } else {
+ // Если назначаем по ID
+ if (_surfaces[surface] == undefined) {
+ // Если нет поверхности с таким ID
+ throw new SurfaceNotFoundError(surface, this);
+ } else if (!(_surfaces[surface] is Surface)) {
+ throw new InvalidIDError(surface, this);
+ }
+ }
+
+ // Находим поверхность
+ var s:Surface = byLink ? Surface(surface) : _surfaces[surface];
+
+ // Назначаем материал
+ s.material = material;
+ }
+
+ /**
+ * Установка материала для всех поверхностей объекта. Для каждой поверхности устанавливается копия материала.
+ * При передаче null в качестве параметра происходит сброс материалов у всех поверхностей.
+ *
+ * @param material устанавливаемый материал
+ */
+ public function cloneMaterialToAllSurfaces(material:SurfaceMaterial):void {
+ for each (var surface:Surface in _surfaces) {
+ surface.material = (material != null) ? SurfaceMaterial(material.clone()) : null;
+ }
+ }
+
+ /**
+ * Установка UV-координат для указанной грани объекта. Матрица преобразования UV-координат расчитывается по
+ * UV-координатам первых трёх вершин грани, поэтому для корректного текстурирования эти вершины должны образовывать
+ * невырожденный треугольник в UV-пространстве.
+ *
+ * @param aUV UV-координаты, соответствующие первой вершине грани
+ * @param bUV UV-координаты, соответствующие второй вершине грани
+ * @param cUV UV-координаты, соответствующие третьей вершине грани
+ * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Face
+ */
+ public function setUVsToFace(aUV:Point, bUV:Point, cUV:Point, face:Object):void {
+ var byLink:Boolean = face is Face;
+
+ // Проверяем на null
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие грани в меше
+ if (byLink) {
+ // Если назначаем по ссылке
+ if (Face(face)._mesh != this) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ } else {
+ // Если назначаем по ID
+ if (_faces[face] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(face, this);
+ } else if (!(_faces[face] is Face)) {
+ throw new InvalidIDError(face, this);
+ }
+ }
+
+ // Находим грань
+ var f:Face = byLink ? Face(face) : _faces[face];
+
+ // Назначаем UV-координаты
+ f.aUV = aUV;
+ f.bUV = bUV;
+ f.cUV = cUV;
+ }
+
+ /**
+ * Набор вершин объекта. Ключами ассоциативного массива являются идентификаторы вершин, значениями - экземпляры вершин.
+ */
+ public function get vertices():Map {
+ return _vertices.clone();
+ }
+
+ /**
+ * Набор граней объекта. Ключами ассоциативного массива являются идентификаторы граней, значениями - экземпляры граней.
+ */
+ public function get faces():Map {
+ return _faces.clone();
+ }
+
+ /**
+ * Набор поверхностей объекта. Ключами ассоциативного массива являются идентификаторы поверхностей, значениями - экземпляры поверхностей.
+ */
+ public function get surfaces():Map {
+ return _surfaces.clone();
+ }
+
+ /**
+ * Получение вершины объекта по её идентификатору.
+ *
+ * @param id идентификатор вершины
+ *
+ * @return экземпляр вершины с указанным идентификатором
+ *
+ * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит вершину с указанным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function getVertexById(id:Object):Vertex {
+ if (id == null) {
+ throw new VertexNotFoundError(null, this);
+ }
+ if (_vertices[id] == undefined) {
+ // Если нет вершины с таким ID
+ throw new VertexNotFoundError(id, this);
+ } else {
+ if (_vertices[id] is Vertex) {
+ return _vertices[id];
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ }
+
+ /**
+ * Получение идентификатора вершины объекта.
+ *
+ * @param экземпляр вершины
+ *
+ * @return идентификатор указанной вершины
+ *
+ * @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину
+ */
+ public function getVertexId(vertex:Vertex):Object {
+ if (vertex == null) {
+ throw new VertexNotFoundError(null, this);
+ }
+ if (vertex._mesh != this) {
+ // Если вершина не в меше
+ throw new VertexNotFoundError(vertex, this);
+ }
+ for (var i:Object in _vertices) {
+ if (_vertices[i] == vertex) {
+ return i;
+ }
+ }
+ throw new VertexNotFoundError(vertex, this);
+ }
+
+ /**
+ * Проверка наличия вершины в объекте.
+ *
+ * @param vertex экземпляр класса alternativa.engine3d.core.Vertex или идентификатор вершины
+ *
+ * @return true, если объект содержит указанную вершину, иначе false
+ *
+ * @throws alternativa.engine3d.errors.VertexNotFoundError в качестве vertex был передан null
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Vertex
+ */
+ public function hasVertex(vertex:Object):Boolean {
+ if (vertex == null) {
+ throw new VertexNotFoundError(null, this);
+ }
+ if (vertex is Vertex) {
+ // Проверка вершины
+ return vertex._mesh == this;
+ } else {
+ // Проверка ID вершины
+ if (_vertices[vertex] != undefined) {
+ // По этому ID есть объект
+ if (_vertices[vertex] is Vertex) {
+ // Объект является вершиной
+ return true;
+ } else {
+ // ID некорректный
+ throw new InvalidIDError(vertex, this);
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Получение грани объекта по ее идентификатору.
+ *
+ * @param id идентификатор грани
+ *
+ * @return экземпляр грани с указанным идентификатором
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит грань с указанным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function getFaceById(id:Object):Face {
+ if (id == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+ if (_faces[id] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(id, this);
+ } else {
+ if (_faces[id] is Face) {
+ return _faces[id];
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ }
+
+ /**
+ * Получение идентификатора грани объекта.
+ *
+ * @param face экземпляр грани
+ *
+ * @return идентификатор указанной грани
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
+ */
+ public function getFaceId(face:Face):Object {
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+ if (face._mesh != this) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ for (var i:Object in _faces) {
+ if (_faces[i] == face) {
+ return i;
+ }
+ }
+ throw new FaceNotFoundError(face, this);
+ }
+
+ /**
+ * Проверка наличия грани в объекте.
+ *
+ * @param face экземпляр класса Face или идентификатор грани
+ *
+ * @return true, если объект содержит указанную грань, иначе false
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError в качестве face был указан null
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function hasFace(face:Object):Boolean {
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+ if (face is Face) {
+ // Проверка грани
+ return face._mesh == this;
+ } else {
+ // Проверка ID грани
+ if (_faces[face] != undefined) {
+ // По этому ID есть объект
+ if (_faces[face] is Face) {
+ // Объект является гранью
+ return true;
+ } else {
+ // ID некорректный
+ throw new InvalidIDError(face, this);
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Получение поверхности объекта по ее идентификатору
+ *
+ * @param id идентификатор поверхности
+ *
+ * @return экземпляр поверхности с указанным идентификатором
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит поверхность с указанным идентификатором
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function getSurfaceById(id:Object):Surface {
+ if (id == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+ if (_surfaces[id] == undefined) {
+ // Если нет поверхности с таким ID
+ throw new SurfaceNotFoundError(id, this);
+ } else {
+ if (_surfaces[id] is Surface) {
+ return _surfaces[id];
+ } else {
+ throw new InvalidIDError(id, this);
+ }
+ }
+ }
+
+ /**
+ * Получение идентификатора поверхности объекта.
+ *
+ * @param surface экземпляр поверхности
+ *
+ * @return идентификатор указанной поверхности
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
+ */
+ public function getSurfaceId(surface:Surface):Object {
+ if (surface == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+ if (surface._mesh != this) {
+ // Если поверхность не в меше
+ throw new SurfaceNotFoundError(surface, this);
+ }
+ for (var i:Object in _surfaces) {
+ if (_surfaces[i] == surface) {
+ return i;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Проверка наличия поверхности в объекте.
+ *
+ * @param surface экземпляр класса Surface или идентификатор поверхности
+ *
+ * @return true, если объект содержит указанную поверхность, иначе false
+ *
+ * @throws alternativa.engine3d.errors.SurfaceNotFoundError в качестве surface был передан null
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ */
+ public function hasSurface(surface:Object):Boolean {
+ if (surface == null) {
+ throw new SurfaceNotFoundError(null, this);
+ }
+ if (surface is Surface) {
+ // Проверка поверхности
+ return surface._mesh == this;
+ } else {
+ // Проверка ID поверхности
+ if (_surfaces[surface] != undefined) {
+ // По этому ID есть объект
+ if (_surfaces[surface] is Surface) {
+ // Объект является поверхностью
+ return true;
+ } else {
+ // ID некорректный
+ throw new InvalidIDError(surface, this);
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function setScene(value:Scene3D):void {
+ if (_scene != value) {
+ var vertex:Vertex;
+ var face:Face;
+ var surface:Surface;
+ if (value != null) {
+ // Добавить вершины на сцену
+ for each (vertex in _vertices) {
+ vertex.addToScene(value);
+ }
+ // Добавить грани на сцену
+ for each (face in _faces) {
+ face.addToScene(value);
+ }
+ // Добавить поверхности на сцену
+ for each (surface in _surfaces) {
+ surface.addToScene(value);
+ }
+ } else {
+ // Удалить вершины из сцены
+ for each (vertex in _vertices) {
+ vertex.removeFromScene(_scene);
+ }
+ // Удалить грани из сцены
+ for each (face in _faces) {
+ face.removeFromScene(_scene);
+ }
+ // Удалить поверхности из сцены
+ for each (surface in _surfaces) {
+ surface.removeFromScene(_scene);
+ }
+ }
+ }
+ super.setScene(value);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function defaultName():String {
+ return "mesh" + ++counter;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function toString():String {
+ return "[" + ObjectUtils.getClassName(this) + " " + _name + " vertices: " + _vertices.length + " faces: " + _faces.length + "]";
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function createEmptyObject():Object3D {
+ return new Mesh();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function clonePropertiesFrom(source:Object3D):void {
+ super.clonePropertiesFrom(source);
+
+ var src:Mesh = Mesh(source);
+
+ var id:*;
+ var len:int;
+ var i:int;
+ // Копирование вершин
+ var vertexMap:Map = new Map(true);
+ for (id in src._vertices) {
+ var sourceVertex:Vertex = src._vertices[id];
+ vertexMap[sourceVertex] = createVertex(sourceVertex.x, sourceVertex.y, sourceVertex.z, id);
+ }
+
+ // Копирование граней
+ var faceMap:Map = new Map(true);
+ for (id in src._faces) {
+ var sourceFace:Face = src._faces[id];
+ len = sourceFace._vertices.length;
+ var faceVertices:Array = new Array(len);
+ for (i = 0; i < len; i++) {
+ faceVertices[i] = vertexMap[sourceFace._vertices[i]];
+ }
+ var newFace:Face = createFace(faceVertices, id);
+ newFace.aUV = sourceFace._aUV;
+ newFace.bUV = sourceFace._bUV;
+ newFace.cUV = sourceFace._cUV;
+ faceMap[sourceFace] = newFace;
+ }
+
+ // Копирование поверхностей
+ for (id in src._surfaces) {
+ var sourceSurface:Surface = src._surfaces[id];
+ var surfaceFaces:Array = sourceSurface._faces.toArray();
+ len = surfaceFaces.length;
+ for (i = 0; i < len; i++) {
+ surfaceFaces[i] = faceMap[surfaceFaces[i]];
+ }
+ var surface:Surface = createSurface(surfaceFaces, id);
+ var sourceMaterial:SurfaceMaterial = sourceSurface.material;
+ if (sourceMaterial != null) {
+ surface.material = SurfaceMaterial(sourceMaterial.clone());
+ }
+ }
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Object3D.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Object3D.as
new file mode 100644
index 0000000..be74ffd
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Object3D.as
@@ -0,0 +1,991 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.errors.Object3DHierarchyError;
+ import alternativa.engine3d.errors.Object3DNotFoundError;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+ import alternativa.utils.ObjectUtils;
+
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IEventDispatcher;
+
+ use namespace alternativa3d;
+
+ /**
+ * Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одним и тем же объектом.
+ * Между нажатием и отпусканием кнопки могут происходить любые другие события.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.CLICK
+ */
+ [Event (name="click", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь нажимает левую кнопку мыши над объектом.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN
+ */
+ [Event (name="mouseDown", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь отпускает левую кнопку мыши над объектом.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP
+ */
+ [Event (name="mouseUp", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь наводит курсор мыши на объект.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER
+ */
+ [Event (name="mouseOver", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь уводит курсор мыши с объекта.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT
+ */
+ [Event (name="mouseOut", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь перемещает курсор мыши над объектом.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE
+ */
+ [Event (name="mouseMove", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь вращает колесо мыши над объектом.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL
+ */
+ [Event (name="mouseWheel", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Базовый класс для объектов, находящихся в сцене. Класс реализует иерархию объектов сцены, а также содержит сведения
+ * о трансформации объекта как единого целого.
+ *
+ * Масштабирование, ориентация и положение объекта задаются в родительской системе координат. Результирующая
+ * локальная трансформация является композицией операций масштабирования, поворотов объекта относительно осей
+ * X, Y, Z и параллельного переноса центра объекта из начала координат.
+ * Операции применяются в порядке их перечисления.
+ *
+ * Глобальная трансформация (в системе координат корневого объекта сцены) является композицией трансформаций
+ * самого объекта и всех его предков по иерархии объектов сцены.
+ *
+ * Класс реализует интерфейс flash.events.IEventDispatcher и может рассылать мышиные события, содержащие информацию
+ * о точке в трёхмерном пространстве, в которой произошло событие. На данный момент не реализованы capture и bubbling фазы рассылки
+ * событий.
+ */
+ public class Object3D implements IEventDispatcher {
+ /**
+ * @private
+ * Вспомогательная матрица
+ */
+ alternativa3d static var matrix1:Matrix3D = new Matrix3D();
+ /**
+ * @private
+ * Вспомогательная матрица
+ */
+ alternativa3d static var matrix2:Matrix3D = new Matrix3D();
+
+ /**
+ * @private
+ * Поворот или масштабирование
+ */
+ alternativa3d var changeRotationOrScaleOperation:Operation = new Operation("changeRotationOrScale", this);
+ /**
+ * @private
+ * Перемещение
+ */
+ alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this);
+ /**
+ * @private
+ * Расчёт матрицы трансформации
+ */
+ alternativa3d var calculateTransformationOperation:Operation = new Operation("calculateTransformation", this, calculateTransformation, Operation.OBJECT_CALCULATE_TRANSFORMATION);
+ /**
+ * @private
+ * Изменение уровеня мобильности
+ */
+ alternativa3d var calculateMobilityOperation:Operation = new Operation("calculateMobility", this, calculateMobility, Operation.OBJECT_CALCULATE_MOBILITY);
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ /**
+ * @private
+ * Наименование
+ */
+ alternativa3d var _name:String;
+ /**
+ * @private
+ * Сцена
+ */
+ alternativa3d var _scene:Scene3D;
+ /**
+ * @private
+ * Родительский объект
+ */
+ alternativa3d var _parent:Object3D;
+ /**
+ * @private
+ * Дочерние объекты
+ */
+ alternativa3d var _children:Set = new Set();
+ /**
+ * @private
+ * Уровень мобильности
+ */
+ alternativa3d var _mobility:int = 0;
+ /**
+ * @private
+ */
+ alternativa3d var inheritedMobility:int;
+ /**
+ * @private
+ * Координаты объекта относительно родителя
+ */
+ alternativa3d var _coords:Point3D = new Point3D();
+ /**
+ * @private
+ * Поворот объекта по оси X относительно родителя. Угол измеряется в радианах.
+ */
+ alternativa3d var _rotationX:Number = 0;
+ /**
+ * @private
+ * Поворот объекта по оси Y относительно родителя. Угол измеряется в радианах.
+ */
+ alternativa3d var _rotationY:Number = 0;
+ /**
+ * @private
+ * Поворот объекта по оси Z относительно родителя. Угол измеряется в радианах.
+ */
+ alternativa3d var _rotationZ:Number = 0;
+ /**
+ * @private
+ * Мастшаб объекта по оси X относительно родителя
+ */
+ alternativa3d var _scaleX:Number = 1;
+ /**
+ * @private
+ * Мастшаб объекта по оси Y относительно родителя
+ */
+ alternativa3d var _scaleY:Number = 1;
+ /**
+ * @private
+ * Мастшаб объекта по оси Z относительно родителя
+ */
+ alternativa3d var _scaleZ:Number = 1;
+ /**
+ * @private
+ * Полная матрица трансформации, переводящая координаты из локальной системы координат объекта в систему координат сцены
+ */
+ alternativa3d var _transformation:Matrix3D = new Matrix3D();
+ /**
+ * @private
+ * Координаты в сцене
+ */
+ alternativa3d var globalCoords:Point3D = new Point3D();
+
+ /**
+ * Флаг указывает, будет ли объект принимать мышиные события.
+ */
+ public var mouseEnabled:Boolean = true;
+ /**
+ * Диспетчер событий.
+ */
+ private var dispatcher:EventDispatcher;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param name имя экземпляра
+ */
+ public function Object3D(name:String = null) {
+ // Имя по-умолчанию
+ _name = (name != null) ? name : defaultName();
+
+ // Последствия операций
+ changeRotationOrScaleOperation.addSequel(calculateTransformationOperation);
+ changeCoordsOperation.addSequel(calculateTransformationOperation);
+ }
+
+ /**
+ * @private
+ * Расчёт трансформации
+ */
+ alternativa3d function calculateTransformation():void {
+ if (changeRotationOrScaleOperation.queued) {
+ // Если полная трансформация
+ _transformation.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ);
+ if (_parent != null) {
+ _transformation.combine(_parent._transformation);
+ }
+ // Сохраняем глобальные координаты объекта
+ globalCoords.x = _transformation.d;
+ globalCoords.y = _transformation.h;
+ globalCoords.z = _transformation.l;
+ } else {
+ // Если только перемещение
+ globalCoords.copy(_coords);
+ if (_parent != null) {
+ globalCoords.transform(_parent._transformation);
+ }
+ _transformation.offset(globalCoords.x, globalCoords.y, globalCoords.z);
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт общей мобильности
+ */
+ private function calculateMobility():void {
+ inheritedMobility = ((_parent != null) ? _parent.inheritedMobility : 0) + _mobility;
+ }
+
+ /**
+ * Добавляет дочерний объект. Добавляемый объект удаляется из списка детей предыдущего родителя.
+ * Новой сценой дочернего объекта становится сцена родителя.
+ *
+ * @param child добавляемый объект
+ *
+ * @return добавленный объект
+ *
+ * @throws alternativa.engine3d.errors.Object3DHierarchyError нарушение иерархии объектов сцены
+ */
+ public function addChild(child:Object3D):Object3D {
+
+ // Проверка на null
+ if (child == null) {
+ throw new Object3DHierarchyError(null, this);
+ }
+
+ // Проверка на наличие
+ if (child._parent == this) {
+ return child;
+ }
+
+ // Проверка на добавление к самому себе
+ if (child == this) {
+ throw new Object3DHierarchyError(this, this);
+ }
+
+ // Проверка на добавление родительского объекта
+ if (child._scene == _scene) {
+ // Если объект был в той же сцене, либо оба не были в сцене
+ var parentObject:Object3D = _parent;
+ while (parentObject != null) {
+ if (child == parentObject) {
+ throw new Object3DHierarchyError(child, this);
+ return;
+ }
+ parentObject = parentObject._parent;
+ }
+ }
+
+ // Если объект был в другом объекте
+ if (child._parent != null) {
+ // Удалить его оттуда
+ child._parent._children.remove(child);
+ } else {
+ // Если объект был корневым в сцене
+ if (child._scene != null && child._scene._root == child) {
+ child._scene.root = null;
+ }
+ }
+
+ // Добавляем в список
+ _children.add(child);
+ // Указываем себя как родителя
+ child.setParent(this);
+ // Устанавливаем уровни
+ child.setLevel((calculateTransformationOperation.priority & 0xFFFFFF) + 1);
+ // Указываем сцену
+ child.setScene(_scene);
+
+ return child;
+ }
+
+ /**
+ * Удаляет указанный объект из списка детей.
+ *
+ * @param child удаляемый дочерний объект
+ *
+ * @return удаленный объект
+ *
+ * @throws alternativa.engine3d.errors.Object3DNotFoundError указанный объект не содержится в списке детей текущего объекта
+ */
+ public function removeChild(child:Object3D):Object3D {
+ // Проверка на null
+ if (child == null) {
+ throw new Object3DNotFoundError(null, this);
+ }
+ // Проверка на наличие
+ if (child._parent != this) {
+ throw new Object3DNotFoundError(child, this);
+ }
+ // Убираем из списка
+ _children.remove(child);
+ // Удаляем ссылку на родителя
+ child.setParent(null);
+ // Удаляем ссылку на сцену
+ child.setScene(null);
+
+ return child;
+ }
+
+ /**
+ * @private
+ * Установка родительского объекта.
+ *
+ * @param value родительский объект
+ */
+ alternativa3d function setParent(value:Object3D):void {
+ // Отписываемся от сигналов старого родителя
+ if (_parent != null) {
+ removeParentSequels();
+ }
+ // Сохранить родителя
+ _parent = value;
+ // Если устанавливаем родителя
+ if (value != null) {
+ // Подписка на сигналы родителя
+ addParentSequels();
+ }
+ }
+
+ /**
+ * @private
+ * Установка новой сцены для объекта.
+ *
+ * @param value сцена
+ */
+ alternativa3d function setScene(value:Scene3D):void {
+ if (_scene != value) {
+ // Если была сцена
+ if (_scene != null) {
+ // Удалиться из сцены
+ removeFromScene(_scene);
+ }
+ // Если новая сцена
+ if (value != null) {
+ // Добавиться на сцену
+ addToScene(value);
+ }
+ // Сохранить сцену
+ _scene = value;
+ } else {
+ // Посылаем операцию трансформации
+ addOperationToScene(changeRotationOrScaleOperation);
+ // Посылаем операцию пересчёта мобильности
+ addOperationToScene(calculateMobilityOperation);
+ }
+ // Установить эту сцену у дочерних объектов
+ for (var key:* in _children) {
+ var object:Object3D = key;
+ object.setScene(_scene);
+ }
+ }
+
+ /**
+ * @private
+ * Установка уровня операции трансформации.
+ *
+ * @param value уровень операции трансформации
+ */
+ alternativa3d function setLevel(value:uint):void {
+ // Установить уровень операции трансформации и расчёта мобильности
+ calculateTransformationOperation.priority = (calculateTransformationOperation.priority & 0xFF000000) | value;
+ calculateMobilityOperation.priority = (calculateMobilityOperation.priority & 0xFF000000) | value;
+ // Установить уровни у дочерних объектов
+ for (var key:* in _children) {
+ var object:Object3D = key;
+ object.setLevel(value + 1);
+ }
+ }
+
+ /**
+ * @private
+ * Подписка на сигналы родителя.
+ */
+ private function addParentSequels():void {
+ _parent.changeCoordsOperation.addSequel(changeCoordsOperation);
+ _parent.changeRotationOrScaleOperation.addSequel(changeRotationOrScaleOperation);
+ _parent.calculateMobilityOperation.addSequel(calculateMobilityOperation);
+ }
+
+ /**
+ * @private
+ * Удаление подписки на сигналы родителя.
+ */
+ private function removeParentSequels():void {
+ _parent.changeCoordsOperation.removeSequel(changeCoordsOperation);
+ _parent.changeRotationOrScaleOperation.removeSequel(changeRotationOrScaleOperation);
+ _parent.calculateMobilityOperation.removeSequel(calculateMobilityOperation);
+ }
+
+ /**
+ * Метод вызывается при добавлении объекта на сцену. Наследники могут переопределять метод для выполнения
+ * специфических действий.
+ *
+ * @param scene сцена, в которую добавляется объект
+ */
+ protected function addToScene(scene:Scene3D):void {
+ // При добавлении на сцену полная трансформация и расчёт мобильности
+ scene.addOperation(changeRotationOrScaleOperation);
+ scene.addOperation(calculateMobilityOperation);
+ }
+
+ /**
+ * Метод вызывается при удалении объекта со сцены. Наследники могут переопределять метод для выполнения
+ * специфических действий.
+ *
+ * @param scene сцена, из которой удаляется объект
+ */
+ protected function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(changeRotationOrScaleOperation);
+ scene.removeOperation(changeCoordsOperation);
+ scene.removeOperation(calculateMobilityOperation);
+ }
+
+ /**
+ * Имя объекта.
+ */
+ public function get name():String {
+ return _name;
+ }
+
+ /**
+ * @private
+ */
+ public function set name(value:String):void {
+ _name = value;
+ }
+
+ /**
+ * Сцена, которой принадлежит объект.
+ */
+ public function get scene():Scene3D {
+ return _scene;
+ }
+
+ /**
+ * Родительский объект.
+ */
+ public function get parent():Object3D {
+ return _parent;
+ }
+
+ /**
+ * Набор дочерних объектов.
+ */
+ public function get children():Set {
+ return _children.clone();
+ }
+
+ /**
+ * Уровень мобильности. Результирующая мобильность объекта является суммой мобильностей объекта и всех его предков
+ * по иерархии объектов в сцене. Результирующая мобильность влияет на положение объекта в BSP-дереве. Менее мобильные
+ * объекты находятся ближе к корню дерева, чем более мобильные.
+ */
+ public function get mobility():int {
+ return _mobility;
+ }
+
+ /**
+ * @private
+ */
+ public function set mobility(value:int):void {
+ if (_mobility != value) {
+ _mobility = value;
+ addOperationToScene(calculateMobilityOperation);
+ }
+ }
+
+ /**
+ * Координата X.
+ */
+ public function get x():Number {
+ return _coords.x;
+ }
+
+ /**
+ * Координата Y.
+ */
+ public function get y():Number {
+ return _coords.y;
+ }
+
+ /**
+ * Координата Z.
+ */
+ public function get z():Number {
+ return _coords.z;
+ }
+
+ /**
+ * @private
+ */
+ public function set x(value:Number):void {
+ if (_coords.x != value) {
+ _coords.x = value;
+ addOperationToScene(changeCoordsOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set y(value:Number):void {
+ if (_coords.y != value) {
+ _coords.y = value;
+ addOperationToScene(changeCoordsOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set z(value:Number):void {
+ if (_coords.z != value) {
+ _coords.z = value;
+ addOperationToScene(changeCoordsOperation);
+ }
+ }
+
+ /**
+ * Координаты объекта.
+ */
+ public function get coords():Point3D {
+ return _coords.clone();
+ }
+
+ /**
+ * @private
+ */
+ public function set coords(value:Point3D):void {
+ if (!_coords.equals(value)) {
+ _coords.copy(value);
+ addOperationToScene(changeCoordsOperation);
+ }
+ }
+
+ /**
+ * Угол поворота вокруг оси X, заданный в радианах.
+ */
+ public function get rotationX():Number {
+ return _rotationX;
+ }
+
+ /**
+ * Угол поворота вокруг оси Y, заданный в радианах.
+ */
+ public function get rotationY():Number {
+ return _rotationY;
+ }
+
+ /**
+ * Угол поворота вокруг оси Z, заданный в радианах.
+ */
+ public function get rotationZ():Number {
+ return _rotationZ;
+ }
+
+ /**
+ * @private
+ */
+ public function set rotationX(value:Number):void {
+ if (_rotationX != value) {
+ _rotationX = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set rotationY(value:Number):void {
+ if (_rotationY != value) {
+ _rotationY = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set rotationZ(value:Number):void {
+ if (_rotationZ != value) {
+ _rotationZ = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * Коэффициент масштабирования вдоль оси X.
+ */
+ public function get scaleX():Number {
+ return _scaleX;
+ }
+
+ /**
+ * Коэффициент масштабирования вдоль оси Y.
+ */
+ public function get scaleY():Number {
+ return _scaleY;
+ }
+
+ /**
+ * Коэффициент масштабирования вдоль оси Z.
+ */
+ public function get scaleZ():Number {
+ return _scaleZ;
+ }
+
+ /**
+ * @private
+ */
+ public function set scaleX(value:Number):void {
+ if (_scaleX != value) {
+ _scaleX = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set scaleY(value:Number):void {
+ if (_scaleY != value) {
+ _scaleY = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set scaleZ(value:Number):void {
+ if (_scaleZ != value) {
+ _scaleZ = value;
+ addOperationToScene(changeRotationOrScaleOperation);
+ }
+ }
+
+ /**
+ * Строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ return "[" + ObjectUtils.getClassName(this) + " " + _name + "]";
+ }
+
+ /**
+ * Имя объекта по умолчанию.
+ *
+ * @return имя объекта по умолчанию
+ */
+ protected function defaultName():String {
+ return "object" + ++counter;
+ }
+
+ /**
+ * @private
+ * Добавление операции в очередь.
+ *
+ * @param operation добавляемая операция
+ */
+ alternativa3d function addOperationToScene(operation:Operation):void {
+ if (_scene != null) {
+ _scene.addOperation(operation);
+ }
+ }
+
+ /**
+ * @private
+ * Удаление операции из очереди.
+ *
+ * @param operation удаляемая операция
+ */
+ alternativa3d function removeOperationFromScene(operation:Operation):void {
+ if (_scene != null) {
+ _scene.removeOperation(operation);
+ }
+ }
+
+ /**
+ * Создаёт пустой объект без какой-либо внутренней структуры. Например, если некоторый геометрический примитив при
+ * своём создании формирует набор вершин, граней и поверхностей, то этот метод не должен создавать вершины, грани и
+ * поверхности. Данный метод используется в методе clone() и должен быть переопределён в потомках для получения
+ * правильного объекта.
+ *
+ * @return новый пустой объект
+ */
+ protected function createEmptyObject():Object3D {
+ return new Object3D();
+ }
+
+ /**
+ * Копирует свойства объекта-источника. Данный метод используется в методе clone() и должен быть переопределён в
+ * потомках для получения правильного объекта. Каждый потомок должен в переопределённом методе копировать только те
+ * свойства, которые добавлены к базовому классу именно в нём. Копирование унаследованных свойств выполняется
+ * вызовом super.clonePropertiesFrom(source).
+ *
+ * @param source объект, свойства которого копируются
+ */
+ protected function clonePropertiesFrom(source:Object3D):void {
+ _name = source._name;
+ _mobility = source._mobility;
+ _coords.x = source._coords.x;
+ _coords.y = source._coords.y;
+ _coords.z = source._coords.z;
+ _rotationX = source._rotationX;
+ _rotationY = source._rotationY;
+ _rotationZ = source._rotationZ;
+ _scaleX = source._scaleX;
+ _scaleY = source._scaleY;
+ _scaleZ = source._scaleZ;
+ }
+
+ /**
+ * Клонирует объект. Для реализации собственного клонирования наследники должны переопределять методы
+ * createEmptyObject() и clonePropertiesFrom().
+ *
+ * @return клонированный экземпляр объекта
+ *
+ * @see #createEmptyObject()
+ * @see #clonePropertiesFrom()
+ */
+ public function clone():Object3D {
+ var copy:Object3D = createEmptyObject();
+ copy.clonePropertiesFrom(this);
+
+ // Клонирование детей
+ for (var key:* in _children) {
+ var child:Object3D = key;
+ copy.addChild(child.clone());
+ }
+
+ return copy;
+ }
+
+ /**
+ * Получает дочерний объект с заданным именем.
+ *
+ * @param name имя дочернего объекта
+ * @param deep флаг углублённого поиска. Если задано значение false, поиск будет осуществляться только среди непосредственных
+ * дочерних объектов, иначе поиск будет выполняться по всему дереву дочерних объектов.
+ *
+ * @return любой дочерний объект с заданным именем или null в случае отсутствия таких объектов
+ */
+ public function getChildByName(name:String, deep:Boolean = false):Object3D {
+ var key:*;
+ var child:Object3D;
+ for (key in _children) {
+ child = key;
+ if (child._name == name) {
+ return child;
+ }
+ }
+ if (deep) {
+ for (key in _children) {
+ child = key;
+ child = child.getChildByName(name, true);
+ if (child != null) {
+ return child;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Трансформирует точку из локальной системы координат объекта в систему координат сцены. Матрица трансформации корневого объекта сцены
+ * не учитывается, т.к. его система координат является системой координат сцены.
+ *
+ * @param point локальные координаты точки
+ * @param result в этот параметр будут записаны трансформированные координаты. Если передано значение null, то будет создан
+ * новый экземпляр класса Point3D.
+ *
+ * @return координаты точки в сцене или null в случае, если объект не находится в сцене
+ */
+ public function localToGlobal(point:Point3D, result:Point3D = null):Point3D {
+ if (_scene == null) {
+ return null;
+ }
+ if (result == null) {
+ result = point.clone();
+ }
+
+ if (_parent == null) {
+ // Для корневого объекта трансформация единичная вне зависимости от матрицы
+ return result;
+ }
+
+ getTransformation(matrix2);
+ result.transform(matrix2);
+
+ return result;
+ }
+
+ /**
+ * Трансформирует точку из глобальной системы координат в локальную.
+ *
+ * @param point точка, заданная в глобальной системе координат.
+ * @param result в этот параметр будут записаны трансформированные координаты. Если передано значение null, то будет создан
+ * новый экземпляр класса Point3D.
+ *
+ * @return точка, трансформированная в локальную систему координат объекта или null в случае, если объект не находится в сцене.
+ */
+ public function globalToLocal(point:Point3D, result:Point3D = null):Point3D {
+ if (_scene == null) {
+ return null;
+ }
+ if (result == null) {
+ result = point.clone();
+ }
+
+ if (_parent == null) {
+ // Для корневого объекта трансформация единичная вне зависимости от матрицы
+ return result;
+ }
+
+ getTransformation(matrix2);
+ matrix2.invert();
+ result.transform(matrix2);
+ return result;
+ }
+
+ /**
+ * Получает матрицу полной трансформации объекта.
+ *
+ * @return матрица полной трансформации, переводящая координаты из локальной системы объекта в систему координат сцены или null в случае,
+ * если объект не находится в сцене
+ */
+ public function get transformation():Matrix3D {
+ if (_scene == null) {
+ return null;
+ }
+ var result:Matrix3D = new Matrix3D();
+ getTransformation(result);
+ return result;
+ }
+
+ /**
+ * Добавляет обработчик события.
+ *
+ * @param type тип события
+ * @param listener обработчик события
+ * @param useCapture не используется,
+ * @param priority приоритет обработчика. Обработчики с большим приоритетом выполняются раньше. Обработчики с одинаковым приоритетом
+ * выполняются в порядке их добавления.
+ * @param useWeakReference флаг использования слабой ссылки для обработчика
+ */
+ public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
+ if (dispatcher == null) {
+ dispatcher = new EventDispatcher(this);
+ }
+ useCapture = false;
+ dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
+ }
+
+ /**
+ * Рассылает событие.
+ *
+ * @param event посылаемое событие
+ * @return false
+ */
+ public function dispatchEvent(event:Event):Boolean {
+ if (dispatcher != null) {
+ dispatcher.dispatchEvent(event);
+ }
+ return false;
+ }
+
+ /**
+ * Проверяет наличие зарегистрированных обработчиков события указанного типа.
+ *
+ * @param type тип события
+ * @return true если есть обработчики события указанного типа, иначе false
+ */
+ public function hasEventListener(type:String):Boolean {
+ if (dispatcher != null) {
+ return dispatcher.hasEventListener(type);
+ }
+ return false;
+ }
+
+ /**
+ * Удаляет обработчик события.
+ *
+ * @param type тип события
+ * @param listener обработчик события
+ * @param useCapture не используется
+ */
+ public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void {
+ if (dispatcher != null) {
+ useCapture = false;
+ dispatcher.removeEventListener(type, listener, useCapture);
+ }
+ }
+
+ /**
+ *
+ */
+ public function willTrigger(type:String):Boolean {
+ if (dispatcher != null) {
+ return dispatcher.willTrigger(type);
+ }
+ return false;
+ }
+
+ /**
+ * @private
+ * Получение матрицы трансформации объекта. Перед расчётом выполняется проверка необходимости пересчёта матрицы.
+ *
+ * @param matrix матрица, в которую записывается результат
+ *
+ * @return true, если был произведён пересчёт матрицы трансформации, false, если пересчёт не понадобился
+ */
+ alternativa3d function getTransformation(matrix:Matrix3D):Boolean {
+ var rootObject:Object3D = _scene._root;
+ var topNonTransformedObject:Object3D;
+ var currentObject:Object3D = this;
+
+ // Поиск первого нетрансформированного объекта в дереве объектов
+ do {
+ if (currentObject.changeCoordsOperation.queued || currentObject.changeRotationOrScaleOperation.queued) {
+ topNonTransformedObject = currentObject._parent;
+ }
+ } while ((currentObject = currentObject._parent) != rootObject);
+
+ if (topNonTransformedObject != null) {
+ // Расчёт матрицы трансформации
+ matrix.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ);
+ currentObject = this;
+ while ((currentObject = currentObject._parent) != topNonTransformedObject) {
+ matrix1.toTransform(currentObject._coords.x, currentObject._coords.y, currentObject._coords.z, currentObject._rotationX, currentObject._rotationY, currentObject._rotationZ, currentObject._scaleX, currentObject._scaleY, currentObject._scaleZ);
+ matrix.combine(matrix1);
+ }
+ if (topNonTransformedObject != rootObject) {
+ matrix.combine(topNonTransformedObject._transformation);
+ }
+ return true;
+ }
+ matrix.copy(_transformation);
+ return false;
+ }
+
+ /**
+ * Вызывает заданную функцию для объекта и всех его детей, передавая текущий объект в качестве параметра.
+ *
+ * @param func функция вида Function(object:Object3D):void
+ */
+ public function forEach(func:Function):void {
+ func.call(this, this);
+ for (var child:* in _children) {
+ Object3D(child).forEach(func);
+ }
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Operation.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Operation.as
new file mode 100644
index 0000000..5d1e8e2
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Operation.as
@@ -0,0 +1,131 @@
+package alternativa.engine3d.core {
+ import alternativa.engine3d.*;
+ import alternativa.types.Set;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ */
+ public class Operation {
+
+ alternativa3d static const OBJECT_CALCULATE_TRANSFORMATION:uint = 0x01000000;
+ alternativa3d static const OBJECT_CALCULATE_MOBILITY:uint = 0x02000000;
+ alternativa3d static const VERTEX_CALCULATE_COORDS:uint = 0x03000000;
+ alternativa3d static const FACE_CALCULATE_BASE_UV:uint = 0x04000000;
+ alternativa3d static const FACE_CALCULATE_NORMAL:uint = 0x05000000;
+ alternativa3d static const FACE_CALCULATE_UV:uint = 0x06000000;
+ alternativa3d static const FACE_UPDATE_PRIMITIVE:uint = 0x07000000;
+ alternativa3d static const SECTOR_UPDATE:uint = 0x08000000;
+ alternativa3d static const SPLITTER_UPDATE:uint = 0x09000000;
+ alternativa3d static const SCENE_CALCULATE_BSP:uint = 0x0A000000;
+ alternativa3d static const SECTOR_FIND_NODE:uint = 0x0B000000;
+ alternativa3d static const SPLITTER_CHANGE_STATE:uint = 0x0C000000;
+ alternativa3d static const SECTOR_CHANGE_VISIBLE:uint = 0x0D000000;
+ alternativa3d static const FACE_UPDATE_MATERIAL:uint = 0x0E000000;
+ alternativa3d static const SPRITE_UPDATE_MATERIAL:uint = 0x0F000000;
+ alternativa3d static const CAMERA_CALCULATE_MATRIX:uint = 0x10000000;
+ alternativa3d static const CAMERA_CALCULATE_PLANES:uint = 0x11000000;
+ alternativa3d static const CAMERA_RENDER:uint = 0x12000000;
+ alternativa3d static const SCENE_CLEAR_PRIMITIVES:uint = 0x13000000;
+
+ // Объект
+ alternativa3d var object:Object;
+
+ // Метод
+ alternativa3d var method:Function;
+
+ // Название метода
+ alternativa3d var name:String;
+
+ // Последствия
+ private var sequel:Operation;
+ private var sequels:Set;
+
+ // Приоритет операции
+ alternativa3d var priority:uint;
+
+ // Находится ли операция в очереди
+ alternativa3d var queued:Boolean = false;
+
+ public function Operation(name:String, object:Object = null, method:Function = null, priority:uint = 0) {
+ this.object = object;
+ this.method = method;
+ this.name = name;
+ this.priority = priority;
+ }
+
+ // Добавить последствие
+ alternativa3d function addSequel(operation:Operation):void {
+ if (sequel == null) {
+ if (sequels == null) {
+ sequel = operation;
+ } else {
+ sequels[operation] = true;
+ }
+ } else {
+ if (sequel != operation) {
+ sequels = new Set(true);
+ sequels[sequel] = true;
+ sequels[operation] = true;
+ sequel = null;
+ }
+ }
+ }
+
+ // Удалить последствие
+ alternativa3d function removeSequel(operation:Operation):void {
+ if (sequel == null) {
+ if (sequels != null) {
+ delete sequels[operation];
+ var key:*;
+ var single:Boolean = false;
+ for (key in sequels) {
+ if (single) {
+ single = false;
+ break;
+ }
+ single = true;
+ }
+ if (single) {
+ sequel = key;
+ sequels = null;
+ }
+ }
+ } else {
+ if (sequel == operation) {
+ sequel = null;
+ }
+ }
+ }
+
+ alternativa3d function collectSequels(collector:Array):void {
+ if (sequel == null) {
+ // Проверяем последствия
+ for (var key:* in sequels) {
+ var operation:Operation = key;
+ // Если операция ещё не в очереди
+ if (!operation.queued) {
+ // Добавляем её в очередь
+ collector.push(operation);
+ // Устанавливаем флаг очереди
+ operation.queued = true;
+ // Вызываем добавление в очередь её последствий
+ operation.collectSequels(collector);
+ }
+ }
+ } else {
+ if (!sequel.queued) {
+ collector.push(sequel);
+ sequel.queued = true;
+ sequel.collectSequels(collector);
+ }
+ }
+ }
+
+ public function toString():String {
+ return "[Operation " + (priority >>> 24) + "/" + (priority & 0xFFFFFF) + " " + object + "." + name + "]";
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/PolyPrimitive.as b/Alternativa3D5/5.6/alternativa/engine3d/core/PolyPrimitive.as
new file mode 100644
index 0000000..87dbc74
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/core/PolyPrimitive.as
@@ -0,0 +1,123 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.*;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ * Примитивный полигон (примитив), хранящийся в узле BSP-дерева.
+ */
+ public class PolyPrimitive {
+
+ /**
+ * @private
+ * Количество точек
+ */
+ alternativa3d var num:uint;
+ /**
+ * @private
+ * Точки
+ */
+ alternativa3d var points:Array = new Array();
+ /**
+ * @private
+ * Грань
+ */
+ alternativa3d var face:Face;
+ /**
+ * @private
+ * Родительский примитив
+ */
+ alternativa3d var parent:PolyPrimitive;
+ /**
+ * @private
+ * Соседний примитив (при наличии родительского)
+ */
+ alternativa3d var sibling:PolyPrimitive;
+ /**
+ * @private
+ * Фрагменты
+ */
+ alternativa3d var backFragment:PolyPrimitive;
+ /**
+ * @private
+ */
+ alternativa3d var frontFragment:PolyPrimitive;
+ /**
+ * @private
+ * BSP-нода, в которой находится примитив
+ */
+ alternativa3d var node:BSPNode;
+ /**
+ * @private
+ * Значения для расчёта качества сплиттера
+ */
+ alternativa3d var splits:uint;
+ /**
+ * @private
+ */
+ alternativa3d var disbalance:int;
+ /**
+ * @private
+ * Качество примитива как сплиттера (меньше - лучше)
+ */
+ public var splitQuality:Number;
+ /**
+ * @private
+ * Приоритет в BSP-дереве. Чем ниже мобильность, тем примитив выше в дереве.
+ */
+ public var mobility:int;
+
+ /**
+ * @private
+ * Метод создает новый фрагмент этого примитива.
+ */
+ alternativa3d function createFragment():PolyPrimitive {
+ var primitive:PolyPrimitive = create();
+ primitive.face = face;
+ primitive.mobility = mobility;
+ return primitive;
+ }
+
+ // Хранилище неиспользуемых примитивов
+ static private var collector:Array = new Array();
+
+ /**
+ * @private
+ * Создать примитив
+ */
+ static alternativa3d function create():PolyPrimitive {
+ var primitive:PolyPrimitive;
+ if ((primitive = collector.pop()) != null) {
+ return primitive;
+ }
+ return new PolyPrimitive();
+ }
+
+ /**
+ * @private
+ * Кладёт примитив в коллектор для последующего реиспользования.
+ * Ссылка на грань и массивы точек зачищаются в этом методе.
+ * Ссылки на фрагменты (parent, sibling, back, front) должны быть зачищены перед запуском метода.
+ *
+ * Исключение:
+ * при сборке примитивов в сцене ссылки на back и front зачищаются после запуска метода.
+ *
+ * @param primitive примитив на реиспользование
+ */
+ static alternativa3d function destroy(primitive:PolyPrimitive):void {
+ primitive.face = null;
+ primitive.points.length = 0;
+ collector.push(primitive);
+ }
+
+ /**
+ * Строковое представление объекта.
+ */
+ public function toString():String {
+ return "[Primitive " + face._mesh._name + "]";
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Scene3D.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Scene3D.as
new file mode 100644
index 0000000..1f26485
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Scene3D.as
@@ -0,0 +1,1425 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.errors.SectorInOtherSceneError;
+ import alternativa.engine3d.errors.SplitterInOtherSceneError;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.SpriteTextureMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.engine3d.materials.WireMaterial;
+ import alternativa.types.*;
+
+ import flash.display.Shape;
+ import flash.display.Sprite;
+
+ use namespace alternativa3d;
+ use namespace alternativatypes;
+
+ /**
+ * Сцена является контейнером 3D-объектов, с которыми ведётся работа. Все взаимодействия объектов
+ * происходят в пределах одной сцены. Класс обеспечивает работу системы сигналов и реализует алгоритм построения
+ * BSP-дерева для содержимого сцены.
+ *
+ * Можно управлять видимостью между частями сцены. Для этого сцена разделяется на части при помощи
+ * сплиттеров. Затем частям сцены назначаются сектора которые будут определять отношения видимости между ними.
+ *
+ * @see #splitters
+ * @see #sectors
+ */
+ public class Scene3D {
+
+ /**
+ * @private
+ * Полное обновление BSP-дерева
+ */
+ alternativa3d var updateBSPOperation:Operation = new Operation("updateBSP", this);
+ /**
+ * @private
+ * Обновление сплиттеров
+ */
+ alternativa3d var updateSplittersOperation:Operation = new Operation("updateSplitters", this);
+ /**
+ * @private
+ * Изменение примитивов
+ */
+ alternativa3d var changePrimitivesOperation:Operation = new Operation("changePrimitives", this);
+ /**
+ * @private
+ * Расчёт BSP-дерева
+ */
+ alternativa3d var calculateBSPOperation:Operation = new Operation("calculateBSP", this, calculateBSP, Operation.SCENE_CALCULATE_BSP);
+ /**
+ * @private
+ * Очистка списков изменений
+ */
+ alternativa3d var clearPrimitivesOperation:Operation = new Operation("clearPrimitives", this, clearPrimitives, Operation.SCENE_CLEAR_PRIMITIVES);
+
+ /**
+ * @private
+ * Корневой объект
+ */
+ alternativa3d var _root:Object3D;
+
+ /**
+ * @private
+ * Список операций на выполнение
+ */
+ alternativa3d var operations:Array = new Array();
+ /**
+ * @private
+ * Вспомогательная пустая операция, используется при удалении операций из списка
+ */
+ alternativa3d var dummyOperation:Operation = new Operation("removed", this);
+
+ /**
+ * @private
+ * Флаг анализа сплиттеров
+ */
+ alternativa3d var _splitAnalysis:Boolean = true;
+ /**
+ * @private
+ * Cбалансированность дерева
+ */
+ alternativa3d var _splitBalance:Number = 0;
+ /**
+ * @private
+ * Список изменённых примитивов
+ */
+ alternativa3d var changedPrimitives:Set = new Set();
+
+ // Вспомогательный список для сборки дочерних примитивов
+ private var childPrimitives:Set = new Set();
+ /**
+ * @private
+ * Список примитивов на добавление/удаление
+ */
+ alternativa3d var addPrimitives:Array = new Array();
+
+ /**
+ * @private
+ * Погрешность при определении точек на плоскости
+ */
+ private var _planeOffsetThreshold:Number = 0.01;
+ /**
+ * @private
+ * BSP-дерево
+ */
+ alternativa3d var bsp:BSPNode;
+
+ /**
+ * @private
+ * Список нод на удаление
+ */
+ alternativa3d var removeNodes:Set = new Set();
+ /**
+ * @private
+ * Вспомогательная пустая нода, используется при удалении нод из дерева
+ */
+ alternativa3d var dummyNode:BSPNode = new BSPNode();
+
+ /**
+ * @private
+ * Сплиттеры
+ */
+ private var _splitters:Array = new Array();
+
+ /**
+ * @private
+ * Сектора
+ */
+ private var _sectors:Array = new Array();
+
+ /**
+ * Создание экземпляра сцены.
+ */
+ public function Scene3D() {
+ // Обновление BSP-дерева требует его пересчёта
+ updateBSPOperation.addSequel(calculateBSPOperation);
+ // Обновление BSP-дерева требует пересчёта сплиттеров
+ updateBSPOperation.addSequel(updateSplittersOperation);
+ // Обновление сплиттеров требует пересчёта БСП дерева
+ updateSplittersOperation.addSequel(calculateBSPOperation);
+ // Изменение примитивов в случае пересчёта дерева
+ calculateBSPOperation.addSequel(changePrimitivesOperation);
+ // При изменении примитивов необходимо очистить списки изменений
+ changePrimitivesOperation.addSequel(clearPrimitivesOperation);
+ }
+
+ /**
+ * Расчёт сцены. Метод анализирует все изменения, произошедшие с момента предыдущего расчёта, формирует список
+ * команд и исполняет их в необходимой последовательности. В результате расчёта происходит перерисовка во всех
+ * областях вывода, к которым подключены находящиеся в сцене камеры.
+ */
+ public function calculate():void {
+ if (operations[0] != undefined) {
+ // Формируем последствия
+ var operation:Operation;
+ var length:uint = operations.length;
+ var i:uint;
+ for (i = 0; i < length; i++) {
+ operation = operations[i];
+ operation.collectSequels(operations);
+ }
+ // Сортируем операции
+ length = operations.length;
+ sortOperations(0, length - 1);
+ // Запускаем операции
+ //trace("----------------------------------------");
+ for (i = 0; i < length; i++) {
+ operation = operations[i];
+ if (operation.method != null) {
+ //trace("EXECUTE:", operation);
+ operation.method();
+ } else {
+ /*if (operation == dummyOperation) {
+ trace("REMOVED");
+ } else {
+ trace(operation);
+ }*/
+ }
+ }
+ // Очищаем список операций
+ for (i = 0; i < length; i++) {
+ operation = operations.pop();
+ operation.queued = false;
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Сортировка операций, если массив operations пуст, будет ошибка
+ *
+ * @param l начальный элемент
+ * @param r конечный элемент
+ */
+ alternativa3d function sortOperations(l:int, r:int):void {
+ var i:int = l;
+ var j:int = r;
+ var left:Operation;
+ var mid:uint = operations[(r + l) >> 1].priority;
+ var right:Operation;
+ do {
+ while ((left = operations[i]).priority < mid) {i++};
+ while (mid < (right = operations[j]).priority) {j--};
+ if (i <= j) {
+ operations[i++] = right;
+ operations[j--] = left;
+ }
+ } while (i <= j)
+ if (l < j) {
+ sortOperations(l, j);
+ }
+ if (i < r) {
+ sortOperations(i, r);
+ }
+ }
+
+ /**
+ * @private
+ * Добавление операции в список
+ *
+ * @param operation добавляемая операция
+ */
+ alternativa3d function addOperation(operation:Operation):void {
+ if (!operation.queued) {
+ operations.push(operation);
+ operation.queued = true;
+ }
+ }
+
+ /**
+ * @private
+ * удаление операции из списка
+ *
+ * @param operation удаляемая операция
+ */
+ alternativa3d function removeOperation(operation:Operation):void {
+ if (operation.queued) {
+ operations[operations.indexOf(operation)] = dummyOperation;
+ operation.queued = false;
+ }
+ }
+
+ /**
+ * @private
+ * Расчёт изменений в BSP-дереве.
+ * Обработка удалённых и добавленных примитивов.
+ */
+ protected function calculateBSP():void {
+ if (updateSplittersOperation.queued || updateBSPOperation.queued) {
+ // Удаление списка нод, помеченных на удаление
+ removeNodes.clear();
+
+ // Удаление BSP-дерева, перенос примитивов в список дочерних
+ childBSP(bsp);
+ // Собираем дочерние примитивы в список нижних
+ assembleChildPrimitives();
+
+ // Встраиваем сплиттеры в БСП дерево
+ var count:int = _splitters.length;
+ if (count > 0) {
+ var splitter:Splitter;
+ // Если корневого нода ещё нет, создаём
+ splitter = _splitters[0];
+ bsp = BSPNode.create(splitter.primitive);
+ for (var i:int = 1; i < count; i++) {
+ splitter = splitters[i];
+ addBSP(bsp, splitter.primitive);
+ }
+ } else {
+ // Нет сплиттеров
+ bsp = null;
+ }
+ } else {
+ var key:*;
+ var primitive:PolyPrimitive;
+
+ // Удаляем ноды из дерева
+ if (!removeNodes.isEmpty()) {
+ var node:BSPNode;
+ while ((node = removeNodes.peek()) != null) {
+ // Ищем верхнюю удаляемую ноду
+ var removeNode:BSPNode = node;
+ while ((node = node.parent) != null) {
+ if (removeNodes[node]) {
+ removeNode = node;
+ }
+ }
+
+ // Удаляем ветку
+ var parent:BSPNode = removeNode.parent;
+ var replace:BSPNode = removeBSPNode(removeNode);
+
+ // Если вернулась вспомогательная нода, игнорируем её
+ if (replace == dummyNode) {
+ replace = null;
+ }
+
+ // Если есть родительская нода
+ if (parent != null) {
+ // Заменяем себя на указанную ноду
+ if (parent.front == removeNode) {
+ parent.front = replace;
+ } else {
+ parent.back = replace;
+ }
+ } else {
+ // Если нет родительской ноды, значит заменяем корень на указанную ноду
+ bsp = replace;
+ }
+
+ // Устанавливаем связь с родителем для заменённой ноды
+ if (replace != null) {
+ replace.parent = parent;
+ }
+ }
+
+ // Собираем дочерние примитивы в список на добавление
+ assembleChildPrimitives();
+ }
+ }
+
+ // Если есть примитивы на добавление
+ if (addPrimitives[0] != undefined) {
+ // Если включен анализ сплиттеров
+ if (_splitAnalysis) {
+ // Рассчитываем качество рассечения примитивов
+ analyseSplitQuality();
+ // Сортируем массив примитивов c учётом качества
+ sortPrimitives(0, addPrimitives.length - 1);
+ } else {
+ // Сортируем массив по мобильности
+ sortPrimitivesByMobility(0, addPrimitives.length - 1);
+ }
+
+ // Если корневого нода ещё нет, создаём
+ if (bsp == null) {
+ primitive = addPrimitives.pop();
+ bsp = BSPNode.create(primitive);
+ changedPrimitives[primitive] = true;
+ }
+
+ // Встраиваем примитивы в дерево
+ while ((primitive = addPrimitives.pop()) != null) {
+ addBSP(bsp, primitive);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Сортировка граней, если массив addPrimitives пуст будет ошибка
+ *
+ * @param l начальный элемент
+ * @param r конечный элемент
+ */
+ alternativa3d function sortPrimitives(l:int, r:int):void {
+ var i:int = l;
+ var j:int = r;
+ var left:PolyPrimitive;
+ var mid:PolyPrimitive = addPrimitives[(r + l) >> 1];
+ var midMobility:int = mid.mobility;
+ var midSplitQuality:Number = mid.splitQuality;
+ var right:PolyPrimitive;
+ do {
+ while (((left = addPrimitives[i]).mobility > midMobility) || ((left.mobility == midMobility) && (left.splitQuality > midSplitQuality))) {i++};
+ while ((midMobility > (right = addPrimitives[j]).mobility) || ((midMobility == right.mobility) && (midSplitQuality > right.splitQuality))) {j--};
+ if (i <= j) {
+ addPrimitives[i++] = right;
+ addPrimitives[j--] = left;
+ }
+ } while (i <= j)
+ if (l < j) {
+ sortPrimitives(l, j);
+ }
+ if (i < r) {
+ sortPrimitives(i, r);
+ }
+ }
+
+ /**
+ * @private
+ * Сортировка только по мобильности
+ *
+ * @param l начальный элемент
+ * @param r конечный элемент
+ */
+ alternativa3d function sortPrimitivesByMobility(l:int, r:int):void {
+ var i:int = l;
+ var j:int = r;
+ var left:PolyPrimitive;
+ var mid:int = addPrimitives[(r + l) >> 1].mobility;
+ var right:PolyPrimitive;
+ do {
+ while ((left = addPrimitives[i]).mobility > mid) {i++};
+ while (mid > (right = addPrimitives[j]).mobility) {j--};
+ if (i <= j) {
+ addPrimitives[i++] = right;
+ addPrimitives[j--] = left;
+ }
+ } while (i <= j)
+ if (l < j) {
+ sortPrimitivesByMobility(l, j);
+ }
+ if (i < r) {
+ sortPrimitivesByMobility(i, r);
+ }
+ }
+
+ /**
+ * @private
+ * Анализ качества сплиттеров
+ */
+ private function analyseSplitQuality():void {
+ // Перебираем примитивы на добавление
+ var i:uint;
+ var length:uint = addPrimitives.length;
+ var maxSplits:uint = 0;
+ var maxDisbalance:uint = 0;
+ var splitter:PolyPrimitive;
+ for (i = 0; i < length; i++) {
+ splitter = addPrimitives[i];
+ if (splitter.face == null) {
+ // Пропускаем спрайтовые примитивы
+ continue;
+ }
+ splitter.splits = 0;
+ splitter.disbalance = 0;
+ var normal:Point3D = splitter.face.globalNormal;
+ var offset:Number = splitter.face.globalOffset;
+ // Проверяем соотношение с другими примитивами не меньшей мобильности на добавление
+ for (var j:uint = 0; j < length; j++) {
+ if (i != j) {
+ var primitive:PolyPrimitive = addPrimitives[j];
+ if (primitive.face == null) {
+ // Пропускаем спрайтовые примитивы
+ continue;
+ }
+ if (splitter.mobility <= primitive.mobility) {
+ // Проверяем наличие точек спереди и сзади сплиттера
+ var pointsFront:Boolean = false;
+ var pointsBack:Boolean = false;
+ for (var k:uint = 0; k < primitive.num; k++) {
+ var point:Point3D = primitive.points[k];
+ var pointOffset:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - offset;
+ if (pointOffset > _planeOffsetThreshold) {
+ if (!pointsFront) {
+ splitter.disbalance++;
+ pointsFront = true;
+ }
+ if (pointsBack) {
+ splitter.splits++;
+ break;
+ }
+ } else {
+ if (pointOffset < -_planeOffsetThreshold) {
+ if (!pointsBack) {
+ splitter.disbalance--;
+ pointsBack = true;
+ }
+ if (pointsFront) {
+ splitter.splits++;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ // Абсолютное значение дисбаланса
+ splitter.disbalance = (splitter.disbalance > 0) ? splitter.disbalance : -splitter.disbalance;
+ // Ищем максимальное количество рассечений и значение дисбаланса
+ maxSplits = (maxSplits > splitter.splits) ? maxSplits : splitter.splits;
+ maxDisbalance = (maxDisbalance > splitter.disbalance) ? maxDisbalance : splitter.disbalance;
+ }
+ // Расчитываем качество сплиттеров
+ for (i = 0; i < length; i++) {
+ splitter = addPrimitives[i];
+ splitter.splitQuality = (1 - _splitBalance)*splitter.splits/maxSplits + _splitBalance*splitter.disbalance/maxDisbalance;
+ }
+ }
+
+ /**
+ * @private
+ * Добавление примитива в BSP-дерево
+ *
+ * @param node текущий узел дерева, в который добавляется примитив
+ * @param primitive добавляемый примитив
+ */
+ protected function addBSP(node:BSPNode, primitive:PolyPrimitive):void {
+ var point:Point3D;
+ var normal:Point3D;
+ var key:*;
+
+ // Сравниваем мобильности ноды и примитива
+ if (primitive.mobility < node.mobility || (node.isSprite && primitive.face != null)) {
+ // Мобильность примитива ниже мобильности ноды
+ // Или мобильность примитива равна мобильности спрайтовой ноды
+
+ // Формируем список содержимого ноды и всех примитивов ниже
+ if (node.primitive != null) {
+ childPrimitives[node.primitive] = true;
+ changedPrimitives[node.primitive] = true;
+ node.primitive.node = null;
+ } else {
+ var p:PolyPrimitive;
+ for (key in node.backPrimitives) {
+ p = key;
+ childPrimitives[p] = true;
+ changedPrimitives[p] = true;
+ p.node = null;
+ }
+ for (key in node.frontPrimitives) {
+ p = key;
+ childPrimitives[p] = true;
+ changedPrimitives[p] = true;
+ p.node = null;
+ }
+ }
+ childBSP(node.back);
+ childBSP(node.front);
+
+ // Собираем дочерние примитивы в список нижних
+ assembleChildPrimitives();
+
+ // Если включен анализ сплиттеров
+ if (_splitAnalysis) {
+ // Рассчитываем качество рассечения примитивов
+ analyseSplitQuality();
+ // Сортируем массив примитивов c учётом качества
+ sortPrimitives(0, addPrimitives.length - 1);
+ } else {
+ // Сортируем массив по мобильности
+ sortPrimitivesByMobility(0, addPrimitives.length - 1);
+ }
+
+ // Добавляем примитив в ноду
+ node.primitive = primitive;
+
+ // Пометка об изменении примитива
+ changedPrimitives[primitive] = true;
+
+ // Сохраняем ноду
+ primitive.node = node;
+
+ // Сохраняем плоскость
+ node.normal.copy(primitive.face.globalNormal);
+ node.offset = primitive.face.globalOffset;
+ node.isSprite = false;
+
+ // Сохраняем мобильность
+ node.mobility = primitive.mobility;
+
+ // Чистим списки примитивов
+ node.backPrimitives = null;
+ node.frontPrimitives = null;
+
+ // Удаляем дочерние ноды
+ node.back = null;
+ node.front = null;
+ } else {
+ // Получаем нормаль из ноды
+ normal = node.normal;
+
+ var points:Array = primitive.points;
+
+ // Собирательные флаги
+ var pointsFront:Boolean = false;
+ var pointsBack:Boolean = false;
+
+ // Собираем расстояния точек до плоскости
+ for (var i:uint = 0; i < primitive.num; i++) {
+ point = points[i];
+ var pointOffset:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset;
+ if (pointOffset > _planeOffsetThreshold) {
+ pointsFront = true;
+ if (pointsBack) {
+ break;
+ }
+ } else {
+ if (pointOffset < -_planeOffsetThreshold) {
+ pointsBack = true;
+ if (pointsFront) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (node.splitter != null && !pointsFront && !pointsBack) {
+ // Примитив в сплиттеровой ноде
+ if (primitive.face == null) {
+ // Сплиттер или спрайт
+ var sprimitive:SplitterPrimitive = primitive as SplitterPrimitive;
+ if (sprimitive != null) {
+ // Сплиттер
+ if (Point3D.dot(normal, sprimitive.splitter.normal) > 0) {
+ pointsFront = true;
+ } else {
+ pointsBack = true;
+ }
+ } else {
+ // Спрайт
+ pointsFront = true;
+ }
+ } else {
+ // Обычный примитив
+ if (Point3D.dot(normal, primitive.face.globalNormal) > 0) {
+ pointsFront = true;
+ } else {
+ pointsBack = true;
+ }
+ }
+ }
+
+ // Если все точки в плоскости или это добавление спрайтовой точки в спрайтовую ноду
+ if (!pointsFront && !pointsBack && (primitive.face != null || node.isSprite)) {
+ // Сохраняем ноду
+ primitive.node = node;
+
+ // Если был только базовый примитив, переносим его в список
+ if (node.primitive != null) {
+ node.frontPrimitives = new Set(true);
+ node.frontPrimitives[node.primitive] = true;
+ node.primitive = null;
+ }
+
+ // Если примитив спрайтовый или нормаль полигона сонаправлена с нормалью ноды
+ if (primitive.face == null || Point3D.dot(primitive.face.globalNormal, normal) > 0) {
+ node.frontPrimitives[primitive] = true;
+ } else {
+ if (node.backPrimitives == null) {
+ node.backPrimitives = new Set(true);
+ }
+ node.backPrimitives[primitive] = true;
+ }
+
+ // Пометка об изменении примитива
+ changedPrimitives[primitive] = true;
+ } else {
+ if (!pointsBack) {
+ // Примитив спереди плоскости ноды
+ if (node.front == null) {
+ // Создаём переднюю ноду
+ node.front = BSPNode.create(primitive);
+ node.front.parent = node;
+ changedPrimitives[primitive] = true;
+ } else {
+ // Добавляем примитив в переднюю ноду
+ addBSP(node.front, primitive);
+ }
+ } else {
+ if (!pointsFront) {
+ // Примитив сзади плоскости ноды
+ if (node.back == null) {
+ // Создаём заднюю ноду
+ node.back = BSPNode.create(primitive);
+ node.back.parent = node;
+ changedPrimitives[primitive] = true;
+ } else {
+ // Добавляем примитив в заднюю ноду
+ addBSP(node.back, primitive);
+ }
+ } else {
+ // Рассечение
+ var backFragment:PolyPrimitive = primitive.createFragment();
+ var frontFragment:PolyPrimitive = primitive.createFragment();
+
+ point = points[0];
+ var offset0:Number = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset;
+ var offset1:Number = offset0;
+ var offset2:Number;
+ for (i = 0; i < primitive.num; i++) {
+ var j:uint;
+ if (i < primitive.num - 1) {
+ j = i + 1;
+ point = points[j];
+ offset2 = point.x*normal.x + point.y*normal.y + point.z*normal.z - node.offset;
+ } else {
+ j = 0;
+ offset2 = offset0;
+ }
+
+ if (offset1 > _planeOffsetThreshold) {
+ // Точка спереди плоскости ноды
+ frontFragment.points.push(points[i]);
+ } else {
+ if (offset1 < -_planeOffsetThreshold) {
+ // Точка сзади плоскости ноды
+ backFragment.points.push(points[i]);
+ } else {
+ // Рассечение по точке примитива
+ backFragment.points.push(points[i]);
+ frontFragment.points.push(points[i]);
+ }
+ }
+
+ // Рассечение ребра
+ if (offset1 > _planeOffsetThreshold && offset2 < -_planeOffsetThreshold || offset1 < -_planeOffsetThreshold && offset2 > _planeOffsetThreshold) {
+ // Находим точку рассечения
+ var t:Number = offset1/(offset1 - offset2);
+ point = Point3D.interpolate(points[i], points[j], t);
+ backFragment.points.push(point);
+ frontFragment.points.push(point);
+ }
+
+ offset1 = offset2;
+ }
+ backFragment.num = backFragment.points.length;
+ frontFragment.num = frontFragment.points.length;
+
+ // Устанавливаем связи рассечённых примитивов
+ backFragment.parent = primitive;
+ frontFragment.parent = primitive;
+ backFragment.sibling = frontFragment;
+ frontFragment.sibling = backFragment;
+ primitive.backFragment = backFragment;
+ primitive.frontFragment = frontFragment;
+
+ // Добавляем фрагменты в дочерние ноды
+ if (node.back == null) {
+ node.back = BSPNode.create(backFragment);
+ node.back.parent = node;
+ changedPrimitives[backFragment] = true;
+ } else {
+ addBSP(node.back, backFragment);
+ }
+ if (node.front == null) {
+ node.front = BSPNode.create(frontFragment);
+ node.front.parent = node;
+ changedPrimitives[frontFragment] = true;
+ } else {
+ addBSP(node.front, frontFragment);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Удаление узла BSP-дерева, включая все дочерние узлы, помеченные для удаления.
+ *
+ * @param node удаляемый узел
+ * @return корневой узел поддерева, оставшегося после операции удаления
+ */
+ protected function removeBSPNode(node:BSPNode):BSPNode {
+ var replaceNode:BSPNode;
+ if (node != null) {
+ // Удаляем дочерние
+ node.back = removeBSPNode(node.back);
+ node.front = removeBSPNode(node.front);
+
+ if (!removeNodes[node]) {
+ // Если нода не удаляется, возвращает себя
+ replaceNode = node;
+
+ // Проверяем дочерние ноды
+ if (node.back != null) {
+ if (node.back != dummyNode) {
+ node.back.parent = node;
+ } else {
+ node.back = null;
+ }
+ }
+ if (node.front != null) {
+ if (node.front != dummyNode) {
+ node.front.parent = node;
+ } else {
+ node.front = null;
+ }
+ }
+ } else {
+ // Проверяем дочерние ветки
+ if (node.back == null) {
+ if (node.front != null) {
+ // Есть только передняя ветка
+ replaceNode = node.front;
+ node.front = null;
+ }
+ } else {
+ if (node.front == null) {
+ // Есть только задняя ветка
+ replaceNode = node.back;
+ node.back = null;
+ } else {
+ // Есть обе ветки - собираем дочерние примитивы
+ childBSP(node.back);
+ childBSP(node.front);
+ // Используем вспомогательную ноду
+ replaceNode = dummyNode;
+ // Удаляем связи с дочерними нодами
+ node.back = null;
+ node.front = null;
+ }
+ }
+
+ // Удаляем ноду из списка на удаление
+ delete removeNodes[node];
+ // Удаляем ноду
+ node.parent = null;
+ BSPNode.destroy(node);
+ }
+ }
+ return replaceNode;
+ }
+
+ /**
+ * @private
+ * Удаление примитива из узла дерева
+ *
+ * @param primitive удаляемый примитив
+ */
+ alternativa3d function removeBSPPrimitive(primitive:PolyPrimitive):void {
+ var node:BSPNode = primitive.node;
+ primitive.node = null;
+
+ var single:Boolean = false;
+ var key:*;
+
+ // Пометка об изменении примитива
+ changedPrimitives[primitive] = true;
+
+ // Если нода единичная
+ if (node.primitive == primitive) {
+ removeNodes[node] = true;
+ node.primitive = null;
+ } else {
+ // Есть передние примитивы
+ if (node.frontPrimitives[primitive]) {
+ // Удаляем примитив спереди
+ delete node.frontPrimitives[primitive];
+
+ // Проверяем количество примитивов спереди
+ for (key in node.frontPrimitives) {
+ if (single) {
+ single = false;
+ break;
+ }
+ single = true;
+ }
+
+ if (key == null) {
+ // Передняя пуста или не спрайтовая нода, значит сзади кто-то есть
+
+ // Переворачиваем дочерние ноды
+ var t:BSPNode = node.back;
+ node.back = node.front;
+ node.front = t;
+
+ // Переворачиваем плоскость ноды
+ node.normal.invert();
+ node.offset = -node.offset;
+
+ // Проверяем количество примитивов сзади
+ for (key in node.backPrimitives) {
+ if (single) {
+ single = false;
+ break;
+ }
+ single = true;
+ }
+
+ // Если сзади один примитив
+ if (single) {
+ // Устанавливаем базовый примитив ноды
+ node.primitive = key;
+ // Устанавливаем мобильность
+ node.mobility = node.primitive.mobility;
+ // Стираем список передних примитивов
+ node.frontPrimitives = null;
+ } else {
+ // Если сзади несколько примитивов, переносим их в передние
+ node.frontPrimitives = node.backPrimitives;
+ // Пересчитываем мобильность ноды по передним примитивам
+ // Присваивается наименьшая мобильность из всех примитивов
+ if (primitive.mobility == node.mobility) {
+ node.mobility = int.MAX_VALUE;
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ }
+ }
+
+ // Стираем список задних примитивов
+ node.backPrimitives = null;
+
+ } else {
+ // Если остался один примитив и сзади примитивов нет
+ if (single && node.backPrimitives == null) {
+ // Устанавливаем базовый примитив ноды
+ node.primitive = key;
+ // Устанавливаем мобильность
+ node.mobility = node.primitive.mobility;
+ // Стираем список передних примитивов
+ node.frontPrimitives = null;
+ } else {
+ // Пересчитываем мобильность ноды
+ if (primitive.mobility == node.mobility) {
+ node.mobility = int.MAX_VALUE;
+ for (key in node.backPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ }
+ }
+ }
+ } else {
+ // Удаляем примитив сзади
+ delete node.backPrimitives[primitive];
+
+ // Проверяем количество примитивов сзади
+ for (key in node.backPrimitives) {
+ break;
+ }
+
+ // Если сзади примитивов больше нет
+ if (key == null) {
+ // Проверяем количество примитивов спереди
+ for (key in node.frontPrimitives) {
+ if (single) {
+ single = false;
+ break;
+ }
+ single = true;
+ }
+
+ // Если спереди один примитив
+ if (single) {
+ // Устанавливаем базовый примитив ноды
+ node.primitive = key;
+ // Устанавливаем мобильность
+ node.mobility = node.primitive.mobility;
+ // Стираем список передних примитивов
+ node.frontPrimitives = null;
+ } else {
+ // Пересчитываем мобильность ноды по передним примитивам
+ if (primitive.mobility == node.mobility) {
+ node.mobility = int.MAX_VALUE;
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ }
+ }
+
+ // Стираем список задних примитивов
+ node.backPrimitives = null;
+ } else {
+ // Пересчитываем мобильность ноды
+ if (primitive.mobility == node.mobility) {
+ node.mobility = int.MAX_VALUE;
+ for (key in node.backPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ node.mobility = (node.mobility > primitive.mobility) ? primitive.mobility : node.mobility;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Удаление и перевставка ветки
+ *
+ * @param node
+ */
+ protected function childBSP(node:BSPNode):void {
+ if (node != null && node != dummyNode) {
+ var primitive:PolyPrimitive = node.primitive;
+ if (primitive != null) {
+ childPrimitives[primitive] = true;
+ changedPrimitives[primitive] = true;
+ node.primitive = null;
+ primitive.node = null;
+ } else {
+ for (var key:* in node.backPrimitives) {
+ primitive = key;
+ childPrimitives[primitive] = true;
+ changedPrimitives[primitive] = true;
+ primitive.node = null;
+ }
+ for (key in node.frontPrimitives) {
+ primitive = key;
+ childPrimitives[primitive] = true;
+ changedPrimitives[primitive] = true;
+ primitive.node = null;
+ }
+ node.backPrimitives = null;
+ node.frontPrimitives = null;
+ }
+ childBSP(node.back);
+ childBSP(node.front);
+ // Удаляем ноду
+ node.parent = null;
+ node.back = null;
+ node.front = null;
+ BSPNode.destroy(node);
+ }
+ }
+
+ /**
+ * @private
+ * Сборка списка дочерних примитивов в коллектор
+ */
+ protected function assembleChildPrimitives():void {
+ var primitive:PolyPrimitive;
+ while ((primitive = childPrimitives.take()) != null) {
+ assemblePrimitive(primitive);
+ }
+ }
+
+ /**
+ * @private
+ * Сборка примитивов и разделение на добавленные и удалённые
+ *
+ * @param primitive
+ */
+ private function assemblePrimitive(primitive:PolyPrimitive):void {
+ // Если есть соседний примитив и он может быть собран
+ if (primitive.sibling != null && canAssemble(primitive.sibling)) {
+ // Собираем их в родительский
+ assemblePrimitive(primitive.parent);
+ // Зачищаем связи между примитивами
+ primitive.sibling.sibling = null;
+ primitive.sibling.parent = null;
+ PolyPrimitive.destroy(primitive.sibling);
+ primitive.sibling = null;
+ primitive.parent.backFragment = null;
+ primitive.parent.frontFragment = null;
+ primitive.parent = null;
+ PolyPrimitive.destroy(primitive);
+ } else {
+ // Если собраться не получилось или родительский
+ addPrimitives.push(primitive);
+ }
+ }
+
+ /**
+ * @private
+ * Проверка, может ли примитив в списке дочерних быть собран
+ *
+ * @param primitive
+ * @return
+ */
+ private function canAssemble(primitive:PolyPrimitive):Boolean {
+ if (childPrimitives[primitive]) {
+ delete childPrimitives[primitive];
+ return true;
+ } else {
+ var backFragment:PolyPrimitive = primitive.backFragment;
+ var frontFragment:PolyPrimitive = primitive.frontFragment;
+ if (backFragment != null) {
+ var assembleBack:Boolean = canAssemble(backFragment);
+ var assembleFront:Boolean = canAssemble(frontFragment);
+ if (assembleBack && assembleFront) {
+ backFragment.parent = null;
+ frontFragment.parent = null;
+ backFragment.sibling = null;
+ frontFragment.sibling = null;
+ primitive.backFragment = null;
+ primitive.frontFragment = null;
+ PolyPrimitive.destroy(backFragment);
+ PolyPrimitive.destroy(frontFragment);
+ return true;
+ } else {
+ if (assembleBack) {
+ addPrimitives.push(backFragment);
+ }
+ if (assembleFront) {
+ addPrimitives.push(frontFragment);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @private
+ * Очистка списка измененных примитивов
+ */
+ private function clearPrimitives():void {
+ changedPrimitives.clear();
+ }
+
+ /**
+ * Проверка наличия изменений в сцене.
+ *
+ * @return true, если в сцене были изменения с предыдущего вызова метода calculate
+ *
+ * @see #calculate()
+ */
+ public function hasChanges():Boolean {
+ var len:int = operations.length;
+ for (var i:int = 0; i < len; i++) {
+ if (operations[i] != dummyOperation) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Корневой объект сцены.
+ */
+ public function get root():Object3D {
+ return _root;
+ }
+
+ /**
+ * @private
+ */
+ public function set root(value:Object3D):void {
+ // Если ещё не является корневым объектом
+ if (_root != value) {
+ // Если устанавливаем не пустой объект
+ if (value != null) {
+ // Если объект был в другом объекте
+ if (value._parent != null) {
+ // Удалить его оттуда
+ value._parent._children.remove(value);
+ } else {
+ // Если объект был корневым в сцене
+ if (value._scene != null && value._scene._root == value) {
+ value._scene.root = null;
+ }
+ }
+ // Удаляем ссылку на родителя
+ value.setParent(null);
+ // Указываем сцену
+ value.setScene(this);
+ // Устанавливаем уровни
+ value.setLevel(0);
+ }
+
+ // Если был корневой объект
+ if (_root != null) {
+ // Удаляем ссылку на родителя
+ _root.setParent(null);
+ // Удаляем ссылку на камеру
+ _root.setScene(null);
+ }
+
+ // Сохраняем корневой объект
+ _root = value;
+ }
+ }
+
+ /**
+ * Список сплиттеров сцены. Сплиттеры предназначены для разделения пространства сцены
+ * на части. Порядок разделения сцены сплиттерами определяется порядком расположения
+ * объекта сплиттера в массиве.
+ *
+ * @see Splitter
+ *
+ * @throws alternativa.engine3d.errors.SplitterInOtherSceneError сплиттер уже расположен в другой сцене.
+ */
+ public function get splitters():Array {
+ return new Array().concat(_splitters);
+ }
+
+ /**
+ * @private
+ */
+ public function set splitters(value:Array):void {
+ // Убираем сплиттеры из сцены
+ var splitter:Splitter;
+ for each (splitter in _splitters) {
+ splitter.removeFromScene(this);
+ }
+ if (value != null) {
+ var count:int = value.length;
+ for (var i:int = 0; i < count; i++) {
+ splitter = value[i];
+ if (splitter._scene == null) {
+ splitter.addToScene(this);
+ _splitters[i] = splitter;
+ } else {
+ _splitters.length = i;
+ addOperation(updateSplittersOperation);
+ throw new SplitterInOtherSceneError(splitter, this);
+ }
+ }
+ _splitters.length = count;
+ } else {
+ _splitters.length = 0;
+ }
+ addOperation(updateSplittersOperation);
+ }
+
+ /**
+ * Сектора сцены. Сектора задают видимость между образованными сплиттерами частями сцены.
+ *
+ * @see Sector
+ *
+ * @throws alternativa.engine3d.errors.SectorInOtherSceneError сектор уже расположен в другой сцене.
+ */
+ public function get sectors():Array {
+ return new Array().concat(_sectors);
+ }
+
+ /**
+ * @private
+ */
+ public function set sectors(value:Array):void {
+ // Убираем полупространства из сцены
+ var sector:Sector;
+ for each (sector in _sectors) {
+ sector.removeFromScene(this);
+ }
+ if (value != null) {
+ var count:int = value.length;
+ for (var i:int = 0; i < count; i++) {
+ sector = value[i];
+ if (sector._scene == null) {
+ sector.addToScene(this);
+ sector.setLevel(i);
+ _sectors[i] = sector;
+ } else {
+ _sectors.length = i;
+ throw new SectorInOtherSceneError(sector, this);
+ }
+ }
+ _sectors.length = count;
+ } else {
+ _sectors.length = 0;
+ }
+ }
+
+ /**
+ * Флаг активности анализа сплиттеров.
+ * В режиме анализа для каждого добавляемого в BSP-дерево полигона выполняется его оценка в качестве разделяющей
+ * плоскости (сплиттера). Наиболее качественные сплиттеры добавляются в BSP-дерево первыми.
+ *
+ * Изменением свойства splitBalance можно влиять на конечный вид BSP-дерева.
+ *
+ * @see #splitBalance
+ * @default true
+ */
+ public function get splitAnalysis():Boolean {
+ return _splitAnalysis;
+ }
+
+ /**
+ * @private
+ */
+ public function set splitAnalysis(value:Boolean):void {
+ if (_splitAnalysis != value) {
+ _splitAnalysis = value;
+ addOperation(updateBSPOperation);
+ }
+ }
+
+ /**
+ * Параметр балансировки BSP-дерева при влюченном режиме анализа сплиттеров.
+ * Может принимать значения от 0 (минимизация фрагментирования полигонов) до 1 (максимальный баланс BSP-дерева).
+ *
+ * @see #splitAnalysis
+ * @default 0
+ */
+ public function get splitBalance():Number {
+ return _splitBalance;
+ }
+
+ /**
+ * @private
+ */
+ public function set splitBalance(value:Number):void {
+ value = (value < 0) ? 0 : ((value > 1) ? 1 : value);
+ if (_splitBalance != value) {
+ _splitBalance = value;
+ if (_splitAnalysis) {
+ addOperation(updateBSPOperation);
+ }
+ }
+ }
+
+ /**
+ * Погрешность определения расстояний и координат. При построении BSP-дерева точка считается попавшей в плоскость сплиттера, если расстояние от точки до плоскости меньше planeOffsetThreshold.
+ *
+ * @default 0.01
+ */
+ public function get planeOffsetThreshold():Number {
+ return _planeOffsetThreshold;
+ }
+
+ /**
+ * @private
+ */
+ public function set planeOffsetThreshold(value:Number):void {
+ value = (value < 0) ? 0 : value;
+ if (_planeOffsetThreshold != value) {
+ _planeOffsetThreshold = value;
+ addOperation(updateBSPOperation);
+ }
+ }
+
+ /**
+ * Визуализация BSP-дерева. Дерево рисуется в заданном контейнере. Каждый узел дерева обозначается точкой, имеющей
+ * цвет материала (в случае текстурного материала показывается цвет первой точки текстуры) первого полигона из этого
+ * узла. Задние узлы рисуются слева-снизу от родителя, передние справа-снизу.
+ *
+ * @param container контейнер для отрисовки дерева
+ */
+ public function drawBSP(container:Sprite):void {
+
+ container.graphics.clear();
+ while (container.numChildren > 0) {
+ container.removeChildAt(0);
+ }
+ if (bsp != null) {
+ drawBSPNode(bsp, container, 0, 0, 1);
+ }
+ }
+
+ /**
+ * @private
+ * Отрисовка узла BSP-дерева при визуализации
+ *
+ * @param node
+ * @param container
+ * @param x
+ * @param y
+ * @param size
+ */
+ private function drawBSPNode(node:BSPNode, container:Sprite, x:Number, y:Number, size:Number):void {
+ var s:Shape = new Shape();
+ container.addChild(s);
+ s.x = x;
+ s.y = y;
+ var color:uint = 0xFF0000;
+ if (node.splitter != null) {
+ // Сплиттеровая нода
+ if (node.splitter._open) {
+ color = 0xFF00;
+ } else {
+ color = 0xFFF000;
+ }
+ s.graphics.beginFill(color);
+ s.graphics.moveTo(-4, 0);
+ s.graphics.lineTo(0, -4);
+ s.graphics.lineTo(4, 0);
+ s.graphics.lineTo(0, 4);
+ s.graphics.endFill();
+ } else {
+ var sprimitive:SpritePrimitive;
+ if (node.isSprite) {
+ if (node.primitive != null) {
+ sprimitive = node.primitive as SpritePrimitive;
+ } else {
+ if (node.frontPrimitives != null) {
+ sprimitive = node.frontPrimitives.peek();
+ }
+ }
+ if (sprimitive != null) {
+ var material:SpriteTextureMaterial = sprimitive.sprite._material as SpriteTextureMaterial;
+ if (material != null && material._texture != null) {
+ color = material._texture._bitmapData.getPixel(material._texture._bitmapData.width >> 1, material._texture._bitmapData.height >> 1);
+ }
+ }
+ s.graphics.beginFill(color);
+ s.graphics.drawRect(0, 0, 5, 5);
+ s.graphics.endFill();
+ } else {
+ var primitive:PolyPrimitive;
+ if (node.primitive != null) {
+ primitive = node.primitive;
+ } else {
+ if (node.frontPrimitives != null) {
+ primitive = node.frontPrimitives.peek();
+ }
+ }
+ if (primitive != null) {
+ if (primitive.face._surface != null && primitive.face._surface._material != null) {
+ if (primitive.face._surface._material is FillMaterial) {
+ color = FillMaterial(primitive.face._surface._material)._color;
+ }
+ if (primitive.face._surface._material is WireMaterial) {
+ color = WireMaterial(primitive.face._surface._material)._color;
+ }
+ if ((primitive.face._surface._material is TextureMaterial) && TextureMaterial(primitive.face._surface._material)._texture != null) {
+ color = TextureMaterial(primitive.face._surface._material).texture._bitmapData.getPixel(0, 0);
+ }
+ }
+ }
+ if (node == dummyNode) {
+ color = 0xFF00FF;
+ }
+ s.graphics.beginFill(color);
+ s.graphics.drawCircle(0, 0, 3);
+ s.graphics.endFill();
+ }
+ }
+
+ var xOffset:Number = 100;
+ var yOffset:Number = 20;
+ if (node.back != null) {
+ container.graphics.lineStyle(0, 0x660000);
+ container.graphics.moveTo(x, y);
+ container.graphics.lineTo(x - xOffset*size, y + yOffset);
+ drawBSPNode(node.back, container, x - xOffset*size, y + yOffset, size*0.8);
+ }
+ if (node.front != null) {
+ container.graphics.lineStyle(0, 0x006600);
+ container.graphics.moveTo(x, y);
+ container.graphics.lineTo(x + xOffset*size, y + yOffset);
+ drawBSPNode(node.front, container, x + xOffset*size, y + yOffset, size*0.8);
+ }
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Sector.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Sector.as
new file mode 100644
index 0000000..2a47f23
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Sector.as
@@ -0,0 +1,321 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.alternativa3d;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ use namespace alternativa3d;
+
+ /**
+ * Cектор предоставляет механизм управления видимостью частей сцены, который дополняет систему сплиттеров. Каждый сектор находится в какой-то
+ * из ветвей BSP-дерева, образованных сплиттерами. Также каждый сектор хранит множество других видимых из него секторов.
+ *
+ * Если камера находится в дочерней ветке узла, образованного сплиттером, то видимость соседней относительно сплиттера ветки определяется
+ * следующим образом:
+ *
+ * - Если в ветке, в которой находится камера, не задан сектор, то видимость соседней ветки определяется состоянием сплиттера.
+ * - Если в ветке, в которой находится камера, задан сектор, а в соседней ветке сектор не задан, то видимость соседней ветки определяется
+ * состоянием сплиттера.
+ * - Если в обоих ветках заданы сектора, то видимость соседней ветки определяется состоянием сплиттера и взаимной видимостью секторов.
+ * То есть если сектора невидимы друг для друга, то даже при открытом сплиттере соседняя ветка дерева будет невидима для камеры.
+ *
+ *
+ *
+ * Сектора задаются в свойстве Scene.sectors.
+ *
+ * @see Scene3D#sectors
+ * @see Splitter
+ */
+ public class Sector {
+
+ // Счетчик имен объекта
+ private static var counter:uint = 0;
+
+ /**
+ * @private
+ * Убрать из бсп дерева перед перестроением сплиттеров.
+ */
+ alternativa3d var updateOperation:Operation = new Operation("removeSector", this, removeFromBSP, Operation.SECTOR_UPDATE);
+ /**
+ * @private
+ * Поиск сплиттеровой ноды в БСП дереве.
+ */
+ alternativa3d var findNodeOperation:Operation = new Operation("addSector", this, addToBSP, Operation.SECTOR_FIND_NODE);
+ /**
+ * @private
+ * Изименение видимости.
+ */
+ alternativa3d var changeVisibleOperation:Operation = new Operation("changeSectorVisibility", this, changeVisible, Operation.SECTOR_CHANGE_VISIBLE);
+
+ /**
+ * @private
+ * Список видимых секторов.
+ */
+ alternativa3d var _visible:Set = new Set();
+
+ /**
+ * @private
+ * Координата x сектора.
+ */
+ private var x:Number;
+ /**
+ * @private
+ * Координата y сектора.
+ */
+ private var y:Number;
+ /**
+ * @private
+ * Координата z сектора.
+ */
+ private var z:Number;
+
+ /**
+ * @private
+ */
+ alternativa3d var _scene:Scene3D;
+
+ // Сплиттеровая нода
+ private var _node:BSPNode;
+
+ /**
+ * Имя сектора.
+ */
+ public var name:String;
+
+ /**
+ * Создаёт новый экземпляр сектора. По координатам сектора определяется та дочерняя ветка сплиттера, для которой сектор задает видимость.
+ *
+ * @param x координата по оси X
+ * @param y координата по оси Y
+ * @param z координата по оси Z
+ * @param name имя сектора. При указании null используется автоматически сгенерированное имя.
+ */
+ public function Sector(x:Number = 0, y:Number = 0, z:Number = 0, name:String = null) {
+ this.name = (name != null) ? name : "sector" + ++counter;
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ _visible[this] = true;
+ // Обновление в дереве требует перевставки
+ updateOperation.addSequel(findNodeOperation);
+ // Обновление в дереве требует перерисовки
+ findNodeOperation.addSequel(changeVisibleOperation);
+ }
+
+ /**
+ * Добавляет сектора в список видимых. Видимость секторов взаимная, поэтому текущий сектор автоматически добавляется
+ * в список видимых для каждого из указанных секторов.
+ *
+ * @param sector сектор, добавляемый в список видимых
+ * @param sectors дополнительные сектора, добавляемые в список видимых
+ *
+ * @see #removeVisible()
+ */
+ public function addVisible(sector:Sector, ...sectors):void {
+ sector._visible[this] = true;
+ _visible[sector] = true;
+ sector.markToChange();
+ var count:int = sectors.length;
+ for (var i:int = 0; i < count; i++) {
+ var sc:Sector = sectors[i];
+ sc._visible[this] = true;
+ _visible[sc] = true;
+ sc.markToChange();
+ }
+ markToChange();
+ }
+
+ /**
+ * Удаляет сектора из списка видимых. Видимость секторов взаимная, поэтому текущий сектор автоматически удаляется из списка видимости указанных секторов.
+ *
+ * @param sector сектор, удаляемый из списка видимости
+ * @param sectors дополнительные сектора, удаляемые из списка видимых
+ *
+ * @see #addVisible()
+ */
+ public function removeVisible(sector:Sector, ...sectors):void {
+ if (_visible[sector] && sector != this) {
+ delete sector._visible[this];
+ sector.markToChange();
+ delete _visible[sector];
+ markToChange();
+ }
+ var count:int = sectors.length;
+ for (var i:int = 0; i < count; i++) {
+ var sc:Sector = sectors[i];
+ if (_visible[sc] && sc != this) {
+ delete sc._visible[this];
+ sc.markToChange();
+ delete _visible[sc];
+ markToChange();
+ }
+ }
+ }
+
+ /**
+ * Определяет потенциальную видимость заданного сектора.
+ *
+ * @param sector сектор, видимость которого проверяется
+ * @return true, если указанный сектор находится в списке видимости текущего, иначе false
+ */
+ public function isVisible(sector:Sector):Boolean {
+ return _visible[sector];
+ }
+
+ /**
+ * Создаёт строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ var s:String = "[Sector " + name + " X:" + x.toFixed(3) + " Y:" + y.toFixed(3) + " Z:" + z.toFixed(3);
+ var sector:Sector;
+ var v:String = "";
+ for (var sc:* in _visible) {
+ if (sc != this) {
+ if (sector == null) {
+ sector = sc;
+ v = sector.name;
+ } else {
+ sector = sc;
+ v += " " + sector.name;
+ }
+ }
+ }
+ return (sector == null) ? s + "]" : s + " visible:[" + v + "]]";
+ }
+
+ /**
+ * @private
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {
+ _scene = scene;
+
+ // Перестройка сплиттеров перевставляет сектор
+ scene.updateSplittersOperation.addSequel(updateOperation);
+ // Изменение видимости вызывает перерисовку
+ changeVisibleOperation.addSequel(scene.changePrimitivesOperation);
+ // Поиск ноды
+ scene.addOperation(findNodeOperation);
+ }
+
+ /**
+ * @private
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {
+ scene.updateSplittersOperation.removeSequel(updateOperation);
+ changeVisibleOperation.removeSequel(scene.changePrimitivesOperation);
+
+ scene.removeOperation(findNodeOperation);
+ scene.removeOperation(changeVisibleOperation);
+
+ // Убираем сектор из бсп дерева
+ removeFromBSP();
+
+ _scene = null;
+ }
+
+ /**
+ * @private
+ * Установка приоритета сектора. Используется для сохранения последовательности добавления в дерево.
+ */
+ alternativa3d function setLevel(level:int):void {
+ findNodeOperation.priority = (findNodeOperation.priority & 0xFF000000) | level;
+ }
+
+ /**
+ * @private
+ * Послать операцию обновления видимости на сцену
+ */
+ alternativa3d function markToChange():void {
+ if (_scene != null) {
+ _scene.addOperation(changeVisibleOperation);
+ }
+ }
+
+ /**
+ * Убирает сектор из БСП дерева.
+ */
+ private function removeFromBSP():void {
+ if (_node != null) {
+ if (_node.frontSector == this) {
+ _node.frontSector = null;
+ } else {
+ _node.backSector = null;
+ }
+ _node = null;
+ }
+ }
+
+ /**
+ * Поиск сплиттеровой ноды для этого сектора.
+ */
+ private function addToBSP():void {
+ findSectorNode(_scene.bsp);
+ }
+
+ /**
+ * Рекурсивный поиск ноды сектора.
+ */
+ private function findSectorNode(node:BSPNode):void {
+ if (node != null && node.splitter != null) {
+ var normal:Point3D = node.normal;
+ if (x*normal.x + y*normal.y + z*normal.z - node.offset >= 0) {
+ if (node.front == null || node.front.splitter == null) {
+ if (node.frontSector == null) {
+ node.frontSector = this;
+ _node = node;
+ }
+ } else {
+ findSectorNode(node.front);
+ }
+ } else {
+ if (node.back == null || node.back.splitter == null) {
+ if (node.backSector == null) {
+ node.backSector = this;
+ _node = node;
+ }
+ } else {
+ findSectorNode(node.back);
+ }
+ }
+ }
+ }
+
+ /**
+ * Отправляет примитивы текущего сектора на перерисовку.
+ */
+ private function changeVisible():void {
+ if (_node != null) {
+ var primitive:*;
+ if (_node.frontSector == this) {
+ changeNode(_node.front);
+ } else {
+ changeNode(_node.back);
+ }
+ }
+ }
+
+ /**
+ * Отправляет на перерисовку ветку бсп дерева.
+ */
+ private function changeNode(node:BSPNode):void {
+ if (node != null) {
+ if (node.primitive != null) {
+ _scene.changedPrimitives[node.primitive] = true;
+ } else {
+ var primitive:*;
+ for (primitive in node.frontPrimitives) {
+ _scene.changedPrimitives[primitive] = true;
+ }
+ for (primitive in node.backPrimitives) {
+ _scene.changedPrimitives[primitive] = true;
+ }
+ }
+ changeNode(node.back);
+ changeNode(node.front);
+ }
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Splitter.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Splitter.as
new file mode 100644
index 0000000..175d39e
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Splitter.as
@@ -0,0 +1,307 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.alternativa3d;
+ import alternativa.engine3d.errors.SplitterNeedMoreVerticesError;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+
+ use namespace alternativa3d;
+
+ /**
+ * Сплиттеры применяются для управления видимостью отдельных частей сцены. Сплиттеры находятся ближе к корню BSP-дерева, чем обычные полигоны.
+ * Таким образом, они образуют систему ветвей, каждая из которых может быть скрыта от своего соседа.
+ *
+ * Например, пусть в сцене задан единственный сплиттер, а камера находится в левой ветке. При открытом сплиттере камера будет
+ * иметь возможность видеть содержимое правой ветки. Если же сплиттер закрыт, то содержимое правой ветки дерева не будет отображаться совсем.
+ *
+ * Глубина сплиттеров в сцене определяется порядком их расположения в массиве при задании свойства Scene3D.splitters. Каждый
+ * последующий сплиттер разделяет на части пространства, образованные предыдущими сплиттерами. При этом он, как и обычный полигон, может быть
+ * поделён на несколько частей в процессе встраивания в BSP-дерево.
+ *
+ * Для управления видимостью ветвей дерева, образованных системой сплиттеров, дополнительно могут быть использованы объекты класса
+ * Sector.
+ *
+ * @see Scene3D#splitters
+ * @see Sector
+ */
+ public class Splitter {
+
+ // Счетчик имен объекта
+ private static var counter:uint = 0;
+
+ /**
+ * Создает сплиттер из грани. Если объект, которому принадлежит грань, находится на сцене,
+ * используются глобальные координаты вершин в сцене, иначе используются локальные координаты вершин грани.
+ *
+ * @param face грань, которая будет использована для создания сплиттера
+ * @param name имя нового экземпляра сплиттера. Если указано значение null, имя будет выбрано автоматически.
+ */
+ public static function createFromFace(face:Face, name:String = null):Splitter {
+ var src:Array = face._vertices;
+ var dest:Array = new Array();
+ var i:int;
+ if (face._mesh != null && face._mesh._scene != null) {
+ var m:Matrix3D = Object3D.matrix2;
+ face._mesh.getTransformation(m);
+ for (i = 0; i < face._verticesCount; i++) {
+ var p:Point3D = Vertex(src[i])._coords.clone();
+ p.transform(m);
+ dest[i] = p;
+ }
+ } else {
+ for (i = 0; i < face._verticesCount; i++) {
+ dest[i] = Vertex(src[i])._coords;
+ }
+ }
+ return new Splitter(dest, name);
+ }
+
+ /**
+ * @private
+ * Изменение состояния сплиттера в БСП дереве.
+ */
+ alternativa3d var changeStateOperation:Operation = new Operation("changeSplitterState", this, changeState, Operation.SPLITTER_CHANGE_STATE);
+ /**
+ * @private
+ * Обновление примитива в сцене.
+ */
+ alternativa3d var updatePrimitiveOperation:Operation = new Operation("updateSplitter", this, updatePrimitive, Operation.SPLITTER_UPDATE);
+
+ /**
+ * @private
+ * Состояние
+ */
+ alternativa3d var _open:Boolean = true;
+
+ /**
+ * @private
+ * Примитив
+ */
+ alternativa3d var primitive:SplitterPrimitive;
+
+ /**
+ * @private
+ * Нормаль
+ */
+ alternativa3d var normal:Point3D = new Point3D();
+
+ /**
+ * @private
+ * Оффсет
+ */
+ alternativa3d var offset:Number;
+
+ /**
+ * @private
+ * Сцена
+ */
+ alternativa3d var _scene:Scene3D;
+
+ /**
+ * Имя объекта.
+ */
+ public var name:String;
+
+ /**
+ * Создает экземпляр сплиттера.
+ *
+ * @param vertices массив координат вершин сплиттера. Плоскость, в которой расположены вершины,
+ * будет использована в качестве плоскости сплиттера.
+ *
+ * @param name имя объекта. Если указано значение null, имя будет выбрано автоматически.
+ *
+ * @throws alternativa.engine3d.errors.SplitterNeedMoreVerticesError для создания сплиттера было передано менее трех точек.
+ */
+ public function Splitter(vertices:Array, name:String = null) {
+ var count:int = vertices.length;
+ if (count < 3) {
+ throw new SplitterNeedMoreVerticesError(count);
+ }
+ primitive = SplitterPrimitive.create();
+ primitive.mobility = int.MIN_VALUE;
+ primitive.splitter = this;
+ for (var i:int = 0; i < count; i++) {
+ primitive.points[i] = Point3D(vertices[i]).clone();
+ }
+ primitive.num = count;
+ calculatePlane();
+
+ this.name = (name != null) ? name : "splitter" + ++counter;
+ }
+
+ /**
+ * Список вершин сплиттера. Элементами являются объекты класса Point3D.
+ */
+ public function get vertices():Array {
+ var res:Array = new Array().concat(primitive.points);
+ for (var i:int = 0; i < primitive.num; i++) {
+ res[i] = Point3D(res[i]).clone();
+ }
+ return res;
+ }
+
+ /**
+ * Состояние сплиттера. При закрытом состоянии сплиттера в камере не рисуются части сцены,
+ * расположенные в соседней ветке относительно данного сплиттера.
+ */
+ public function get open():Boolean {
+ return _open;
+ }
+
+ /**
+ * @private
+ */
+ public function set open(value:Boolean):void {
+ if (_open != value) {
+ _open = value;
+ if (_scene != null) {
+ _scene.addOperation(changeStateOperation);
+ }
+ }
+ }
+
+ /**
+ * Создаёт строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ return "[Splitter " + name + ((_open) ? " open]" : " closed]");
+ }
+
+ /**
+ * @private
+ * Добавление на сцену
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {
+ _scene = scene
+
+ // Изменение состояния вызывает перерисовку
+ changeStateOperation.addSequel(_scene.changePrimitivesOperation);
+ // Обновление сплиттеров в сцене вызывает перевставку
+ _scene.updateBSPOperation.addSequel(updatePrimitiveOperation);
+ }
+
+ /**
+ * @private
+ * Удаление из сцены
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {
+ changeStateOperation.removeSequel(scene.changePrimitivesOperation);
+ scene.updateBSPOperation.removeSequel(updatePrimitiveOperation);
+
+ scene.removeOperation(changeStateOperation);
+
+ // Удаляем примитив сплиттера из сцены
+ removePrimitive(primitive);
+
+ _scene = null
+ }
+
+ /**
+ * @private
+ * Расчет нормали и оффсета плоскости.
+ */
+ private function calculatePlane():void {
+ // Вектор AB
+ var av:Point3D = primitive.points[0];
+ var bv:Point3D = primitive.points[1];
+ var abx:Number = bv.x - av.x;
+ var aby:Number = bv.y - av.y;
+ var abz:Number = bv.z - av.z;
+ // Вектор AC
+ var cv:Point3D = primitive.points[2];
+ var acx:Number = cv.x - av.x;
+ var acy:Number = cv.y - av.y;
+ var acz:Number = cv.z - av.z;
+ // Перпендикуляр к плоскости
+ normal.x = acz*aby - acy*abz;
+ normal.y = acx*abz - acz*abx;
+ normal.z = acy*abx - acx*aby;
+ // Нормализация перпендикуляра
+ normal.normalize();
+ offset = av.x*normal.x + av.y*normal.y + av.z*normal.z;
+ }
+
+ /**
+ * @private
+ * Помечает конечные примитивы на удаление.
+ */
+ private function updatePrimitive():void {
+ removePrimitive(primitive);
+ }
+
+ /**
+ * @private
+ * Отправляет на перерисовку примитивы сплиттеровой ноды.
+ */
+ private function changeState():void {
+ changePrimitiveNode(primitive);
+ }
+
+ /**
+ * @private
+ * Отправляет на перерисовку ноды сплиттера.
+ */
+ private function changePrimitiveNode(primitive:PolyPrimitive):void {
+ if (primitive.backFragment == null) {
+ // Базовый примитив
+ changePrimitivesInNode(primitive.node.back);
+ changePrimitivesInNode(primitive.node.front);
+ } else {
+ // Примитив попилился на куски
+ changePrimitiveNode(primitive.backFragment);
+ changePrimitiveNode(primitive.frontFragment);
+ }
+ }
+
+ /**
+ * @private
+ * Отправляет на перерисовку ветку бсп дерева.
+ */
+ private function changePrimitivesInNode(node:BSPNode):void {
+ if (node != null) {
+ if (node.primitive != null) {
+ _scene.changedPrimitives[node.primitive] = true;
+ } else {
+ var primitive:*;
+ for (primitive in node.frontPrimitives) {
+ _scene.changedPrimitives[primitive] = true;
+ }
+ for (primitive in node.backPrimitives) {
+ _scene.changedPrimitives[primitive] = true;
+ }
+ }
+ changePrimitivesInNode(node.back);
+ changePrimitivesInNode(node.front);
+ }
+ }
+
+ /**
+ * @private
+ * Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на удаление из сцены
+ */
+ private function removePrimitive(primitive:PolyPrimitive):void {
+ if (primitive.backFragment != null) {
+ // Удаляем куски примитива
+ removePrimitive(primitive.backFragment);
+ removePrimitive(primitive.frontFragment);
+ primitive.backFragment = null;
+ primitive.frontFragment = null;
+ } else {
+ // Если примитив в BSP-дереве
+ if (primitive.node != null) {
+ primitive.node.splitter = null;
+ // Удаление примитива
+ _scene.removeBSPPrimitive(primitive);
+ }
+ }
+ if (primitive != this.primitive) {
+ primitive.parent = null;
+ primitive.sibling = null;
+ SplitterPrimitive.destroy(primitive as SplitterPrimitive);
+ }
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/SplitterPrimitive.as b/Alternativa3D5/5.6/alternativa/engine3d/core/SplitterPrimitive.as
new file mode 100644
index 0000000..431d749
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/core/SplitterPrimitive.as
@@ -0,0 +1,61 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.alternativa3d;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ * Сплиттеровый примитив.
+ */
+ public class SplitterPrimitive extends PolyPrimitive {
+
+ /**
+ * Сплиттер
+ */
+ alternativa3d var splitter:Splitter;
+
+ /**
+ * @inheritDoc
+ */
+ override alternativa3d function createFragment():PolyPrimitive {
+ var primitive:SplitterPrimitive = create();
+ primitive.splitter = splitter;
+ primitive.mobility = mobility;
+ return primitive;
+ }
+
+ // Хранилище неиспользуемых примитивов
+ static private var collector:Array = new Array();
+
+ /**
+ * @private
+ * Создать примитив
+ */
+ static alternativa3d function create():SplitterPrimitive {
+ var primitive:SplitterPrimitive;
+ if ((primitive = collector.pop()) != null) {
+ return primitive;
+ }
+ return new SplitterPrimitive();
+ }
+
+ /**
+ * @private
+ * Кладёт примитив в коллектор для последующего реиспользования.
+ * Ссылка на грань и массивы точек зачищаются в этом методе.
+ * Ссылки на фрагменты (parent, sibling, back, front) должны быть зачищены перед запуском метода.
+ *
+ * Исключение:
+ * при сборке примитивов в сцене ссылки на back и front зачищаются после запуска метода.
+ *
+ * @param primitive примитив на реиспользование
+ */
+ static alternativa3d function destroy(primitive:SplitterPrimitive):void {
+ primitive.splitter = null;
+ primitive.points.length = 0;
+ collector.push(primitive);
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Sprite3D.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Sprite3D.as
new file mode 100644
index 0000000..af54c44
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Sprite3D.as
@@ -0,0 +1,208 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.materials.SpriteMaterial;
+
+ use namespace alternativa3d;
+
+ /**
+ * Объект представляет собой точку в трёхмерном пространстве. Объекту может быть назначен материал для вывода различных изображений в
+ * месте его нахождения.
+ */
+ public class Sprite3D extends Object3D {
+ /**
+ * Счетчик имен объектов
+ */
+ private static var counter:uint = 0;
+
+ /**
+ * @private
+ * Обновление материала.
+ */
+ alternativa3d var updateMaterialOperation:Operation = new Operation("updateSpriteMaterial", this, updateMaterial, Operation.SPRITE_UPDATE_MATERIAL);
+ /**
+ * @private
+ * Примитив.
+ */
+ alternativa3d var primitive:SpritePrimitive;
+ /**
+ * @private
+ * Материал.
+ */
+ alternativa3d var _material:SpriteMaterial;
+ /**
+ * @private
+ * Размер материала.
+ */
+ alternativa3d var _materialScale:Number;
+
+ /**
+ * Создание экземпляра спрайта.
+ *
+ * @param name имя спрайта
+ */
+ public function Sprite3D(name:String = null) {
+ super(name);
+ // Создаем примитив спрайта
+ primitive = new SpritePrimitive();
+ primitive.sprite = this;
+ // В примитиве одна точка - координата спрайта
+ primitive.points = [this.globalCoords];
+ primitive.num = 1;
+ primitive.mobility = int.MAX_VALUE;
+ }
+
+ /**
+ * @private
+ * Расчет перемещения точки спрайта.
+ */
+ override alternativa3d function calculateTransformation():void {
+ super.calculateTransformation();
+ // Произошло перемещение спрайта, необходимо перевставить точку в БСП
+ updatePrimitive();
+ if (changeRotationOrScaleOperation.queued) {
+ // Считаем размер материала
+ // Считается для любого материала, без отдельных операций
+ var a:Number = _transformation.a;
+ var b:Number = _transformation.b;
+ var c:Number = _transformation.c;
+ var e:Number = _transformation.e;
+ var f:Number = _transformation.f;
+ var g:Number = _transformation.g;
+ var i:Number = _transformation.i;
+ var j:Number = _transformation.j;
+ var k:Number = _transformation.k;
+ _materialScale = (Math.sqrt(a*a + e*e + i*i) + Math.sqrt(b*b + f*f + j*j) + Math.sqrt(c*c + g*g + k*k))/3;
+ }
+ }
+
+ /**
+ * Перевставка точки спрайта в БСП дереве.
+ */
+ private function updatePrimitive():void {
+ // Если примитив в BSP-дереве
+ if (primitive.node != null) {
+ // Удаление примитива
+ _scene.removeBSPPrimitive(primitive);
+ }
+ _scene.addPrimitives.push(primitive);
+ }
+
+ /**
+ * Перерисовка скинов спрайта.
+ */
+ private function updateMaterial():void {
+ if (!calculateTransformationOperation.queued) {
+ _scene.changedPrimitives[primitive] = true;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function addToScene(scene:Scene3D):void {
+ super.addToScene(scene);
+ // Подписываем сцену на операции
+ calculateTransformationOperation.addSequel(scene.calculateBSPOperation);
+ updateMaterialOperation.addSequel(scene.changePrimitivesOperation);
+ // Добавляем на сцену материал
+ if (_material != null) {
+ _material.addToScene(scene);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(updateMaterialOperation);
+
+ // Если примитив в BSP-дереве
+ if (primitive.node != null) {
+ // Удаляем примитив из сцены
+ scene.removeBSPPrimitive(primitive);
+ }
+
+ // Посылаем операцию сцены на расчёт BSP
+ scene.addOperation(scene.calculateBSPOperation);
+
+ // Отписываем сцену от операций
+ calculateTransformationOperation.removeSequel(scene.calculateBSPOperation);
+ updateMaterialOperation.removeSequel(scene.changePrimitivesOperation);
+ // Удаляем из сцены материал
+ if (_material != null) {
+ _material.removeFromScene(scene);
+ }
+ super.removeFromScene(scene);
+ }
+
+ /**
+ * @private
+ * Изменение материала.
+ */
+ alternativa3d function addMaterialChangedOperationToScene():void {
+ if (_scene != null) {
+ _scene.addOperation(updateMaterialOperation);
+ }
+ }
+
+ /**
+ * Материал для отображения спрайта.
+ */
+ public function get material():SpriteMaterial {
+ return _material;
+ }
+
+ /**
+ * @private
+ */
+ public function set material(value:SpriteMaterial):void {
+ if (_material != value) {
+ if (_material != null) {
+ _material.removeFromSprite(this);
+ if (_scene != null) {
+ _material.removeFromScene(_scene);
+ }
+ }
+ if (value != null) {
+ if (value._sprite != null) {
+ value._sprite.material = null;
+ }
+ value.addToSprite(this);
+ if (_scene != null) {
+ value.addToScene(_scene);
+ }
+ }
+ _material = value;
+ // Отправляем операцию изменения материала
+ addMaterialChangedOperationToScene();
+ }
+ }
+
+ /**
+ * Имя объекта по умолчанию.
+ *
+ * @return имя объекта по умолчанию
+ */
+ override protected function defaultName():String {
+ return "sprite" + ++counter;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Sprite3D();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function clonePropertiesFrom(source:Object3D):void {
+ super.clonePropertiesFrom(source);
+ material = (source as Sprite3D).material.clone() as SpriteMaterial;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/SpritePrimitive.as b/Alternativa3D5/5.6/alternativa/engine3d/core/SpritePrimitive.as
new file mode 100644
index 0000000..6d143c1
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/core/SpritePrimitive.as
@@ -0,0 +1,33 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.*;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ * Спрайтовый примитив.
+ */
+ public class SpritePrimitive extends PolyPrimitive {
+
+ /**
+ * @private
+ * Спрайт, которому принадлежит примитив.
+ */
+ alternativa3d var sprite:Sprite3D;
+ /**
+ * @private
+ * Параметр используется для сортировки примитивов в камере.
+ */
+ alternativa3d var screenDepth:Number;
+
+ /**
+ * @private
+ * Строковое представление объекта.
+ */
+ override public function toString():String {
+ return "[SpritePrimitive " + sprite.toString() + "]";
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Surface.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Surface.as
new file mode 100644
index 0000000..6147199
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Surface.as
@@ -0,0 +1,479 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.errors.FaceExistsError;
+ import alternativa.engine3d.errors.FaceNotFoundError;
+ import alternativa.engine3d.errors.InvalidIDError;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.types.Set;
+
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IEventDispatcher;
+
+ use namespace alternativa3d;
+
+ /**
+ * Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одной и той же поверхностью.
+ * Между нажатием и отпусканием кнопки могут происходить любые другие события.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.CLICK
+ */
+ [Event (name="click", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь нажимает левую кнопку мыши над поверхностью.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN
+ */
+ [Event (name="mouseDown", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь отпускает левую кнопку мыши над поверхностью.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP
+ */
+ [Event (name="mouseUp", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь наводит курсор мыши на поверхность.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER
+ */
+ [Event (name="mouseOver", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь уводит курсор мыши с поверхности.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT
+ */
+ [Event (name="mouseOut", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь перемещает курсор мыши над поверхностью.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE
+ */
+ [Event (name="mouseMove", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Событие рассылается когда пользователь вращает колесо мыши над поверхностью.
+ *
+ * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL
+ */
+ [Event (name="mouseWheel", type="alternativa.engine3d.events.MouseEvent3D")]
+ /**
+ * Поверхность — набор граней, объединённых в группу. Поверхности используются для установки материалов,
+ * визуализирующих грани объекта.
+ *
+ * Класс реализует интерфейс flash.events.IEventDispatcher и может рассылать мышиные события, содержащие информацию
+ * о точке в трёхмерном пространстве, в которой произошло событие.
+ */
+ public class Surface implements IEventDispatcher {
+ // Операции
+ /**
+ * @private
+ * Изменение набора граней
+ */
+ alternativa3d var changeFacesOperation:Operation = new Operation("changeFaces", this);
+ /**
+ * @private
+ * Изменение материала
+ */
+ alternativa3d var changeMaterialOperation:Operation = new Operation("changeMaterial", this);
+
+ /**
+ * @private
+ * Меш
+ */
+ alternativa3d var _mesh:Mesh;
+ /**
+ * @private
+ * Материал
+ */
+ alternativa3d var _material:SurfaceMaterial;
+ /**
+ * @private
+ * Грани
+ */
+ alternativa3d var _faces:Set = new Set();
+
+ /**
+ * Флаг указывает, будет ли объект принимать мышиные события.
+ */
+ public var mouseEnabled:Boolean = true;
+ /**
+ * Диспетчер событий.
+ */
+ private var dispatcher:EventDispatcher;
+
+ /**
+ * Создание экземпляра поверхности.
+ */
+ public function Surface() {}
+
+ /**
+ * Добавление грани в поверхность.
+ *
+ * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани полигонального объекта
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError грань не найдена в полигональном объекте содержащем поверхность
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ * @throws alternativa.engine3d.errors.FaceExistsError поверхность уже содержит указанную грань
+ *
+ * @see Face
+ */
+ public function addFace(face:Object):void {
+ var byLink:Boolean = face is Face;
+
+ // Проверяем на нахождение поверхности в меше
+ if (_mesh == null) {
+ throw new FaceNotFoundError(face, this);
+ }
+
+ // Проверяем на null
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие грани в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Face(face)._mesh != _mesh) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_mesh._faces[face] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(face, this);
+ } else {
+ if (!(_mesh._faces[face] is Face)) {
+ throw new InvalidIDError(face, this);
+ }
+ }
+ }
+
+ // Находим грань
+ var f:Face = byLink ? Face(face) : _mesh._faces[face];
+
+ // Проверяем наличие грани в поверхности
+ if (_faces.has(f)) {
+ // Если грань уже в поверхности
+ throw new FaceExistsError(f, this);
+ }
+
+ // Проверяем грань на нахождение в другой поверхности
+ if (f._surface != null) {
+ // Удаляем её из той поверхности
+ f._surface._faces.remove(f);
+ f.removeFromSurface(f._surface);
+ }
+
+ // Добавляем грань в поверхность
+ _faces.add(f);
+ f.addToSurface(this);
+
+ // Отправляем операцию изменения набора граней
+ _mesh.addOperationToScene(changeFacesOperation);
+ }
+
+ /**
+ * Удаление грани из поверхности.
+ *
+ * @param face экземпляр класса alternativa.engine3d.core.Face или идентификатор грани полигонального объекта
+ *
+ * @throws alternativa.engine3d.errors.FaceNotFoundError поверхность не содержит указанную грань
+ * @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
+ *
+ * @see Face
+ */
+ public function removeFace(face:Object):void {
+ var byLink:Boolean = face is Face;
+
+ // Проверяем на нахождение поверхности в меше
+ if (_mesh == null) {
+ throw new FaceNotFoundError(face, this);
+ }
+
+ // Проверяем на null
+ if (face == null) {
+ throw new FaceNotFoundError(null, this);
+ }
+
+ // Проверяем наличие грани в меше
+ if (byLink) {
+ // Если удаляем по ссылке
+ if (Face(face)._mesh != _mesh) {
+ // Если грань не в меше
+ throw new FaceNotFoundError(face, this);
+ }
+ } else {
+ // Если удаляем по ID
+ if (_mesh._faces[face] == undefined) {
+ // Если нет грани с таким ID
+ throw new FaceNotFoundError(face, this);
+ } else {
+ if (!(_mesh._faces[face] is Face)) {
+ throw new InvalidIDError(face, this);
+ }
+ }
+
+ }
+
+ // Находим грань
+ var f:Face = byLink ? Face(face) : _mesh._faces[face];
+
+ // Проверяем наличие грани в поверхности
+ if (!_faces.has(f)) {
+ // Если грань не в поверхности
+ throw new FaceNotFoundError(f, this);
+ }
+
+ // Удаляем грань из поверхности
+ _faces.remove(f);
+ f.removeFromSurface(this);
+
+ // Отправляем операцию изменения набора граней
+ _mesh.addOperationToScene(changeFacesOperation);
+ }
+
+ /**
+ * Материал поверхности. При установке нового значения, устанавливаемый материал будет удалён из старой поверхности.
+ */
+ public function get material():SurfaceMaterial {
+ return _material;
+ }
+
+ /**
+ * @private
+ */
+ public function set material(value:SurfaceMaterial):void {
+ if (_material != value) {
+ // Если был материал
+ if (_material != null) {
+ // Удалить материал из поверхности
+ _material.removeFromSurface(this);
+ // Удалить материал из меша
+ if (_mesh != null) {
+ _material.removeFromMesh(_mesh);
+ // Удалить материал из сцены
+ if (_mesh._scene != null) {
+ _material.removeFromScene(_mesh._scene);
+ }
+ }
+ }
+ // Если новый материал
+ if (value != null) {
+ // Если материал был в другой поверхности
+ if (value._surface != null) {
+ // Удалить его оттуда
+ value._surface.material = null;
+ }
+ // Добавить материал в поверхность
+ value.addToSurface(this);
+ // Добавить материал в меш
+ if (_mesh != null) {
+ value.addToMesh(_mesh);
+ // Добавить материал в сцену
+ if (_mesh._scene != null) {
+ value.addToScene(_mesh._scene);
+ }
+ }
+ }
+ // Сохраняем материал
+ _material = value;
+ // Отправляем операцию изменения материала
+ addMaterialChangedOperationToScene();
+ }
+ }
+
+ /**
+ * Набор граней поверхности.
+ */
+ public function get faces():Set {
+ return _faces.clone();
+ }
+
+ /**
+ * Полигональный объект, которому принадлежит поверхность.
+ */
+ public function get mesh():Mesh {
+ return _mesh;
+ }
+
+ /**
+ * Идентификатор поверхности в полигональном объекте. Если поверхность не принадлежит ни одному объекту,
+ * значение идентификатора равно null.
+ */
+ public function get id():Object {
+ return (_mesh != null) ? _mesh.getSurfaceId(this) : null;
+ }
+
+ /**
+ * @private
+ * Добавление в сцену.
+ *
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {
+ // Добавляем на сцену материал
+ if (_material != null) {
+ _material.addToScene(scene);
+ }
+ }
+
+ /**
+ * @private
+ * Удаление из сцены.
+ *
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(changeFacesOperation);
+ scene.removeOperation(changeMaterialOperation);
+ // Удаляем из сцены материал
+ if (_material != null) {
+ _material.removeFromScene(scene);
+ }
+ }
+
+ /**
+ * @private
+ * Добавление к мешу
+ *
+ * @param mesh
+ */
+ alternativa3d function addToMesh(mesh:Mesh):void {
+ // Подписка на операции меша
+
+ // Добавляем в меш материал
+ if (_material != null) {
+ _material.addToMesh(mesh);
+ }
+ // Сохранить меш
+ _mesh = mesh;
+ }
+
+ /**
+ * @private
+ * Удаление из меша
+ *
+ * @param mesh
+ */
+ alternativa3d function removeFromMesh(mesh:Mesh):void {
+ // Отписка от операций меша
+
+ // Удаляем из меша материал
+ if (_material != null) {
+ _material.removeFromMesh(mesh);
+ }
+ // Удалить ссылку на меш
+ _mesh = null;
+ }
+
+ /**
+ * @private
+ * Удаление граней
+ */
+ alternativa3d function removeFaces():void {
+ for (var key:* in _faces) {
+ var face:Face = key;
+ _faces.remove(face);
+ face.removeFromSurface(this);
+ }
+ }
+
+ /**
+ * @private
+ * Изменение материала
+ */
+ alternativa3d function addMaterialChangedOperationToScene():void {
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeMaterialOperation);
+ }
+ }
+
+ /**
+ * Строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ var length:uint = _faces.length;
+ var res:String = "[Surface ID:" + id + ((length > 0) ? " faces:" : "");
+ var i:uint = 0;
+ for (var key:* in _faces) {
+ var face:Face = key;
+ res += face.id + ((i < length - 1) ? ", " : "");
+ i++;
+ }
+ res += "]";
+ return res;
+ }
+
+ /**
+ * Добавление обработчика события.
+ *
+ * @param type тип события
+ * @param listener обработчик события
+ * @param useCapture не используется,
+ * @param priority приоритет обработчика. Обработчики с большим приоритетом выполняются раньше. Обработчики с одинаковым приоритетом
+ * выполняются в порядке их добавления.
+ * @param useWeakReference флаг использования слабой ссылки для обработчика
+ */
+ public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
+ if (dispatcher == null) {
+ dispatcher = new EventDispatcher(this);
+ }
+ useCapture = false;
+ dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
+ }
+
+ /**
+ * Рассылка события.
+ *
+ * @param event посылаемое событие
+ * @return false
+ */
+ public function dispatchEvent(event:Event):Boolean {
+ if (dispatcher != null) {
+ dispatcher.dispatchEvent(event);
+ }
+ return false;
+ }
+
+ /**
+ * Проверка наличия зарегистрированных обработчиков события указанного типа.
+ *
+ * @param type тип события
+ * @return true если есть обработчики события указанного типа, иначе false
+ */
+ public function hasEventListener(type:String):Boolean {
+ if (dispatcher != null) {
+ return dispatcher.hasEventListener(type);
+ }
+ return false;
+ }
+
+ /**
+ * Удаление обработчика события.
+ *
+ * @param type тип события
+ * @param listener обработчик события
+ * @param useCapture не используется
+ */
+ public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void {
+ if (dispatcher != null) {
+ useCapture = false;
+ dispatcher.removeEventListener(type, listener, useCapture);
+ }
+ }
+
+ /**
+ *
+ */
+ public function willTrigger(type:String):Boolean {
+ if (dispatcher != null) {
+ return dispatcher.willTrigger(type);
+ }
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/core/Vertex.as b/Alternativa3D5/5.6/alternativa/engine3d/core/Vertex.as
new file mode 100644
index 0000000..bbfbf7c
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/core/Vertex.as
@@ -0,0 +1,251 @@
+package alternativa.engine3d.core {
+
+ import alternativa.engine3d.*;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+
+ use namespace alternativa3d;
+
+ /**
+ * Вершина полигона в трёхмерном пространстве. Вершина хранит свои координаты, а также ссылки на
+ * полигональный объект и грани этого объекта, которым она принадлежит.
+ */
+ final public class Vertex {
+ // Операции
+ /**
+ * @private
+ * Изменение локальных координат
+ */
+ alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this);
+ /**
+ * @private
+ * Расчёт глобальных координат
+ */
+ alternativa3d var calculateCoordsOperation:Operation = new Operation("calculateCoords", this, calculateCoords, Operation.VERTEX_CALCULATE_COORDS);
+
+ /**
+ * @private
+ * Меш
+ */
+ alternativa3d var _mesh:Mesh;
+ /**
+ * @private
+ * Координаты точки
+ */
+ alternativa3d var _coords:Point3D;
+ /**
+ * @private
+ * Грани
+ */
+ alternativa3d var _faces:Set = new Set();
+ /**
+ * @private
+ * Координаты в сцене
+ */
+ alternativa3d var globalCoords:Point3D = new Point3D();
+
+ /**
+ * Создание экземпляра вершины.
+ *
+ * @param x координата вершины по оси X
+ * @param y координата вершины по оси Y
+ * @param z координата вершины по оси Z
+ */
+ public function Vertex(x:Number = 0, y:Number = 0, z:Number = 0) {
+ _coords = new Point3D(x, y, z);
+
+ // Изменение координат инициирует пересчёт глобальных координат
+ changeCoordsOperation.addSequel(calculateCoordsOperation);
+ }
+
+ /**
+ * Вызывается из операции calculateCoordsOperation для расчета глобальных координат вершины
+ */
+ private function calculateCoords():void {
+ globalCoords.copy(_coords);
+ globalCoords.transform(_mesh._transformation);
+ }
+
+ /**
+ * @private
+ */
+ public function set x(value:Number):void {
+ if (_coords.x != value) {
+ _coords.x = value;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeCoordsOperation);
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set y(value:Number):void {
+ if (_coords.y != value) {
+ _coords.y = value;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeCoordsOperation);
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set z(value:Number):void {
+ if (_coords.z != value) {
+ _coords.z = value;
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeCoordsOperation);
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function set coords(value:Point3D):void {
+ if (!_coords.equals(value)) {
+ _coords.copy(value);
+ if (_mesh != null) {
+ _mesh.addOperationToScene(changeCoordsOperation);
+ }
+ }
+ }
+
+ /**
+ * координата вершины по оси X.
+ */
+ public function get x():Number {
+ return _coords.x;
+ }
+
+ /**
+ * координата вершины по оси Y.
+ */
+ public function get y():Number {
+ return _coords.y;
+ }
+
+ /**
+ * координата вершины по оси Z.
+ */
+ public function get z():Number {
+ return _coords.z;
+ }
+
+ /**
+ * Координаты вершины.
+ */
+ public function get coords():Point3D {
+ return _coords.clone();
+ }
+
+ /**
+ * Полигональный объект, которому принадлежит вершина.
+ */
+ public function get mesh():Mesh {
+ return _mesh;
+ }
+
+ /**
+ * Множество граней, которым принадлежит вершина. Каждый элемент множества является объектом класса
+ * altertnativa.engine3d.core.Face.
+ *
+ * @see Face
+ */
+ public function get faces():Set {
+ return _faces.clone();
+ }
+
+ /**
+ * Идентификатор вершины в полигональном объекте. Если вершина не принадлежит полигональному объекту, возвращается null.
+ */
+ public function get id():Object {
+ return (_mesh != null) ? _mesh.getVertexId(this) : null;
+ }
+
+ /**
+ * @private
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {
+ // При добавлении на сцену расчитать глобальные координаты
+ scene.addOperation(calculateCoordsOperation);
+ }
+
+ /**
+ * @private
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {
+ // Удаляем все операции из очереди
+ scene.removeOperation(calculateCoordsOperation);
+ scene.removeOperation(changeCoordsOperation);
+ }
+
+ /**
+ * @private
+ * @param mesh
+ */
+ alternativa3d function addToMesh(mesh:Mesh):void {
+ // Подписка на операции меша
+ mesh.changeCoordsOperation.addSequel(calculateCoordsOperation);
+ mesh.changeRotationOrScaleOperation.addSequel(calculateCoordsOperation);
+ // Сохранить меш
+ _mesh = mesh;
+ }
+
+ /**
+ * @private
+ * @param mesh
+ */
+ alternativa3d function removeFromMesh(mesh:Mesh):void {
+ // Отписка от операций меша
+ mesh.changeCoordsOperation.removeSequel(calculateCoordsOperation);
+ mesh.changeRotationOrScaleOperation.removeSequel(calculateCoordsOperation);
+ // Удалить зависимые грани
+ for (var key:* in _faces) {
+ var face:Face = key;
+ mesh.removeFace(face);
+ }
+ // Удалить ссылку на меш
+ _mesh = null;
+ }
+
+ /**
+ * @private
+ * @param face
+ */
+ alternativa3d function addToFace(face:Face):void {
+ // Подписка грани на операции
+ changeCoordsOperation.addSequel(face.calculateUVOperation);
+ changeCoordsOperation.addSequel(face.calculateNormalOperation);
+ // Добавить грань в список
+ _faces.add(face);
+ }
+
+ /**
+ * @private
+ * @param face
+ */
+ alternativa3d function removeFromFace(face:Face):void {
+ // Отписка грани от операций
+ changeCoordsOperation.removeSequel(face.calculateUVOperation);
+ changeCoordsOperation.removeSequel(face.calculateNormalOperation);
+ // Удалить грань из списка
+ _faces.remove(face);
+ }
+
+ /**
+ * Строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ public function toString():String {
+ return "[Vertex ID:" + id + " " + _coords.x.toFixed(2) + ", " + _coords.y.toFixed(2) + ", " + _coords.z.toFixed(2) + "]";
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/display/Skin.as b/Alternativa3D5/5.6/alternativa/engine3d/display/Skin.as
new file mode 100644
index 0000000..2aaae45
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/display/Skin.as
@@ -0,0 +1,65 @@
+package alternativa.engine3d.display {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.materials.Material;
+
+ import flash.display.Graphics;
+ import flash.display.Sprite;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ * Контейнер, используемый материалами для отрисовки примитивов. Каждый примитив BSP-дерева рисуется в своём контейнере.
+ */
+ public class Skin extends Sprite {
+
+ /**
+ * @private
+ * Графика скина (для быстрого доступа)
+ */
+ alternativa3d var gfx:Graphics = graphics;
+
+ /**
+ * @private
+ * Ссылка на следующий скин
+ */
+ alternativa3d var nextSkin:Skin;
+
+ /**
+ * @private
+ * Примитив
+ */
+ alternativa3d var primitive:PolyPrimitive;
+
+ /**
+ * @private
+ * Материал, связанный со скином.
+ */
+ alternativa3d var material:Material;
+
+ // Хранилище неиспользуемых скинов
+ static private var collector:Array = new Array();
+
+ /**
+ * @private
+ * Создание скина.
+ */
+ static alternativa3d function createSkin():Skin {
+ var skin:Skin;
+ if ((skin = collector.pop()) != null) {
+ return skin;
+ }
+ return new Skin();
+ }
+
+ /**
+ * @private
+ * Удаление скина, все ссылки должны быть почищены.
+ */
+ static alternativa3d function destroySkin(skin:Skin):void {
+ collector.push(skin);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/display/View.as b/Alternativa3D5/5.6/alternativa/engine3d/display/View.as
new file mode 100644
index 0000000..0d39fbd
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/display/View.as
@@ -0,0 +1,892 @@
+package alternativa.engine3d.display {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Sprite3D;
+ import alternativa.engine3d.core.SpritePrimitive;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.events.MouseEvent3D;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+
+ import flash.display.Sprite;
+ import flash.events.Event;
+ import flash.events.MouseEvent;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Вьюпорт для вывода изображения с камеры.
+ */
+ public class View extends Sprite {
+
+ /**
+ * @private
+ * Область отрисовки спрайтов
+ */
+ alternativa3d var canvas:Sprite;
+
+ private var _camera:Camera3D;
+
+ /**
+ * @private
+ * Ширина области вывода
+ */
+ alternativa3d var _width:Number;
+ /**
+ * @private
+ * Высота области вывода
+ */
+ alternativa3d var _height:Number;
+
+ /**
+ * @private
+ * Флаг интерактивности вьюпорта. В интерактивном режиме вьюпорт принимает события мыши и транслирует их
+ * в подсистему трёхмерных событий, которая, в свою очередь, преобразует двумерный клик в трёхмерный и рассылает
+ * события всем подписчикам.
+ */
+ alternativa3d var _interactive:Boolean;
+
+ // Грань под курсором
+ private var faceUnderPoint:Face;
+ private var objectUnderPoint:Object3D;
+
+ private var lastMouseEvent:MouseEvent;
+ private var stagePoint:Point = new Point();
+
+ // Текущая грань
+ private var currentFace:Face;
+ // Текущая поверхность
+ private var currentSurface:Surface;
+ // Текущий объект
+ private var currentObject:Object3D;
+ // Грань, на которой было событие MOUSE_DOWN
+ private var pressedFace:Face;
+ // Поверхность, на которой было событие MOUSE_DOWN
+ private var pressedSurface:Surface;
+ // Объект, на котором было событие MOUSE_DOWN
+ private var pressedObject:Object3D;
+
+ // Направляющий вектор проецирующей прямой в камере
+ private var lineVector:Point3D = new Point3D();
+ // Вспомогательная переменная для хранения точки проецирующей прямой в ортографической камере
+ private var linePoint:Point3D = new Point3D();
+ // Точка на объекте под курсором в глобальной системе координат.
+ private var globalCursor3DCoords:Point3D = new Point3D();
+ // Координаты курсора в системе координат объекта
+ private var localCursor3DCoords:Point3D = new Point3D();
+ // UV-координаты в грани под курсором
+ private var uvPoint:Point = new Point();
+ // Вспомогательная матрица
+ private var inverseMatrix:Matrix3D = new Matrix3D();
+
+ /**
+ * Создаёт новый экземпляр вьюпорта.
+ *
+ * @param camera камера, связанная с вьюпортом
+ * @param width ширина вьюпорта
+ * @param height высота вьюпорта
+ */
+ public function View(camera:Camera3D = null, width:Number = 0, height:Number = 0) {
+ canvas = new Sprite();
+ canvas.mouseEnabled = false;
+ canvas.mouseChildren = false;
+ canvas.tabEnabled = false;
+ canvas.tabChildren = false;
+ addChild(canvas);
+
+ this.camera = camera;
+ this.width = width;
+ this.height = height;
+
+ addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);
+ }
+
+ /**
+ * Камера, с которой выводится изображение.
+ */
+ public function get camera():Camera3D {
+ return _camera;
+ }
+
+ /**
+ * @private
+ */
+ public function set camera(value:Camera3D):void {
+ if (_camera != value) {
+ // Если была камера
+ if (_camera != null) {
+ // Удалить камеру
+ _camera.removeFromView(this);
+ }
+ // Если новая камера
+ if (value != null) {
+ // Если камера была в другом вьюпорте
+ if (value._view != null) {
+ // Удалить её оттуда
+ value._view.camera = null;
+ }
+ // Добавить камеру
+ value.addToView(this);
+ } else {
+ // Зачистка скинов
+ if (canvas.numChildren > 0) {
+ var skin:Skin = Skin(canvas.getChildAt(0));
+ while (skin != null) {
+ // Сохраняем следующий
+ var next:Skin = skin.nextSkin;
+ // Удаляем из канваса
+ canvas.removeChild(skin);
+ // Очистка скина
+ if (skin.material != null) {
+ skin.material.clear(skin);
+ }
+ // Зачищаем ссылки
+ skin.nextSkin = null;
+ skin.primitive = null;
+ skin.material = null;
+ // Удаляем
+ Skin.destroySkin(skin);
+ // Следующий устанавливаем текущим
+ skin = next;
+ }
+ }
+ }
+ // Сохраняем камеру
+ _camera = value;
+ }
+ }
+
+ /**
+ * Ширина вьюпорта в пикселях.
+ */
+ override public function get width():Number {
+ return _width;
+ }
+
+ /**
+ * @private
+ */
+ override public function set width(value:Number):void {
+ if (_width != value) {
+ _width = value;
+ canvas.x = _width*0.5;
+ if (_camera != null) {
+ camera.addOperationToScene(camera.calculatePlanesOperation);
+ }
+ }
+ }
+
+ /**
+ * Высота вьюпорта в пикселях.
+ */
+ override public function get height():Number {
+ return _height;
+ }
+
+ /**
+ * @private
+ */
+ override public function set height(value:Number):void {
+ if (_height != value) {
+ _height = value;
+ canvas.y = _height*0.5;
+ if (_camera != null) {
+ camera.addOperationToScene(camera.calculatePlanesOperation);
+ }
+ }
+ }
+
+ /**
+ * Возвращает объект, находящийся под указанной точкой вьюпорта.
+ *
+ * @param viewPoint координаты точки относительно вьюпорта. Верхнему левому углу соотвествуют координаты (0, 0).
+ *
+ * @return ближайший к камере объект под заданной точкой вьюпорта, либо null,
+ * если под указанной точкой нет объектов или вьюпорт не помещён на Stage.
+ * Объект может быть гранью (Face) или спрайтом (Sprite3D).
+ */
+ public function getObjectUnderPoint(viewPoint:Point):Object {
+ if (stage == null) {
+ return null;
+ }
+ var stagePoint:Point = localToGlobal(viewPoint);
+ var objects:Array = stage.getObjectsUnderPoint(stagePoint);
+ var skin:Skin;
+ for (var i:int = objects.length - 1; i >= 0; i--) {
+ skin = objects[i] as Skin;
+ if (skin != null && skin.parent.parent == this) {
+ return skin.primitive.face != null ? skin.primitive.face : (skin.primitive as SpritePrimitive).sprite;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Возвращает объекты, находящиеся под указанной точкой вьюпорта.
+ *
+ * @param viewPoint координаты точки относительно вьюпорта. Верхнему левому углу соотвествуют координаты (0, 0).
+ *
+ * @return массив объектов, расположенных под заданной точкой вьюпорта. Первым элементом массива является самый дальний объект.
+ * Объектами могут быть грани (Face) или спрайты (Sprite3D). Если под указанной точкой нет ни одного объекта, массив будет пустым.
+ * Если вьюпорт не помещен на Stage, возвращается null.
+ */
+ override public function getObjectsUnderPoint(viewPoint:Point):Array {
+ if (stage == null) {
+ return null;
+ }
+ var stagePoint:Point = localToGlobal(viewPoint);
+ var objects:Array = stage.getObjectsUnderPoint(stagePoint);
+ var res:Array = new Array();
+ var length:uint = objects.length;
+ for (var i:uint = 0; i < length; i++) {
+ var skin:Skin = objects[i] as Skin;
+ if (skin != null && skin.parent.parent == this) {
+ if (skin.primitive.face != null) {
+ res.push(skin.primitive.face);
+ } else {
+ res.push((skin.primitive as SpritePrimitive).sprite);
+ }
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Проецирует заданную глобальными координатами точку на плоскость вьюпорта.
+ *
+ * @param point глобальные координаты проецируемой точки
+ *
+ * @return объект Point3D, содержащий координаты проекции точки относительно левого верхнего угла вьюпорта и z-координату
+ * точки в системе координат камеры. Если вьюпорту не назначена камера или камера не находится в сцене, возвращается null.
+ */
+ public function projectPoint(point:Point3D):Point3D {
+ if (_camera == null || _camera._scene == null) {
+ return null;
+ }
+
+ var cameraMatrix:Matrix3D = Object3D.matrix2;
+ var focalLength:Number = _camera._focalLength;;
+ var zoom:Number;
+
+ // Вычисление матрицы трансформации камеры
+ if (_camera.getTransformation(cameraMatrix)) {
+ // Матрица была пересчитана заново
+ cameraMatrix.invert();
+ if (_camera._orthographic) {
+ // Учёт масштабирования в ортографической камере
+ zoom = _camera.zoom;
+ cameraMatrix.scale(zoom, zoom, zoom);
+ }
+ } else {
+ // Пересчёта не потребовалось, проверяем изменение зума
+ if (_camera._orthographic && _camera.calculateMatrixOperation.queued) {
+ cameraMatrix.invert();
+ zoom = _camera.zoom;
+ cameraMatrix.scale(zoom, zoom, zoom);
+ } else {
+ // Зум не менялся или перспективный режим, просто копируем обратную матрицу
+ cameraMatrix = _camera.cameraMatrix;
+ }
+ }
+ // Расчёт фокусного расстояния
+ if (!_camera._orthographic && _camera.calculatePlanesOperation.queued) {
+ focalLength = 0.5 * Math.sqrt(_height * _height + _width * _width) / Math.tan(0.5 * _camera._fov);
+ }
+ // Координаты точки в системе координат камеры
+ var x:Number = cameraMatrix.a * point.x + cameraMatrix.b * point.y + cameraMatrix.c * point.z + cameraMatrix.d;
+ var y:Number = cameraMatrix.e * point.x + cameraMatrix.f * point.y + cameraMatrix.g * point.z + cameraMatrix.h;
+ var z:Number = cameraMatrix.i * point.x + cameraMatrix.j * point.y + cameraMatrix.k * point.z + cameraMatrix.l;
+ // Проекция точки на вьюпорт
+ if (_camera._orthographic) {
+ return new Point3D(x + (_width >> 1), y + (_height >> 1), z);
+ } else {
+ return new Point3D(x * focalLength / z + (_width >> 1), y * focalLength / z + (_height >> 1), z);
+ }
+ }
+
+ /**
+ * Интерактивность области вьюпорта. При включённой интерактивности возможно использование системы мышиных событий.
+ *
+ * @default false
+ */
+ public function get interactive():Boolean {
+ return _interactive;
+ }
+
+ /**
+ * @private
+ */
+ public function set interactive(value:Boolean):void {
+ if (_interactive == value) {
+ return;
+ }
+ _interactive = value;
+
+ if (_interactive) {
+ addEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent);
+ addEventListener(MouseEvent.MOUSE_UP, onMouseEvent);
+ addEventListener(MouseEvent.MOUSE_MOVE, onMouseEvent);
+ addEventListener(MouseEvent.MOUSE_WHEEL, onMouseEvent);
+ addEventListener(MouseEvent.MOUSE_OUT, onMouseEvent);
+ } else {
+ removeEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent);
+ removeEventListener(MouseEvent.MOUSE_UP, onMouseEvent);
+ removeEventListener(MouseEvent.MOUSE_MOVE, onMouseEvent);
+ removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseEvent);
+ removeEventListener(MouseEvent.MOUSE_OUT, onMouseEvent);
+ if (stage != null) {
+ stage.removeEventListener(MouseEvent.MOUSE_UP, stageMouseUp);
+ }
+ pressedFace = currentFace = null;
+ pressedSurface = currentSurface = null;
+ pressedObject = currentObject = null;
+ }
+ }
+
+ /**
+ * Проецирует двумерную точку во вьюпорте на заданную плоскость в трёхмерном пространстве.
+ *
+ * @param viewPoint координаты точки во вьюпорте. Верхнему левому углу соответствуют координаты (0, 0).
+ * @param planeNormal нормаль плоскости в глобальной системе координат, на которую проецируется точка
+ * @param planeOffset смещение плоскости в глобальной системе координат, на которую проецируется точка
+ * @param result результат проецирования будет записан в этот параметр. Если в качестве значения будет указано null, метод
+ * создаст новый экземпляр Point3D и вернёт результат в нём.
+ * @return переданный в параметре result экземпляр Point3D или новый экземпляр, если значение result равно null. Если возможно бесконечное
+ * количество решений (линия зрения параллельна заданной плоскости), то результат содержит значения NaN.
+ * Если вьюпорту не назначена камера или камера не находится в сцене, возвращается null.
+ */
+ public function projectViewPointToPlane(viewPoint:Point, planeNormal:Point3D, planeOffset:Number, result:Point3D = null):Point3D {
+ if (_camera == null || _camera._scene == null) {
+ return null;
+ }
+ if (result == null) {
+ result = new Point3D();
+ }
+ calculateRayOriginAndVector(viewPoint.x - (_width >> 1), viewPoint.y - (_height >> 1), linePoint, lineVector, true);
+ if (!calculateLineAndPlaneIntersection(linePoint, lineVector, planeNormal, planeOffset, result)) {
+ result.reset(NaN, NaN, NaN);
+ }
+ return result;
+ }
+
+ /**
+ * Вычисляет координаты точки в системе координат камеры, связанной с вьюпортом. Если камера в режиме перспективной
+ * проекции, то метод вычислит координаты точки, лежащей на прямой, проходящей через начало координат камеры и указанную точку
+ * вьюпорта. Если камера в режиме ортографической проекции, то метод вычислит координаты точки, лежащей на прямой,
+ * перпендикулярной фокальной плоскости камеры и проходящей через указанную точку вьюпорта.
+ *
+ * @param viewPoint координаты точки во вьюпорте. Верхнему левому углу соответствуют координаты (0, 0).
+ * @param depth глубина точки в камере — координата Z в системе координат камеры
+ * @param result результат будет записан в этот параметр. Если в качестве значения будет указано null, метод
+ * создаст новый экземпляр Point3D и вернёт результат в нём.
+ * @return координаты точки в системе координат камеры или null, если с вьюпортом не связана камера
+ */
+ public function get3DCoords(viewPoint:Point, depth:Number, result:Point3D = null):Point3D {
+ if (_camera == null) {
+ return null;
+ }
+ if (result == null) {
+ result = new Point3D();
+ }
+ if (_camera._orthographic) {
+ result.x = (viewPoint.x - (_width >> 1))/camera._zoom;
+ result.y = (viewPoint.y - (_height >> 1))/camera._zoom;
+ } else {
+ var k:Number = depth/_camera.focalLength;
+ result.x = (viewPoint.x - (_width >> 1))*k;
+ result.y = (viewPoint.y - (_height >> 1))*k;
+ }
+ result.z = depth;
+ return result;
+ }
+
+ /**
+ *
+ */
+ private function onRemovedFromStage(e:Event):void {
+ interactive = false;
+ }
+
+ /**
+ * Сброс нажатых объектов при отпускании кнопки мыши вне вьюпорта.
+ */
+ private function stageMouseUp(e:MouseEvent):void {
+ if (stage == null) {
+ return;
+ }
+ pressedFace = null;
+ pressedSurface = null;
+ pressedObject = null;
+ stage.removeEventListener(MouseEvent.MOUSE_UP, stageMouseUp);
+ }
+
+ /**
+ * Метод находит интерактивный объект (Object3D) и интерактивную грань, если возможно, находящиеся под указанной точкой в области вывода.
+ *
+ * @param pointX X-координата точки относительно области вывода
+ * @param pointY Y-координата точки относительно области вывода
+ */
+ private function getInteractiveObjectUnderPoint(pointX:Number, pointY:Number):void {
+ if (stage == null) {
+ return;
+ }
+ faceUnderPoint = null;
+ objectUnderPoint = null;
+ stagePoint.x = pointX;
+ stagePoint.y = pointY;
+ var objects:Array = stage.getObjectsUnderPoint(stagePoint);
+ var skin:Skin;
+ for (var i:int = objects.length - 1; i >= 0; i--) {
+ skin = objects[i] as Skin;
+ if (skin != null && skin.parent.parent == this) {
+ if (skin.primitive.face != null) {
+ // Скин, содержащий PolyPrimitive
+ if (skin.primitive.face._mesh.mouseEnabled) {
+ faceUnderPoint = skin.primitive.face;
+ objectUnderPoint = faceUnderPoint._mesh;
+ return;
+ }
+ } else {
+ // Скин, содержащий SpritePrimitive
+ var sprite:Sprite3D = (skin.primitive as SpritePrimitive).sprite;
+ if (sprite.mouseEnabled) {
+ objectUnderPoint = sprite;
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Вычисление свойств точки объекта, находящегося под указанной точкой фокусной плоскости камеры. Метод расчитывает глобальные и локальные
+ * 3D-координаты точки, а также её UV-координаты.
+ *
+ * @param canvasX
+ * @param canvasY
+ */
+ private function getInteractiveObjectPointProperties(canvasX:Number, canvasY:Number):void {
+ if (objectUnderPoint == null) {
+ return;
+ }
+ calculateRayOriginAndVector(canvasX, canvasY, linePoint, lineVector);
+ // Вычисление глобальных координат точки пересечения проецирующей прямой и плоскости объекта
+ var normal:Point3D;
+ var offset:Number;
+ if (faceUnderPoint != null) {
+ // Работаем с гранью
+ normal = faceUnderPoint.globalNormal;
+ offset = faceUnderPoint.globalOffset;
+ } else {
+ // Работаем со спрайтом
+ normal = lineVector.clone();
+ normal.invert();
+ globalCursor3DCoords.copy(objectUnderPoint._coords);
+ globalCursor3DCoords.transform(objectUnderPoint._transformation);
+ offset = globalCursor3DCoords.dot(normal);
+ }
+ calculateLineAndPlaneIntersection(linePoint, lineVector, normal, offset, globalCursor3DCoords);
+ // Вычисление локальных координат точки пересечения
+ inverseMatrix.copy((faceUnderPoint != null ? faceUnderPoint._mesh : objectUnderPoint)._transformation);
+ inverseMatrix.invert();
+ localCursor3DCoords.copy(globalCursor3DCoords);
+ localCursor3DCoords.transform(inverseMatrix);
+ // Вычисление UV-координат
+ if (faceUnderPoint != null) {
+ var uv:Point = faceUnderPoint.getUV(localCursor3DCoords);
+ if (uv != null) {
+ uvPoint.x = uv.x;
+ uvPoint.y = uv.y;
+ } else {
+ uvPoint.x = NaN;
+ uvPoint.y = NaN;
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private function createSimpleMouseEvent3D(type:String, object:Object3D, surface:Surface, face:Face):MouseEvent3D {
+ var altKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.altKey;
+ var ctrlKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.ctrlKey;
+ var shiftKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.shiftKey;
+ var delta:int = lastMouseEvent == null ? 0 : lastMouseEvent.delta;
+ return new MouseEvent3D(type, this, object, surface, face,
+ NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN,
+ altKey, ctrlKey, shiftKey, delta);
+ }
+
+ /**
+ *
+ */
+ private function createFullMouseEvent3D(type:String, object:Object3D, surface:Surface, face:Face):MouseEvent3D {
+ var altKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.altKey;
+ var ctrlKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.ctrlKey;
+ var shiftKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.shiftKey;
+ var delta:int = lastMouseEvent == null ? 0 : lastMouseEvent.delta;
+ return new MouseEvent3D(type, this, object, surface, face,
+ globalCursor3DCoords.x, globalCursor3DCoords.y, globalCursor3DCoords.z, localCursor3DCoords.x, localCursor3DCoords.y, localCursor3DCoords.z, uvPoint.x, uvPoint.y,
+ altKey, ctrlKey, shiftKey, delta);
+ }
+
+ /**
+ * Обработка мышиного события на вьюпорте и передача его в систему трёхмерных событий.
+ */
+ private function onMouseEvent(e:MouseEvent):void {
+ if (stage == null) {
+ return;
+ }
+ // Сохранение события для использования в функциях создания MouseEvent3D
+ lastMouseEvent = e;
+ // Получение объекта под курсором и свойств точки на этом объекте
+ getInteractiveObjectUnderPoint(stage.mouseX, stage.mouseY);
+ getInteractiveObjectPointProperties(mouseX - (_width >> 1), mouseY - (_height >> 1));
+ // Обработка события
+ switch (e.type) {
+ case MouseEvent.MOUSE_MOVE:
+ processMouseMove();
+ break;
+ case MouseEvent.MOUSE_OUT:
+ stage.addEventListener(MouseEvent.MOUSE_UP, stageMouseUp);
+ checkMouseOverOut();
+ break;
+ case MouseEvent.MOUSE_DOWN:
+ processMouseDown();
+ break;
+ case MouseEvent.MOUSE_UP:
+ processMouseUp();
+ break;
+ case MouseEvent.MOUSE_WHEEL:
+ processMouseWheel();
+ break;
+ }
+ lastMouseEvent = null;
+ }
+
+ /**
+ * Обработка нажатия кнопки мыши во вьюпорте. Генерируются события: MouseEvent3D.MOUSE_DOWN.
+ */
+ private function processMouseDown():void {
+ if (objectUnderPoint == null) {
+ return;
+ }
+ if (faceUnderPoint != null) {
+ currentFace = faceUnderPoint;
+ currentSurface = faceUnderPoint._surface;
+ } else {
+ currentFace = null;
+ currentSurface = null;
+ }
+ currentObject = pressedObject = objectUnderPoint;
+
+ var evt:MouseEvent3D;
+ if (currentFace != null && currentFace.mouseEnabled) {
+ pressedFace = currentFace;
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_DOWN, currentObject, currentSurface, currentFace);
+ currentFace.dispatchEvent(evt);
+ }
+
+ if (currentSurface != null && currentSurface.mouseEnabled) {
+ pressedSurface = currentSurface;
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_DOWN, currentObject, currentSurface, currentFace);
+ currentSurface.dispatchEvent(evt);
+ }
+
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_DOWN, currentObject, currentSurface, currentFace);
+ currentObject.dispatchEvent(evt);
+ }
+
+ /**
+ * Обработка отжатия кнопки мыши во вьюпорте. Генерируются события: MouseEvent3D.MOUSE_UP, MouseEvent3D.CLICK.
+ */
+ private function processMouseUp():void {
+ if (objectUnderPoint == null) {
+ pressedFace = null;
+ pressedSurface = null;
+ pressedObject = null;
+ return;
+ }
+
+ if (faceUnderPoint != null) {
+ currentFace = faceUnderPoint;
+ currentSurface = faceUnderPoint._surface;
+ } else {
+ currentFace = null;
+ currentSurface = null;
+ }
+ currentObject = objectUnderPoint;
+
+ var evt:MouseEvent3D;
+ // MouseEvent3D.MOUSE_UP
+ if (currentFace != null && currentFace.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_UP, currentObject, currentSurface, currentFace);
+ currentFace.dispatchEvent(evt);
+ }
+
+ if (currentSurface != null && currentSurface.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_UP, currentObject, currentSurface, currentFace);
+ currentSurface.dispatchEvent(evt);
+ }
+
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_UP, currentObject, currentSurface, currentFace);
+ currentObject.dispatchEvent(evt);
+
+ // MouseEvent3D.CLICK
+ if (currentFace != null && currentFace == pressedFace && currentFace.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.CLICK, currentObject, currentSurface, currentFace);
+ currentFace.dispatchEvent(evt);
+ }
+
+ if (currentSurface != null && currentSurface == pressedSurface && currentSurface.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.CLICK, currentObject, currentSurface, currentFace);
+ currentSurface.dispatchEvent(evt);
+ }
+
+ if (currentObject == pressedObject) {
+ evt = createFullMouseEvent3D(MouseEvent3D.CLICK, currentObject, currentSurface, currentFace);
+ currentObject.dispatchEvent(evt);
+ }
+
+ pressedFace = null;
+ pressedSurface = null;
+ pressedObject = null;
+ }
+
+ /**
+ * Обработка вращения колеса мыши во вьюпорте. Генерируются события: MouseEvent3D.MOUSE_WHEEL.
+ */
+ private function processMouseWheel():void {
+ if (objectUnderPoint == null) {
+ return;
+ }
+
+ var evt:MouseEvent3D;
+ if (faceUnderPoint != null) {
+ currentFace = faceUnderPoint;
+ currentSurface = faceUnderPoint._surface;
+
+ if (currentFace.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_WHEEL, currentObject, currentSurface, currentFace);
+ currentFace.dispatchEvent(evt);
+ }
+
+ if (currentSurface.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_WHEEL, currentObject, currentSurface, currentFace);
+ currentSurface.dispatchEvent(evt);
+ }
+ } else {
+ currentFace = null;
+ currentSurface = null;
+ }
+ currentObject = objectUnderPoint;
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_WHEEL, currentObject, currentSurface, currentFace);
+ currentObject.dispatchEvent(evt);
+ }
+
+ /**
+ * @private
+ * Метод проверяет наличиче событий MOUSE_OVER, MOUSE_OUT для объектов сцены, их поверхностей и граней.
+ *
+ * @param checkObject флаг необходимости предварительно получить объект под курсором. Используется при вызове метода из функции отрисовки камеры.
+ */
+ alternativa3d function checkMouseOverOut(checkObject:Boolean = false):void {
+ if (stage == null) {
+ return;
+ }
+ if (checkObject) {
+ getInteractiveObjectUnderPoint(stage.mouseX, stage.mouseY);
+ getInteractiveObjectPointProperties(mouseX - (_width >> 1), mouseY - (_height >> 1));
+ }
+ var evt:MouseEvent3D;
+ if (objectUnderPoint == null) {
+ // Мышь ушла с объекта, генерируются события MOUSE_OUT
+ if (currentFace != null) {
+ // MOUSE_OUT для грани
+ if (currentFace.mouseEnabled) {
+ evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
+ currentFace.dispatchEvent(evt);
+ }
+ // MOUSE_OUT для поверхности
+ if (currentSurface.mouseEnabled) {
+ evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
+ currentSurface.dispatchEvent(evt);
+ }
+ }
+
+ if (currentObject != null) {
+ // MOUSE_OUT для объекта
+ evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
+ currentObject.dispatchEvent(evt);
+ }
+
+ currentFace = null;
+ currentSurface = null;
+ currentObject = null;
+ } else {
+ // Мышь на каком-то объекте
+ var surface:Surface;
+ var faceChanged:Boolean;
+ var surfaceChanged:Boolean;
+ var objectChanged:Boolean;
+
+ if (faceUnderPoint != null) {
+ surface = faceUnderPoint._surface;
+ }
+ //
+ if (faceUnderPoint != currentFace) {
+ // MOUSE_OUT для грани
+ if (currentFace != null && currentFace.mouseEnabled) {
+ evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
+ currentFace.dispatchEvent(evt);
+ }
+ faceChanged = true;
+ // MOUSE_OUT для поверхности
+ if (surface != currentSurface) {
+ if (currentSurface != null && currentSurface.mouseEnabled) {
+ evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
+ currentSurface.dispatchEvent(evt);
+ }
+ surfaceChanged = true;
+ }
+ }
+ // MOUSE_OUT для объекта
+ if (objectUnderPoint != currentObject) {
+ if (currentObject != null) {
+ evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
+ currentObject.dispatchEvent(evt);
+ }
+ objectChanged = true;
+ }
+
+ currentFace = faceUnderPoint;
+ currentSurface = surface;
+ currentObject = objectUnderPoint;
+ if (currentFace != null) {
+ // MOUSE_OVER для грани
+ if (faceChanged && currentFace.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_OVER, currentObject, currentSurface, currentFace);
+ currentFace.dispatchEvent(evt);
+ }
+ // MOUSE_OVER для поверхности
+ if (surfaceChanged && currentSurface.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_OVER, currentObject, currentSurface, currentFace);
+ currentSurface.dispatchEvent(evt);
+ }
+ }
+ // MOUSE_OVER для объекта
+ if (objectChanged) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_OVER, currentObject, currentSurface, currentFace);
+ currentObject.dispatchEvent(evt);
+ }
+ }
+ }
+
+ /**
+ * Обработчик движения мыши.
+ */
+ private function processMouseMove():void {
+ // Запуск проверки на наличие событий MOUSE_OVER и MOUSE_OUT
+ checkMouseOverOut();
+ // Генерация событий MOUSE_MOVE
+ var evt:MouseEvent3D;
+ if (currentFace != null) {
+ // Мышь на каком-то объекте
+ if (currentFace.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_MOVE, currentObject, currentSurface, currentFace);
+ currentFace.dispatchEvent(evt);
+ }
+
+ if (currentSurface.mouseEnabled) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_MOVE, currentObject, currentSurface, currentFace);
+ currentSurface.dispatchEvent(evt);
+ }
+ }
+
+ if (currentObject != null) {
+ evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_MOVE, currentObject, currentSurface, currentFace);
+ currentObject.dispatchEvent(evt);
+ }
+ }
+
+ /**
+ * @private
+ * Вычисляет точку пересечения прямой и плоскости.
+ *
+ * @param linePoint точка, лежащая на прямой
+ * @param lineVector направляющий вектор прямой
+ * @param planeNormal нормаль плоскости
+ * @param planeOffset смещение плоскости
+ * @param result переменная для сохранения координат точки пересечения
+ *
+ * @return true если прямая и плоскость пересекаются, иначе false
+ */
+ private function calculateLineAndPlaneIntersection(linePoint:Point3D, lineVector:Point3D, planeNormal:Point3D, planeOffset:Number, result:Point3D):Boolean {
+ var dot:Number = planeNormal.x*lineVector.x + planeNormal.y*lineVector.y + planeNormal.z*lineVector.z;
+ if (dot < 1E-8 && dot > -1E-8) {
+ // Прямая и плосоксть параллельны
+ return false;
+ }
+ var k:Number = (planeOffset - linePoint.x*planeNormal.x - linePoint.y*planeNormal.y - linePoint.z*planeNormal.z)/dot;
+ result.x = linePoint.x + k*lineVector.x;
+ result.y = linePoint.y + k*lineVector.y;
+ result.z = linePoint.z + k*lineVector.z;
+ return true;
+ }
+
+ /**
+ * Вычисляет точку и направляющий вектор для луча зрения, проходящего через заданную точку вьюпорта.
+ *
+ * @param rayOrigin сюда будут записаны глобальные координаты начальной точки луча
+ * @param rayVector сюда будут записаны глобальные координаты направляющего вектора
+ * @param calculate определяет необходимо ли вычислять актуальные значения объекта при вызове метода
+ */
+ private function calculateRayOriginAndVector(canvasX:Number, canvasY:Number, rayOrigin:Point3D, rayVector:Point3D, calculate:Boolean = false):void {
+ var x:Number;
+ var y:Number;
+ var z:Number;
+ // Вычисление направляющего вектора и точки проецирующей прямой в глобальном пространстве
+ // Вычисление матрицы трансформации камеры
+ var m:Matrix3D;
+ if (calculate) {
+ m = Object3D.matrix2;
+ _camera.getTransformation(m);
+ } else {
+ m = _camera._transformation;
+ }
+ if (_camera._orthographic) {
+ // Координаты точки на луче
+ x = canvasX/_camera.zoom;
+ y = canvasY/_camera.zoom;
+ rayOrigin.x = m.a*x + m.b*y + m.d;
+ rayOrigin.y = m.e*x + m.f*y + m.h;
+ rayOrigin.z = m.i*x + m.j*y + m.l;
+ // Координаты локального направляющего вектора
+ x = y = 0;
+ z = 1;
+ } else {
+ // Координаты точки на луче
+ rayOrigin.x = m.d;
+ rayOrigin.y = m.h;
+ rayOrigin.z = m.l;
+ // Координаты локального направляющего вектора
+ x = canvasX;
+ y = canvasY;
+ if (calculate) {
+ z = _camera.focalLength;
+ } else {
+ z = _camera._focalLength;
+ }
+ }
+ // Направляющий вектор в глобальном пространстве
+ lineVector.x = x*m.a + y*m.b + z*m.c;
+ lineVector.y = x*m.e + y*m.f + z*m.g;
+ lineVector.z = x*m.i + y*m.j + z*m.k;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/Engine3DError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/Engine3DError.as
new file mode 100644
index 0000000..7c3edd0
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/Engine3DError.as
@@ -0,0 +1,25 @@
+package alternativa.engine3d.errors {
+
+ /**
+ * Базовый класс для ошибок 3d-engine.
+ */
+ public class Engine3DError extends Error {
+
+ /**
+ * Источник ошибки - объект в котором произошла ошибка.
+ */
+ public var source:Object;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param message описание ошибки
+ * @param source источник ошибки
+ */
+ public function Engine3DError(message:String = "", source:Object = null) {
+ super(message);
+ this.source = source;
+ this.name = "Engine3DError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceExistsError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceExistsError.as
new file mode 100644
index 0000000..ecc7f59
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceExistsError.as
@@ -0,0 +1,35 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+ import alternativa.engine3d.core.Surface;
+
+ /**
+ * Ошибка, возникающая при попытке добавить в какой-либо объект грань, уже содержащуюся в данном объекте.
+ */
+ public class FaceExistsError extends ObjectExistsError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param face экземпляр или идентификатор грани, которая уже содержится в объекте
+ * @param source источник ошибки
+ */
+ public function FaceExistsError(face:Object = null, source:Object = null) {
+ var message:String;
+ if (source is Mesh) {
+ message = "Mesh ";
+ } else if (source is Surface) {
+ message = "Surface ";
+ }
+ if (face is Face) {
+ message += "%1. Face %2 already exists.";
+ } else {
+ message += "%1. Face with ID '%2' already exists.";
+ }
+ super(TextUtils.insertVars(message, source, face), face, source);
+ this.name = "FaceExistsError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as
new file mode 100644
index 0000000..3341f63
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as
@@ -0,0 +1,29 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая недостаточное количество вершин для создания грани.
+ * Для создания грани должно быть указано не менее трех вершин.
+ */
+ public class FaceNeedMoreVerticesError extends Engine3DError {
+
+ /**
+ * Количество переданных для создания грани вершин
+ */
+ public var count:uint;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param mesh объект, в котором произошла ошибка
+ * @param count количество вершин, переданное для создания грани
+ */
+ public function FaceNeedMoreVerticesError(mesh:Mesh = null, count:uint = 0) {
+ super(TextUtils.insertVars("Mesh %1. %2 vertices not enough for face creation.", mesh, count), mesh);
+ this.count = count;
+ this.name = "FaceNeedMoreVerticesError";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceNotFoundError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceNotFoundError.as
new file mode 100644
index 0000000..912d6a4
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/FaceNotFoundError.as
@@ -0,0 +1,34 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, возникающая, если грань не найдена в объекте.
+ */
+ public class FaceNotFoundError extends ObjectNotFoundError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param face экземпляр или идентификатор грани
+ * @param source объект, в котором произошла ошибка
+ */
+ public function FaceNotFoundError(face:Object = null, source:Object = null) {
+ var message:String;
+ if (source is Mesh) {
+ message = "Mesh ";
+ } else {
+ message = "Surface ";
+ }
+ if (face is Face) {
+ message += "%1. Face %2 not found.";
+ } else {
+ message += "%1. Face with ID '%2' not found.";
+ }
+ super(TextUtils.insertVars(message, source, face), face, source);
+ this.name = "FaceNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/InvalidIDError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/InvalidIDError.as
new file mode 100644
index 0000000..fbf4666
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/InvalidIDError.as
@@ -0,0 +1,34 @@
+package alternativa.engine3d.errors {
+ import alternativa.utils.TextUtils;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Surface;
+
+
+ /**
+ * Ошибка, обозначающая, что идентификатор зарезервирован и не может быть использован.
+ */
+ public class InvalidIDError extends Engine3DError {
+ /**
+ * Зарезервированный идентификатор
+ */
+ public var id:Object;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param id идентификатор
+ * @param source объект, в котором произошла ошибка
+ */
+ public function InvalidIDError(id:Object = null, source:Object = null) {
+ var message:String;
+ if (source is Mesh) {
+ message = "Mesh %2. ";
+ } else if (source is Surface) {
+ message = "Surface %2. ";
+ }
+ super(TextUtils.insertVars(message + "ID %1 is reserved and cannot be used", [id, source]), source);
+ this.id = id;
+ this.name = "InvalidIDError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/Object3DHierarchyError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/Object3DHierarchyError.as
new file mode 100644
index 0000000..8d61bd0
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/Object3DHierarchyError.as
@@ -0,0 +1,29 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, связанная с нарушением иерархии объектов сцены.
+ */
+ public class Object3DHierarchyError extends Engine3DError
+ {
+
+ /**
+ * Объект сцены, нарушающий иерархию
+ */
+ public var object:Object3D;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param object объект, нарушающий иерархию
+ * @param source источник ошибки
+ */
+ public function Object3DHierarchyError(object:Object3D = null, source:Object3D = null) {
+ super(TextUtils.insertVars("Object3D %1. Object %2 cannot be added", source, object), source);
+ this.object = object;
+ this.name = "Object3DHierarchyError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/Object3DNotFoundError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/Object3DNotFoundError.as
new file mode 100644
index 0000000..d1ddd1e
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/Object3DNotFoundError.as
@@ -0,0 +1,22 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, возникающая, когда объект сцены не был найден в списке связанных с необходимым объектом сцены.
+ */
+ public class Object3DNotFoundError extends ObjectNotFoundError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param object ненайденный объект сцены
+ * @param source объект сцены, в котором произошла ошибка
+ */
+ public function Object3DNotFoundError(object:Object3D = null, source:Object3D = null) {
+ super(TextUtils.insertVars("Object3D %1. Object %2 not in child list", source, object), object, source);
+ this.name = "Object3DNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/ObjectExistsError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/ObjectExistsError.as
new file mode 100644
index 0000000..7743a53
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/ObjectExistsError.as
@@ -0,0 +1,26 @@
+package alternativa.engine3d.errors {
+
+ /**
+ * Ошибка, обозначающая, что объект уже присутствует в контейнере.
+ */
+ public class ObjectExistsError extends Engine3DError {
+
+ /**
+ * Экземпляр или идентификатор объекта, который уже присутствует в контейнере
+ */
+ public var object:Object;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param message описание ошибки
+ * @param object объект, который уже присутствует в контейнере
+ * @param source объект, вызвавший ошибку
+ */
+ public function ObjectExistsError(message:String = "", object:Object = null, source:Object = null) {
+ super(message, source);
+ this.object = object;
+ this.name = "ObjectExistsError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/ObjectNotFoundError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/ObjectNotFoundError.as
new file mode 100644
index 0000000..87b3d14
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/ObjectNotFoundError.as
@@ -0,0 +1,26 @@
+package alternativa.engine3d.errors {
+
+ /**
+ * Необходимый объект не был найден в контейнере.
+ */
+ public class ObjectNotFoundError extends Engine3DError {
+
+ /**
+ * Объект, который отсутствует в контейнере.
+ */
+ public var object:Object;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param message описание ошибки
+ * @param object отсутствующий объект
+ * @param source объект, вызвавший ошибку
+ */
+ public function ObjectNotFoundError(message:String = "", object:Object = null, source:Object = null) {
+ super(message, source);
+ this.object = object;
+ this.name = "ObjectNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/SectorInOtherSceneError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/SectorInOtherSceneError.as
new file mode 100644
index 0000000..4d3dced
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/SectorInOtherSceneError.as
@@ -0,0 +1,24 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Scene3D;
+ import alternativa.engine3d.core.Sector;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, возникающая при попытке добавить на сцену сектор, расположенный в другой сцене.
+ */
+ public class SectorInOtherSceneError extends Engine3DError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param sector экземпляр сектора, расположенный в другой сцене
+ * @param source сцена, из которой было вызвано исключение.
+ */
+ public function SectorInOtherSceneError(sector:Sector = null, source:Scene3D = null) {
+ super(TextUtils.insertVars("%1. Sector %2 is aready situated in the other scene", source, sector), source);
+ this.name = "SectorInOtherSceneError";
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/SplitterInOtherSceneError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/SplitterInOtherSceneError.as
new file mode 100644
index 0000000..a0f3789
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/SplitterInOtherSceneError.as
@@ -0,0 +1,24 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Scene3D;
+ import alternativa.engine3d.core.Splitter;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, возникающая при попытке добавить на сцену сплиттер, расположенный в другой сцене.
+ */
+ public class SplitterInOtherSceneError extends Engine3DError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param splitter экземпляр сплиттера, расположенный в другой сцене
+ * @param source сцена, из которой было вызвано исключение.
+ */
+ public function SplitterInOtherSceneError(splitter:Splitter = null, source:Scene3D = null) {
+ super(TextUtils.insertVars("%1. Splitter %2 is aready situated in the other scene", source, splitter), source);
+ this.name = "SplitterInOtherSceneError";
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/SplitterNeedMoreVerticesError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/SplitterNeedMoreVerticesError.as
new file mode 100644
index 0000000..f262da9
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/SplitterNeedMoreVerticesError.as
@@ -0,0 +1,29 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая недостаточное количество точек для создания сплиттера.
+ * Для создания сплиттера должно быть указано не менее трех точек.
+ */
+ public class SplitterNeedMoreVerticesError extends Engine3DError {
+
+ /**
+ * Количество переданных для создания сплиттера точек
+ */
+ public var count:uint;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param count количество точек, переданное для создания сплиттера
+ */
+ public function SplitterNeedMoreVerticesError(count:uint = 0) {
+ super(TextUtils.insertVars("%1 points not enough for splitter creation.", count), null);
+ this.count = count;
+ this.name = "SplitterNeedMoreVerticesError";
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/SurfaceExistsError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/SurfaceExistsError.as
new file mode 100644
index 0000000..e13bee7
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/SurfaceExistsError.as
@@ -0,0 +1,22 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая, что поверхность уже присутствует в контейнере.
+ */
+ public class SurfaceExistsError extends ObjectExistsError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param surface поверхность, которая уже присутствует в контейнере
+ * @param mesh объект, вызвавший ошибку
+ */
+ public function SurfaceExistsError(surface:Object = null, mesh:Mesh = null) {
+ super(TextUtils.insertVars("Mesh %1. Surface with ID '%2' already exists.", mesh, surface), surface, mesh);
+ this.name = "SurfaceExistsError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/SurfaceNotFoundError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/SurfaceNotFoundError.as
new file mode 100644
index 0000000..05457bf
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/SurfaceNotFoundError.as
@@ -0,0 +1,31 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая, что поверхность не найдена в контейнере.
+ */
+ public class SurfaceNotFoundError extends ObjectNotFoundError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param surface поверхность, которая отсутствует в объекте
+ * @param mesh объект, который вызвал ошибку
+ */
+ public function SurfaceNotFoundError(surface:Object = null, mesh:Mesh = null) {
+ if (mesh == null) {
+
+ }
+ if (surface is Surface) {
+ message = "Mesh %1. Surface %2 not found.";
+ } else {
+ message = "Mesh %1. Surface with ID '%2' not found.";
+ }
+ super(TextUtils.insertVars(message, mesh, surface), surface, mesh);
+ this.name = "SurfaceNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/VertexExistsError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/VertexExistsError.as
new file mode 100644
index 0000000..9f0fff5
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/VertexExistsError.as
@@ -0,0 +1,22 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая, что вершина уже содержится в объекте.
+ */
+ public class VertexExistsError extends ObjectExistsError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param vertex вершина, которая уже есть в объекте
+ * @param mesh объект, вызвавший ошибку
+ */
+ public function VertexExistsError(vertex:Object = null, mesh:Mesh = null) {
+ super(TextUtils.insertVars("Mesh %1. Vertex with ID '%2' already exists.", mesh, vertex), vertex, mesh);
+ this.name = "VertexExistsError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/errors/VertexNotFoundError.as b/Alternativa3D5/5.6/alternativa/engine3d/errors/VertexNotFoundError.as
new file mode 100644
index 0000000..edf80aa
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/errors/VertexNotFoundError.as
@@ -0,0 +1,28 @@
+package alternativa.engine3d.errors {
+
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.utils.TextUtils;
+
+ /**
+ * Ошибка, обозначающая, что вершина не найдена в объекте.
+ */
+ public class VertexNotFoundError extends ObjectNotFoundError {
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param vertex вершина, которая не найдена в объекте
+ * @param mesh объект, вызвавший ошибку
+ */
+ public function VertexNotFoundError(vertex:Object = null, mesh:Mesh = null) {
+ if (vertex is Vertex) {
+ message = "Mesh %1. Vertex %2 not found.";
+ } else {
+ message = "Mesh %1. Vertex with ID '%2' not found.";
+ }
+ super(TextUtils.insertVars(message, mesh, vertex), vertex, mesh);
+ this.name = "VertexNotFoundError";
+ }
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/events/MouseEvent3D.as b/Alternativa3D5/5.6/alternativa/engine3d/events/MouseEvent3D.as
new file mode 100644
index 0000000..24c6b84
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/events/MouseEvent3D.as
@@ -0,0 +1,174 @@
+package alternativa.engine3d.events {
+
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.display.View;
+
+ import flash.events.Event;
+
+ /**
+ * Событие, возникающее при взаимодействии мыши с объектами сцены.
+ */
+ public class MouseEvent3D extends Event {
+ /**
+ * Значение свойства type для объекта события click.
+ * @eventType click
+ */
+ public static const CLICK:String = "click";
+ /**
+ * Значение свойства type для объекта события mouseDown.
+ * @eventType mouseDown
+ */
+ public static const MOUSE_DOWN:String = "mouseDown";
+ /**
+ * Значение свойства type для объекта события mouseUp.
+ * @eventType mouseUp
+ */
+ public static const MOUSE_UP:String = "mouseUp";
+ /**
+ * Значение свойства type для объекта события mouseOver.
+ * @eventType mouseOver
+ */
+ public static const MOUSE_OVER:String = "mouseOver";
+ /**
+ * Значение свойства type для объекта события mouseOut.
+ * @eventType mouseOut
+ */
+ public static const MOUSE_OUT:String = "mouseOut";
+ /**
+ * Значение свойства type для объекта события mouseMove.
+ * @eventType mouseMove
+ */
+ public static const MOUSE_MOVE:String = "mouseMove";
+ /**
+ * Значение свойства type для объекта события mouseWheel.
+ * @eventType mouseWheel
+ */
+ public static const MOUSE_WHEEL:String = "mouseWheel";
+
+ /**
+ * Объект сцены, с которым связано событие.
+ */
+ public var object:Object3D;
+ /**
+ * Поверхность объекта сцены, с которой связано событие.
+ */
+ public var surface:Surface;
+ /**
+ * Грань объекта сцены, с которой связано событие.
+ */
+ public var face:Face;
+ /**
+ * Область вывода, в которой произошло событие.
+ */
+ public var view:View;
+
+ /**
+ * X-координата мышиного курсора в сцене.
+ */
+ public var globalX:Number;
+ /**
+ * Y-координата мышиного курсора в сцене.
+ */
+ public var globalY:Number;
+ /**
+ * Z-координата мышиного курсора в сцене.
+ */
+ public var globalZ:Number;
+
+ /**
+ * X-координата мышиного курсора в системе координат объекта.
+ */
+ public var localX:Number;
+ /**
+ * Y-координата мышиного курсора в системе координат объекта.
+ */
+ public var localY:Number;
+ /**
+ * Z-координата мышиного курсора в системе координат объекта.
+ */
+ public var localZ:Number;
+
+ /**
+ * Текстурная координата U в точке нахождения мышиного курсора. При отсутствии текстурных координат у грани, поле содержит значение NaN.
+ */
+ public var u:Number;
+ /**
+ * Текстурная координата V в точке нахождения мышиного курсора. При отсутствии текстурных координат у грани, поле содержит значение NaN.
+ */
+ public var v:Number;
+ /**
+ * Индикатор нажатой (true) или отпущенной (false) клавиши Alt.
+ */
+ public var altKey:Boolean;
+ /**
+ * Индикатор нажатой (true) или отпущенной (false) клавиши Control.
+ */
+ public var ctrlKey:Boolean;
+ /**
+ * Индикатор нажатой (true) или отпущенной (false) клавиши Shift.
+ */
+ public var shiftKey:Boolean;
+ /**
+ * Количество линий прокрутки при вращении колеса мыши.
+ */
+ public var delta:int;
+
+ /**
+ * Создаёт новый экземпляр события.
+ *
+ * @param type тип события
+ * @param view область вывода, в которой произошло событие
+ * @param object объект сцены, с которым связано событие
+ * @param surface поверхность, с которой связано событие
+ * @param face грань, с которой связано событие
+ * @param globalX X-координата мышиного курсора в сцене
+ * @param globalY Y-координата мышиного курсора в сцене
+ * @param globalZ Z-координата мышиного курсора в сцене
+ * @param localX X-координата мышиного курсора в системе координат объекта
+ * @param localY Y-координата мышиного курсора в системе координат объекта
+ * @param localZ Z-координата мышиного курсора в системе координат объекта
+ * @param u текстурная координата U в точке нахождения мышиного курсора
+ * @param v текстурная координата V в точке нахождения мышиного курсора
+ */
+ public function MouseEvent3D(type:String, view:View, object:Object3D, surface:Surface, face:Face, globalX:Number = NaN, globalY:Number = NaN, globalZ:Number = NaN, localX:Number = NaN, localY:Number = NaN, localZ:Number = NaN, u:Number = NaN, v:Number = NaN, altKey:Boolean = false, ctrlKey:Boolean = false, shiftKey:Boolean = false, delta:int = 0) {
+ super(type);
+ this.view = view;
+ this.object = object;
+ this.surface = surface;
+ this.face = face;
+ this.globalX = globalX;
+ this.globalY = globalY;
+ this.globalZ = globalZ;
+ this.localX = localX;
+ this.localY = localY;
+ this.localZ = localZ;
+ this.u = u;
+ this.v = v;
+ this.altKey = altKey;
+ this.ctrlKey = ctrlKey;
+ this.shiftKey = shiftKey;
+ this.delta = delta;
+ }
+
+ /**
+ * Получение строкового представления объекта.
+ *
+ * @return строковое представление объекта
+ */
+ override public function toString():String {
+ return formatToString("MouseEvent3D", "object", "surface", "face", "globalX", "globalY", "globalZ", "localX", "localY", "localZ", "u", "v", "delta", "altKey", "ctrlKey", "shiftKey");
+ }
+
+ /**
+ * Возвращает клон объекта.
+ *
+ * @return клон события
+ */
+ override public function clone():Event {
+ return new MouseEvent3D(type, view, object, surface, face, globalX, globalY, globalZ, localX, localY, localZ, u, v, altKey, ctrlKey, shiftKey, delta);
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3D.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3D.as
new file mode 100644
index 0000000..ced1012
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3D.as
@@ -0,0 +1,269 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.loaders.events.LoaderEvent;
+ import alternativa.engine3d.loaders.events.LoaderProgressEvent;
+
+ import flash.events.ErrorEvent;
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IOErrorEvent;
+ import flash.events.ProgressEvent;
+ import flash.events.SecurityErrorEvent;
+ import flash.net.URLLoader;
+ import flash.net.URLLoaderDataFormat;
+ import flash.net.URLRequest;
+ import flash.system.LoaderContext;
+ import flash.utils.ByteArray;
+
+ /**
+ * Рассылается в начале очередного этапа загрузки сцены.
+ *
+ * @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_START
+ */
+ [Event (name="loadingStart", type="alternativa.engine3d.loaders.events.LoaderEvent")]
+ /**
+ * Рассылается в процессе получения данных во время загрузки.
+ *
+ * @eventType alternativa.engine3d.loaders.events.LoaderProgressEvent.LOADING_PROGRESS
+ */
+ [Event (name="loadingProgress", type="alternativa.engine3d.loaders.events.LoaderProgressEvent")]
+ /**
+ * Рассылается после окончания очередного этапа загрузки сцены.
+ *
+ * @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_COMPLETE
+ */
+ [Event (name="loadingComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")]
+ /**
+ * Рассылается после окончания загрузки сцены.
+ *
+ * @eventType flash.events.Event.COMPLETE
+ */
+ [Event (name="complete", type="flash.events.Event")]
+ /**
+ * Рассылается, если в процессе загрузки возникает ошибка, которая прерывает загрузку.
+ *
+ * @eventType flash.events.IOErrorEvent.IO_ERROR
+ */
+ [Event (name="ioError", type="flash.events.IOErrorEvent")]
+ /**
+ * Рассылается, если вызов Loader3D.load() нарушает текущие правила безопасности.
+ *
+ * @eventType flash.events.SecurityErrorEvent.SECURITY_ERROR
+ */
+ [Event (name="securityError", type="flash.events.SecurityErrorEvent")]
+
+ /**
+ * Базовый класс загрузчиков. Реализует загрузку основного файла сцены и включает ряд методов, расширяя которые,
+ * наследники могут реализовывать специфическую функциональность для разбора сцены и загрузки дополнительных данных.
+ */
+ public class Loader3D extends EventDispatcher {
+ /**
+ * Текущее состояние загрузчика. Показывает наличие активных процессов загрузки. В качестве значений используются объявленные в классе константы.
+ */
+ protected var loaderState:int = Loader3DState.IDLE;
+ /**
+ * Базовый URL основного файла сцены, не включающий имя файла. Если путь не пустой, то он заканчивается символом "/".
+ */
+ protected var baseURL:String;
+ /**
+ * LoaderContext для загрузки файлов текстур.
+ */
+ protected var loaderContext:LoaderContext;
+ /**
+ * Контейнер, который должен содержать объекты, загруженные из файла сцены.
+ */
+ protected var _content:Object3D;
+ /**
+ * Загрузчик основного файла сцены.
+ */
+ private var mainLoader:URLLoader;
+
+ /**
+ * Создаёт новый экземпляр класса.
+ */
+ public function Loader3D() {
+ super(this);
+ }
+
+ /**
+ * Контейнер, содержащий все загруженные из сцены объекты.
+ */
+ public final function get content():Object3D {
+ return _content;
+ }
+
+ /**
+ * Загружает сцену из файла с заданным URL. Метод отменяет текущую загрузку.
+ * Если файл успешно загружен, вызывается метод parse(), который выполняет разбор полученных данных.
+ *
+ * @param url URL файла со сценой
+ * @param context LoaderContext для загрузки файлов текстур
+ *
+ * @see #parse()
+ */
+ public final function load(url:String, loaderContext:LoaderContext = null):void {
+ this.baseURL = url.substring(0, url.lastIndexOf("/") + 1);
+ this.loaderContext = loaderContext;
+
+ if (mainLoader == null) {
+ // Первоначальное создание загрузчика файла сцены
+ mainLoader = new URLLoader();
+ mainLoader.dataFormat = URLLoaderDataFormat.BINARY;
+ mainLoader.addEventListener(Event.COMPLETE, onMainLoadingComplete);
+ mainLoader.addEventListener(ProgressEvent.PROGRESS, onMainLoadingProgress);
+ mainLoader.addEventListener(IOErrorEvent.IO_ERROR, onMainLoadingError);
+ mainLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onMainLoadingError);
+ } else {
+ // Прекращение активной загрузки
+ close();
+ }
+ _content = null;
+ // Загрузка основного файла
+ setState(Loader3DState.LOADING_MAIN);
+ mainLoader.load(new URLRequest(url));
+ if (hasEventListener(LoaderEvent.LOADING_START)) {
+ dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_START, LoadingStage.MAIN_FILE));
+ }
+ }
+
+ /**
+ * Загружает сцену из массива бинарных данных.
+ * Для чтения и разбора данных из массива вызывается метод parse().
+ *
+ * @param data данные сцены
+ * @param baseUrl базовый URL для файлов текстур
+ * @param loaderContext LoaderContext для загрузки файлов текстур
+ *
+ * @see #parse()
+ */
+ public final function loadBytes(data:ByteArray, baseUrl:String = null, loaderContext:LoaderContext = null):void {
+ if (baseUrl == null) {
+ baseUrl = "";
+ } else if (baseUrl.length > 0 && baseUrl.charAt(baseUrl.length - 1) != "/") {
+ baseUrl += "/";
+ }
+ this.baseURL = baseUrl;
+ this.loaderContext = loaderContext;
+ close();
+ _content = null;
+ parse(data);
+ }
+
+ /**
+ * Прекращает текущую загрузку. Базовая реализация останавливает процесс загрузки основного файла сцены. Наследники должны расширять метод closeInternal для
+ * прекращения специфических процессов загрузки.
+ *
+ * @see #closeInternal()
+ */
+ public final function close():void {
+ if (loaderState == Loader3DState.LOADING_MAIN) {
+ mainLoader.close();
+ }
+ closeInternal();
+ clean();
+ setState(Loader3DState.IDLE);
+ }
+
+ /**
+ * Очищает внутренние ссылки на загруженные данные, чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает
+ * во время процесса загрузки данных. Реализация метода удаляет сылку на контейнер с загруженными объектами и вызывает метод unloadInternal().
+ *
+ * @see #unloadInternal()
+ */
+ public final function unload():void {
+ if (loaderState != Loader3DState.IDLE) {
+ return;
+ }
+ _content = null;
+ unloadInternal();
+ }
+
+ /**
+ * Метод вызывается из close() и должен прекращать процессы загрузки, инициированные наследниками. Базовая реализация не делает ничего.
+ *
+ * @see #close()
+ */
+ protected function closeInternal():void {
+ }
+
+ /**
+ * Вызывается из метода unload(). Наследники должны расширять метод для удаления внутренних ссылок на объекты. Базовая реализация не делает ничего.
+ *
+ * @see #unload()
+ */
+ protected function unloadInternal():void {
+ }
+
+ /**
+ * Устанавливает внутреннее состояние загрузчика.
+ *
+ * @param state новое состояние
+ */
+ protected function setState(state:int):void {
+ loaderState = state;
+ }
+
+ /**
+ * Наследники должны расширять метод, реализуя функционал по разбору данных. Базовая реализация не делает ничего.
+ *
+ * @param data данные трёхмерной сцены
+ */
+ protected function parse(data:ByteArray):void {
+ }
+
+ /**
+ * Обрабатывает ошибку, возникшую при загрузке основного файла. Базовая реализация устанавливает состояние загрузчика в STATE_IDLE и рассылает
+ * полученное событие.
+ *
+ * @param e событие, описывающего ошибку
+ */
+ protected function onMainLoadingError(e:ErrorEvent):void {
+ setState(Loader3DState.IDLE);
+ dispatchEvent(e);
+ }
+
+ /**
+ * Метод должен вызываться после того как все данные сцены успешно загружены. Метод переводит загрузчик в состояние ожидания, вызывает метод очистки
+ * clean() и рассылает событие Event.COMPLETE.
+ *
+ * @see #clean()
+ */
+ protected final function complete():void {
+ setState(Loader3DState.IDLE);
+ clean();
+ if (hasEventListener(Event.COMPLETE)) {
+ dispatchEvent(new Event(Event.COMPLETE));
+ }
+ }
+
+ /**
+ * Метод должен очищать внутренние вспомогательные переменные. Вызывается из метода complete. Базовая реализация не делает ничего, наследники должны расширять
+ * метод при необходимости.
+ *
+ * @see #complete()
+ */
+ protected function clean():void {
+ }
+
+ /**
+ * Обработчик успешной загрузки основного файла. Запускает функцию parse() для разбора данных.
+ */
+ private function onMainLoadingComplete(e:Event):void {
+ setState(Loader3DState.IDLE);
+ if (hasEventListener(LoaderEvent.LOADING_COMPLETE)) {
+ dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_COMPLETE, LoadingStage.MAIN_FILE));
+ }
+ parse(mainLoader.data);
+ }
+
+ /**
+ * Рассылает событие прогресса загрузки основного файла.
+ */
+ private function onMainLoadingProgress(e:ProgressEvent):void {
+ if (hasEventListener(LoaderProgressEvent.LOADING_PROGRESS)) {
+ dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADING_PROGRESS, LoadingStage.MAIN_FILE, 1, 0, e.bytesLoaded, e.bytesTotal));
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3DS.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3DS.as
new file mode 100644
index 0000000..71eb0e1
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3DS.as
@@ -0,0 +1,262 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.alternativa3d;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.loaders.events.LoaderEvent;
+ import alternativa.engine3d.loaders.events.LoaderProgressEvent;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.types.Texture;
+
+ import flash.events.Event;
+ import flash.events.IOErrorEvent;
+ import flash.utils.ByteArray;
+
+ use namespace alternativa3d;
+
+ /**
+ * Загрузчик сцен в формате 3DS.
+ *
+ * Класс предоставляет возможность загрузить 3DS-данные из сети или из бинарного массива. Полученные данные разбираются с помощью класса
+ * Parser3DS, после чего автоматически загружаются текстуры, используемые в сцене, которые затем назначаются текстурным материалам.
+ * Если при загрузке текстуры происходит ошибка (например, файл отсутствует), то текстура заменяется текстурой-заглушкой и рассылается
+ * сообщение IOErrorEvent.
+ *
+ *
+ * Перед загрузкой данных можно установить ряд свойств, влияющих на создаваемые текстурные материалы.
+ *
+ *
+ * @see alternativa.engine3d.loaders.Parser3DS
+ */
+ public class Loader3DS extends Loader3D {
+
+ /**
+ * Если указано значение false, то материалы загружаться не будут.
+ *
+ * @default true
+ */
+ public var loadMaterials:Boolean = true;
+
+ private var bitmapLoader:TextureMapsBatchLoader;
+ private var parser:Parser3DS;
+
+ /**
+ * Создаёт новый экземпляр класса.
+ */
+ public function Loader3DS() {
+ super();
+ parser = new Parser3DS();
+ }
+
+ /**
+ * Значение свойства repeat для текстурных материалов.
+ *
+ * @default true
+ * @see alternativa.engine3d.materials.TextureMaterial
+ */
+ public function get repeat():Boolean {
+ return parser.repeat;
+ }
+
+ /**
+ * @private
+ */
+ public function set repeat(value:Boolean):void {
+ parser.repeat = value;
+ }
+
+ /**
+ * Значение свойства smooth для текстурных материалов.
+ *
+ * @default false
+ * @see alternativa.engine3d.materials.TextureMaterial#smooth
+ */
+ public function get smooth():Boolean {
+ return parser.smooth;
+ }
+
+ /**
+ * @private
+ */
+ public function set smooth(value:Boolean):void {
+ parser.smooth = value;
+ }
+
+ /**
+ * Значение свойства blendMode для текстурных материалов.
+ *
+ * @default BlendMode.NORMAL
+ * @see alternativa.engine3d.materials.Material#blendMode
+ */
+ public function get blendMode():String {
+ return parser.blendMode;
+ }
+
+ /**
+ * @private
+ */
+ public function set blendMode(value:String):void {
+ parser.blendMode = value;
+ }
+
+ /**
+ * Значение свойства precision для текстурных материалов.
+ *
+ * @default TextureMaterialPrecision.MEDIUM
+ * @see alternativa.engine3d.materials.TextureMaterial#precision
+ * @see alternativa.engine3d.materials.TextureMaterialPrecision
+ */
+ public function get precision():Number {
+ return parser.precision;
+ }
+
+ /**
+ * @private
+ */
+ public function set precision(value:Number):void {
+ parser.precision = value;
+ }
+
+ /**
+ * Коэффициент пересчёта единиц измерения сцены. Размеры создаваемых объектов будут умножены на заданное значение.
+ * @default 1
+ */
+ public function get scale():Number {
+ return parser.scale;
+ }
+
+ /**
+ * @private
+ */
+ public function set scale(value:Number):void {
+ parser.scale = value;
+ }
+
+ /**
+ * Уровень мобильности для загруженных объектов.
+ * @default 0
+ * @see alternativa.engine3d.core.Object3D#mobility
+ */
+ public function get mobility():int {
+ return parser.mobility;
+ }
+
+ /**
+ * @private
+ */
+ public function set mobility(value:int):void {
+ parser.mobility = value;
+ }
+
+ /**
+ * Выполняет действия для прекращения текущей загрузки.
+ */
+ override protected function closeInternal():void {
+ super.closeInternal();
+ if (loaderState == Loader3DState.LOADING_TEXTURE) {
+ bitmapLoader.close();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function unloadInternal():void {
+ if (bitmapLoader != null) {
+ bitmapLoader.unload();
+ }
+ }
+
+ /**
+ * Разбирает загруженные данные.
+ *
+ * @param data данные в формате 3DS
+ */
+ override protected function parse(data:ByteArray):void {
+ parser.parse(data);
+ _content = parser.content;
+
+ if (loadMaterials && parser.textureMaterials != null) {
+ loadTextures();
+ } else {
+ complete();
+ }
+ }
+
+ /**
+ * Запускает процесс загрузки текстур.
+ */
+ private function loadTextures():void {
+ if (bitmapLoader == null) {
+ bitmapLoader = new TextureMapsBatchLoader();
+ bitmapLoader.addEventListener(LoaderEvent.LOADING_START, onTextureLoadingStart);
+ bitmapLoader.addEventListener(LoaderEvent.LOADING_COMPLETE, onTextureLoadingComplete);
+ bitmapLoader.addEventListener(LoaderProgressEvent.LOADING_PROGRESS, onTextureLoadingProgress);
+ bitmapLoader.addEventListener(Event.COMPLETE, onTextureMaterialsLoadingComplete);
+ bitmapLoader.addEventListener(IOErrorEvent.IO_ERROR, onTextureLoadingError);
+ }
+ setState(Loader3DState.LOADING_TEXTURE);
+ bitmapLoader.load(baseURL, parser.textureMaterials, loaderContext);
+ }
+
+ /**
+ * Обрабатывает сообщение об ошибке загрузки текстуры.
+ */
+ private function onTextureLoadingError(e:IOErrorEvent):void {
+ dispatchEvent(e);
+ }
+
+ /**
+ * Обрабатывает сообщение о начале загрузки очередной текстуры.
+ */
+ private function onTextureLoadingStart(e:LoaderEvent):void {
+ if (hasEventListener(e.type)) {
+ dispatchEvent(e);
+ }
+ }
+
+ /**
+ * Обрабатывает сообщение об окончании загрузки очередного файла текстуры.
+ */
+ private function onTextureLoadingComplete(e:LoaderEvent):void {
+ if (hasEventListener(e.type)) {
+ dispatchEvent(e);
+ }
+ }
+
+ /**
+ * Рассылает событие прогресса загрузки очередного файла текстуры.
+ */
+ private function onTextureLoadingProgress(e:LoaderProgressEvent):void {
+ if (hasEventListener(e.type)) {
+ dispatchEvent(e);
+ }
+ }
+
+ /**
+ * Устанавливает текстуры для материалов после завершения загрузки всех текстур.
+ */
+ private function onTextureMaterialsLoadingComplete(e:Event):void {
+ parser.content.forEach(setTextures);
+ complete();
+ }
+
+ /**
+ * Устанавливает текстуры для текстурных материалов объекта.
+ */
+ private function setTextures(object:Object3D):void {
+ var mesh:Mesh = object as Mesh;
+ if (mesh != null) {
+ for (var key:String in mesh._surfaces) {
+ var textureMapsInfo:TextureMapsInfo = parser.textureMaterials[key];
+ if (textureMapsInfo != null) {
+ var texture:Texture = new Texture(bitmapLoader.textures[key], textureMapsInfo.diffuseMapFileName);
+ var s:Surface = mesh._surfaces[key];
+ TextureMaterial(s.material).texture = texture;
+ }
+ }
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3DState.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3DState.as
new file mode 100644
index 0000000..4650bd6
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/Loader3DState.as
@@ -0,0 +1,15 @@
+package alternativa.engine3d.loaders {
+
+ /**
+ * @private
+ * Возможные состояния загрузчиков.
+ */
+ public final class Loader3DState {
+
+ public static const IDLE:int = 0;
+ public static const LOADING_MAIN:int = 1;
+ public static const LOADING_TEXTURE:int = 2;
+ public static const LOADING_LIBRARY:int = 3;
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoaderMTL.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoaderMTL.as
new file mode 100644
index 0000000..2958619
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoaderMTL.as
@@ -0,0 +1,257 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.*;
+ import alternativa.types.Map;
+ import alternativa.utils.ColorUtils;
+
+ import flash.display.BitmapData;
+ import flash.events.ErrorEvent;
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IOErrorEvent;
+ import flash.events.ProgressEvent;
+ import flash.events.SecurityErrorEvent;
+ import flash.geom.Point;
+ import flash.net.URLLoader;
+ import flash.net.URLRequest;
+ import flash.system.LoaderContext;
+
+ use namespace alternativa3d;
+
+ /**
+ * Событие рассылается после начала загрузки.
+ *
+ * @eventType flash.events.Event.OPEN
+ */
+ [Event (name="open", type="flash.events.Event")]
+ /**
+ * Событие рассылается в процессе получения данных во время загрузки.
+ *
+ * @eventType flash.events.ProgressEvent.PROGRESS
+ */
+ [Event (name="progress", type="flash.events.ProgressEvent")]
+ /**
+ * Событие рассылается после завершении загрузки.
+ *
+ * @eventType flash.events.Event.COMPLETE
+ */
+ [Event (name="complete", type="flash.events.Event")]
+ /**
+ * Событие рассылается при ошибке загрузки.
+ *
+ * @eventType flash.events.IOErrorEvent.IO_ERROR
+ */
+ [Event (name="ioError", type="flash.events.IOErrorEvent")]
+ /**
+ * Событие рассылается при нарушении безопасности.
+ *
+ * @eventType flash.events.SecurityErrorEvent.SECURITY_ERROR
+ */
+ [Event (name="securityError", type="flash.events.SecurityErrorEvent")]
+ /**
+ * @private
+ * Загрузчик библиотеки материалов из файлов в формате MTL material format (Lightwave, OBJ).
+ *
+ * На данный момент обеспечивается загрузка цвета, прозрачности и диффузной текстуры материала.
+ *
+ */
+ public class LoaderMTL extends EventDispatcher {
+
+ private static const COMMENT_CHAR:String = "#";
+ private static const CMD_NEW_MATERIAL:String = "newmtl";
+ private static const CMD_DIFFUSE_REFLECTIVITY:String = "Kd";
+ private static const CMD_DISSOLVE:String = "d";
+ private static const CMD_MAP_DIFFUSE:String = "map_Kd";
+ private static const CMD_MAP_DISSOLVE:String = "map_d";
+
+ private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/;
+ private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/;
+ private static const REGEXP_SPLIT_LINE:RegExp = /\s+/;
+
+ private static const STATE_IDLE:int = 0;
+ private static const STATE_LOADING:int = 1;
+
+ // Загрузчик файла MTL
+ private var mtlFileLoader:URLLoader;
+
+ // Библиотека загруженных материалов
+ private var _library:Map;
+ // Имя текущего материала
+ private var materialName:String;
+ // параметры текущего материала
+ private var currentMaterialInfo:MTLMaterialInfo = new MTLMaterialInfo();
+
+ private var loaderState:int = STATE_IDLE;
+
+ /**
+ * Создаёт новый экземпляр класса.
+ */
+ public function LoaderMTL() {
+ }
+
+ /**
+ * Прекращение текущей загрузки.
+ */
+ public function close():void {
+ if (loaderState == STATE_LOADING) {
+ mtlFileLoader.close();
+ }
+ loaderState = STATE_IDLE;
+ }
+
+ /**
+ * Метод очищает внутренние ссылки на загруженные данные чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает
+ * во время загрузки.
+ */
+ public function unload():void {
+ if (loaderState == STATE_IDLE) {
+ _library = null;
+ }
+ }
+
+ /**
+ * Библиотека материалов. Ключами являются наименования материалов, значениями -- объекты, наследники класса
+ * alternativa.engine3d.loaders.MaterialInfo.
+ * @see alternativa.engine3d.loaders.MaterialInfo
+ */
+ public function get library():Map {
+ return _library;
+ }
+
+ /**
+ * Метод выполняет загрузку файла материалов, разбор его содержимого, загрузку текстур при необходимости и
+ * формирование библиотеки материалов. После окончания работы метода посылается сообщение
+ * Event.COMPLETE и становится доступна библиотека материалов через свойство library.
+ *
+ * При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения IOErrorEvent.IO_ERROR и
+ *
+ * SecurityErrorEvent.SECURITY_ERROR соответственно.
+ *
+ * Если происходит ошибка при загрузке файла текстуры, то соответствующая текстура заменяется на текстуру-заглушку.
+ *
+ *
+ * @param url URL MTL-файла
+ * @param loaderContext LoaderContext для загрузки файлов текстур
+ *
+ * @see #library
+ */
+ public function load(url:String):void {
+ if (mtlFileLoader == null) {
+ mtlFileLoader = new URLLoader();
+ mtlFileLoader.addEventListener(Event.OPEN, retranslateEvent);
+ mtlFileLoader.addEventListener(ProgressEvent.PROGRESS, retranslateEvent);
+ mtlFileLoader.addEventListener(Event.COMPLETE, parseMTLFile);
+ mtlFileLoader.addEventListener(IOErrorEvent.IO_ERROR, onError);
+ mtlFileLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
+ } else {
+ close();
+ }
+ loaderState = STATE_LOADING;
+ mtlFileLoader.load(new URLRequest(url));
+ }
+
+ /**
+ *
+ */
+ private function retranslateEvent(e:Event):void {
+ dispatchEvent(e);
+ }
+
+ /**
+ * Обработка ошибки загрузки MTL-файла.
+ */
+ private function onError(e:Event):void {
+ loaderState = STATE_IDLE;
+ dispatchEvent(e);
+ }
+
+ /**
+ * Разбор содержимого загруженного файла материалов.
+ */
+ private function parseMTLFile(e:Event = null):void {
+ loaderState = STATE_IDLE;
+ parse(mtlFileLoader.data);
+ complete();
+ }
+
+ /**
+ *
+ */
+ public function parse(data:String):void {
+ _library = new Map();
+ data.split(REGEXP_SPLIT_FILE).forEach(parseLine);
+ }
+
+ /**
+ * Разбор строки файла.
+ *
+ * @param line строка файла
+ */
+ private function parseLine(line:String, index:int, lines:Array):void {
+ line = line.replace(REGEXP_TRIM,"$1")
+ if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) {
+ return;
+ }
+ var parts:Array = line.split(REGEXP_SPLIT_LINE);
+ switch (parts[0]) {
+ case CMD_NEW_MATERIAL:
+ defineMaterial(parts);
+ break;
+ case CMD_DIFFUSE_REFLECTIVITY:
+ readDiffuseReflectivity(parts);
+ break;
+ case CMD_DISSOLVE:
+ readAlpha(parts);
+ break;
+ case CMD_MAP_DIFFUSE:
+ currentMaterialInfo.diffuseMapInfo = MTLTextureMapInfo.parse(parts);
+ break;
+ case CMD_MAP_DISSOLVE:
+ currentMaterialInfo.dissolveMapInfo = MTLTextureMapInfo.parse(parts);
+ break;
+ }
+ }
+
+ /**
+ * Определение нового материала.
+ */
+ private function defineMaterial(parts:Array = null):void {
+ materialName = parts[1];
+ currentMaterialInfo = new MTLMaterialInfo();
+ _library[materialName] = currentMaterialInfo;
+ }
+
+ /**
+ * Чтение коэффициентов диффузного отражения. Считываются только коэффициенты, заданные в формате r g b. Для текущей
+ * версии движка данные коэффициенты преобразуются в цвет материала.
+ */
+ private function readDiffuseReflectivity(parts:Array):void {
+ var r:Number = Number(parts[1]);
+ // Проверка, заданы ли коэффициенты в виде r g b
+ if (!isNaN(r)) {
+ var g:Number = Number(parts[2]);
+ var b:Number = Number(parts[3]);
+ currentMaterialInfo.color = ColorUtils.rgb(255*r, 255*g, 255*b);
+ }
+ }
+
+ /**
+ * Чтение коэффициента непрозрачности. Считывается только коэффициент, заданный числом
+ * (не поддерживается параметр -halo).
+ */
+ private function readAlpha(parts:Array):void {
+ var alpha:Number = Number(parts[1]);
+ if (!isNaN(alpha)) {
+ currentMaterialInfo.alpha = alpha;
+ }
+ }
+
+ /**
+ * Обработка успешного завершения загрузки.
+ */
+ private function complete():void {
+ loaderState = STATE_IDLE;
+ dispatchEvent(new Event(Event.COMPLETE));
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoaderOBJ.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoaderOBJ.as
new file mode 100644
index 0000000..5b31862
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoaderOBJ.as
@@ -0,0 +1,423 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.loaders.events.LoaderEvent;
+ import alternativa.engine3d.loaders.events.LoaderProgressEvent;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.engine3d.materials.TextureMaterialPrecision;
+ import alternativa.types.Map;
+ import alternativa.types.Texture;
+
+ import flash.display.BlendMode;
+ import flash.events.ErrorEvent;
+ import flash.events.Event;
+ import flash.events.IOErrorEvent;
+ import flash.events.ProgressEvent;
+ import flash.events.SecurityErrorEvent;
+ import flash.geom.Point;
+ import flash.utils.ByteArray;
+
+ use namespace alternativa3d;
+
+ /**
+ * Загрузчик моделей из файла в формате OBJ. Загрузчик использует класс ParserOBJ для создания объектов из OBJ-файла, после чего выполняет загрузку
+ * библиотек материалов, создаёт материалы и назначает их объектам.
+ *
+ * При загрузке текстурных материалов учитывается наличие карт прозрачности.
+ *
+ *
+ * @see alternativa.engine3d.loaders.ParserOBJ
+ */
+ public class LoaderOBJ extends Loader3D {
+
+ /**
+ * Если указано значение false, то материалы загружаться не будут.
+ *
+ * @default true
+ */
+ public var loadMaterials:Boolean = true;
+
+ /**
+ * Значение свойства smooth для текстурных материалов.
+ *
+ * @default false
+ * @see alternativa.engine3d.materials.TextureMaterial#smooth
+ */
+ public var smooth:Boolean = false;
+
+ /**
+ * Значение свойства blendMode для текстурных материалов.
+ *
+ * @default BlendMode.NORMAL
+ * @see alternativa.engine3d.materials.Material#blendMode
+ */
+ public var blendMode:String = BlendMode.NORMAL;
+
+ /**
+ * Значение свойства precision для текстурных материалов.
+ *
+ * @default TextureMaterialPrecision.MEDIUM
+ * @see alternativa.engine3d.materials.TextureMaterial#precision
+ * @see alternativa.engine3d.materials.TextureMaterialPrecision
+ */
+ public var precision:Number = TextureMaterialPrecision.MEDIUM;
+
+ private static const STATE_LOADING_LIBRARY:int = 3;
+
+ private var mtlLoader:LoaderMTL;
+ private var materialsLibrary:Map;
+ private var uv:Point = new Point();
+ private var parser:ParserOBJ;
+
+ private var currentMaterialFileIndex:int;
+ private var numMaterialFiles:int;
+ private var textureMaterials:Map;
+
+ private var bitmapsLoader:TextureMapsBatchLoader;
+
+ /**
+ * Создаёт новый экземпляр класса.
+ */
+ public function LoaderOBJ() {
+ super();
+ parser = new ParserOBJ();
+ }
+
+ /**
+ * Коэффициент пересчёта единиц измерения сцены. Размеры создаваемых объектов будут умножены на заданное значение.
+ * @default 1
+ */
+ public function get scale():Number {
+ return parser.scale;
+ }
+
+ /**
+ * @private
+ */
+ public function set scale(value:Number):void {
+ parser.scale = value;
+ }
+
+ /**
+ * При установленном значении true объекты будут определяться не ключевым словом "o", а словом "g". Это может быть полезно, т.к. некоторые OBJ-експортёры могут
+ * использовать слово "g" для определения объектов в OBJ-файле.
+ *
+ * @default false
+ */
+ public function get objectsAsGroups():Boolean {
+ return parser.objectsAsGroups;
+ }
+
+ /**
+ * @private
+ */
+ public function set objectsAsGroups(value:Boolean):void {
+ parser.objectsAsGroups = value;
+ }
+
+ /**
+ * При установленном значении true выполняется преобразование координат геометрических вершин посредством
+ * поворота на 90 градусов относительно оси X. Смысл флага в преобразовании системы координат, в которой направление вверх определяется осью Y,
+ * в систему координат, использующуюся в Alternativa3D (вверх направлена ось Z).
+ *
+ * @default false
+ */
+ public function get rotateModel():Boolean {
+ return parser.rotateModel;
+ }
+
+ /**
+ * @private
+ */
+ public function set rotateModel(value:Boolean):void {
+ parser.rotateModel = value;
+ }
+
+ /**
+ * Уровень мобильности для загруженных объектов.
+ * @default 0
+ * @see alternativa.engine3d.core.Object3D#mobility
+ */
+ public function get mobility():int {
+ return parser.mobility;
+ }
+
+ /**
+ * @private
+ */
+ public function set mobility(value:int):void {
+ parser.mobility = value;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function closeInternal():void {
+ super.closeInternal();
+ if (loaderState == Loader3DState.LOADING_LIBRARY) {
+ mtlLoader.close();
+ }
+ }
+
+ /**
+ * Метод очищает внутренние ссылки на загруженные данные, чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает
+ * во время загрузки.
+ */
+ override protected function unloadInternal():void {
+ if (mtlLoader != null) {
+ mtlLoader.unload();
+ }
+ if (bitmapsLoader != null) {
+ bitmapsLoader.unload();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function clean():void {
+ super.clean();
+ loaderContext = null;
+ textureMaterials = null;
+ }
+
+ /**
+ * Метод выполняет разбор данных, полученных из OBJ-файла.
+ *
+ * @param s содержимое OBJ-файла
+ * @param materialLibrary библиотека материалов
+ * @return объект, содержащий все трёхмерные объекты, определённые в OBJ-файле
+ */
+ override protected function parse(data:ByteArray):void {
+ parser.parse(data.toString());
+ // После разбора файла имеем дерево 3д-объектов и массив с именами файлов библиотек материалов
+ _content = parser.content;
+ // Загружаем библиотеки материалов или заканчиваем работу, если материалов нет
+ numMaterialFiles = parser.materialFileNames.length;
+ if (loadMaterials && numMaterialFiles > 0) {
+ loadMaterialsLibrary();
+ } else {
+ complete();
+ }
+ }
+
+ /**
+ * Загружает библиотеки материалов.
+ *
+ * @param materialFileNames массив с именами файлов библиотек материалов
+ */
+ private function loadMaterialsLibrary():void {
+ loaderState = Loader3DState.LOADING_LIBRARY;
+ if (mtlLoader == null) {
+ mtlLoader = new LoaderMTL();
+ mtlLoader.addEventListener(Event.OPEN, onMaterialLibLoadingStart);
+ mtlLoader.addEventListener(ProgressEvent.PROGRESS, onMaterialLibLoadingProgress);
+ mtlLoader.addEventListener(Event.COMPLETE, onMaterialLibLoadingComplete);
+ mtlLoader.addEventListener(IOErrorEvent.IO_ERROR, onMaterialLibLoadingError);
+ mtlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onMaterialLibLoadingError);
+ }
+ materialsLibrary = new Map();
+
+ currentMaterialFileIndex = -1;
+ loadNextMaterialFile();
+ }
+
+ /**
+ * Обрабатывает ошибки при загрузке библиотеки материалов.
+ */
+ private function onMaterialLibLoadingError(e:ErrorEvent):void {
+ close();
+ dispatchEvent(e);
+ }
+
+ /**
+ *
+ */
+ private function onMaterialLibLoadingStart(e:Event):void {
+ dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_START, LoadingStage.MATERIAL_LIBRARY));
+ }
+
+ /**
+ *
+ */
+ private function onMaterialLibLoadingProgress(e:ProgressEvent):void {
+ dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADING_PROGRESS, LoadingStage.MATERIAL_LIBRARY, numMaterialFiles, currentMaterialFileIndex, e.bytesLoaded, e.bytesTotal));
+ }
+
+ /**
+ * Обработка успешной загрузки библиотеки материалов.
+ */
+ private function onMaterialLibLoadingComplete(e:Event):void {
+ dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_COMPLETE, LoadingStage.MATERIAL_LIBRARY));
+ // Слияние загруженной библиотеки с уже имеющейся
+ materialsLibrary.concat(mtlLoader.library);
+ // Загрузка следующего файла материалов
+ loadNextMaterialFile();
+ }
+
+ /**
+ * Загрузка и разбор очередного файла материалов.
+ */
+ private function loadNextMaterialFile():void {
+ currentMaterialFileIndex++;
+ if (currentMaterialFileIndex == numMaterialFiles) {
+ // Все материалы загружены, проверяется наличие текстурных материалов и выполняется их загрузка
+ checkMaterials();
+ } else {
+ mtlLoader.load(baseURL + parser.materialFileNames[currentMaterialFileIndex]);
+ }
+ }
+
+ /**
+ *
+ */
+ private function checkMaterials():void {
+ collectTextureMaterialNames();
+ if (textureMaterials != null) {
+ loadTextures();
+ } else {
+ setMaterials();
+ complete();
+ }
+ }
+
+ /**
+ * Запускает процесс загрузки текстур.
+ */
+ private function loadTextures():void {
+ if (bitmapsLoader == null) {
+ bitmapsLoader = new TextureMapsBatchLoader();
+ bitmapsLoader.addEventListener(LoaderEvent.LOADING_START, onTextureLoadingStart);
+ bitmapsLoader.addEventListener(LoaderEvent.LOADING_COMPLETE, onTextureLoadingComplete);
+ bitmapsLoader.addEventListener(LoaderProgressEvent.LOADING_PROGRESS, onTextureLoadingProgress);
+ bitmapsLoader.addEventListener(Event.COMPLETE, onTextureMaterialsLoadingComplete);
+ bitmapsLoader.addEventListener(IOErrorEvent.IO_ERROR, onTextureLoadingError);
+ }
+ setState(Loader3DState.LOADING_TEXTURE);
+ bitmapsLoader.load(baseURL, textureMaterials, loaderContext);
+ }
+
+ /**
+ * Обрабатывает неудачную загрузку текстуры.
+ */
+ private function onTextureLoadingError(e:IOErrorEvent):void {
+ dispatchEvent(e);
+ }
+
+ /**
+ * Обрабатывает начало загрузки очередного файла текстуры.
+ */
+ private function onTextureLoadingStart(e:Event):void {
+ dispatchEvent(e);
+ }
+
+ /**
+ * Обрабатывает окончание загрузки очередного файла текстуры.
+ */
+ private function onTextureLoadingComplete(e:Event):void {
+ dispatchEvent(e);
+ }
+
+ /**
+ * Рассылает событие прогресса загрузки файла текстуры.
+ */
+ private function onTextureLoadingProgress(e:LoaderProgressEvent):void {
+ dispatchEvent(e);
+ }
+
+ /**
+ * Обрабатывает завершение загрузки текстур материала.
+ */
+ private function onTextureMaterialsLoadingComplete(e:Event):void {
+ setMaterials();
+ complete();
+ }
+
+ /**
+ *
+ */
+ private function collectTextureMaterialNames():void {
+ for (var materialName:String in materialsLibrary) {
+ var info:MTLMaterialInfo = materialsLibrary[materialName];
+ if (info.diffuseMapInfo != null) {
+ if (textureMaterials == null) {
+ textureMaterials = new Map();
+ }
+ var textureMapsInfo:TextureMapsInfo = new TextureMapsInfo();
+ textureMapsInfo.diffuseMapFileName = info.diffuseMapInfo.fileName;
+ if (info.dissolveMapInfo != null) {
+ textureMapsInfo.opacityMapFileName = info.dissolveMapInfo.fileName;
+ }
+ textureMaterials.add(materialName, textureMapsInfo);
+ }
+ }
+ }
+
+ /**
+ * Устанавливает материалы.
+ */
+ private function setMaterials():void {
+ if (materialsLibrary != null) {
+ for (var objectKey:* in _content.children) {
+ var object:Mesh = objectKey;
+ for (var surfaceKey:* in object._surfaces) {
+ var surface:Surface = object._surfaces[surfaceKey];
+ // Поверхности имеют идентификаторы, соответствующие именам материалов
+ var materialInfo:MTLMaterialInfo = materialsLibrary[surfaceKey];
+ if (materialInfo != null) {
+ if (materialInfo.diffuseMapInfo == null) {
+ surface.material = new FillMaterial(materialInfo.color, materialInfo.alpha, blendMode);
+ } else {
+ var texture:Texture = new Texture(bitmapsLoader.textures[surfaceKey], materialInfo.diffuseMapInfo.fileName);
+ surface.material = new TextureMaterial(texture, materialInfo.alpha, materialInfo.diffuseMapInfo.repeat, smooth, blendMode, -1, 0, precision);
+ transformUVs(surface, new Point(materialInfo.diffuseMapInfo.offsetU, materialInfo.diffuseMapInfo.offsetV), new Point(materialInfo.diffuseMapInfo.sizeU, materialInfo.diffuseMapInfo.sizeV));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Метод выполняет преобразование UV-координат текстурированных граней. В связи с тем, что в формате MTL предусмотрено
+ * масштабирование и смещение текстурной карты в UV-пространстве, а в движке такой фунциональности нет, необходимо
+ * эмулировать преобразования текстуры преобразованием UV-координат граней. Преобразования выполняются исходя из предположения,
+ * что текстурное пространство сначала масштабируется относительно центра, а затем сдвигается на указанную величину
+ * смещения.
+ *
+ * @param surface поверхность, грани которой обрабатываюся
+ * @param mapOffset смещение текстурной карты. Значение mapOffset.x указывает смещение по U, значение mapOffset.y
+ * указывает смещение по V.
+ * @param mapSize коэффициенты масштабирования текстурной карты. Значение mapSize.x указывает коэффициент масштабирования
+ * по оси U, значение mapSize.y указывает коэффициент масштабирования по оси V.
+ */
+ private function transformUVs(surface:Surface, mapOffset:Point, mapSize:Point):void {
+ for (var key:* in surface._faces) {
+ var face:Face = key;
+ if (face._aUV) {
+ uv.x = face._aUV.x;
+ uv.y = face._aUV.y;
+ uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x)*mapSize.x;
+ uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y)*mapSize.y;
+ face.aUV = uv;
+
+ uv.x = face._bUV.x;
+ uv.y = face._bUV.y;
+ uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x)*mapSize.x;
+ uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y)*mapSize.y;
+ face.bUV = uv;
+
+ uv.x = face._cUV.x;
+ uv.y = face._cUV.y;
+ uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x)*mapSize.x;
+ uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y)*mapSize.y;
+ face.cUV = uv;
+ }
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoadingStage.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoadingStage.as
new file mode 100644
index 0000000..0e4aad2
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/LoadingStage.as
@@ -0,0 +1,20 @@
+package alternativa.engine3d.loaders {
+
+ /**
+ * Набор констант, которые описывают этапы загрузки.
+ */
+ public final class LoadingStage {
+ /**
+ * Обозначает загрузку основного файла.
+ */
+ public static const MAIN_FILE:int = 0;
+ /**
+ * Обозначает загрузку текстур.
+ */
+ public static const TEXTURE:int = 1;
+ /**
+ * Обозначает загрузку библиотек материалов.
+ */
+ public static const MATERIAL_LIBRARY:int = 2;
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/MTLMaterialInfo.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/MTLMaterialInfo.as
new file mode 100644
index 0000000..4149a8b
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/MTLMaterialInfo.as
@@ -0,0 +1,16 @@
+package alternativa.engine3d.loaders {
+ import flash.display.BitmapData;
+ import flash.geom.Point;
+
+ /**
+ * @private
+ * Класс содержит обобщённую информацию о материале.
+ */
+ public class MTLMaterialInfo {
+ public var color:uint;
+ public var alpha:Number;
+
+ public var diffuseMapInfo:MTLTextureMapInfo;
+ public var dissolveMapInfo:MTLTextureMapInfo;
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/MTLTextureMapInfo.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/MTLTextureMapInfo.as
new file mode 100644
index 0000000..421b3cb
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/MTLTextureMapInfo.as
@@ -0,0 +1,120 @@
+package alternativa.engine3d.loaders {
+ /**
+ * @private
+ * Класс содержит информацию о текстуре в формате MTL material format (Lightwave, OBJ) и функционал для разбора
+ * описания текстуры.
+ * Описание формата можно посмотреть по адресу: http://local.wasp.uwa.edu.au/~pbourke/dataformats/mtl/
+ */
+ public class MTLTextureMapInfo {
+
+ // Ассоциация параметров команды объявления текстуры и методов для их чтения
+ private static const optionReaders:Object = {
+ "-clamp": clampReader,
+ "-o": offsetReader,
+ "-s": sizeReader,
+
+ "-blendu": stubReader,
+ "-blendv": stubReader,
+ "-bm": stubReader,
+ "-boost": stubReader,
+ "-cc": stubReader,
+ "-imfchan": stubReader,
+ "-mm": stubReader,
+ "-t": stubReader,
+ "-texres": stubReader
+ };
+
+ // Смещение в текстурном пространстве
+ public var offsetU:Number = 0;
+ public var offsetV:Number = 0;
+ public var offsetW:Number = 0;
+
+ // Масштабирование текстурного пространства
+ public var sizeU:Number = 1;
+ public var sizeV:Number = 1;
+ public var sizeW:Number = 1;
+
+ // Флаг повторения текстуры
+ public var repeat:Boolean = true;
+ // Имя файла текстуры
+ public var fileName:String;
+
+ /**
+ * Метод выполняет разбор данных о текстуре.
+ *
+ * @param parts Данные о текстуре. Массив должен содержать части разделённой по пробелам входной строки MTL-файла.
+ * @return объект, содержащий данные о текстуре
+ */
+ public static function parse(parts:Array):MTLTextureMapInfo {
+ var info:MTLTextureMapInfo = new MTLTextureMapInfo();
+ // Начальное значение индекса единица, т.к. первый элемент массива содержит тип текстуры
+ var index:int = 1;
+ var reader:Function;
+ // Чтение параметров текстуры
+ while ((reader = optionReaders[parts[index]]) != null) {
+ index = reader(index, parts, info);
+ }
+ // Если не было ошибок, последний элемент массива должен содержать имя файла текстуры
+ info.fileName = parts[index];
+ return info;
+ }
+
+ /**
+ * Читатель-заглушка. Пропускает все неподдерживаемые параметры.
+ */
+ private static function stubReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
+ index++;
+ var maxIndex:int = parts.length - 1;
+ while ((MTLTextureMapInfo.optionReaders[parts[index]] == null) && (index < maxIndex)) {
+ index++;
+ }
+ return index;
+ }
+
+ /**
+ * Метод чтения параметров масштабирования текстурного пространства.
+ */
+ private static function sizeReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
+ info.sizeU = Number(parts[index + 1]);
+ index += 2;
+ var value:Number = Number(parts[index]);
+ if (!isNaN(value)) {
+ info.sizeV = value;
+ index++;
+ value = Number(parts[index]);
+ if (!isNaN(value)) {
+ info.sizeW = value;
+ index++;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Метод чтения параметров смещения текстуры.
+ */
+ private static function offsetReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
+ info.offsetU = Number(parts[index + 1]);
+ index += 2;
+ var value:Number = Number(parts[index]);
+ if (!isNaN(value)) {
+ info.offsetV = value;
+ index++;
+ value = Number(parts[index]);
+ if (!isNaN(value)) {
+ info.offsetW = value;
+ index++;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Метод чтения параметра повторения текстуры.
+ */
+ private static function clampReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
+ info.repeat = parts[index + 1] == "off";
+ return index + 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/Parser3DS.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/Parser3DS.as
new file mode 100644
index 0000000..f2d9932
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/Parser3DS.as
@@ -0,0 +1,1121 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.alternativa3d;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.engine3d.materials.TextureMaterialPrecision;
+ import alternativa.engine3d.materials.WireMaterial;
+ import alternativa.types.Map;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.utils.ColorUtils;
+ import alternativa.utils.MathUtils;
+
+ import flash.display.BlendMode;
+ import flash.geom.Matrix;
+ import flash.geom.Point;
+ import flash.utils.ByteArray;
+ import flash.utils.Endian;
+
+ use namespace alternativa3d;
+
+ /**
+ * Класс позволяет разобрать бинарные данные в формате 3DS. После разбора данных получается дерево трёхмерных объектов и список используемых текстур.
+ *
+ * В процессе построения дерева объектов неподдерживаемые объекты заменяются экземплярами Object3D.
+ *
+ *
+ * Материалы, для которых указан файл диффузной текстуры, представляются в виде TextureMaterial с пустой текстурой, которая должна быть установлена позднее.
+ * Информация о файлах диффузной карты и карты прозрачности добавляется в список textureMaterials. При этом имена файлов переводятся в нижний регистр.
+ *
+ *
+ * Материалы, для которых файл диффузной текстуры не указан, представляются экземпляром FillMaterial с указанным цветом.
+ *
+ *
+ * При формировании каждого объекта, имеющие одинаковый материал грани объединяются в поверхности. Идентификаторы поверхностей совпадают
+ * с названием соответствующего материала. Если для объекта не задан ни один материал, то все грани помещаются в одну поверхность, для которой
+ * устанавливается материал WireMaterial с линиями серого цвета.
+ *
+ *
+ * Класс имеет ряд свойств, влияющих на создаваемые текстурные материалы.
+ *
+ */
+ public class Parser3DS {
+ private static const CHUNK_MAIN:uint = 0x4D4D;
+ private static const CHUNK_VERSION:uint = 0x0002;
+ private static const CHUNK_SCENE:uint = 0x3D3D;
+ private static const CHUNK_ANIMATION:uint = 0xB000;
+ private static const CHUNK_OBJECT:uint = 0x4000;
+ private static const CHUNK_TRIMESH:uint = 0x4100;
+ private static const CHUNK_VERTICES:uint = 0x4110;
+ private static const CHUNK_FACES:uint = 0x4120;
+ private static const CHUNK_FACESMATERIAL:uint = 0x4130;
+ private static const CHUNK_MAPPINGCOORDS:uint = 0x4140;
+ private static const CHUNK_OBJECTCOLOR:uint = 0x4165;
+ private static const CHUNK_TRANSFORMATION:uint = 0x4160;
+ private static const CHUNK_MESHANIMATION:uint = 0xB002;
+ private static const CHUNK_MATERIAL:uint = 0xAFFF;
+
+ private var data:ByteArray;
+ // Контейнер с объектами сцены
+ private var _content:Object3D;
+ // Мапа, хранящая имена файлов текстур. Ключи -- имена материалов, значения -- объекты типа TextureMapsInfo.
+ private var _textureMaterials:Map;
+ // Флаг повтора текстуры для создаваемых текстурных материалов.
+ private var _repeat:Boolean = true;
+ // Флаг сглаживания текстур для создаваемых текстурных материалов.
+ private var _smooth:Boolean = false;
+ // Режим наложения цвета для создаваемых текстурных материалов.
+ private var _blendMode:String = BlendMode.NORMAL;
+ // Точность перспективной коррекции для создаваемых текстурных материалов.
+ private var _precision:Number = TextureMaterialPrecision.MEDIUM;
+ // Коэффициент пересчёта единиц измерения сцены. Размеры создаваемых объектов будут умножены на заданное значение.
+ private var _scale:Number = 1;
+ // Уровень мобильности для загруженных объектов.
+ private var _mobility:int = 0;
+
+ // Внутренние переменные для хранения промежуточных данных при разборе
+ private var version:uint;
+ private var objectDatas:Map;
+ private var animationDatas:Array;
+ private var materialDatas:Map;
+
+ /**
+ * Объект-контейнер, содержащий все загруженные объекты.
+ */
+ public function get content():Object3D {
+ return _content;
+ }
+
+ /**
+ * Флаг повтора текстуры для создаваемых текстурных материалов.
+ *
+ * @default true
+ * @see alternativa.engine3d.materials.TextureMaterial#repeat
+ */
+ public function get repeat():Boolean {
+ return _repeat;
+ }
+
+ /**
+ * @private
+ */
+ public function set repeat(value:Boolean):void {
+ _repeat = value;
+ }
+
+ /**
+ * Флаг сглаживания текстур для создаваемых текстурных материалов.
+ *
+ * @default false
+ * @see alternativa.engine3d.materials.TextureMaterial#smooth
+ */
+ public function get smooth():Boolean {
+ return _smooth;
+ }
+
+ /**
+ * @private
+ */
+ public function set smooth(value:Boolean):void {
+ _smooth = value;
+ }
+
+ /**
+ * Режим наложения цвета для создаваемых текстурных материалов.
+ *
+ * @default BlendMode.NORMAL
+ * @see alternativa.engine3d.materials.Material#blendMode
+ */
+ public function get blendMode():String {
+ return _blendMode;
+ }
+
+ /**
+ * @private
+ */
+ public function set blendMode(value:String):void {
+ _blendMode = value;
+ }
+
+ /**
+ * Точность перспективной коррекции для создаваемых текстурных материалов.
+ *
+ * @default TextureMaterialPrecision.MEDIUM
+ * @see alternativa.engine3d.materials.TextureMaterial#precision
+ * @see alternativa.engine3d.materials.TextureMaterialPrecision
+ */
+ public function get precision():Number {
+ return _precision;
+ }
+
+ /**
+ * @private
+ */
+ public function set precision(value:Number):void {
+ _precision = value;
+ }
+
+ /**
+ * Коэффициент пересчёта единиц измерения сцены. Размеры создаваемых объектов будут умножены на заданное значение.
+ * @default 1
+ */
+ public function get scale():Number {
+ return _scale;
+ }
+
+ /**
+ * @private
+ */
+ public function set scale(value:Number):void {
+ _scale = value;
+ }
+
+ /**
+ * Уровень мобильности для загруженных объектов.
+ * @default 0
+ * @see alternativa.engine3d.core.Object3D#mobility
+ */
+ public function get mobility():int {
+ return _mobility;
+ }
+
+ /**
+ * @private
+ */
+ public function set mobility(value:int):void {
+ _mobility = value;
+ }
+
+ /**
+ * Список файлов текстур. Ключами являются имена материалов, значениями — объекты класса TextureMapsInfo, содержащие
+ * имена файлов диффузной текстуры и карты прозрачности.
+ *
+ * @see alternativa.engine3d.loaders.TextureMapsInfo
+ */
+ public function get textureMaterials():Map {
+ return _textureMaterials;
+ }
+
+ /**
+ * Разбирает 3DS-данные и формирует дерево трёхмерных объектов. Чтение данных начинается с текущей позиции в массиве. После успешного окончания работы метода
+ * контейнер с объектами становится доступным через свойство content. Свойство textureMaterials будет содержать имена файлов текстур для каждого
+ * текстурного материала, либо будет равно null, если в сцене нет текстурных материалов.
+ *
+ * @param data массив с данными в формате 3DS
+ * @return true в случае успешного разбора данных, иначе false
+ *
+ * @see #content
+ * @see #textureMaterials
+ */
+ public function parse(data:ByteArray):Boolean {
+ unload();
+
+ if (data.bytesAvailable < 6) {
+ return false;
+ }
+ this.data = data;
+ data.endian = Endian.LITTLE_ENDIAN;
+
+ try {
+ parse3DSChunk(data.position, data.bytesAvailable);
+ buildContent();
+ } catch (e:Error) {
+ unload();
+ throw e;
+ } finally {
+ clean();
+ }
+
+ return true;
+ }
+
+ /**
+ * Удаляет внутренние ссылки на сформированные данные.
+ */
+ public function unload():void {
+ _content = null;
+ _textureMaterials = null;
+ }
+
+ /**
+ * Удаляет ссылки на временные данные.
+ */
+ private function clean():void {
+ version = 0;
+ objectDatas = null;
+ animationDatas = null;
+ materialDatas = null;
+ }
+
+ /**
+ * Читает заголовок блока и возвращает его описание.
+ *
+ * @param dataPosition
+ * @param bytesAvailable
+ * @return
+ */
+ private function readChunkInfo(dataPosition:uint, bytesAvailable:uint):ChunkInfo {
+ if (bytesAvailable < 6) {
+ return null;
+ }
+ data.position = dataPosition;
+ var chunkInfo:ChunkInfo = new ChunkInfo();
+ chunkInfo.id = data.readUnsignedShort();
+ chunkInfo.size = data.readUnsignedInt();
+ chunkInfo.dataSize = chunkInfo.size - 6;
+ chunkInfo.dataPosition = data.position;
+ chunkInfo.nextChunkPosition = dataPosition + chunkInfo.size;
+ return chunkInfo;
+ }
+
+ /**
+ *
+ */
+ private function parse3DSChunk(dataPosition:uint, bytesAvailable:uint):void {
+ var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable);
+ if (chunkInfo == null) {
+ return;
+ }
+
+ data.position = dataPosition;
+
+ switch (chunkInfo.id) {
+ // Главный
+ case CHUNK_MAIN:
+ parseMainChunk(chunkInfo.dataPosition, chunkInfo.dataSize);
+ break;
+ }
+
+ parse3DSChunk(chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size);
+ }
+
+ /**
+ *
+ */
+ private function parseMainChunk(dataPosition:uint, bytesAvailable:uint):void {
+ var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable);
+ if (chunkInfo == null) {
+ return;
+ }
+
+ switch (chunkInfo.id) {
+ // Версия
+ case CHUNK_VERSION:
+ version = data.readUnsignedInt();
+ break;
+ // 3D-сцена
+ case CHUNK_SCENE:
+ parse3DChunk(chunkInfo.dataPosition, chunkInfo.dataSize);
+ break;
+ // Анимация
+ case CHUNK_ANIMATION:
+ parseAnimationChunk(chunkInfo.dataPosition, chunkInfo.dataSize);
+ break;
+ }
+
+ parseMainChunk(chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size);
+ }
+
+ /**
+ *
+ */
+ private function parse3DChunk(dataPosition:uint, bytesAvailable:uint):void {
+ var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable);
+ if (chunkInfo == null) {
+ return;
+ }
+
+ switch (chunkInfo.id) {
+ // Материал
+ case CHUNK_MATERIAL:
+ // Парсим материал
+ var material:MaterialData = new MaterialData();
+ parseMaterialChunk(material, chunkInfo.dataPosition, chunkInfo.dataSize);
+ break;
+ // Объект
+ case CHUNK_OBJECT:
+ parseObject(chunkInfo);
+ break;
+ }
+
+ parse3DChunk(chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size);
+ }
+
+ /**
+ *
+ */
+ private function parseObject(chunkInfo:ChunkInfo):void {
+ // Создаём список объектов, если надо
+ if (objectDatas == null) {
+ objectDatas = new Map();
+ }
+ // Создаём данные объекта
+ var object:ObjectData = new ObjectData();
+ // Получаем название объекта
+ object.name = getString(chunkInfo.dataPosition);
+ // Помещаем данные объекта в список
+ objectDatas[object.name] = object;
+ // Парсим объект
+ var offset:int = object.name.length + 1;
+ parseObjectChunk(object, chunkInfo.dataPosition + offset, chunkInfo.dataSize - offset);
+ }
+
+ /**
+ *
+ */
+ private function parseObjectChunk(object:ObjectData, dataPosition:uint, bytesAvailable:uint):void {
+ var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable);
+ if (chunkInfo == null) {
+ return;
+ }
+
+ switch (chunkInfo.id) {
+ // Меш
+ case CHUNK_TRIMESH:
+ parseMeshChunk(object, chunkInfo.dataPosition, chunkInfo.dataSize);
+ break;
+ // Источник света
+ case 0x4600:
+ break;
+ // Камера
+ case 0x4700:
+ break;
+ }
+
+ parseObjectChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size);
+ }
+
+ /**
+ *
+ */
+ private function parseMeshChunk(object:ObjectData, dataPosition:uint, bytesAvailable:uint):void {
+ var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable);
+ if (chunkInfo == null) {
+ return;
+ }
+
+ switch (chunkInfo.id) {
+ // Вершины
+ case CHUNK_VERTICES:
+ parseVertices(object);
+ break;
+ // UV
+ case CHUNK_MAPPINGCOORDS:
+ parseUVs(object);
+ break;
+ // Трансформация
+ case CHUNK_TRANSFORMATION:
+ parseMatrix(object);
+ break;
+ // Грани
+ case CHUNK_FACES:
+ parseFaces(object, chunkInfo);
+ break;
+ }
+
+ parseMeshChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size);
+ }
+
+ /**
+ *
+ */
+ private function parseVertices(object:ObjectData):void {
+ var num:uint = data.readUnsignedShort();
+ object.vertices = new Array();
+ for (var i:uint = 0; i < num; i++) {
+ object.vertices.push(new Point3D(data.readFloat(), data.readFloat(), data.readFloat()));
+ }
+ }
+
+ /**
+ *
+ */
+ private function parseUVs(object:ObjectData):void {
+ var num:uint = data.readUnsignedShort();
+ object.uvs = new Array();
+ for (var i:uint = 0; i < num; i++) {
+ object.uvs.push(new Point(data.readFloat(), data.readFloat()));
+ }
+ }
+
+ /**
+ *
+ */
+ private function parseMatrix(object:ObjectData):void {
+ object.matrix = new Matrix3D();
+ object.matrix.a = data.readFloat();
+ object.matrix.e = data.readFloat();
+ object.matrix.i = data.readFloat();
+ object.matrix.b = data.readFloat();
+ object.matrix.f = data.readFloat();
+ object.matrix.j = data.readFloat();
+ object.matrix.c = data.readFloat();
+ object.matrix.g = data.readFloat();
+ object.matrix.k = data.readFloat();
+ object.matrix.d = data.readFloat();
+ object.matrix.h = data.readFloat();
+ object.matrix.l = data.readFloat();
+ }
+
+ /**
+ *
+ */
+ private function parseFaces(object:ObjectData, chunkInfo:ChunkInfo):void {
+ var num:uint = data.readUnsignedShort();
+ object.faces = new Array();
+ for (var i:uint = 0; i < num; i++) {
+ var face:FaceData = new FaceData();
+ face.a = data.readUnsignedShort();
+ face.b = data.readUnsignedShort();
+ face.c = data.readUnsignedShort();
+ object.faces.push(face);
+ data.position += 2; // Пропускаем флаг отрисовки рёбер
+ }
+ var offset:uint = 2 + 8*num;
+ parseFacesChunk(object, chunkInfo.dataPosition + offset, chunkInfo.dataSize - offset);
+ }
+
+ /**
+ *
+ */
+ private function parseFacesChunk(object:ObjectData, dataPosition:uint, bytesAvailable:uint):void {
+ var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable);
+ if (chunkInfo == null) {
+ return;
+ }
+
+ switch (chunkInfo.id) {
+ // Поверхности
+ case CHUNK_FACESMATERIAL:
+ parseSurface(object);
+ break;
+ }
+
+ parseFacesChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size);
+ }
+
+ /**
+ *
+ */
+ private function parseSurface(object:ObjectData):void {
+ // Создаём список поверхностей, если надо
+ if (object.surfaces == null) {
+ object.surfaces = new Map();
+ }
+ // Создаём данные поверхности
+ var surface:SurfaceData = new SurfaceData();
+ // Получаем название материала
+ surface.materialName = getString(data.position);
+ // Помещаем данные поверхности в список
+ object.surfaces[surface.materialName] = surface;
+ // Получаем грани поверхности
+ var num:uint = data.readUnsignedShort();
+ surface.faces = new Array(num);
+ for (var i:uint = 0; i < num; i++) {
+ surface.faces[i] = data.readUnsignedShort();
+ }
+ }
+
+ /**
+ *
+ */
+ private function parseAnimationChunk(dataPosition:uint, bytesAvailable:uint):void {
+ var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable);
+ if (chunkInfo == null) {
+ return;
+ }
+
+ switch (chunkInfo.id) {
+ // Анимация объекта
+ case 0xB001:
+ case 0xB002:
+ case 0xB003:
+ case 0xB004:
+ case 0xB005:
+ case 0xB006:
+ case 0xB007:
+ if (animationDatas == null) {
+ animationDatas = new Array();
+ }
+ var animation:AnimationData = new AnimationData();
+ animationDatas.push(animation);
+ parseObjectAnimationChunk(animation, chunkInfo.dataPosition, chunkInfo.dataSize);
+ break;
+ // Таймлайн
+ case 0xB008:
+ break;
+ }
+
+ parseAnimationChunk(chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size);
+ }
+
+ /**
+ *
+ */
+ private function parseObjectAnimationChunk(animation:AnimationData, dataPosition:uint, bytesAvailable:uint):void {
+ var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable);
+ if (chunkInfo == null) {
+ return;
+ }
+
+ switch (chunkInfo.id) {
+ // Идентификация объекта и его связь
+ case 0xB010:
+ // Имя объекта
+ animation.objectName = getString(data.position);
+ data.position += 4;
+ // Индекс родительского объекта в линейном списке объектов сцены
+ animation.parentIndex = data.readUnsignedShort();
+ break;
+ // Имя dummy объекта
+ case 0xB011:
+ animation.objectName = getString(data.position);
+ break;
+ // Точка привязки объекта (pivot)
+ case 0xB013:
+ animation.pivot = new Point3D(data.readFloat(), data.readFloat(), data.readFloat());
+ break;
+ // Смещение объекта относительно родителя
+ case 0xB020:
+ data.position += 20;
+ animation.position = new Point3D(data.readFloat(), data.readFloat(), data.readFloat());
+ break;
+ // Поворот объекта относительно родителя (angle-axis)
+ case 0xB021:
+ data.position += 20;
+ animation.rotation = getRotationFrom3DSAngleAxis(data.readFloat(), data.readFloat(), data.readFloat(), data.readFloat());
+ break;
+ // Масштабирование объекта относительно родителя
+ case 0xB022:
+ data.position += 20;
+ animation.scale = new Point3D(data.readFloat(), data.readFloat(), data.readFloat());
+ break;
+ }
+
+ parseObjectAnimationChunk(animation, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size);
+ }
+
+ /**
+ *
+ */
+ private function parseMaterialChunk(material:MaterialData, dataPosition:uint, bytesAvailable:uint):void {
+ var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable);
+ if (chunkInfo == null) {
+ return;
+ }
+
+ var textureMapsInfo:TextureMapsInfo;
+ switch (chunkInfo.id) {
+ // Имя материала
+ case 0xA000:
+ parseMaterialName(material);
+ break;
+ // Ambient color
+ case 0xA010:
+ break;
+ // Diffuse color
+ case 0xA020:
+ data.position = chunkInfo.dataPosition + 6;
+ material.color = ColorUtils.rgb(data.readUnsignedByte(), data.readUnsignedByte(), data.readUnsignedByte());
+ break;
+ // Specular color
+ case 0xA030:
+ break;
+ // Shininess percent
+ case 0xA040:
+ data.position = chunkInfo.dataPosition + 6;
+ material.glossiness = data.readUnsignedShort();
+ break;
+ // Shininess strength percent
+ case 0xA041:
+ data.position = chunkInfo.dataPosition + 6;
+ material.specular = data.readUnsignedShort();
+ break;
+ // Transperensy
+ case 0xA050:
+ data.position = chunkInfo.dataPosition + 6;
+ material.transparency = data.readUnsignedShort();
+ break;
+ // Texture map 1
+ case 0xA200:
+ material.diffuseMap = new MapData();
+ parseMapChunk(material.name, material.diffuseMap, chunkInfo.dataPosition, chunkInfo.dataSize);
+ textureMapsInfo = getTextureMapsInfo(material.name);
+ textureMapsInfo.diffuseMapFileName = material.diffuseMap.filename;
+ break;
+ // Texture map 2
+ case 0xA33A:
+ break;
+ // Opacity map
+ case 0xA210:
+ material.opacityMap = new MapData();
+ parseMapChunk(material.name, material.opacityMap, chunkInfo.dataPosition, chunkInfo.dataSize);
+ textureMapsInfo = getTextureMapsInfo(material.name);
+ textureMapsInfo.opacityMapFileName = material.opacityMap.filename;
+ break;
+ // Bump map
+ case 0xA230:
+ //material.normalMap = new MapData();
+ //parseMapChunk(material.normalMap, dataIndex, dataLength);
+ break;
+ // Shininess map
+ case 0xA33C:
+ break;
+ // Specular map
+ case 0xA204:
+ break;
+ // Self-illumination map
+ case 0xA33D:
+ break;
+ // Reflection map
+ case 0xA220:
+ break;
+ }
+
+ parseMaterialChunk(material, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size);
+ }
+
+ /**
+ *
+ */
+ private function getTextureMapsInfo(materialName:String):TextureMapsInfo {
+ if (_textureMaterials == null) {
+ _textureMaterials = new Map();
+ }
+ var info:TextureMapsInfo = _textureMaterials[materialName];
+ if (info == null) {
+ info = new TextureMapsInfo();
+ _textureMaterials[materialName] = info;
+ }
+ return info;
+ }
+
+ /**
+ *
+ */
+ private function parseMaterialName(material:MaterialData):void {
+ // Создаём список материалов, если надо
+ if (materialDatas == null) {
+ materialDatas = new Map();
+ }
+ // Получаем название материала
+ material.name = getString(data.position);
+ // Помещаем данные материала в список
+ materialDatas[material.name] = material;
+ }
+
+ /**
+ *
+ */
+ private function parseMapChunk(materialName:String, map:MapData, dataPosition:uint, bytesAvailable:uint):void {
+ var chunkInfo:ChunkInfo = readChunkInfo(dataPosition, bytesAvailable);
+ if (chunkInfo == null) {
+ return;
+ }
+
+ switch (chunkInfo.id) {
+ // Имя файла
+ case 0xA300:
+ map.filename = getString(chunkInfo.dataPosition).toLowerCase();
+ break;
+ case 0xA351:
+ // Параметры текстурирования
+// trace("MAP OPTIONS", data.readShort().toString(2));
+ break;
+ // Масштаб по U
+ case 0xA354:
+ map.scaleU = data.readFloat();
+ break;
+ // Масштаб по V
+ case 0xA356:
+ map.scaleV = data.readFloat();
+ break;
+ // Смещение по U
+ case 0xA358:
+ map.offsetU = data.readFloat();
+ break;
+ // Смещение по V
+ case 0xA35A:
+ map.offsetV = data.readFloat();
+ break;
+ // Угол поворота
+ case 0xA35C:
+ map.rotation = data.readFloat();
+ break;
+ }
+
+ parseMapChunk(materialName, map, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size);
+ }
+
+ /**
+ *
+ */
+ private function buildContent():void {
+ // Формируем связи объектов
+ _content = new Object3D("container_3ds");
+
+ // Расчёт матриц текстурных материалов
+ buildMaterialMatrices();
+
+ var i:uint;
+ var length:uint;
+ var objectName:String;
+ var objectData:ObjectData;
+ var mesh:Mesh;
+ // В сцене есть иерархически связанные оьъекты и (или) указаны данные о трансформации объектов.
+ if (animationDatas != null) {
+ if (objectDatas != null) {
+ length = animationDatas.length;
+ for (i = 0; i < length; i++) {
+ var animationData:AnimationData = animationDatas[i];
+ objectName = animationData.objectName;
+ objectData = objectDatas[objectName];
+ // Для проверки, не представлен ли один объект в нескольких экземплярах, каждый раз проходим до конца списка секций анимации,
+ // проверяя совпадение очередного имени объекта с текущим именем. Если обнаруживается совпадение, в список объектных данных
+ // добавляется новая запись. Имя нового объекта формируется из имени оригинального объекта с добавлением значения счётчика.
+ if (objectData != null) {
+ var nameCounter:uint = 2;
+ for (var j:uint = i + 1; j < length; j++) {
+ var animationData2:AnimationData = animationDatas[j];
+ if (objectName == animationData2.objectName) {
+ // Найдено совпадение имени объекта в проверяемой секции анимации. Создаём описание нового экземпляра объекта.
+ var newName:String = objectName + nameCounter;
+ animationData2.objectName = newName;
+ var newObjectData:ObjectData = new ObjectData();
+ newObjectData.name = newName;
+ if (objectData.vertices != null) {
+ newObjectData.vertices = new Array().concat(objectData.vertices);
+ }
+ if (objectData.uvs != null) {
+ newObjectData.uvs = new Array().concat(objectData.uvs);
+ }
+ if (objectData.matrix != null) {
+ newObjectData.matrix = objectData.matrix.clone();
+ }
+ if (objectData.faces != null) {
+ newObjectData.faces = new Array().concat(objectData.faces);
+ }
+ if (objectData.surfaces != null) {
+ newObjectData.surfaces = objectData.surfaces.clone();
+ }
+ objectDatas[newName] = newObjectData;
+ nameCounter++;
+ }
+ }
+ }
+
+ if (objectData != null && objectData.vertices != null) {
+ // Создание полигонального объекта
+ mesh = new Mesh(objectName);
+ animationData.object = mesh;
+ setBasicObjectProperties(animationData);
+ buildMesh(mesh, objectData, animationData);
+ } else {
+ // Создание пустого 3д-объекта
+ var object:Object3D = new Object3D(objectName);
+ animationData.object = object;
+ setBasicObjectProperties(animationData);
+ }
+ }
+ // Создание дерева объектов
+ buildHierarchy();
+ }
+ } else {
+ // В сцене нет иерархически связанных объектов и не заданы трансформации для объектов. В контейнер добавляются только полигональные объекты.
+ for (objectName in objectDatas) {
+ objectData = objectDatas[objectName];
+ if (objectData.vertices != null) {
+ // Меш
+ mesh = new Mesh(objectName);
+ buildMesh(mesh, objectData, null);
+ _content.addChild(mesh);
+ }
+ }
+ }
+ }
+
+ /**
+ * Расчитывает матрицы преобразования UV-координат для всех текстурных материалов.
+ */
+ private function buildMaterialMatrices():void {
+ var materialData:MaterialData;
+ for (var materialName:String in materialDatas) {
+ materialData = materialDatas[materialName];
+ var materialMatrix:Matrix = new Matrix();
+ var mapData:MapData = materialData.diffuseMap;
+ if (mapData != null) {
+ var rot:Number = MathUtils.toRadian(mapData.rotation);
+ var rotSin:Number = Math.sin(rot);
+ var rotCos:Number = Math.cos(rot);
+ materialMatrix.translate(-mapData.offsetU, mapData.offsetV);
+ materialMatrix.translate(-0.5, -0.5);
+ materialMatrix.rotate(-rot);
+ materialMatrix.scale(mapData.scaleU, mapData.scaleV);
+ materialMatrix.translate(0.5, 0.5);
+ }
+ materialData.matrix = materialMatrix;
+ }
+ }
+
+ /**
+ * Устанавливает базовые свойства трёхмерных объектов.
+ *
+ * @param animationData
+ */
+ private function setBasicObjectProperties(animationData:AnimationData):void {
+ var object:Object3D = animationData.object;
+ if (animationData.position != null) {
+ object.x = animationData.position.x*_scale;
+ object.y = animationData.position.y*_scale;
+ object.z = animationData.position.z*_scale;
+ }
+ if (animationData.rotation != null) {
+ object.rotationX = animationData.rotation.x;
+ object.rotationY = animationData.rotation.y;
+ object.rotationZ = animationData.rotation.z;
+ }
+ if (animationData.scale != null) {
+ object.scaleX = animationData.scale.x;
+ object.scaleY = animationData.scale.y;
+ object.scaleZ = animationData.scale.z;
+ }
+ object.mobility = _mobility;
+ }
+
+ /**
+ * Создаёт геометрию, поверхности и материалы объекта.
+ *
+ * @param mesh
+ * @param objectData
+ * @param animationData
+ */
+ private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData):void {
+ // Добавляем вершины
+ var i:int;
+ var key:*;
+ var vertex:Vertex;
+ var face:Face;
+ var length:int = objectData.vertices.length;
+ for (i = 0; i < length; i++) {
+ var vertexData:Point3D = objectData.vertices[i];
+ objectData.vertices[i] = mesh.createVertex(vertexData.x, vertexData.y, vertexData.z, i);
+ }
+ // Коррекция вершин
+ if (animationData != null) {
+ // Инвертируем матрицу
+ objectData.matrix.invert();
+ // Вычитаем точку привязки из смещения матрицы
+ if (animationData.pivot != null) {
+ objectData.matrix.d -= animationData.pivot.x;
+ objectData.matrix.h -= animationData.pivot.y;
+ objectData.matrix.l -= animationData.pivot.z;
+ }
+ // Трансформируем вершины
+ for (key in mesh._vertices) {
+ vertex = mesh._vertices[key];
+ vertex._coords.transform(objectData.matrix);
+ }
+ }
+ // Преобразование единиц измерения
+ for (key in mesh._vertices) {
+ vertex = mesh._vertices[key];
+ vertex._coords.multiply(_scale);
+ }
+ // Добавляем грани
+ length = (objectData.faces == null) ? 0 : objectData.faces.length;
+ for (i = 0; i < length; i++) {
+ var faceData:FaceData = objectData.faces[i];
+ face = mesh.createFace([objectData.vertices[faceData.a], objectData.vertices[faceData.b], objectData.vertices[faceData.c]], i);
+ if (objectData.uvs != null) {
+ face.aUV = objectData.uvs[faceData.a];
+ face.bUV = objectData.uvs[faceData.b];
+ face.cUV = objectData.uvs[faceData.c];
+ }
+ }
+ // Добавляем поверхности
+ if (objectData.surfaces != null) {
+ for (var surfaceId:String in objectData.surfaces) {
+ var materialData:MaterialData = materialDatas[surfaceId];
+ var surfaceData:SurfaceData = objectData.surfaces[surfaceId];
+ var surface:Surface = mesh.createSurface(surfaceData.faces, surfaceId);
+ if (materialData.diffuseMap != null) {
+ // Текстурный материал
+ length = surfaceData.faces.length;
+ for (i = 0; i < length; i++) {
+ face = mesh.getFaceById(surfaceData.faces[i]);
+ if (face._aUV != null && face._bUV != null && face._cUV != null) {
+ var m:Matrix = materialData.matrix;
+ var x:Number = face.aUV.x;
+ var y:Number = face.aUV.y;
+ face._aUV.x = m.a*x + m.b*y + m.tx;
+ face._aUV.y = m.c*x + m.d*y + m.ty;
+ x = face._bUV.x;
+ y = face._bUV.y;
+ face._bUV.x = m.a*x + m.b*y + m.tx;
+ face._bUV.y = m.c*x + m.d*y + m.ty;
+ x = face._cUV.x;
+ y = face._cUV.y;
+ face._cUV.x = m.a*x + m.b*y + m.tx;
+ face._cUV.y = m.c*x + m.d*y + m.ty;
+ }
+ }
+ // Материал создаётся с нулевой текстурой, которая в дальнейшем должна быть установлена внешними средствами
+ surface.material = new TextureMaterial(null, 1 - materialData.transparency/100, _repeat, _smooth, _blendMode, -1, 0, _precision);
+ } else {
+ surface.material = new FillMaterial(materialDatas[surfaceId].color, 1 - materialData.transparency/100);
+ }
+ }
+ } else {
+ // Поверхность по умолчанию
+ var defaultSurface:Surface = mesh.createSurface();
+ // Добавляем грани
+ for (var faceId:String in mesh._faces) {
+ defaultSurface.addFace(mesh._faces[faceId]);
+ }
+ defaultSurface.material = new WireMaterial(0, 0x7F7F7F);
+ }
+ }
+
+ /**
+ * Создаёт дерево объектов сцены.
+ */
+ private function buildHierarchy():void {
+ var len:Number = animationDatas.length;
+ for (var i:int = 0; i < len; i++) {
+ var animData:AnimationData = animationDatas[i];
+ if (animData.parentIndex == 0xFFFF) {
+ _content.addChild(animData.object);
+ } else {
+ AnimationData(animationDatas[animData.parentIndex]).object.addChild(animData.object);
+ }
+ }
+ }
+
+ /**
+ * Считывает строку, заканчивающуюся на нулевой байт.
+ *
+ * @param index
+ * @return
+ */
+ private function getString(index:uint):String {
+ data.position = index;
+ var charCode:uint;
+ var res:String = "";
+ while ((charCode = data.readByte()) != 0) {
+ res += String.fromCharCode(charCode);
+ }
+ return res;
+ }
+
+ /**
+ *
+ * @param angle
+ * @param x
+ * @param z
+ * @param y
+ * @return
+ */
+ private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Point3D {
+ var res:Point3D = new Point3D();
+ var s:Number = Math.sin(angle);
+ var c:Number = Math.cos(angle);
+ var t:Number = 1 - c;
+ var k:Number = x*y*t + z*s;
+ var half:Number;
+ if (k >= 1) {
+ half = angle/2;
+ res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half));
+ res.y = -Math.PI/2;
+ res.x = 0;
+ return res;
+ }
+ if (k <= -1) {
+ half = angle/2;
+ res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half));
+ res.y = Math.PI/2;
+ res.x = 0;
+ return res;
+ }
+ res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t);
+ res.y = -Math.asin(x*y*t + z*s);
+ res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t);
+ return res;
+ }
+
+ }
+}
+
+import alternativa.engine3d.core.Object3D;
+import alternativa.types.Matrix3D;
+import alternativa.types.Point3D;
+
+import flash.geom.Matrix;
+import alternativa.types.Map;
+import flash.utils.ByteArray;
+
+class MaterialData {
+ public var name:String;
+ public var color:uint;
+ public var specular:uint;
+ public var glossiness:uint;
+ public var transparency:uint;
+ public var diffuseMap:MapData;
+ public var opacityMap:MapData;
+ //public var normalMap:MapData;
+ public var matrix:Matrix;
+}
+
+class MapData {
+ public var filename:String;
+ public var scaleU:Number = 1;
+ public var scaleV:Number = 1;
+ public var offsetU:Number = 0;
+ public var offsetV:Number = 0;
+ public var rotation:Number = 0;
+}
+
+class ObjectData {
+ public var name:String;
+ public var vertices:Array;
+ public var uvs:Array;
+ public var matrix:Matrix3D;
+ public var faces:Array;
+ public var surfaces:Map;
+}
+
+class FaceData {
+ public var a:uint;
+ public var b:uint;
+ public var c:uint;
+}
+
+class SurfaceData {
+ public var materialName:String;
+ public var faces:Array;
+}
+
+class AnimationData {
+ public var objectName:String;
+ public var object:Object3D;
+ public var parentIndex:uint;
+ public var pivot:Point3D;
+ public var position:Point3D;
+ public var rotation:Point3D;
+ public var scale:Point3D;
+}
+
+/**
+ *
+ */
+class ChunkInfo {
+ public var id:uint;
+ public var size:uint;
+ public var dataSize:uint;
+ public var dataPosition:uint;
+ public var nextChunkPosition:uint;
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/ParserOBJ.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/ParserOBJ.as
new file mode 100644
index 0000000..b6d1d86
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/ParserOBJ.as
@@ -0,0 +1,374 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.alternativa3d;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.types.Point3D;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Класс позволяет разобрать данные в формате OBJ. После обработки данных контейнер с трёхмерными объектами становится доступным через свойство content, а свойство
+ * materialFileNames содержит список имён файлов библиотек материалов. Каждый созданный объект содержит поверхности, идентификаторы которых совпадают с именами
+ * назначенных материалов, но сами материалы поверхностей не создаются, т.к. они будут известны только после загрузки библиотек материалов.
+ *
+ * При разборе данных распознаются следующие ключевые слова формата OBJ:
+ *
+ *
+ *
+ *
+ * Ключевое слово
+ * Описание
+ * Действие парсера
+ *
+ * o object_name
+ * Объявление нового объекта с именем object_name
+ * Если для текущего объекта были определены грани, то команда создаёт новый текущий объект с указанным именем,
+ * иначе у текущего объекта просто меняется имя на указанное.
+ *
+ *
+ * v x y z
+ * Объявление вершины с координатами x y z
+ * Вершина помещается в общий список вершин сцены для дальнейшего использования
+ *
+ *
+ * vt u [v]
+ * Объявление текстурной вершины с координатами u v
+ * Вершина помещается в общий список текстурных вершин сцены для дальнейшего использования
+ *
+ *
+ * f v0[/vt0] v1[/vt1] ... vN[/vtN]
+ * Объявление грани, состоящей из указанных вершин и опционально имеющую заданные текстурные координаты для вершин.
+ * Грань добавляется к текущему активному объекту. Если есть активный материал, то грань также добавляется в поверхность
+ * текущего объекта, соответствующую текущему материалу.
+ *
+ *
+ * usemtl material_name
+ * Установка текущего материала с именем material_name
+ * С момента установки текущего материала все грани, создаваемые в текущем объекте будут помещаться в поверхность,
+ * соотвествующую этому материалу и имеющую идентификатор, совпадающий с его именем.
+ *
+ *
+ * mtllib file1 file2 ...
+ * Объявление файлов, содержащих определения материалов
+ * Имена файлов добавляются в список.
+ *
+ *
+ */
+ public class ParserOBJ {
+
+ private static const COMMENT_CHAR:String = "#";
+
+ private static const CMD_OBJECT_NAME:String = "o";
+ private static const CMD_GROUP_NAME:String = "g";
+ private static const CMD_VERTEX:String = "v";
+ private static const CMD_TEXTURE_VERTEX:String = "vt";
+ private static const CMD_FACE:String = "f";
+ private static const CMD_MATERIAL_LIB:String = "mtllib";
+ private static const CMD_USE_MATERIAL:String = "usemtl";
+
+ private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/;
+ private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/;
+ private static const REGEXP_SPLIT_LINE:RegExp = /\s+/;
+
+ private var objectKey:String = CMD_OBJECT_NAME;
+
+ // Контейнер, содержащий все определённые в OBJ-файле объекты
+ private var _content:Object3D;
+ // Текущий конструируемый объект
+ private var currentObject:Mesh;
+ // Стартовый индекс вершины в глобальном массиве вершин для текущего объекта
+ private var vIndexStart:int = 0;
+ // Стартовый индекс текстурной вершины в глобальном массиве текстурных вершин для текущего объекта
+ private var vtIndexStart:int = 0;
+ // Глобальный массив вершин, определённых во входном файле
+ private var globalVertices:Array;
+ // Глобальный массив текстурных вершин, определённых во входном файле
+ private var globalTextureVertices:Array;
+ // Имя текущего активного материала. Если значение равно null, то активного материала нет.
+ private var currentMaterialName:String;
+ // Массив граней текущего объекта, которым назначен текущий материал
+ private var materialFaces:Array;
+ // Массив имён файлов, содержащих определения материалов
+ private var _materialFileNames:Array;
+ // Мобильность объектов
+ private var _mobility:int;
+ // Коэффициент пересчёта единиц измерения сцены. Размеры создаваемых объектов будут умножены на заданное значение.
+ private var _scale:Number = 1;
+ // Флаг поворота объектов на 90 градусов по оси X
+ private var _rotateModel:Boolean;
+
+ /**
+ * Создаёт новый экземпляр класса.
+ */
+ public function ParserOBJ() {
+ }
+
+ /**
+ * Коэффициент пересчёта единиц измерения сцены. Размеры создаваемых объектов будут умножены на заданное значение.
+ * @default 1
+ */
+ public function get scale():Number {
+ return _scale;
+ }
+
+ /**
+ * @private
+ */
+ public function set scale(value:Number):void {
+ _scale = value;
+ }
+
+ /**
+ * При установленном значении true объекты будут определяться не ключевым словом "o", а словом "g". Это может быть полезно, т.к. некоторые OBJ-експортёры могут
+ * использовать слово "g" для определения объектов в OBJ-файле.
+ *
+ * @default false
+ */
+ public function get objectsAsGroups():Boolean {
+ return objectKey == CMD_GROUP_NAME;
+ }
+
+ /**
+ * @private
+ */
+ public function set objectsAsGroups(value:Boolean):void {
+ objectKey = value ? CMD_GROUP_NAME : CMD_OBJECT_NAME;
+ }
+
+ /**
+ * Уровень мобильности для загруженных объектов.
+ * @default 0
+ * @see alternativa.engine3d.core.Object3D#mobility
+ */
+ public function get mobility():int {
+ return _mobility;
+ }
+
+ /**
+ * @private
+ */
+ public function set mobility(value:int):void {
+ _mobility = value;
+ }
+
+ /**
+ * При установленном значении true выполняется преобразование координат геометрических вершин посредством
+ * поворота на 90 градусов относительно оси X. Смысл флага в преобразовании системы координат, в которой направление вверх определяется осью Y,
+ * в систему координат, использующуюся в Alternativa3D (вверх направлена ось Z).
+ *
+ * @default false
+ */
+ public function get rotateModel():Boolean {
+ return _rotateModel;
+ }
+
+ /**
+ * @private
+ */
+ public function set rotateModel(value:Boolean):void {
+ _rotateModel = value;
+ }
+
+ /**
+ * Контейнер, содержащий все определённые в OBJ-файле объекты.
+ */
+ public function get content():Object3D {
+ return _content;
+ }
+
+ /**
+ * Массив имён файлов, содержащих библиотеки материалов.
+ */
+ public function get materialFileNames():Array {
+ return _materialFileNames;
+ }
+
+ /**
+ * Разбирает содержимое OBJ-файла и формирует объекты.
+ *
+ * @param data данные OBJ-файла
+ */
+ public function parse(data:String):void {
+ globalVertices = new Array();
+ globalTextureVertices = new Array();
+ _materialFileNames = new Array();
+
+ _content = new Object3D();
+ createNewObject("");
+
+ var lines:Array = data.split(REGEXP_SPLIT_FILE);
+ lines.forEach(parseLine);
+ moveFacesToSurface();
+ }
+
+ /**
+ *
+ */
+ private function createNewObject(objectName:String):void {
+ currentObject = new Mesh(objectName);
+ currentObject.mobility = _mobility;
+ _content.addChild(currentObject);
+ }
+
+ /**
+ * Разбирает строку входного файла.
+ */
+ private function parseLine(line:String, index:int, lines:Array):void {
+ line = line.replace(REGEXP_TRIM,"$1");
+ if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) {
+ return;
+ }
+ var parts:Array = line.split(REGEXP_SPLIT_LINE);
+ switch (parts[0]) {
+ // Объявление нового объекта
+ case objectKey:
+ defineObject(parts[1]);
+ break;
+ // Объявление вершины
+ case CMD_VERTEX:
+ globalVertices.push(new Point3D(Number(parts[1])*_scale, Number(parts[2])*_scale, Number(parts[3])*_scale));
+ break;
+ // Объявление текстурной вершины
+ case CMD_TEXTURE_VERTEX:
+ globalTextureVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3])));
+ break;
+ // Объявление грани
+ case CMD_FACE:
+ createFace(parts);
+ break;
+ case CMD_MATERIAL_LIB:
+ storeMaterialFileNames(parts);
+ break;
+ case CMD_USE_MATERIAL:
+ setNewMaterial(parts);
+ break;
+ }
+ }
+
+ /**
+ * Определяет новый объект.
+ *
+ * @param objectName имя объекта
+ */
+ private function defineObject(objectName:String):void {
+ if (currentObject._faces.length == 0) {
+ // Если у текущего объекта нет граней, то он остаётся текущим, но меняется имя
+ currentObject.name = objectName;
+ } else {
+ // Если у текущего объекта есть грани, то обявление нового имени создаёт новый объект
+ moveFacesToSurface();
+ createNewObject(objectName);
+ }
+ vIndexStart = globalVertices.length;
+ vtIndexStart = globalTextureVertices.length;
+ }
+
+ /**
+ * Создаёт грани в текущем объекте.
+ *
+ * @param parts массив, содержащий индексы вершин грани, начиная с элемента с индексом 1
+ */
+ private function createFace(parts:Array):void {
+ // Стартовый индекс вершины в объекте для добавляемой грани
+ var startVertexIndex:int = currentObject._vertices.length;
+ // Создание вершин в объекте
+ var faceVertexCount:int = parts.length - 1;
+ var vtIndices:Array = new Array(3);
+ // Массив идентификаторов вершин грани
+ var faceVertices:Array = new Array(faceVertexCount);
+ for (var i:int = 0; i < faceVertexCount; i++) {
+ var indices:Array = parts[i + 1].split("/");
+ // Создание вершины
+ var vIdx:int = int(indices[0]);
+ // Если индекс положительный, то его значение уменьшается на единицу, т.к. в obj формате индексация начинается с 1.
+ // Если индекс отрицательный, то выполняется смещение на его значение назад от стартового глобального индекса вершин для текущего объекта.
+ var actualIndex:int = vIdx > 0 ? vIdx - 1 : (vIndexStart + vIdx);
+
+ var vertex:Vertex = currentObject._vertices[actualIndex];
+ // Если вершины нет в объекте, она добавляется
+ if (vertex == null) {
+ var p:Point3D = globalVertices[actualIndex];
+ if (_rotateModel) {
+ // В формате obj направление "вверх" совпадает с осью Y, поэтому выполняется поворот объекта на 90 градусов по оси X
+ vertex = currentObject.createVertex(p.x, -p.z, p.y, actualIndex);
+ } else {
+ vertex = currentObject.createVertex(p.x, p.y, p.z, actualIndex);
+ }
+ }
+ faceVertices[i] = vertex;
+
+ // Запись индекса текстурной вершины
+ if (i < 3) {
+ vtIndices[i] = int(indices[1]);
+ }
+ }
+ // Создание грани
+ var face:Face = currentObject.createFace(faceVertices, currentObject._faces.length);
+ // Установка uv координат
+ if (vtIndices[0] != 0) {
+ p = globalTextureVertices[vtIndices[0] - 1];
+ face.aUV = new Point(p.x, p.y);
+ p = globalTextureVertices[vtIndices[1] - 1];
+ face.bUV = new Point(p.x, p.y);
+ p = globalTextureVertices[vtIndices[2] - 1];
+ face.cUV = new Point(p.x, p.y);
+ }
+ // Если есть активный материал, то грань заносится в массив для последующего формирования поверхности в объекте
+ if (currentMaterialName != null) {
+ materialFaces.push(face);
+ }
+ }
+
+ /**
+ * Сохраняет имена библиотек материалов.
+ *
+ * @param parts массив, содержащий имена файлов материалов, начиная с элемента с индексом 1
+ */
+ private function storeMaterialFileNames(parts:Array):void {
+ for (var i:int = 1; i < parts.length; i++) {
+ _materialFileNames.push(parts[i]);
+ }
+ }
+
+ /**
+ * Устанавливает новый текущий материал.
+ *
+ * @param parts массив, во втором элементе которого содержится имя материала
+ */
+ private function setNewMaterial(parts:Array):void {
+ // Все сохранённые грани добавляются в соответствующую поверхность текущего объекта
+ moveFacesToSurface();
+ // Установка нового текущего материала
+ currentMaterialName = parts[1];
+ }
+
+ /**
+ * Добавляет все грани с текущим материалом в поверхность с идентификатором, совпадающим с именем материала.
+ */
+ private function moveFacesToSurface():void {
+ if (currentMaterialName != null && materialFaces.length > 0) {
+ if (currentObject.hasSurface(currentMaterialName)) {
+ // При наличии поверхности с таким идентификатором, грани добавляются в неё
+ var surface:Surface = currentObject.getSurfaceById(currentMaterialName);
+ for each (var face:* in materialFaces) {
+ surface.addFace(face);
+ }
+ } else {
+ // При отсутствии поверхности с таким идентификатором, создатся новая поверхность
+ currentObject.createSurface(materialFaces, currentMaterialName);
+ }
+ }
+ if (materialFaces == null) {
+ materialFaces = new Array();
+ } else {
+ materialFaces.length = 0;
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsBatchLoader.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsBatchLoader.as
new file mode 100644
index 0000000..c21dae9
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsBatchLoader.as
@@ -0,0 +1,218 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.loaders.events.LoaderEvent;
+ import alternativa.engine3d.loaders.events.LoaderProgressEvent;
+ import alternativa.types.Map;
+
+ import flash.display.BitmapData;
+ import flash.events.ErrorEvent;
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IOErrorEvent;
+ import flash.events.ProgressEvent;
+ import flash.system.LoaderContext;
+
+ /**
+ * Событие рассылается в начале очередного этапа загрузки сцены.
+ *
+ * @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_START
+ */
+ [Event (name="loadingStart", type="alternativa.engine3d.loaders.events.LoaderEvent")]
+ /**
+ * Событие рассылается в процессе получения данных во время загрузки.
+ *
+ * @eventType alternativa.engine3d.loaders.events.LoaderProgressEvent.LOADING_PROGRESS
+ */
+ [Event (name="loadingProgress", type="alternativa.engine3d.loaders.events.LoaderProgressEvent")]
+ /**
+ * Событие рассылается после окончания очередного этапа загрузки сцены.
+ *
+ * @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_COMPLETE
+ */
+ [Event (name="loadingComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")]
+ /**
+ * Событие рассылается после окончания загрузки сцены.
+ *
+ * @eventType flash.events.Event.COMPLETE
+ */
+ [Event (name="complete", type="flash.events.Event")]
+ /**
+ * Тип события, рассылаемого при возникновении ошибки загрузки текстуры.
+ *
+ * @eventType flash.events.IOErrorEvent.IO_ERROR
+ */
+ [Event (name="ioError", type="flash.events.IOErrorEvent")]
+
+ /**
+ * @private
+ * Пакетный загрузчик текстур. Используется загрузчиками внешних сцен для получения битмапов текстур материалов.
+ */
+ public class TextureMapsBatchLoader extends EventDispatcher {
+ /**
+ * Текстура-заглушка для замены незагруженных текстур.
+ */
+ public static var stubBitmapData:BitmapData;
+
+ // Загрузчик файлов текстур.
+ private var loader:TextureMapsLoader;
+ // Контекст безопасности загрузчика.
+ private var loaderContext:LoaderContext;
+ // Базовый URL файлов текстур.
+ private var baseUrl:String;
+ // Пакет с описанием текстур материалов.
+ private var batch:Map;
+ // Список имён материалов.
+ private var materialNames:Array;
+ // Общее количество файлов текстур.
+ private var totalFiles:int;
+ // Номер текущего
+ private var currFileIndex:int;
+ // Индекс текущего материала.
+ private var materialIndex:int;
+ // Результирующий список битмапов для каждого материала.
+ private var _textures:Map;
+
+ /**
+ * Создаёт новый экземпляр загрузчика.
+ */
+ public function TextureMapsBatchLoader() {
+ }
+
+ /**
+ * Результирующий список битмапов для каждого материала. Ключами являются имена материалов, значениями -- объекты класса BitmapData.
+ */
+ public function get textures():Map {
+ return _textures;
+ }
+
+ /**
+ * Метод для получения текстуры-заглушки.
+ *
+ * @return текстура-заглушка для замещения незагруженных текстур
+ */
+ private function getStubBitmapData():BitmapData {
+ if (stubBitmapData == null) {
+ var size:uint = 20;
+ stubBitmapData = new BitmapData(size, size, false, 0);
+ for (var i:uint = 0; i < size; i++) {
+ for (var j:uint = 0; j < size; j += 2) {
+ stubBitmapData.setPixel((i%2) ? j : (j + 1), i, 0xFF00FF);
+ }
+ }
+ }
+ return stubBitmapData;
+ }
+
+ /**
+ * Прекращает текущую загрузку.
+ */
+ public function close():void {
+ if (loader != null) {
+ loader.close();
+ }
+ }
+
+ /**
+ * Очищает внутренние ссылки на объекты.
+ */
+ private function clean():void {
+ loaderContext = null;
+ batch = null;
+ materialNames = null;
+ }
+
+ /**
+ * Очищает ссылку на загруженный список текстур материалов.
+ */
+ public function unload():void {
+ _textures = null;
+ }
+
+ /**
+ * Загружает текстуры для материалов.
+ *
+ * @param baseURL базовый URL файлов текстур
+ * @param batch массив соответствий имён текстурных материалов и их текстур, описываемых объектами класса TextureMapsInfo
+ * @param loaderContext LoaderContext для загрузки файлов текстур
+ */
+ public function load(baseURL:String, batch:Map, loaderContext:LoaderContext):void {
+ this.baseUrl = baseURL;
+ this.batch = batch;
+ this.loaderContext = loaderContext;
+
+ if (loader == null) {
+ loader = new TextureMapsLoader();
+ loader.addEventListener(LoaderEvent.LOADING_START, onTextureLoadingStart);
+ loader.addEventListener(LoaderEvent.LOADING_COMPLETE, onTextureLoadingComplete);
+ loader.addEventListener(ProgressEvent.PROGRESS, onProgress);
+ loader.addEventListener(Event.COMPLETE, onMaterialTexturesLoadingComplete);
+ loader.addEventListener(IOErrorEvent.IO_ERROR, onMaterialTexturesLoadingComplete);
+ } else {
+ close();
+ }
+ // Получение массива имён материалов и подсчёт количества файлов текстур
+ totalFiles = 0;
+ materialNames = new Array();
+ for (var materialName:String in batch) {
+ materialNames.push(materialName);
+ var info:TextureMapsInfo = batch[materialName];
+ totalFiles += info.opacityMapFileName == null ? 1 : 2;
+ }
+ // Старт загрузки
+ currFileIndex = 0;
+ materialIndex = 0;
+ _textures = new Map();
+ loadNextTextureFile();
+ }
+
+ /**
+ * Загружает очередной файл с текстурой.
+ */
+ private function loadNextTextureFile():void {
+ var info:TextureMapsInfo = batch[materialNames[materialIndex]];
+ loader.load(baseUrl + info.diffuseMapFileName, info.opacityMapFileName == null ? null : baseUrl + info.opacityMapFileName, loaderContext);
+ }
+
+ /**
+ * Ретранслирует событие начала загрузки текстуры.
+ */
+ private function onTextureLoadingStart(e:Event):void {
+ dispatchEvent(e);
+ }
+
+ /**
+ * Ретранслирует событие окончания загрузки текстуры.
+ */
+ private function onTextureLoadingComplete(e:Event):void {
+ dispatchEvent(e);
+ currFileIndex++;
+ }
+
+ /**
+ * Рассылает событие прогресса загрузки файлов.
+ */
+ private function onProgress(e:ProgressEvent):void {
+ dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADING_PROGRESS, LoadingStage.TEXTURE, totalFiles, currFileIndex, e.bytesLoaded, e.bytesTotal));
+ }
+
+ /**
+ * Обрабатывает завершение загрузки текстуры материала.
+ */
+ private function onMaterialTexturesLoadingComplete(e:Event):void {
+ // В зависимости от полученного события устанавливается загруженное изображение или битмап-заглушка
+ if (e is IOErrorEvent) {
+ _textures.add(materialNames[materialIndex], getStubBitmapData());
+ dispatchEvent(e);
+ } else {
+ _textures.add(materialNames[materialIndex], loader.bitmapData);
+ }
+ if ((++materialIndex) == materialNames.length) {
+ // Загружены текстуры для всех материалов, отправляется сообщение о завершении
+ clean();
+ dispatchEvent(new Event(Event.COMPLETE));
+ } else {
+ loadNextTextureFile();
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsInfo.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsInfo.as
new file mode 100644
index 0000000..d2bc121
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsInfo.as
@@ -0,0 +1,21 @@
+package alternativa.engine3d.loaders {
+
+ /**
+ * Структура для хранения имён файла диффузной текстуры и файла карты прозрачности.
+ */
+ public class TextureMapsInfo {
+ /**
+ * Имя файла диффузной текстуры.
+ */
+ public var diffuseMapFileName:String;
+ /**
+ * Имя файла карты прозрачности.
+ */
+ public var opacityMapFileName:String;
+
+ public function TextureMapsInfo(diffuseMapFileName:String = null, opacityMapFileName:String = null) {
+ this.diffuseMapFileName = diffuseMapFileName;
+ this.opacityMapFileName = opacityMapFileName;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsLoader.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsLoader.as
new file mode 100644
index 0000000..a71107b
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/TextureMapsLoader.as
@@ -0,0 +1,215 @@
+package alternativa.engine3d.loaders {
+ import alternativa.engine3d.loaders.events.LoaderEvent;
+
+ import flash.display.Bitmap;
+ import flash.display.BitmapData;
+ import flash.display.BitmapDataChannel;
+ import flash.display.BlendMode;
+ import flash.display.Loader;
+ import flash.errors.IOError;
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IOErrorEvent;
+ import flash.events.ProgressEvent;
+ import flash.geom.Matrix;
+ import flash.geom.Point;
+ import flash.net.URLRequest;
+ import flash.system.LoaderContext;
+
+ /**
+ * Событие рассылается в начале загрузки каждой текстуры.
+ *
+ * @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_START
+ */
+ [Event (name="loadingStart", type="alternativa.engine3d.loaders.events.LoaderEvent")]
+ /**
+ * Событие рассылается после окончания загрузки каждой текстуры.
+ *
+ * @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_COMPLETE
+ */
+ [Event (name="loadingComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")]
+ /**
+ * Событие рассылается после завершения загрузки.
+ *
+ * @eventType flash.events.Event.COMPLETE
+ */
+ [Event (name="complete", type="flash.events.Event")]
+ /**
+ * Событие рассылается при возникновении ошибки, приводящей к прерыванию загрузки.
+ *
+ * @eventType flash.events.IOErrorEvent.IO_ERROR
+ */
+ [Event (name="ioError", type="flash.events.IOErrorEvent")]
+ /**
+ * Событие рассылается в процессе получения данных во время загрузки.
+ *
+ * @eventType flash.events.ProgressEvent.PROGRESS
+ */
+ [Event (name="progress", type="flash.events.ProgressEvent")]
+ /**
+ * Загрузчик битмапов диффузной текстуры и карты прозрачности.
+ * @private
+ */
+ public class TextureMapsLoader extends EventDispatcher {
+
+ private static const STATE_IDLE:int = 0;
+ private static const STATE_LOADING_DIFFUSE_MAP:int = 1;
+ private static const STATE_LOADING_ALPHA_MAP:int = 2;
+
+ private var _bitmapData:BitmapData;
+ private var bitmapLoader:Loader;
+ private var alphaTextureUrl:String;
+ private var loaderContext:LoaderContext;
+
+ private var loaderState:int = STATE_IDLE;
+
+ /**
+ * Создаёт новый экземпляр класса. Если параметр diffuseTextureUrl не равен null, конструктор запускает процесс
+ * загрузки.
+ *
+ * @param diffuseTextureUrl URL файла диффузной карты
+ * @param alphaTextureUrl URL файла карты прозрачности
+ * @param loaderContext LoaderContext, используемый для загрузки файлов
+ */
+ public function TextureMapsLoader(diffuseTextureUrl:String = null, alphaTextureUrl:String = null, loaderContext:LoaderContext = null) {
+ if (diffuseTextureUrl != null) {
+ load(diffuseTextureUrl, alphaTextureUrl, loaderContext);
+ }
+ }
+
+ /**
+ * Загружает текстурные карты. Если помимо файла диффузной текстуры указан файл карты прозрачности, результирующая текстура будет получена из диффузной карты путём заполнения
+ * её альфа-канала на основе карты прозрачности. Карта прозрачности должна быть задана оттенками серого, при этом белый цвет должен задавать полностью непрозрачную область.
+ *
+ * После завершения загрузки текстура становится доступной через свойство bitmapData.
+ *
+ *
+ * @param diffuseTextureUrl URL файла диффузной карты
+ * @param alphaTextureUrl URL файла карты прозрачности
+ * @param loaderContext LoaderContext, используемый для загрузки файлов
+ *
+ * @see #bitmapData
+ */
+ public function load(diffuseTextureUrl:String, alphaTextureUrl:String = null, loaderContext:LoaderContext = null):void {
+ this.alphaTextureUrl = alphaTextureUrl;
+ this.loaderContext = loaderContext;
+ if (bitmapLoader == null) {
+ bitmapLoader = new Loader();
+ bitmapLoader.contentLoaderInfo.addEventListener(Event.OPEN, onOpen);
+ bitmapLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
+ bitmapLoader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onProgress);
+ bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoadError);
+ } else {
+ close();
+ }
+ startLoading(STATE_LOADING_DIFFUSE_MAP, diffuseTextureUrl);
+ }
+
+ /**
+ *
+ */
+ private function onOpen(e:Event):void {
+ dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_START, LoadingStage.TEXTURE));
+ }
+
+ /**
+ *
+ */
+ private function onProgress(e:Event):void {
+ dispatchEvent(e);
+ }
+
+ /**
+ * Запускает загрузку файла текстуры.
+ *
+ * @param state фаза загрузки
+ * @param url URL загружаемого файла
+ */
+ private function startLoading(state:int, url:String):void {
+ loaderState = state;
+ bitmapLoader.load(new URLRequest(url), loaderContext);
+ }
+
+ /**
+ *
+ */
+ private function onComplete(e:Event):void {
+ dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_COMPLETE, LoadingStage.TEXTURE));
+ switch (loaderState) {
+ case STATE_LOADING_DIFFUSE_MAP:
+ // Загрузилась диффузная текстура. При необходимости загружается карта прозрачности.
+ _bitmapData = Bitmap(bitmapLoader.content).bitmapData;
+ if (alphaTextureUrl != null) {
+ startLoading(STATE_LOADING_ALPHA_MAP, alphaTextureUrl);
+ } else {
+ complete();
+ }
+ break;
+ case STATE_LOADING_ALPHA_MAP:
+ // Загрузилась карта прозрачности. Выполняется копирование прозрачности в альфа-канал диффузной текстуры.
+ var tmpBmp:BitmapData = _bitmapData;
+ _bitmapData = new BitmapData(_bitmapData.width, _bitmapData.height, true, 0);
+ _bitmapData.copyPixels(tmpBmp, tmpBmp.rect, new Point());
+
+ var alpha:BitmapData = Bitmap(bitmapLoader.content).bitmapData;
+ if (_bitmapData.width != alpha.width || _bitmapData.height != alpha.height) {
+ tmpBmp.draw(alpha, new Matrix(_bitmapData.width/alpha.width, 0, 0, _bitmapData.height/alpha.height), null, BlendMode.NORMAL, null, true);
+ alpha.dispose();
+ alpha = tmpBmp;
+ }
+ _bitmapData.copyChannel(alpha, alpha.rect, new Point(), BitmapDataChannel.RED, BitmapDataChannel.ALPHA);
+ alpha.dispose();
+ complete();
+ break;
+ }
+ }
+
+ /**
+ *
+ */
+ private function onLoadError(e:IOErrorEvent):void {
+ loaderState = STATE_IDLE;
+ dispatchEvent(e);
+ }
+
+ /**
+ *
+ */
+ private function complete():void {
+ loaderState = STATE_IDLE;
+ bitmapLoader.unload();
+ dispatchEvent(new Event(Event.COMPLETE));
+ }
+
+ /**
+ * Загруженная текстура.
+ */
+ public function get bitmapData():BitmapData {
+ return _bitmapData;
+ }
+
+ /**
+ * Прекращает текущую загрузку.
+ */
+ public function close():void {
+ if (loaderState != STATE_IDLE) {
+ loaderState = STATE_IDLE;
+ bitmapLoader.close();
+ }
+ unload();
+ }
+
+ /**
+ * Очищает внутренние ссылки на загруженные объекты.
+ */
+ public function unload():void {
+ if (loaderState == STATE_IDLE) {
+ if (bitmapLoader != null) {
+ bitmapLoader.unload();
+ }
+ loaderContext = null;
+ _bitmapData = null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/events/LoaderEvent.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/events/LoaderEvent.as
new file mode 100644
index 0000000..b2fdc3c
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/events/LoaderEvent.as
@@ -0,0 +1,60 @@
+package alternativa.engine3d.loaders.events {
+ import flash.events.Event;
+
+ /**
+ * Рассылается загрузчиками на различных этапах загрузки.
+ */
+ public class LoaderEvent extends Event {
+ /**
+ * Значение свойства type для события loadingStart.
+ * @eventType loadingStart
+ */
+ public static const LOADING_START:String = "loadingStart";
+ /**
+ * Значение свойства type для события loadingComplete.
+ * @eventType loadingComplete
+ */
+ public static const LOADING_COMPLETE:String = "loadingComplete";
+
+ // Этап загрузки
+ private var _loadingStage:int;
+
+ /**
+ * Создаёт новый экземпляр объекта.
+ *
+ * @param type тип события
+ * @param loadingStage этап загрузки
+ */
+ public function LoaderEvent(type:String, loadingStage:int) {
+ super(type);
+ _loadingStage = loadingStage;
+ }
+
+ /**
+ * Этап загрузки. Может принимать значения констант, описанных в классе LoadingStage.
+ *
+ * @see alternativa.engine3d.loaders.LoadingStage
+ */
+ public function get loadingStage():int {
+ return _loadingStage;
+ }
+
+ /**
+ * Создаёт клон объекта.
+ *
+ * @return клонированный объект
+ */
+ override public function clone():Event {
+ return new LoaderEvent(type, _loadingStage);
+ }
+
+ /**
+ * Создаёт строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ override public function toString():String {
+ return "[LoaderEvent type=\"" + type + "\", loadingStage=" + _loadingStage + "]";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/loaders/events/LoaderProgressEvent.as b/Alternativa3D5/5.6/alternativa/engine3d/loaders/events/LoaderProgressEvent.as
new file mode 100644
index 0000000..44eafd5
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/loaders/events/LoaderProgressEvent.as
@@ -0,0 +1,84 @@
+package alternativa.engine3d.loaders.events {
+ import flash.events.Event;
+ import flash.events.ProgressEvent;
+
+ /**
+ * Рассылается загрузчиками для отображения прогресса загрузки.
+ * Свойства bytesLoaded и bytesTotal показывают значения для текущего загружаемого элемента.
+ */
+ public class LoaderProgressEvent extends ProgressEvent {
+ /**
+ * Значение свойства type для события loadingProgress.
+ * @eventType loadingProgress
+ */
+ public static const LOADING_PROGRESS:String = "loadingProgress";
+
+ // Этап загрузки сцены
+ private var _loadingStage:int;
+ // Общее количество загружаемых элементов на текущем этапе загрузки
+ private var _totalItems:int;
+ // Номер элемента на текущем этапе загрузки сцены, с которым связано событие. Нумерация начинается с нуля.
+ private var _currentItem:int;
+
+ /**
+ * Создаёт новый экземпляр события.
+ *
+ * @param type тип события
+ * @param loadingStage этап загрузки сцены, в качестве значения параметра могут быть использованы константы класса LoadingStage
+ * @param totalItems общее количество загружаемых элементов на текущем этапе загрузки
+ * @param currentItem номер элемента на текущем этапе загрузки, с которым связано событие. Нумерация начинается с нуля.
+ * @param bytesLoaded количество загруженных байтов текущего элемента
+ * @param bytesTotal общее количество байтов текущего элемента
+ *
+ * @see alternativa.engine3d.loaders.LoadingStage
+ */
+ public function LoaderProgressEvent(type:String, loadingStage:int, totalItems:int, currentItem:int, bytesLoaded:uint = 0, bytesTotal:uint = 0) {
+ super(type, false, false, bytesLoaded, bytesTotal);
+ _loadingStage = loadingStage;
+ _totalItems = totalItems;
+ _currentItem = currentItem;
+ }
+
+ /**
+ * Этап загрузки сцены. В качестве значения параметра могут быть использованы константы класса LoadingStage.
+ *
+ * @see alternativa.engine3d.loaders.LoadingStage
+ */
+ public function get loadingStage():int {
+ return _loadingStage;
+ }
+
+ /**
+ * Общее количество загружаемых элементов на текущем этапе загрузки.
+ */
+ public function get totalItems():int {
+ return _totalItems;
+ }
+
+ /**
+ * Номер элемента на текущем этапе загрузки, с которым связано событие. Нумерация начинается с нуля.
+ */
+ public function get currentItem():int {
+ return _currentItem;
+ }
+
+ /**
+ * Создаёт клон объекта.
+ *
+ * @return клонированный объект
+ */
+ override public function clone():Event {
+ return new LoaderProgressEvent(type, _loadingStage, _totalItems, _currentItem, bytesLoaded, bytesTotal);
+ }
+
+ /**
+ * Создаёт строковое представление объекта.
+ *
+ * @return строковое представление объекта
+ */
+ override public function toString():String {
+ return "[LoaderProgressEvent type=\"" + type + "\", loadingStage=" + _loadingStage + ", totalItems=" + _totalItems + ", currentItem=" + _currentItem + ", bytesTotal=" + bytesTotal + ", bytesLoaded=" + bytesLoaded + "]";
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/DevMaterial.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/DevMaterial.as
new file mode 100644
index 0000000..e87b3d1
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/DevMaterial.as
@@ -0,0 +1,504 @@
+package alternativa.engine3d.materials {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.BSPNode;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.engine3d.display.Skin;
+ import alternativa.types.Matrix3D;
+ import alternativa.types.Point3D;
+ import alternativa.utils.ColorUtils;
+
+ import flash.display.BlendMode;
+ import flash.display.Graphics;
+
+ use namespace alternativa3d;
+
+ /**
+ * Материал, раскрашивающий грани оттенками заданного цвета в зависимости от значения свойства parameterType.
+ */
+ public class DevMaterial extends SurfaceMaterial {
+ /**
+ * Значение свойства parameterType для отображения глубины полигона в BSP-дереве. Глубина кодируется оттенком базового цвета материала. Оттенок
+ * получается покомпонентным умножением базового цвета на отношение значения уровня полигона в дереве к максимальному значению, задаваемому
+ * свойством maxParameterValue. Уровни нумеруются начиная от корня дерева, таким образом, чем глубже в дереве расположен
+ * полигон, тем он будет светлее.
+ */
+ public static const BSP_DEPTH:int = 0;
+ /**
+ * Значение свойства parameterType для отображения мобильности полигона. Мобильность кодируется оттенком базового цвета материала. Оттенок
+ * получается покомпонентным умножением базового цвета на коэффициент, характеризующий положение мобильности полигона на отрезке,
+ * задаваемом свойствами minMobility и maxMobility. Более мобильные полигоны имеют более светлый оттенок.
+ */
+ public static const MOBILITY:int = 1;
+ /**
+ * Значение свойства parameterType для отображения количества фрагментов грани, которой принадлежит отрисовываемый полигон. Количество фрагментов кодируется
+ * оттенком базового цвета материала. Оттенок получается покомпонентным умножением базового цвета на отношние количества фрагментов текущей грани
+ * к максимальному значению, задаваемому свойством maxParameterValue. Чем больше грань фрагментирована, тем она светлее.
+ */
+ public static const FRAGMENTATION:int = 2;
+ /**
+ * Значение свойства parameterType для отображения граней с отсутствующими UV-координатами. Такие грани отображаются красной заливкой.
+ */
+ public static const NO_UV_MAPPING:int = 3;
+ /**
+ * Значение свойства parameterType для отображения вырожденных полигонов. Вырожденные полигоны отображаются красной заливкой с красной обводкой толщиной пять
+ * пикселей. Для лучшей видимости таких полигонов можно сделать материал полупрозрачным.
+ */
+ public static const DEGENERATE_POLY:int = 4;
+ /**
+ * Значение свойства parameterType для отображения неплоских полигонов.
+ * Значение maxParameterValue в этом режиме определяет максимальное отклонение в геометрии полигона от абсолютно плоского для признания его неплоским.
+ * Неплоские полигоны отображаются красной заливкой с красной обводкой толщиной пять
+ * пикселей. Для лучшей видимости таких полигонов можно сделать материал полупрозрачным.
+ */
+ public static const NON_PLANAR_POLY:int = 5;
+
+ private static const point1:Point3D = new Point3D();
+ private static const point2:Point3D = new Point3D();
+
+ private var _parameterType:int = BSP_DEPTH;
+
+ private var _showNormals:Boolean;
+ private var _normalsColor:uint = 0x00FFFF;
+ private var _minMobility:int = 0;
+ private var _maxMobility:int = 255;
+ private var _maxParameterValue:Number = 20;
+ private var currentColor:int;
+ private var currentWireThickness:Number;
+ private var currentWireColor:uint;
+
+ /**
+ * @private
+ * Цвет
+ */
+ alternativa3d var _color:uint;
+
+ /**
+ * @private
+ * Толщина линий обводки
+ */
+ alternativa3d var _wireThickness:Number;
+
+ /**
+ * @private
+ * Цвет линий обводки
+ */
+ alternativa3d var _wireColor:uint;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param parameterType тип отображаемого параметра
+ * @param color цвет заливки
+ * @param maxParameterValue максимальное значение отображаемого параметра
+ * @param showNormals включение режима отображения нормалей
+ * @param normalsColor цвет нормалей
+ * @param minMobility начало интервала мобильности
+ * @param maxMobility окончание интервала мобильности
+ * @param alpha прозрачность
+ * @param blendMode режим наложения цвета
+ * @param wireThickness толщина линии обводки
+ * @param wireColor цвет линии обводки
+ */
+ public function DevMaterial(parameterType:uint = 0, color:uint = 0xFFFFFF, maxParameterValue:Number = 20, showNormals:Boolean = false, normalsColor:uint = 0x00FFFF, minMobility:int = 0, maxMobility:int = 255, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) {
+ super(alpha, blendMode);
+ _parameterType = parameterType;
+ _color = color;
+ _maxParameterValue = maxParameterValue;
+ _showNormals = showNormals;
+ _normalsColor = normalsColor;
+ _minMobility = minMobility;
+ _maxMobility = maxMobility;
+ _wireThickness = wireThickness;
+ _wireColor = wireColor;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+
+ var i:uint;
+ var point:DrawPoint;
+ var gfx:Graphics = skin.gfx;
+ var perspective:Number;
+
+ setDrawingParameters(skin);
+
+ if (currentColor > -1) {
+ gfx.beginFill(currentColor);
+ }
+ if (currentWireThickness >= 0) {
+ gfx.lineStyle(currentWireThickness, currentWireColor);
+ }
+ point = points[0];
+
+ var minX:Number;
+ var maxX:Number;
+ var minY:Number;
+ var maxY:Number;
+
+ if (camera._orthographic) {
+ minX = point.x;
+ maxX = minX;
+ minY = point.y;
+ maxY = minY;
+ gfx.moveTo(minX, minY);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ if (point.x > maxX) {
+ maxX = point.x;
+ } else if (point.x < minX) {
+ minX = point.x;
+ }
+ if (point.y > maxY) {
+ maxY = point.y;
+ } else if (point.y < minY) {
+ minY = point.y;
+ }
+ gfx.lineTo(point.x, point.y);
+ }
+ if (currentWireThickness >= 0) {
+ point = points[0];
+ if ((maxX - minX) > 0.125 && (maxY - minY) > 0.125) {
+ gfx.lineTo(point.x, point.y);
+ } else {
+ // Очень маленький полигон
+ gfx.drawRect(point.x - 0.5, point.y - 0.5, 1, 1);
+ }
+ }
+ } else {
+ perspective = camera._focalLength/point.z;
+ minX = point.x*perspective;
+ maxX = minX;
+ minY = point.y*perspective;
+ maxY = minY;
+ gfx.moveTo(minX, minY);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ perspective = camera._focalLength/point.z;
+ var x:Number = point.x*perspective;
+ var y:Number = point.y*perspective;
+ if (x > maxX) {
+ maxX = x;
+ } else if (x < minX) {
+ minX = x;
+ }
+ if (y > maxY) {
+ maxY = y;
+ } else if (y < minY) {
+ minY = y;
+ }
+ gfx.lineTo(x, y);
+ }
+ if (currentWireThickness >= 0) {
+ point = points[0];
+ perspective = camera._focalLength/point.z;
+ if ((maxX - minX) > 0.125 && (maxY - minY) > 0.125) {
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ } else {
+ // Очень маленький полигон
+ gfx.drawRect(point.x*perspective - 0.5, point.y*perspective - 0.5, 1, 1);
+ }
+ }
+ }
+
+ // Отрисовка нормали
+ if (_showNormals) {
+ point1.reset();
+ for (i = 0; i < length; i++) {
+ point = points[i];
+ point1.x += point.x;
+ point1.y += point.y;
+ point1.z += point.z;
+ }
+ point1.multiply(1/length);
+
+ var multiplyer:Number = 10;
+ var normal:Point3D = skin.primitive.face.globalNormal;
+ var m:Matrix3D = camera.cameraMatrix;
+ point2.x = (normal.x*m.a + normal.y*m.b + normal.z*m.c)*multiplyer + point1.x;
+ point2.y = (normal.x*m.e + normal.y*m.f + normal.z*m.g)*multiplyer + point1.y;
+ point2.z = (normal.x*m.i + normal.y*m.j + normal.z*m.k)*multiplyer + point1.z;
+
+ if (camera._orthographic) {
+ gfx.moveTo(point1.x, point1.y);
+ gfx.lineStyle(0, _normalsColor);
+ gfx.lineTo(point2.x, point2.y);
+ } else {
+ perspective = camera._focalLength/point1.z;
+ gfx.moveTo(point1.x*perspective, point1.y*perspective);
+ gfx.lineStyle(0, _normalsColor);
+ perspective = camera._focalLength/point2.z;
+ gfx.lineTo(point2.x*perspective, point2.y*perspective);
+ }
+ gfx.lineStyle();
+ }
+ }
+
+ /**
+ * Установка параметров отрисовки.
+ */
+ private function setDrawingParameters(skin:Skin):void {
+ currentColor = _color;
+ currentWireColor = _wireColor;
+ currentWireThickness = _wireThickness;
+
+ var param:int = 0;
+ var i:int;
+ switch (_parameterType) {
+ case BSP_DEPTH:
+ // Глубина вложенности в BSP-tree
+ var node:BSPNode = skin.primitive.node;
+ while (node != null) {
+ node = node.parent;
+ param++;
+ }
+ currentColor = ColorUtils.multiply(_color, param/_maxParameterValue);
+ break;
+ case MOBILITY:
+ // Мобильность
+ var value:Number = (skin.primitive.mobility - _minMobility)/(_maxMobility - _minMobility);
+ if (value < 0) {
+ value = 0;
+ }
+ currentColor = ColorUtils.multiply(_color, value);
+ break;
+ case FRAGMENTATION:
+ // Степень фрагментирования
+ currentColor = ColorUtils.multiply(_color, calculateFragments(skin.primitive.face.primitive)/_maxParameterValue);
+ break;
+ case NO_UV_MAPPING:
+ // Отсутствие UV
+ if (skin.primitive.face.uvMatrix == null) {
+ currentColor = 0xFF0000;
+ }
+ break;
+ case DEGENERATE_POLY:
+ // Вырожденные полигоны
+ var face:Face = skin.primitive.face;
+ var point0:Point3D = face._vertices[0]._coords;
+ point1.copy(face._vertices[1]._coords);
+ point2.copy(face._vertices[2]._coords);
+ point2.subtract(point1);
+ point1.subtract(point0);
+ var crossX:Number = point1.y*point2.z - point1.z*point2.y;
+ var crossY:Number = point1.z*point2.x - point1.x*point2.z;
+ var crossZ:Number = point1.x*point2.y - point1.y*point2.x;
+ var sum:Number = crossX*crossX + crossY*crossY + crossZ*crossZ;
+ for (i = 3; i < face._verticesCount; i++) {
+ point1.copy(face._vertices[i - 1]._coords);
+ point2.copy(face._vertices[i]._coords);
+ point2.subtract(point1);
+ point1.subtract(point0);
+ crossX = point1.y*point2.z - point1.z*point2.y;
+ crossY = point1.z*point2.x - point1.x*point2.z;
+ crossZ = point1.x*point2.y - point1.y*point2.x;
+ sum += crossX*crossX + crossY*crossY + crossZ*crossZ;
+ }
+ if (sum < 0.001) {
+ currentColor = 0xFF0000;
+ currentWireColor = 0xFF0000;
+ currentWireThickness = 5;
+ }
+ break;
+ case NON_PLANAR_POLY:
+ // Неплоские полигоны
+ face = skin.primitive.face;
+ var normal:Point3D = face.globalNormal;
+ var offset:Number = face.globalOffset;
+ for (i = 3; i < face._verticesCount; i++) {
+ var vertex:Point3D = (face._vertices[i] as Vertex).globalCoords;
+ var distance:Number = vertex.x*normal.x + vertex.y*normal.y + vertex.z*normal.z - offset;
+ if (distance > _maxParameterValue || distance < -_maxParameterValue) {
+ currentColor = 0xFF0000;
+ currentWireColor = 0xFF0000;
+ currentWireThickness = -1;
+ return;
+ }
+ }
+ break;
+ }
+ }
+
+ /**
+ * Расчёт количества фрагментов грани примитива.
+ */
+ private function calculateFragments(primitive:PolyPrimitive):int {
+ if (primitive.frontFragment == null) {
+ return 1;
+ }
+ return calculateFragments(primitive.frontFragment) + calculateFragments(primitive.backFragment);
+ }
+
+ /**
+ * Цвет заливки.
+ */
+ public function get color():uint {
+ return _color;
+ }
+
+ /**
+ * @private
+ */
+ public function set color(value:uint):void {
+ if (_color != value) {
+ _color = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Толщина линии обводки. Если значение отрицательное, то отрисовка линии не выполняется.
+ */
+ public function get wireThickness():Number {
+ return _wireThickness;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireThickness(value:Number):void {
+ if (_wireThickness != value) {
+ _wireThickness = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Цвет линии обводки.
+ */
+ public function get wireColor():uint {
+ return _wireColor;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireColor(value:uint):void {
+ if (_wireColor != value) {
+ _wireColor = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ var res:DevMaterial = new DevMaterial(_parameterType, _color, _maxParameterValue, _showNormals, _normalsColor, _minMobility, _maxMobility, _alpha, _blendMode, _wireThickness, _wireColor);
+ return res;
+ }
+
+ /**
+ * Тип отображаемого параметра.
+ */
+ public function set parameterType(value:int):void {
+ if (_parameterType != value) {
+ _parameterType = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function get parameterType():int {
+ return _parameterType;
+ }
+
+ /**
+ * Включение режима отображения нормалей.
+ */
+ public function set showNormals(value:Boolean):void {
+ if (_showNormals != value) {
+ _showNormals = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function get showNormals():Boolean {
+ return _showNormals;
+ }
+
+ /**
+ * Цвет нормалей.
+ *
+ * @default 0x00FFFF
+ */
+ public function set normalsColor(value:uint):void {
+ if (_normalsColor != value) {
+ _normalsColor = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function get normalsColor():uint {
+ return _normalsColor;
+ }
+
+ /**
+ * Начало интервала мобильности.
+ */
+ public function set minMobility(value:int):void {
+ if (_minMobility != value) {
+ _minMobility = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function get minMobility():int {
+ return _minMobility;
+ }
+
+ /**
+ * Окончание интервала мобильности.
+ */
+ public function set maxMobility(value:int):void {
+ if (_maxMobility != value) {
+ _maxMobility = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function get maxMobility():int {
+ return _maxMobility;
+ }
+
+ /**
+ * Максимальное значение отображаемого параметра.
+ */
+ public function set maxParameterValue(value:Number):void {
+ if (_maxParameterValue != value) {
+ _maxParameterValue = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @private
+ */
+ public function get maxParameterValue():Number {
+ return _maxParameterValue;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/DrawPoint.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/DrawPoint.as
new file mode 100644
index 0000000..2447283
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/DrawPoint.as
@@ -0,0 +1,47 @@
+package alternativa.engine3d.materials {
+ /**
+ * @private
+ * Точка, подготовленная к отрисовке.
+ */
+ public final class DrawPoint {
+
+ /**
+ * Координата X в системе координат камеры.
+ */
+ public var x:Number;
+ /**
+ * Координата Y в системе координат камеры.
+ */
+ public var y:Number;
+ /**
+ * Координата Z в системе координат камеры.
+ */
+ public var z:Number;
+ /**
+ * Координата U в текстурном пространстве.
+ */
+ public var u:Number;
+ /**
+ * Координата V в текстурном пространстве.
+ */
+ public var v:Number;
+
+ /**
+ * Создаёт новый экземпляр класса.
+ *
+ * @param x координата X в системе координат камеры
+ * @param y координата Y в системе координат камеры
+ * @param z координата Z в системе координат камеры
+ * @param u координата U в текстурном пространстве
+ * @param v координата V в текстурном пространстве
+ */
+ public function DrawPoint(x:Number, y:Number, z:Number, u:Number = 0, v:Number = 0) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.u = u;
+ this.v = v;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/FillMaterial.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/FillMaterial.as
new file mode 100644
index 0000000..c2e88dc
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/FillMaterial.as
@@ -0,0 +1,160 @@
+package alternativa.engine3d.materials {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.display.Skin;
+
+ import flash.display.BlendMode;
+ import flash.display.Graphics;
+
+ use namespace alternativa3d;
+
+ /**
+ * Материал, заполняющий полигон сплошной одноцветной заливкой. Помимо заливки цветом, материал может рисовать границу
+ * полигона линией заданной толщины и цвета.
+ */
+ public class FillMaterial extends SurfaceMaterial {
+
+ /**
+ * @private
+ * Цвет
+ */
+ alternativa3d var _color:uint;
+
+ /**
+ * @private
+ * Толщина линий обводки
+ */
+ alternativa3d var _wireThickness:Number;
+
+ /**
+ * @private
+ * Цвет линий обводки
+ */
+ alternativa3d var _wireColor:uint;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param color цвет заливки
+ * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ * @param blendMode режим наложения цвета
+ * @param wireThickness толщина линии обводки
+ * @param wireColor цвет линии обводки
+ */
+ public function FillMaterial(color:uint, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) {
+ super(alpha, blendMode);
+ _color = color;
+ _wireThickness = wireThickness;
+ _wireColor = wireColor;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+
+ var i:uint;
+ var point:DrawPoint;
+ var gfx:Graphics = skin.gfx;
+
+ if (camera._orthographic) {
+ gfx.beginFill(_color);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ gfx.moveTo(point.x, point.y);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ gfx.beginFill(_color);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ var perspective:Number = camera._focalLength/point.z;
+ gfx.moveTo(point.x*perspective, point.y*perspective);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ perspective = camera._focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ perspective = camera._focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ }
+ }
+
+ /**
+ * Цвет заливки.
+ */
+ public function get color():uint {
+ return _color;
+ }
+
+ /**
+ * @private
+ */
+ public function set color(value:uint):void {
+ if (_color != value) {
+ _color = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Толщина линии обводки. Если значение отрицательное, то обводка не рисуется.
+ */
+ public function get wireThickness():Number {
+ return _wireThickness;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireThickness(value:Number):void {
+ if (_wireThickness != value) {
+ _wireThickness = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Цвет линии обводки.
+ */
+ public function get wireColor():uint {
+ return _wireColor;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireColor(value:uint):void {
+ if (_wireColor != value) {
+ _wireColor = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ var res:FillMaterial = new FillMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor);
+ return res;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/Material.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/Material.as
new file mode 100644
index 0000000..e7dcedc
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/Material.as
@@ -0,0 +1,94 @@
+package alternativa.engine3d.materials {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.display.Skin;
+
+ use namespace alternativa3d;
+
+ /**
+ * Базовый класс для материалов.
+ */
+ public class Material {
+
+ /**
+ * @private
+ * Альфа.
+ */
+ alternativa3d var _alpha:Number;
+ /**
+ * @private
+ * Режим наложения цвета.
+ */
+ alternativa3d var _blendMode:String;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ * @param blendMode режим наложения цвета
+ */
+ public function Material(alpha:Number, blendMode:String) {
+ _alpha = alpha;
+ _blendMode = blendMode;
+ }
+
+ /**
+ * Коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ */
+ public function get alpha():Number {
+ return _alpha;
+ }
+
+ /**
+ * @private
+ */
+ public function set alpha(value:Number):void {
+ if (_alpha != value) {
+ _alpha = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Режим наложения цвета.
+ */
+ public function get blendMode():String {
+ return _blendMode;
+ }
+
+ /**
+ * @private
+ */
+ public function set blendMode(value:String):void {
+ if (_blendMode != value) {
+ _blendMode = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Отметить материал на перерисовку.
+ */
+ protected function markToChange():void {}
+
+ /**
+ * @private
+ * Метод очищает переданный скин (нарисованную графику, дочерние объекты и т.д.).
+ *
+ * @param skin скин для очистки
+ */
+ alternativa3d function clear(skin:Skin):void {
+ skin.gfx.clear();
+ }
+
+ /**
+ * Создание клона материала.
+ *
+ * @return клон материала
+ */
+ public function clone():Material {
+ return new Material(_alpha, _blendMode);
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/MovieClipMaterial.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/MovieClipMaterial.as
new file mode 100644
index 0000000..f55cdce
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/MovieClipMaterial.as
@@ -0,0 +1,226 @@
+package alternativa.engine3d.materials {
+
+ import alternativa.engine3d.alternativa3d;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.events.MouseEvent3D;
+ import alternativa.types.Texture;
+
+ import flash.display.BitmapData;
+ import flash.display.BlendMode;
+ import flash.display.MovieClip;
+ import flash.events.Event;
+ import flash.geom.Matrix;
+ import flash.geom.Rectangle;
+
+ use namespace alternativa3d;
+
+ /**
+ * Материал, позволяющий использовать мувиклип в качестве интерактивной текстуры.
+ *
+ * При использовании материала следует учитывать тот факт, что для перерисовки текстуры мувиклипу назначается обработчик события
+ * ENTER_FRAME. Если ссылка на материал будет потеряна, обработчик всё равно будет выполняться. Поэтому при окончании работы с
+ * материалом следует установить свойству movieClip значение null для отключения обработчика.
+ *
+ */
+ public class MovieClipMaterial extends TextureMaterial {
+
+ private var _movieClip:MovieClip;
+ private var texWidth:uint;
+ private var texHeight:uint;
+ private var _clearRect:Rectangle;
+ private var _clipRect:Rectangle;
+ private var _matrix:Matrix;
+ private var _refreshRate:int;
+ private var _refreshCounter:int;
+ /**
+ * Цвет, которым заливается текстура материала перед отрисовкой мувиклипа.
+ */
+ public var fillColor:uint;
+
+ /**
+ * Создаёт новый экземпляр материала.
+ *
+ * @param movieClip мувиклип, используемый в качестве текстуры
+ * @param textureWidth ширина текстуры материала
+ * @param textureHeight высота текстуры материала
+ * @param clipRect область мувиклипа, отрисовываемая в текстуру материала. Если передано значение null, будет отрисовываться вся область мувиклипа.
+ * @param matrix матрица трансформации мувиклипа при отрисовке. Если передано значение null, трансформация выполняться не будет.
+ * @param smooth сглаживание текстуры при увеличении масштаба
+ * @param precision точность перспективной коррекции. Может быть задана одной из констант класса
+ * TextureMaterialPrecision или числом типа Number. Во втором случае, чем ближе заданное значение к единице, тем более
+ * качественная перспективная коррекция будет выполнена, и тем больше времени будет затрачено на расчёт кадра.
+ * @param fillColor цвет, которым заливается текстура материала перед отрисовкой мувиклипа
+ * @param refreshRate частота обновления текстуры материала. Единица означает перерисовку каждый кадр, двойка — каждые два кадра и так далее
+ *
+ * @see TextureMaterialPrecision
+ */
+ public function MovieClipMaterial(movieClip:MovieClip, textureWidth:uint, textureHeight:uint, clipRect:Rectangle = null, matrix:Matrix = null, smooth:Boolean = false, precision:Number = 10, fillColor:uint = 0, refreshRate:int = 1) {
+ texWidth = textureWidth;
+ texHeight = textureHeight;
+ _clearRect = new Rectangle(0, 0, texWidth, texHeight);
+ if (clipRect != null) {
+ _clipRect = clipRect.clone();
+ }
+ if (matrix != null) {
+ _matrix = matrix.clone();
+ }
+ _texture = new Texture(new BitmapData(texWidth, texWidth));
+ this.refreshRate = refreshRate;
+ this.movieClip = movieClip;
+ this.fillColor = fillColor;
+
+ super(_texture, 1, false, smooth, BlendMode.NORMAL, -1, 0, precision);
+ }
+
+ /**
+ * Мувиклип, используемый в качестве текстуры.
+ *
+ * При установке свойства указанному мувиклипу назначается обработчик события ENTER_FRAME. Во избежании утечки памяти, при прекращении работы с материалом
+ * следует устанавливать данному свойству значение null для отключения обработчика.
+ *
+ */
+ public function get movieClip():MovieClip {
+ return _movieClip;
+ }
+
+ /**
+ * @private
+ */
+ public function set movieClip(value:MovieClip):void {
+ if (value != _movieClip) {
+ if (_movieClip != null) {
+ _movieClip.removeEventListener(Event.ENTER_FRAME, redraw);
+ }
+ _movieClip = value;
+ if (_movieClip != null) {
+ _movieClip.addEventListener(Event.ENTER_FRAME, redraw);
+ } else {
+ _texture.bitmapData.fillRect(_clearRect, fillColor);
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ private function redraw(e:Event):void {
+ if (_movieClip != null) {
+ _refreshCounter++;
+ if (_refreshCounter == _refreshRate) {
+ _refreshCounter = 0;
+ _texture.bitmapData.fillRect(_clearRect, fillColor);
+ _texture.bitmapData.draw(_movieClip, _matrix, null, BlendMode.NORMAL, _clearRect, _smooth);
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ override alternativa3d function addToSurface(surface:Surface):void {
+ super.addToSurface(surface);
+ // Устанавливаем обработчики мышиных событий, которые будут обеспечивать корректное позиционирование мувиклипа
+ surface.addEventListener(MouseEvent3D.MOUSE_MOVE, onSurfaceMouseMove);
+ surface.addEventListener(MouseEvent3D.MOUSE_OVER, onSurfaceMouseOverOut);
+ surface.addEventListener(MouseEvent3D.MOUSE_OUT, onSurfaceMouseOverOut);
+ }
+
+ /**
+ * @private
+ */
+ override alternativa3d function removeFromSurface(surface:Surface):void {
+ super.removeFromSurface(surface);
+ // Удаляем обработчики мышиных событий
+ surface.removeEventListener(MouseEvent3D.MOUSE_MOVE, onSurfaceMouseMove);
+ surface.removeEventListener(MouseEvent3D.MOUSE_OVER, onSurfaceMouseOverOut);
+ surface.removeEventListener(MouseEvent3D.MOUSE_OUT, onSurfaceMouseOverOut);
+ }
+
+ /**
+ * @private
+ */
+ private function onSurfaceMouseMove(e:MouseEvent3D):void {
+ if (_movieClip != null) {
+ // При перемещении мыши над гранью позиционируем мувиклип так, чтобы совместить точку грани под курсором мыши с соотвествующей точкой на мувиклипе
+ _movieClip.x = e.view.mouseX - texWidth*e.u;
+ _movieClip.y = e.view.mouseY - texHeight*(1 - e.v);
+ }
+ }
+
+ /**
+ * @private
+ */
+ private function onSurfaceMouseOverOut(e:MouseEvent3D):void {
+ if (_movieClip != null) {
+ // Удаляем мувиклип с вьюпорта при уходе мыши с грани и добавляем при наведении мыши на грань.
+ // Это нужно, чтобы вьюпорт получал событие MOUSE_MOVE при перемещении мыши над мувиклипом.
+ if (e.type == MouseEvent3D.MOUSE_OVER) {
+ e.view.addChild(_movieClip);
+ _movieClip.alpha = 0;
+ } else {
+ if (_movieClip.parent == e.view) {
+ e.view.removeChild(_movieClip);
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ override public function set texture(value:Texture):void {
+ }
+
+ /**
+ * Частота обновления текстуры материала. Единица означает перерисовку каждый кадр, двойка — каждые два кадра и так далее.
+ */
+ public function get refreshRate():int {
+ return _refreshRate;
+ }
+
+ /**
+ * @private
+ */
+ public function set refreshRate(value:int):void {
+ _refreshRate = value > 0 ? value : 1;
+ }
+
+ /**
+ * Область мувиклипа, отрисовываемая в текстуру материала. При установленном значении null будет отрисовываться вся область мувиклипа.
+ */
+ public function get clipRect():Rectangle {
+ return _clipRect == null ? null : _clipRect.clone();
+ }
+
+ /**
+ * @private
+ */
+ public function set clipRect(value:Rectangle):void {
+ _clipRect = value;
+ }
+
+ /**
+ * Матрица трансформации мувиклипа при отрисовке. При установленном значении null трансформация выполняться не будет.
+ */
+ public function get matrix():Matrix {
+ return _matrix == null ? null : _matrix.clone();
+ }
+
+ /**
+ * @private
+ */
+ public function set matrix(value:Matrix):void {
+ _matrix = value;
+ }
+
+ /**
+ * Выполняет клонирование материала.
+ *
+ * @return копия материала
+ */
+ override public function clone():Material {
+ return new MovieClipMaterial(_movieClip, texWidth, texHeight, _clipRect, _matrix, _smooth, _precision, fillColor, _refreshRate);
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/SpriteMaterial.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/SpriteMaterial.as
new file mode 100644
index 0000000..94bd4c1
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/SpriteMaterial.as
@@ -0,0 +1,122 @@
+package alternativa.engine3d.materials {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Scene3D;
+ import alternativa.engine3d.core.Sprite3D;
+ import alternativa.engine3d.display.Skin;
+ import alternativa.types.*;
+
+ import flash.display.BlendMode;
+
+ use namespace alternativa3d;
+ use namespace alternativatypes;
+
+ /**
+ * Базовый класс для материалов спрайтов.
+ */
+ public class SpriteMaterial extends Material {
+
+ /**
+ * @private
+ * Спрайт.
+ */
+ alternativa3d var _sprite:Sprite3D;
+
+ /**
+ * Создание экземпляра материала.
+ *
+ * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ * @param blendMode режим наложения цвета
+ */
+ public function SpriteMaterial(alpha:Number = 1, blendMode:String = BlendMode.NORMAL) {
+ super(alpha, blendMode);
+ }
+
+ /**
+ * Спрайт, которому назначен материал.
+ */
+ public function get sprite():Sprite3D {
+ return _sprite;
+ }
+
+ /**
+ * @private
+ * Добавление на сцену.
+ *
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {}
+
+ /**
+ * @private
+ * Удаление из сцены.
+ *
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {}
+
+ /**
+ * @private
+ * Назначение спрайту.
+ *
+ * @param sprite спрайт
+ */
+ alternativa3d function addToSprite(sprite:Sprite3D):void {
+ _sprite = sprite;
+ }
+
+ /**
+ * @private
+ * Удаление из спрайта.
+ *
+ * @param sprite спрайт
+ */
+ alternativa3d function removeFromSprite(sprite:Sprite3D):void {
+ _sprite = null;
+ }
+
+ /**
+ * @private
+ * Метод определяет, может ли материал нарисовать спрайт. Метод используется в системе отрисовки сцены и должен использоваться
+ * наследниками для указания видимости связанной со спрайтом. Реализация по умолчанию возвращает
+ * true.
+ *
+ * @param camera камера через которую происходит отрисовка.
+ *
+ * @return true, если материал может отрисовать указанный примитив, иначе false
+ */
+ alternativa3d function canDraw(camera:Camera3D):Boolean {
+ return true;
+ }
+
+ /**
+ * @private
+ * Метод выполняет отрисовку в заданный скин.
+ *
+ * @param camera камера, вызвавшая метод
+ * @param skin скин, в котором нужно отрисовать
+ */
+ alternativa3d function draw(camera:Camera3D, skin:Skin):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function markToChange():void {
+ if (_sprite != null) {
+ _sprite.addMaterialChangedOperationToScene();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ return new SpriteMaterial(_alpha, _blendMode);
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/SpriteTextureMaterial.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/SpriteTextureMaterial.as
new file mode 100644
index 0000000..00fad62
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/SpriteTextureMaterial.as
@@ -0,0 +1,243 @@
+package alternativa.engine3d.materials {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.display.Skin;
+ import alternativa.types.*;
+
+ import flash.display.BlendMode;
+ import flash.geom.Matrix;
+ import flash.geom.Rectangle;
+
+ use namespace alternativa3d;
+ use namespace alternativatypes;
+
+ /**
+ * Материал, отображающий заданную текстуру в точке нахождения спрайта. Текстура отображается так, как если бы она находилась в плоскости,
+ * параллельной плоскости области вывода камеры, а верхний край текстуры был параллелен верхнему краю области вывода. При отрисовке изображения
+ * начало координат текстуры в области вывода совпадает с проекцией точки спрайта. По умолчанию начало координат текстуры перенесено в её центр.
+ */
+ public class SpriteTextureMaterial extends SpriteMaterial {
+
+ /**
+ * @private
+ * Вспомогательный прямоугольник, используется при отрисовке для хранения параметров отрисовки.
+ */
+ private static var drawRect:Rectangle = new Rectangle();
+
+ /**
+ * @private
+ * Матрица, используемая для отрисовки текстуры спрайта
+ */
+ private static var textureMatrix:Matrix = new Matrix();
+
+ /**
+ * @private
+ * Текстура
+ */
+ alternativa3d var _texture:Texture;
+
+ /**
+ * @private
+ * Сглаженность текстуры
+ */
+ alternativa3d var _smooth:Boolean;
+
+ /**
+ * @private
+ * Смещение начала координат по оси X
+ */
+ alternativa3d var _originX:Number;
+
+ /**
+ * @private
+ * Смещение начала координат по оси Y
+ */
+ alternativa3d var _originY:Number;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param texture текстура для отображения
+ * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ * @param smooth сглаживание текстуры
+ * @param blendMode режим наложения цвета
+ * @param originX относительное смещение начала координат в текстуре по оси X
+ * @param originY относительное смещение начала координат в текстуре по оси Y
+ */
+ public function SpriteTextureMaterial(texture:Texture, alpha:Number = 1, smooth:Boolean = false, blendMode:String = BlendMode.NORMAL, originX:Number = 0.5, originY:Number = 0.5) {
+ super(alpha, blendMode);
+ _texture = texture;
+ _smooth = smooth;
+ _originX = originX;
+ _originY = originY;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function canDraw(camera:Camera3D):Boolean {
+ if (_texture == null) {
+ return false;
+ }
+
+ // Переводим координаты в систему камеры
+ var cameraMatrix:Matrix3D = camera.cameraMatrix;
+
+ var x:Number = _sprite.globalCoords.x;
+ var y:Number = _sprite.globalCoords.y;
+ var z:Number = _sprite.globalCoords.z;
+ var pointX:Number = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d;
+ var pointY:Number = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h;
+ var pointZ:Number = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l;
+
+ var w:Number;
+ var h:Number;
+
+ if (camera._orthographic) {
+ if ((camera._nearClipping && pointZ < camera._nearClippingDistance) || (camera._farClipping && pointZ > camera._farClippingDistance)) {
+ return false;
+ }
+ w = _texture._width*camera._zoom*_sprite._materialScale;
+ h = _texture._height*camera._zoom*_sprite._materialScale;
+ x = pointX - w*_originX;
+ y = pointY - h*_originY;
+ } else {
+ if ((pointZ <= 0) || (camera._nearClipping && pointZ < camera._nearClippingDistance) || (camera._farClipping && pointZ > camera._farClippingDistance)) {
+ return false;
+ }
+ var perspective:Number = camera._focalLength/pointZ;
+ w = _texture._width*perspective*_sprite._materialScale;
+ h = _texture._height*perspective*_sprite._materialScale;
+ x = pointX*perspective - w*_originX;
+ y = pointY*perspective - h*_originY;
+ }
+ var halfW:Number = camera._view._width*0.5;
+ var halfH:Number = camera._view._height*0.5;
+
+ if (camera._viewClipping && (x >= halfW || y >= halfH || x + w <= -halfW || y + h <= -halfH)) {
+ return false;
+ }
+
+ textureMatrix.a = w/_texture._width;
+ textureMatrix.d = h/_texture._height;
+ textureMatrix.tx = x;
+ textureMatrix.ty = y;
+
+ if (camera._viewClipping) {
+ if (x < -halfW) {
+ w -= -halfW - x;
+ x = -halfW;
+ }
+ if (x + w > halfW) {
+ w = halfW - x;
+ }
+ if (y < -halfH) {
+ h -= -halfH - y;
+ y = -halfH;
+ }
+ if (y + h > halfH) {
+ h = halfH - y;
+ }
+ }
+ drawRect.x = x;
+ drawRect.y = y;
+ drawRect.width = w;
+ drawRect.height = h;
+
+ return true;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+ skin.gfx.beginBitmapFill(_texture._bitmapData, textureMatrix, false, _smooth);
+ skin.gfx.drawRect(drawRect.x, drawRect.y, drawRect.width, drawRect.height);
+ }
+
+ /**
+ * Текстура.
+ */
+ public function get texture():Texture {
+ return _texture;
+ }
+
+ /**
+ * @private
+ */
+ public function set texture(value:Texture):void {
+ if (_texture != value) {
+ _texture = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Сглаживание текстуры.
+ */
+ public function get smooth():Boolean {
+ return _smooth;
+ }
+
+ /**
+ * @private
+ */
+ public function set smooth(value:Boolean):void {
+ if (_smooth != value) {
+ _smooth = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Относительное смещение начала координат в текстуре по оси X.
+ *
+ * @default 0.5
+ */
+ public function get originX():Number {
+ return _originX;
+ }
+
+ /**
+ * @private
+ */
+ public function set originX(value:Number):void {
+ if (_originX != value) {
+ _originX = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Относительное смещение начала координат в текстуре по оси Y.
+ *
+ * @default 0.5
+ */
+ public function get originY():Number {
+ return _originY;
+ }
+
+ /**
+ * @private
+ */
+ public function set originY(value:Number):void {
+ if (_originY != value) {
+ _originY = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ return new SpriteTextureMaterial(_texture, _alpha, _smooth, _blendMode, _originX, _originY);
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/SurfaceMaterial.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/SurfaceMaterial.as
new file mode 100644
index 0000000..c429d8b
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/SurfaceMaterial.as
@@ -0,0 +1,150 @@
+package alternativa.engine3d.materials {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.core.Scene3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.display.Skin;
+
+ import flash.display.BlendMode;
+
+ use namespace alternativa3d;
+
+ /**
+ * Базовый класс для материалов полигональных поверхностей.
+ */
+ public class SurfaceMaterial extends Material {
+
+ /**
+ * @private
+ * Поверхность
+ */
+ alternativa3d var _surface:Surface;
+ /**
+ * @private
+ * Флаг, определяет использует ли материал UV-координаты в грани
+ */
+ alternativa3d var useUV:Boolean = false;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ * @param blendMode режим наложения цвета
+ */
+ public function SurfaceMaterial(alpha:Number = 1, blendMode:String = BlendMode.NORMAL) {
+ super(alpha, blendMode);
+ }
+
+ /**
+ * Поверхность, которой назначен материал.
+ */
+ public function get surface():Surface {
+ return _surface;
+ }
+
+ /**
+ * @private
+ * Добавление на сцену
+ *
+ * @param scene
+ */
+ alternativa3d function addToScene(scene:Scene3D):void {}
+
+ /**
+ * @private
+ * Удаление из сцены
+ *
+ * @param scene
+ */
+ alternativa3d function removeFromScene(scene:Scene3D):void {}
+
+ /**
+ * @private
+ * Добавление к мешу
+ *
+ * @param mesh
+ */
+ alternativa3d function addToMesh(mesh:Mesh):void {}
+
+ /**
+ * @private
+ * Удаление из меша
+ *
+ * @param mesh
+ */
+ alternativa3d function removeFromMesh(mesh:Mesh):void {}
+
+ /**
+ * @private
+ * Добавление на поверхность
+ *
+ * @param surface
+ */
+ alternativa3d function addToSurface(surface:Surface):void {
+ // Сохраняем поверхность
+ _surface = surface;
+ }
+
+ /**
+ * @private
+ * Удаление с поверхности
+ *
+ * @param surface
+ */
+ alternativa3d function removeFromSurface(surface:Surface):void {
+ // Удаляем ссылку на поверхность
+ _surface = null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function markToChange():void {
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+
+ /**
+ * @private
+ * Метод определяет, может ли материал нарисовать указанный примитив. Метод используется в системе отрисовки сцены и должен использоваться
+ * наследниками для указания видимости связанной с материалом поверхности или отдельного примитива. Реализация по умолчанию возвращает
+ * true.
+ *
+ * @param primitive примитив для проверки
+ *
+ * @return true, если материал может отрисовать указанный примитив, иначе false
+ */
+ alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
+ return true;
+ }
+
+ /**
+ * @private
+ * Метод выполняет отрисовку в заданный скин.
+ *
+ * @param camera камера, вызвавшая метод
+ * @param skin скин, в котором нужно рисовать
+ * @param length длина массива points
+ * @param points массив точек, определяющих отрисовываемый полигон. Каждый элемент массива является объектом класса
+ * alternativa.engine3d.materials.DrawPoint
+ *
+ * @see DrawPoint
+ */
+ alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ return new SurfaceMaterial(_alpha, _blendMode);
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/TextureMaterial.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/TextureMaterial.as
new file mode 100644
index 0000000..6e717b0
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/TextureMaterial.as
@@ -0,0 +1,356 @@
+package alternativa.engine3d.materials {
+ import __AS3__.vec.Vector;
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.display.Skin;
+ import alternativa.types.*;
+
+ import flash.display.BitmapData;
+ import flash.display.BlendMode;
+ import flash.geom.Matrix;
+ import flash.display.BitmapData;
+ import flash.display.Graphics;
+
+ use namespace alternativa3d;
+ use namespace alternativatypes;
+
+ /**
+ * Материал, заполняющий полигон текстурой. Помимо наложения текстуры, материал может рисовать границу полигона линией
+ * заданной толщины и цвета.
+ */
+ public class TextureMaterial extends SurfaceMaterial {
+
+ private static var stubBitmapData:BitmapData;
+ private static var stubMatrix:Matrix;
+
+ private var gfx:Graphics;
+ private var textureMatrix:Matrix = new Matrix();
+ private var focalLength:Number;
+ private var distortion:Number;
+
+ /**
+ * @private
+ * Текстура
+ */
+ alternativa3d var _texture:Texture;
+ /**
+ * @private
+ * Повтор текстуры
+ */
+ alternativa3d var _repeat:Boolean;
+ /**
+ * @private
+ * Сглаженность текстуры
+ */
+ alternativa3d var _smooth:Boolean;
+ /**
+ * @private
+ * Точность перспективной коррекции
+ */
+ alternativa3d var _precision:Number;
+
+ /**
+ * @private
+ * Толщина линий обводки
+ */
+ alternativa3d var _wireThickness:Number;
+
+ /**
+ * @private
+ * Цвет линий обводки
+ */
+ alternativa3d var _wireColor:uint;
+
+ /**
+ * Создание экземпляра текстурного материала.
+ *
+ * @param texture текстура материала
+ * @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ * @param repeat повтор текстуры при заполнении
+ * @param smooth сглаживание текстуры при увеличении масштаба
+ * @param blendMode режим наложения цвета
+ * @param wireThickness толщина линии обводки
+ * @param wireColor цвет линии обводки
+ * @param precision точность перспективной коррекции. Может быть задана одной из констант класса
+ * TextureMaterialPrecision или числом типа Number. Во втором случае, чем ближе заданное значение к единице, тем более
+ * качественная перспективная коррекция будет выполнена, и тем больше времени будет затрачено на расчёт кадра.
+ *
+ * @see TextureMaterialPrecision
+ */
+ public function TextureMaterial(texture:Texture, alpha:Number = 1, repeat:Boolean = true, smooth:Boolean = false, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0, precision:Number = TextureMaterialPrecision.MEDIUM) {
+ super(alpha, blendMode);
+ _texture = texture;
+ _repeat = repeat;
+ _smooth = smooth;
+ _wireThickness = wireThickness;
+ _wireColor = wireColor;
+ _precision = precision;
+ useUV = true;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
+ return _texture != null;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+
+ var i:uint;
+ var point:DrawPoint;
+ gfx = skin.gfx;
+
+ // Проверка на нулевую UV-матрицу
+ if (skin.primitive.face.uvMatrixBase == null) {
+ if (stubBitmapData == null) {
+ // Создание текстуры-заглушки
+ stubBitmapData = new BitmapData(2, 2, false, 0);
+ stubBitmapData.setPixel(0, 0, 0xFF00FF);
+ stubBitmapData.setPixel(1, 1, 0xFF00FF);
+ stubMatrix = new Matrix(10, 0, 0, 10, 0, 0);
+ }
+ gfx.beginBitmapFill(stubBitmapData, stubMatrix);
+ if (camera._orthographic) {
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ gfx.moveTo(point.x, point.y);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ var perspective:Number = camera._focalLength/point.z;
+ gfx.moveTo(point.x*perspective, point.y*perspective);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ perspective = camera._focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ perspective = camera._focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ }
+ return;
+ }
+
+ if (camera._orthographic) {
+ // Расчитываем матрицу наложения текстуры
+ var face:Face = skin.primitive.face;
+ // Если матрица не расчитана, считаем
+ if (!camera.uvMatricesCalculated[face]) {
+ camera.calculateUVMatrix(face, _texture._width, _texture._height);
+ }
+ gfx.beginBitmapFill(_texture._bitmapData, face.orthoTextureMatrix, _repeat, _smooth);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ point = points[0];
+ gfx.moveTo(point.x, point.y);
+ for (i = 1; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ if (_wireThickness >= 0) {
+ point = points[0];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ // Отрисовка
+ focalLength = camera._focalLength;
+ //distortion = camera.focalDistortion*_precision;
+
+ var front:int = 0;
+ var back:int = length - 1;
+
+ var newFront:int = 1;
+ var newBack:int = (back > 0) ? (back - 1) : (length - 1);
+ var direction:Boolean = true;
+
+ var a:DrawPoint = points[back];
+ var b:DrawPoint;
+ var c:DrawPoint = points[front];
+
+ var drawVertices:Vector. = new Vector.();
+ var drawUVTs:Vector. = new Vector.();
+
+ for (i = 0; i < length; i++) {
+ var p:DrawPoint = points[i];
+ var t:Number = focalLength/p.z;
+ drawVertices[i << 1] = p.x*t;
+ drawVertices[(i << 1) + 1] = p.y*t;
+ drawUVTs.push(p.u, 1 - p.v, t);
+ }
+
+ var drawIndices:Vector. = new Vector.();
+
+ while (front != newBack) {
+ if (direction) {
+/* a = points[front];
+ b = points[newFront];
+ c = points[back];
+ */
+ drawIndices.push(front, newFront, back);
+
+ front = newFront;
+ newFront = (front < length - 1) ? (front + 1) : 0;
+ } else {
+/* a = points[newBack];
+ b = points[back];
+ c = points[front];
+ */
+ drawIndices.push(newBack, back, front);
+
+ back = newBack;
+ newBack = (back > 0) ? (back - 1) : (length - 1);
+ }
+
+ direction = !direction;
+ }
+ gfx.beginBitmapFill(_texture.bitmapData, null, _repeat, _smooth);
+ if (_wireThickness >= 0) {
+ gfx.lineStyle(_wireThickness, _wireColor);
+ }
+ gfx.drawTriangles(drawVertices, drawIndices, drawUVTs);
+
+ }
+ }
+
+ /**
+ * Текстура материала. Материал не выполняет никаких действий по отрисовке, если не задана текстура.
+ */
+ public function get texture():Texture {
+ return _texture;
+ }
+
+ /**
+ * @private
+ */
+ public function set texture(value:Texture):void {
+ if (_texture != value) {
+ _texture = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Повтор текстуры при заливке. Более подробную информацию можно найти в описании метода
+ * flash.display.Graphics#beginBitmapFill().
+ */
+ public function get repeat():Boolean {
+ return _repeat;
+ }
+
+ /**
+ * @private
+ */
+ public function set repeat(value:Boolean):void {
+ if (_repeat != value) {
+ _repeat = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Сглаживание текстуры при увеличении масштаба. Более подробную информацию можно найти в описании метода
+ * flash.display.Graphics#beginBitmapFill().
+ */
+ public function get smooth():Boolean {
+ return _smooth;
+ }
+
+ /**
+ * @private
+ */
+ public function set smooth(value:Boolean):void {
+ if (_smooth != value) {
+ _smooth = value;
+ if (_surface != null) {
+ _surface.addMaterialChangedOperationToScene();
+ }
+ }
+ }
+
+ /**
+ * Толщина линии обводки полигона. Если значение отрицательное, то обводка не рисуется.
+ */
+ public function get wireThickness():Number {
+ return _wireThickness;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireThickness(value:Number):void {
+ if (_wireThickness != value) {
+ _wireThickness = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Цвет линии обводки полигона.
+ */
+ public function get wireColor():uint {
+ return _wireColor;
+ }
+
+ /**
+ * @private
+ */
+ public function set wireColor(value:uint):void {
+ if (_wireColor != value) {
+ _wireColor = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Точность перспективной коррекции.
+ */
+ public function get precision():Number {
+ return _precision;
+ }
+
+ /**
+ * @private
+ */
+ public function set precision(value:Number):void {
+ if (_precision != value) {
+ _precision = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ var res:TextureMaterial = new TextureMaterial(_texture, _alpha, _repeat, _smooth, _blendMode, _wireThickness, _wireColor, _precision);
+ return res;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/TextureMaterialPrecision.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/TextureMaterialPrecision.as
new file mode 100644
index 0000000..22be349
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/TextureMaterialPrecision.as
@@ -0,0 +1,40 @@
+package alternativa.engine3d.materials {
+
+ /**
+ * Класс содержит константы точности перспективной коррекции текстурного материала.
+ *
+ * @see TextureMaterial
+ */
+ public class TextureMaterialPrecision {
+
+ /**
+ * Адаптивная триангуляция не будет выполняться, только простая триангуляция.
+ */
+ public static const NONE:Number = -1;
+ /**
+ * Очень низкое качество адаптивной триангуляции.
+ */
+ public static const VERY_LOW:Number = 50;
+ /**
+ * Низкое качество адаптивной триангуляции.
+ */
+ public static const LOW:Number = 25;
+ /**
+ * Среднее качество адаптивной триангуляции.
+ */
+ public static const MEDIUM:Number = 10;
+ /**
+ * Высокое качество адаптивной триангуляции.
+ */
+ public static const HIGH:Number = 6;
+ /**
+ * Очень высокое качество адаптивной триангуляции.
+ */
+ public static const VERY_HIGH:Number = 3;
+ /**
+ * Максимальное качество адаптивной триангуляции.
+ */
+ public static const BEST:Number = 1;
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/materials/WireMaterial.as b/Alternativa3D5/5.6/alternativa/engine3d/materials/WireMaterial.as
new file mode 100644
index 0000000..2407d74
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/materials/WireMaterial.as
@@ -0,0 +1,127 @@
+package alternativa.engine3d.materials {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Camera3D;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.display.Skin;
+
+ import flash.display.BlendMode;
+ import flash.display.Graphics;
+
+ use namespace alternativa3d;
+
+ /**
+ * Материал для рисования рёбер полигонов.
+ */
+ public class WireMaterial extends SurfaceMaterial {
+
+ /**
+ * @private
+ * Цвет
+ */
+ alternativa3d var _color:uint;
+ /**
+ * @private
+ * Толщина линий
+ */
+ alternativa3d var _thickness:Number;
+
+ /**
+ * Создание экземпляра класса.
+ *
+ * @param thickness толщина линий
+ * @param color цвет линий
+ * @param alpha коэффициент непрозрачности линий. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
+ * @param blendMode режим наложения цвета
+ */
+ public function WireMaterial(thickness:Number = 0, color:uint = 0, alpha:Number = 1, blendMode:String = BlendMode.NORMAL) {
+ super(alpha, blendMode);
+ _color = color;
+ _thickness = thickness;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
+ return _thickness >= 0;
+ }
+
+ /**
+ * @private
+ * @inheritDoc
+ */
+ override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
+ skin.alpha = _alpha;
+ skin.blendMode = _blendMode;
+
+ var i:uint;
+ var point:DrawPoint;
+ var gfx:Graphics = skin.gfx;
+
+ if (camera._orthographic) {
+ gfx.lineStyle(_thickness, _color);
+ point = points[length - 1];
+ gfx.moveTo(point.x, point.y);
+ for (i = 0; i < length; i++) {
+ point = points[i];
+ gfx.lineTo(point.x, point.y);
+ }
+ } else {
+ // Отрисовка
+ gfx.lineStyle(_thickness, _color);
+ point = points[length - 1];
+ var perspective:Number = camera._focalLength/point.z;
+ gfx.moveTo(point.x*perspective, point.y*perspective);
+ for (i = 0; i < length; i++) {
+ point = points[i];
+ perspective = camera._focalLength/point.z;
+ gfx.lineTo(point.x*perspective, point.y*perspective);
+ }
+ }
+ }
+
+ /**
+ * Цвет линий.
+ */
+ public function get color():uint {
+ return _color;
+ }
+
+ /**
+ * @private
+ */
+ public function set color(value:uint):void {
+ if (_color != value) {
+ _color = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * Толщина линий. Если толщина отрицательная, то отрисовка не выполняется.
+ */
+ public function get thickness():Number {
+ return _thickness;
+ }
+
+ /**
+ * @private
+ */
+ public function set thickness(value:Number):void {
+ if (_thickness != value) {
+ _thickness = value;
+ markToChange();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override public function clone():Material {
+ return new WireMaterial(_thickness, _color, _alpha, _blendMode);
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/physics/Collision.as b/Alternativa3D5/5.6/alternativa/engine3d/physics/Collision.as
new file mode 100644
index 0000000..3442e66
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/physics/Collision.as
@@ -0,0 +1,32 @@
+package alternativa.engine3d.physics {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.types.Point3D;
+
+ use namespace alternativa3d;
+
+ /**
+ * Параметры столкновения эллипсоида с гранью объекта. Плоскостью столкновения является касательная к
+ * эллипсоиду плоскость, проходящая через точку столкновения с гранью.
+ */
+ public class Collision {
+ /**
+ * Грань, с которой произошло столкновение.
+ */
+ public var face:Face;
+ /**
+ * Нормаль плоскости столкновения.
+ */
+ public var normal:Point3D;
+ /**
+ * Смещение плоскости столкновения.
+ */
+ public var offset:Number;
+ /**
+ * Координаты точки столкновения.
+ */
+ public var point:Point3D;
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/physics/CollisionPlane.as b/Alternativa3D5/5.6/alternativa/engine3d/physics/CollisionPlane.as
new file mode 100644
index 0000000..200b5a2
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/physics/CollisionPlane.as
@@ -0,0 +1,62 @@
+package alternativa.engine3d.physics {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.BSPNode;
+
+ use namespace alternativa3d;
+
+ /**
+ * @private
+ */
+ public class CollisionPlane {
+
+ // Узел BSP дерева, который содержит плоскость
+ public var node:BSPNode;
+ // Индикатор положения объекта относительно плоскости (спереди или сзади)
+ public var infront:Boolean;
+ // Расстояние до плоскости в начальной точке (всегда положительное)
+ public var sourceOffset:Number;
+ // Расстояние до плоскости в конечной точке
+ public var destinationOffset:Number;
+
+ // Хранилище неиспользуемых плоскостей
+ static private var collector:Array = new Array();
+
+ /**
+ * Создание плоскости
+ *
+ * @param node
+ * @param infront
+ * @param sourceOffset
+ * @param destinationOffset
+ * @return
+ */
+ static alternativa3d function createCollisionPlane(node:BSPNode, infront:Boolean, sourceOffset:Number, destinationOffset:Number):CollisionPlane {
+
+ // Достаём плоскость из коллектора
+ var plane:CollisionPlane = collector.pop();
+ // Если коллектор пуст, создаём новую плоскость
+ if (plane == null) {
+ plane = new CollisionPlane();
+ }
+
+ plane.node = node;
+ plane.infront = infront;
+ plane.sourceOffset = sourceOffset;
+ plane.destinationOffset = destinationOffset;
+
+ return plane;
+ }
+
+ /**
+ * Удаление плоскости, все ссылки должны быть почищены
+ *
+ * @param plane
+ */
+ static alternativa3d function destroyCollisionPlane(plane:CollisionPlane):void {
+ plane.node = null;
+ collector.push(plane);
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/physics/CollisionSetMode.as b/Alternativa3D5/5.6/alternativa/engine3d/physics/CollisionSetMode.as
new file mode 100644
index 0000000..0eba376
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/physics/CollisionSetMode.as
@@ -0,0 +1,20 @@
+package alternativa.engine3d.physics {
+
+ /**
+ * Константы, определяющие режим учёта объектов сцены, заданных в множестве EllipsoidCollider.collisionSet
+ * при определении столкновений.
+ *
+ * @see EllipsoidCollider#collisionSet
+ */
+ public class CollisionSetMode {
+ /**
+ * Грани объектов игнорируются при определении столкновений.
+ */
+ static public const EXCLUDE:int = 1;
+ /**
+ * Учитываются только столкновения с гранями, принадлежащим перечисленным в множестве объектам.
+ */
+ static public const INCLUDE:int = 2;
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/physics/EllipsoidCollider.as b/Alternativa3D5/5.6/alternativa/engine3d/physics/EllipsoidCollider.as
new file mode 100644
index 0000000..e2ef88a
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/physics/EllipsoidCollider.as
@@ -0,0 +1,1003 @@
+package alternativa.engine3d.physics {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.BSPNode;
+ import alternativa.engine3d.core.PolyPrimitive;
+ import alternativa.engine3d.core.Scene3D;
+ import alternativa.engine3d.core.SplitterPrimitive;
+ import alternativa.types.Point3D;
+ import alternativa.types.Set;
+ import alternativa.utils.ObjectUtils;
+
+ use namespace alternativa3d;
+
+ /**
+ * Класс реализует алгоритм непрерывного определения столкновений эллипсоида с плоскими выпуклыми многоугольниками.
+ */
+ public class EllipsoidCollider {
+
+ // Максимальное количество попыток найти свободное от столкновения со сценой направление
+ private static const MAX_COLLISIONS:uint = 50;
+ // Радиус наибольшей сферы
+ private var _radius:Number = 100;
+ private var _radius2:Number = _radius * _radius;
+ private var _radiusX:Number = _radius;
+ private var _radiusY:Number = _radius;
+ private var _radiusZ:Number = _radius;
+ private var _radiusX2:Number = _radiusX * _radiusX;
+ private var _radiusY2:Number = _radiusY * _radiusY;
+ private var _radiusZ2:Number = _radiusZ * _radiusZ;
+ // Коэффициенты масштабирования осей
+ private var _scaleX:Number = 1;
+ private var _scaleY:Number = 1;
+ private var _scaleZ:Number = 1;
+ // Квадраты коэффициентов масштабирования осей
+ private var _scaleX2:Number = 1;
+ private var _scaleY2:Number = 1;
+ private var _scaleZ2:Number = 1;
+
+ private var collisionSource:Point3D;
+ private var currentDisplacement:Point3D = new Point3D();
+ private var collisionDestination:Point3D = new Point3D();
+
+ private var collisionPlanes:Array = new Array();
+ private var collisionPrimitive:PolyPrimitive;
+ private var collisionPrimitiveNearest:PolyPrimitive;
+ private var collisionPlanePoint:Point3D = new Point3D();
+ private var collisionPrimitiveNearestLengthSqr:Number;
+ private var collisionPrimitivePoint:Point3D = new Point3D();
+
+ private var collisionNormal:Point3D = new Point3D();
+ private var collisionPoint:Point3D = new Point3D();
+ private var collisionOffset:Number;
+
+ private var currentCoords:Point3D = new Point3D();
+ private var collision:Collision = new Collision();
+ private var collisionRadius:Number;
+ private var radiusVector:Point3D = new Point3D();
+ private var p1:Point3D = new Point3D();
+ private var p2:Point3D = new Point3D();;
+ private var localCollisionPlanePoint:Point3D = new Point3D();
+
+ // Флаг использования упорщённого алгоритма. Включается когда эллипсоид представляет собой сферу.
+ private var useSimpleAlgorithm:Boolean = true;
+
+ /**
+ * Сцена, в которой определяются столкновения.
+ */
+ public var scene:Scene3D;
+ /**
+ * Погрешность определения расстояний и координат. Две точки совпадают, если модуль разности любых соответствующих
+ * координат меньше указанной погрешности.
+ */
+ public var offsetThreshold:Number = 0.0001;
+ /**
+ * Множество объектов, учитываемых в процессе определения столкновений. В качестве объектов могут выступать экземпляры
+ * классов Mesh и Surface. Каким образом учитываются перечисленные в множестве объекты зависит
+ * от значения поля collisionSetMode. Значение null эквивалентно заданию пустого множества.
+ *
+ * @see #collisionSetMode
+ * @see alternativa.engine3d.core.Mesh
+ * @see alternativa.engine3d.core.Surface
+ */
+ public var collisionSet:Set;
+ /**
+ * Параметр определяет, каким образом учитываются объекты, перечисленные в множестве collisionSet. Если
+ * значение параметра равно true, то грани объектов из множества игнорируются при определении столкновений.
+ * При значении параметра false учитываются только столкновения с гранями, принадлежащим перечисленным
+ * в множестве объектам.
+ *
+ * @default true
+ * @see #collisionSet
+ */
+ private var _collisionSetMode:int = CollisionSetMode.EXCLUDE;
+
+ /**
+ * Создаёт новый экземпляр класса.
+ *
+ * @param scene сцена, в которой определяются столкновения
+ * @param scaleX радиус эллипсоида по оси X
+ * @param scaleY радиус эллипсоида по оси Y
+ * @param scaleZ радиус эллипсоида по оси Z
+ */
+ public function EllipsoidCollider(scene:Scene3D = null, radiusX:Number = 100, radiusY:Number = 100, radiusZ:Number = 100) {
+ this.scene = scene;
+ this.radiusX = radiusX;
+ this.radiusY = radiusY;
+ this.radiusZ = radiusZ;
+ }
+
+ /**
+ * Параметр определяет, каким образом учитываются объекты, перечисленные в множестве collisionSet.
+ *
+ * @default CollisionSetMode.EXCLUDE
+ * @see #collisionSet
+ * @see CollisionSetMode
+ *
+ * @throws ArgumentError было указано значение не являющееся константой CollisionSetMode
+ */
+ public function get collisionSetMode():int {
+ return _collisionSetMode;
+ }
+
+ /**
+ * @private
+ */
+ public function set collisionSetMode(value:int):void {
+ if (value != CollisionSetMode.EXCLUDE && value != CollisionSetMode.INCLUDE) {
+ throw ArgumentError(ObjectUtils.getClassName(this) + ".collisionSetMode invalid value");
+ }
+ _collisionSetMode = value;
+ }
+
+ /**
+ * Величина радиуса (полуоси) эллипсоида по оси X. При установке отрицательного значения берётся модуль.
+ *
+ * @default 100
+ */
+ public function get radiusX():Number {
+ return _radiusX;
+ }
+
+ /**
+ * @private
+ */
+ public function set radiusX(value:Number):void {
+ _radiusX = value >= 0 ? value : -value;
+ _radiusX2 = _radiusX * _radiusX;
+ calculateScales();
+ }
+
+ /**
+ * Величина радиуса (полуоси) эллипсоида по оси Y. При установке отрицательного значения берётся модуль.
+ *
+ * @default 100
+ */
+ public function get radiusY():Number {
+ return _radiusY;
+ }
+
+ /**
+ * @private
+ */
+ public function set radiusY(value:Number):void {
+ _radiusY = value >= 0 ? value : -value;
+ _radiusY2 = _radiusY * _radiusY;
+ calculateScales();
+ }
+
+ /**
+ * Величина радиуса (полуоси) эллипсоида по оси Z. При установке отрицательного значения берётся модуль.
+ *
+ * @default 100
+ */
+ public function get radiusZ():Number {
+ return _radiusZ;
+ }
+
+ /**
+ * @private
+ */
+ public function set radiusZ(value:Number):void {
+ _radiusZ = value >= 0 ? value : -value;
+ _radiusZ2 = _radiusZ * _radiusZ;
+ calculateScales();
+ }
+
+ /**
+ * Расчёт коэффициентов масштабирования осей.
+ */
+ private function calculateScales():void {
+ _radius = _radiusX;
+ if (_radiusY > _radius) {
+ _radius = _radiusY;
+ }
+ if (_radiusZ > _radius) {
+ _radius = _radiusZ;
+ }
+ _radius2 = _radius * _radius;
+ _scaleX = _radiusX / _radius;
+ _scaleY = _radiusY / _radius;
+ _scaleZ = _radiusZ / _radius;
+ _scaleX2 = _scaleX * _scaleX;
+ _scaleY2 = _scaleY * _scaleY;
+ _scaleZ2 = _scaleZ * _scaleZ;
+
+ useSimpleAlgorithm = (_radiusX == _radiusY) && (_radiusX == _radiusZ);
+ }
+
+ /**
+ * Расчёт конечного положения эллипсоида по заданному начальному положению и вектору смещения. Если задано значение
+ * поля scene, то при вычислении конечного положения учитываются столкновения с объектами сцены,
+ * принимая во внимание множество collisionSet и режим работы collisionSetMode. Если
+ * значение поля scene равно null, то результат работы метода будет простой суммой двух
+ * входных векторов.
+ *
+ * @param sourcePoint начальное положение центра эллипсоида в системе координат корневого объекта сцены
+ * @param displacementVector вектор перемещения эллипсоида в системе координат корневого объекта сцены. Если модуль
+ * каждого компонента вектора не превышает значения offsetThreshold, эллипсоид остаётся в начальной точке.
+ * @param destinationPoint в эту переменную записывается расчётное положение центра эллипсоида в системе координат
+ * корневого объекта сцены
+ *
+ * @see #scene
+ * @see #collisionSet
+ * @see #collisionSetMode
+ * @see #offsetThreshold
+ */
+ public function calculateDestination(sourcePoint:Point3D, displacementVector:Point3D, destinationPoint:Point3D):void {
+ // Расчеты не производятся, если перемещение мало
+ if (displacementVector.x < offsetThreshold && displacementVector.x > -offsetThreshold &&
+ displacementVector.y < offsetThreshold && displacementVector.y > -offsetThreshold &&
+ displacementVector.z < offsetThreshold && displacementVector.z > -offsetThreshold) {
+ destinationPoint.x = sourcePoint.x;
+ destinationPoint.y = sourcePoint.y;
+ destinationPoint.z = sourcePoint.z;
+ return;
+ }
+
+ // Начальные координаты
+ currentCoords.x = sourcePoint.x;
+ currentCoords.y = sourcePoint.y;
+ currentCoords.z = sourcePoint.z;
+ // Начальный вектор перемещения
+ currentDisplacement.x = displacementVector.x;
+ currentDisplacement.y = displacementVector.y;
+ currentDisplacement.z = displacementVector.z;
+ // Начальная точка назначения
+ destinationPoint.x = sourcePoint.x + currentDisplacement.x;
+ destinationPoint.y = sourcePoint.y + currentDisplacement.y;
+ destinationPoint.z = sourcePoint.z + currentDisplacement.z;
+
+ if (useSimpleAlgorithm) {
+ calculateDestinationS(sourcePoint, destinationPoint);
+ } else {
+ calculateDestinationE(sourcePoint, destinationPoint);
+ }
+ }
+
+ /**
+ * Вычисление точки назначения для сферы.
+ * @param sourcePoint
+ * @param destinationPoint
+ */
+ private function calculateDestinationS(sourcePoint:Point3D, destinationPoint:Point3D):void {
+ var collisionCount:uint = 0;
+ var hasCollision:Boolean;
+ do {
+ hasCollision = getCollision(currentCoords, currentDisplacement, collision);
+ if (hasCollision ) {
+ // Вынос точки назначения из-за плоскости столкновения на высоту радиуса сферы над плоскостью по направлению нормали
+ var offset:Number = _radius + offsetThreshold + collision.offset - destinationPoint.x*collision.normal.x - destinationPoint.y*collision.normal.y - destinationPoint.z*collision.normal.z;
+ destinationPoint.x += collision.normal.x * offset;
+ destinationPoint.y += collision.normal.y * offset;
+ destinationPoint.z += collision.normal.z * offset;
+ // Коррекция текущих кординат центра сферы для следующей итерации
+ currentCoords.x = collision.point.x + collision.normal.x * (_radius + offsetThreshold);
+ currentCoords.y = collision.point.y + collision.normal.y * (_radius + offsetThreshold);
+ currentCoords.z = collision.point.z + collision.normal.z * (_radius + offsetThreshold);
+ // Коррекция вектора скорости. Результирующий вектор направлен вдоль плоскости столкновения.
+ currentDisplacement.x = destinationPoint.x - currentCoords.x;
+ currentDisplacement.y = destinationPoint.y - currentCoords.y;
+ currentDisplacement.z = destinationPoint.z - currentCoords.z;
+
+ // Если смещение слишком мало, останавливаемся
+ if (currentDisplacement.x < offsetThreshold && currentDisplacement.x > -offsetThreshold &&
+ currentDisplacement.y < offsetThreshold && currentDisplacement.y > -offsetThreshold &&
+ currentDisplacement.z < offsetThreshold && currentDisplacement.z > -offsetThreshold) {
+ break;
+ }
+ }
+ } while (hasCollision && (++collisionCount < MAX_COLLISIONS));
+ // Если количество итераций достигло максимально возможного значения, то остаемся на старом месте
+ if (collisionCount == MAX_COLLISIONS) {
+ destinationPoint.x = sourcePoint.x;
+ destinationPoint.y = sourcePoint.y;
+ destinationPoint.z = sourcePoint.z;
+ }
+ }
+
+ /**
+ * Вычисление точки назначения для эллипсоида.
+ * @param destinationPoint
+ * @return
+ */
+ private function calculateDestinationE(sourcePoint:Point3D, destinationPoint:Point3D):void {
+ var collisionCount:uint = 0;
+ var hasCollision:Boolean;
+ // Цикл выполняется до тех пор, пока не будет найдено ни одного столкновения на очередной итерации или пока не
+ // будет достигнуто максимально допустимое количество столкновений, что означает зацикливание алгоритма и
+ // необходимость принудительного выхода.
+ do {
+ hasCollision = getCollision(currentCoords, currentDisplacement, collision);
+ if (hasCollision) {
+ // Вынос точки назначения из-за плоскости столкновения на высоту эффективного радиуса эллипсоида над плоскостью по направлению нормали
+ var offset:Number = collisionRadius + offsetThreshold + collision.offset - destinationPoint.x * collision.normal.x - destinationPoint.y * collision.normal.y - destinationPoint.z * collision.normal.z;
+ destinationPoint.x += collision.normal.x * offset;
+ destinationPoint.y += collision.normal.y * offset;
+ destinationPoint.z += collision.normal.z * offset;
+ // Коррекция текущих кординат центра эллипсоида для следующей итерации
+ collisionRadius = (collisionRadius + offsetThreshold) / collisionRadius;
+ currentCoords.x = collision.point.x - collisionRadius * radiusVector.x;
+ currentCoords.y = collision.point.y - collisionRadius * radiusVector.y;
+ currentCoords.z = collision.point.z - collisionRadius * radiusVector.z;
+ // Коррекция вектора смещения. Результирующий вектор направлен параллельно плоскости столкновения.
+ currentDisplacement.x = destinationPoint.x - currentCoords.x;
+ currentDisplacement.y = destinationPoint.y - currentCoords.y;
+ currentDisplacement.z = destinationPoint.z - currentCoords.z;
+ // Если смещение слишком мало, останавливаемся
+ if (currentDisplacement.x < offsetThreshold && currentDisplacement.x > -offsetThreshold &&
+ currentDisplacement.y < offsetThreshold && currentDisplacement.y > -offsetThreshold &&
+ currentDisplacement.z < offsetThreshold && currentDisplacement.z > -offsetThreshold) {
+ destinationPoint.x = currentCoords.x;
+ destinationPoint.y = currentCoords.y;
+ destinationPoint.z = currentCoords.z;
+ break;
+ }
+ }
+ } while (hasCollision && (++collisionCount < MAX_COLLISIONS));
+ // Если количество итераций достигло максимально возможного значения, то остаемся на старом месте
+ if (collisionCount == MAX_COLLISIONS) {
+ destinationPoint.x = sourcePoint.x;
+ destinationPoint.y = sourcePoint.y;
+ destinationPoint.z = sourcePoint.z;
+ }
+ }
+
+ /**
+ * Метод определяет наличие столкновения при смещении эллипсоида из заданной точки на величину указанного вектора
+ * перемещения, принимая во внимание множество collisionSet и режим работы collisionSetMode.
+ *
+ * @param sourcePoint начальное положение центра эллипсоида в системе координат корневого объекта сцены
+ * @param displacementVector вектор перемещения эллипсоида в системе координат корневого объекта сцены
+ * @param collision в эту переменную будут записаны данные о плоскости и точке столкновения в системе координат
+ * корневого объекта сцены
+ *
+ * @return true, если эллипсоид при заданном перемещении столкнётся с каким-либо полигоном сцены,
+ * false если столкновений нет или не задано значение поля scene.
+ *
+ * @see #scene
+ * @see #collisionSet
+ * @see #collisionSetMode
+ */
+ public function getCollision(sourcePoint:Point3D, displacementVector:Point3D, collision:Collision):Boolean {
+ if (scene == null) {
+ return false;
+ }
+
+ collisionSource = sourcePoint;
+
+ currentDisplacement.x = displacementVector.x;
+ currentDisplacement.y = displacementVector.y;
+ currentDisplacement.z = displacementVector.z;
+
+ collisionDestination.x = collisionSource.x + currentDisplacement.x;
+ collisionDestination.y = collisionSource.y + currentDisplacement.y;
+ collisionDestination.z = collisionSource.z + currentDisplacement.z;
+
+ collectPotentialCollisionPlanes(scene.bsp);
+ collisionPlanes.sortOn("sourceOffset", Array.NUMERIC | Array.DESCENDING);
+
+ var plane:CollisionPlane;
+ // Пока не найдём столкновение с примитивом или плоскости не кончатся
+ if (useSimpleAlgorithm) {
+ while ((plane = collisionPlanes.pop()) != null) {
+ if (collisionPrimitive == null) {
+ calculateCollisionWithPlaneS(plane);
+ }
+ CollisionPlane.destroyCollisionPlane(plane);
+ }
+ } else {
+ while ((plane = collisionPlanes.pop()) != null) {
+ if (collisionPrimitive == null) {
+ calculateCollisionWithPlaneE(plane);
+ }
+ CollisionPlane.destroyCollisionPlane(plane);
+ }
+ }
+
+ var collisionFound:Boolean = collisionPrimitive != null;
+ if (collisionFound) {
+ collision.face = collisionPrimitive.face;
+ collision.normal = collisionNormal;
+ collision.offset = collisionOffset;
+ collision.point = collisionPoint;
+ }
+
+ collisionPrimitive = null;
+ collisionSource = null;
+
+ return collisionFound;
+ }
+
+ /**
+ * Сбор потенциальных плоскостей столкновения.
+ *
+ * @param node текущий узел BSP-дерева
+ */
+ private function collectPotentialCollisionPlanes(node:BSPNode):void {
+ if (node == null) {
+ return;
+ }
+
+ var sourceOffset:Number = collisionSource.x * node.normal.x + collisionSource.y * node.normal.y + collisionSource.z * node.normal.z - node.offset;
+ var destinationOffset:Number = collisionDestination.x * node.normal.x + collisionDestination.y * node.normal.y + collisionDestination.z * node.normal.z - node.offset;
+ var plane:CollisionPlane;
+
+ if (sourceOffset >= 0) {
+ // Исходное положение центра перед плоскостью ноды
+ // Проверяем передние ноды
+ collectPotentialCollisionPlanes(node.front);
+ // Грубая оценка пересечения с плоскостью по радиусу ограничивающей сферы эллипсоида
+ // Или мы наткнулись на спрайтовую ноду
+ if (destinationOffset < _radius && !node.isSprite) {
+ // Добавляем плоскость если это не сплитеровая нода
+ if (node.splitter == null) {
+ // Нашли потенциальное пересечение с плоскостью
+ plane = CollisionPlane.createCollisionPlane(node, true, sourceOffset, destinationOffset);
+ collisionPlanes.push(plane);
+ }
+ // Проверяем задние ноды
+ collectPotentialCollisionPlanes(node.back);
+ }
+ } else {
+ // Исходное положение центра за плоскостью ноды
+ // Проверяем задние ноды
+ collectPotentialCollisionPlanes(node.back);
+ // Грубая оценка пересечения с плоскостью по радиусу ограничивающей сферы эллипсоида
+ if (destinationOffset > -_radius) {
+ // Столкновение возможно только в случае если в ноде есть примитивы, направленные назад
+ if (node.backPrimitives != null) {
+ // Нашли потенциальное пересечение с плоскостью
+ plane = CollisionPlane.createCollisionPlane(node, false, -sourceOffset, -destinationOffset);
+ collisionPlanes.push(plane);
+ }
+ // Проверяем передние ноды
+ collectPotentialCollisionPlanes(node.front);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Определение пересечения сферы с примитивами, лежащими в заданной плоскости.
+ *
+ * @param plane плоскость, содержащая примитивы для проверки
+ */
+ private function calculateCollisionWithPlaneS(plane:CollisionPlane):void {
+ collisionPlanePoint.copy(collisionSource);
+
+ var normal:Point3D = plane.node.normal;
+ // Если сфера врезана в плоскость
+ if (plane.sourceOffset <= _radius) {
+ if (plane.infront) {
+ collisionPlanePoint.x -= normal.x * plane.sourceOffset;
+ collisionPlanePoint.y -= normal.y * plane.sourceOffset;
+ collisionPlanePoint.z -= normal.z * plane.sourceOffset;
+ } else {
+ collisionPlanePoint.x += normal.x * plane.sourceOffset;
+ collisionPlanePoint.y += normal.y * plane.sourceOffset;
+ collisionPlanePoint.z += normal.z * plane.sourceOffset;
+ }
+ } else {
+ // Находим центр сферы во время столкновения с плоскостью
+ var time:Number = (plane.sourceOffset - _radius) / (plane.sourceOffset - plane.destinationOffset);
+ collisionPlanePoint.x = collisionSource.x + currentDisplacement.x * time;
+ collisionPlanePoint.y = collisionSource.y + currentDisplacement.y * time;
+ collisionPlanePoint.z = collisionSource.z + currentDisplacement.z * time;
+
+ // Устанавливаем точку пересечения cферы с плоскостью
+ if (plane.infront) {
+ collisionPlanePoint.x -= normal.x * _radius;
+ collisionPlanePoint.y -= normal.y * _radius;
+ collisionPlanePoint.z -= normal.z * _radius;
+ } else {
+ collisionPlanePoint.x += normal.x * _radius;
+ collisionPlanePoint.y += normal.y * _radius;
+ collisionPlanePoint.z += normal.z * _radius;
+ }
+ }
+
+ // Проверяем примитивы плоскости
+ var primitive:*;
+ collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE;
+ collisionPrimitiveNearest = null;
+ if (plane.infront) {
+ if ((primitive = plane.node.primitive) != null) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveS(plane.node.primitive);
+ }
+ } else {
+ for (primitive in plane.node.frontPrimitives) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveS(primitive);
+ if (collisionPrimitive != null) break;
+ }
+ }
+ }
+ } else {
+ for (primitive in plane.node.backPrimitives) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveS(primitive);
+ if (collisionPrimitive != null) break;
+ }
+ }
+ }
+
+ if (collisionPrimitive != null) {
+ // Если точка пересечения попала в примитив
+
+ // Нормаль плоскости при столкновении - нормаль плоскости
+ if (plane.infront) {
+ collisionNormal.x = normal.x;
+ collisionNormal.y = normal.y;
+ collisionNormal.z = normal.z;
+ collisionOffset = plane.node.offset;
+ } else {
+ collisionNormal.x = -normal.x;
+ collisionNormal.y = -normal.y;
+ collisionNormal.z = -normal.z;
+ collisionOffset = -plane.node.offset;
+ }
+
+ // Точка столкновения в точке столкновения с плоскостью
+ collisionPoint.x = collisionPlanePoint.x;
+ collisionPoint.y = collisionPlanePoint.y;
+ collisionPoint.z = collisionPlanePoint.z;
+
+ } else {
+ // Если точка пересечения не попала ни в один примитив, проверяем столкновение с ближайшей
+
+ // Вектор из ближайшей точки в центр сферы
+ var nearestPointToSourceX:Number = collisionSource.x - collisionPrimitivePoint.x;
+ var nearestPointToSourceY:Number = collisionSource.y - collisionPrimitivePoint.y;
+ var nearestPointToSourceZ:Number = collisionSource.z - collisionPrimitivePoint.z;
+
+ // Если движение в сторону точки
+ if (nearestPointToSourceX * currentDisplacement.x + nearestPointToSourceY * currentDisplacement.y + nearestPointToSourceZ * currentDisplacement.z <= 0) {
+
+ // Ищем нормализованный вектор обратного направления
+ var vectorLength:Number = Math.sqrt(currentDisplacement.x * currentDisplacement.x + currentDisplacement.y * currentDisplacement.y + currentDisplacement.z * currentDisplacement.z);
+ var vectorX:Number = -currentDisplacement.x / vectorLength;
+ var vectorY:Number = -currentDisplacement.y / vectorLength;
+ var vectorZ:Number = -currentDisplacement.z / vectorLength;
+
+ // Длина вектора из ближайшей точки в центр сферы
+ var nearestPointToSourceLengthSqr:Number = nearestPointToSourceX * nearestPointToSourceX + nearestPointToSourceY * nearestPointToSourceY + nearestPointToSourceZ * nearestPointToSourceZ;
+
+ // Проекция вектора из ближайшей точки в центр сферы на нормализованный вектор обратного направления
+ var projectionLength:Number = nearestPointToSourceX * vectorX + nearestPointToSourceY * vectorY + nearestPointToSourceZ * vectorZ;
+
+ var projectionInsideSphereLengthSqr:Number = _radius2 - nearestPointToSourceLengthSqr + projectionLength * projectionLength;
+
+ if (projectionInsideSphereLengthSqr > 0) {
+ // Находим расстояние из ближайшей точки до сферы
+ var distance:Number = projectionLength - Math.sqrt(projectionInsideSphereLengthSqr);
+
+ if (distance < vectorLength) {
+ // Столкновение сферы с ближайшей точкой произошло
+
+ // Точка столкновения в ближайшей точке
+ collisionPoint.x = collisionPrimitivePoint.x;
+ collisionPoint.y = collisionPrimitivePoint.y;
+ collisionPoint.z = collisionPrimitivePoint.z;
+
+ // Находим нормаль плоскости столкновения
+ var nearestPointToSourceLength:Number = Math.sqrt(nearestPointToSourceLengthSqr);
+ collisionNormal.x = nearestPointToSourceX / nearestPointToSourceLength;
+ collisionNormal.y = nearestPointToSourceY / nearestPointToSourceLength;
+ collisionNormal.z = nearestPointToSourceZ / nearestPointToSourceLength;
+
+ // Смещение плоскости столкновения
+ collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z;
+ collisionPrimitive = collisionPrimitiveNearest;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Определение столкновения сферы с примитивом.
+ *
+ * @param primitive примитив, столкновение с которым проверяется
+ */
+ private function calculateCollisionWithPrimitiveS(primitive:PolyPrimitive):void {
+
+ var length:uint = primitive.num;
+ var points:Array = primitive.points;
+ var normal:Point3D = primitive.face.globalNormal;
+ var inside:Boolean = true;
+
+ for (var i:uint = 0; i < length; i++) {
+
+ var p1:Point3D = points[i];
+ var p2:Point3D = points[(i < length - 1) ? (i + 1) : 0];
+
+ var edgeX:Number = p2.x - p1.x;
+ var edgeY:Number = p2.y - p1.y;
+ var edgeZ:Number = p2.z - p1.z;
+
+ var vectorX:Number = collisionPlanePoint.x - p1.x;
+ var vectorY:Number = collisionPlanePoint.y - p1.y;
+ var vectorZ:Number = collisionPlanePoint.z - p1.z;
+
+ var crossX:Number = vectorY * edgeZ - vectorZ * edgeY;
+ var crossY:Number = vectorZ * edgeX - vectorX * edgeZ;
+ var crossZ:Number = vectorX * edgeY - vectorY * edgeX;
+
+ if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) {
+ // Точка за пределами полигона
+ inside = false;
+
+ var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ;
+ var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr;
+
+ // Если расстояние до прямой меньше текущего ближайшего
+ if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) {
+
+ // Ищем нормализованный вектор ребра
+ var edgeLength:Number = Math.sqrt(edgeLengthSqr);
+ var edgeNormX:Number = edgeX / edgeLength;
+ var edgeNormY:Number = edgeY / edgeLength;
+ var edgeNormZ:Number = edgeZ / edgeLength;
+
+ // Находим расстояние до точки перпендикуляра вдоль ребра
+ var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ;
+
+ var vectorLengthSqr:Number;
+ if (t < 0) {
+ // Ближайшая точка - первая
+ vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
+ if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
+ collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
+ collisionPrimitivePoint.x = p1.x;
+ collisionPrimitivePoint.y = p1.y;
+ collisionPrimitivePoint.z = p1.z;
+ collisionPrimitiveNearest = primitive;
+ }
+ } else {
+ if (t > edgeLength) {
+ // Ближайшая точка - вторая
+ vectorX = collisionPlanePoint.x - p2.x;
+ vectorY = collisionPlanePoint.y - p2.y;
+ vectorZ = collisionPlanePoint.z - p2.z;
+ vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
+ if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
+ collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
+ collisionPrimitivePoint.x = p2.x;
+ collisionPrimitivePoint.y = p2.y;
+ collisionPrimitivePoint.z = p2.z;
+ collisionPrimitiveNearest = primitive;
+ }
+ } else {
+ // Ближайшая точка на ребре
+ collisionPrimitiveNearestLengthSqr = edgeDistanceSqr;
+ collisionPrimitivePoint.x = p1.x + edgeNormX * t;
+ collisionPrimitivePoint.y = p1.y + edgeNormY * t;
+ collisionPrimitivePoint.z = p1.z + edgeNormZ * t;
+ collisionPrimitiveNearest = primitive;
+ }
+ }
+ }
+ }
+ }
+
+ // Если попали в примитив
+ if (inside) {
+ collisionPrimitive = primitive;
+ }
+ }
+
+ /**
+ * Проверка на действительное столкновение эллипсоида с плоскостью.
+ */
+ private function calculateCollisionWithPlaneE(plane:CollisionPlane):void {
+ var normalX:Number = plane.node.normal.x;
+ var normalY:Number = plane.node.normal.y;
+ var normalZ:Number = plane.node.normal.z;
+ // Смещение по направлению к плоскости вдоль нормали. Положительное смещение означает приближение к плоскости, отрицательное -- удаление
+ // от плоскости, в этом случае столкновения не происходит.
+ var displacementAlongNormal:Number = currentDisplacement.x * normalX + currentDisplacement.y * normalY + currentDisplacement.z * normalZ;
+ if (plane.infront) {
+ displacementAlongNormal = -displacementAlongNormal;
+ }
+ // Выходим из функции в случае удаления от плоскости
+ if (displacementAlongNormal < 0) {
+ return;
+ }
+ // Определение ближайшей к плоскости точки эллипсоида
+ var k:Number = _radius / Math.sqrt(normalX * normalX * _scaleX2 + normalY * normalY * _scaleY2 + normalZ * normalZ * _scaleZ2);
+ // Положение точки в локальной системе координат эллипсоида
+ var localClosestX:Number = k * normalX * _scaleX2;
+ var localClosestY:Number = k * normalY * _scaleY2;
+ var localClosestZ:Number = k * normalZ * _scaleZ2;
+ // Глобальные координаты точки
+ var px:Number = collisionSource.x + localClosestX;
+ var py:Number = collisionSource.y + localClosestY;
+ var pz:Number = collisionSource.z + localClosestZ;
+ // Растояние от найденной точки эллипсоида до плоскости
+ var closestPointDistance:Number = px * normalX + py * normalY + pz * normalZ - plane.node.offset;
+ if (!plane.infront) {
+ closestPointDistance = -closestPointDistance;
+ }
+ if (closestPointDistance > plane.sourceOffset) {
+ // Найдена наиболее удалённая точка, расчитываем вторую
+ px = collisionSource.x - localClosestX;
+ py = collisionSource.y - localClosestY;
+ pz = collisionSource.z - localClosestZ;
+ closestPointDistance = px * normalX + py * normalY + pz * normalZ - plane.node.offset;
+ if (!plane.infront) {
+ closestPointDistance = -closestPointDistance;
+ }
+ }
+ // Если расстояние от ближайшей точки эллипсоида до плоскости больше, чем смещение эллипсоида вдоль нормали плоскости,
+ // то столкновения не произошло и нужно завершить выполнение функции
+ if (closestPointDistance > displacementAlongNormal) {
+ return;
+ }
+ // Если добрались до этого места, значит произошло столкновение с плоскостью. Требуется определить точку столкновения
+ // с ближайшим полигоном, лежащим в этой плоскости
+ if (closestPointDistance <= 0 ) {
+ // Эллипсоид пересекается с плоскостью, ищем проекцию ближайшей точки эллипсоида на плоскость
+ if (plane.infront) {
+ collisionPlanePoint.x = px - normalX * closestPointDistance;
+ collisionPlanePoint.y = py - normalY * closestPointDistance;
+ collisionPlanePoint.z = pz - normalZ * closestPointDistance;
+ } else {
+ collisionPlanePoint.x = px + normalX * closestPointDistance;
+ collisionPlanePoint.y = py + normalY * closestPointDistance;
+ collisionPlanePoint.z = pz + normalZ * closestPointDistance;
+ }
+ } else {
+ // Эллипсоид не пересекается с плоскостью, ищем точку контакта
+ var t:Number = closestPointDistance / displacementAlongNormal;
+ collisionPlanePoint.x = px + currentDisplacement.x * t;
+ collisionPlanePoint.y = py + currentDisplacement.y * t;
+ collisionPlanePoint.z = pz + currentDisplacement.z * t;
+ }
+ // Проверяем примитивы плоскости
+ var primitive:*;
+ collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE;
+ collisionPrimitiveNearest = null;
+ if (plane.infront) {
+ if ((primitive = plane.node.primitive) != null) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveE(primitive);
+ }
+ } else {
+ for (primitive in plane.node.frontPrimitives) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveE(primitive);
+ if (collisionPrimitive != null) {
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ for (primitive in plane.node.backPrimitives) {
+ if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
+ ((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
+ calculateCollisionWithPrimitiveE(primitive);
+ if (collisionPrimitive != null) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (collisionPrimitive != null) {
+ // Если точка пересечения попала в примитив
+ // Нормаль плоскости при столкновении - нормаль плоскости примитива
+ if (plane.infront) {
+ collisionNormal.x = normalX;
+ collisionNormal.y = normalY;
+ collisionNormal.z = normalZ;
+ collisionOffset = plane.node.offset;
+ } else {
+ collisionNormal.x = -normalX;
+ collisionNormal.y = -normalY;
+ collisionNormal.z = -normalZ;
+ collisionOffset = -plane.node.offset;
+ }
+ // Радиус эллипсоида в точке столкновения
+ collisionRadius = localClosestX * collisionNormal.x + localClosestY * collisionNormal.y + localClosestZ * collisionNormal.z;
+ if (collisionRadius < 0) {
+ collisionRadius = -collisionRadius;
+ }
+ radiusVector.x = px - collisionSource.x;
+ radiusVector.y = py - collisionSource.y;
+ radiusVector.z = pz - collisionSource.z;
+ // Точка столкновения совпадает с точкой столкновения с плоскостью примитива
+ collisionPoint.x = collisionPlanePoint.x;
+ collisionPoint.y = collisionPlanePoint.y;
+ collisionPoint.z = collisionPlanePoint.z;
+ } else {
+ // Если точка пересечения не попала внутрь примитива, находим пересечение с ближайшей точкой ближайшего примитива
+ // Трансформированная в пространство эллипсоида ближайшая точка на примитиве
+ px = collisionPrimitivePoint.x;
+ py = collisionPrimitivePoint.y;
+ pz = collisionPrimitivePoint.z;
+
+ var collisionExists:Boolean;
+ // Квадрат расстояния из центра эллипсоида до точки примитива
+ var r2:Number = px*px + py*py + pz*pz;
+ if (r2 < _radius2) {
+ // Точка оказалась внутри эллипсоида, находим точку на поверхности эллипсоида, лежащую на том же радиусе
+ k = _radius / Math.sqrt(r2);
+ px *= k * _scaleX;
+ py *= k * _scaleY;
+ pz *= k * _scaleZ;
+
+ collisionExists = true;
+ } else {
+ // Точка вне эллипсоида, находим пересечение луча, направленного противоположно скорости эллипсоида из точки
+ // примитива, с поверхностью эллипсоида
+ // Трансформированный в пространство эллипсоида противоположный вектор скорости
+ var vx:Number = - currentDisplacement.x / _scaleX;
+ var vy:Number = - currentDisplacement.y / _scaleY;
+ var vz:Number = - currentDisplacement.z / _scaleZ;
+ // Нахождение точки пересечения сферы и луча, направленного вдоль вектора скорости
+ var a:Number = vx*vx + vy*vy + vz*vz;
+ var b:Number = 2 * (px*vx + py*vy + pz*vz);
+ var c:Number = r2 - _radius2;
+ var d:Number = b*b - 4*a*c;
+ // Решение есть только при действительном дискриминанте квадратного уравнения
+ if (d >=0) {
+ // Выбирается минимальное время, т.к. нужна первая точка пересечения
+ t = -0.5 * (b + Math.sqrt(d)) / a;
+ // Точка лежит на луче только если время положительное
+ if (t >= 0 && t <= 1) {
+ // Координаты точки пересечения луча с эллипсоидом, переведённые обратно в нормальное пространство
+ px = (px + t * vx) * _scaleX;
+ py = (py + t * vy) * _scaleY;
+ pz = (pz + t * vz) * _scaleZ;
+
+ collisionExists = true;
+ }
+ }
+ }
+ if (collisionExists) {
+ // Противоположная нормаль к эллипсоиду в точке пересечения
+ collisionNormal.x = - px / _scaleX2;
+ collisionNormal.y = - py / _scaleY2;
+ collisionNormal.z = - pz / _scaleZ2;
+ collisionNormal.normalize();
+ // Радиус эллипсоида в точке столкновения
+ collisionRadius = px * collisionNormal.x + py * collisionNormal.y + pz * collisionNormal.z;
+ if (collisionRadius < 0) {
+ collisionRadius = -collisionRadius;
+ }
+ radiusVector.x = px;
+ radiusVector.y = py;
+ radiusVector.z = pz;
+ // Точка столкновения в ближайшей точке
+ collisionPoint.x = collisionPrimitivePoint.x * _scaleX + currentCoords.x;
+ collisionPoint.y = collisionPrimitivePoint.y * _scaleY + currentCoords.y;
+ collisionPoint.z = collisionPrimitivePoint.z * _scaleZ + currentCoords.z;
+ // Смещение плоскости столкновения
+ collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z;
+ collisionPrimitive = collisionPrimitiveNearest;
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Определение наличия столкновения эллипсоида с примитивом. Все расчёты выполняются в пространстве эллипсоида, где он выглядит
+ * как сфера. По окончании работы может быть установлена переменная collisionPrimitive в случае попадания точки
+ * столкновения внутрь примитива или collisionPrimitiveNearest в случае столкновения с ребром примитива через
+ * минимальное время.
+ *
+ * @param primitive примитив, столкновение с которым проверяется
+ */
+ private function calculateCollisionWithPrimitiveE(primitive:PolyPrimitive):void {
+ var length:uint = primitive.num;
+ var points:Array = primitive.points;
+ var normal:Point3D = primitive.face.globalNormal;
+ var inside:Boolean = true;
+
+ var point1:Point3D;
+ var point2:Point3D = points[length - 1];
+ p2.x = (point2.x - currentCoords.x) / _scaleX;
+ p2.y = (point2.y - currentCoords.y) / _scaleY;
+ p2.z = (point2.z - currentCoords.z) / _scaleZ;
+
+ localCollisionPlanePoint.x = (collisionPlanePoint.x - currentCoords.x) / _scaleX;
+ localCollisionPlanePoint.y = (collisionPlanePoint.y - currentCoords.y) / _scaleY;
+ localCollisionPlanePoint.z = (collisionPlanePoint.z - currentCoords.z) / _scaleZ;
+ // Обход всех рёбер примитива
+ for (var i:uint = 0; i < length; i++) {
+ point1 = point2;
+ point2 = points[i];
+
+ p1.x = p2.x;
+ p1.y = p2.y;
+ p1.z = p2.z;
+
+ p2.x = (point2.x - currentCoords.x) / _scaleX;
+ p2.y = (point2.y - currentCoords.y) / _scaleY;
+ p2.z = (point2.z - currentCoords.z) / _scaleZ;
+
+ // Расчёт векторного произведения вектора ребра на радиус-вектор точки столкновения относительно начала ребра
+ // с целью определения положения точки столкновения относительно полигона
+ var edgeX:Number = p2.x - p1.x;
+ var edgeY:Number = p2.y - p1.y;
+ var edgeZ:Number = p2.z - p1.z;
+
+ var vectorX:Number = localCollisionPlanePoint.x - p1.x;
+ var vectorY:Number = localCollisionPlanePoint.y - p1.y;
+ var vectorZ:Number = localCollisionPlanePoint.z - p1.z;
+
+ var crossX:Number = vectorY * edgeZ - vectorZ * edgeY;
+ var crossY:Number = vectorZ * edgeX - vectorX * edgeZ;
+ var crossZ:Number = vectorX * edgeY - vectorY * edgeX;
+
+ if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) {
+ // Точка за пределами полигона
+ inside = false;
+
+ var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ;
+ var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr;
+
+ // Если расстояние до прямой меньше текущего ближайшего
+ if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) {
+ // Ищем нормализованный вектор ребра
+ var edgeLength:Number = Math.sqrt(edgeLengthSqr);
+ var edgeNormX:Number = edgeX / edgeLength;
+ var edgeNormY:Number = edgeY / edgeLength;
+ var edgeNormZ:Number = edgeZ / edgeLength;
+ // Находим расстояние до точки перпендикуляра вдоль ребра
+ var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ;
+ var vectorLengthSqr:Number;
+ if (t < 0) {
+ // Ближайшая точка - первая
+ vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
+ if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
+ collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
+ collisionPrimitivePoint.x = p1.x;
+ collisionPrimitivePoint.y = p1.y;
+ collisionPrimitivePoint.z = p1.z;
+ collisionPrimitiveNearest = primitive;
+ }
+ } else {
+ if (t > edgeLength) {
+ // Ближайшая точка - вторая
+ vectorX = localCollisionPlanePoint.x - p2.x;
+ vectorY = localCollisionPlanePoint.y - p2.y;
+ vectorZ = localCollisionPlanePoint.z - p2.z;
+ vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
+ if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
+ collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
+ collisionPrimitivePoint.x = p2.x;
+ collisionPrimitivePoint.y = p2.y;
+ collisionPrimitivePoint.z = p2.z;
+ collisionPrimitiveNearest = primitive;
+ }
+ } else {
+ // Ближайшая точка на ребре
+ collisionPrimitiveNearestLengthSqr = edgeDistanceSqr;
+ collisionPrimitivePoint.x = p1.x + edgeNormX * t;
+ collisionPrimitivePoint.y = p1.y + edgeNormY * t;
+ collisionPrimitivePoint.z = p1.z + edgeNormZ * t;
+ collisionPrimitiveNearest = primitive;
+ }
+ }
+ }
+ }
+ }
+
+ // Если попали в примитив
+ if (inside) {
+ collisionPrimitive = primitive;
+ }
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/primitives/Box.as b/Alternativa3D5/5.6/alternativa/engine3d/primitives/Box.as
new file mode 100644
index 0000000..2a34fed
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/primitives/Box.as
@@ -0,0 +1,328 @@
+package alternativa.engine3d.primitives {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Прямоугольный параллелепипед.
+ * Параллелепипед содержит шесть поверхностей с идентификаторами "front", "back", "left",
+ * "right", "top", "bottom", на каждую из которых может быть установлен свой материал.
+ *
+ */
+ public class Box extends Mesh {
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ /**
+ * Создание нового параллелепипеда.
+ *
+ * @param width ширина. Размерность по оси X. Не может быть меньше нуля.
+ * @param length длина. Размерность по оси Y. Не может быть меньше нуля.
+ * @param height высота. Размерность по оси Z. Не может быть меньше нуля.
+ * @param widthSegments количество сегментов по ширине
+ * @param lengthSegments количество сегментов по длине
+ * @param heightSegments количество сегментов по по высоте
+ * @param reverse задает направление нормалей граней. Если указано значение true, то нормали будут направлены внутрь фигуры.
+ * @param triangulate флаг триангуляции. Если указано значение true, четырехугольники в параллелепипеде будут триангулированы.
+ */
+ public function Box(width:Number = 100, length:Number = 100, height:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false, triangulate:Boolean = false) {
+ super();
+
+ if ((widthSegments == 0) || (lengthSegments == 0) || (heightSegments == 0)) {
+ return;
+ }
+ width = (width < 0)? 0 : width;
+ length = (length < 0)? 0 : length;
+ height = (height < 0)? 0 : height;
+
+ var wh:Number = width/2;
+ var lh:Number = length/2;
+ var hh:Number = height/2;
+ var ws:Number = width/widthSegments;
+ var ls:Number = length/lengthSegments;
+ var hs:Number = height/heightSegments;
+ var x:int;
+ var y:int;
+ var z:int;
+
+ // Создание точек
+ for (x = 0; x <= widthSegments; x++) {
+ for (y = 0; y <= lengthSegments; y++) {
+ for (z = 0; z <= heightSegments; z++) {
+ if (x == 0 || x == widthSegments || y == 0 || y == lengthSegments || z == 0 || z == heightSegments) {
+ createVertex(x*ws - wh, y*ls - lh, z*hs - hh, x + "_" + y + "_" + z);
+ }
+ }
+ }
+ }
+
+ // Создание поверхностей
+ var front:Surface = createSurface(null, "front");
+ var back:Surface = createSurface(null, "back");
+ var left:Surface = createSurface(null, "left");
+ var right:Surface = createSurface(null, "right");
+ var top:Surface = createSurface(null, "top");
+ var bottom:Surface = createSurface(null, "bottom");
+
+ // Создание граней
+ var wd:Number = 1/widthSegments;
+ var ld:Number = 1/lengthSegments;
+ var hd:Number = 1/heightSegments;
+ var faceId:String;
+
+ // Для оптимизаций UV при триангуляции
+ var aUV:Point;
+ var cUV:Point;
+
+ // Построение верхней грани
+ for (y = 0; y < lengthSegments; y++) {
+ for (x = 0; x < widthSegments; x++) {
+ faceId = "top_"+x+"_"+y;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point(x*wd, (lengthSegments - y)*ld);
+ cUV = new Point((x + 1)*wd, (lengthSegments - y - 1)*ld);
+ createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":1");
+ setUVsToFace(aUV, new Point(x*wd, (lengthSegments - y - 1)*ld), cUV, faceId + ":1");
+ createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":0");
+ setUVsToFace(cUV, new Point((x + 1)*wd, (lengthSegments - y)*ld), aUV, faceId + ":0");
+ } else {
+ createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments], faceId);
+ setUVsToFace(new Point(x*wd, (lengthSegments - y)*ld), new Point(x*wd, (lengthSegments - y - 1)*ld), new Point((x + 1)*wd, (lengthSegments - y - 1)*ld), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point(x*wd, y*ld);
+ cUV = new Point((x + 1)*wd, (y + 1)*ld);
+ createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":0");
+ setUVsToFace(aUV, new Point((x + 1)*wd, y*ld), cUV, faceId + ":0");
+ createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":1");
+ setUVsToFace(cUV, new Point(x*wd, (y + 1)*ld), aUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments], faceId);
+ setUVsToFace(new Point(x*wd, y*ld), new Point((x + 1)*wd, y*ld), new Point((x + 1)*wd, (y + 1)*ld), faceId);
+ }
+ }
+ if (triangulate) {
+ top.addFace(faceId + ":0");
+ top.addFace(faceId + ":1");
+ } else {
+ top.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение нижней грани
+ for (y = 0; y < lengthSegments; y++) {
+ for (x = 0; x < widthSegments; x++) {
+ faceId = "bottom_" + x + "_" + y;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point((widthSegments - x)*wd, (lengthSegments - y)*ld);
+ cUV = new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld);
+ createFace([x + "_" + y + "_" + 0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":0");
+ setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), cUV, faceId + ":0");
+ createFace([(x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0, x + "_" + y + "_" + 0], faceId + ":1");
+ setUVsToFace(cUV, new Point((widthSegments - x)*wd, (lengthSegments - y - 1)*ld), aUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + y + "_"+0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0], faceId);
+ setUVsToFace(new Point((widthSegments - x)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point((widthSegments - x)*wd, y*ld);
+ cUV = new Point((widthSegments - x - 1)*wd, (y + 1)*ld);
+ createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":1");
+ setUVsToFace(aUV, new Point((widthSegments - x)*wd, (y + 1)*ld), cUV, faceId + ":1");
+ createFace([(x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0, x + "_" + y + "_" + 0], faceId + ":0");
+ setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, y*ld), aUV, faceId + ":0");
+ } else {
+ createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) +"_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0], faceId);
+ setUVsToFace(new Point((widthSegments - x)*wd, y*ld), new Point((widthSegments - x)*wd, (y + 1)*ld), new Point((widthSegments - x - 1)*wd, (y + 1)*ld), faceId);
+ }
+ }
+ if (triangulate) {
+ bottom.addFace(faceId + ":0");
+ bottom.addFace(faceId + ":1");
+ } else {
+ bottom.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение фронтальной грани
+ for (z = 0; z < heightSegments; z++) {
+ for (x = 0; x < widthSegments; x++) {
+ faceId = "front_"+x+"_"+z;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point((widthSegments - x)*wd, z*hd);
+ cUV = new Point((widthSegments - x - 1)*wd, (z + 1)*hd);
+ createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":1");
+ setUVsToFace(aUV, new Point((widthSegments - x)*wd, (z + 1)*hd), cUV, faceId + ":1");
+ createFace([(x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z, x + "_" + 0 + "_" + z], faceId + ":0");
+ setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, z*hd), aUV, faceId + ":0");
+ } else {
+ createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z], faceId);
+ setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point(x*wd, z*hd);
+ cUV = new Point((x + 1)*wd, (z + 1)*hd);
+ createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":0");
+ setUVsToFace(aUV, new Point((x + 1)*wd, z*hd), cUV, faceId + ":0");
+ createFace([(x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + z], faceId + ":1");
+ setUVsToFace(cUV, new Point(x*wd, (z + 1)*hd), aUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1)], faceId);
+ setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId);
+ }
+ }
+ if (triangulate) {
+ front.addFace(faceId + ":0");
+ front.addFace(faceId + ":1");
+ } else {
+ front.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение задней грани
+ for (z = 0; z < heightSegments; z++) {
+ for (x = 0; x < widthSegments; x++) {
+ faceId = "back_"+x+"_"+z;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point(x * wd, (z + 1) * hd);
+ cUV = new Point((x + 1) * wd, z * hd);
+ createFace([x + "_" + lengthSegments+"_" + (z + 1), x + "_"+lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0");
+ setUVsToFace(aUV, new Point(x * wd, z * hd), cUV, faceId + ":0");
+ createFace([(x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId + ":1");
+ setUVsToFace(cUV, new Point((x + 1) * wd, (z + 1) * hd), aUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId);
+ setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point((widthSegments - x)*wd, (z + 1)*hd);
+ cUV = new Point((widthSegments - x - 1)*wd, z*hd);
+ createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0");
+ setUVsToFace(new Point((widthSegments - x)*wd, z*hd), aUV, cUV, faceId + ":0");
+ createFace([x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":1");
+ setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (z + 1)*hd), cUV, faceId + ":1");
+ } else {
+ createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId);
+ setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId);
+ }
+ }
+ if (triangulate) {
+ back.addFace(faceId + ":0");
+ back.addFace(faceId + ":1");
+ } else {
+ back.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение левой грани
+ for (y = 0; y < lengthSegments; y++) {
+ for (z = 0; z < heightSegments; z++) {
+ faceId = "left_" + y + "_" + z;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point(y*ld, (z + 1)*hd);
+ cUV = new Point((y + 1)*ld, z*hd);
+ createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z], faceId + ":0");
+ setUVsToFace(aUV, new Point(y*ld, z*hd), cUV, faceId + ":0");
+ createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId + ":1");
+ setUVsToFace(cUV, new Point((y + 1)*ld, (z + 1)*hd), aUV, faceId + ":1");
+ } else {
+ createFace([0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId);
+ setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point((lengthSegments - y - 1)*ld, z*hd);
+ cUV = new Point((lengthSegments - y)*ld, (z + 1)*hd);
+ createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1)], faceId + ":0");
+ setUVsToFace(aUV, new Point((lengthSegments - y)*ld, z*hd), cUV, faceId + ":0");
+ createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + (y + 1) + "_" + z], faceId + ":1");
+ setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), aUV, faceId + ":1");
+ } else {
+ createFace([0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + z], faceId);
+ setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId);
+ }
+ }
+ if (triangulate) {
+ left.addFace(faceId + ":0");
+ left.addFace(faceId + ":1");
+ } else {
+ left.addFace(faceId);
+ }
+ }
+ }
+
+ // Построение правой грани
+ for (y = 0; y < lengthSegments; y++) {
+ for (z = 0; z < heightSegments; z++) {
+ faceId = "right_" + y + "_" + z;
+ if (reverse) {
+ if (triangulate) {
+ aUV = new Point((lengthSegments - y)*ld, z*hd);
+ cUV = new Point((lengthSegments - y - 1)*ld, (z + 1)*hd);
+ createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":1");
+ setUVsToFace(aUV, new Point((lengthSegments - y)*ld, (z + 1)*hd), cUV, faceId + ":1");
+ createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + y + "_" + z], faceId + ":0");
+ setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, z*hd), aUV, faceId + ":0");
+ } else {
+ createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z], faceId);
+ setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId);
+ }
+ } else {
+ if (triangulate) {
+ aUV = new Point(y*ld, z*hd);
+ cUV = new Point((y + 1)*ld, (z + 1)*hd);
+ createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":0");
+ setUVsToFace(aUV, new Point((y + 1)*ld, z*hd), cUV, faceId + ":0");
+ createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + y + "_" + z], faceId + ":1");
+ setUVsToFace(cUV, new Point(y*ld, (z + 1)*hd), aUV, faceId + ":1");
+ } else {
+ createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1)], faceId);
+ setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId);
+ }
+ }
+ if (triangulate) {
+ right.addFace(faceId + ":0");
+ right.addFace(faceId + ":1");
+ } else {
+ right.addFace(faceId);
+ }
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Box(0, 0, 0, 0);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function defaultName():String {
+ return "box" + ++counter;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/primitives/Cone.as b/Alternativa3D5/5.6/alternativa/engine3d/primitives/Cone.as
new file mode 100644
index 0000000..b410241
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/primitives/Cone.as
@@ -0,0 +1,277 @@
+package alternativa.engine3d.primitives {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.utils.MathUtils;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Усеченный конус или цилиндр.
+ */
+ public class Cone extends Mesh {
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ /**
+ * Создает усеченный конус или цилиндр.
+ * Различные значения параметров позволяют создавать различные примитивы.
+ * При установленном параметре topRadius = 0 или bottomRadius = 0 будет построен конус. При установленном
bottomRadius = topRadius будет построен цилиндр.
+ * По умолчанию параметр triangulate установлен в false и на примитив не может быть наложена текстура.
+ * Только при установленном параметре triangulate в true это возможно.
После создания примитив всегда содержит в себе поверхность "side".
+ * При установленном параметре bottomRadius не равном нулю в примитиве создается поверхность "bottom",
+ * при установленном параметре topRadius в примитиве создается поверхность "top".
+ * На каждую из поверхностей может быть наложен свой материал
true нормли будут направлены внутрь примитива.
+ * @param triangulate флаг триангуляции. При значении true все четырехугольные грани примитива будут триангулированы
+ * и появится возможность наложить на примитив текстуру.
+ */
+ public function Cone(height:Number = 100, bottomRadius:Number = 100, topRadius:Number = 0, heightSegments:uint = 1, radialSegments:uint = 12, reverse:Boolean = false, triangulate:Boolean = false) {
+
+ if ((radialSegments < 3) || (heightSegments < 1) || (heightSegments == 1 && topRadius == 0 && bottomRadius == 0)) {
+ return;
+ }
+ height = (height < 0)? 0 : height;
+ bottomRadius = (bottomRadius < 0)? 0 : bottomRadius;
+ topRadius = (topRadius < 0)? 0 : topRadius;
+
+ const radialSegment:Number = MathUtils.DEG360/radialSegments;
+ const radiusSegment:Number = (bottomRadius - topRadius)/heightSegments;
+ const heightSegment:Number = height/heightSegments;
+ const halfHeight:Number = height*0.5
+ const uSegment:Number = 1/radialSegments;
+ const vSegment:Number = 1/heightSegments;
+
+ // Создание вершин
+ if (topRadius == 0 || triangulate) {
+ var poleUp:Vertex = createVertex(0, 0, halfHeight, "poleUp");
+ }
+ if (bottomRadius == 0 || triangulate) {
+ var poleDown:Vertex = createVertex(0, 0, -halfHeight, "poleDown");
+ }
+
+ var radial:uint;
+ var segment:uint;
+
+ var topSegment:uint = heightSegments - int(topRadius == 0);
+ var bottomSegment:uint = int(bottomRadius == 0) ;
+ for (segment = bottomSegment; segment <= topSegment; segment++) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ var currentAngle:Number = radialSegment*radial;
+ var currentRadius:Number = bottomRadius - (radiusSegment*segment);
+ createVertex(Math.cos(currentAngle)*currentRadius, Math.sin(currentAngle)*currentRadius, heightSegment*segment - halfHeight, radial + "_" + segment);
+ }
+ }
+
+ // Создание граней и поверхности
+ var face:Face;
+
+ var points:Array;
+
+ var side:Surface = createSurface(null, "side");
+
+ if (topRadius == 0) {
+ // Создание граней у верхнего полюса
+ var prevRadial:uint = radialSegments - 1;
+ var centerUV:Point = new Point(0.5, 1);
+ var v:Number = topSegment*vSegment;
+ if (reverse) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], prevRadial + "_" + topSegment);
+ if (triangulate) {
+ setUVsToFace(centerUV, new Point(1 - (prevRadial + 1)*uSegment, v) , new Point(1 - prevRadial*uSegment, v), face);
+ }
+ side.addFace(face);
+ prevRadial = radial;
+ }
+ } else {
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], prevRadial + "_" + topSegment);
+ if (triangulate) {
+ setUVsToFace(centerUV, new Point(prevRadial*uSegment, v), new Point((prevRadial + 1)*uSegment, v), face);
+ }
+ side.addFace(face);
+ prevRadial = radial;
+ }
+ }
+ } else {
+ // Создание граней верхней крышки
+ var top:Surface = createSurface(null, "top");
+ if (triangulate) {
+ prevRadial = radialSegments - 1;
+ centerUV = new Point(0.5, 0.5);
+ var UV:Point;
+ var prevUV:Point;
+ if (reverse) {
+ prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5);
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], "top_" + prevRadial);
+ currentAngle = radial * radialSegment;
+ UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5);
+ setUVsToFace(centerUV, UV, prevUV, face);
+ top.addFace(face);
+ prevUV = UV;
+ prevRadial = radial;
+ }
+ } else {
+ prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5);
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], "top_" + prevRadial);
+ currentAngle = radial*radialSegment;
+ UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5);
+ setUVsToFace(centerUV, prevUV, UV, face);
+ top.addFace(face);
+ prevUV = UV;
+ prevRadial = radial;
+ }
+ }
+ } else {
+ points = new Array();
+ if (reverse) {
+ for (radial = (radialSegments - 1); radial < uint(-1); radial--) {
+ points.push(radial + "_" + topSegment);
+ }
+ } else {
+ for (radial = 0; radial < radialSegments; radial++) {
+ points.push(radial + "_" + topSegment);
+ }
+ }
+ top.addFace(createFace(points, "top"));
+ }
+ }
+ // Создание боковых граней
+ var face2:Face;
+ var aUV:Point;
+ var cUV:Point;
+ for (segment = bottomSegment; segment < topSegment; segment++) {
+ prevRadial = radialSegments - 1;
+ v = segment * vSegment;
+ for (radial = 0; radial < radialSegments; radial++) {
+ if (triangulate) {
+ if (reverse) {
+ face = createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":0");
+ face2 = createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1");
+ aUV = new Point(1 - (prevRadial + 1)*uSegment, v + vSegment)
+ cUV = new Point(1 - prevRadial*uSegment, v);
+ setUVsToFace(aUV, new Point(1 - (prevRadial + 1)*uSegment, v), cUV, face);
+ setUVsToFace(cUV, new Point(1 - prevRadial*uSegment, v + vSegment), aUV, face2);
+ } else {
+ face = createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0");
+ face2 = createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":1");
+ aUV = new Point(prevRadial*uSegment, v)
+ cUV = new Point((prevRadial + 1)*uSegment, v + vSegment);
+ setUVsToFace(aUV, new Point((prevRadial + 1)*uSegment, v), cUV, face);
+ setUVsToFace(cUV, new Point(prevRadial*uSegment, v + vSegment), aUV, face2);
+ }
+ side.addFace(face);
+ side.addFace(face2);
+ } else {
+ if (reverse) {
+ side.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment));
+ } else {
+ side.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1)], prevRadial + "_" + segment));
+ }
+ }
+ prevRadial = radial;
+ }
+ }
+
+ if (bottomRadius == 0) {
+ // Создание граней у нижнего полюса
+ prevRadial = radialSegments - 1;
+ centerUV = new Point(0.5, 0);
+ v = bottomSegment*vSegment;
+ if (reverse) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], prevRadial + "_0");
+ if (triangulate) {
+ setUVsToFace(centerUV, new Point(1 - prevRadial*uSegment, v), new Point(1 - (prevRadial + 1)*uSegment, v), face);
+ }
+ side.addFace(face);
+ prevRadial = radial;
+ }
+ } else {
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], prevRadial + "_0");
+ if (triangulate) {
+ setUVsToFace(centerUV, new Point((prevRadial + 1)*uSegment, v), new Point(prevRadial*uSegment, v), face);
+ }
+ side.addFace(face);
+ prevRadial = radial;
+ }
+ }
+ } else {
+ // Создание граней нижней крышки
+ var bottom:Surface = createSurface(null, "bottom");
+ if (triangulate) {
+ prevRadial = radialSegments - 1;
+ centerUV = new Point(0.5, 0.5);
+ if (reverse) {
+ prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5);
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], "bottom_" + prevRadial);
+ currentAngle = radial*radialSegment;
+ UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5);
+ setUVsToFace(centerUV, prevUV, UV, face);
+ bottom.addFace(face);
+ prevUV = UV;
+ prevRadial = radial;
+ }
+ } else {
+ prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5);
+ for (radial = 0; radial < radialSegments; radial++) {
+ face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], "bottom_" + prevRadial);
+ currentAngle = radial * radialSegment;
+ UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5);
+ setUVsToFace(centerUV, UV, prevUV, face);
+ bottom.addFace(face);
+ prevUV = UV;
+ prevRadial = radial;
+ }
+ }
+ } else {
+ points = new Array();
+ if (reverse) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ points.push(radial + "_" + bottomSegment);
+ }
+ } else {
+ for (radial = (radialSegments - 1); radial < uint(-1); radial--) {
+ points.push(radial + "_" + bottomSegment);
+ }
+ }
+ bottom.addFace(createFace(points, "bottom"));
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Cone(0, 0, 0, 0);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function defaultName():String {
+ return "cone" + ++counter;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/primitives/GeoPlane.as b/Alternativa3D5/5.6/alternativa/engine3d/primitives/GeoPlane.as
new file mode 100644
index 0000000..dddcf48
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/primitives/GeoPlane.as
@@ -0,0 +1,209 @@
+package alternativa.engine3d.primitives {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Геоплоскость.
+ */
+ public class GeoPlane extends Mesh {
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ /**
+ * Создает геоплоскость.
+ * Геоплоскость это плоскость с сетчатой структурой граней.
+ *Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров.
+ * При значении reverse установленном в true примитив будет содержать грань - "back".
+ * При значении reverse установленном в false примитив будет содержать грань - "front".
+ * Параметр twoSided указывает методу создать обе поверхности.
true, то создаётся двусторонняя поверхность
+ * @param reverse флаг инвертирования нормалей
+ */
+ public function GeoPlane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false) {
+ super();
+
+ if ((widthSegments == 0) || (lengthSegments == 0)) {
+ return;
+ }
+ width = (width < 0)? 0 : width;
+ length = (length < 0)? 0 : length;
+
+ // Середина
+ var wh:Number = width/2;
+ var hh:Number = length/2;
+ // Размеры сегмента
+ var ws:Number = width/widthSegments;
+ var hs:Number = length/lengthSegments;
+
+ // Размеры UV-сегмента
+ var us:Number = 1/widthSegments;
+ var vs:Number = 1/lengthSegments;
+
+ // Создание точек
+ var x:uint;
+ var y:uint;
+ var frontUV:Array = new Array();
+ var backUV:Array = ((lengthSegments & 1) == 0) ? null : new Array();
+ for (y = 0; y <= lengthSegments; y++) {
+ frontUV[y] = new Array();
+ if (backUV != null) {
+ backUV[y] = new Array();
+ }
+ for (x = 0; x <= widthSegments; x++) {
+ if ((y & 1) == 0) {
+ // Если чётный ряд
+ createVertex(x*ws - wh, y*hs - hh, 0, y + "_" + x);
+ frontUV[y][x] = new Point(x*us, y*vs);
+ if (backUV != null) {
+ backUV[y][x] = new Point(x*us, 1 - y*vs);
+ }
+ } else {
+ // Если нечётный ряд
+ if (x == 0) {
+ // Первая точка
+ createVertex(-wh, y*hs - hh, 0, y + "_" + x);
+ frontUV[y][x] = new Point(0, y*vs);
+ if (backUV != null) {
+ backUV[y][x] = new Point(0, 1 - y*vs);
+ }
+ } else {
+ createVertex(x*ws - wh - ws/2, y*hs - hh, 0, y + "_" + x);
+ frontUV[y][x] = new Point((x - 0.5)*us, y*vs);
+ if (backUV != null) {
+ backUV[y][x] = new Point((x - 0.5)*us, 1 - y*vs);
+ }
+ if (x == widthSegments) {
+ // Последняя точка
+ createVertex(wh, y*hs - hh, 0, y + "_" + (x + 1));
+ frontUV[y][x + 1] = new Point(1, y*vs);
+ if (backUV != null) {
+ backUV[y][x + 1] = new Point(1, 1 - y*vs);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Создание поверхностей
+ var front:Surface;
+ var back:Surface;
+
+ if (twoSided || !reverse) {
+ front = createSurface(null, "front");
+ }
+ if (twoSided || reverse) {
+ back = createSurface(null, "back");
+ }
+
+ // Создание полигонов
+ var face:Face;
+ for (y = 0; y < lengthSegments; y++) {
+ for (var n:uint = 0; n <= (widthSegments << 1); n++) {
+ x = n >> 1;
+ if ((y & 1) == 0) {
+ // Если чётный ряд
+ if ((n & 1) == 0) {
+ // Если остриём вверх
+ if (twoSided || !reverse) {
+ face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]);
+ setUVsToFace(frontUV[y][x], frontUV[y + 1][x + 1], frontUV[y + 1][x], face);
+ front.addFace(face);
+ }
+ if (twoSided || reverse) {
+ face = createFace([y + "_" + x, (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]);
+ if (backUV != null) {
+ setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y + 1][x + 1], face);
+ } else {
+ setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face);
+ }
+ back.addFace(face);
+ }
+ } else {
+ // Если остриём вниз
+ if (twoSided || !reverse) {
+ face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + (x + 1)]);
+ setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x + 1], face);
+ front.addFace(face);
+ }
+ if (twoSided || reverse) {
+ face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), y + "_" + (x + 1)]);
+ if (backUV != null) {
+ setUVsToFace(backUV[y][x], backUV[y + 1][x + 1], backUV[y][x + 1], face);
+ } else {
+ setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x + 1], frontUV[lengthSegments - y][x + 1], face);
+ }
+ back.addFace(face);
+ }
+ }
+ } else {
+ // Если нечётный ряд
+ if ((n & 1) == 0) {
+ // Если остриём вниз
+ if (twoSided || !reverse) {
+ face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + x]);
+ setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x], face);
+ front.addFace(face);
+ }
+ if (twoSided || reverse) {
+ face = createFace([y + "_" + x, (y + 1) + "_" + x, y + "_" + (x + 1)]);
+ if (backUV != null) {
+ setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y][x + 1], face);
+ } else {
+ setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y][x + 1], face);
+ }
+ back.addFace(face);
+ }
+ } else {
+ // Если остриём вверх
+ if (twoSided || !reverse) {
+ face = createFace([y + "_" + (x + 1), (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]);
+ setUVsToFace(frontUV[y][x+1], frontUV[y + 1][x + 1], frontUV[y + 1][x], face);
+ front.addFace(face);
+ }
+ if (twoSided || reverse) {
+ face = createFace([y + "_" + (x + 1), (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]);
+ if (backUV != null) {
+ setUVsToFace(backUV[y][x + 1], backUV[y + 1][x], backUV[y + 1][x + 1], face);
+ } else {
+ setUVsToFace(frontUV[lengthSegments - y][x + 1], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face);
+ }
+ back.addFace(face);
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new GeoPlane(0, 0, 0);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function defaultName():String {
+ return "geoPlane" + ++counter;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/primitives/GeoSphere.as b/Alternativa3D5/5.6/alternativa/engine3d/primitives/GeoSphere.as
new file mode 100644
index 0000000..fd11ecd
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/primitives/GeoSphere.as
@@ -0,0 +1,334 @@
+package alternativa.engine3d.primitives {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.types.Point3D;
+ import alternativa.utils.MathUtils;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Геосфера.
+ */
+ public class GeoSphere extends Mesh {
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ /**
+ * Создает геосферу.
+ * Геосфера после создания содержит в себе одну поверхность с идентификатором по умолчанию.
+ *Текстурные координаты у геосферы не находятся в промежутке [0, 1],
+ * поэтому для материала с текстурой необходимо устанавливать флаг repeat.
true нормали направлены внуть геосферы.
+ */
+ public function GeoSphere(radius:Number = 100, segments:uint = 2, reverse:Boolean = false) {
+ if (segments == 0) {
+ return;
+ }
+ radius = (radius < 0)? 0 : radius;
+
+ const sections:uint = 20;
+
+ //var nfaces:uint = sections*segments*segments;
+ //var nverts:Number = nfaces/2 + 2;
+ var points:Array = new Array();
+
+ var i:uint;
+ var f:uint;
+
+ var theta:Number;
+ var sin:Number;
+ var cos:Number;
+ // z расстояние до нижней и верхней крышки полюса
+ var subz:Number = 4.472136E-001*radius;
+ // радиус на расстоянии subz
+ var subrad:Number = 2*subz;
+ points.push(createVertex(0, 0, radius, "poleUp"));
+ // Создание вершин верхней крышки
+ for (i = 0; i < 5; i++) {
+ theta = MathUtils.DEG360*i/5;
+ sin = Math.sin(theta);
+ cos = Math.cos(theta);
+ points.push(createVertex(subrad*cos, subrad*sin, subz));
+ }
+ // Создание вершин нижней крышки
+ for (i = 0; i < 5; i++) {
+ theta = MathUtils.DEG180*((i << 1) + 1)/5;
+ sin = Math.sin(theta);
+ cos = Math.cos(theta);
+ points.push(createVertex(subrad*cos, subrad*sin, -subz));
+ }
+ points.push(createVertex(0, 0, -radius, "poleDown"));
+
+ for (i = 1; i < 6; i++) {
+ interpolate(0, i, segments, points);
+ }
+ for (i = 1; i < 6; i++) {
+ interpolate(i, i % 5 + 1, segments, points);
+ }
+ for (i = 1; i < 6; i++) {
+ interpolate(i, i + 5, segments, points);
+ }
+ for (i = 1; i < 6; i++) {
+ interpolate(i, (i + 3) % 5 + 6, segments, points);
+ }
+ for (i = 1; i < 6; i++) {
+ interpolate(i + 5, i % 5 + 6, segments, points);
+ }
+ for (i = 6; i < 11; i++) {
+ interpolate(11, i, segments, points);
+ }
+ for (f = 0; f < 5; f++) {
+ for (i = 1; i <= segments - 2; i++) {
+ interpolate(12 + f*(segments - 1) + i, 12 + (f + 1) % 5*(segments - 1) + i, i + 1, points);
+ }
+ }
+ for (f = 0; f < 5; f++) {
+ for (i = 1; i <= segments - 2; i++) {
+ interpolate(12 + (f + 15)*(segments - 1) + i, 12 + (f + 10)*(segments - 1) + i, i + 1, points);
+ }
+ }
+ for (f = 0; f < 5; f++) {
+ for (i = 1; i <= segments - 2; i++) {
+ interpolate(12 + ((f + 1) % 5 + 15)*(segments - 1) + segments - 2 - i, 12 + (f + 10)*(segments - 1) + segments - 2 - i, i + 1, points);
+ }
+ }
+ for (f = 0; f < 5; f++) {
+ for (i = 1; i <= segments - 2; i++) {
+ interpolate(12 + ((f + 1) % 5 + 25)*(segments - 1) + i, 12 + (f + 25)*(segments - 1) + i, i + 1, points);
+ }
+ }
+ // Создание граней
+ var face:Face;
+ var surface:Surface = createSurface();
+ for (f = 0; f < sections; f++) {
+ for (var row:uint = 0; row < segments; row++) {
+ for (var column:uint = 0; column <= row; column++) {
+ var a:uint = findVertices(segments, f, row, column);
+ var b:uint = findVertices(segments, f, row + 1, column);
+ var c:uint = findVertices(segments, f, row + 1, column + 1);
+ var va:Vertex = points[a];
+ var vb:Vertex = points[b];
+ var vc:Vertex = points[c];
+ var aUV:Point;
+ var bUV:Point;
+ var cUV:Point;
+ var coordA:Point3D = va._coords;
+ var coordB:Point3D = vb._coords;
+ var coordC:Point3D = vc._coords;
+
+ if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) {
+ aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) {
+ bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) {
+ cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ // полюс
+ if (a == 0 || a == 11) {
+ aUV.x = bUV.x + (cUV.x - bUV.x)*0.5;
+ }
+ if (b == 0 || b == 11) {
+ bUV.x = aUV.x + (cUV.x - aUV.x)*0.5;
+ }
+ if (c == 0 || c == 11) {
+ cUV.x = aUV.x + (bUV.x - aUV.x)*0.5;
+ }
+
+ if (reverse) {
+ face = createFace([va, vc, vb], (column << 1) + "_" + row + "_" + f);
+ aUV.x = 1 - aUV.x;
+ bUV.x = 1 - bUV.x;
+ cUV.x = 1 - cUV.x;
+ setUVsToFace(aUV, cUV, bUV, face);
+ } else {
+ face = createFace([va, vb, vc], (column << 1) + "_" + row + "_" + f);
+ setUVsToFace(aUV, bUV, cUV, face);
+ }
+ surface.addFace(face);
+ //trace(a + "_" + b + "_" + c);
+ if (column < row) {
+ b = findVertices(segments, f, row, column + 1);
+ var vd:Vertex = points[b];
+ coordB = vd._coords;
+
+ if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) {
+ aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) {
+ bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) {
+ cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
+ } else {
+ cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ if (a == 0 || a == 11) {
+ aUV.x = bUV.x + (cUV.x - bUV.x)*0.5;
+ }
+ if (b == 0 || b == 11) {
+ bUV.x = aUV.x + (cUV.x - aUV.x)*0.5;
+ }
+ if (c == 0 || c == 11) {
+ cUV.x = aUV.x + (bUV.x - aUV.x)*0.5;
+ }
+
+ if (reverse) {
+ face = createFace([va, vd, vc], ((column << 1) + 1) + "_" + row + "_" + f);
+ aUV.x = 1 - aUV.x;
+ bUV.x = 1 - bUV.x;
+ cUV.x = 1 - cUV.x;
+ setUVsToFace(aUV, bUV, cUV, face);
+ } else {
+ face = createFace([va, vc, vd], ((column << 1) + 1) + "_" + row + "_" + f);
+ setUVsToFace(aUV, cUV, bUV, face);
+ }
+ surface.addFace(face);
+ }
+ }
+ }
+ }
+ }
+
+/* private function getUVSpherical(point:Point3D, radius:Number = 0, reverse:Boolean = false):Point {
+ if (radius == 0) {
+ radius = point.length;
+ }
+ if (reverse) {
+ var u:Number = 0.5 - Math.atan2(point.y, point.x)/MathUtils.DEG360;
+ } else {
+ u = Math.atan2(point.y, point.x)/MathUtils.DEG360 + 0.5;
+ }
+ return new Point(u, Math.asin(point.z/radius)/MathUtils.DEG180 + 0.5);
+ }
+ */
+ private function interpolate(v1:uint, v2:uint, num:uint, points:Array):void {
+ if (num < 2) {
+ return;
+ }
+ var a:Vertex = Vertex(points[v1]);
+ var b:Vertex = Vertex(points[v2]);
+ var cos:Number = (a.x*b.x + a.y*b.y + a.z*b.z)/(a.x*a.x + a.y*a.y + a.z*a.z);
+ cos = (cos < -1) ? -1 : ((cos > 1) ? 1 : cos);
+ var theta:Number = Math.acos(cos);
+ var sin:Number = Math.sin(theta);
+ for (var e:uint = 1; e < num; e++) {
+ var theta1:Number = theta*e/num;
+ var theta2:Number = theta*(num - e)/num;
+ var st1:Number = Math.sin(theta1);
+ var st2:Number = Math.sin(theta2);
+ points.push(createVertex((a.x*st2 + b.x*st1)/sin, (a.y*st2 + b.y*st1)/sin, (a.z*st2 + b.z*st1)/sin));
+ }
+ }
+
+ private function findVertices(segments:uint, section:uint, row:uint, column:uint):uint {
+ if (row == 0) {
+ if (section < 5) {
+ return (0);
+ }
+ if (section > 14) {
+ return (11);
+ }
+ return (section - 4);
+ }
+ if (row == segments && column == 0) {
+ if (section < 5) {
+ return (section + 1);
+ }
+ if (section < 10) {
+ return ((section + 4) % 5 + 6);
+ }
+ if (section < 15) {
+ return ((section + 1) % 5 + 1);
+ }
+ return ((section + 1) % 5 + 6);
+ }
+ if (row == segments && column == segments) {
+ if (section < 5) {
+ return ((section + 1) % 5 + 1);
+ }
+ if (section < 10) {
+ return (section + 1);
+ }
+ if (section < 15) {
+ return (section - 9);
+ }
+ return (section - 9);
+ }
+ if (row == segments) {
+ if (section < 5) {
+ return (12 + (5 + section)*(segments - 1) + column - 1);
+ }
+ if (section < 10) {
+ return (12 + (20 + (section + 4) % 5)*(segments - 1) + column - 1);
+ }
+ if (section < 15) {
+ return (12 + (section - 5)*(segments - 1) + segments - 1 - column);
+ }
+ return (12 + (5 + section)*(segments - 1) + segments - 1 - column);
+ }
+ if (column == 0) {
+ if (section < 5) {
+ return (12 + section*(segments - 1) + row - 1);
+ }
+ if (section < 10) {
+ return (12 + (section % 5 + 15)*(segments - 1) + row - 1);
+ }
+ if (section < 15) {
+ return (12 + ((section + 1) % 5 + 15)*(segments - 1) + segments - 1 - row);
+ }
+ return (12 + ((section + 1) % 5 + 25)*(segments - 1) + row - 1);
+ }
+ if (column == row) {
+ if (section < 5) {
+ return (12 + (section + 1) % 5*(segments - 1) + row - 1);
+ }
+ if (section < 10) {
+ return (12 + (section % 5 + 10)*(segments - 1) + row - 1);
+ }
+ if (section < 15) {
+ return (12 + (section % 5 + 10)*(segments - 1) + segments - row - 1);
+ }
+ return (12 + (section % 5 + 25)*(segments - 1) + row - 1);
+ }
+ return (12 + 30*(segments - 1) + section*(segments - 1)*(segments - 2)/2 + (row - 1)*(row - 2)/2 + column - 1);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new GeoSphere(0, 0);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function defaultName():String {
+ return "geoSphere" + ++counter;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/primitives/Plane.as b/Alternativa3D5/5.6/alternativa/engine3d/primitives/Plane.as
new file mode 100644
index 0000000..167a323
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/primitives/Plane.as
@@ -0,0 +1,129 @@
+package alternativa.engine3d.primitives {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Плоскость.
+ */
+ public class Plane extends Mesh {
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ /**
+ * Создает плоскость.
+ * Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров.
+ * При значении reverse установленном в true примитив будет содержать грань - "back".
+ * При значении reverse установленном в false примитив будет содержать грань - "front".
+ * Параметр twoSided указывает методу создать обе поверхности.
true, то формируется двусторонняя плоскость
+ * @param reverse инвертирование нормалей
+ * @param triangulate флаг триангуляции. Если указано значение true, четырехугольники в плоскости будут триангулированы.
+ */
+ public function Plane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false, triangulate:Boolean = false) {
+ super();
+
+ if ((widthSegments == 0) || (lengthSegments == 0)) {
+ return;
+ }
+ width = (width < 0)? 0 : width;
+ length = (length < 0)? 0 : length;
+
+ // Середина
+ var wh:Number = width/2;
+ var lh:Number = length/2;
+
+ // Размеры сегмента
+ var ws:Number = width/widthSegments;
+ var ls:Number = length/lengthSegments;
+
+ // Размеры UV-сегмента
+ var wd:Number = 1/widthSegments;
+ var ld:Number = 1/lengthSegments;
+
+ // Создание точек и UV
+ var x:int;
+ var y:int;
+ var uv:Array = new Array();
+ for (y = 0; y <= lengthSegments; y++) {
+ uv[y] = new Array();
+ for (x = 0; x <= widthSegments; x++) {
+ uv[y][x] = new Point(x*wd, y*ld);
+ createVertex(x*ws - wh, y*ls - lh, 0, x+"_"+y);
+ }
+ }
+
+ // Создание поверхностей
+ var front:Surface;
+ var back:Surface;
+
+ if (twoSided || !reverse) {
+ front = createSurface(null, "front");
+ }
+ if (twoSided || reverse) {
+ back = createSurface(null, "back");
+ }
+
+ // Создание полигонов
+ for (y = 0; y < lengthSegments; y++) {
+ for (x = 0; x < widthSegments; x++) {
+ if (twoSided || !reverse) {
+ if (triangulate) {
+ createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1)], "front" + x + "_" + y + ":0");
+ setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y + ":0");
+ createFace([(x + 1) + "_" + (y + 1), x + "_" + (y + 1), x + "_" + y], "front" + x + "_" + y + ":1");
+ setUVsToFace(uv[y + 1][x + 1], uv[y + 1][x], uv[y][x], "front" + x + "_" + y + ":1");
+ front.addFace("front" + x + "_" + y + ":0");
+ front.addFace("front" + x + "_" + y + ":1");
+ } else {
+ createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1), x + "_" + (y + 1)], "front" + x + "_" + y);
+ setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y);
+ front.addFace("front" + x + "_" + y);
+ }
+ }
+ if (twoSided || reverse) {
+ if (triangulate) {
+ createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1)], "back" + x + "_" + y + ":0");
+ setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y + ":0");
+ createFace([(x + 1) + "_" + (y + 1), (x + 1) + "_" + y, x + "_" + y], "back" + x + "_" + y + ":1");
+ setUVsToFace(uv[lengthSegments - y - 1][x + 1], uv[lengthSegments - y][x + 1], uv[lengthSegments - y][x], "back" + x + "_" + y + ":1");
+ back.addFace("back" + x + "_" + y + ":0");
+ back.addFace("back"+x+"_"+y + ":1");
+ } else {
+ createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1), (x + 1) + "_" + y], "back" + x + "_" + y);
+ setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y);
+ back.addFace("back" + x + "_" + y);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Plane(0, 0, 0);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function defaultName():String {
+ return "plane" + ++counter;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/engine3d/primitives/Sphere.as b/Alternativa3D5/5.6/alternativa/engine3d/primitives/Sphere.as
new file mode 100644
index 0000000..cb2424f
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/engine3d/primitives/Sphere.as
@@ -0,0 +1,157 @@
+package alternativa.engine3d.primitives {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Object3D;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.utils.MathUtils;
+
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+
+ /**
+ * Сфера.
+ */
+ public class Sphere extends Mesh {
+
+ // Инкремент количества объектов
+ private static var counter:uint = 0;
+
+ /**
+ * Создает сферу.
+ * После создания примитив содержит в себе одну поверхность с идентификатором по умолчанию.
+ *По умолчанию параметр triangulate установлен в false и на сферу нельзя наложить текстуру.
+ * Только при установленном triangulate в true это возможно.
true нормали направлены внутрь сферы.
+ * @param triangulate флаг триангуляции. Если указано значение true, грани будут триангулированы,
+ * и будет возможно наложить на примитив текстуру.
+ */
+ public function Sphere(radius:Number = 100, radialSegments:uint = 8, heightSegments:uint = 8, reverse:Boolean = false, triangulate:Boolean = false) {
+ if ((radialSegments < 3) || (heightSegments < 2)) {
+ return;
+ }
+ radius = (radius < 0)? 0 : radius;
+
+ var poleUp:Vertex = createVertex(0, 0, radius, "poleUp");
+ var poleDown:Vertex = createVertex(0, 0, -radius, "poleDown");
+
+ const radialAngle:Number = MathUtils.DEG360/radialSegments;
+ const heightAngle:Number = MathUtils.DEG360/(heightSegments << 1);
+
+ var radial:uint;
+ var segment:uint;
+
+ // Создание вершин
+ for (segment = 1; segment < heightSegments; segment++) {
+ var currentHeightAngle:Number = heightAngle*segment;
+ var segmentRadius:Number = Math.sin(currentHeightAngle)*radius;
+ var segmentZ:Number = Math.cos(currentHeightAngle)*radius;
+ for (radial = 0; radial < radialSegments; radial++) {
+ var currentRadialAngle:Number = radialAngle*radial;
+ createVertex(-Math.sin(currentRadialAngle)*segmentRadius, Math.cos(currentRadialAngle)*segmentRadius, segmentZ, radial + "_" + segment);
+ }
+ }
+
+ // Создание граней и поверхности
+ var surface:Surface = createSurface();
+
+ var prevRadial:uint = radialSegments - 1;
+ var lastSegmentString:String = "_" + (heightSegments - 1);
+
+ var uStep:Number = 1/radialSegments;
+ var vStep:Number = 1/heightSegments;
+
+ var face:Face;
+
+ // Для триангуляции
+ var aUV:Point;
+ var cUV:Point;
+
+ var u:Number;
+
+ if (reverse) {
+ for (radial = 0; radial < radialSegments; radial++) {
+ // Грани верхнего полюса
+ surface.addFace(createFace([poleUp, radial + "_1", prevRadial + "_1"], prevRadial + "_0"));
+ // Грани нижнего полюса
+ surface.addFace(createFace([radial + lastSegmentString, poleDown, prevRadial + lastSegmentString], prevRadial + lastSegmentString));
+
+ // Если включена триангуляция
+ if (triangulate) {
+ // Триангулируем середину и просчитываем маппинг
+ u = uStep*prevRadial;
+ setUVsToFace(new Point(1 - u, 1), new Point(1 - u - uStep, 1 - vStep), new Point(1 - u, 1 - vStep), prevRadial + "_0");
+ // Грани середки
+ for (segment = 1; segment < (heightSegments - 1); segment++) {
+ aUV = new Point(1 - u - uStep, 1 - (vStep*(segment + 1)));
+ cUV = new Point(1 - u, 1 - vStep*segment);
+ surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":0"));
+ surface.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1"));
+ setUVsToFace(aUV, new Point(1 - u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0");
+ setUVsToFace(cUV, new Point(1 - u - uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1");
+ }
+ setUVsToFace(new Point(1 - u - uStep, vStep), new Point(1 - u, 0), new Point(1 - u, vStep), prevRadial + lastSegmentString);
+
+ } else {
+ // Просто создаем середину
+ // Грани середки
+ for (segment = 1; segment < (heightSegments - 1); segment++) {
+ surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment, radial + "_" + segment], prevRadial + "_" + segment));
+ }
+ }
+ prevRadial = (radial == 0) ? 0 : prevRadial + 1;
+ }
+ } else {
+ for (radial = 0; radial < radialSegments; radial++) {
+ // Грани верхнего полюса
+ surface.addFace(createFace([poleUp, prevRadial + "_1", radial + "_1"], prevRadial + "_0"));
+ // Грани нижнего полюса
+ surface.addFace(createFace([prevRadial + lastSegmentString, poleDown, radial + lastSegmentString], prevRadial + lastSegmentString));
+
+ if (triangulate) {
+ u = uStep*prevRadial;
+ setUVsToFace(new Point(u, 1), new Point(u, 1 - vStep), new Point(u + uStep, 1 - vStep), prevRadial + "_0");
+ // Грани середки
+ for (segment = 1; segment < (heightSegments - 1); segment++) {
+ aUV = new Point(u, 1 - (vStep*segment));
+ cUV = new Point(u + uStep, 1 - vStep * (segment + 1));
+ surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0"));
+ surface.addFace(createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":1"));
+ setUVsToFace(aUV, new Point(u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0");
+ setUVsToFace(cUV, new Point(u + uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1");
+ }
+ setUVsToFace(new Point(u, vStep), new Point(u, 0), new Point(u + uStep, vStep), prevRadial + lastSegmentString);
+ } else {
+ // Грани середки
+ for (segment = 1; segment < (heightSegments - 1); segment++) {
+ surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment));
+ }
+ }
+ prevRadial = (radial == 0) ? 0 : prevRadial + 1;
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected override function createEmptyObject():Object3D {
+ return new Sphere(0, 0);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ override protected function defaultName():String {
+ return "sphere" + ++counter;
+ }
+
+ }
+}
diff --git a/Alternativa3D5/5.6/alternativa/utils/MeshUtils.as b/Alternativa3D5/5.6/alternativa/utils/MeshUtils.as
new file mode 100644
index 0000000..5d25b2a
--- /dev/null
+++ b/Alternativa3D5/5.6/alternativa/utils/MeshUtils.as
@@ -0,0 +1,843 @@
+package alternativa.utils {
+
+ import alternativa.engine3d.*;
+ import alternativa.engine3d.core.Face;
+ import alternativa.engine3d.core.Mesh;
+ import alternativa.engine3d.core.Surface;
+ import alternativa.engine3d.core.Vertex;
+ import alternativa.engine3d.materials.FillMaterial;
+ import alternativa.engine3d.materials.SurfaceMaterial;
+ import alternativa.engine3d.materials.TextureMaterial;
+ import alternativa.engine3d.materials.TextureMaterialPrecision;
+ import alternativa.engine3d.materials.WireMaterial;
+ import alternativa.types.*;
+
+ import flash.display.BlendMode;
+ import flash.geom.Point;
+
+ use namespace alternativa3d;
+ use namespace alternativatypes;
+
+ /**
+ * Утилиты для работы с Mesh-объектами.
+ */
+ public class MeshUtils {
+
+ static private var verticesSort:Array = ["x", "y", "z"];
+ static private var verticesSortOptions:Array = [Array.NUMERIC, Array.NUMERIC, Array.NUMERIC];
+
+ /**
+ * Объединение нескольких Mesh-объектов. Объекты, переданные как аргументы метода, не изменяются.
+ *
+ * @param meshes объединяемые объекты класса alternativa.engine3d.core.Mesh
+ *
+ * @return новый Mesh-объект, содержащий результат объединения переданных Mesh-объектов
+ */
+ static public function uniteMeshes(... meshes):Mesh {
+ var res:Mesh = new Mesh();
+
+ var length:uint = meshes.length;
+ var key:*;
+ var vertex:Vertex;
+ var face:Face;
+ var j:uint;
+ for (var i:uint = 0; i < length; i++) {
+ var mesh:Mesh = meshes[i];
+ var vertices:Map = mesh._vertices.clone();
+ for (key in vertices) {
+ vertex = vertices[key];
+ vertices[key] = res.createVertex(vertex.x, vertex.y, vertex.z);
+ }
+ var faces:Map = mesh._faces.clone();
+ for (key in faces) {
+ face = faces[key];
+ var faceVertices:Array = new Array().concat(face._vertices);
+ for (j = 0; j < face._verticesCount; j++) {
+ vertex = faceVertices[j];
+ faceVertices[j] = vertices[vertex.id];
+ }
+ faces[key] = res.createFace(faceVertices);
+ res.setUVsToFace(face._aUV, face._bUV, face._cUV, faces[key]);
+ }
+ for (key in mesh._surfaces) {
+ var surface:Surface = mesh._surfaces[key];
+ var surfaceFaces:Array = surface._faces.toArray();
+ var numFaces:uint = surfaceFaces.length;
+ for (j = 0; j < numFaces; j++) {
+ face = surfaceFaces[j];
+ surfaceFaces[j] = faces[face.id];
+ }
+ var newSurface:Surface = res.createSurface(surfaceFaces);
+ newSurface.material = SurfaceMaterial(surface.material.clone());
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Слияние вершин Mesh-объекта с одинаковыми координатами. Равенство координат проверяется с учётом погрешности.
+ *
+ * @param mesh объект, вершины которого объединяются
+ * @param threshold погрешность измерения расстояний
+ */
+ static public function autoWeldVertices(mesh:Mesh, threshold:Number = 0):void {
+ // Получаем список вершин меша и сортируем по координатам
+ var vertices:Array = mesh._vertices.toArray(true);
+ vertices.sortOn(verticesSort, verticesSortOptions);
+
+ // Поиск вершин с одинаковыми координатами
+ var weld:Map = new Map(true);
+ var vertex:Vertex;
+ var currentVertex:Vertex = vertices[0];
+ var length:uint = vertices.length;
+ var i:uint;
+ for (i = 1; i < length; i++) {
+ vertex = vertices[i];
+ if ((currentVertex.x - vertex.x <= threshold) && (currentVertex.x - vertex.x >= -threshold) && (currentVertex.y - vertex.y <= threshold) && (currentVertex.y - vertex.y >= -threshold) && (currentVertex.z - vertex.z <= threshold) && (currentVertex.z - vertex.z >= -threshold)) {
+ weld[vertex] = currentVertex;
+ } else {
+ currentVertex = vertex;
+ }
+ }
+
+ // Собираем грани объединяемых вершин
+ var faces:Set = new Set(true);
+ var keyVertex:*;
+ var keyFace:*;
+ for (keyVertex in weld) {
+ vertex = keyVertex;
+ for (keyFace in vertex._faces) {
+ faces[keyFace] = true;
+ }
+ }
+
+ // Заменяем грани
+ for (keyFace in faces) {
+ var face:Face = keyFace;
+ var id:Object = mesh.getFaceId(face);
+ var surface:Surface = face._surface;
+ var aUV:Point = face._aUV;
+ var bUV:Point = face._bUV;
+ var cUV:Point = face._cUV;
+ vertices = new Array().concat(face._vertices);
+ length = vertices.length;
+ for (i = 0; i < length; i++) {
+ vertex = weld[vertices[i]];
+ if (vertex != null) {
+ vertices[i] = vertex;
+ }
+ }
+ mesh.removeFace(face);
+ face = mesh.createFace(vertices, id);
+ if (surface != null) {
+ surface.addFace(face);
+ }
+ face.aUV = aUV;
+ face.bUV = bUV;
+ face.cUV = cUV;
+ }
+
+ // Удаляем вершины
+ for (keyVertex in weld) {
+ mesh.removeVertex(keyVertex);
+ }
+ }
+
+ /**
+ * Объединение соседних граней, образующих плоский выпуклый многоугольник.
+ *
+ * @param mesh объект, грани которого объединяются
+ * @param angleThreshold погрешность измерения углов
+ * @param uvThreshold погрешность измерения UV-координат
+ * @param ignoreLineJoints значение true запрещает объединения, в результате которых два соседних ребра оказываются на одной линии.
+ * Например, если флаг включен, два прямоугольника с общим ребром не объединятся.
+ */
+ static public function autoWeldFaces(mesh:Mesh, angleThreshold:Number = 0, uvThreshold:Number = 0, ignoreLineJoints:Boolean = false):void {
+ angleThreshold = Math.cos(angleThreshold);
+ var digitThreshold:Number = 0.001;
+
+ var vertex:Vertex;
+ var face:Face;
+ var sibling:Face;
+ var key:*;
+ var i:uint;
+ var normal:Point3D;
+
+ // Формируем списки граней
+ var faces1:Set = new Set(true);
+ var faces2:Set = new Set(true);
+
+ // Формируем список нормалей
+ var normals:Map = new Map(true);
+ for each (face in mesh._faces.clone()) {
+ normal = new Point3D();
+ vertex = face._vertices[0];
+ var av:Point3D = vertex._coords;
+ vertex = face._vertices[1];
+ var abx:Number = vertex._coords.x - av.x;
+ var aby:Number = vertex._coords.y - av.y;
+ var abz:Number = vertex._coords.z - av.z;
+ vertex = face._vertices[2];
+ var acx:Number = vertex._coords.x - av.x;
+ var acy:Number = vertex._coords.y - av.y;
+ var acz:Number = vertex._coords.z - av.z;
+ normal.x = acz*aby - acy*abz;
+ normal.y = acx*abz - acz*abx;
+ normal.z = acy*abx - acx*aby;
+ var normalLength:Number = Math.sqrt(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z);
+ if (normalLength > digitThreshold) {
+ normal.x /= normalLength;
+ normal.y /= normalLength;
+ normal.z /= normalLength;
+ faces1[face] = true;
+ normals[face] = normal;
+ } else {
+ mesh.removeFace(face);
+ }
+ }
+
+ // Объединение
+ do {
+ // Флаг объединения
+ var weld:Boolean = false;
+ // Объединяем грани
+ while ((face = faces1.take()) != null) {
+ //var num:uint = face.num;
+ //var vertices:Array = face.vertices;
+ var currentWeld:Boolean = false;
+
+ // Проверка общих граней по точкам
+
+
+ // Проверка общих граней по рёбрам
+
+ // Перебираем точки грани
+ for (i = 0; (i < face._verticesCount) && !currentWeld; i++) {
+ var faceIndex1:uint = i;
+ var faceIndex2:uint;
+ var siblingIndex1:int;
+ var siblingIndex2:uint;
+
+ // Перебираем грани текущей точки
+ vertex = face._vertices[faceIndex1];
+ var vertexFaces:Set = vertex.faces;
+ for (key in vertexFaces) {
+ sibling = key;
+ // Если грань в списке на объединение и в одной поверхности
+ if (faces1[sibling] && face._surface == sibling._surface) {
+ faceIndex2 = (faceIndex1 < face._verticesCount - 1) ? (faceIndex1 + 1) : 0;
+ siblingIndex1 = sibling._vertices.indexOf(face._vertices[faceIndex2]);
+ // Если общее ребро
+ if (siblingIndex1 >= 0) {
+ // Если грани сонаправлены
+ normal = normals[face];
+ if (Point3D.dot(normal, normals[sibling]) >= angleThreshold) {
+ // Если в точках объединения нет перегибов
+ siblingIndex2 = (siblingIndex1 < sibling._verticesCount - 1) ? (siblingIndex1 + 1) : 0;
+
+ // Расширяем грани объединения
+ var i1:uint;
+ var i2:uint;
+ while (true) {
+ i1 = (faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1);
+ i2 = (siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0;
+ if (face._vertices[i1] == sibling._vertices[i2]) {
+ faceIndex1 = i1;
+ siblingIndex2 = i2;
+ } else {
+ break;
+ }
+ }
+
+ while (true) {
+ i1 = (faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0;
+ i2 = (siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1);
+ if (face._vertices[i1] == sibling._vertices[i2]) {
+ faceIndex2 = i1;
+ siblingIndex1 = i2;
+ } else {
+ break;
+ }
+ }
+
+ vertex = face._vertices[faceIndex1];
+ var a:Point3D = vertex.coords;
+ vertex = face._vertices[faceIndex2];
+ var b:Point3D = vertex.coords;
+
+ // Считаем первый перегиб
+ vertex = sibling._vertices[(siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0];
+ var c:Point3D = vertex.coords;
+ vertex = face._vertices[(faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1)];
+ var d:Point3D = vertex.coords;
+
+ var cx:Number = c.x - a.x;
+ var cy:Number = c.y - a.y;
+ var cz:Number = c.z - a.z;
+ var dx:Number = d.x - a.x;
+ var dy:Number = d.y - a.y;
+ var dz:Number = d.z - a.z;
+
+ var crossX:Number = cy*dz - cz*dy;
+ var crossY:Number = cz*dx - cx*dz;
+ var crossZ:Number = cx*dy - cy*dx;
+ var zeroCross:Boolean = crossX < digitThreshold && crossX > -digitThreshold && crossY < digitThreshold && crossY > -digitThreshold && crossZ < digitThreshold && crossZ > -digitThreshold;
+ if (zeroCross && (cx*dx + cy*dy + cz*dz > 0 || ignoreLineJoints) || !zeroCross && crossX*normal.x + crossY*normal.y + crossZ*normal.z < 0) {
+ break;
+ }
+
+ // Считаем второй перегиб
+ vertex = face._vertices[(faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0];
+ c = vertex.coords;
+ vertex = sibling._vertices[(siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1)];
+ d = vertex.coords;
+
+ cx = c.x - b.x;
+ cy = c.y - b.y;
+ cz = c.z - b.z;
+ dx = d.x - b.x;
+ dy = d.y - b.y;
+ dz = d.z - b.z;
+
+ crossX = cy*dz - cz*dy;
+ crossY = cz*dx - cx*dz;
+ crossZ = cx*dy - cy*dx;
+ zeroCross = crossX < digitThreshold && crossX > -digitThreshold && crossY < digitThreshold && crossY > -digitThreshold && crossZ < digitThreshold && crossZ > -digitThreshold;
+ if (zeroCross && (cx*dx + cy*dy + cz*dz > 0 || ignoreLineJoints) || !zeroCross && crossX*normal.x + crossY*normal.y + crossZ*normal.z < 0) {
+ break;
+ }
+
+ // Флаг наличия UV у обеих граней
+ var hasUV:Boolean = (face._aUV != null && face._bUV != null && face._cUV != null && sibling._aUV != null && sibling._bUV != null && sibling._cUV != null);
+
+ if (hasUV || (face._aUV == null && face._bUV == null && face._cUV == null && sibling._aUV == null && sibling._bUV == null && sibling._cUV == null)) {
+
+ // Если грани имеют UV, проверяем совместимость
+ if (hasUV) {
+ vertex = sibling._vertices[0];
+ var uv:Point = face.getUVFast(vertex.coords, normal);
+ if ((uv.x - sibling._aUV.x > uvThreshold) || (uv.x - sibling._aUV.x < -uvThreshold) || (uv.y - sibling._aUV.y > uvThreshold) || (uv.y - sibling._aUV.y < -uvThreshold)) {
+ break;
+ }
+
+ vertex = sibling._vertices[1];
+ uv = face.getUVFast(vertex.coords, normal);
+ if ((uv.x - sibling._bUV.x > uvThreshold) || (uv.x - sibling._bUV.x < -uvThreshold) || (uv.y - sibling._bUV.y > uvThreshold) || (uv.y - sibling._bUV.y < -uvThreshold)) {
+ break;
+ }
+
+ vertex = sibling._vertices[2];
+ uv = face.getUVFast(vertex.coords, normal);
+ if ((uv.x - sibling._cUV.x > uvThreshold) || (uv.x - sibling._cUV.x < -uvThreshold) || (uv.y - sibling._cUV.y > uvThreshold) || (uv.y - sibling._cUV.y < -uvThreshold)) {
+ break;
+ }
+ }
+
+ // Формируем новую грань
+ var newVertices:Array = new Array();
+ var n:uint = faceIndex2;
+ do {
+ newVertices.push(face._vertices[n]);
+ n = (n < face._verticesCount - 1) ? (n + 1) : 0;
+ } while (n != faceIndex1);
+ n = siblingIndex2;
+ do {
+ newVertices.push(sibling._vertices[n]);
+ n = (n < sibling._verticesCount - 1) ? (n + 1) : 0;
+ } while (n != siblingIndex1);
+
+ // Выбираем начальную точку
+ n = getBestBeginVertexIndex(newVertices);
+ for (var m:uint = 0; m < n; m++) {
+ newVertices.push(newVertices.shift());
+ }
+
+ // Заменяем грани новой
+ var surface:Surface = face._surface;
+ var newFace:Face = mesh.createFace(newVertices);
+ if (hasUV) {
+ newFace.aUV = face.getUVFast(newVertices[0].coords, normal);
+ newFace.bUV = face.getUVFast(newVertices[1].coords, normal);
+ newFace.cUV = face.getUVFast(newVertices[2].coords, normal);
+ }
+ if (surface != null) {
+ surface.addFace(newFace);
+ }
+ mesh.removeFace(face);
+ mesh.removeFace(sibling);
+
+ // Обновляем список нормалей
+ delete normals[sibling];
+ delete normals[face];
+ normals[newFace] = newFace.normal;
+
+ // Обновляем списки расчётов
+ delete faces1[sibling];
+ faces2[newFace] = true;
+
+ // Помечаем объединение
+ weld = true;
+ currentWeld = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Если не удалось объединить, переносим грань
+ faces2[face] = true;
+ }
+
+ // Меняем списки
+ var fs:Set = faces1;
+ faces1 = faces2;
+ faces2 = fs;
+ } while (weld);
+
+ removeIsolatedVertices(mesh);
+ removeUselessVertices(mesh);
+ }
+
+ /**
+ * Удаление вершин объекта, не принадлежащим ни одной грани.
+ *
+ * @param mesh объект, вершины которого удаляются
+ */
+ static public function removeIsolatedVertices(mesh:Mesh):void {
+ for each (var vertex:Vertex in mesh._vertices.clone()) {
+ if (vertex._faces.isEmpty()) {
+ mesh.removeVertex(vertex);
+ }
+ }
+ }
+
+ /**
+ * Удаление вершин объекта, которые во всех своих гранях лежат на отрезке между предыдущей и следующей вершиной.
+ *
+ * @param mesh объект, вершины которого удаляются
+ */
+ static public function removeUselessVertices(mesh:Mesh):void {
+ var digitThreshold:Number = 0.001;
+ var v:Vertex;
+ var key:*;
+ var face:Face;
+ var index:uint;
+ var length:uint;
+ for each (var vertex:Vertex in mesh._vertices.clone()) {
+ var useless:Boolean = true;
+ var indexes:Map = new Map(true);
+ for (key in vertex._faces) {
+ face = key;
+ length = face._vertices.length;
+ index = face._vertices.indexOf(vertex);
+ v = face._vertices[index];
+ var a:Point3D = v.coords;
+ v = face._vertices[(index < length - 1) ? (index + 1) : 0];
+ var b:Point3D = v.coords;
+ v = face._vertices[(index > 0) ? (index - 1) : (length - 1)];
+ var c:Point3D = v.coords;
+ var abx:Number = b.x - a.x;
+ var aby:Number = b.y - a.y;
+ var abz:Number = b.z - a.z;
+ var acx:Number = c.x - a.x;
+ var acy:Number = c.y - a.y;
+ var acz:Number = c.z - a.z;
+ var crossX:Number = aby*acz - abz*acy;
+ var crossY:Number = abz*acx - abx*acz;
+ var crossZ:Number = abx*acy - aby*acx;
+ if (crossX < digitThreshold && crossX > -digitThreshold && crossY < digitThreshold && crossY > -digitThreshold && crossZ < digitThreshold && crossZ > -digitThreshold) {
+ indexes[face] = index;
+ } else {
+ useless = false;
+ break;
+ }
+ }
+ if (useless && !indexes.isEmpty()) {
+ // Удаляем
+ for (key in indexes) {
+ var i:uint;
+ face = key;
+ index = indexes[face];
+ length = face._vertices.length;
+ var newVertices:Array = new Array();
+ for (i = 0; i < length; i++) {
+ if (i != index) {
+ newVertices.push(face._vertices[i]);
+ }
+ }
+ var n:uint = getBestBeginVertexIndex(newVertices);
+ for (i = 0; i < n; i++) {
+ newVertices.push(newVertices.shift());
+ }
+ var surface:Surface = face._surface;
+ var newFace:Face = mesh.createFace(newVertices);
+ if (face._aUV != null && face._bUV != null && face._cUV != null) {
+ var normal:Point3D = face.normal;
+ newFace.aUV = face.getUVFast(newVertices[0].coords, normal);
+ newFace.bUV = face.getUVFast(newVertices[1].coords, normal);
+ newFace.cUV = face.getUVFast(newVertices[2].coords, normal);
+ }
+ if (surface != null) {
+ surface.addFace(newFace);
+ }
+ mesh.removeFace(face);
+ }
+ mesh.removeVertex(vertex);
+ }
+ }
+ }
+
+ /**
+ * Удаление вырожденных граней.
+ *
+ * @param mesh объект, грани которого удаляются
+ */
+ static public function removeSingularFaces(mesh:Mesh):void {
+ for each (var face:Face in mesh._faces.clone()) {
+ var normal:Point3D = face.normal;
+ if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
+ mesh.removeFace(face);
+ }
+ }
+ }
+
+ /**
+ * @private
+ * Находит наиболее подходящую первую точку.
+ * @param vertices
+ * @return
+ */
+ static public function getBestBeginVertexIndex(vertices:Array):uint {
+ var bestIndex:uint = 0;
+ var num:uint = vertices.length;
+ if (num > 3) {
+ var maxCrossLength:Number = 0;
+ var v:Vertex = vertices[num - 1];
+ var c1:Point3D = v.coords;
+ v = vertices[0];
+ var c2:Point3D = v.coords;
+
+ var prevX:Number = c2.x - c1.x;
+ var prevY:Number = c2.y - c1.y;
+ var prevZ:Number = c2.z - c1.z;
+
+ for (var i:uint = 0; i < num; i++) {
+ c1 = c2;
+ v = vertices[(i < num - 1) ? (i + 1) : 0];
+ c2 = v.coords;
+
+ var nextX:Number = c2.x - c1.x;
+ var nextY:Number = c2.y - c1.y;
+ var nextZ:Number = c2.z - c1.z;
+
+ var crossX:Number = prevY*nextZ - prevZ*nextY;
+ var crossY:Number = prevZ*nextX - prevX*nextZ;
+ var crossZ:Number = prevX*nextY - prevY*nextX;
+
+
+ var crossLength:Number = crossX*crossX + crossY*crossY + crossZ*crossZ;
+ if (crossLength > maxCrossLength) {
+ maxCrossLength = crossLength;
+ bestIndex = i;
+ }
+
+ prevX = nextX;
+ prevY = nextY;
+ prevZ = nextZ;
+ }
+ // Берём предыдущий
+ bestIndex = (bestIndex > 0) ? (bestIndex - 1) : (num - 1);
+ }
+
+ return bestIndex;
+ }
+
+ /**
+ * Генерация AS-класса.
+ *
+ * @param mesh объект, на базе которого генерируется класс
+ * @param packageName имя пакета для генерируемого класса
+ * @return AS-класс в текстовом виде
+ */
+ static public function generateClass(mesh:Mesh, packageName:String = ""):String {
+
+ var className:String = mesh._name.charAt(0).toUpperCase() + mesh._name.substr(1);
+
+ var header:String = "package" + ((packageName != "") ? (" " + packageName + " ") : " ") + "{\r\r";
+
+ var importSet:Object = new Object();
+ importSet["alternativa.engine3d.core.Mesh"] = true;
+
+ var materialSet:Map = new Map(true);
+ var materialName:String;
+ var materialNum:uint = 1;
+
+ var footer:String = "\t\t}\r\t}\r}";
+
+ var classHeader:String = "\tpublic class "+ className + " extends Mesh {\r\r";
+
+ var constructor:String = "\t\tpublic function " + className + "() {\r";
+ constructor += "\t\t\tsuper(\"" + mesh._name +"\");\r\r";
+
+
+ var newLine:Boolean = false;
+ if (mesh.mobility != 0) {
+ constructor += "\t\t\tmobility = " + mesh.mobility +";\r";
+ newLine = true;
+ }
+
+ if (mesh.x != 0 && mesh.y != 0 && mesh.z != 0) {
+ importSet["alternativa.types.Point3D"] = true;
+ constructor += "\t\t\tcoords = new Point3D(" + mesh.x + ", " + mesh.y + ", " + mesh.z +");\r";
+ newLine = true;
+ } else {
+ if (mesh.x != 0) {
+ constructor += "\t\t\tx = " + mesh.x + ";\r";
+ newLine = true;
+ }
+ if (mesh.y != 0) {
+ constructor += "\t\t\ty = " + mesh.y + ";\r";
+ newLine = true;
+ }
+ if (mesh.z != 0) {
+ constructor += "\t\t\tz = " + mesh.z + ";\r";
+ newLine = true;
+ }
+ }
+ if (mesh.rotationX != 0) {
+ constructor += "\t\t\trotationX = " + mesh.rotationX + ";\r";
+ newLine = true;
+ }
+ if (mesh.rotationY != 0) {
+ constructor += "\t\t\trotationY = " + mesh.rotationY + ";\r";
+ newLine = true;
+ }
+ if (mesh.rotationZ != 0) {
+ constructor += "\t\t\trotationZ = " + mesh.rotationZ + ";\r";
+ newLine = true;
+ }
+ if (mesh.scaleX != 1) {
+ constructor += "\t\t\tscaleX = " + mesh.scaleX + ";\r";
+ newLine = true;
+ }
+ if (mesh.scaleY != 1) {
+ constructor += "\t\t\tscaleY = " + mesh.scaleY + ";\r";
+ newLine = true;
+ }
+ if (mesh.scaleZ != 1) {
+ constructor += "\t\t\tscaleZ = " + mesh.scaleZ + ";\r";
+ newLine = true;
+ }
+
+ constructor += newLine ? "\r" : "";
+
+ function idToString(value:*):String {
+ return isNaN(value) ? ("\"" + value + "\"") : value;
+ }
+
+ function blendModeToString(value:String):String {
+ switch (value) {
+ case BlendMode.ADD: return "BlendMode.ADD";
+ case BlendMode.ALPHA: return "BlendMode.ALPHA";
+ case BlendMode.DARKEN: return "BlendMode.DARKEN";
+ case BlendMode.DIFFERENCE: return "BlendMode.DIFFERENCE";
+ case BlendMode.ERASE: return "BlendMode.ERASE";
+ case BlendMode.HARDLIGHT: return "BlendMode.HARDLIGHT";
+ case BlendMode.INVERT: return "BlendMode.INVERT";
+ case BlendMode.LAYER: return "BlendMode.LAYER";
+ case BlendMode.LIGHTEN: return "BlendMode.LIGHTEN";
+ case BlendMode.MULTIPLY: return "BlendMode.MULTIPLY";
+ case BlendMode.NORMAL: return "BlendMode.NORMAL";
+ case BlendMode.OVERLAY: return "BlendMode.OVERLAY";
+ case BlendMode.SCREEN: return "BlendMode.SCREEN";
+ case BlendMode.SUBTRACT: return "BlendMode.SUBTRACT";
+ default: return "BlendMode.NORMAL";
+ }
+ }
+
+ function colorToString(value:uint):String {
+ var hex:String = value.toString(16).toUpperCase();
+ var res:String = "0x";
+ var len:uint = 6 - hex.length;
+ for (var j:uint = 0; j < len; j++) {
+ res += "0";
+ }
+ res += hex;
+ return res;
+ }
+
+ var i:uint;
+ var length:uint;
+ var key:*;
+ var id:String;
+ var face:Face;
+ var surface:Surface;
+
+ newLine = false;
+ for (id in mesh._vertices) {
+ var vertex:Vertex = mesh._vertices[id];
+ var coords:Point3D = vertex.coords;
+ constructor += "\t\t\tcreateVertex(" + coords.x + ", " + coords.y + ", " + coords.z + ", " + idToString(id) + ");\r";
+ newLine = true;
+ }
+
+ constructor += newLine ? "\r" : "";
+
+ newLine = false;
+ for (id in mesh._faces) {
+ face = mesh._faces[id];
+ length = face._verticesCount;
+ constructor += "\t\t\tcreateFace(["
+ for (i = 0; i < length - 1; i++) {
+ constructor += idToString(mesh.getVertexId(face._vertices[i])) + ", ";
+ }
+ constructor += idToString(mesh.getVertexId(face._vertices[i])) + "], " + idToString(id) + ");\r";
+
+ if (face._aUV != null || face._bUV != null || face._cUV != null) {
+ importSet["flash.geom.Point"] = true;
+ constructor += "\t\t\tsetUVsToFace(new Point(" + face._aUV.x + ", " + face._aUV.y + "), new Point(" + face._bUV.x + ", " + face._bUV.y + "), new Point(" + face._cUV.x + ", " + face._cUV.y + "), " + idToString(id) + ");\r";
+ }
+ newLine = true;
+ }
+
+ constructor += newLine ? "\r" : "";
+
+ for (id in mesh._surfaces) {
+ surface = mesh._surfaces[id];
+ var facesStr:String = "";
+ for (key in surface._faces) {
+ facesStr += idToString(mesh.getFaceId(key)) + ", ";
+ }
+ constructor += "\t\t\tcreateSurface([" + facesStr.substr(0, facesStr.length - 2) + "], " + idToString(id) + ");\r";
+
+ if (surface.material != null) {
+ var material:String;
+ var defaultAlpha:Boolean = surface.material.alpha == 1;
+ var defaultBlendMode:Boolean = surface.material.blendMode == BlendMode.NORMAL;
+ if (surface.material is WireMaterial) {
+ importSet["alternativa.engine3d.materials.WireMaterial"] = true;
+ var defaultThickness:Boolean = WireMaterial(surface.material).thickness == 0;
+ var defaultColor:Boolean = WireMaterial(surface.material).color == 0;
+ material = "new WireMaterial(";
+ if (!defaultThickness || !defaultColor || !defaultAlpha || !defaultBlendMode) {
+ material += WireMaterial(surface.material).thickness;
+ if (!defaultColor || !defaultAlpha || !defaultBlendMode) {
+ material += ", " + colorToString(WireMaterial(surface.material).color);
+ if (!defaultAlpha || !defaultBlendMode) {
+ material += ", " + surface.material.alpha ;
+ if (!defaultBlendMode) {
+ importSet["flash.display.BlendMode"] = true;
+ material += ", " + blendModeToString(surface.material.blendMode);
+ }
+ }
+ }
+ }
+ }
+ var defaultWireThickness:Boolean;
+ var defaultWireColor:Boolean;
+ if (surface.material is FillMaterial) {
+ importSet["alternativa.engine3d.materials.FillMaterial"] = true;
+ defaultWireThickness = FillMaterial(surface.material).wireThickness < 0;
+ defaultWireColor = FillMaterial(surface.material).wireColor == 0;
+ material = "new FillMaterial(" + colorToString(FillMaterial(surface.material).color);
+ if (!defaultAlpha || !defaultBlendMode || !defaultWireThickness || !defaultWireColor) {
+ material += ", " + surface.material.alpha;
+ if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor) {
+ importSet["flash.display.BlendMode"] = true;
+ material += ", " + blendModeToString(surface.material.blendMode);
+ if (!defaultWireThickness || !defaultWireColor) {
+ material += ", " + FillMaterial(surface.material).wireThickness;
+ if (!defaultWireColor) {
+ material += ", " + colorToString(FillMaterial(surface.material).wireColor);
+ }
+ }
+ }
+ }
+ }
+ if (surface.material is TextureMaterial) {
+ importSet["alternativa.engine3d.materials.TextureMaterial"] = true;
+ var defaultRepeat:Boolean = TextureMaterial(surface.material).repeat;
+ var defaultSmooth:Boolean = !TextureMaterial(surface.material).smooth;
+ defaultWireThickness = TextureMaterial(surface.material).wireThickness < 0;
+ defaultWireColor = TextureMaterial(surface.material).wireColor == 0;
+ var defaultPrecision:Boolean = TextureMaterial(surface.material).precision == TextureMaterialPrecision.MEDIUM;
+
+ if (TextureMaterial(surface.material).texture == null) {
+ materialName = "null";
+ } else {
+ importSet["alternativa.types.Texture"] = true;
+ if (materialSet[TextureMaterial(surface.material).texture] == undefined) {
+ materialName = (TextureMaterial(surface.material).texture._name != null) ? TextureMaterial(surface.material).texture._name : "texture" + materialNum++;
+ materialSet[TextureMaterial(surface.material).texture] = materialName;
+ } else {
+ materialName = materialSet[TextureMaterial(surface.material).texture];
+ }
+ materialName = materialName.split(".")[0];
+ }
+ material = "new TextureMaterial(" + materialName;
+ if (!defaultAlpha || !defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).alpha;
+ if (!defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).repeat;
+ if (!defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).smooth;
+ if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ importSet["flash.display.BlendMode"] = true;
+ material += ", " + blendModeToString(surface.material.blendMode);
+ if (!defaultWireThickness || !defaultWireColor || !defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).wireThickness;
+ if (!defaultWireColor || !defaultPrecision) {
+ material += ", " + colorToString(TextureMaterial(surface.material).wireColor);
+ if (!defaultPrecision) {
+ material += ", " + TextureMaterial(surface.material).precision;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ constructor += "\t\t\tsetMaterialToSurface(" + material + "), " + idToString(id) + ");\r";
+ }
+ }
+
+ var imports:String = "";
+ newLine = false;
+
+ var importArray:Array = new Array();
+ for (key in importSet) {
+ importArray.push(key);
+ }
+ importArray.sort();
+
+ length = importArray.length;
+ for (i = 0; i < length; i++) {
+ var pack:String = importArray[i];
+ var current:String = pack.substr(0, pack.indexOf("."));
+ imports += (current != prev && prev != null) ? "\r" : "";
+ imports += "\timport " + pack + ";\r";
+ var prev:String = current;
+ newLine = true;
+ }
+ imports += newLine ? "\r" : "";
+
+ var embeds:String = "";
+ newLine = false;
+ for each (materialName in materialSet) {
+ var materialClassName:String = materialName.split(".")[0];
+ var materialBmpName:String = materialClassName.charAt(0).toUpperCase() + materialClassName.substr(1);
+ embeds += "\t\t[Embed(source=\"" + materialName + "\")] private static const bmp" + materialBmpName + ":Class;\r";
+ embeds += "\t\tprivate static const " + materialClassName + ":Texture = new Texture(new bmp" + materialBmpName + "().bitmapData, \"" + materialName + "\");\r";
+ newLine = true;
+ }
+ embeds += newLine ? "\r" : "";
+
+ return header + imports + classHeader + embeds + constructor + footer;
+ }
+
+ }
+}