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); } } } }