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