package alternativa.engine3d.core { import alternativa.engine3d.*; import alternativa.engine3d.errors.Object3DHierarchyError; import alternativa.engine3d.errors.Object3DNotFoundError; import alternativa.types.Matrix3D; import alternativa.types.Point3D; import alternativa.types.Set; import alternativa.utils.ObjectUtils; use namespace alternativa3d; /** * Базовый класс для объектов, находящихся в сцене. Класс реализует иерархию объектов сцены, а также содержит сведения * о трансформации объекта как единого целого. * *

Масштабирование, ориентация и положение объекта задаются в родительской системе координат. Результирующая * локальная трансформация является композицией операций масштабирования, поворотов объекта относительно осей * X, Y, Z и параллельного переноса центра объекта из начала координат. * Операции применяются в порядке их перечисления. * *

Глобальная трансформация (в системе координат корневого объекта сцены) является композицией трансформаций * самого объекта и всех его предков по иерархии объектов сцены. */ public class Object3D { // Операции /** * @private * Поворот или масштабирование */ alternativa3d var changeRotationOrScaleOperation:Operation = new Operation("changeRotationOrScale", this); /** * @private * Перемещение */ alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this); /** * @private * Расчёт матрицы трансформации */ alternativa3d var calculateTransformationOperation:Operation = new Operation("calculateTransformation", this, calculateTransformation, Operation.OBJECT_CALCULATE_TRANSFORMATION); /** * @private * Изменение уровеня мобильности */ alternativa3d var calculateMobilityOperation:Operation = new Operation("calculateMobility", this, calculateMobility, Operation.OBJECT_CALCULATE_MOBILITY); // Инкремент количества объектов private static var counter:uint = 0; /** * @private * Наименование */ alternativa3d var _name:String; /** * @private * Сцена */ alternativa3d var _scene:Scene3D; /** * @private * Родительский объект */ alternativa3d var _parent:Object3D; /** * @private * Дочерние объекты */ alternativa3d var _children:Set = new Set(); /** * @private * Уровень мобильности */ alternativa3d var _mobility:int = 0; /** * @private */ alternativa3d var inheritedMobility:int; /** * @private * Координаты объекта относительно родителя */ alternativa3d var _coords:Point3D = new Point3D(); /** * @private * Поворот объекта по оси X относительно родителя. Угол измеряется в радианах. */ alternativa3d var _rotationX:Number = 0; /** * @private * Поворот объекта по оси Y относительно родителя. Угол измеряется в радианах. */ alternativa3d var _rotationY:Number = 0; /** * @private * Поворот объекта по оси Z относительно родителя. Угол измеряется в радианах. */ alternativa3d var _rotationZ:Number = 0; /** * @private * Мастшаб объекта по оси X относительно родителя */ alternativa3d var _scaleX:Number = 1; /** * @private * Мастшаб объекта по оси Y относительно родителя */ alternativa3d var _scaleY:Number = 1; /** * @private * Мастшаб объекта по оси Z относительно родителя */ alternativa3d var _scaleZ:Number = 1; /** * @private * Полная матрица трансформации, переводящая координаты из локальной системы координат объекта в систему координат сцены */ alternativa3d var transformation:Matrix3D = new Matrix3D(); /** * @private * Координаты в сцене */ alternativa3d var globalCoords:Point3D = new Point3D(); /** * Создание экземпляра класса. * * @param name имя экземпляра */ public function Object3D(name:String = null) { // Имя по-умолчанию _name = (name != null) ? name : defaultName(); // Последствия операций changeRotationOrScaleOperation.addSequel(calculateTransformationOperation); changeCoordsOperation.addSequel(calculateTransformationOperation); } /** * @private * Расчёт трансформации */ private function calculateTransformation():void { if (changeRotationOrScaleOperation.queued) { // Если полная трансформация transformation.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ); if (_parent != null) { transformation.combine(_parent.transformation); } // Сохраняем глобальные координаты объекта globalCoords.x = transformation.d; globalCoords.y = transformation.h; globalCoords.z = transformation.l; } else { // Если только перемещение globalCoords.copy(_coords); if (_parent != null) { globalCoords.transform(_parent.transformation); } transformation.offset(globalCoords.x, globalCoords.y, globalCoords.z); } } /** * @private * Расчёт общей мобильности */ private function calculateMobility():void { inheritedMobility = ((_parent != null) ? _parent.inheritedMobility : 0) + _mobility; } /** * Добавление дочернего объекта. Добавляемый объект удаляется из списка детей предыдущего родителя. * Новой сценой дочернего объекта становится сцена родителя. * * @param child добавляемый объект * * @throws alternativa.engine3d.errors.Object3DHierarchyError нарушение иерархии объектов сцены */ public function addChild(child:Object3D):void { // Проверка на null if (child == null) { throw new Object3DHierarchyError(null, this); } // Проверка на наличие if (child._parent == this) { return; } // Проверка на добавление к самому себе if (child == this) { throw new Object3DHierarchyError(this, this); } // Проверка на добавление родительского объекта if (child._scene == _scene) { // Если объект был в той же сцене, либо оба не были в сцене var parentObject:Object3D = _parent; while (parentObject != null) { if (child == parentObject) { throw new Object3DHierarchyError(child, this); return; } parentObject = parentObject._parent; } } // Если объект был в другом объекте if (child._parent != null) { // Удалить его оттуда child._parent._children.remove(child); } else { // Если объект был корневым в сцене if (child._scene != null && child._scene._root == child) { child._scene.root = null; } } // Добавляем в список _children.add(child); // Указываем себя как родителя child.setParent(this); // Устанавливаем уровни child.setLevel((calculateTransformationOperation.priority & 0xFFFFFF) + 1); // Указываем сцену child.setScene(_scene); } /** * Удаление дочернего объекта. * * @param child удаляемый дочерний объект * * @throws alternativa.engine3d.errors.Object3DNotFoundError указанный объект не содержится в списке детей текущего объекта */ public function removeChild(child:Object3D):void { // Проверка на null if (child == null) { throw new Object3DNotFoundError(null, this); } // Проверка на наличие if (child._parent != this) { throw new Object3DNotFoundError(child, this); } // Убираем из списка _children.remove(child); // Удаляем ссылку на родителя child.setParent(null); // Удаляем ссылку на сцену child.setScene(null); } /** * @private * Установка родительского объекта. * * @param value родительский объект */ alternativa3d function setParent(value:Object3D):void { // Отписываемся от сигналов старого родителя if (_parent != null) { removeParentSequels(); } // Сохранить родителя _parent = value; // Если устанавливаем родителя if (value != null) { // Подписка на сигналы родителя addParentSequels(); } } /** * @private * Установка новой сцены для объекта. * * @param value сцена */ alternativa3d function setScene(value:Scene3D):void { if (_scene != value) { // Если была сцена if (_scene != null) { // Удалиться из сцены removeFromScene(_scene); } // Если новая сцена if (value != null) { // Добавиться на сцену addToScene(value); } // Сохранить сцену _scene = value; } else { // Посылаем операцию трансформации addOperationToScene(changeRotationOrScaleOperation); // Посылаем операцию пересчёта мобильности addOperationToScene(calculateMobilityOperation); } // Установить эту сцену у дочерних объектов for (var key:* in _children) { var object:Object3D = key; object.setScene(_scene); } } /** * @private * Установка уровня операции трансформации. * * @param value уровень операции трансформации */ alternativa3d function setLevel(value:uint):void { // Установить уровень операции трансформации и расчёта мобильности calculateTransformationOperation.priority = (calculateTransformationOperation.priority & 0xFF000000) | value; calculateMobilityOperation.priority = (calculateMobilityOperation.priority & 0xFF000000) | value; // Установить уровни у дочерних объектов for (var key:* in _children) { var object:Object3D = key; object.setLevel(value + 1); } } /** * @private * Подписка на сигналы родителя. */ private function addParentSequels():void { _parent.changeCoordsOperation.addSequel(changeCoordsOperation); _parent.changeRotationOrScaleOperation.addSequel(changeRotationOrScaleOperation); _parent.calculateMobilityOperation.addSequel(calculateMobilityOperation); } /** * @private * Удаление подписки на сигналы родителя. */ private function removeParentSequels():void { _parent.changeCoordsOperation.removeSequel(changeCoordsOperation); _parent.changeRotationOrScaleOperation.removeSequel(changeRotationOrScaleOperation); _parent.calculateMobilityOperation.removeSequel(calculateMobilityOperation); } /** * Метод вызывается при добавлении объекта на сцену. Наследники могут переопределять метод для выполнения * специфических действий. * * @param scene сцена, в которую добавляется объект */ protected function addToScene(scene:Scene3D):void { // При добавлении на сцену полная трансформация и расчёт мобильности scene.addOperation(changeRotationOrScaleOperation); scene.addOperation(calculateMobilityOperation); } /** * Метод вызывается при удалении объекта со сцены. Наследники могут переопределять метод для выполнения * специфических действий. * * @param scene сцена, из которой удаляется объект */ protected function removeFromScene(scene:Scene3D):void { // Удаляем все операции из очереди scene.removeOperation(changeRotationOrScaleOperation); scene.removeOperation(changeCoordsOperation); scene.removeOperation(calculateMobilityOperation); } /** * Имя объекта. */ public function get name():String { return _name; } /** * @private */ public function set name(value:String):void { _name = value; } /** * Сцена, которой принадлежит объект. */ public function get scene():Scene3D { return _scene; } /** * Родительский объект. */ public function get parent():Object3D { return _parent; } /** * Набор дочерних объектов. */ public function get children():Set { return _children.clone(); } /** * Уровень мобильности. Результирующая мобильность объекта является суммой мобильностей объекта и всех его предков * по иерархии объектов в сцене. Результирующая мобильность влияет на положение объекта в BSP-дереве. Менее мобильные * объекты находятся ближе к корню дерева, чем более мобильные. */ public function get mobility():int { return _mobility; } /** * @private */ public function set mobility(value:int):void { if (_mobility != value) { _mobility = value; addOperationToScene(calculateMobilityOperation); } } /** * Координата X. */ public function get x():Number { return _coords.x; } /** * Координата Y. */ public function get y():Number { return _coords.y; } /** * Координата Z. */ public function get z():Number { return _coords.z; } /** * @private */ public function set x(value:Number):void { if (_coords.x != value) { _coords.x = value; addOperationToScene(changeCoordsOperation); } } /** * @private */ public function set y(value:Number):void { if (_coords.y != value) { _coords.y = value; addOperationToScene(changeCoordsOperation); } } /** * @private */ public function set z(value:Number):void { if (_coords.z != value) { _coords.z = value; addOperationToScene(changeCoordsOperation); } } /** * Координаты объекта. */ public function get coords():Point3D { return _coords.clone(); } /** * @private */ public function set coords(value:Point3D):void { if (!_coords.equals(value)) { _coords.copy(value); addOperationToScene(changeCoordsOperation); } } /** * Угол поворота вокруг оси X, заданный в радианах. */ public function get rotationX():Number { return _rotationX; } /** * Угол поворота вокруг оси Y, заданный в радианах. */ public function get rotationY():Number { return _rotationY; } /** * Угол поворота вокруг оси Z, заданный в радианах. */ public function get rotationZ():Number { return _rotationZ; } /** * @private */ public function set rotationX(value:Number):void { if (_rotationX != value) { _rotationX = value; addOperationToScene(changeRotationOrScaleOperation); } } /** * @private */ public function set rotationY(value:Number):void { if (_rotationY != value) { _rotationY = value; addOperationToScene(changeRotationOrScaleOperation); } } /** * @private */ public function set rotationZ(value:Number):void { if (_rotationZ != value) { _rotationZ = value; addOperationToScene(changeRotationOrScaleOperation); } } /** * Коэффициент масштабирования вдоль оси X. */ public function get scaleX():Number { return _scaleX; } /** * Коэффициент масштабирования вдоль оси Y. */ public function get scaleY():Number { return _scaleY; } /** * Коэффициент масштабирования вдоль оси Z. */ public function get scaleZ():Number { return _scaleZ; } /** * @private */ public function set scaleX(value:Number):void { if (_scaleX != value) { _scaleX = value; addOperationToScene(changeRotationOrScaleOperation); } } /** * @private */ public function set scaleY(value:Number):void { if (_scaleY != value) { _scaleY = value; addOperationToScene(changeRotationOrScaleOperation); } } /** * @private */ public function set scaleZ(value:Number):void { if (_scaleZ != value) { _scaleZ = value; addOperationToScene(changeRotationOrScaleOperation); } } /** * Строковое представление объекта. * * @return строковое представление объекта */ public function toString():String { return "[" + ObjectUtils.getClassName(this) + " " + _name + "]"; } /** * Имя объекта по умолчанию. * * @return имя объекта по умолчанию */ protected function defaultName():String { return "object" + ++counter; } /** * @private * Добавление операции в очередь. * * @param operation добавляемая операция */ alternativa3d function addOperationToScene(operation:Operation):void { if (_scene != null) { _scene.addOperation(operation); } } /** * @private * Удаление операции из очереди. * * @param operation удаляемая операция */ alternativa3d function removeOperationFromScene(operation:Operation):void { if (_scene != null) { _scene.removeOperation(operation); } } /** * Создание пустого объекта без какой-либо внутренней структуры. Например, если некоторый геометрический примитив при * своём создании формирует набор вершин, граней и поверхностей, то этот метод не должен создавать вершины, грани и * поверхности. Данный метод используется в методе clone() и должен быть переопределён в потомках для получения * правильного объекта. * * @return новый пустой объект */ protected function createEmptyObject():Object3D { return new Object3D(); } /** * Копирование свойств объекта-источника. Данный метод используется в методе clone() и должен быть переопределён в * потомках для получения правильного объекта. Каждый потомок должен в переопределённом методе копировать только те * свойства, которые добавлены к базовому классу именно в нём. Копирование унаследованных свойств выполняется * вызовом super.clonePropertiesFrom(source). * * @param source объект, свойства которого копируются */ protected function clonePropertiesFrom(source:Object3D):void { _name = source._name; _mobility = source._mobility; _coords.x = source._coords.x; _coords.y = source._coords.y; _coords.z = source._coords.z; _rotationX = source._rotationX; _rotationY = source._rotationY; _rotationZ = source._rotationZ; _scaleX = source._scaleX; _scaleY = source._scaleY; _scaleZ = source._scaleZ; } /** * Клонирование объекта. Для реализации собственного клонирования наследники должны переопределять методы * createEmptyObject() и clonePropertiesFrom(). * * @return клонированный экземпляр объекта * * @see #createEmptyObject() * @see #clonePropertiesFrom() */ public function clone():Object3D { var copy:Object3D = createEmptyObject(); copy.clonePropertiesFrom(this); // Клонирование детей for (var key:* in _children) { var child:Object3D = key; copy.addChild(child.clone()); } return copy; } /** * Получение дочернего объекта с заданным именем. * * @param name имя дочернего объекта * @return любой дочерний объект с заданным именем или null в случае отсутствия таких объектов */ public function getChildByName(name:String):Object3D { for (var key:* in _children) { var child:Object3D = key; if (child._name == name) { return child; } } return null; } } }