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 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 space:Space;
/**
* @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 spaceMatrix:Matrix3D = new Matrix3D();
/**
* Создание экземпляра класса.
*
* @param name имя экземпляра
*/
public function Object3D(name:String = null) {
// Имя по-умолчанию
_name = (name != null) ? name : defaultName();
}
protected function transform():void {
trace(this, "- transform");
// Если объект был в пространстве, помечаем пространство на пересчет
//if (space != null) {
// _scene.spacesToCalculate[space] = true;
//}
// Обновляем пространство
space = (_parent is Space) ? Space(_parent) : _parent.space;
// Помечаем пространство на пересчет
//_scene.spacesToCalculate[space] = true;
// Локальная матрица трансформации
spaceMatrix.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ);
// Если родитель не является пространством
if (!(_parent is Space)) {
// Наследуем трансформацию у родителя
spaceMatrix.combine(_parent.spaceMatrix);
}
}
protected function move():void {
trace("- move");
// Помечаем пространство на пересчет
//_scene.spacesToCalculate[space] = true;
// Если родитель является пространством
if (_parent is Space) {
// Смещение равно локальным координатам
spaceMatrix.d = _coords.x;
spaceMatrix.h = _coords.y;
spaceMatrix.l = _coords.z;
} else {
// Расчитываем новое смещение c учётом трансформации родителя
var x:Number = _coords.x;
var y:Number = _coords.y;
var z:Number = _coords.z;
var parentTransformation:Matrix3D = _parent.spaceMatrix;
spaceMatrix.d = parentTransformation.a*x + parentTransformation.b*y + parentTransformation.c*z + parentTransformation.d;
spaceMatrix.h = parentTransformation.e*x + parentTransformation.f*y + parentTransformation.g*z + parentTransformation.h;
spaceMatrix.l = parentTransformation.i*x + parentTransformation.j*y + parentTransformation.k*z + parentTransformation.l;
}
}
alternativa3d function transformBranch():void {
trace(this, "transformBranch");
// Трансформация
transform();
// Если объект не пространство, наследуем трансформацию
if (!(this is Space)) {
// Обрабатываем дочерние объекты
for (var key:* in _children) {
var child:Object3D = key;
child.transformBranch();
}
}
// Снимаем отметки о перемещении, трансформации и мобильности
delete _scene.objectsToTransform[this];
delete _scene.objectsToMove[this];
}
alternativa3d function moveBranch():void {
trace(this, "moveBranch");
// Перемещение
move();
// Если объект не пространство, наследуем перемещение
if (!(this is Space)) {
// Обрабатываем дочерние объекты
for (var key:* in _children) {
var child:Object3D = key;
_scene.objectsToTransform[child] ? child.transformBranch() : child.moveBranch();
}
}
// Снимаем отметку о перемещении
delete _scene.objectsToMove[this];
}
/**
* Добавление дочернего объекта. Добавляемый объект удаляется из списка детей предыдущего родителя.
* Новой сценой дочернего объекта становится сцена родителя.
*
* @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.space = null;
}
}
// Добавляем в список
_children.add(child);
// Указываем себя как родителя
child._parent = this;
// Указываем сцену
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._parent = null;
// Удаляем ссылку на сцену
child.setScene(null);
}
/**
* @private
* Установка новой сцены для объекта.
*
* @param value сцена
*/
alternativa3d function setScene(value:Scene3D):void {
if (_scene != value) {
// Если была сцена
if (_scene != null) {
// Удалиться из сцены
removeFromScene();
}
// Сохранить сцену
_scene = value;
// Если новая сцена
if (value != null) {
// Добавиться на сцену
addToScene();
}
// Установить эту сцену у дочерних объектов
for (var key:* in _children) {
var object:Object3D = key;
object.setScene(value);
}
} else {
// При перемещении в пределах сцены пересчёт
if (_scene != null) {
_scene.objectsToTransform[this] = true;
}
}
}
/**
* Метод вызывается при добавлении объекта на сцену. Наследники могут переопределять метод для выполнения
* специфических действий.
*/
protected function addToScene():void {
_scene.objectsToTransform[this] = true;
}
/**
* Метод вызывается при удалении объекта со сцены. Наследники могут переопределять метод для выполнения
* специфических действий.
*/
protected function removeFromScene():void {
/* // Если объект был в пространстве
if (space != null) {
// Помечаем пространство на пересчет
_scene.spacesToCalculate[space] = true;
// Удаляем ссылку на пространство
space = null;
}
*/
// Удаляем ссылку на пространство
space = null;
// Удаляем все пометки в сцене
delete _scene.objectsToTransform[this];
delete _scene.objectsToMove[this];
}
/**
* Имя объекта.
*/
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();
}
/**
* Координата 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;
if (_scene != null) {
_scene.objectsToMove[this] = true;
}
}
}
/**
* @private
*/
public function set y(value:Number):void {
if (_coords.y != value) {
_coords.y = value;
if (_scene != null) {
_scene.objectsToMove[this] = true;
}
}
}
/**
* @private
*/
public function set z(value:Number):void {
if (_coords.z != value) {
_coords.z = value;
if (_scene != null) {
_scene.objectsToMove[this] = true;
}
}
}
/**
* Координаты объекта.
*/
public function get coords():Point3D {
return _coords.clone();
}
/**
* @private
*/
public function set coords(value:Point3D):void {
if (!_coords.equals(value)) {
_coords.copy(value);
if (_scene != null) {
_scene.objectsToMove[this] = true;
}
}
}
/**
* Угол поворота вокруг оси 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;
if (_scene != null) {
_scene.objectsToTransform[this] = true;
}
}
}
/**
* @private
*/
public function set rotationY(value:Number):void {
if (_rotationY != value) {
_rotationY = value;
if (_scene != null) {
_scene.objectsToTransform[this] = true;
}
}
}
/**
* @private
*/
public function set rotationZ(value:Number):void {
if (_rotationZ != value) {
_rotationZ = value;
if (_scene != null) {
_scene.objectsToTransform[this] = true;
}
}
}
/**
* Коэффициент масштабирования вдоль оси 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;
if (_scene != null) {
_scene.objectsToTransform[this] = true;
}
}
}
/**
* @private
*/
public function set scaleY(value:Number):void {
if (_scaleY != value) {
_scaleY = value;
if (_scene != null) {
_scene.objectsToTransform[this] = true;
}
}
}
/**
* @private
*/
public function set scaleZ(value:Number):void {
if (_scaleZ != value) {
_scaleZ = value;
if (_scene != null) {
_scene.objectsToTransform[this] = true;
}
}
}
/**
* Строковое представление объекта.
*
* @return строковое представление объекта
*/
public function toString():String {
return "[" + ObjectUtils.getClassName(this) + " " + _name + "]";
}
/**
* Имя объекта по умолчанию.
*
* @return имя объекта по умолчанию
*/
protected function defaultName():String {
return "object" + ++counter;
}
/**
* Создание пустого объекта без какой-либо внутренней структуры. Например, если некоторый геометрический примитив при
* своём создании формирует набор вершин, граней и поверхностей, то этот метод не должен создавать вершины, грани и
* поверхности. Данный метод используется в методе clone() и должен быть переопределён в потомках для получения
* правильного объекта.
*
* @return новый пустой объект
*/
protected function createEmptyObject():Object3D {
return new Object3D();
}
/**
* Копирование свойств объекта-источника. Данный метод используется в методе clone() и должен быть переопределён в
* потомках для получения правильного объекта. Каждый потомок должен в переопределённом методе копировать только те
* свойства, которые добавлены к базовому классу именно в нём. Копирование унаследованных свойств выполняется
* вызовом super.clonePropertiesFrom(source).
*
* @param source объект, свойства которого копируются
*/
protected function clonePropertiesFrom(source:Object3D):void {
_name = source._name;
_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;
}
}
}