mirror of
				https://github.com/MapMakersAndProgrammers/alternativa3d-archive.git
				synced 2025-10-31 01:06:16 -07:00 
			
		
		
		
	
		
			
				
	
	
		
			1256 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			ActionScript
		
	
	
	
	
	
			
		
		
	
	
			1256 lines
		
	
	
		
			43 KiB
		
	
	
	
		
			ActionScript
		
	
	
	
	
	
| 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-дерево первыми.
 | ||
| 		 * 
 | ||
| 		 * <p> Изменением свойства <code>splitBalance</code> можно влиять на конечный вид 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);
 | ||
| 			}
 | ||
| 		}
 | ||
| 		
 | ||
| 	}
 | ||
| } | 
