more versions added

This commit is contained in:
Tubix
2024-10-05 12:11:16 +01:00
parent 413f563f33
commit c32c7e8c34
7661 changed files with 1343635 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
K 25
svn:wc:ra_dav:version-url
V 82
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa
END
Alternativa3D.as
K 25
svn:wc:ra_dav:version-url
V 99
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/Alternativa3D.as
END

View File

@@ -0,0 +1,46 @@
8
dir
46043
http://svndev.alternativaplatform.com/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa
http://svndev.alternativaplatform.com
2008-09-08T10:18:35.971715Z
496
mike
svn:special svn:externals svn:needs-lock
d9e2387a-1f3e-40e2-b57f-9df5970a2fa5
utils
dir
Alternativa3D.as
file
2010-10-28T04:31:16.000000Z
18109d1146c9ae7270dff914e704698e
2008-09-08T08:01:08.116493Z
478
mike
engine3d
dir

View File

@@ -0,0 +1 @@
8

View File

@@ -0,0 +1,14 @@
package alternativa {
/**
* Класс содержит информацию о версии библиотеки.
* Также используется для интеграции библиотеки в среду разработки Adobe Flash.
*/
public class Alternativa3D {
/**
* Версия библиотеки в формате: поколение.feature-версия.fix-версия
*/
public static const version:String = "5.4.1";
}
}

View File

@@ -0,0 +1,14 @@
package alternativa {
/**
* Класс содержит информацию о версии библиотеки.
* Также используется для интеграции библиотеки в среду разработки Adobe Flash.
*/
public class Alternativa3D {
/**
* Версия библиотеки в формате: поколение.feature-версия.fix-версия
*/
public static const version:String = "5.4.1";
}
}

View File

@@ -0,0 +1,11 @@
K 25
svn:wc:ra_dav:version-url
V 91
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d
END
alternativa3d.as
K 25
svn:wc:ra_dav:version-url
V 108
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/alternativa3d.as
END

View File

@@ -0,0 +1,64 @@
8
dir
46043
http://svndev.alternativaplatform.com/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d
http://svndev.alternativaplatform.com
2008-09-08T10:18:35.971715Z
496
mike
svn:special svn:externals svn:needs-lock
d9e2387a-1f3e-40e2-b57f-9df5970a2fa5
materials
dir
physics
dir
alternativa3d.as
file
2010-10-28T04:31:16.000000Z
64183f832985e252cc4bc98977484bc9
2008-08-25T13:44:47.077292Z
176
int
display
dir
controllers
dir
core
dir
loaders
dir
primitives
dir
errors
dir

View File

@@ -0,0 +1 @@
8

View File

@@ -0,0 +1,3 @@
package alternativa.engine3d {
public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d";
}

View File

@@ -0,0 +1,3 @@
package alternativa.engine3d {
public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d";
}

View File

@@ -0,0 +1,29 @@
K 25
svn:wc:ra_dav:version-url
V 103
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/controllers
END
FlyController.as
K 25
svn:wc:ra_dav:version-url
V 120
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/controllers/FlyController.as
END
WalkController.as
K 25
svn:wc:ra_dav:version-url
V 121
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/controllers/WalkController.as
END
ObjectController.as
K 25
svn:wc:ra_dav:version-url
V 123
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/controllers/ObjectController.as
END
CameraController.as
K 25
svn:wc:ra_dav:version-url
V 123
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/controllers/CameraController.as
END

View File

@@ -0,0 +1,76 @@
8
dir
46043
http://svndev.alternativaplatform.com/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/controllers
http://svndev.alternativaplatform.com
2008-09-08T06:50:26.103978Z
468
mike
svn:special svn:externals svn:needs-lock
d9e2387a-1f3e-40e2-b57f-9df5970a2fa5
FlyController.as
file
2010-10-28T04:31:16.000000Z
819162f5f890b890eca280272d1c4dd8
2008-08-29T11:41:21.451513Z
304
wolf
WalkController.as
file
2010-10-28T04:31:16.000000Z
67cb497ffc9a536d65065dacb16a99be
2008-09-08T06:50:26.103978Z
468
mike
ObjectController.as
file
2010-10-28T04:31:16.000000Z
20fdfabe14732c8fb81c041f142da4a4
2008-08-29T11:41:21.451513Z
304
wolf
CameraController.as
file
2010-10-28T04:31:16.000000Z
cc1c7ecdc58c115dcdab53d2ea0ef1d9
2008-08-29T11:41:21.451513Z
304
wolf

View File

@@ -0,0 +1,889 @@
package alternativa.engine3d.controllers {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.physics.EllipsoidCollider;
import alternativa.types.Map;
import alternativa.types.Matrix3D;
import alternativa.types.Point3D;
import alternativa.types.Set;
import alternativa.utils.KeyboardUtils;
import alternativa.utils.MathUtils;
import flash.display.DisplayObject;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.utils.getTimer;
use namespace alternativa3d;
/**
* Контроллер камеры. Контроллер обеспечивает управление движением и поворотами камеры с использованием
* клавиатуры и мыши, а также предоставляет простую проверку столкновений камеры с объектами сцены.
*/
public class CameraController {
/**
* Имя действия для привязки клавиш движения вперёд.
*/
public static const ACTION_FORWARD:String = "ACTION_FORWARD";
/**
* Имя действия для привязки клавиш движения назад.
*/
public static const ACTION_BACK:String = "ACTION_BACK";
/**
* Имя действия для привязки клавиш движения влево.
*/
public static const ACTION_LEFT:String = "ACTION_LEFT";
/**
* Имя действия для привязки клавиш движения вправо.
*/
public static const ACTION_RIGHT:String = "ACTION_RIGHT";
/**
* Имя действия для привязки клавиш движения вверх.
*/
public static const ACTION_UP:String = "ACTION_UP";
/**
* Имя действия для привязки клавиш движения вниз.
*/
public static const ACTION_DOWN:String = "ACTION_DOWN";
// public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT";
// public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT";
/**
* Имя действия для привязки клавиш увеличения угла тангажа.
*/
public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP";
/**
* Имя действия для привязки клавиш уменьшения угла тангажа.
*/
public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN";
/**
* Имя действия для привязки клавиш уменьшения угла рысканья (поворота налево).
*/
public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT";
/**
* Имя действия для привязки клавиш увеличения угла рысканья (поворота направо).
*/
public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT";
/**
* Имя действия для привязки клавиш увеличения скорости.
*/
public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE";
// флаги действий
private var _forward:Boolean;
private var _back:Boolean;
private var _left:Boolean;
private var _right:Boolean;
private var _up:Boolean;
private var _down:Boolean;
private var _pitchUp:Boolean;
private var _pitchDown:Boolean;
private var _yawLeft:Boolean;
private var _yawRight:Boolean;
private var _accelerate:Boolean;
private var _moveLocal:Boolean = true;
// Флаг включения управления камерой
private var _controlsEnabled:Boolean = false;
// Значение таймера в начале прошлого кадра
private var lastFrameTime:uint;
// Чувствительность обзора мышью. Коэффициент умножения базовых коэффициентов поворотов.
private var _mouseSensitivity:Number = 1;
// Коэффициент поворота камеры по тангажу. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
private var _mousePitch:Number = Math.PI / 360;
// Результирующий коэффициент поворота камеры мышью по тангажу
private var _mousePitchCoeff:Number = _mouseSensitivity * _mousePitch;
// Коэффициент поворота камеры по рысканью. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
private var _mouseYaw:Number = Math.PI / 360;
// Результирующий коэффициент поворота камеры мышью по расканью
private var _mouseYawCoeff:Number = _mouseSensitivity * _mouseYaw;
// Вспомогательные переменные для обзора мышью
private var mouseLookActive:Boolean;
private var startDragCoords:Point = new Point();
private var currentDragCoords:Point = new Point();
private var prevDragCoords:Point = new Point();
private var startRotX:Number;
private var startRotZ:Number;
// Скорость изменения тангажа в радианах за секунду при управлении с клавиатуры
private var _pitchSpeed:Number = 1;
// Скорость изменения рысканья в радианах за секунду при управлении с клавиатуры
private var _yawSpeed:Number = 1;
// Скорость изменения крена в радианах за секунду при управлении с клавиатуры
// public var bankSpeed:Number = 2;
// private var bankMatrix:Matrix3D = new Matrix3D();
// Скорость поступательного движения в единицах за секунду
private var _speed:Number = 100;
// Коэффициент увеличения скорости при соответствующей нажатой клавише
private var _speedMultiplier:Number = 2;
private var velocity:Point3D = new Point3D();
private var destination:Point3D = new Point3D();
private var _fovStep:Number = Math.PI / 180;
private var _zoomMultiplier:Number = 0.1;
// Привязка клавиш к действиям
private var keyBindings:Map = new Map();
// Привязка действий к обработчикам
private var actionBindings:Map = new Map();
// Источник событий клавиатуры и мыши
private var _eventsSource:DisplayObject;
// Управляемая камера
private var _camera:Camera3D;
// Класс реализации определния столкновений
private var _collider:EllipsoidCollider;
// Флаг необходимости проверки столкновений
private var _checkCollisions:Boolean;
// Радиус сферы для определения столкновений
private var _collisionRadius:Number = 0;
// Набор исключаемых из проверки столкновений объектов
private var _collisionIgnoreSet:Set = new Set(true);
// Флаг движения
private var _isMoving:Boolean;
private var _onStartMoving:Function;
private var _onStopMoving:Function;
/**
* Создание экземпляра контроллера.
*
* @param eventsSourceObject объект, используемый для получения событий мыши и клавиатуры
*/
public function CameraController(eventsSourceObject:DisplayObject) {
if (eventsSourceObject == null) {
throw new ArgumentError("CameraController: eventsSource is null");
}
_eventsSource = eventsSourceObject;
actionBindings[ACTION_FORWARD] = forward;
actionBindings[ACTION_BACK] = back;
actionBindings[ACTION_LEFT] = left;
actionBindings[ACTION_RIGHT] = right;
actionBindings[ACTION_UP] = up;
actionBindings[ACTION_DOWN] = down;
actionBindings[ACTION_PITCH_UP] = pitchUp;
actionBindings[ACTION_PITCH_DOWN] = pitchDown;
actionBindings[ACTION_YAW_LEFT] = yawLeft;
actionBindings[ACTION_YAW_RIGHT] = yawRight;
actionBindings[ACTION_ACCELERATE] = accelerate;
}
/**
* Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
* <table border="1" style="border-collapse: collapse">
* <tr><th>Клавиша</th><th>Действие</th></tr>
* <tr><td>W</td><td>ACTION_FORWARD</td></tr>
* <tr><td>S</td><td>ACTION_BACK</td></tr>
* <tr><td>A</td><td>ACTION_LEFT</td></tr>
* <tr><td>D</td><td>ACTION_RIGHT</td></tr>
* <tr><td>SPACE</td><td>ACTION_UP</td></tr>
* <tr><td>CONTROL</td><td>ACTION_DOWN</td></tr>
* <tr><td>SHIFT</td><td>ACTION_ACCELERATE</td></tr>
* <tr><td>UP</td><td>ACTION_PITCH_UP</td></tr>
* <tr><td>DOWN</td><td>ACTION_PITCH_DOWN</td></tr>
* <tr><td>LEFT</td><td>ACTION_YAW_LEFT</td></tr>
* <tr><td>RIGHT</td><td>ACTION_YAW_RIGHT</td></tr>
* </table>
*/
public function setDefaultBindings():void {
unbindAll();
bindKey(KeyboardUtils.W, ACTION_FORWARD);
bindKey(KeyboardUtils.S, ACTION_BACK);
bindKey(KeyboardUtils.A, ACTION_LEFT);
bindKey(KeyboardUtils.D, ACTION_RIGHT);
bindKey(KeyboardUtils.SPACE, ACTION_UP);
bindKey(KeyboardUtils.CONTROL, ACTION_DOWN);
bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT);
bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT);
bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE);
}
/**
* Направление камеры на точку.
*
* @param point координаты точки направления камеры
*/
public function lookAt(point:Point3D):void {
if (_camera == null) {
return;
}
var dx:Number = point.x - _camera.x;
var dy:Number = point.y - _camera.y;
var dz:Number = point.z - _camera.z;
_camera.rotationZ = -Math.atan2(dx, dy);
_camera.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - MathUtils.DEG90;
}
/**
* Callback-функция, вызываемая при начале движения камеры.
*/
public function get onStartMoving():Function {
return _onStartMoving;
}
/**
* @private
*/
public function set onStartMoving(value:Function):void {
_onStartMoving = value;
}
/**
* Callback-функция, вызываемая при прекращении движения камеры.
*/
public function get onStopMoving():Function {
return _onStopMoving;
}
/**
* @private
*/
public function set onStopMoving(value:Function):void {
_onStopMoving = value;
}
/**
* Набор объектов, исключаемых из проверки столкновений.
*/
public function get collisionIgnoreSet():Set {
return _collisionIgnoreSet;
}
/**
* Источник событий клавиатуры и мыши.
*/
public function get eventsSource():DisplayObject {
return _eventsSource;
}
/**
* @private
*/
public function set eventsSource(value:DisplayObject):void {
if (_eventsSource != value) {
if (value == null) {
throw new ArgumentError("CameraController: eventsSource is null");
}
if (_controlsEnabled) {
unregisterEventsListeners();
}
_eventsSource = value;
if (_controlsEnabled) {
registerEventListeners();
}
}
}
/**
* Ассоциированная камера.
*/
public function get camera():Camera3D {
return _camera;
}
/**
* @private
*/
public function set camera(value:Camera3D):void {
if (_camera != value) {
_camera = value;
if (value == null) {
controlsEnabled = false;
} else {
createCollider();
}
}
}
/**
* Режим движения камеры. Если значение равно <code>true</code>, то перемещения камеры происходят относительно
* локальной системы координат, иначе относительно глобальной.
*
* @default true
*/
public function get moveLocal():Boolean {
return _moveLocal;
}
/**
* @private
*/
public function set moveLocal(value:Boolean):void {
_moveLocal = value;
}
/**
* Включение режима проверки столкновений.
*/
public function get checkCollisions():Boolean {
return _checkCollisions;
}
/**
* @private
*/
public function set checkCollisions(value:Boolean):void {
_checkCollisions = value;
}
/**
* Радиус сферы для определения столкновений.
*
* @default 0
*/
public function get collisionRadius():Number {
return _collisionRadius;
}
/**
* @private
*/
public function set collisionRadius(value:Number):void {
_collisionRadius = value;
if (_collider != null) {
_collider.radiusX = _collisionRadius;
_collider.radiusY = _collisionRadius;
_collider.radiusZ = _collisionRadius;
}
}
/**
* Привязка клавиши к действию.
*
* @param keyCode код клавиши
* @param action наименование действия
*/
public function bindKey(keyCode:uint, action:String):void {
var method:Function = actionBindings[action];
if (method != null) {
keyBindings[keyCode] = method;
}
}
/**
* Очистка привязки клавиши.
*
* @param keyCode код клавиши
*/
public function unbindKey(keyCode:uint):void {
keyBindings.remove(keyCode);
}
/**
* Очистка привязки всех клавиш.
*/
public function unbindAll():void {
keyBindings.clear();
}
/**
* Активация движения камеры вперёд.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function forward(value:Boolean):void {
_forward = value;
}
/**
* Активация движения камеры назад.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function back(value:Boolean):void {
_back = value;
}
/**
* Активация движения камеры влево.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function left(value:Boolean):void {
_left = value;
}
/**
* Активация движения камеры вправо.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function right(value:Boolean):void {
_right = value;
}
/**
* Активация движения камеры вверх.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function up(value:Boolean):void {
_up = value;
}
/**
* Активация движения камеры вниз.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function down(value:Boolean):void {
_down = value;
}
/**
* Активация поворота камеры вверх.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function pitchUp(value:Boolean):void {
_pitchUp = value;
}
/**
* Активация поворота камеры вниз.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function pitchDown(value:Boolean):void {
_pitchDown = value;
}
/**
* Активация поворота камеры влево.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function yawLeft(value:Boolean):void {
_yawLeft = value;
}
/**
* Активация поворота камеры вправо.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function yawRight(value:Boolean):void {
_yawRight = value;
}
/**
* Активация режима увеличенной скорости.
*
* @param value <code>true</code> для включения ускорения, <code>false</code> для выключения
*/
public function accelerate(value:Boolean):void {
_accelerate = value;
}
/**
* @private
*/
private function createCollider():void {
_collider = new EllipsoidCollider(_camera.scene, _collisionRadius);
_collider.offsetThreshold = 0.01;
_collider.collisionSet = _collisionIgnoreSet;
}
/**
* Чувствительность мыши &mdash; коэффициент умножения <code>mousePitch</code> и <code>mouseYaw</code>.
*
* @default 1
*
* @see #mousePitch()
* @see #mouseYaw()
*/
public function get mouseSensitivity():Number {
return _mouseSensitivity;
}
/**
* @private
*/
public function set mouseSensitivity(sensitivity:Number):void {
_mouseSensitivity = sensitivity;
_mousePitchCoeff = _mouseSensitivity * _mousePitch;
_mouseYawCoeff = _mouseSensitivity * _mouseYaw;
}
/**
* Скорость изменения угла тангажа при управлении мышью (радианы на пиксель).
*
* @default Math.PI / 360
*/
public function get mousePitch():Number {
return _mousePitch;
}
/**
* @private
*/
public function set mousePitch(pitch:Number):void {
_mousePitch = pitch;
_mousePitchCoeff = _mouseSensitivity * _mousePitch;
}
/**
* Скорость изменения угла рысканья при управлении мышью (радианы на пиксель).
*
* @default Math.PI / 360
*/
public function get mouseYaw():Number {
return _mouseYaw;
}
/**
* @private
*/
public function set mouseYaw(yaw:Number):void {
_mouseYaw = yaw;
_mouseYawCoeff = _mouseSensitivity * _mouseYaw;
}
/**
* Угловая скорость по тангажу при управлении с клавиатуры (радианы в секунду).
*
* @default 1
*/
public function get pitchSpeed():Number {
return _pitchSpeed;
}
/**
* @private
*/
public function set pitchSpeed(spd:Number):void {
_pitchSpeed = spd;
}
/**
* Угловая скорость по рысканью при управлении с клавиатуры (радианы в секунду).
*
* @default 1
*/
public function get yawSpeed():Number {
return _yawSpeed;
}
/**
* @private
*/
public function set yawSpeed(spd:Number):void {
_yawSpeed = spd;
}
/**
* Скорость поступательного движения (единицы в секунду).
*/
public function get speed():Number {
return _speed;
}
/**
* @private
*/
public function set speed(spd:Number):void {
_speed = spd;
}
/**
* Коэффициент увеличения скорости при активном действии <code>ACTION_ACCELERATE</code>.
*
* @default 2
*/
public function get speedMultiplier():Number {
return _speedMultiplier;
}
/**
* @private
*/
public function set speedMultiplier(value:Number):void {
_speedMultiplier = value;
}
/**
* Активность управления камеры.
*
* @default false
*/
public function get controlsEnabled():Boolean {
return _controlsEnabled;
}
/**
* @private
*/
public function set controlsEnabled(value:Boolean):void {
if (_camera == null || _controlsEnabled == value) return;
if (value) {
lastFrameTime = getTimer();
registerEventListeners();
}
else {
unregisterEventsListeners();
}
_controlsEnabled = value;
}
/**
* Базовый шаг изменения угла зрения в радианах. Реальный шаг получаеся умножением этого значения на величину
* <code>MouseEvent.delta</code>.
*
* @default Math.PI / 180
*/
public function get fovStep():Number {
return _fovStep;
}
/**
* @private
*/
public function set fovStep(value:Number):void {
_fovStep = value;
}
/**
* Множитель при изменении коэффициента увеличения. Закон изменения коэффициента увеличения описывается формулой:<br>
* <code>zoom (1 + MouseEvent.delta zoomMultiplier)</code>.
*
* @default 0.1
*/
public function get zoomMultiplier():Number {
return _zoomMultiplier;
}
/**
* @private
*/
public function set zoomMultiplier(value:Number):void {
_zoomMultiplier = value;
}
/**
* @private
*/
private function registerEventListeners():void {
_eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey);
_eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKey);
_eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
_eventsSource.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
}
/**
* @private
*/
private function unregisterEventsListeners():void {
_eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey);
_eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKey);
_eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
_eventsSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
_eventsSource.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
}
/**
* @private
* @param e
*/
private function onKey(e:KeyboardEvent):void {
var method:Function = keyBindings[e.keyCode];
if (method != null) {
method.call(this, e.type == KeyboardEvent.KEY_DOWN);
}
}
/**
* @private
* @param e
*/
private function onMouseDown(e:MouseEvent):void {
mouseLookActive = true;
currentDragCoords.x = startDragCoords.x = _eventsSource.stage.mouseX;
currentDragCoords.y = startDragCoords.y = _eventsSource.stage.mouseY;
startRotX = _camera.rotationX;
startRotZ = _camera.rotationZ;
_eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
/**
* @private
* @param e
*/
private function onMouseUp(e:MouseEvent):void {
mouseLookActive = false;
_eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
/**
* @private
* @param e
*/
private function onMouseWheel(e:MouseEvent):void {
if (_camera.orthographic) {
_camera.zoom = _camera.zoom * (1 + e.delta * _zoomMultiplier);
} else {
_camera.fov -= _fovStep * e.delta;
}
}
/**
* Обработка управляющих воздействий.
* Метод должен вызываться каждый кадр перед вызовом <code>Scene3D.calculate()</code>.
*
* @see alternativa.engine3d.core.Scene3D#calculate()
*/
public function processInput(): void {
if (!_controlsEnabled || _camera == null) return;
// Время в секундах от начала предыдущего кадра
var frameTime:Number = getTimer() - lastFrameTime;
lastFrameTime += frameTime;
frameTime /= 1000;
// Обработка mouselook
if (mouseLookActive) {
prevDragCoords.x = currentDragCoords.x;
prevDragCoords.y = currentDragCoords.y;
currentDragCoords.x = _eventsSource.stage.mouseX;
currentDragCoords.y = _eventsSource.stage.mouseY;
if (!prevDragCoords.equals(currentDragCoords)) {
_camera.rotationZ = startRotZ + (startDragCoords.x - currentDragCoords.x) * _mouseYawCoeff;
var rotX:Number = startRotX + (startDragCoords.y - currentDragCoords.y) * _mousePitchCoeff;
_camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
}
}
// Поворот относительно вертикальной оси (рысканье, yaw)
if (_yawLeft) {
_camera.rotationZ += _yawSpeed * frameTime;
} else if (_yawRight) {
_camera.rotationZ -= _yawSpeed * frameTime;
}
// Поворот относительно поперечной оси (тангаж, pitch)
if (_pitchUp) {
rotX = _camera.rotationX + _pitchSpeed * frameTime;
_camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
} else if (_pitchDown) {
rotX = _camera.rotationX - _pitchSpeed * frameTime;
_camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
}
// TODO: Поворот относительно продольной оси (крен, roll)
var frameDistance:Number = _speed * frameTime;
if (_accelerate) {
frameDistance *= _speedMultiplier;
}
velocity.x = 0;
velocity.y = 0;
velocity.z = 0;
var transformation:Matrix3D = _camera.transformation;
if (_moveLocal) {
// Режим относительных пермещений
// Движение вперед-назад
if (_forward) {
velocity.x += frameDistance * transformation.c;
velocity.y += frameDistance * transformation.g;
velocity.z += frameDistance * transformation.k;
} else if (_back) {
velocity.x -= frameDistance * transformation.c;
velocity.y -= frameDistance * transformation.g;
velocity.z -= frameDistance * transformation.k;
}
// Движение влево-вправо
if (_left) {
velocity.x -= frameDistance * transformation.a;
velocity.y -= frameDistance * transformation.e;
velocity.z -= frameDistance * transformation.i;
} else if (_right) {
velocity.x += frameDistance * transformation.a;
velocity.y += frameDistance * transformation.e;
velocity.z += frameDistance * transformation.i;
}
// Движение вверх-вниз
if (_up) {
velocity.x -= frameDistance * transformation.b;
velocity.y -= frameDistance * transformation.f;
velocity.z -= frameDistance * transformation.j;
} else if (_down) {
velocity.x += frameDistance * transformation.b;
velocity.y += frameDistance * transformation.f;
velocity.z += frameDistance * transformation.j;
}
}
else {
// Режим глобальных перемещений
var cosZ:Number = Math.cos(_camera.rotationZ);
var sinZ:Number = Math.sin(_camera.rotationZ);
// Движение вперед-назад
if (_forward) {
velocity.x -= frameDistance * sinZ;
velocity.y += frameDistance * cosZ;
} else if (_back) {
velocity.x += frameDistance * sinZ;
velocity.y -= frameDistance * cosZ;
}
// Движение влево-вправо
if (_left) {
velocity.x -= frameDistance * cosZ;
velocity.y -= frameDistance * sinZ;
} else if (_right) {
velocity.x += frameDistance * cosZ;
velocity.y += frameDistance * sinZ;
}
// Движение вверх-вниз
if (_up) {
velocity.z += frameDistance;
} else if (_down) {
velocity.z -= frameDistance;
}
}
// Коррекция модуля вектора скорости
if (velocity.x != 0 || velocity.y != 0 || velocity.z != 0) {
velocity.length = frameDistance;
}
// Проверка столкновений
if (_checkCollisions) {
_collider.calculateDestination(_camera.coords, velocity, destination);
_camera.x = destination.x;
_camera.y = destination.y;
_camera.z = destination.z;
} else {
_camera.x += velocity.x;
_camera.y += velocity.y;
_camera.z += velocity.z;
}
// Обработка начала/окончания движения
if (_camera.changeRotationOrScaleOperation.queued || _camera.changeCoordsOperation.queued) {
if (!_isMoving) {
_isMoving = true;
if (_onStartMoving != null) {
_onStartMoving.call(this);
}
}
} else {
if (_isMoving) {
_isMoving = false;
if (_onStopMoving != null) {
_onStopMoving.call(this);
}
}
}
}
}
}

View File

@@ -0,0 +1,450 @@
package alternativa.engine3d.controllers {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.types.Matrix3D;
import alternativa.types.Point3D;
import alternativa.utils.KeyboardUtils;
import alternativa.utils.MathUtils;
import flash.display.DisplayObject;
use namespace alternativa3d;
/**
* Контроллер, реализующий управление, подобное управлению летательным аппаратом для объекта, находящегося в системе
* координат корневого объекта сцены. Повороты выполняются вокруг локальных осей объекта, собственные ускорения
* действуют вдоль локальных осей.
*
* <p>Соответствия локальных осей для объектов, не являющихся камерой:
* <table border="1" style="border-collapse: collapse">
* <tr>
* <th>Ось</th><th>Направление</th><th>Поворот</th>
* </tr>
* <tr>
* <td>X</td><td>Вправо</td><td>Тангаж</td>
* </tr>
* <tr>
* <td>Y</td><td>Вперёд</td><td>Крен</td>
* </tr>
* <tr>
* <td>Z</td><td>Вверх</td><td>Рысканье</td>
* </tr>
* </table>
*
* <p>Соответствия локальных осей для объектов, являющихся камерой:
* <table border="1" style="border-collapse: collapse">
* <tr>
* <th>Ось</th><th>Направление</th><th>Поворот</th>
* </tr>
* <tr>
* <td>X</td><td>Вправо</td><td>Тангаж</td>
* </tr>
* <tr>
* <td>Y</td><td>Вниз</td><td>Рысканье</td>
* </tr>
* <tr>
* <td>Z</td><td>Вперёд</td><td>Крен</td>
* </tr>
* </table>
*
* Поворот мышью реализован следующим образом: в момент активации режима поворота (нажата левая кнопка мыши или
* соответствующая кнопка на клавиатуре) текущее положение курсора становится точкой, относительно которой определяются
* дальнейшие отклонения. Отклонение курсора по вертикали в пикселях, умноженное на коэффициент чувствительности мыши
* по вертикали даёт угловую скорость по тангажу. Отклонение курсора по горизонтали в пикселях, умноженное на коэффициент
* чувствительности мыши по горизонтали даёт угловую скорость по крену.
*/
public class FlyController extends ObjectController {
/**
* Имя действия для привязки клавиш поворота по крену влево.
*/
public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT";
/**
* Имя действия для привязки клавиш поворота по крену вправо.
*/
public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT";
private var _rollLeft:Boolean;
private var _rollRight:Boolean;
private var _rollSpeed:Number = 1;
private var rotations:Point3D;
private var rollMatrix:Matrix3D = new Matrix3D();
private var transformation:Matrix3D = new Matrix3D();
private var axis:Point3D = new Point3D();
private var velocity:Point3D = new Point3D();
private var displacement:Point3D = new Point3D();
private var destination:Point3D = new Point3D();
private var deltaVelocity:Point3D = new Point3D();
private var accelerationVector:Point3D = new Point3D();
private var currentTransform:Matrix3D = new Matrix3D();
private var _currentSpeed:Number = 1;
/**
* Текущие координаты мышиного курсора в режиме mouse look.
*/
private var currentMouseCoords:Point3D = new Point3D();
/**
* Модуль вектора ускорния, получаемого от команд движения.
*/
public var acceleration:Number = 1000;
/**
* Модуль вектора замедляющего ускорения.
*/
public var deceleration:Number = 50;
/**
* Погрешность определения скорости. Скорость приравнивается к нулю, если её модуль не превышает заданного значения.
*/
public var speedThreshold:Number = 1;
/**
* Переключение инерционного режима. В инерционном режиме отсутствует замедляющее ускорение, в результате чего вектор
* скорости объекта остаётся постоянным, если нет управляющих воздействий. При выключенном инерционном режиме к объекту
* прикладывается замедляющее ускорение.
*/
public var inertialMode:Boolean;
/**
* @inheritDoc
*/
public function FlyController(eventsSourceObject:DisplayObject) {
super(eventsSourceObject);
actionBindings[ACTION_ROLL_LEFT] = rollLeft;
actionBindings[ACTION_ROLL_RIGHT] = rollRight;
}
/**
* Текущая скорость движения.
*/
public function get currentSpeed():Number {
return _currentSpeed;
}
/**
* Активация вращения по крену влево.
*/
public function rollLeft(value:Boolean):void {
_rollLeft = value;
}
/**
* Активация вращения по крену вправо.
*/
public function rollRight(value:Boolean):void {
_rollRight = value;
}
/**
* Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
* <table border="1" style="border-collapse: collapse">
* <tr><th>Клавиша</th><th>Действие</th></tr>
* <tr><td>W</td><td>ACTION_FORWARD</td></tr>
* <tr><td>S</td><td>ACTION_BACK</td></tr>
* <tr><td>A</td><td>ACTION_LEFT</td></tr>
* <tr><td>D</td><td>ACTION_RIGHT</td></tr>
* <tr><td>SPACE</td><td>ACTION_UP</td></tr>
* <tr><td>CONTROL</td><td>ACTION_DOWN</td></tr>
* <tr><td>UP</td><td>ACTION_PITCH_UP</td></tr>
* <tr><td>DOWN</td><td>ACTION_PITCH_DOWN</td></tr>
* <tr><td>LEFT</td><td>ACTION_ROLL_LEFT</td></tr>
* <tr><td>RIGHT</td><td>ACTION_ROLL_RIGHT</td></tr>
* <tr><td>Q</td><td>ACTION_YAW_LEFT</td></tr>
* <tr><td>E</td><td>ACTION_YAW_RIGHT</td></tr>
* <tr><td>M</td><td>ACTION_MOUSE_LOOK</td></tr>
* </table>
*/
override public function setDefaultBindings():void {
unbindAll();
bindKey(KeyboardUtils.W, ACTION_FORWARD);
bindKey(KeyboardUtils.S, ACTION_BACK);
bindKey(KeyboardUtils.A, ACTION_LEFT);
bindKey(KeyboardUtils.D, ACTION_RIGHT);
bindKey(KeyboardUtils.SPACE, ACTION_UP);
bindKey(KeyboardUtils.CONTROL, ACTION_DOWN);
bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
bindKey(KeyboardUtils.LEFT, ACTION_ROLL_LEFT);
bindKey(KeyboardUtils.RIGHT, ACTION_ROLL_RIGHT);
bindKey(KeyboardUtils.Q, ACTION_YAW_LEFT);
bindKey(KeyboardUtils.E, ACTION_YAW_RIGHT);
bindKey(KeyboardUtils.M, ACTION_MOUSE_LOOK);
}
/**
* Метод выполняет поворот объекта относительно локальных осей в соответствии с имеющимися воздействиями.
*
* @param frameTime длительность текущего кадра в секундах
*/
override protected function rotateObject(frameTime:Number):void {
var transformation:Matrix3D = _object.transformation;
if (_mouseLookActive) {
currentMouseCoords.x = _eventsSource.stage.mouseX;
currentMouseCoords.y = _eventsSource.stage.mouseY;
if (!currentMouseCoords.equals(startMouseCoords)) {
var deltaYaw:Number = (currentMouseCoords.x - startMouseCoords.x) * _mouseCoefficientX;
if (_object is Camera3D) {
axis.x = transformation.c;
axis.y = transformation.g;
axis.z = transformation.k;
} else {
axis.x = transformation.b;
axis.y = transformation.f;
axis.z = transformation.j;
}
rotateObjectAroundAxis(axis, deltaYaw * frameTime);
currentTransform.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1);
var deltaPitch:Number = (startMouseCoords.y - currentMouseCoords.y) * _mouseCoefficientY;
axis.x = currentTransform.a;
axis.y = currentTransform.e;
axis.z = currentTransform.i;
rotateObjectAroundAxis(axis, deltaPitch * frameTime);
}
}
// Поворот относительно продольной оси (крен, roll)
if (_rollLeft) {
if (_object is Camera3D) {
axis.x = transformation.c;
axis.y = transformation.g;
axis.z = transformation.k;
} else {
axis.x = transformation.b;
axis.y = transformation.f;
axis.z = transformation.j;
}
rotateObjectAroundAxis(axis, -_rollSpeed * frameTime);
} else if (_rollRight) {
if (_object is Camera3D) {
axis.x = transformation.c;
axis.y = transformation.g;
axis.z = transformation.k;
} else {
axis.x = transformation.b;
axis.y = transformation.f;
axis.z = transformation.j;
}
rotateObjectAroundAxis(axis, _rollSpeed * frameTime);
}
// Поворот относительно поперечной оси (тангаж, pitch)
if (_pitchUp) {
axis.x = transformation.a;
axis.y = transformation.e;
axis.z = transformation.i;
rotateObjectAroundAxis(axis, _pitchSpeed * frameTime);
} else if (_pitchDown) {
axis.x = transformation.a;
axis.y = transformation.e;
axis.z = transformation.i;
rotateObjectAroundAxis(axis, -_pitchSpeed * frameTime);
}
// Поворот относительно вертикальной оси (рысканье, yaw)
if (_yawRight) {
if (_object is Camera3D) {
axis.x = transformation.b;
axis.y = transformation.f;
axis.z = transformation.j;
rotateObjectAroundAxis(axis, _yawSpeed * frameTime);
} else {
axis.x = transformation.c;
axis.y = transformation.g;
axis.z = transformation.k;
rotateObjectAroundAxis(axis, -_yawSpeed * frameTime);
}
} else if (_yawLeft) {
if (_object is Camera3D) {
axis.x = transformation.b;
axis.y = transformation.f;
axis.z = transformation.j;
rotateObjectAroundAxis(axis, -_yawSpeed * frameTime);
} else {
axis.x = transformation.c;
axis.y = transformation.g;
axis.z = transformation.k;
rotateObjectAroundAxis(axis, _yawSpeed * frameTime);
}
}
}
/**
* Метод вычисляет вектор потенциального смещения эллипсоида.
*
* @param frameTime длительность текущего кадра в секундах
* @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта
*/
override protected function getDisplacement(frameTime:Number, displacement:Point3D):void {
// Движение вперед-назад
accelerationVector.x = 0;
accelerationVector.y = 0;
accelerationVector.z = 0;
if (_forward) {
accelerationVector.y = 1;
} else if (_back) {
accelerationVector.y = -1;
}
// Движение влево-вправо
if (_right) {
accelerationVector.x = 1;
} else if (_left) {
accelerationVector.x = -1;
}
// Движение ввверх-вниз
if (_up) {
accelerationVector.z = 1;
} else if (_down) {
accelerationVector.z = -1;
}
var speedLoss:Number;
var len:Number;
if (accelerationVector.x != 0 || accelerationVector.y != 0 || accelerationVector.z != 0) {
// Управление активно
if (_object is Camera3D) {
var tmp:Number = accelerationVector.z;
accelerationVector.z = accelerationVector.y;
accelerationVector.y = -tmp;
}
accelerationVector.normalize();
accelerationVector.x *= acceleration;
accelerationVector.y *= acceleration;
accelerationVector.z *= acceleration;
currentTransform.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1);
accelerationVector.transform(currentTransform);
deltaVelocity.x = accelerationVector.x;
deltaVelocity.y = accelerationVector.y;
deltaVelocity.z = accelerationVector.z;
deltaVelocity.x *= frameTime;
deltaVelocity.y *= frameTime;
deltaVelocity.z *= frameTime;
if (!inertialMode) {
speedLoss = deceleration * frameTime;
var dot:Number = Point3D.dot(velocity, accelerationVector);
if (dot > 0) {
len = accelerationVector.length;
var x:Number = accelerationVector.x / len;
var y:Number = accelerationVector.y / len;
var z:Number = accelerationVector.z / len;
len = dot / len;
x = velocity.x - len * x;
y = velocity.y - len * y;
z = velocity.z - len * z;
len = Math.sqrt(x*x + y*y + z*z);
if (len > speedLoss) {
x *= speedLoss / len;
y *= speedLoss / len;
z *= speedLoss / len;
}
velocity.x -= x;
velocity.y -= y;
velocity.z -= z;
} else {
len = velocity.length;
velocity.length = (len > speedLoss) ? (len - speedLoss) : 0;
}
}
velocity.x += deltaVelocity.x;
velocity.y += deltaVelocity.y;
velocity.z += deltaVelocity.z;
if (velocity.length > _speed) {
velocity.length = _speed;
}
} else {
// Управление неактивно
if (!inertialMode) {
speedLoss = deceleration * frameTime;
len = velocity.length;
velocity.length = (len > speedLoss) ? (len - speedLoss) : 0;
}
}
displacement.x = velocity.x * frameTime;
displacement.y = velocity.y * frameTime;
displacement.z = velocity.z * frameTime;
}
/**
* Метод применяет потенциальный вектор смещения к эллипсоиду с учётом столкновений с геометрией сцены, если включён
* соотвествующий режим.
*
* @param frameTime время кадра в секундах
* @param displacement векотр потенциального смещения эллипсоида
*/
override protected function applyDisplacement(frameTime:Number, displacement:Point3D):void {
if (checkCollisions) {
_collider.calculateDestination(_coords, displacement, destination);
displacement.x = destination.x - _coords.x;
displacement.y = destination.y - _coords.y;
displacement.z = destination.z - _coords.z;
} else {
destination.x = _coords.x + displacement.x;
destination.y = _coords.y + displacement.y;
destination.z = _coords.z + displacement.z;
}
velocity.x = displacement.x / frameTime;
velocity.y = displacement.y / frameTime;
velocity.z = displacement.z / frameTime;
_coords.x = destination.x;
_coords.y = destination.y;
_coords.z = destination.z;
setObjectCoords();
var len:Number = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
if (len < speedThreshold) {
velocity.x = 0;
velocity.y = 0;
velocity.z = 0;
_currentSpeed = 0;
} else {
_currentSpeed = len;
}
}
/**
* Поворот объекта вокруг заданной оси.
*
* @param axis
* @param angle
*/
private function rotateObjectAroundAxis(axis:Point3D, angle:Number):void {
transformation.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1);
rollMatrix.fromAxisAngle(axis, angle);
rollMatrix.inverseCombine(transformation);
rotations = rollMatrix.getRotations(rotations);
_object.rotationX = rotations.x;
_object.rotationY = rotations.y;
_object.rotationZ = rotations.z;
}
/**
* Направление объекта на точку. В результате работы метода локальная ось объекта, соответствующая направлению "вперёд"
* будет направлена на указанную точку, а угол поворота вокруг этой оси будет равен нулю.
*
* @param point координаты точки, на которую должен быть направлен объект
*/
public function lookAt(point:Point3D):void {
if (_object == null) {
return;
}
var dx:Number = point.x - _object.x;
var dy:Number = point.y - _object.y;
var dz:Number = point.z - _object.z;
_object.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - (_object is Camera3D ? MathUtils.DEG90 : 0);
_object.rotationY = 0;
_object.rotationZ = -Math.atan2(dx, dy);
}
}
}

View File

@@ -0,0 +1,839 @@
package alternativa.engine3d.controllers {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.physics.EllipsoidCollider;
import alternativa.types.Map;
import alternativa.types.Point3D;
import alternativa.utils.MathUtils;
import alternativa.utils.ObjectUtils;
import flash.display.DisplayObject;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.utils.getTimer;
use namespace alternativa3d;
/**
* Базовый контроллер для изменения ориентации и положения объекта в сцене с помощью клавиатуры и мыши. В классе
* реализована поддержка назначения обработчиков клавиатурных команд, а также обработчики для основных команд
* перемещения.
*/
public class ObjectController {
/**
* Имя действия для привязки клавиш движения вперёд.
*/
public static const ACTION_FORWARD:String = "ACTION_FORWARD";
/**
* Имя действия для привязки клавиш движения назад.
*/
public static const ACTION_BACK:String = "ACTION_BACK";
/**
* Имя действия для привязки клавиш движения влево.
*/
public static const ACTION_LEFT:String = "ACTION_LEFT";
/**
* Имя действия для привязки клавиш движения вправо.
*/
public static const ACTION_RIGHT:String = "ACTION_RIGHT";
/**
* Имя действия для привязки клавиш движения вверх.
*/
public static const ACTION_UP:String = "ACTION_UP";
/**
* Имя действия для привязки клавиш движения вниз.
*/
public static const ACTION_DOWN:String = "ACTION_DOWN";
/**
* Имя действия для привязки клавиш поворота вверх.
*/
public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP";
/**
* Имя действия для привязки клавиш поворота вниз.
*/
public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN";
/**
* Имя действия для привязки клавиш поворота налево.
*/
public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT";
/**
* Имя действия для привязки клавиш поворота направо.
*/
public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT";
/**
* Имя действия для привязки клавиш увеличения скорости.
*/
public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE";
/**
* Имя действия для привязки клавиш активации обзора мышью.
*/
public static const ACTION_MOUSE_LOOK:String = "ACTION_MOUSE_LOOK";
/**
* Флаг движения вперёд.
*/
protected var _forward:Boolean;
/**
* Флаг движения назад.
*/
protected var _back:Boolean;
/**
* Флаг движения влево.
*/
protected var _left:Boolean;
/**
* Флаг движения вправо.
*/
protected var _right:Boolean;
/**
* Флаг движения вверх.
*/
protected var _up:Boolean;
/**
* Флаг движения вниз.
*/
protected var _down:Boolean;
/**
* Флаг поворота относительно оси X в положительном направлении (взгляд вверх).
*/
protected var _pitchUp:Boolean;
/**
* Флаг поворота относительно оси X в отрицательном направлении (взгляд вниз).
*/
protected var _pitchDown:Boolean;
/**
* Флаг поворота относительно оси Z в положительном направлении (взгляд налево).
*/
protected var _yawLeft:Boolean;
/**
* Флаг активности поворота относительно оси Z в отрицательном направлении (взгляд направо).
*/
protected var _yawRight:Boolean;
/**
* Флаг активности режима ускорения.
*/
protected var _accelerate:Boolean;
/**
* Флаг активности режима поворотов мышью.
*/
protected var _mouseLookActive:Boolean;
/**
* Начальные координаты мышиного курсора в режиме mouse look.
*/
protected var startMouseCoords:Point3D = new Point3D();
/**
* Флаг активности контроллера.
*/
protected var _enabled:Boolean = true;
/**
* Источник событий клавиатуры и мыши
*/
protected var _eventsSource:DisplayObject;
/**
* Ассоциативный массив, связывающий коды клавиатурных клавиш с именами команд.
*/
protected var keyBindings:Map = new Map();
/**
* Ассоциативный массив, связывающий имена команд с реализующими их функциями. Функции должны иметь вид
* function(value:Boolean):void. Значение параметра <code>value</code> указывает, нажата или отпущена соответсвующая команде
* клавиша.
*/
protected var actionBindings:Map = new Map();
/**
* Флаг активности клавиатуры.
*/
protected var _keyboardEnabled:Boolean;
/**
* Флаг активности мыши.
*/
protected var _mouseEnabled:Boolean;
/**
* Общая чувствительность мыши. Коэффициент умножения чувствительности по вертикали и горизонтали.
*/
protected var _mouseSensitivity:Number = 1;
/**
* Коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
*/
protected var _mouseSensitivityY:Number = Math.PI / 360;
/**
* Коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
*/
protected var _mouseSensitivityX:Number = Math.PI / 360;
/**
* Результирующий коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
*/
protected var _mouseCoefficientY:Number = _mouseSensitivity * _mouseSensitivityY;
/**
* Результирующий коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
*/
protected var _mouseCoefficientX:Number = _mouseSensitivity * _mouseSensitivityX;
/**
* Угловая скорость поворота вокруг поперечной оси в радианах за секунду.
*/
protected var _pitchSpeed:Number = 1;
/**
* Угловая скорость поворота вокруг вертикальной оси в радианах за секунду.
*/
protected var _yawSpeed:Number = 1;
/**
* Скорость поступательного движения в единицах за секунду.
*/
protected var _speed:Number = 100;
/**
* Коэффициент увеличения скорости при соответствующей активной команде.
*/
protected var _speedMultiplier:Number = 2;
/**
* Управляемый объект.
*/
protected var _object:Object3D;
/**
* Время в секундах, прошедшее с последнего вызова метода processInput (обычно с последнего кадра).
*/
protected var lastFrameTime:uint;
/**
* Текущие координаты контроллера.
*/
protected var _coords:Point3D = new Point3D();
/**
* Индикатор движения объекта (перемещения или поворота) в текущем кадре.
*/
protected var _isMoving:Boolean;
/**
* Объект для определения столкновений.
*/
protected var _collider:EllipsoidCollider = new EllipsoidCollider();
/**
* Включение и выключение режима проверки столкновений.
*/
public var checkCollisions:Boolean;
/**
* Функция вида <code>function():void</code>, вызываемая при начале движения объекта. Под движением
* понимается изменение координат или ориентации.
*/
public var onStartMoving:Function;
/**
* Функция вида <code>function():void</code>, вызываемая при прекращении движения объекта. Под движением
* понимается изменение координат или ориентации.
*/
public var onStopMoving:Function;
// Вектор смещения
private var _displacement:Point3D = new Point3D();
/**
* Создаёт новый экземпляр контролллера.
*
* @param eventsSourceObject источник событий клавиатуры и мыши
*/
public function ObjectController(eventsSourceObject:DisplayObject) {
if (eventsSourceObject == null) {
throw new ArgumentError(ObjectUtils.getClassName(this) + ": eventsSourceObject is null");
}
_eventsSource = eventsSourceObject;
actionBindings[ACTION_FORWARD] = moveForward;
actionBindings[ACTION_BACK] = moveBack;
actionBindings[ACTION_LEFT] = moveLeft;
actionBindings[ACTION_RIGHT] = moveRight;
actionBindings[ACTION_UP] = moveUp;
actionBindings[ACTION_DOWN] = moveDown;
actionBindings[ACTION_PITCH_UP] = pitchUp;
actionBindings[ACTION_PITCH_DOWN] = pitchDown;
actionBindings[ACTION_YAW_LEFT] = yawLeft;
actionBindings[ACTION_YAW_RIGHT] = yawRight;
actionBindings[ACTION_ACCELERATE] = accelerate;
actionBindings[ACTION_MOUSE_LOOK] = setMouseLook;
keyboardEnabled = true;
mouseEnabled = true;
}
/**
* Включение и выключение контроллера. Выключенный контроллер пропускает выполнение метода <code>processInput()</code>.
*
* @default true
*
* @see #processInput()
*/
public function get enabled():Boolean {
return _enabled;
}
/**
* @private
*/
public function set enabled(value:Boolean):void {
_enabled = value;
}
/**
* Координаты контроллера. Координаты совпадают с координатами центра эллипсоида, используемого для определения
* столкновений. Координаты управляемого объекта могут не совпадать с координатами контроллера.
*
* @see #setObjectCoords()
*/
public function get coords():Point3D {
return _coords.clone();
}
/**
* @private
*/
public function set coords(value:Point3D):void {
_coords.copy(value);
setObjectCoords();
}
/**
* Чтение координат контроллера в заданную переменную.
*
* @param point переменная, в которую записываются координаты контроллера
*/
public function readCoords(point:Point3D):void {
point.copy(_coords);
}
/**
* Управляемый объект.
*/
public function get object():Object3D {
return _object;
}
/**
* @private
* При установке объекта устанавливается сцена для коллайдера.
*/
public function set object(value:Object3D):void {
_object = value;
_collider.scene = _object == null ? null : _object.scene;
}
/**
* Объект, реализующий проверку столкновений для эллипсоида.
*/
public function get collider():EllipsoidCollider {
return _collider;
}
/**
* Активация движения вперёд.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function moveForward(value:Boolean):void {
_forward = value;
}
/**
* Активация движения назад.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function moveBack(value:Boolean):void {
_back = value;
}
/**
* Активация движения влево.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function moveLeft(value:Boolean):void {
_left = value;
}
/**
* Активация движения вправо.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function moveRight(value:Boolean):void {
_right = value;
}
/**
* Активация движения вверх.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function moveUp(value:Boolean):void {
_up = value;
}
/**
* Активация движения вниз.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function moveDown(value:Boolean):void {
_down = value;
}
/**
* Активация поворота вверх.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function pitchUp(value:Boolean):void {
_pitchUp = value;
}
/**
* Активация поворота вниз.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function pitchDown(value:Boolean):void {
_pitchDown = value;
}
/**
* Активация поворота влево.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function yawLeft(value:Boolean):void {
_yawLeft = value;
}
/**
* Активация поворота вправо.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function yawRight(value:Boolean):void {
_yawRight = value;
}
/**
* Активация режима увеличенной скорости.
*
* @param value <code>true</code> для включения ускорения, <code>false</code> для выключения
*/
public function accelerate(value:Boolean):void {
_accelerate = value;
}
/**
* Угловая скорость поворота вокруг поперечной оси (радианы в секунду).
*
* @default 1
*/
public function get pitchSpeed():Number {
return _pitchSpeed;
}
/**
* @private
*/
public function set pitchSpeed(spd:Number):void {
_pitchSpeed = spd;
}
/**
* Угловая скорость поворота вокруг вертикальной оси (радианы в секунду).
*
* @default 1
*/
public function get yawSpeed():Number {
return _yawSpeed;
}
/**
* @private
*/
public function set yawSpeed(spd:Number):void {
_yawSpeed = spd;
}
/**
* Скорость движения в единицах за секунду. При установке отрицательного значения берётся модуль.
*
* @default 100
*/
public function get speed():Number {
return _speed;
}
/**
* @private
*/
public function set speed(value:Number):void {
_speed = value < 0 ? -value : value;
}
/**
* Коэффициент увеличения скорости при активном действии <code>ACTION_ACCELERATE</code>.
*
* @default 2
*/
public function get speedMultiplier():Number {
return _speedMultiplier;
}
/**
* @private
*/
public function set speedMultiplier(value:Number):void {
_speedMultiplier = value;
}
/**
* Чувствительность мыши &mdash; коэффициент умножения <code>mouseSensitivityX</code> и <code>mouseSensitivityY</code>.
*
* @default 1
*
* @see #mouseSensitivityY()
* @see #mouseSensitivityX()
*/
public function get mouseSensitivity():Number {
return _mouseSensitivity;
}
/**
* @private
*/
public function set mouseSensitivity(sensitivity:Number):void {
_mouseSensitivity = sensitivity;
_mouseCoefficientY = _mouseSensitivity * _mouseSensitivityY;
_mouseCoefficientX = _mouseSensitivity * _mouseSensitivityX;
}
/**
* Чувтсвительность мыши по вертикали.
*
* @default Math.PI / 360
*
* @see #mouseSensitivity()
* @see #mouseSensitivityX()
*/
public function get mouseSensitivityY():Number {
return _mouseSensitivityY;
}
/**
* @private
*/
public function set mouseSensitivityY(value:Number):void {
_mouseSensitivityY = value;
_mouseCoefficientY = _mouseSensitivity * _mouseSensitivityY;
}
/**
* Чувтсвительность мыши по горизонтали.
*
* @default Math.PI / 360
*
* @see #mouseSensitivity()
* @see #mouseSensitivityY()
*/
public function get mouseSensitivityX():Number {
return _mouseSensitivityX;
}
/**
* @private
*/
public function set mouseSensitivityX(value:Number):void {
_mouseSensitivityX = value;
_mouseCoefficientX = _mouseSensitivity * _mouseSensitivityX;
}
/**
* Включение/выключение режима вращения объекта мышью. При включении режима вполняется метод <code>startMouseLook()</code>,
* при выключении &mdash; <code>stoptMouseLook()</code>.
*
* @see #startMouseLook()
* @see #stopMouseLook()
*/
public function setMouseLook(value:Boolean):void {
if (_mouseLookActive != value) {
_mouseLookActive = value;
if (_mouseLookActive) {
startMouseLook();
} else {
stopMouseLook();
}
}
}
/**
* Метод выполняет необходимые действия при включении режима вращения объекта мышью.
* Реализация по умолчанию записывает начальные глобальные координаты курсора мыши в переменную <code>startMouseCoords</code>.
*
* @see #startMouseCoords
* @see #setMouseLook()
* @see #stopMouseLook()
*/
protected function startMouseLook():void {
startMouseCoords.x = _eventsSource.stage.mouseX;
startMouseCoords.y = _eventsSource.stage.mouseY;
}
/**
* Метод выполняет необходимые действия при выключении вращения объекта мышью. Реализация по умолчанию не делает
* ничего.
*
* @see #setMouseLook()
* @see #startMouseLook()
*/
protected function stopMouseLook():void {
}
/**
* Метод выполняет обработку всех воздействий на объект. Если объект не установлен или свойство <code>enabled</code>
* равно <code>false</code>, метод не выполняется.
* <p>
* Алгоритм работы метода следующий:
* <ul>
* <li> Вычисляется время в секундах, прошедшее с последнего вызова метода (с последнего кадра). Это время считается
* длительностью текущего кадра;
* <li> Вызывается метод rotateObject(), который изменяет ориентацию объекта в соответствии с воздействиями;
* <li> Вызывается метод getDisplacement(), который вычисляет потенциальное перемещение объекта;
* <li> Вызывается метод applyDisplacement(), которому передаётся вектор перемещения, полученный на предыдущем шаге.
* Задачей метода является применение заданного вектора перемещения;
* <li> При необходимости вызываются обработчики начала и прекращения движения управляемого объекта;
* </ul>
*/
public function processInput():void {
if (!_enabled || _object == null) {
return;
}
var frameTime:Number = getTimer() - lastFrameTime;
lastFrameTime += frameTime;
if (frameTime > 100) {
frameTime = 100;
}
frameTime /= 1000;
rotateObject(frameTime);
getDisplacement(frameTime, _displacement);
applyDisplacement(frameTime, _displacement);
// Обработка начала/окончания движения
if (_object.changeRotationOrScaleOperation.queued || _object.changeCoordsOperation.queued) {
if (!_isMoving) {
_isMoving = true;
if (onStartMoving != null) {
onStartMoving.call(this);
}
}
} else {
if (_isMoving) {
_isMoving = false;
if (onStopMoving != null) {
onStopMoving.call(this);
}
}
}
}
/**
* Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Реализация по умолчанию не делает ничего.
*
* @param frameTime длительность текущего кадра в секундах
*/
protected function rotateObject(frameTime:Number):void {
}
/**
* Метод вычисляет потенциальное смещение объекта за кадр. Реализация по умолчанию не делает ничего.
*
* @param frameTime длительность текущего кадра в секундах
* @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта
*/
protected function getDisplacement(frameTime:Number, displacement:Point3D):void {
}
/**
* Метод применяет потенциальное смещение объекта. Реализация по умолчанию не делает ничего.
*
* @param frameTime длительность текущего кадра в секундах
* @param displacement смещение объекта, которое нужно обработать
*/
protected function applyDisplacement(frameTime:Number, displacement:Point3D):void {
}
/**
* Метод выполняет привязку клавиши к действию. Одной клавише может быть назначено только одно действие.
*
* @param keyCode код клавиши
* @param action наименование действия
*
* @see #unbindKey()
* @see #unbindAll()
*/
public function bindKey(keyCode:uint, action:String):void {
var method:Function = actionBindings[action];
if (method != null) {
keyBindings[keyCode] = method;
}
}
/**
* Очистка привязки клавиши.
*
* @param keyCode код клавиши
*
* @see #bindKey()
* @see #unbindAll()
*/
public function unbindKey(keyCode:uint):void {
keyBindings.remove(keyCode);
}
/**
* Очистка привязки всех клавиш.
*
* @see #bindKey()
* @see #unbindKey()
*/
public function unbindAll():void {
keyBindings.clear();
}
/**
* Метод устанавливает привязки клавиш по умолчанию. Реализация по умолчанию не делает ничего.
*
* @see #bindKey()
* @see #unbindKey()
* @see #unbindAll()
*/
public function setDefaultBindings():void {
}
/**
* Включение и выключение обработки клавиатурных событий. При включении выполняется метод <code>registerKeyboardListeners</code>,
* при выключении &mdash; <code>unregisterKeyboardListeners</code>.
*
* @see #registerKeyboardListeners()
* @see #unregisterKeyboardListeners()
*/
public function get keyboardEnabled():Boolean {
return _keyboardEnabled;
}
/**
* @private
*/
public function set keyboardEnabled(value:Boolean):void {
if (_keyboardEnabled != value) {
_keyboardEnabled = value;
if (_keyboardEnabled) {
registerKeyboardListeners();
} else {
unregisterKeyboardListeners();
}
}
}
/**
* @private
* Запуск обработчиков клавиатурных команд.
*/
private function onKeyboardEvent(e:KeyboardEvent):void {
var method:Function = keyBindings[e.keyCode];
if (method != null) {
method.call(this, e.type == KeyboardEvent.KEY_DOWN);
}
}
/**
* Регистрация необходимых обработчиков при включении обработки клавиатурных событий.
*
* @see #unregisterKeyboardListeners()
*/
protected function registerKeyboardListeners():void {
_eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent);
_eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent);
}
/**
* Удаление обработчиков при выключении обработки клавиатурных событий.
*
* @see #registerKeyboardListeners()
*/
protected function unregisterKeyboardListeners():void {
_eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent);
_eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent);
}
/**
* Включение и выключение обработки мышиных событий. При включении выполняется метод <code>registerMouseListeners</code>,
* при выключении &mdash; <code>unregisterMouseListeners</code>.
*
* @see #registerMouseListeners()
* @see #unregisterMouseListeners()
*/
public function get mouseEnabled():Boolean {
return _mouseEnabled;
}
/**
* @private
*/
public function set mouseEnabled(value:Boolean):void {
if (_mouseEnabled != value) {
_mouseEnabled = value;
if (_mouseEnabled) {
registerMouseListeners();
} else {
unregisterMouseListeners();
}
}
}
/**
* Регистрация необходимых обработчиков при включении обработки мышиных событий.
*
* @see #unregisterMouseListeners()
*/
protected function registerMouseListeners():void {
_eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
}
/**
* Удаление используемых обработчиков при выключении обработки мышиных событий.
*
* @see #registerMouseListeners()
*/
protected function unregisterMouseListeners():void {
_eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
_eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
/**
* Активация mouselook
*/
private function onMouseDown(e:MouseEvent):void {
setMouseLook(true);
_eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
/**
* Отключение mouselook
*/
private function onMouseUp(e:MouseEvent):void {
setMouseLook(false);
_eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
/**
* Установка координат управляемого объекта в зависимости от текущих координат контроллера.
*/
protected function setObjectCoords():void {
_object.coords = _coords;
}
/**
* Индикатор режима увеличенной скорости.
*/
public function get accelerated():Boolean {
return _accelerate;
}
}
}

View File

@@ -0,0 +1,508 @@
package alternativa.engine3d.controllers {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.physics.Collision;
import alternativa.types.Matrix3D;
import alternativa.types.Point3D;
import alternativa.utils.KeyboardUtils;
import alternativa.utils.MathUtils;
import flash.display.DisplayObject;
use namespace alternativa3d;
/**
* Контроллер, реализующий управление движением объекта, находящегося в системе координат корневого объекта сцены.
*
* <p>Контроллер предоставляет два режима движения: режим ходьбы с учётом силы тяжести и режим полёта, в котором сила
* тяжести не учитывается. В обоих режимах может быть включена проверка столкновений с объектами сцены. Если проверка
* столкновений отключена, то в режиме ходьбы сила тяжести также игнорируется и дополнительно появляется возможность
* движения по вертикали.
*
* <p>Для всех объектов, за исключением <code>Camera3D</code>, направлением "вперёд" считается направление его оси
* <code>Y</code>, направлением "вверх" &mdash; направление оси <code>Z</code>. Для объектов класса
* <code>Camera3D</code> направление "вперёд" совпадает с направлением локальной оси <code>Z</code>, а направление
* "вверх" противоположно направлению локальной оси <code>Y</code>.
*
* <p>Вне зависимости от того, включена проверка столкновений или нет, координаты при перемещении расчитываются для
* эллипсоида, параметры которого устанавливаются через свойство <code>collider</code>. Координаты управляемого
* объекта вычисляются исходя из положения центра эллипсоида и положения объекта на вертикальной оси эллипсоида,
* задаваемого параметром <code>objectZPosition</code>.
*
* <p>Команда <code>ACTION_UP</code> в режиме ходьбы при ненулевой гравитации вызывает прыжок, в остальных случаях
* происходит движение вверх.
*/
public class WalkController extends ObjectController {
/**
* Величина ускорения свободного падения. При положительном значении сила тяжести направлена против оси Z,
* при отрицательном &mdash; по оси Z.
*/
public var gravity:Number = 0;
/**
* Вертикальная скорость прыжка.
*/
public var jumpSpeed:Number = 0;
/**
* Объект, на котором стоит эллипсоид при ненулевой гравитации.
*/
private var _groundMesh:Mesh;
/**
* Погрешность определения скорости. В режиме полёта или в режиме ходьбы при нахождении на поверхности
* скорость приравнивается к нулю, если её модуль не превышает заданного значения.
*/
public var speedThreshold:Number = 1;
// Коэффициент эффективности управления перемещением при нахождении в воздухе в режиме ходьбы и нулевой гравитации.
private var _airControlCoefficient:Number = 1;
private var _currentSpeed:Number = 0;
private var minGroundCos:Number = Math.cos(MathUtils.toRadian(70));
private var destination:Point3D = new Point3D();
private var collision:Collision = new Collision();
private var _objectZPosition:Number = 0.5;
private var _flyMode:Boolean;
private var _onGround:Boolean;
private var velocity:Point3D = new Point3D();
private var tmpVelocity:Point3D = new Point3D();
private var controlsActive:Boolean;
private var inJump:Boolean;
private var startRotX:Number;
private var startRotZ:Number;
// Координаты мышиного курсора в режиме mouse look в предыдущем кадре.
private var prevMouseCoords:Point3D = new Point3D();
// Текущие координаты мышиного курсора в режиме mouse look.
private var currentMouseCoords:Point3D = new Point3D();
/**
* @inheritDoc
*/
public function WalkController(eventSourceObject:DisplayObject) {
super(eventSourceObject);
}
/**
* Объект, на котором стоит эллипсоид при ненулевой гравитации.
*/
public function get groundMesh():Mesh {
return _groundMesh;
}
/**
* Направление объекта на точку. В результате работы метода локальная ось объекта, соответствующая направлению "вперёд"
* будет направлена на указанную точку, а угол поворота вокруг этой оси будет равен нулю.
*
* @param point координаты точки, на которую должен быть направлен объект
*/
public function lookAt(point:Point3D):void {
if (_object == null) {
return;
}
var dx:Number = point.x - _object.x;
var dy:Number = point.y - _object.y;
var dz:Number = point.z - _object.z;
_object.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - (_object is Camera3D ? MathUtils.DEG90 : 0);
_object.rotationY = 0;
_object.rotationZ = -Math.atan2(dx, dy);
}
/**
* Коэффициент эффективности управления перемещением в режиме ходьбы при нахождении в воздухе и ненулевой гравитации.
* Значение 0 обозначает полное отсутствие контроля, значение 1 указывает, что управление так же эффективно, как при
* нахождении на поверхности.
*
* @default 1
*/
public function get airControlCoefficient():Number {
return _airControlCoefficient;
}
/**
* @private
*/
public function set airControlCoefficient(value:Number):void {
_airControlCoefficient = value > 0 ? value : -value;
}
/**
* Максимальный угол наклона поверхности в радианах, на которой возможен прыжок и на которой объект стоит на месте
* в отсутствие управляющих воздействий. Если угол наклона поверхности превышает заданное значение, свойство
* <code>onGround</code> будет иметь значение <code>false</code>.
*
* @see #onGround
*/
public function get maxGroundAngle():Number {
return Math.acos(minGroundCos);
}
/**
* @private
*/
public function set maxGroundAngle(value:Number):void {
minGroundCos = Math.cos(value);
}
/**
* Положение управляемого объекта на оси Z эллипсоида. Значение 0 указывает положение в нижней точке эллипсоида,
* значение 1 -- положение в верхней точке эллипсоида.
*/
public function get objectZPosition():Number {
return _objectZPosition;
}
/**
* @private
*/
public function set objectZPosition(value:Number):void {
_objectZPosition = value;
setObjectCoords();
}
/**
* Включене и выключение режима полёта.
*
* @default false
*/
public function get flyMode():Boolean {
return _flyMode;
}
/**
* @private
*/
public function set flyMode(value:Boolean):void {
_flyMode = value;
}
/**
* Индикатор положения эллипсоида на поверхности в режиме ходьбы. Считается, что эллипсоид находится на поверхности,
* если угол наклона поверхности под ним не превышает заданного свойством <code>maxGroundAngle</code> значения.
*
* @see #maxGroundAngle
*/
public function get onGround():Boolean {
return _onGround;
}
/**
* Модуль текущей скорости.
*/
public function get currentSpeed():Number {
return _currentSpeed;
}
/**
* Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
* <table border="1" style="border-collapse: collapse">
* <tr><th>Клавиша</th><th>Действие</th></tr>
* <tr><td>W</td><td>ACTION_FORWARD</td></tr>
* <tr><td>S</td><td>ACTION_BACK</td></tr>
* <tr><td>A</td><td>ACTION_LEFT</td></tr>
* <tr><td>D</td><td>ACTION_RIGHT</td></tr>
* <tr><td>SPACE</td><td>ACTION_UP</td></tr>
* <tr><td>CONTROL</td><td>ACTION_DOWN</td></tr>
* <tr><td>SHIFT</td><td>ACTION_ACCELERATE</td></tr>
* <tr><td>UP</td><td>ACTION_PITCH_UP</td></tr>
* <tr><td>DOWN</td><td>ACTION_PITCH_DOWN</td></tr>
* <tr><td>LEFT</td><td>ACTION_YAW_LEFT</td></tr>
* <tr><td>RIGHT</td><td>ACTION_YAW_RIGHT</td></tr>
* <tr><td>M</td><td>ACTION_MOUSE_LOOK</td></tr>
* </table>
*/
override public function setDefaultBindings():void {
unbindAll();
bindKey(KeyboardUtils.W, ACTION_FORWARD);
bindKey(KeyboardUtils.S, ACTION_BACK);
bindKey(KeyboardUtils.A, ACTION_LEFT);
bindKey(KeyboardUtils.D, ACTION_RIGHT);
bindKey(KeyboardUtils.SPACE, ACTION_UP);
bindKey(KeyboardUtils.CONTROL, ACTION_DOWN);
bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT);
bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT);
bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE);
bindKey(KeyboardUtils.M, ACTION_MOUSE_LOOK);
}
/**
* Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Взгляд вверх и вниз ограничен
* отклонением в 90 градусов от горизонтали.
*
* @param frameTime длительность текущего кадра в секундах
*/
override protected function rotateObject(frameTime:Number):void {
// Mouse look
var rotX:Number;
if (_mouseLookActive) {
prevMouseCoords.copy(currentMouseCoords);
currentMouseCoords.x = _eventsSource.stage.mouseX;
currentMouseCoords.y = _eventsSource.stage.mouseY;
if (!prevMouseCoords.equals(currentMouseCoords)) {
_object.rotationZ = startRotZ + (startMouseCoords.x - currentMouseCoords.x) * _mouseCoefficientX;
rotX = startRotX + (startMouseCoords.y - currentMouseCoords.y) * _mouseCoefficientY;
if (_object is Camera3D) {
// Коррекция поворота для камеры
_object.rotationX = (rotX > MathUtils.DEG90) ? 0 : (rotX < -MathUtils.DEG90) ? -Math.PI : rotX - MathUtils.DEG90;
} else {
_object.rotationX = (rotX > MathUtils.DEG90) ? MathUtils.DEG90 : (rotX < -MathUtils.DEG90) ? -MathUtils.DEG90 : rotX;
}
}
}
// Повороты влево-вправо
if (_yawLeft) {
_object.rotationZ += _yawSpeed * frameTime;
} else if (_yawRight) {
_object.rotationZ -= _yawSpeed * frameTime;
}
// Взгляд вверх-вниз
rotX = NaN;
if (_pitchUp) {
rotX = _object.rotationX + _pitchSpeed * frameTime;
} else if (_pitchDown) {
rotX = _object.rotationX - _pitchSpeed * frameTime;
}
if (!isNaN(rotX)) {
if (_object is Camera3D) {
// Коррекция поворота для камеры
_object.rotationX = (rotX > 0) ? 0 : (rotX < -Math.PI) ? -Math.PI : rotX;
} else {
_object.rotationX = (rotX > MathUtils.DEG90) ? MathUtils.DEG90 : (rotX < -MathUtils.DEG90) ? -MathUtils.DEG90 : rotX;
}
}
}
/**
* Метод вычисляет вектор потенциального смещения эллипсоида, учитывая режим перемещения, команды перемещения и силу тяжести.
*
* @param frameTime длительность текущего кадра в секундах
* @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта
*/
override protected function getDisplacement(frameTime:Number, displacement:Point3D):void {
var cos:Number = 0;
if (checkCollisions && !_flyMode) {
// Проверка наличия под ногами поверхности
displacement.x = 0;
displacement.y = 0;
displacement.z = - 0.5 * gravity * frameTime * frameTime;
if (_collider.getCollision(_coords, displacement, collision)) {
cos = collision.normal.z;
_groundMesh = collision.face._mesh;
} else {
_groundMesh = null;
}
}
_onGround = cos > minGroundCos;
if (_onGround && inJump) {
inJump = false;
}
var len:Number;
var x:Number;
var y:Number;
var z:Number;
// При наличии управляющих воздействий расчитывается приращение скорости
controlsActive = _forward || _back || _right || _left || _up || _down;
if (controlsActive) {
if (_flyMode) {
tmpVelocity.x = 0;
tmpVelocity.y = 0;
tmpVelocity.z = 0;
// Режим полёта, ускорения направлены вдоль локальных осей
// Ускорение вперёд-назад
if (_forward) {
tmpVelocity.y = 1;
} else if (_back) {
tmpVelocity.y = -1;
}
// Ускорение влево-вправо
if (_right) {
tmpVelocity.x = 1;
} else if (_left) {
tmpVelocity.x = -1;
}
// Ускорение вверх-вниз
if (_up) {
tmpVelocity.z = 1;
} else if (_down) {
tmpVelocity.z = -1;
}
var matrix:Matrix3D = _object.transformation;
x = tmpVelocity.x;
if (_object is Camera3D) {
y = -tmpVelocity.z;
z = tmpVelocity.y;
} else {
y = tmpVelocity.y;
z = tmpVelocity.z;
}
// Поворот вектора из локальной системы координат объекта в глобальную
velocity.x += (x * matrix.a + y * matrix.b + z * matrix.c) * _speed;
velocity.y += (x * matrix.e + y * matrix.f + z * matrix.g) * _speed;
velocity.z += (x * matrix.i + y * matrix.j + z * matrix.k) * _speed;
} else {
// Режим хождения, ускорения вперёд-назад-влево-вправо лежат в глобальной плоскости XY, вверх-вниз направлены вдоль глобальной оси Z
var heading:Number = _object.rotationZ;
var headingCos:Number = Math.cos(heading);
var headingSin:Number = Math.sin(heading);
var spd:Number = _speed;
if (gravity != 0 && !_onGround) {
spd *= _airControlCoefficient;
}
// Вперёд-назад
if (_forward) {
velocity.x -= spd * headingSin;
velocity.y += spd * headingCos;
} else if (_back) {
velocity.x += spd * headingSin;
velocity.y -= spd * headingCos;
}
// Влево-вправо
if (_right) {
velocity.x += spd * headingCos;
velocity.y += spd * headingSin;
} else if (_left) {
velocity.x -= spd * headingCos;
velocity.y -= spd * headingSin;
}
if (gravity == 0) {
// Ускорение вверх-вниз
if (_up) {
velocity.z += _speed;
} else if (_down) {
velocity.z -= _speed;
}
}
}
} else {
// Управление неактивно, замедление движения
len = 1 / Math.pow(3, frameTime * 10);
if (_flyMode || gravity == 0) {
velocity.x *= len;
velocity.y *= len;
velocity.z *= len;
} else {
if (_onGround) {
velocity.x *= len;
velocity.y *= len;
if (velocity.z < 0) {
velocity.z *= len;
}
} else {
if (cos > 0 && velocity.z > 0) {
velocity.z = 0;
}
}
}
}
// Прыжок
if (_onGround && _up && !inJump) {
velocity.z = jumpSpeed;
inJump = true;
_onGround = false;
cos = 0;
}
// В режиме ходьбы добавляется ускорение свободного падения, если находимся не на ровной поверхности
if (!(_flyMode || _onGround)) {
velocity.z -= gravity * frameTime;
}
// Ограничение скорости
var max:Number = _accelerate ? _speed * _speedMultiplier : _speed;
if (_flyMode || gravity == 0) {
len = velocity.length;
if (len > max) {
velocity.length = max;
}
} else {
len = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y);
if (len > max) {
velocity.x *= max / len;
velocity.y *= max / len;
}
if (cos > 0 && velocity.z > 0) {
velocity.z = 0;
}
}
// Cмещение за кадр
displacement.x = velocity.x * frameTime;
displacement.y = velocity.y * frameTime;
displacement.z = velocity.z * frameTime;
}
/**
* Метод применяет потенциальный вектор смещения к эллипсоиду с учётом столкновений с геометрией сцены, если включён
* соотвествующий режим.
*
* @param frameTime время кадра в секундах
* @param displacement векотр потенциального смещения эллипсоида
*/
override protected function applyDisplacement(frameTime:Number, displacement:Point3D):void {
if (checkCollisions) {
_collider.calculateDestination(_coords, displacement, destination);
displacement.x = destination.x - _coords.x;
displacement.y = destination.y - _coords.y;
displacement.z = destination.z - _coords.z;
} else {
destination.x = _coords.x + displacement.x;
destination.y = _coords.y + displacement.y;
destination.z = _coords.z + displacement.z;
}
velocity.x = displacement.x / frameTime;
velocity.y = displacement.y / frameTime;
velocity.z = displacement.z / frameTime;
_coords.x = destination.x;
_coords.y = destination.y;
_coords.z = destination.z;
setObjectCoords();
var len:Number = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
if (len < speedThreshold) {
velocity.x = 0;
velocity.y = 0;
velocity.z = 0;
_currentSpeed = 0;
} else {
_currentSpeed = len;
}
}
/**
* Метод устанавливает координаты управляемого объекта в зависимости от параметра <code>objectZPosition</code>.
*
* @see #objectZPosition
*/
override protected function setObjectCoords():void {
_object.x = _coords.x;
_object.y = _coords.y;
_object.z = _coords.z + (2 * _objectZPosition - 1) * _collider.radiusZ;
}
/**
* Метод выполняет необходимые действия при включении вращения объекта мышью.
*/
override protected function startMouseLook():void {
super.startMouseLook();
startRotX = _object is Camera3D ? _object.rotationX + MathUtils.DEG90 : _object.rotationX;
startRotZ = _object.rotationZ;
}
}
}

View File

@@ -0,0 +1,889 @@
package alternativa.engine3d.controllers {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.physics.EllipsoidCollider;
import alternativa.types.Map;
import alternativa.types.Matrix3D;
import alternativa.types.Point3D;
import alternativa.types.Set;
import alternativa.utils.KeyboardUtils;
import alternativa.utils.MathUtils;
import flash.display.DisplayObject;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.utils.getTimer;
use namespace alternativa3d;
/**
* Контроллер камеры. Контроллер обеспечивает управление движением и поворотами камеры с использованием
* клавиатуры и мыши, а также предоставляет простую проверку столкновений камеры с объектами сцены.
*/
public class CameraController {
/**
* Имя действия для привязки клавиш движения вперёд.
*/
public static const ACTION_FORWARD:String = "ACTION_FORWARD";
/**
* Имя действия для привязки клавиш движения назад.
*/
public static const ACTION_BACK:String = "ACTION_BACK";
/**
* Имя действия для привязки клавиш движения влево.
*/
public static const ACTION_LEFT:String = "ACTION_LEFT";
/**
* Имя действия для привязки клавиш движения вправо.
*/
public static const ACTION_RIGHT:String = "ACTION_RIGHT";
/**
* Имя действия для привязки клавиш движения вверх.
*/
public static const ACTION_UP:String = "ACTION_UP";
/**
* Имя действия для привязки клавиш движения вниз.
*/
public static const ACTION_DOWN:String = "ACTION_DOWN";
// public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT";
// public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT";
/**
* Имя действия для привязки клавиш увеличения угла тангажа.
*/
public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP";
/**
* Имя действия для привязки клавиш уменьшения угла тангажа.
*/
public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN";
/**
* Имя действия для привязки клавиш уменьшения угла рысканья (поворота налево).
*/
public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT";
/**
* Имя действия для привязки клавиш увеличения угла рысканья (поворота направо).
*/
public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT";
/**
* Имя действия для привязки клавиш увеличения скорости.
*/
public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE";
// флаги действий
private var _forward:Boolean;
private var _back:Boolean;
private var _left:Boolean;
private var _right:Boolean;
private var _up:Boolean;
private var _down:Boolean;
private var _pitchUp:Boolean;
private var _pitchDown:Boolean;
private var _yawLeft:Boolean;
private var _yawRight:Boolean;
private var _accelerate:Boolean;
private var _moveLocal:Boolean = true;
// Флаг включения управления камерой
private var _controlsEnabled:Boolean = false;
// Значение таймера в начале прошлого кадра
private var lastFrameTime:uint;
// Чувствительность обзора мышью. Коэффициент умножения базовых коэффициентов поворотов.
private var _mouseSensitivity:Number = 1;
// Коэффициент поворота камеры по тангажу. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
private var _mousePitch:Number = Math.PI / 360;
// Результирующий коэффициент поворота камеры мышью по тангажу
private var _mousePitchCoeff:Number = _mouseSensitivity * _mousePitch;
// Коэффициент поворота камеры по рысканью. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
private var _mouseYaw:Number = Math.PI / 360;
// Результирующий коэффициент поворота камеры мышью по расканью
private var _mouseYawCoeff:Number = _mouseSensitivity * _mouseYaw;
// Вспомогательные переменные для обзора мышью
private var mouseLookActive:Boolean;
private var startDragCoords:Point = new Point();
private var currentDragCoords:Point = new Point();
private var prevDragCoords:Point = new Point();
private var startRotX:Number;
private var startRotZ:Number;
// Скорость изменения тангажа в радианах за секунду при управлении с клавиатуры
private var _pitchSpeed:Number = 1;
// Скорость изменения рысканья в радианах за секунду при управлении с клавиатуры
private var _yawSpeed:Number = 1;
// Скорость изменения крена в радианах за секунду при управлении с клавиатуры
// public var bankSpeed:Number = 2;
// private var bankMatrix:Matrix3D = new Matrix3D();
// Скорость поступательного движения в единицах за секунду
private var _speed:Number = 100;
// Коэффициент увеличения скорости при соответствующей нажатой клавише
private var _speedMultiplier:Number = 2;
private var velocity:Point3D = new Point3D();
private var destination:Point3D = new Point3D();
private var _fovStep:Number = Math.PI / 180;
private var _zoomMultiplier:Number = 0.1;
// Привязка клавиш к действиям
private var keyBindings:Map = new Map();
// Привязка действий к обработчикам
private var actionBindings:Map = new Map();
// Источник событий клавиатуры и мыши
private var _eventsSource:DisplayObject;
// Управляемая камера
private var _camera:Camera3D;
// Класс реализации определния столкновений
private var _collider:EllipsoidCollider;
// Флаг необходимости проверки столкновений
private var _checkCollisions:Boolean;
// Радиус сферы для определения столкновений
private var _collisionRadius:Number = 0;
// Набор исключаемых из проверки столкновений объектов
private var _collisionIgnoreSet:Set = new Set(true);
// Флаг движения
private var _isMoving:Boolean;
private var _onStartMoving:Function;
private var _onStopMoving:Function;
/**
* Создание экземпляра контроллера.
*
* @param eventsSourceObject объект, используемый для получения событий мыши и клавиатуры
*/
public function CameraController(eventsSourceObject:DisplayObject) {
if (eventsSourceObject == null) {
throw new ArgumentError("CameraController: eventsSource is null");
}
_eventsSource = eventsSourceObject;
actionBindings[ACTION_FORWARD] = forward;
actionBindings[ACTION_BACK] = back;
actionBindings[ACTION_LEFT] = left;
actionBindings[ACTION_RIGHT] = right;
actionBindings[ACTION_UP] = up;
actionBindings[ACTION_DOWN] = down;
actionBindings[ACTION_PITCH_UP] = pitchUp;
actionBindings[ACTION_PITCH_DOWN] = pitchDown;
actionBindings[ACTION_YAW_LEFT] = yawLeft;
actionBindings[ACTION_YAW_RIGHT] = yawRight;
actionBindings[ACTION_ACCELERATE] = accelerate;
}
/**
* Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
* <table border="1" style="border-collapse: collapse">
* <tr><th>Клавиша</th><th>Действие</th></tr>
* <tr><td>W</td><td>ACTION_FORWARD</td></tr>
* <tr><td>S</td><td>ACTION_BACK</td></tr>
* <tr><td>A</td><td>ACTION_LEFT</td></tr>
* <tr><td>D</td><td>ACTION_RIGHT</td></tr>
* <tr><td>SPACE</td><td>ACTION_UP</td></tr>
* <tr><td>CONTROL</td><td>ACTION_DOWN</td></tr>
* <tr><td>SHIFT</td><td>ACTION_ACCELERATE</td></tr>
* <tr><td>UP</td><td>ACTION_PITCH_UP</td></tr>
* <tr><td>DOWN</td><td>ACTION_PITCH_DOWN</td></tr>
* <tr><td>LEFT</td><td>ACTION_YAW_LEFT</td></tr>
* <tr><td>RIGHT</td><td>ACTION_YAW_RIGHT</td></tr>
* </table>
*/
public function setDefaultBindings():void {
unbindAll();
bindKey(KeyboardUtils.W, ACTION_FORWARD);
bindKey(KeyboardUtils.S, ACTION_BACK);
bindKey(KeyboardUtils.A, ACTION_LEFT);
bindKey(KeyboardUtils.D, ACTION_RIGHT);
bindKey(KeyboardUtils.SPACE, ACTION_UP);
bindKey(KeyboardUtils.CONTROL, ACTION_DOWN);
bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT);
bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT);
bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE);
}
/**
* Направление камеры на точку.
*
* @param point координаты точки направления камеры
*/
public function lookAt(point:Point3D):void {
if (_camera == null) {
return;
}
var dx:Number = point.x - _camera.x;
var dy:Number = point.y - _camera.y;
var dz:Number = point.z - _camera.z;
_camera.rotationZ = -Math.atan2(dx, dy);
_camera.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - MathUtils.DEG90;
}
/**
* Callback-функция, вызываемая при начале движения камеры.
*/
public function get onStartMoving():Function {
return _onStartMoving;
}
/**
* @private
*/
public function set onStartMoving(value:Function):void {
_onStartMoving = value;
}
/**
* Callback-функция, вызываемая при прекращении движения камеры.
*/
public function get onStopMoving():Function {
return _onStopMoving;
}
/**
* @private
*/
public function set onStopMoving(value:Function):void {
_onStopMoving = value;
}
/**
* Набор объектов, исключаемых из проверки столкновений.
*/
public function get collisionIgnoreSet():Set {
return _collisionIgnoreSet;
}
/**
* Источник событий клавиатуры и мыши.
*/
public function get eventsSource():DisplayObject {
return _eventsSource;
}
/**
* @private
*/
public function set eventsSource(value:DisplayObject):void {
if (_eventsSource != value) {
if (value == null) {
throw new ArgumentError("CameraController: eventsSource is null");
}
if (_controlsEnabled) {
unregisterEventsListeners();
}
_eventsSource = value;
if (_controlsEnabled) {
registerEventListeners();
}
}
}
/**
* Ассоциированная камера.
*/
public function get camera():Camera3D {
return _camera;
}
/**
* @private
*/
public function set camera(value:Camera3D):void {
if (_camera != value) {
_camera = value;
if (value == null) {
controlsEnabled = false;
} else {
createCollider();
}
}
}
/**
* Режим движения камеры. Если значение равно <code>true</code>, то перемещения камеры происходят относительно
* локальной системы координат, иначе относительно глобальной.
*
* @default true
*/
public function get moveLocal():Boolean {
return _moveLocal;
}
/**
* @private
*/
public function set moveLocal(value:Boolean):void {
_moveLocal = value;
}
/**
* Включение режима проверки столкновений.
*/
public function get checkCollisions():Boolean {
return _checkCollisions;
}
/**
* @private
*/
public function set checkCollisions(value:Boolean):void {
_checkCollisions = value;
}
/**
* Радиус сферы для определения столкновений.
*
* @default 0
*/
public function get collisionRadius():Number {
return _collisionRadius;
}
/**
* @private
*/
public function set collisionRadius(value:Number):void {
_collisionRadius = value;
if (_collider != null) {
_collider.radiusX = _collisionRadius;
_collider.radiusY = _collisionRadius;
_collider.radiusZ = _collisionRadius;
}
}
/**
* Привязка клавиши к действию.
*
* @param keyCode код клавиши
* @param action наименование действия
*/
public function bindKey(keyCode:uint, action:String):void {
var method:Function = actionBindings[action];
if (method != null) {
keyBindings[keyCode] = method;
}
}
/**
* Очистка привязки клавиши.
*
* @param keyCode код клавиши
*/
public function unbindKey(keyCode:uint):void {
keyBindings.remove(keyCode);
}
/**
* Очистка привязки всех клавиш.
*/
public function unbindAll():void {
keyBindings.clear();
}
/**
* Активация движения камеры вперёд.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function forward(value:Boolean):void {
_forward = value;
}
/**
* Активация движения камеры назад.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function back(value:Boolean):void {
_back = value;
}
/**
* Активация движения камеры влево.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function left(value:Boolean):void {
_left = value;
}
/**
* Активация движения камеры вправо.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function right(value:Boolean):void {
_right = value;
}
/**
* Активация движения камеры вверх.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function up(value:Boolean):void {
_up = value;
}
/**
* Активация движения камеры вниз.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function down(value:Boolean):void {
_down = value;
}
/**
* Активация поворота камеры вверх.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function pitchUp(value:Boolean):void {
_pitchUp = value;
}
/**
* Активация поворота камеры вниз.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function pitchDown(value:Boolean):void {
_pitchDown = value;
}
/**
* Активация поворота камеры влево.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function yawLeft(value:Boolean):void {
_yawLeft = value;
}
/**
* Активация поворота камеры вправо.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function yawRight(value:Boolean):void {
_yawRight = value;
}
/**
* Активация режима увеличенной скорости.
*
* @param value <code>true</code> для включения ускорения, <code>false</code> для выключения
*/
public function accelerate(value:Boolean):void {
_accelerate = value;
}
/**
* @private
*/
private function createCollider():void {
_collider = new EllipsoidCollider(_camera.scene, _collisionRadius);
_collider.offsetThreshold = 0.01;
_collider.collisionSet = _collisionIgnoreSet;
}
/**
* Чувствительность мыши &mdash; коэффициент умножения <code>mousePitch</code> и <code>mouseYaw</code>.
*
* @default 1
*
* @see #mousePitch()
* @see #mouseYaw()
*/
public function get mouseSensitivity():Number {
return _mouseSensitivity;
}
/**
* @private
*/
public function set mouseSensitivity(sensitivity:Number):void {
_mouseSensitivity = sensitivity;
_mousePitchCoeff = _mouseSensitivity * _mousePitch;
_mouseYawCoeff = _mouseSensitivity * _mouseYaw;
}
/**
* Скорость изменения угла тангажа при управлении мышью (радианы на пиксель).
*
* @default Math.PI / 360
*/
public function get mousePitch():Number {
return _mousePitch;
}
/**
* @private
*/
public function set mousePitch(pitch:Number):void {
_mousePitch = pitch;
_mousePitchCoeff = _mouseSensitivity * _mousePitch;
}
/**
* Скорость изменения угла рысканья при управлении мышью (радианы на пиксель).
*
* @default Math.PI / 360
*/
public function get mouseYaw():Number {
return _mouseYaw;
}
/**
* @private
*/
public function set mouseYaw(yaw:Number):void {
_mouseYaw = yaw;
_mouseYawCoeff = _mouseSensitivity * _mouseYaw;
}
/**
* Угловая скорость по тангажу при управлении с клавиатуры (радианы в секунду).
*
* @default 1
*/
public function get pitchSpeed():Number {
return _pitchSpeed;
}
/**
* @private
*/
public function set pitchSpeed(spd:Number):void {
_pitchSpeed = spd;
}
/**
* Угловая скорость по рысканью при управлении с клавиатуры (радианы в секунду).
*
* @default 1
*/
public function get yawSpeed():Number {
return _yawSpeed;
}
/**
* @private
*/
public function set yawSpeed(spd:Number):void {
_yawSpeed = spd;
}
/**
* Скорость поступательного движения (единицы в секунду).
*/
public function get speed():Number {
return _speed;
}
/**
* @private
*/
public function set speed(spd:Number):void {
_speed = spd;
}
/**
* Коэффициент увеличения скорости при активном действии <code>ACTION_ACCELERATE</code>.
*
* @default 2
*/
public function get speedMultiplier():Number {
return _speedMultiplier;
}
/**
* @private
*/
public function set speedMultiplier(value:Number):void {
_speedMultiplier = value;
}
/**
* Активность управления камеры.
*
* @default false
*/
public function get controlsEnabled():Boolean {
return _controlsEnabled;
}
/**
* @private
*/
public function set controlsEnabled(value:Boolean):void {
if (_camera == null || _controlsEnabled == value) return;
if (value) {
lastFrameTime = getTimer();
registerEventListeners();
}
else {
unregisterEventsListeners();
}
_controlsEnabled = value;
}
/**
* Базовый шаг изменения угла зрения в радианах. Реальный шаг получаеся умножением этого значения на величину
* <code>MouseEvent.delta</code>.
*
* @default Math.PI / 180
*/
public function get fovStep():Number {
return _fovStep;
}
/**
* @private
*/
public function set fovStep(value:Number):void {
_fovStep = value;
}
/**
* Множитель при изменении коэффициента увеличения. Закон изменения коэффициента увеличения описывается формулой:<br>
* <code>zoom (1 + MouseEvent.delta zoomMultiplier)</code>.
*
* @default 0.1
*/
public function get zoomMultiplier():Number {
return _zoomMultiplier;
}
/**
* @private
*/
public function set zoomMultiplier(value:Number):void {
_zoomMultiplier = value;
}
/**
* @private
*/
private function registerEventListeners():void {
_eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey);
_eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKey);
_eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
_eventsSource.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
}
/**
* @private
*/
private function unregisterEventsListeners():void {
_eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey);
_eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKey);
_eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
_eventsSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
_eventsSource.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
}
/**
* @private
* @param e
*/
private function onKey(e:KeyboardEvent):void {
var method:Function = keyBindings[e.keyCode];
if (method != null) {
method.call(this, e.type == KeyboardEvent.KEY_DOWN);
}
}
/**
* @private
* @param e
*/
private function onMouseDown(e:MouseEvent):void {
mouseLookActive = true;
currentDragCoords.x = startDragCoords.x = _eventsSource.stage.mouseX;
currentDragCoords.y = startDragCoords.y = _eventsSource.stage.mouseY;
startRotX = _camera.rotationX;
startRotZ = _camera.rotationZ;
_eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
/**
* @private
* @param e
*/
private function onMouseUp(e:MouseEvent):void {
mouseLookActive = false;
_eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
/**
* @private
* @param e
*/
private function onMouseWheel(e:MouseEvent):void {
if (_camera.orthographic) {
_camera.zoom = _camera.zoom * (1 + e.delta * _zoomMultiplier);
} else {
_camera.fov -= _fovStep * e.delta;
}
}
/**
* Обработка управляющих воздействий.
* Метод должен вызываться каждый кадр перед вызовом <code>Scene3D.calculate()</code>.
*
* @see alternativa.engine3d.core.Scene3D#calculate()
*/
public function processInput(): void {
if (!_controlsEnabled || _camera == null) return;
// Время в секундах от начала предыдущего кадра
var frameTime:Number = getTimer() - lastFrameTime;
lastFrameTime += frameTime;
frameTime /= 1000;
// Обработка mouselook
if (mouseLookActive) {
prevDragCoords.x = currentDragCoords.x;
prevDragCoords.y = currentDragCoords.y;
currentDragCoords.x = _eventsSource.stage.mouseX;
currentDragCoords.y = _eventsSource.stage.mouseY;
if (!prevDragCoords.equals(currentDragCoords)) {
_camera.rotationZ = startRotZ + (startDragCoords.x - currentDragCoords.x) * _mouseYawCoeff;
var rotX:Number = startRotX + (startDragCoords.y - currentDragCoords.y) * _mousePitchCoeff;
_camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
}
}
// Поворот относительно вертикальной оси (рысканье, yaw)
if (_yawLeft) {
_camera.rotationZ += _yawSpeed * frameTime;
} else if (_yawRight) {
_camera.rotationZ -= _yawSpeed * frameTime;
}
// Поворот относительно поперечной оси (тангаж, pitch)
if (_pitchUp) {
rotX = _camera.rotationX + _pitchSpeed * frameTime;
_camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
} else if (_pitchDown) {
rotX = _camera.rotationX - _pitchSpeed * frameTime;
_camera.rotationX = (rotX > 0) ? 0 : (rotX < -MathUtils.DEG180) ? -MathUtils.DEG180 : rotX;
}
// TODO: Поворот относительно продольной оси (крен, roll)
var frameDistance:Number = _speed * frameTime;
if (_accelerate) {
frameDistance *= _speedMultiplier;
}
velocity.x = 0;
velocity.y = 0;
velocity.z = 0;
var transformation:Matrix3D = _camera.transformation;
if (_moveLocal) {
// Режим относительных пермещений
// Движение вперед-назад
if (_forward) {
velocity.x += frameDistance * transformation.c;
velocity.y += frameDistance * transformation.g;
velocity.z += frameDistance * transformation.k;
} else if (_back) {
velocity.x -= frameDistance * transformation.c;
velocity.y -= frameDistance * transformation.g;
velocity.z -= frameDistance * transformation.k;
}
// Движение влево-вправо
if (_left) {
velocity.x -= frameDistance * transformation.a;
velocity.y -= frameDistance * transformation.e;
velocity.z -= frameDistance * transformation.i;
} else if (_right) {
velocity.x += frameDistance * transformation.a;
velocity.y += frameDistance * transformation.e;
velocity.z += frameDistance * transformation.i;
}
// Движение вверх-вниз
if (_up) {
velocity.x -= frameDistance * transformation.b;
velocity.y -= frameDistance * transformation.f;
velocity.z -= frameDistance * transformation.j;
} else if (_down) {
velocity.x += frameDistance * transformation.b;
velocity.y += frameDistance * transformation.f;
velocity.z += frameDistance * transformation.j;
}
}
else {
// Режим глобальных перемещений
var cosZ:Number = Math.cos(_camera.rotationZ);
var sinZ:Number = Math.sin(_camera.rotationZ);
// Движение вперед-назад
if (_forward) {
velocity.x -= frameDistance * sinZ;
velocity.y += frameDistance * cosZ;
} else if (_back) {
velocity.x += frameDistance * sinZ;
velocity.y -= frameDistance * cosZ;
}
// Движение влево-вправо
if (_left) {
velocity.x -= frameDistance * cosZ;
velocity.y -= frameDistance * sinZ;
} else if (_right) {
velocity.x += frameDistance * cosZ;
velocity.y += frameDistance * sinZ;
}
// Движение вверх-вниз
if (_up) {
velocity.z += frameDistance;
} else if (_down) {
velocity.z -= frameDistance;
}
}
// Коррекция модуля вектора скорости
if (velocity.x != 0 || velocity.y != 0 || velocity.z != 0) {
velocity.length = frameDistance;
}
// Проверка столкновений
if (_checkCollisions) {
_collider.calculateDestination(_camera.coords, velocity, destination);
_camera.x = destination.x;
_camera.y = destination.y;
_camera.z = destination.z;
} else {
_camera.x += velocity.x;
_camera.y += velocity.y;
_camera.z += velocity.z;
}
// Обработка начала/окончания движения
if (_camera.changeRotationOrScaleOperation.queued || _camera.changeCoordsOperation.queued) {
if (!_isMoving) {
_isMoving = true;
if (_onStartMoving != null) {
_onStartMoving.call(this);
}
}
} else {
if (_isMoving) {
_isMoving = false;
if (_onStopMoving != null) {
_onStopMoving.call(this);
}
}
}
}
}
}

View File

@@ -0,0 +1,450 @@
package alternativa.engine3d.controllers {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.types.Matrix3D;
import alternativa.types.Point3D;
import alternativa.utils.KeyboardUtils;
import alternativa.utils.MathUtils;
import flash.display.DisplayObject;
use namespace alternativa3d;
/**
* Контроллер, реализующий управление, подобное управлению летательным аппаратом для объекта, находящегося в системе
* координат корневого объекта сцены. Повороты выполняются вокруг локальных осей объекта, собственные ускорения
* действуют вдоль локальных осей.
*
* <p>Соответствия локальных осей для объектов, не являющихся камерой:
* <table border="1" style="border-collapse: collapse">
* <tr>
* <th>Ось</th><th>Направление</th><th>Поворот</th>
* </tr>
* <tr>
* <td>X</td><td>Вправо</td><td>Тангаж</td>
* </tr>
* <tr>
* <td>Y</td><td>Вперёд</td><td>Крен</td>
* </tr>
* <tr>
* <td>Z</td><td>Вверх</td><td>Рысканье</td>
* </tr>
* </table>
*
* <p>Соответствия локальных осей для объектов, являющихся камерой:
* <table border="1" style="border-collapse: collapse">
* <tr>
* <th>Ось</th><th>Направление</th><th>Поворот</th>
* </tr>
* <tr>
* <td>X</td><td>Вправо</td><td>Тангаж</td>
* </tr>
* <tr>
* <td>Y</td><td>Вниз</td><td>Рысканье</td>
* </tr>
* <tr>
* <td>Z</td><td>Вперёд</td><td>Крен</td>
* </tr>
* </table>
*
* Поворот мышью реализован следующим образом: в момент активации режима поворота (нажата левая кнопка мыши или
* соответствующая кнопка на клавиатуре) текущее положение курсора становится точкой, относительно которой определяются
* дальнейшие отклонения. Отклонение курсора по вертикали в пикселях, умноженное на коэффициент чувствительности мыши
* по вертикали даёт угловую скорость по тангажу. Отклонение курсора по горизонтали в пикселях, умноженное на коэффициент
* чувствительности мыши по горизонтали даёт угловую скорость по крену.
*/
public class FlyController extends ObjectController {
/**
* Имя действия для привязки клавиш поворота по крену влево.
*/
public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT";
/**
* Имя действия для привязки клавиш поворота по крену вправо.
*/
public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT";
private var _rollLeft:Boolean;
private var _rollRight:Boolean;
private var _rollSpeed:Number = 1;
private var rotations:Point3D;
private var rollMatrix:Matrix3D = new Matrix3D();
private var transformation:Matrix3D = new Matrix3D();
private var axis:Point3D = new Point3D();
private var velocity:Point3D = new Point3D();
private var displacement:Point3D = new Point3D();
private var destination:Point3D = new Point3D();
private var deltaVelocity:Point3D = new Point3D();
private var accelerationVector:Point3D = new Point3D();
private var currentTransform:Matrix3D = new Matrix3D();
private var _currentSpeed:Number = 1;
/**
* Текущие координаты мышиного курсора в режиме mouse look.
*/
private var currentMouseCoords:Point3D = new Point3D();
/**
* Модуль вектора ускорния, получаемого от команд движения.
*/
public var acceleration:Number = 1000;
/**
* Модуль вектора замедляющего ускорения.
*/
public var deceleration:Number = 50;
/**
* Погрешность определения скорости. Скорость приравнивается к нулю, если её модуль не превышает заданного значения.
*/
public var speedThreshold:Number = 1;
/**
* Переключение инерционного режима. В инерционном режиме отсутствует замедляющее ускорение, в результате чего вектор
* скорости объекта остаётся постоянным, если нет управляющих воздействий. При выключенном инерционном режиме к объекту
* прикладывается замедляющее ускорение.
*/
public var inertialMode:Boolean;
/**
* @inheritDoc
*/
public function FlyController(eventsSourceObject:DisplayObject) {
super(eventsSourceObject);
actionBindings[ACTION_ROLL_LEFT] = rollLeft;
actionBindings[ACTION_ROLL_RIGHT] = rollRight;
}
/**
* Текущая скорость движения.
*/
public function get currentSpeed():Number {
return _currentSpeed;
}
/**
* Активация вращения по крену влево.
*/
public function rollLeft(value:Boolean):void {
_rollLeft = value;
}
/**
* Активация вращения по крену вправо.
*/
public function rollRight(value:Boolean):void {
_rollRight = value;
}
/**
* Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
* <table border="1" style="border-collapse: collapse">
* <tr><th>Клавиша</th><th>Действие</th></tr>
* <tr><td>W</td><td>ACTION_FORWARD</td></tr>
* <tr><td>S</td><td>ACTION_BACK</td></tr>
* <tr><td>A</td><td>ACTION_LEFT</td></tr>
* <tr><td>D</td><td>ACTION_RIGHT</td></tr>
* <tr><td>SPACE</td><td>ACTION_UP</td></tr>
* <tr><td>CONTROL</td><td>ACTION_DOWN</td></tr>
* <tr><td>UP</td><td>ACTION_PITCH_UP</td></tr>
* <tr><td>DOWN</td><td>ACTION_PITCH_DOWN</td></tr>
* <tr><td>LEFT</td><td>ACTION_ROLL_LEFT</td></tr>
* <tr><td>RIGHT</td><td>ACTION_ROLL_RIGHT</td></tr>
* <tr><td>Q</td><td>ACTION_YAW_LEFT</td></tr>
* <tr><td>E</td><td>ACTION_YAW_RIGHT</td></tr>
* <tr><td>M</td><td>ACTION_MOUSE_LOOK</td></tr>
* </table>
*/
override public function setDefaultBindings():void {
unbindAll();
bindKey(KeyboardUtils.W, ACTION_FORWARD);
bindKey(KeyboardUtils.S, ACTION_BACK);
bindKey(KeyboardUtils.A, ACTION_LEFT);
bindKey(KeyboardUtils.D, ACTION_RIGHT);
bindKey(KeyboardUtils.SPACE, ACTION_UP);
bindKey(KeyboardUtils.CONTROL, ACTION_DOWN);
bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
bindKey(KeyboardUtils.LEFT, ACTION_ROLL_LEFT);
bindKey(KeyboardUtils.RIGHT, ACTION_ROLL_RIGHT);
bindKey(KeyboardUtils.Q, ACTION_YAW_LEFT);
bindKey(KeyboardUtils.E, ACTION_YAW_RIGHT);
bindKey(KeyboardUtils.M, ACTION_MOUSE_LOOK);
}
/**
* Метод выполняет поворот объекта относительно локальных осей в соответствии с имеющимися воздействиями.
*
* @param frameTime длительность текущего кадра в секундах
*/
override protected function rotateObject(frameTime:Number):void {
var transformation:Matrix3D = _object.transformation;
if (_mouseLookActive) {
currentMouseCoords.x = _eventsSource.stage.mouseX;
currentMouseCoords.y = _eventsSource.stage.mouseY;
if (!currentMouseCoords.equals(startMouseCoords)) {
var deltaYaw:Number = (currentMouseCoords.x - startMouseCoords.x) * _mouseCoefficientX;
if (_object is Camera3D) {
axis.x = transformation.c;
axis.y = transformation.g;
axis.z = transformation.k;
} else {
axis.x = transformation.b;
axis.y = transformation.f;
axis.z = transformation.j;
}
rotateObjectAroundAxis(axis, deltaYaw * frameTime);
currentTransform.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1);
var deltaPitch:Number = (startMouseCoords.y - currentMouseCoords.y) * _mouseCoefficientY;
axis.x = currentTransform.a;
axis.y = currentTransform.e;
axis.z = currentTransform.i;
rotateObjectAroundAxis(axis, deltaPitch * frameTime);
}
}
// Поворот относительно продольной оси (крен, roll)
if (_rollLeft) {
if (_object is Camera3D) {
axis.x = transformation.c;
axis.y = transformation.g;
axis.z = transformation.k;
} else {
axis.x = transformation.b;
axis.y = transformation.f;
axis.z = transformation.j;
}
rotateObjectAroundAxis(axis, -_rollSpeed * frameTime);
} else if (_rollRight) {
if (_object is Camera3D) {
axis.x = transformation.c;
axis.y = transformation.g;
axis.z = transformation.k;
} else {
axis.x = transformation.b;
axis.y = transformation.f;
axis.z = transformation.j;
}
rotateObjectAroundAxis(axis, _rollSpeed * frameTime);
}
// Поворот относительно поперечной оси (тангаж, pitch)
if (_pitchUp) {
axis.x = transformation.a;
axis.y = transformation.e;
axis.z = transformation.i;
rotateObjectAroundAxis(axis, _pitchSpeed * frameTime);
} else if (_pitchDown) {
axis.x = transformation.a;
axis.y = transformation.e;
axis.z = transformation.i;
rotateObjectAroundAxis(axis, -_pitchSpeed * frameTime);
}
// Поворот относительно вертикальной оси (рысканье, yaw)
if (_yawRight) {
if (_object is Camera3D) {
axis.x = transformation.b;
axis.y = transformation.f;
axis.z = transformation.j;
rotateObjectAroundAxis(axis, _yawSpeed * frameTime);
} else {
axis.x = transformation.c;
axis.y = transformation.g;
axis.z = transformation.k;
rotateObjectAroundAxis(axis, -_yawSpeed * frameTime);
}
} else if (_yawLeft) {
if (_object is Camera3D) {
axis.x = transformation.b;
axis.y = transformation.f;
axis.z = transformation.j;
rotateObjectAroundAxis(axis, -_yawSpeed * frameTime);
} else {
axis.x = transformation.c;
axis.y = transformation.g;
axis.z = transformation.k;
rotateObjectAroundAxis(axis, _yawSpeed * frameTime);
}
}
}
/**
* Метод вычисляет вектор потенциального смещения эллипсоида.
*
* @param frameTime длительность текущего кадра в секундах
* @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта
*/
override protected function getDisplacement(frameTime:Number, displacement:Point3D):void {
// Движение вперед-назад
accelerationVector.x = 0;
accelerationVector.y = 0;
accelerationVector.z = 0;
if (_forward) {
accelerationVector.y = 1;
} else if (_back) {
accelerationVector.y = -1;
}
// Движение влево-вправо
if (_right) {
accelerationVector.x = 1;
} else if (_left) {
accelerationVector.x = -1;
}
// Движение ввверх-вниз
if (_up) {
accelerationVector.z = 1;
} else if (_down) {
accelerationVector.z = -1;
}
var speedLoss:Number;
var len:Number;
if (accelerationVector.x != 0 || accelerationVector.y != 0 || accelerationVector.z != 0) {
// Управление активно
if (_object is Camera3D) {
var tmp:Number = accelerationVector.z;
accelerationVector.z = accelerationVector.y;
accelerationVector.y = -tmp;
}
accelerationVector.normalize();
accelerationVector.x *= acceleration;
accelerationVector.y *= acceleration;
accelerationVector.z *= acceleration;
currentTransform.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1);
accelerationVector.transform(currentTransform);
deltaVelocity.x = accelerationVector.x;
deltaVelocity.y = accelerationVector.y;
deltaVelocity.z = accelerationVector.z;
deltaVelocity.x *= frameTime;
deltaVelocity.y *= frameTime;
deltaVelocity.z *= frameTime;
if (!inertialMode) {
speedLoss = deceleration * frameTime;
var dot:Number = Point3D.dot(velocity, accelerationVector);
if (dot > 0) {
len = accelerationVector.length;
var x:Number = accelerationVector.x / len;
var y:Number = accelerationVector.y / len;
var z:Number = accelerationVector.z / len;
len = dot / len;
x = velocity.x - len * x;
y = velocity.y - len * y;
z = velocity.z - len * z;
len = Math.sqrt(x*x + y*y + z*z);
if (len > speedLoss) {
x *= speedLoss / len;
y *= speedLoss / len;
z *= speedLoss / len;
}
velocity.x -= x;
velocity.y -= y;
velocity.z -= z;
} else {
len = velocity.length;
velocity.length = (len > speedLoss) ? (len - speedLoss) : 0;
}
}
velocity.x += deltaVelocity.x;
velocity.y += deltaVelocity.y;
velocity.z += deltaVelocity.z;
if (velocity.length > _speed) {
velocity.length = _speed;
}
} else {
// Управление неактивно
if (!inertialMode) {
speedLoss = deceleration * frameTime;
len = velocity.length;
velocity.length = (len > speedLoss) ? (len - speedLoss) : 0;
}
}
displacement.x = velocity.x * frameTime;
displacement.y = velocity.y * frameTime;
displacement.z = velocity.z * frameTime;
}
/**
* Метод применяет потенциальный вектор смещения к эллипсоиду с учётом столкновений с геометрией сцены, если включён
* соотвествующий режим.
*
* @param frameTime время кадра в секундах
* @param displacement векотр потенциального смещения эллипсоида
*/
override protected function applyDisplacement(frameTime:Number, displacement:Point3D):void {
if (checkCollisions) {
_collider.calculateDestination(_coords, displacement, destination);
displacement.x = destination.x - _coords.x;
displacement.y = destination.y - _coords.y;
displacement.z = destination.z - _coords.z;
} else {
destination.x = _coords.x + displacement.x;
destination.y = _coords.y + displacement.y;
destination.z = _coords.z + displacement.z;
}
velocity.x = displacement.x / frameTime;
velocity.y = displacement.y / frameTime;
velocity.z = displacement.z / frameTime;
_coords.x = destination.x;
_coords.y = destination.y;
_coords.z = destination.z;
setObjectCoords();
var len:Number = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
if (len < speedThreshold) {
velocity.x = 0;
velocity.y = 0;
velocity.z = 0;
_currentSpeed = 0;
} else {
_currentSpeed = len;
}
}
/**
* Поворот объекта вокруг заданной оси.
*
* @param axis
* @param angle
*/
private function rotateObjectAroundAxis(axis:Point3D, angle:Number):void {
transformation.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1);
rollMatrix.fromAxisAngle(axis, angle);
rollMatrix.inverseCombine(transformation);
rotations = rollMatrix.getRotations(rotations);
_object.rotationX = rotations.x;
_object.rotationY = rotations.y;
_object.rotationZ = rotations.z;
}
/**
* Направление объекта на точку. В результате работы метода локальная ось объекта, соответствующая направлению "вперёд"
* будет направлена на указанную точку, а угол поворота вокруг этой оси будет равен нулю.
*
* @param point координаты точки, на которую должен быть направлен объект
*/
public function lookAt(point:Point3D):void {
if (_object == null) {
return;
}
var dx:Number = point.x - _object.x;
var dy:Number = point.y - _object.y;
var dz:Number = point.z - _object.z;
_object.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - (_object is Camera3D ? MathUtils.DEG90 : 0);
_object.rotationY = 0;
_object.rotationZ = -Math.atan2(dx, dy);
}
}
}

View File

@@ -0,0 +1,839 @@
package alternativa.engine3d.controllers {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.physics.EllipsoidCollider;
import alternativa.types.Map;
import alternativa.types.Point3D;
import alternativa.utils.MathUtils;
import alternativa.utils.ObjectUtils;
import flash.display.DisplayObject;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.utils.getTimer;
use namespace alternativa3d;
/**
* Базовый контроллер для изменения ориентации и положения объекта в сцене с помощью клавиатуры и мыши. В классе
* реализована поддержка назначения обработчиков клавиатурных команд, а также обработчики для основных команд
* перемещения.
*/
public class ObjectController {
/**
* Имя действия для привязки клавиш движения вперёд.
*/
public static const ACTION_FORWARD:String = "ACTION_FORWARD";
/**
* Имя действия для привязки клавиш движения назад.
*/
public static const ACTION_BACK:String = "ACTION_BACK";
/**
* Имя действия для привязки клавиш движения влево.
*/
public static const ACTION_LEFT:String = "ACTION_LEFT";
/**
* Имя действия для привязки клавиш движения вправо.
*/
public static const ACTION_RIGHT:String = "ACTION_RIGHT";
/**
* Имя действия для привязки клавиш движения вверх.
*/
public static const ACTION_UP:String = "ACTION_UP";
/**
* Имя действия для привязки клавиш движения вниз.
*/
public static const ACTION_DOWN:String = "ACTION_DOWN";
/**
* Имя действия для привязки клавиш поворота вверх.
*/
public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP";
/**
* Имя действия для привязки клавиш поворота вниз.
*/
public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN";
/**
* Имя действия для привязки клавиш поворота налево.
*/
public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT";
/**
* Имя действия для привязки клавиш поворота направо.
*/
public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT";
/**
* Имя действия для привязки клавиш увеличения скорости.
*/
public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE";
/**
* Имя действия для привязки клавиш активации обзора мышью.
*/
public static const ACTION_MOUSE_LOOK:String = "ACTION_MOUSE_LOOK";
/**
* Флаг движения вперёд.
*/
protected var _forward:Boolean;
/**
* Флаг движения назад.
*/
protected var _back:Boolean;
/**
* Флаг движения влево.
*/
protected var _left:Boolean;
/**
* Флаг движения вправо.
*/
protected var _right:Boolean;
/**
* Флаг движения вверх.
*/
protected var _up:Boolean;
/**
* Флаг движения вниз.
*/
protected var _down:Boolean;
/**
* Флаг поворота относительно оси X в положительном направлении (взгляд вверх).
*/
protected var _pitchUp:Boolean;
/**
* Флаг поворота относительно оси X в отрицательном направлении (взгляд вниз).
*/
protected var _pitchDown:Boolean;
/**
* Флаг поворота относительно оси Z в положительном направлении (взгляд налево).
*/
protected var _yawLeft:Boolean;
/**
* Флаг активности поворота относительно оси Z в отрицательном направлении (взгляд направо).
*/
protected var _yawRight:Boolean;
/**
* Флаг активности режима ускорения.
*/
protected var _accelerate:Boolean;
/**
* Флаг активности режима поворотов мышью.
*/
protected var _mouseLookActive:Boolean;
/**
* Начальные координаты мышиного курсора в режиме mouse look.
*/
protected var startMouseCoords:Point3D = new Point3D();
/**
* Флаг активности контроллера.
*/
protected var _enabled:Boolean = true;
/**
* Источник событий клавиатуры и мыши
*/
protected var _eventsSource:DisplayObject;
/**
* Ассоциативный массив, связывающий коды клавиатурных клавиш с именами команд.
*/
protected var keyBindings:Map = new Map();
/**
* Ассоциативный массив, связывающий имена команд с реализующими их функциями. Функции должны иметь вид
* function(value:Boolean):void. Значение параметра <code>value</code> указывает, нажата или отпущена соответсвующая команде
* клавиша.
*/
protected var actionBindings:Map = new Map();
/**
* Флаг активности клавиатуры.
*/
protected var _keyboardEnabled:Boolean;
/**
* Флаг активности мыши.
*/
protected var _mouseEnabled:Boolean;
/**
* Общая чувствительность мыши. Коэффициент умножения чувствительности по вертикали и горизонтали.
*/
protected var _mouseSensitivity:Number = 1;
/**
* Коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
*/
protected var _mouseSensitivityY:Number = Math.PI / 360;
/**
* Коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
*/
protected var _mouseSensitivityX:Number = Math.PI / 360;
/**
* Результирующий коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
*/
protected var _mouseCoefficientY:Number = _mouseSensitivity * _mouseSensitivityY;
/**
* Результирующий коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
*/
protected var _mouseCoefficientX:Number = _mouseSensitivity * _mouseSensitivityX;
/**
* Угловая скорость поворота вокруг поперечной оси в радианах за секунду.
*/
protected var _pitchSpeed:Number = 1;
/**
* Угловая скорость поворота вокруг вертикальной оси в радианах за секунду.
*/
protected var _yawSpeed:Number = 1;
/**
* Скорость поступательного движения в единицах за секунду.
*/
protected var _speed:Number = 100;
/**
* Коэффициент увеличения скорости при соответствующей активной команде.
*/
protected var _speedMultiplier:Number = 2;
/**
* Управляемый объект.
*/
protected var _object:Object3D;
/**
* Время в секундах, прошедшее с последнего вызова метода processInput (обычно с последнего кадра).
*/
protected var lastFrameTime:uint;
/**
* Текущие координаты контроллера.
*/
protected var _coords:Point3D = new Point3D();
/**
* Индикатор движения объекта (перемещения или поворота) в текущем кадре.
*/
protected var _isMoving:Boolean;
/**
* Объект для определения столкновений.
*/
protected var _collider:EllipsoidCollider = new EllipsoidCollider();
/**
* Включение и выключение режима проверки столкновений.
*/
public var checkCollisions:Boolean;
/**
* Функция вида <code>function():void</code>, вызываемая при начале движения объекта. Под движением
* понимается изменение координат или ориентации.
*/
public var onStartMoving:Function;
/**
* Функция вида <code>function():void</code>, вызываемая при прекращении движения объекта. Под движением
* понимается изменение координат или ориентации.
*/
public var onStopMoving:Function;
// Вектор смещения
private var _displacement:Point3D = new Point3D();
/**
* Создаёт новый экземпляр контролллера.
*
* @param eventsSourceObject источник событий клавиатуры и мыши
*/
public function ObjectController(eventsSourceObject:DisplayObject) {
if (eventsSourceObject == null) {
throw new ArgumentError(ObjectUtils.getClassName(this) + ": eventsSourceObject is null");
}
_eventsSource = eventsSourceObject;
actionBindings[ACTION_FORWARD] = moveForward;
actionBindings[ACTION_BACK] = moveBack;
actionBindings[ACTION_LEFT] = moveLeft;
actionBindings[ACTION_RIGHT] = moveRight;
actionBindings[ACTION_UP] = moveUp;
actionBindings[ACTION_DOWN] = moveDown;
actionBindings[ACTION_PITCH_UP] = pitchUp;
actionBindings[ACTION_PITCH_DOWN] = pitchDown;
actionBindings[ACTION_YAW_LEFT] = yawLeft;
actionBindings[ACTION_YAW_RIGHT] = yawRight;
actionBindings[ACTION_ACCELERATE] = accelerate;
actionBindings[ACTION_MOUSE_LOOK] = setMouseLook;
keyboardEnabled = true;
mouseEnabled = true;
}
/**
* Включение и выключение контроллера. Выключенный контроллер пропускает выполнение метода <code>processInput()</code>.
*
* @default true
*
* @see #processInput()
*/
public function get enabled():Boolean {
return _enabled;
}
/**
* @private
*/
public function set enabled(value:Boolean):void {
_enabled = value;
}
/**
* Координаты контроллера. Координаты совпадают с координатами центра эллипсоида, используемого для определения
* столкновений. Координаты управляемого объекта могут не совпадать с координатами контроллера.
*
* @see #setObjectCoords()
*/
public function get coords():Point3D {
return _coords.clone();
}
/**
* @private
*/
public function set coords(value:Point3D):void {
_coords.copy(value);
setObjectCoords();
}
/**
* Чтение координат контроллера в заданную переменную.
*
* @param point переменная, в которую записываются координаты контроллера
*/
public function readCoords(point:Point3D):void {
point.copy(_coords);
}
/**
* Управляемый объект.
*/
public function get object():Object3D {
return _object;
}
/**
* @private
* При установке объекта устанавливается сцена для коллайдера.
*/
public function set object(value:Object3D):void {
_object = value;
_collider.scene = _object == null ? null : _object.scene;
}
/**
* Объект, реализующий проверку столкновений для эллипсоида.
*/
public function get collider():EllipsoidCollider {
return _collider;
}
/**
* Активация движения вперёд.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function moveForward(value:Boolean):void {
_forward = value;
}
/**
* Активация движения назад.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function moveBack(value:Boolean):void {
_back = value;
}
/**
* Активация движения влево.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function moveLeft(value:Boolean):void {
_left = value;
}
/**
* Активация движения вправо.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function moveRight(value:Boolean):void {
_right = value;
}
/**
* Активация движения вверх.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function moveUp(value:Boolean):void {
_up = value;
}
/**
* Активация движения вниз.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function moveDown(value:Boolean):void {
_down = value;
}
/**
* Активация поворота вверх.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function pitchUp(value:Boolean):void {
_pitchUp = value;
}
/**
* Активация поворота вниз.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function pitchDown(value:Boolean):void {
_pitchDown = value;
}
/**
* Активация поворота влево.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function yawLeft(value:Boolean):void {
_yawLeft = value;
}
/**
* Активация поворота вправо.
*
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
*/
public function yawRight(value:Boolean):void {
_yawRight = value;
}
/**
* Активация режима увеличенной скорости.
*
* @param value <code>true</code> для включения ускорения, <code>false</code> для выключения
*/
public function accelerate(value:Boolean):void {
_accelerate = value;
}
/**
* Угловая скорость поворота вокруг поперечной оси (радианы в секунду).
*
* @default 1
*/
public function get pitchSpeed():Number {
return _pitchSpeed;
}
/**
* @private
*/
public function set pitchSpeed(spd:Number):void {
_pitchSpeed = spd;
}
/**
* Угловая скорость поворота вокруг вертикальной оси (радианы в секунду).
*
* @default 1
*/
public function get yawSpeed():Number {
return _yawSpeed;
}
/**
* @private
*/
public function set yawSpeed(spd:Number):void {
_yawSpeed = spd;
}
/**
* Скорость движения в единицах за секунду. При установке отрицательного значения берётся модуль.
*
* @default 100
*/
public function get speed():Number {
return _speed;
}
/**
* @private
*/
public function set speed(value:Number):void {
_speed = value < 0 ? -value : value;
}
/**
* Коэффициент увеличения скорости при активном действии <code>ACTION_ACCELERATE</code>.
*
* @default 2
*/
public function get speedMultiplier():Number {
return _speedMultiplier;
}
/**
* @private
*/
public function set speedMultiplier(value:Number):void {
_speedMultiplier = value;
}
/**
* Чувствительность мыши &mdash; коэффициент умножения <code>mouseSensitivityX</code> и <code>mouseSensitivityY</code>.
*
* @default 1
*
* @see #mouseSensitivityY()
* @see #mouseSensitivityX()
*/
public function get mouseSensitivity():Number {
return _mouseSensitivity;
}
/**
* @private
*/
public function set mouseSensitivity(sensitivity:Number):void {
_mouseSensitivity = sensitivity;
_mouseCoefficientY = _mouseSensitivity * _mouseSensitivityY;
_mouseCoefficientX = _mouseSensitivity * _mouseSensitivityX;
}
/**
* Чувтсвительность мыши по вертикали.
*
* @default Math.PI / 360
*
* @see #mouseSensitivity()
* @see #mouseSensitivityX()
*/
public function get mouseSensitivityY():Number {
return _mouseSensitivityY;
}
/**
* @private
*/
public function set mouseSensitivityY(value:Number):void {
_mouseSensitivityY = value;
_mouseCoefficientY = _mouseSensitivity * _mouseSensitivityY;
}
/**
* Чувтсвительность мыши по горизонтали.
*
* @default Math.PI / 360
*
* @see #mouseSensitivity()
* @see #mouseSensitivityY()
*/
public function get mouseSensitivityX():Number {
return _mouseSensitivityX;
}
/**
* @private
*/
public function set mouseSensitivityX(value:Number):void {
_mouseSensitivityX = value;
_mouseCoefficientX = _mouseSensitivity * _mouseSensitivityX;
}
/**
* Включение/выключение режима вращения объекта мышью. При включении режима вполняется метод <code>startMouseLook()</code>,
* при выключении &mdash; <code>stoptMouseLook()</code>.
*
* @see #startMouseLook()
* @see #stopMouseLook()
*/
public function setMouseLook(value:Boolean):void {
if (_mouseLookActive != value) {
_mouseLookActive = value;
if (_mouseLookActive) {
startMouseLook();
} else {
stopMouseLook();
}
}
}
/**
* Метод выполняет необходимые действия при включении режима вращения объекта мышью.
* Реализация по умолчанию записывает начальные глобальные координаты курсора мыши в переменную <code>startMouseCoords</code>.
*
* @see #startMouseCoords
* @see #setMouseLook()
* @see #stopMouseLook()
*/
protected function startMouseLook():void {
startMouseCoords.x = _eventsSource.stage.mouseX;
startMouseCoords.y = _eventsSource.stage.mouseY;
}
/**
* Метод выполняет необходимые действия при выключении вращения объекта мышью. Реализация по умолчанию не делает
* ничего.
*
* @see #setMouseLook()
* @see #startMouseLook()
*/
protected function stopMouseLook():void {
}
/**
* Метод выполняет обработку всех воздействий на объект. Если объект не установлен или свойство <code>enabled</code>
* равно <code>false</code>, метод не выполняется.
* <p>
* Алгоритм работы метода следующий:
* <ul>
* <li> Вычисляется время в секундах, прошедшее с последнего вызова метода (с последнего кадра). Это время считается
* длительностью текущего кадра;
* <li> Вызывается метод rotateObject(), который изменяет ориентацию объекта в соответствии с воздействиями;
* <li> Вызывается метод getDisplacement(), который вычисляет потенциальное перемещение объекта;
* <li> Вызывается метод applyDisplacement(), которому передаётся вектор перемещения, полученный на предыдущем шаге.
* Задачей метода является применение заданного вектора перемещения;
* <li> При необходимости вызываются обработчики начала и прекращения движения управляемого объекта;
* </ul>
*/
public function processInput():void {
if (!_enabled || _object == null) {
return;
}
var frameTime:Number = getTimer() - lastFrameTime;
lastFrameTime += frameTime;
if (frameTime > 100) {
frameTime = 100;
}
frameTime /= 1000;
rotateObject(frameTime);
getDisplacement(frameTime, _displacement);
applyDisplacement(frameTime, _displacement);
// Обработка начала/окончания движения
if (_object.changeRotationOrScaleOperation.queued || _object.changeCoordsOperation.queued) {
if (!_isMoving) {
_isMoving = true;
if (onStartMoving != null) {
onStartMoving.call(this);
}
}
} else {
if (_isMoving) {
_isMoving = false;
if (onStopMoving != null) {
onStopMoving.call(this);
}
}
}
}
/**
* Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Реализация по умолчанию не делает ничего.
*
* @param frameTime длительность текущего кадра в секундах
*/
protected function rotateObject(frameTime:Number):void {
}
/**
* Метод вычисляет потенциальное смещение объекта за кадр. Реализация по умолчанию не делает ничего.
*
* @param frameTime длительность текущего кадра в секундах
* @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта
*/
protected function getDisplacement(frameTime:Number, displacement:Point3D):void {
}
/**
* Метод применяет потенциальное смещение объекта. Реализация по умолчанию не делает ничего.
*
* @param frameTime длительность текущего кадра в секундах
* @param displacement смещение объекта, которое нужно обработать
*/
protected function applyDisplacement(frameTime:Number, displacement:Point3D):void {
}
/**
* Метод выполняет привязку клавиши к действию. Одной клавише может быть назначено только одно действие.
*
* @param keyCode код клавиши
* @param action наименование действия
*
* @see #unbindKey()
* @see #unbindAll()
*/
public function bindKey(keyCode:uint, action:String):void {
var method:Function = actionBindings[action];
if (method != null) {
keyBindings[keyCode] = method;
}
}
/**
* Очистка привязки клавиши.
*
* @param keyCode код клавиши
*
* @see #bindKey()
* @see #unbindAll()
*/
public function unbindKey(keyCode:uint):void {
keyBindings.remove(keyCode);
}
/**
* Очистка привязки всех клавиш.
*
* @see #bindKey()
* @see #unbindKey()
*/
public function unbindAll():void {
keyBindings.clear();
}
/**
* Метод устанавливает привязки клавиш по умолчанию. Реализация по умолчанию не делает ничего.
*
* @see #bindKey()
* @see #unbindKey()
* @see #unbindAll()
*/
public function setDefaultBindings():void {
}
/**
* Включение и выключение обработки клавиатурных событий. При включении выполняется метод <code>registerKeyboardListeners</code>,
* при выключении &mdash; <code>unregisterKeyboardListeners</code>.
*
* @see #registerKeyboardListeners()
* @see #unregisterKeyboardListeners()
*/
public function get keyboardEnabled():Boolean {
return _keyboardEnabled;
}
/**
* @private
*/
public function set keyboardEnabled(value:Boolean):void {
if (_keyboardEnabled != value) {
_keyboardEnabled = value;
if (_keyboardEnabled) {
registerKeyboardListeners();
} else {
unregisterKeyboardListeners();
}
}
}
/**
* @private
* Запуск обработчиков клавиатурных команд.
*/
private function onKeyboardEvent(e:KeyboardEvent):void {
var method:Function = keyBindings[e.keyCode];
if (method != null) {
method.call(this, e.type == KeyboardEvent.KEY_DOWN);
}
}
/**
* Регистрация необходимых обработчиков при включении обработки клавиатурных событий.
*
* @see #unregisterKeyboardListeners()
*/
protected function registerKeyboardListeners():void {
_eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent);
_eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent);
}
/**
* Удаление обработчиков при выключении обработки клавиатурных событий.
*
* @see #registerKeyboardListeners()
*/
protected function unregisterKeyboardListeners():void {
_eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent);
_eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent);
}
/**
* Включение и выключение обработки мышиных событий. При включении выполняется метод <code>registerMouseListeners</code>,
* при выключении &mdash; <code>unregisterMouseListeners</code>.
*
* @see #registerMouseListeners()
* @see #unregisterMouseListeners()
*/
public function get mouseEnabled():Boolean {
return _mouseEnabled;
}
/**
* @private
*/
public function set mouseEnabled(value:Boolean):void {
if (_mouseEnabled != value) {
_mouseEnabled = value;
if (_mouseEnabled) {
registerMouseListeners();
} else {
unregisterMouseListeners();
}
}
}
/**
* Регистрация необходимых обработчиков при включении обработки мышиных событий.
*
* @see #unregisterMouseListeners()
*/
protected function registerMouseListeners():void {
_eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
}
/**
* Удаление используемых обработчиков при выключении обработки мышиных событий.
*
* @see #registerMouseListeners()
*/
protected function unregisterMouseListeners():void {
_eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
_eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
/**
* Активация mouselook
*/
private function onMouseDown(e:MouseEvent):void {
setMouseLook(true);
_eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
/**
* Отключение mouselook
*/
private function onMouseUp(e:MouseEvent):void {
setMouseLook(false);
_eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
/**
* Установка координат управляемого объекта в зависимости от текущих координат контроллера.
*/
protected function setObjectCoords():void {
_object.coords = _coords;
}
/**
* Индикатор режима увеличенной скорости.
*/
public function get accelerated():Boolean {
return _accelerate;
}
}
}

View File

@@ -0,0 +1,508 @@
package alternativa.engine3d.controllers {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.physics.Collision;
import alternativa.types.Matrix3D;
import alternativa.types.Point3D;
import alternativa.utils.KeyboardUtils;
import alternativa.utils.MathUtils;
import flash.display.DisplayObject;
use namespace alternativa3d;
/**
* Контроллер, реализующий управление движением объекта, находящегося в системе координат корневого объекта сцены.
*
* <p>Контроллер предоставляет два режима движения: режим ходьбы с учётом силы тяжести и режим полёта, в котором сила
* тяжести не учитывается. В обоих режимах может быть включена проверка столкновений с объектами сцены. Если проверка
* столкновений отключена, то в режиме ходьбы сила тяжести также игнорируется и дополнительно появляется возможность
* движения по вертикали.
*
* <p>Для всех объектов, за исключением <code>Camera3D</code>, направлением "вперёд" считается направление его оси
* <code>Y</code>, направлением "вверх" &mdash; направление оси <code>Z</code>. Для объектов класса
* <code>Camera3D</code> направление "вперёд" совпадает с направлением локальной оси <code>Z</code>, а направление
* "вверх" противоположно направлению локальной оси <code>Y</code>.
*
* <p>Вне зависимости от того, включена проверка столкновений или нет, координаты при перемещении расчитываются для
* эллипсоида, параметры которого устанавливаются через свойство <code>collider</code>. Координаты управляемого
* объекта вычисляются исходя из положения центра эллипсоида и положения объекта на вертикальной оси эллипсоида,
* задаваемого параметром <code>objectZPosition</code>.
*
* <p>Команда <code>ACTION_UP</code> в режиме ходьбы при ненулевой гравитации вызывает прыжок, в остальных случаях
* происходит движение вверх.
*/
public class WalkController extends ObjectController {
/**
* Величина ускорения свободного падения. При положительном значении сила тяжести направлена против оси Z,
* при отрицательном &mdash; по оси Z.
*/
public var gravity:Number = 0;
/**
* Вертикальная скорость прыжка.
*/
public var jumpSpeed:Number = 0;
/**
* Объект, на котором стоит эллипсоид при ненулевой гравитации.
*/
private var _groundMesh:Mesh;
/**
* Погрешность определения скорости. В режиме полёта или в режиме ходьбы при нахождении на поверхности
* скорость приравнивается к нулю, если её модуль не превышает заданного значения.
*/
public var speedThreshold:Number = 1;
// Коэффициент эффективности управления перемещением при нахождении в воздухе в режиме ходьбы и нулевой гравитации.
private var _airControlCoefficient:Number = 1;
private var _currentSpeed:Number = 0;
private var minGroundCos:Number = Math.cos(MathUtils.toRadian(70));
private var destination:Point3D = new Point3D();
private var collision:Collision = new Collision();
private var _objectZPosition:Number = 0.5;
private var _flyMode:Boolean;
private var _onGround:Boolean;
private var velocity:Point3D = new Point3D();
private var tmpVelocity:Point3D = new Point3D();
private var controlsActive:Boolean;
private var inJump:Boolean;
private var startRotX:Number;
private var startRotZ:Number;
// Координаты мышиного курсора в режиме mouse look в предыдущем кадре.
private var prevMouseCoords:Point3D = new Point3D();
// Текущие координаты мышиного курсора в режиме mouse look.
private var currentMouseCoords:Point3D = new Point3D();
/**
* @inheritDoc
*/
public function WalkController(eventSourceObject:DisplayObject) {
super(eventSourceObject);
}
/**
* Объект, на котором стоит эллипсоид при ненулевой гравитации.
*/
public function get groundMesh():Mesh {
return _groundMesh;
}
/**
* Направление объекта на точку. В результате работы метода локальная ось объекта, соответствующая направлению "вперёд"
* будет направлена на указанную точку, а угол поворота вокруг этой оси будет равен нулю.
*
* @param point координаты точки, на которую должен быть направлен объект
*/
public function lookAt(point:Point3D):void {
if (_object == null) {
return;
}
var dx:Number = point.x - _object.x;
var dy:Number = point.y - _object.y;
var dz:Number = point.z - _object.z;
_object.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - (_object is Camera3D ? MathUtils.DEG90 : 0);
_object.rotationY = 0;
_object.rotationZ = -Math.atan2(dx, dy);
}
/**
* Коэффициент эффективности управления перемещением в режиме ходьбы при нахождении в воздухе и ненулевой гравитации.
* Значение 0 обозначает полное отсутствие контроля, значение 1 указывает, что управление так же эффективно, как при
* нахождении на поверхности.
*
* @default 1
*/
public function get airControlCoefficient():Number {
return _airControlCoefficient;
}
/**
* @private
*/
public function set airControlCoefficient(value:Number):void {
_airControlCoefficient = value > 0 ? value : -value;
}
/**
* Максимальный угол наклона поверхности в радианах, на которой возможен прыжок и на которой объект стоит на месте
* в отсутствие управляющих воздействий. Если угол наклона поверхности превышает заданное значение, свойство
* <code>onGround</code> будет иметь значение <code>false</code>.
*
* @see #onGround
*/
public function get maxGroundAngle():Number {
return Math.acos(minGroundCos);
}
/**
* @private
*/
public function set maxGroundAngle(value:Number):void {
minGroundCos = Math.cos(value);
}
/**
* Положение управляемого объекта на оси Z эллипсоида. Значение 0 указывает положение в нижней точке эллипсоида,
* значение 1 -- положение в верхней точке эллипсоида.
*/
public function get objectZPosition():Number {
return _objectZPosition;
}
/**
* @private
*/
public function set objectZPosition(value:Number):void {
_objectZPosition = value;
setObjectCoords();
}
/**
* Включене и выключение режима полёта.
*
* @default false
*/
public function get flyMode():Boolean {
return _flyMode;
}
/**
* @private
*/
public function set flyMode(value:Boolean):void {
_flyMode = value;
}
/**
* Индикатор положения эллипсоида на поверхности в режиме ходьбы. Считается, что эллипсоид находится на поверхности,
* если угол наклона поверхности под ним не превышает заданного свойством <code>maxGroundAngle</code> значения.
*
* @see #maxGroundAngle
*/
public function get onGround():Boolean {
return _onGround;
}
/**
* Модуль текущей скорости.
*/
public function get currentSpeed():Number {
return _currentSpeed;
}
/**
* Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
* <table border="1" style="border-collapse: collapse">
* <tr><th>Клавиша</th><th>Действие</th></tr>
* <tr><td>W</td><td>ACTION_FORWARD</td></tr>
* <tr><td>S</td><td>ACTION_BACK</td></tr>
* <tr><td>A</td><td>ACTION_LEFT</td></tr>
* <tr><td>D</td><td>ACTION_RIGHT</td></tr>
* <tr><td>SPACE</td><td>ACTION_UP</td></tr>
* <tr><td>CONTROL</td><td>ACTION_DOWN</td></tr>
* <tr><td>SHIFT</td><td>ACTION_ACCELERATE</td></tr>
* <tr><td>UP</td><td>ACTION_PITCH_UP</td></tr>
* <tr><td>DOWN</td><td>ACTION_PITCH_DOWN</td></tr>
* <tr><td>LEFT</td><td>ACTION_YAW_LEFT</td></tr>
* <tr><td>RIGHT</td><td>ACTION_YAW_RIGHT</td></tr>
* <tr><td>M</td><td>ACTION_MOUSE_LOOK</td></tr>
* </table>
*/
override public function setDefaultBindings():void {
unbindAll();
bindKey(KeyboardUtils.W, ACTION_FORWARD);
bindKey(KeyboardUtils.S, ACTION_BACK);
bindKey(KeyboardUtils.A, ACTION_LEFT);
bindKey(KeyboardUtils.D, ACTION_RIGHT);
bindKey(KeyboardUtils.SPACE, ACTION_UP);
bindKey(KeyboardUtils.CONTROL, ACTION_DOWN);
bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT);
bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT);
bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE);
bindKey(KeyboardUtils.M, ACTION_MOUSE_LOOK);
}
/**
* Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Взгляд вверх и вниз ограничен
* отклонением в 90 градусов от горизонтали.
*
* @param frameTime длительность текущего кадра в секундах
*/
override protected function rotateObject(frameTime:Number):void {
// Mouse look
var rotX:Number;
if (_mouseLookActive) {
prevMouseCoords.copy(currentMouseCoords);
currentMouseCoords.x = _eventsSource.stage.mouseX;
currentMouseCoords.y = _eventsSource.stage.mouseY;
if (!prevMouseCoords.equals(currentMouseCoords)) {
_object.rotationZ = startRotZ + (startMouseCoords.x - currentMouseCoords.x) * _mouseCoefficientX;
rotX = startRotX + (startMouseCoords.y - currentMouseCoords.y) * _mouseCoefficientY;
if (_object is Camera3D) {
// Коррекция поворота для камеры
_object.rotationX = (rotX > MathUtils.DEG90) ? 0 : (rotX < -MathUtils.DEG90) ? -Math.PI : rotX - MathUtils.DEG90;
} else {
_object.rotationX = (rotX > MathUtils.DEG90) ? MathUtils.DEG90 : (rotX < -MathUtils.DEG90) ? -MathUtils.DEG90 : rotX;
}
}
}
// Повороты влево-вправо
if (_yawLeft) {
_object.rotationZ += _yawSpeed * frameTime;
} else if (_yawRight) {
_object.rotationZ -= _yawSpeed * frameTime;
}
// Взгляд вверх-вниз
rotX = NaN;
if (_pitchUp) {
rotX = _object.rotationX + _pitchSpeed * frameTime;
} else if (_pitchDown) {
rotX = _object.rotationX - _pitchSpeed * frameTime;
}
if (!isNaN(rotX)) {
if (_object is Camera3D) {
// Коррекция поворота для камеры
_object.rotationX = (rotX > 0) ? 0 : (rotX < -Math.PI) ? -Math.PI : rotX;
} else {
_object.rotationX = (rotX > MathUtils.DEG90) ? MathUtils.DEG90 : (rotX < -MathUtils.DEG90) ? -MathUtils.DEG90 : rotX;
}
}
}
/**
* Метод вычисляет вектор потенциального смещения эллипсоида, учитывая режим перемещения, команды перемещения и силу тяжести.
*
* @param frameTime длительность текущего кадра в секундах
* @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта
*/
override protected function getDisplacement(frameTime:Number, displacement:Point3D):void {
var cos:Number = 0;
if (checkCollisions && !_flyMode) {
// Проверка наличия под ногами поверхности
displacement.x = 0;
displacement.y = 0;
displacement.z = - 0.5 * gravity * frameTime * frameTime;
if (_collider.getCollision(_coords, displacement, collision)) {
cos = collision.normal.z;
_groundMesh = collision.face._mesh;
} else {
_groundMesh = null;
}
}
_onGround = cos > minGroundCos;
if (_onGround && inJump) {
inJump = false;
}
var len:Number;
var x:Number;
var y:Number;
var z:Number;
// При наличии управляющих воздействий расчитывается приращение скорости
controlsActive = _forward || _back || _right || _left || _up || _down;
if (controlsActive) {
if (_flyMode) {
tmpVelocity.x = 0;
tmpVelocity.y = 0;
tmpVelocity.z = 0;
// Режим полёта, ускорения направлены вдоль локальных осей
// Ускорение вперёд-назад
if (_forward) {
tmpVelocity.y = 1;
} else if (_back) {
tmpVelocity.y = -1;
}
// Ускорение влево-вправо
if (_right) {
tmpVelocity.x = 1;
} else if (_left) {
tmpVelocity.x = -1;
}
// Ускорение вверх-вниз
if (_up) {
tmpVelocity.z = 1;
} else if (_down) {
tmpVelocity.z = -1;
}
var matrix:Matrix3D = _object.transformation;
x = tmpVelocity.x;
if (_object is Camera3D) {
y = -tmpVelocity.z;
z = tmpVelocity.y;
} else {
y = tmpVelocity.y;
z = tmpVelocity.z;
}
// Поворот вектора из локальной системы координат объекта в глобальную
velocity.x += (x * matrix.a + y * matrix.b + z * matrix.c) * _speed;
velocity.y += (x * matrix.e + y * matrix.f + z * matrix.g) * _speed;
velocity.z += (x * matrix.i + y * matrix.j + z * matrix.k) * _speed;
} else {
// Режим хождения, ускорения вперёд-назад-влево-вправо лежат в глобальной плоскости XY, вверх-вниз направлены вдоль глобальной оси Z
var heading:Number = _object.rotationZ;
var headingCos:Number = Math.cos(heading);
var headingSin:Number = Math.sin(heading);
var spd:Number = _speed;
if (gravity != 0 && !_onGround) {
spd *= _airControlCoefficient;
}
// Вперёд-назад
if (_forward) {
velocity.x -= spd * headingSin;
velocity.y += spd * headingCos;
} else if (_back) {
velocity.x += spd * headingSin;
velocity.y -= spd * headingCos;
}
// Влево-вправо
if (_right) {
velocity.x += spd * headingCos;
velocity.y += spd * headingSin;
} else if (_left) {
velocity.x -= spd * headingCos;
velocity.y -= spd * headingSin;
}
if (gravity == 0) {
// Ускорение вверх-вниз
if (_up) {
velocity.z += _speed;
} else if (_down) {
velocity.z -= _speed;
}
}
}
} else {
// Управление неактивно, замедление движения
len = 1 / Math.pow(3, frameTime * 10);
if (_flyMode || gravity == 0) {
velocity.x *= len;
velocity.y *= len;
velocity.z *= len;
} else {
if (_onGround) {
velocity.x *= len;
velocity.y *= len;
if (velocity.z < 0) {
velocity.z *= len;
}
} else {
if (cos > 0 && velocity.z > 0) {
velocity.z = 0;
}
}
}
}
// Прыжок
if (_onGround && _up && !inJump) {
velocity.z = jumpSpeed;
inJump = true;
_onGround = false;
cos = 0;
}
// В режиме ходьбы добавляется ускорение свободного падения, если находимся не на ровной поверхности
if (!(_flyMode || _onGround)) {
velocity.z -= gravity * frameTime;
}
// Ограничение скорости
var max:Number = _accelerate ? _speed * _speedMultiplier : _speed;
if (_flyMode || gravity == 0) {
len = velocity.length;
if (len > max) {
velocity.length = max;
}
} else {
len = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y);
if (len > max) {
velocity.x *= max / len;
velocity.y *= max / len;
}
if (cos > 0 && velocity.z > 0) {
velocity.z = 0;
}
}
// Cмещение за кадр
displacement.x = velocity.x * frameTime;
displacement.y = velocity.y * frameTime;
displacement.z = velocity.z * frameTime;
}
/**
* Метод применяет потенциальный вектор смещения к эллипсоиду с учётом столкновений с геометрией сцены, если включён
* соотвествующий режим.
*
* @param frameTime время кадра в секундах
* @param displacement векотр потенциального смещения эллипсоида
*/
override protected function applyDisplacement(frameTime:Number, displacement:Point3D):void {
if (checkCollisions) {
_collider.calculateDestination(_coords, displacement, destination);
displacement.x = destination.x - _coords.x;
displacement.y = destination.y - _coords.y;
displacement.z = destination.z - _coords.z;
} else {
destination.x = _coords.x + displacement.x;
destination.y = _coords.y + displacement.y;
destination.z = _coords.z + displacement.z;
}
velocity.x = displacement.x / frameTime;
velocity.y = displacement.y / frameTime;
velocity.z = displacement.z / frameTime;
_coords.x = destination.x;
_coords.y = destination.y;
_coords.z = destination.z;
setObjectCoords();
var len:Number = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
if (len < speedThreshold) {
velocity.x = 0;
velocity.y = 0;
velocity.z = 0;
_currentSpeed = 0;
} else {
_currentSpeed = len;
}
}
/**
* Метод устанавливает координаты управляемого объекта в зависимости от параметра <code>objectZPosition</code>.
*
* @see #objectZPosition
*/
override protected function setObjectCoords():void {
_object.x = _coords.x;
_object.y = _coords.y;
_object.z = _coords.z + (2 * _objectZPosition - 1) * _collider.radiusZ;
}
/**
* Метод выполняет необходимые действия при включении вращения объекта мышью.
*/
override protected function startMouseLook():void {
super.startMouseLook();
startRotX = _object is Camera3D ? _object.rotationX + MathUtils.DEG90 : _object.rotationX;
startRotZ = _object.rotationZ;
}
}
}

View File

@@ -0,0 +1,65 @@
K 25
svn:wc:ra_dav:version-url
V 96
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/core
END
Object3D.as
K 25
svn:wc:ra_dav:version-url
V 108
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/core/Object3D.as
END
Vertex.as
K 25
svn:wc:ra_dav:version-url
V 106
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/core/Vertex.as
END
Face.as
K 25
svn:wc:ra_dav:version-url
V 104
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/core/Face.as
END
Camera3D.as
K 25
svn:wc:ra_dav:version-url
V 108
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/core/Camera3D.as
END
Operation.as
K 25
svn:wc:ra_dav:version-url
V 109
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/core/Operation.as
END
Scene3D.as
K 25
svn:wc:ra_dav:version-url
V 107
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/core/Scene3D.as
END
Surface.as
K 25
svn:wc:ra_dav:version-url
V 107
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/core/Surface.as
END
BSPNode.as
K 25
svn:wc:ra_dav:version-url
V 107
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/core/BSPNode.as
END
Mesh.as
K 25
svn:wc:ra_dav:version-url
V 104
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/core/Mesh.as
END
PolyPrimitive.as
K 25
svn:wc:ra_dav:version-url
V 113
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/core/PolyPrimitive.as
END

View File

@@ -0,0 +1,148 @@
8
dir
46043
http://svndev.alternativaplatform.com/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/core
http://svndev.alternativaplatform.com
2008-09-08T10:18:35.971715Z
496
mike
svn:special svn:externals svn:needs-lock
d9e2387a-1f3e-40e2-b57f-9df5970a2fa5
Object3D.as
file
2010-10-28T04:31:16.000000Z
b073db9ca4e4c92a2dd85f27d9bc4984
2008-08-25T13:44:47.077292Z
176
int
Vertex.as
file
2010-10-28T04:31:16.000000Z
45061bec8e94d8b1a0e676cc97d1cd98
2008-08-25T13:44:47.077292Z
176
int
Face.as
file
2010-10-28T04:31:16.000000Z
69f10c80b9cfa8708935d58574478212
2008-08-25T13:44:47.077292Z
176
int
Camera3D.as
file
2010-10-28T04:31:16.000000Z
dd7767ec455535922dbb40119335493e
2008-08-25T13:44:47.077292Z
176
int
Operation.as
file
2010-10-28T04:31:16.000000Z
417bf52e75ce5e357a143f017420d7f8
2008-08-25T13:44:47.077292Z
176
int
Scene3D.as
file
2010-10-28T04:31:16.000000Z
c046f1821c29117d8a0512737aa59ba0
2008-09-08T10:18:35.971715Z
496
mike
Surface.as
file
2010-10-28T04:31:16.000000Z
32a190736e7772887ee56ff4fda61749
2008-08-25T13:44:47.077292Z
176
int
BSPNode.as
file
2010-10-28T04:31:16.000000Z
e252b1e950354fdb596a11c374363487
2008-08-25T13:44:47.077292Z
176
int
Mesh.as
file
2010-10-28T04:31:16.000000Z
7ca25e823b271a1d575f79379f2e42e8
2008-08-29T11:41:21.451513Z
304
wolf
PolyPrimitive.as
file
2010-10-28T04:31:16.000000Z
9ff789ed50e7857a8987830bfdaa1f9f
2008-09-08T06:50:26.103978Z
468
mike

View File

@@ -0,0 +1 @@
8

View File

@@ -0,0 +1,65 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
import alternativa.types.Point3D;
import alternativa.types.Set;
use namespace alternativa3d;
/**
* @private
*/
public final class BSPNode {
// Родительская нода
alternativa3d var parent:BSPNode;
// Дочерние ветки
alternativa3d var front:BSPNode;
alternativa3d var back:BSPNode;
// Нормаль плоскости ноды
alternativa3d var normal:Point3D = new Point3D();
// Смещение плоскости примитива
alternativa3d var offset:Number;
// Минимальная мобильность ноды
alternativa3d var mobility:int = int.MAX_VALUE;
// Набор примитивов в ноде
alternativa3d var primitive:PolyPrimitive;
alternativa3d var backPrimitives:Set;
alternativa3d var frontPrimitives:Set;
// Хранилище неиспользуемых нод
static private var collector:Array = new Array();
// Создать ноду на основе примитива
static alternativa3d function createBSPNode(primitive:PolyPrimitive):BSPNode {
// Достаём ноду из коллектора
var node:BSPNode = collector.pop();
// Если коллектор пуст, создаём новую ноду
if (node == null) {
node = new BSPNode();
}
// Добавляем примитив в ноду
node.primitive = primitive;
// Сохраняем ноду
primitive.node = node;
// Сохраняем плоскость
node.normal.copy(primitive.face.globalNormal);
node.offset = primitive.face.globalOffset;
// Сохраняем мобильность
node.mobility = primitive.mobility;
return node;
}
// Удалить ноду, все ссылки должны быть почищены
static alternativa3d function destroyBSPNode(node:BSPNode):void {
//trace(node.back, node.front, node.parent, node.primitive, node.backPrimitives, node.frontPrimitives);
collector.push(node);
}
}
}

View File

@@ -0,0 +1,902 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
import alternativa.engine3d.display.Skin;
import alternativa.engine3d.display.View;
import alternativa.engine3d.materials.DrawPoint;
import alternativa.engine3d.materials.SurfaceMaterial;
import alternativa.types.Matrix3D;
import alternativa.types.Point3D;
import alternativa.types.Set;
import flash.geom.Matrix;
import flash.geom.Point;
import alternativa.utils.MathUtils;
use namespace alternativa3d;
/**
* Камера для отображения 3D-сцены на экране.
*
* <p> Направление камеры совпадает с её локальной осью Z, поэтому только что созданная камера смотрит вверх в системе
* координат родителя.
*
* <p> Для отображения видимой через камеру части сцены на экран, к камере должна быть подключёна область вывода &mdash;
* экземпляр класса <code>alternativa.engine3d.display.View</code>.
*
* @see alternativa.engine3d.display.View
*/
public class Camera3D extends Object3D {
/**
* @private
* Расчёт матрицы пространства камеры
*/
alternativa3d var calculateMatrixOperation:Operation = new Operation("calculateMatrix", this, calculateMatrix, Operation.CAMERA_CALCULATE_MATRIX);
/**
* @private
* Расчёт плоскостей отсечения
*/
alternativa3d var calculatePlanesOperation:Operation = new Operation("calculatePlanes", this, calculatePlanes, Operation.CAMERA_CALCULATE_PLANES);
/**
* @private
* Отрисовка
*/
alternativa3d var renderOperation:Operation = new Operation("render", this, render, Operation.CAMERA_RENDER);
// Инкремент количества объектов
private static var counter:uint = 0;
/**
* @private
* Поле зрения
*/
alternativa3d var _fov:Number = Math.PI/2;
/**
* @private
* Фокусное расстояние
*/
alternativa3d var focalLength:Number;
/**
* @private
* Перспективное искажение
*/
alternativa3d var focalDistortion:Number;
/**
* @private
* Флаги рассчитанности UV-матриц
*/
alternativa3d var uvMatricesCalculated:Set = new Set(true);
// Всмомогательные точки для расчёта UV-матриц
private var textureA:Point3D = new Point3D();
private var textureB:Point3D = new Point3D();
private var textureC:Point3D = new Point3D();
/**
* @private
* Вид из камеры
*/
alternativa3d var _view:View;
/**
* @private
* Режим отрисовки
*/
alternativa3d var _orthographic:Boolean = false;
private var fullDraw:Boolean;
// Масштаб
private var _zoom:Number = 1;
// Синус половинчатого угла обзора камеры
private var viewAngle:Number;
// Направление камеры
private var direction:Point3D = new Point3D(0, 0, 1);
// Обратная трансформация камеры
private var cameraMatrix:Matrix3D = new Matrix3D();
// Скины
private var firstSkin:Skin;
private var prevSkin:Skin;
private var currentSkin:Skin;
// Плоскости отсечения
private var leftPlane:Point3D = new Point3D();
private var rightPlane:Point3D = new Point3D();
private var topPlane:Point3D = new Point3D();
private var bottomPlane:Point3D = new Point3D();
private var leftOffset:Number;
private var rightOffset:Number;
private var topOffset:Number;
private var bottomOffset:Number;
// Вспомогательные массивы точек для отрисовки
private var points1:Array = new Array();
private var points2:Array = new Array();
/**
* Создание нового экземпляра камеры.
*
* @param name имя экземпляра
*/
public function Camera3D(name:String = null) {
super(name);
}
/**
* @private
*/
private function calculateMatrix():void {
// Расчёт матрицы пространства камеры
cameraMatrix.copy(transformation);
cameraMatrix.invert();
if (_orthographic) {
cameraMatrix.scale(_zoom, _zoom, _zoom);
}
// Направление камеры
direction.x = transformation.c;
direction.y = transformation.g;
direction.z = transformation.k;
direction.normalize();
}
/**
* @private
* Расчёт плоскостей отсечения
*/
private function calculatePlanes():void {
var halfWidth:Number = _view._width*0.5;
var halfHeight:Number = _view._height*0.5;
var aw:Number = transformation.a*halfWidth;
var ew:Number = transformation.e*halfWidth;
var iw:Number = transformation.i*halfWidth;
var bh:Number = transformation.b*halfHeight;
var fh:Number = transformation.f*halfHeight;
var jh:Number = transformation.j*halfHeight;
if (_orthographic) {
// Расчёт плоскостей отсечения в изометрии
aw /= _zoom;
ew /= _zoom;
iw /= _zoom;
bh /= _zoom;
fh /= _zoom;
jh /= _zoom;
// Левая плоскость
leftPlane.x = transformation.f*transformation.k - transformation.j*transformation.g;
leftPlane.y = transformation.j*transformation.c - transformation.b*transformation.k;
leftPlane.z = transformation.b*transformation.g - transformation.f*transformation.c;
leftOffset = (transformation.d - aw)*leftPlane.x + (transformation.h - ew)*leftPlane.y + (transformation.l - iw)*leftPlane.z;
// Правая плоскость
rightPlane.x = -leftPlane.x;
rightPlane.y = -leftPlane.y;
rightPlane.z = -leftPlane.z;
rightOffset = (transformation.d + aw)*rightPlane.x + (transformation.h + ew)*rightPlane.y + (transformation.l + iw)*rightPlane.z;
// Верхняя плоскость
topPlane.x = transformation.g*transformation.i - transformation.k*transformation.e;
topPlane.y = transformation.k*transformation.a - transformation.c*transformation.i;
topPlane.z = transformation.c*transformation.e - transformation.g*transformation.a;
topOffset = (transformation.d - bh)*topPlane.x + (transformation.h - fh)*topPlane.y + (transformation.l - jh)*topPlane.z;
// Нижняя плоскость
bottomPlane.x = -topPlane.x;
bottomPlane.y = -topPlane.y;
bottomPlane.z = -topPlane.z;
bottomOffset = (transformation.d + bh)*bottomPlane.x + (transformation.h + fh)*bottomPlane.y + (transformation.l + jh)*bottomPlane.z;
} else {
// Вычисляем расстояние фокуса
focalLength = Math.sqrt(_view._width*_view._width + _view._height*_view._height)*0.5/Math.tan(0.5*_fov);
// Вычисляем минимальное (однопиксельное) искажение перспективной коррекции
focalDistortion = 1/(focalLength*focalLength);
// Расчёт плоскостей отсечения в перспективе
var cl:Number = transformation.c*focalLength;
var gl:Number = transformation.g*focalLength;
var kl:Number = transformation.k*focalLength;
// Угловые вектора пирамиды видимости
var leftTopX:Number = -aw - bh + cl;
var leftTopY:Number = -ew - fh + gl;
var leftTopZ:Number = -iw - jh + kl;
var rightTopX:Number = aw - bh + cl;
var rightTopY:Number = ew - fh + gl;
var rightTopZ:Number = iw - jh + kl;
var leftBottomX:Number = -aw + bh + cl;
var leftBottomY:Number = -ew + fh + gl;
var leftBottomZ:Number = -iw + jh + kl;
var rightBottomX:Number = aw + bh + cl;
var rightBottomY:Number = ew + fh + gl;
var rightBottomZ:Number = iw + jh + kl;
// Левая плоскость
leftPlane.x = leftBottomY*leftTopZ - leftBottomZ*leftTopY;
leftPlane.y = leftBottomZ*leftTopX - leftBottomX*leftTopZ;
leftPlane.z = leftBottomX*leftTopY - leftBottomY*leftTopX;
leftOffset = transformation.d*leftPlane.x + transformation.h*leftPlane.y + transformation.l*leftPlane.z;
// Правая плоскость
rightPlane.x = rightTopY*rightBottomZ - rightTopZ*rightBottomY;
rightPlane.y = rightTopZ*rightBottomX - rightTopX*rightBottomZ;
rightPlane.z = rightTopX*rightBottomY - rightTopY*rightBottomX;
rightOffset = transformation.d*rightPlane.x + transformation.h*rightPlane.y + transformation.l*rightPlane.z;
// Верхняя плоскость
topPlane.x = leftTopY*rightTopZ - leftTopZ*rightTopY;
topPlane.y = leftTopZ*rightTopX - leftTopX*rightTopZ;
topPlane.z = leftTopX*rightTopY - leftTopY*rightTopX;
topOffset = transformation.d*topPlane.x + transformation.h*topPlane.y + transformation.l*topPlane.z;
// Нижняя плоскость
bottomPlane.x = rightBottomY*leftBottomZ - rightBottomZ*leftBottomY;
bottomPlane.y = rightBottomZ*leftBottomX - rightBottomX*leftBottomZ;
bottomPlane.z = rightBottomX*leftBottomY - rightBottomY*leftBottomX;
bottomOffset = transformation.d*bottomPlane.x + transformation.h*bottomPlane.y + transformation.l*bottomPlane.z;
// Расчёт угла конуса
var length:Number = Math.sqrt(leftTopX*leftTopX + leftTopY*leftTopY + leftTopZ*leftTopZ);
leftTopX /= length;
leftTopY /= length;
leftTopZ /= length;
length = Math.sqrt(rightTopX*rightTopX + rightTopY*rightTopY + rightTopZ*rightTopZ);
rightTopX /= length;
rightTopY /= length;
rightTopZ /= length;
length = Math.sqrt(leftBottomX*leftBottomX + leftBottomY*leftBottomY + leftBottomZ*leftBottomZ);
leftBottomX /= length;
leftBottomY /= length;
leftBottomZ /= length;
length = Math.sqrt(rightBottomX*rightBottomX + rightBottomY*rightBottomY + rightBottomZ*rightBottomZ);
rightBottomX /= length;
rightBottomY /= length;
rightBottomZ /= length;
viewAngle = leftTopX*direction.x + leftTopY*direction.y + leftTopZ*direction.z;
var dot:Number = rightTopX*direction.x + rightTopY*direction.y + rightTopZ*direction.z;
viewAngle = (dot < viewAngle) ? dot : viewAngle;
dot = leftBottomX*direction.x + leftBottomY*direction.y + leftBottomZ*direction.z;
viewAngle = (dot < viewAngle) ? dot : viewAngle;
dot = rightBottomX*direction.x + rightBottomY*direction.y + rightBottomZ*direction.z;
viewAngle = (dot < viewAngle) ? dot : viewAngle;
viewAngle = Math.sin(Math.acos(viewAngle));
}
}
/**
* @private
*/
private function render():void {
// Режим отрисовки
fullDraw = (calculateMatrixOperation.queued || calculatePlanesOperation.queued);
// Очистка рассчитанных текстурных матриц
uvMatricesCalculated.clear();
// Отрисовка
prevSkin = null;
currentSkin = firstSkin;
renderBSPNode(_scene.bsp);
// Удаление ненужных скинов
while (currentSkin != null) {
removeCurrentSkin();
}
}
/**
* @private
*/
private function renderBSPNode(node:BSPNode):void {
if (node != null) {
var primitive:*;
var normal:Point3D = node.normal;
var cameraAngle:Number = direction.x*normal.x + direction.y*normal.y + direction.z*normal.z;
var cameraOffset:Number;
if (!_orthographic) {
cameraOffset = globalCoords.x*normal.x + globalCoords.y*normal.y + globalCoords.z*normal.z - node.offset;
}
if (node.primitive != null) {
// В ноде только базовый примитив
if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) {
// Камера спереди ноды
if (_orthographic || cameraAngle < viewAngle) {
renderBSPNode(node.back);
drawSkin(node.primitive);
}
renderBSPNode(node.front);
} else {
// Камера сзади ноды
if (_orthographic || cameraAngle > -viewAngle) {
renderBSPNode(node.front);
}
renderBSPNode(node.back);
}
} else {
// В ноде несколько примитивов
if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) {
// Камера спереди ноды
if (_orthographic || cameraAngle < viewAngle) {
renderBSPNode(node.back);
for (primitive in node.frontPrimitives) {
drawSkin(primitive);
}
}
renderBSPNode(node.front);
} else {
// Камера сзади ноды
if (_orthographic || cameraAngle > -viewAngle) {
renderBSPNode(node.front);
for (primitive in node.backPrimitives) {
drawSkin(primitive);
}
}
renderBSPNode(node.back);
}
}
}
}
/**
* @private
* Отрисовка скина примитива
*/
private function drawSkin(primitive:PolyPrimitive):void {
if (!fullDraw && currentSkin != null && currentSkin.primitive == primitive && !_scene.changedPrimitives[primitive]) {
// Пропуск скина
prevSkin = currentSkin;
currentSkin = currentSkin.nextSkin;
} else {
// Проверка поверхности
var surface:Surface = primitive.face._surface;
if (surface == null) {
return;
}
// Проверка материала
var material:SurfaceMaterial = surface._material;
if (material == null || !material.canDraw(primitive)) {
return;
}
// Отсечение выходящих за окно просмотра частей
var i:uint;
var length:uint = primitive.num;
var primitivePoint:Point3D;
var primitiveUV:Point;
var point:DrawPoint;
var useUV:Boolean = !_orthographic && material.useUV && primitive.face.uvMatrixBase;
if (useUV) {
// Формируем список точек и UV-координат полигона
for (i = 0; i < length; i++) {
primitivePoint = primitive.points[i];
primitiveUV = primitive.uvs[i];
point = points1[i];
if (point == null) {
points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z, primitiveUV.x, primitiveUV.y);
} else {
point.x = primitivePoint.x;
point.y = primitivePoint.y;
point.z = primitivePoint.z;
point.u = primitiveUV.x;
point.v = primitiveUV.y;
}
}
} else {
// Формируем список точек полигона
for (i = 0; i < length; i++) {
primitivePoint = primitive.points[i];
point = points1[i];
if (point == null) {
points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z);
} else {
point.x = primitivePoint.x;
point.y = primitivePoint.y;
point.z = primitivePoint.z;
}
}
}
// Отсечение по левой стороне
length = clip(length, points1, points2, leftPlane, leftOffset, useUV);
if (length < 3) {
return;
}
// Отсечение по правой стороне
length = clip(length, points2, points1, rightPlane, rightOffset, useUV);
if (length < 3) {
return;
}
// Отсечение по верхней стороне
length = clip(length, points1, points2, topPlane, topOffset, useUV);
if (length < 3) {
return;
}
// Отсечение по нижней стороне
length = clip(length, points2, points1, bottomPlane, bottomOffset, useUV);
if (length < 3) {
return;
}
if (fullDraw || _scene.changedPrimitives[primitive]) {
// Если конец списка скинов
if (currentSkin == null) {
// Добавляем скин в конец
addCurrentSkin();
} else {
if (fullDraw || _scene.changedPrimitives[currentSkin.primitive]) {
// Очистка скина
currentSkin.material.clear(currentSkin);
} else {
// Вставка скина перед текущим
insertCurrentSkin();
}
}
// Переводим координаты в систему камеры
var x:Number;
var y:Number;
var z:Number;
for (i = 0; i < length; i++) {
point = points1[i];
x = point.x;
y = point.y;
z = point.z;
point.x = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d;
point.y = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h;
point.z = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l;
}
// Назначаем скину примитив и материал
currentSkin.primitive = primitive;
currentSkin.material = material;
material.draw(this, currentSkin, length, points1);
// Переключаемся на следующий скин
prevSkin = currentSkin;
currentSkin = currentSkin.nextSkin;
} else {
// Удаление ненужных скинов
while (currentSkin != null && _scene.changedPrimitives[currentSkin.primitive]) {
removeCurrentSkin();
}
// Переключение на следующий скин
if (currentSkin != null) {
prevSkin = currentSkin;
currentSkin = currentSkin.nextSkin;
}
}
}
}
/**
* @private
* Отсечение полигона плоскостью.
*/
private function clip(length:uint, points1:Array, points2:Array, plane:Point3D, offset:Number, calculateUV:Boolean):uint {
var i:uint;
var k:Number;
var index:uint = 0;
var point:DrawPoint;
var point1:DrawPoint;
var point2:DrawPoint;
var offset1:Number;
var offset2:Number;
point1 = points1[length - 1];
offset1 = plane.x*point1.x + plane.y*point1.y + plane.z*point1.z - offset;
if (calculateUV) {
for (i = 0; i < length; i++) {
point2 = points1[i];
offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset;
if (offset2 > 0) {
if (offset1 <= 0) {
k = offset2/(offset2 - offset1);
point = points2[index];
if (point == null) {
point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k);
points2[index] = point;
} else {
point.x = point2.x - (point2.x - point1.x)*k;
point.y = point2.y - (point2.y - point1.y)*k;
point.z = point2.z - (point2.z - point1.z)*k;
point.u = point2.u - (point2.u - point1.u)*k;
point.v = point2.v - (point2.v - point1.v)*k;
}
index++;
}
point = points2[index];
if (point == null) {
point = new DrawPoint(point2.x, point2.y, point2.z, point2.u, point2.v);
points2[index] = point;
} else {
point.x = point2.x;
point.y = point2.y;
point.z = point2.z;
point.u = point2.u;
point.v = point2.v;
}
index++;
} else {
if (offset1 > 0) {
k = offset2/(offset2 - offset1);
point = points2[index];
if (point == null) {
point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k);
points2[index] = point;
} else {
point.x = point2.x - (point2.x - point1.x)*k;
point.y = point2.y - (point2.y - point1.y)*k;
point.z = point2.z - (point2.z - point1.z)*k;
point.u = point2.u - (point2.u - point1.u)*k;
point.v = point2.v - (point2.v - point1.v)*k;
}
index++;
}
}
offset1 = offset2;
point1 = point2;
}
} else {
for (i = 0; i < length; i++) {
point2 = points1[i];
offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset;
if (offset2 > 0) {
if (offset1 <= 0) {
k = offset2/(offset2 - offset1);
point = points2[index];
if (point == null) {
point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k);
points2[index] = point;
} else {
point.x = point2.x - (point2.x - point1.x)*k;
point.y = point2.y - (point2.y - point1.y)*k;
point.z = point2.z - (point2.z - point1.z)*k;
}
index++;
}
point = points2[index];
if (point == null) {
point = new DrawPoint(point2.x, point2.y, point2.z);
points2[index] = point;
} else {
point.x = point2.x;
point.y = point2.y;
point.z = point2.z;
}
index++;
} else {
if (offset1 > 0) {
k = offset2/(offset2 - offset1);
point = points2[index];
if (point == null) {
point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k);
points2[index] = point;
} else {
point.x = point2.x - (point2.x - point1.x)*k;
point.y = point2.y - (point2.y - point1.y)*k;
point.z = point2.z - (point2.z - point1.z)*k;
}
index++;
}
}
offset1 = offset2;
point1 = point2;
}
}
return index;
}
/**
* @private
* Добавление текущего скина.
*/
private function addCurrentSkin():void {
currentSkin = Skin.createSkin();
_view.canvas.addChild(currentSkin);
if (prevSkin == null) {
firstSkin = currentSkin;
} else {
prevSkin.nextSkin = currentSkin;
}
}
/**
* @private
* Вставляем под текущий скин.
*/
private function insertCurrentSkin():void {
var skin:Skin = Skin.createSkin();
_view.canvas.addChildAt(skin, _view.canvas.getChildIndex(currentSkin));
skin.nextSkin = currentSkin;
if (prevSkin == null) {
firstSkin = skin;
} else {
prevSkin.nextSkin = skin;
}
currentSkin = skin;
}
/**
* @private
* Удаляет текущий скин.
*/
private function removeCurrentSkin():void {
// Сохраняем следующий
var next:Skin = currentSkin.nextSkin;
// Удаляем из канваса
_view.canvas.removeChild(currentSkin);
// Очистка скина
if (currentSkin.material != null) {
currentSkin.material.clear(currentSkin);
}
// Зачищаем ссылки
currentSkin.nextSkin = null;
currentSkin.primitive = null;
currentSkin.material = null;
// Удаляем
Skin.destroySkin(currentSkin);
// Следующий устанавливаем текущим
currentSkin = next;
// Устанавливаем связь от предыдущего скина
if (prevSkin == null) {
firstSkin = currentSkin;
} else {
prevSkin.nextSkin = currentSkin;
}
}
/**
* @private
*/
alternativa3d function calculateUVMatrix(face:Face, width:uint, height:uint):void {
// Расчёт точек базового примитива в координатах камеры
var point:Point3D = face.primitive.points[0];
textureA.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
textureA.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
point = face.primitive.points[1];
textureB.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
textureB.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
point = face.primitive.points[2];
textureC.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
textureC.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
// Находим AB и AC
var abx:Number = textureB.x - textureA.x;
var aby:Number = textureB.y - textureA.y;
var acx:Number = textureC.x - textureA.x;
var acy:Number = textureC.y - textureA.y;
// Расчёт текстурной матрицы
var uvMatrixBase:Matrix = face.uvMatrixBase;
var uvMatrix:Matrix = face.uvMatrix;
uvMatrix.a = (uvMatrixBase.a*abx + uvMatrixBase.b*acx)/width;
uvMatrix.b = (uvMatrixBase.a*aby + uvMatrixBase.b*acy)/width;
uvMatrix.c = -(uvMatrixBase.c*abx + uvMatrixBase.d*acx)/height;
uvMatrix.d = -(uvMatrixBase.c*aby + uvMatrixBase.d*acy)/height;
uvMatrix.tx = (uvMatrixBase.tx + uvMatrixBase.c)*abx + (uvMatrixBase.ty + uvMatrixBase.d)*acx + textureA.x + cameraMatrix.d;
uvMatrix.ty = (uvMatrixBase.tx + uvMatrixBase.c)*aby + (uvMatrixBase.ty + uvMatrixBase.d)*acy + textureA.y + cameraMatrix.h;
// Помечаем, как рассчитанную
uvMatricesCalculated[face] = true;
}
/**
* Поле вывода, в котором происходит отрисовка камеры.
*/
public function get view():View {
return _view;
}
/**
* @private
*/
public function set view(value:View):void {
if (value != _view) {
if (_view != null) {
_view.camera = null;
}
if (value != null) {
value.camera = this;
}
}
}
/**
* Включение режима аксонометрической проекции.
*
* @default false
*/
public function get orthographic():Boolean {
return _orthographic;
}
/**
* @private
*/
public function set orthographic(value:Boolean):void {
if (_orthographic != value) {
// Отправляем сигнал об изменении типа камеры
addOperationToScene(calculateMatrixOperation);
// Сохраняем новое значение
_orthographic = value;
}
}
/**
* Угол поля зрения в радианах в режиме перспективной проекции. При изменении FOV изменяется фокусное расстояние
* камеры по формуле <code>f = d/tan(fov/2)</code>, где <code>d</code> является половиной диагонали поля вывода.
* Угол зрения ограничен диапазоном 0-180 градусов.
*/
public function get fov():Number {
return _fov;
}
/**
* @private
*/
public function set fov(value:Number):void {
value = (value < 0) ? 0 : ((value > (Math.PI - 0.0001)) ? (Math.PI - 0.0001) : value);
if (_fov != value) {
// Если перспектива
if (!_orthographic) {
// Отправляем сигнал об изменении плоскостей отсечения
addOperationToScene(calculatePlanesOperation);
}
// Сохраняем новое значение
_fov = value;
}
}
/**
* Коэффициент увеличения изображения в режиме аксонометрической проекции.
*/
public function get zoom():Number {
return _zoom;
}
/**
* @private
*/
public function set zoom(value:Number):void {
value = (value < 0) ? 0 : value;
if (_zoom != value) {
// Если изометрия
if (_orthographic) {
// Отправляем сигнал об изменении zoom
addOperationToScene(calculateMatrixOperation);
}
// Сохраняем новое значение
_zoom = value;
}
}
/**
* @inheritDoc
*/
override protected function addToScene(scene:Scene3D):void {
super.addToScene(scene);
if (_view != null) {
// Отправляем операцию расчёта плоскостей отсечения
scene.addOperation(calculatePlanesOperation);
// Подписываемся на сигналы сцены
scene.changePrimitivesOperation.addSequel(renderOperation);
}
}
/**
* @inheritDoc
*/
override protected function removeFromScene(scene:Scene3D):void {
super.removeFromScene(scene);
// Удаляем все операции из очереди
scene.removeOperation(calculateMatrixOperation);
scene.removeOperation(calculatePlanesOperation);
scene.removeOperation(renderOperation);
if (_view != null) {
// Отписываемся от сигналов сцены
scene.changePrimitivesOperation.removeSequel(renderOperation);
}
}
/**
* @private
*/
alternativa3d function addToView(view:View):void {
// Сохраняем первый скин
firstSkin = (view.canvas.numChildren > 0) ? Skin(view.canvas.getChildAt(0)) : null;
// Подписка на свои операции
// При изменении камеры пересчёт матрицы
calculateTransformationOperation.addSequel(calculateMatrixOperation);
// При изменении матрицы или FOV пересчёт плоскостей отсечения
calculateMatrixOperation.addSequel(calculatePlanesOperation);
// При изменении плоскостей перерисовка
calculatePlanesOperation.addSequel(renderOperation);
if (_scene != null) {
// Отправляем сигнал перерисовки
_scene.addOperation(calculateMatrixOperation);
// Подписываемся на сигналы сцены
_scene.changePrimitivesOperation.addSequel(renderOperation);
}
// Сохраняем вид
_view = view;
}
/**
* @private
*/
alternativa3d function removeFromView(view:View):void {
// Сброс ссылки на первый скин
firstSkin = null;
// Отписка от своих операций
// При изменении камеры пересчёт матрицы
calculateTransformationOperation.removeSequel(calculateMatrixOperation);
// При изменении матрицы или FOV пересчёт плоскостей отсечения
calculateMatrixOperation.removeSequel(calculatePlanesOperation);
// При изменении плоскостей перерисовка
calculatePlanesOperation.removeSequel(renderOperation);
if (_scene != null) {
// Удаляем все операции из очереди
_scene.removeOperation(calculateMatrixOperation);
_scene.removeOperation(calculatePlanesOperation);
_scene.removeOperation(renderOperation);
// Отписываемся от сигналов сцены
_scene.changePrimitivesOperation.removeSequel(renderOperation);
}
// Удаляем ссылку на вид
_view = null;
}
/**
* @inheritDoc
*/
override protected function defaultName():String {
return "camera" + ++counter;
}
/**
* @inheritDoc
*/
protected override function createEmptyObject():Object3D {
return new Camera3D();
}
/**
* @inheritDoc
*/
protected override function clonePropertiesFrom(source:Object3D):void {
super.clonePropertiesFrom(source);
var src:Camera3D = Camera3D(source);
orthographic = src._orthographic;
zoom = src._zoom;
fov = src._fov;
}
}
}

View File

@@ -0,0 +1,921 @@
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 массив объектов типа <code>alternativa.engine3d.core.Vertex</code>, задающий вершины грани в
* порядке обхода лицевой стороны грани против часовой стрелки.
*
* @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);
}
}
/**
* Массив вершин грани, представленных объектами класса <code>alternativa.engine3d.core.Vertex</code>.
*
* @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;
}
/**
* Идентификатор грани в полигональном объекте. В случае, если грань не принадлежит ни одному объекту, идентификатор
* имеет значение <code>null</code>.
*/
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;
}
}
}

View File

@@ -0,0 +1,985 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
import alternativa.engine3d.errors.FaceExistsError;
import alternativa.engine3d.errors.FaceNeedMoreVerticesError;
import alternativa.engine3d.errors.FaceNotFoundError;
import alternativa.engine3d.errors.InvalidIDError;
import alternativa.engine3d.errors.SurfaceExistsError;
import alternativa.engine3d.errors.SurfaceNotFoundError;
import alternativa.engine3d.errors.VertexExistsError;
import alternativa.engine3d.errors.VertexNotFoundError;
import alternativa.engine3d.materials.SurfaceMaterial;
import alternativa.types.Map;
import alternativa.utils.ObjectUtils;
import flash.geom.Point;
use namespace alternativa3d;
/**
* Полигональный объект &mdash; базовый класс для трёхмерных объектов, состоящих из граней-полигонов. Объект
* содержит в себе наборы вершин, граней и поверхностей.
*/
public class Mesh extends Object3D {
// Инкремент количества объектов
private static var counter:uint = 0;
// Инкременты для идентификаторов вершин, граней и поверхностей
private var vertexIDCounter:uint = 0;
private var faceIDCounter:uint = 0;
private var surfaceIDCounter:uint = 0;
/**
* @private
* Список вершин
*/
alternativa3d var _vertices:Map = new Map();
/**
* @private
* Список граней
*/
alternativa3d var _faces:Map = new Map();
/**
* @private
* Список поверхностей
*/
alternativa3d var _surfaces:Map = new Map();
/**
* Создание экземпляра полигонального объекта.
*
* @param name имя экземпляра
*/
public function Mesh(name:String = null) {
super(name);
}
/**
* Добавление новой вершины к объекту.
*
* @param x координата X в локальной системе координат объекта
* @param y координата Y в локальной системе координат объекта
* @param z координата Z в локальной системе координат объекта
* @param id идентификатор вершины. Если указано значение <code>null</code>, идентификатор будет
* сформирован автоматически.
*
* @return экземпляр добавленной вершины
*
* @throws alternativa.engine3d.errors.VertexExistsError объект уже содержит вершину с указанным идентификатором
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function createVertex(x:Number = 0, y:Number = 0, z:Number = 0, id:Object = null):Vertex {
// Проверяем ID
if (id != null) {
// Если уже есть вершина с таким ID
if (_vertices[id] != undefined) {
if (_vertices[id] is Vertex) {
throw new VertexExistsError(id, this);
} else {
throw new InvalidIDError(id, this);
}
}
} else {
// Ищем первый свободный
while (_vertices[vertexIDCounter] != undefined) {
vertexIDCounter++;
}
id = vertexIDCounter;
}
// Создаём вершину
var v:Vertex = new Vertex(x, y, z);
// Добавляем вершину на сцену
if (_scene != null) {
v.addToScene(_scene);
}
// Добавляем вершину в меш
v.addToMesh(this);
_vertices[id] = v;
return v;
}
/**
* Удаление вершины из объекта. При удалении вершины из объекта также удаляются все грани, которым принадлежит данная вершина.
*
* @param vertex экземпляр класса <code>alternativa.engine3d.core.Vertex</code> или идентификатор удаляемой вершины
*
* @return экземпляр удалённой вершины
*
* @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function removeVertex(vertex:Object):Vertex {
var byLink:Boolean = vertex is Vertex;
// Проверяем на null
if (vertex == null) {
throw new VertexNotFoundError(null, this);
}
// Проверяем наличие вершины в меше
if (byLink) {
// Если удаляем по ссылке
if (Vertex(vertex)._mesh != this) {
// Если вершина не в меше
throw new VertexNotFoundError(vertex, this);
}
} else {
// Если удаляем по ID
if (_vertices[vertex] == undefined) {
// Если нет вершины с таким ID
throw new VertexNotFoundError(vertex, this);
} else if (!(_vertices[vertex] is Vertex)) {
// По этому id не вершина
throw new InvalidIDError(vertex, this);
}
}
// Находим вершину и её ID
var v:Vertex = byLink ? Vertex(vertex) : _vertices[vertex];
var id:Object = byLink ? getVertexId(Vertex(vertex)) : vertex;
// Удаляем вершину из сцены
if (_scene != null) {
v.removeFromScene(_scene);
}
// Удаляем вершину из меша
v.removeFromMesh(this);
delete _vertices[id];
return v;
}
/**
* Добавление грани к объекту. В результате выполнения метода в объекте появляется новая грань, не привязанная
* ни к одной поверхности.
*
* @param vertices массив вершин грани, указанных в порядке обхода лицевой стороны грани против часовой
* стрелки. Каждый элемент массива может быть либо экземпляром класса <code>alternativa.engine3d.core.Vertex</code>,
* либо идентификатором в наборе вершин объекта. В обоих случаях объект должен содержать указанную вершину.
* @param id идентификатор грани. Если указано значение <code>null</code>, идентификатор будет
* сформирован автоматически.
*
* @return экземпляр добавленной грани
*
* @throws alternativa.engine3d.errors.FaceNeedMoreVerticesError в качестве массива вершин был передан
* <code>null</code>, либо количество вершин в массиве меньше трёх
* @throws alternativa.engine3d.errors.FaceExistsError объект уже содержит грань с заданным идентификатором
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
* @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит какую-либо вершину из входного массива
*
* @see Vertex
*/
public function createFace(vertices:Array, id:Object = null):Face {
// Проверяем на null
if (vertices == null) {
throw new FaceNeedMoreVerticesError(this);
}
// Проверяем ID
if (id != null) {
// Если уже есть грань с таким ID
if (_faces[id] != undefined) {
if (_faces[id] is Face) {
throw new FaceExistsError(id, this);
} else {
throw new InvalidIDError(id, this);
}
}
} else {
// Ищем первый свободный ID
while (_faces[faceIDCounter] != undefined) {
faceIDCounter++;
}
id = faceIDCounter;
}
// Проверяем количество точек
var length:uint = vertices.length;
if (length < 3) {
throw new FaceNeedMoreVerticesError(this, length);
}
// Проверяем и формируем список вершин
var v:Array = new Array();
var vertex:Vertex;
for (var i:uint = 0; i < length; i++) {
if (vertices[i] is Vertex) {
// Если работаем со ссылками
vertex = vertices[i];
if (vertex._mesh != this) {
// Если вершина не в меше
throw new VertexNotFoundError(vertices[i], this);
}
} else {
// Если работаем с ID
if (_vertices[vertices[i]] == null) {
// Если нет вершины с таким ID
throw new VertexNotFoundError(vertices[i], this);
} else if (!(_vertices[vertices[i]] is Vertex)) {
// Если id зарезервировано
throw new InvalidIDError(vertices[i],this);
}
vertex = _vertices[vertices[i]];
}
v.push(vertex);
}
// Создаём грань
var f:Face = new Face(v);
// Добавляем грань на сцену
if (_scene != null) {
f.addToScene(_scene);
}
// Добавляем грань в меш
f.addToMesh(this);
_faces[id] = f;
return f;
}
/**
* Удаление грани из объекта. Грань также удаляется из поверхности объекта, которой она принадлежит.
*
* @param экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор удаляемой грани
*
* @return экземпляр удалённой грани
*
* @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function removeFace(face:Object):Face {
var byLink:Boolean = face is Face;
// Проверяем на null
if (face == null) {
throw new FaceNotFoundError(null, this);
}
// Проверяем наличие грани в меше
if (byLink) {
// Если удаляем по ссылке
if (Face(face)._mesh != this) {
// Если грань не в меше
throw new FaceNotFoundError(face, this);
}
} else {
// Если удаляем по ID
if (_faces[face] == undefined) {
// Если нет грани с таким ID
throw new FaceNotFoundError(face, this);
} else if (!(_faces[face] is Face)) {
throw new InvalidIDError(face, this);
}
}
// Находим грань и её ID
var f:Face = byLink ? Face(face) : _faces[face] ;
var id:Object = byLink ? getFaceId(Face(face)) : face;
// Удаляем грань из сцены
if (_scene != null) {
f.removeFromScene(_scene);
}
// Удаляем вершины из грани
f.removeVertices();
// Удаляем грань из поверхности
if (f._surface != null) {
f._surface._faces.remove(f);
f.removeFromSurface(f._surface);
}
// Удаляем грань из меша
f.removeFromMesh(this);
delete _faces[id];
return f;
}
/**
* Добавление новой поверхности к объекту.
*
* @param faces набор граней, составляющих поверхность. Каждый элемент массива должен быть либо экземпляром класса
* <code>alternativa.engine3d.core.Face</code>, либо идентификатором грани. В обоих случаях объект должен содержать
* указанную грань. Если значение параметра равно <code>null</code>, то будет создана пустая поверхность. Если
* какая-либо грань содержится в другой поверхности, она будет перенесена в новую поверхность.
* @param id идентификатор новой поверхности. Если указано значение <code>null</code>, идентификатор будет
* сформирован автоматически.
*
* @return экземпляр добавленной поверхности
*
* @throws alternativa.engine3d.errors.SurfaceExistsError объект уже содержит поверхность с заданным идентификатором
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*
* @see Face
*/
public function createSurface(faces:Array = null, id:Object = null):Surface {
// Проверяем ID
if (id != null) {
// Если уже есть поверхность с таким ID
if (_surfaces[id] != undefined) {
if (_surfaces[id] is Surface) {
throw new SurfaceExistsError(id, this);
} else {
throw new InvalidIDError(id, this);
}
}
} else {
// Ищем первый свободный ID
while (_surfaces[surfaceIDCounter] != undefined) {
surfaceIDCounter++;
}
id = surfaceIDCounter;
}
// Создаём поверхность
var s:Surface = new Surface();
// Добавляем поверхность на сцену
if (_scene != null) {
s.addToScene(_scene);
}
// Добавляем поверхность в меш
s.addToMesh(this);
_surfaces[id] = s;
// Добавляем грани, если есть
if (faces != null) {
var length:uint = faces.length;
for (var i:uint = 0; i < length; i++) {
s.addFace(faces[i]);
}
}
return s;
}
/**
* Удаление поверхности объекта. Из удаляемой поверхности также удаляются все содержащиеся в ней грани.
*
* @param surface экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор удаляемой поверхности
*
* @return экземпляр удалённой поверхности
*
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function removeSurface(surface:Object):Surface {
var byLink:Boolean = surface is Surface;
// Проверяем на null
if (surface == null) {
throw new SurfaceNotFoundError(null, this);
}
// Проверяем наличие поверхности в меше
if (byLink) {
// Если удаляем по ссылке
if (Surface(surface)._mesh != this) {
// Если поверхность не в меше
throw new SurfaceNotFoundError(surface, this);
}
} else {
// Если удаляем по ID
if (_surfaces[surface] == undefined) {
// Если нет поверхности с таким ID
throw new SurfaceNotFoundError(surface, this);
} else if (!(_surfaces[surface] is Surface)) {
throw new InvalidIDError(surface, this);
}
}
// Находим поверхность и её ID
var s:Surface = byLink ? Surface(surface) : _surfaces[surface];
var id:Object = byLink ? getSurfaceId(Surface(surface)) : surface;
// Удаляем поверхность из сцены
if (_scene != null) {
s.removeFromScene(_scene);
}
// Удаляем грани из поверхности
s.removeFaces();
// Удаляем поверхность из меша
s.removeFromMesh(this);
delete _surfaces[id];
return s;
}
/**
* Добавление всех граней объекта в указанную поверхность.
*
* @param surface экземпляр класса <code>alternativa.engine3d.core.Surface</code> или идентификатор поверхности, в
* которую добавляются грани. Если задан идентификатор, и объект не содержит поверхность с таким идентификатором,
* будет создана новая поверхность.
*
* @param removeSurfaces удалять или нет пустые поверхности после переноса граней
*
* @return экземпляр поверхности, в которую перенесены грани
*
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанный экземпляр поверхности
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function moveAllFacesToSurface(surface:Object = null, removeSurfaces:Boolean = false):Surface {
var returnSurface:Surface;
var returnSurfaceId:Object;
if (surface is Surface) {
// Работаем с экземпляром Surface
if (surface._mesh == this) {
returnSurface = Surface(surface);
} else {
throw new SurfaceNotFoundError(surface, this);
}
} else {
// Работаем с идентификатором
if (_surfaces[surface] == undefined) {
// Поверхности еще нет
returnSurface = createSurface(null, surface);
returnSurfaceId = surface;
} else {
if (_surfaces[surface] is Surface) {
returnSurface = _surfaces[surface];
} else {
// _surfaces[surface] по идентификатору возвращает не Surface
throw new InvalidIDError(surface, this);
}
}
}
// Перемещаем все грани
for each (var face:Face in _faces) {
if (face._surface != returnSurface) {
returnSurface.addFace(face);
}
}
if (removeSurfaces) {
// Удаляем старые, теперь вручную - меньше проверок, но рискованно
if (returnSurfaceId == null) {
returnSurfaceId = getSurfaceId(returnSurface);
}
var newSurfaces:Map = new Map();
newSurfaces[returnSurfaceId] = returnSurface;
delete _surfaces[returnSurfaceId];
// Удаляем оставшиеся
for (var currentSurfaceId:* in _surfaces) {
// Удаляем поверхность из сцены
var currentSurface:Surface = _surfaces[currentSurfaceId];
if (_scene != null) {
currentSurface.removeFromScene(_scene);
}
// Удаляем поверхность из меша
currentSurface.removeFromMesh(this);
delete _surfaces[currentSurfaceId];
}
// Новый список граней
_surfaces = newSurfaces;
}
return returnSurface;
}
/**
* Установка материала для указанной поверхности.
*
* @param material материал, назначаемый поверхности. Один экземпляр SurfaceMaterial можно назначить только одной поверхности.
* @param surface экземпляр класса <code>alternativa.engine3d.core.Surface</code> или идентификатор поверхности
*
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*
* @see Surface
*/
public function setMaterialToSurface(material:SurfaceMaterial, surface:Object):void {
var byLink:Boolean = surface is Surface;
// Проверяем на null
if (surface == null) {
throw new SurfaceNotFoundError(null, this);
}
// Проверяем наличие поверхности в меше
if (byLink) {
// Если назначаем по ссылке
if (Surface(surface)._mesh != this) {
// Если поверхность не в меше
throw new SurfaceNotFoundError(surface, this);
}
} else {
// Если назначаем по ID
if (_surfaces[surface] == undefined) {
// Если нет поверхности с таким ID
throw new SurfaceNotFoundError(surface, this);
} else if (!(_surfaces[surface] is Surface)) {
throw new InvalidIDError(surface, this);
}
}
// Находим поверхность
var s:Surface = byLink ? Surface(surface) : _surfaces[surface];
// Назначаем материал
s.material = material;
}
/**
* Установка материала для всех поверхностей объекта. Для каждой поверхности устанавливается копия материала.
* При передаче <code>null</code> в качестве параметра происходит сброс материалов у всех поверхностей.
*
* @param material устанавливаемый материал
*/
public function cloneMaterialToAllSurfaces(material:SurfaceMaterial):void {
for each (var surface:Surface in _surfaces) {
surface.material = (material != null) ? SurfaceMaterial(material.clone()) : null;
}
}
/**
* Установка UV-координат для указанной грани объекта. Матрица преобразования UV-координат расчитывается по
* UV-координатам первых трёх вершин грани, поэтому для корректного текстурирования эти вершины должны образовывать
* невырожденный треугольник в UV-пространстве.
*
* @param aUV UV-координаты, соответствующие первой вершине грани
* @param bUV UV-координаты, соответствующие второй вершине грани
* @param cUV UV-координаты, соответствующие третьей вершине грани
* @param face экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор грани
*
* @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*
* @see Face
*/
public function setUVsToFace(aUV:Point, bUV:Point, cUV:Point, face:Object):void {
var byLink:Boolean = face is Face;
// Проверяем на null
if (face == null) {
throw new FaceNotFoundError(null, this);
}
// Проверяем наличие грани в меше
if (byLink) {
// Если назначаем по ссылке
if (Face(face)._mesh != this) {
// Если грань не в меше
throw new FaceNotFoundError(face, this);
}
} else {
// Если назначаем по ID
if (_faces[face] == undefined) {
// Если нет грани с таким ID
throw new FaceNotFoundError(face, this);
} else if (!(_faces[face] is Face)) {
throw new InvalidIDError(face, this);
}
}
// Находим грань
var f:Face = byLink ? Face(face) : _faces[face];
// Назначаем UV-координаты
f.aUV = aUV;
f.bUV = bUV;
f.cUV = cUV;
}
/**
* Набор вершин объекта. Ключами ассоциативного массива являются идентификаторы вершин, значениями - экземпляры вершин.
*/
public function get vertices():Map {
return _vertices.clone();
}
/**
* Набор граней объекта. Ключами ассоциативного массива являются идентификаторы граней, значениями - экземпляры граней.
*/
public function get faces():Map {
return _faces.clone();
}
/**
* Набор поверхностей объекта. Ключами ассоциативного массива являются идентификаторы поверхностей, значениями - экземпляры поверхностей.
*/
public function get surfaces():Map {
return _surfaces.clone();
}
/**
* Получение вершины объекта по её идентификатору.
*
* @param id идентификатор вершины
*
* @return экземпляр вершины с указанным идентификатором
*
* @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит вершину с указанным идентификатором
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function getVertexById(id:Object):Vertex {
if (id == null) {
throw new VertexNotFoundError(null, this);
}
if (_vertices[id] == undefined) {
// Если нет вершины с таким ID
throw new VertexNotFoundError(id, this);
} else {
if (_vertices[id] is Vertex) {
return _vertices[id];
} else {
throw new InvalidIDError(id, this);
}
}
}
/**
* Получение идентификатора вершины объекта.
*
* @param экземпляр вершины
*
* @return идентификатор указанной вершины
*
* @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину
*/
public function getVertexId(vertex:Vertex):Object {
if (vertex == null) {
throw new VertexNotFoundError(null, this);
}
if (vertex._mesh != this) {
// Если вершина не в меше
throw new VertexNotFoundError(vertex, this);
}
for (var i:Object in _vertices) {
if (_vertices[i] == vertex) {
return i;
}
}
throw new VertexNotFoundError(vertex, this);
}
/**
* Проверка наличия вершины в объекте.
*
* @param vertex экземпляр класса <code>alternativa.engine3d.core.Vertex</code> или идентификатор вершины
*
* @return <code>true</code>, если объект содержит указанную вершину, иначе <code>false</code>
*
* @throws alternativa.engine3d.errors.VertexNotFoundError в качестве vertex был передан null
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*
* @see Vertex
*/
public function hasVertex(vertex:Object):Boolean {
if (vertex == null) {
throw new VertexNotFoundError(null, this);
}
if (vertex is Vertex) {
// Проверка вершины
return vertex._mesh == this;
} else {
// Проверка ID вершины
if (_vertices[vertex] != undefined) {
// По этому ID есть объект
if (_vertices[vertex] is Vertex) {
// Объект является вершиной
return true;
} else {
// ID некорректный
throw new InvalidIDError(vertex, this);
}
} else {
return false;
}
}
}
/**
* Получение грани объекта по ее идентификатору.
*
* @param id идентификатор грани
*
* @return экземпляр грани с указанным идентификатором
*
* @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит грань с указанным идентификатором
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function getFaceById(id:Object):Face {
if (id == null) {
throw new FaceNotFoundError(null, this);
}
if (_faces[id] == undefined) {
// Если нет грани с таким ID
throw new FaceNotFoundError(id, this);
} else {
if (_faces[id] is Face) {
return _faces[id];
} else {
throw new InvalidIDError(id, this);
}
}
}
/**
* Получение идентификатора грани объекта.
*
* @param face экземпляр грани
*
* @return идентификатор указанной грани
*
* @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
*/
public function getFaceId(face:Face):Object {
if (face == null) {
throw new FaceNotFoundError(null, this);
}
if (face._mesh != this) {
// Если грань не в меше
throw new FaceNotFoundError(face, this);
}
for (var i:Object in _faces) {
if (_faces[i] == face) {
return i;
}
}
throw new FaceNotFoundError(face, this);
}
/**
* Проверка наличия грани в объекте.
*
* @param face экземпляр класса <code>Face</code> или идентификатор грани
*
* @return <code>true</code>, если объект содержит указанную грань, иначе <code>false</code>
*
* @throws alternativa.engine3d.errors.FaceNotFoundError в качестве face был указан null
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function hasFace(face:Object):Boolean {
if (face == null) {
throw new FaceNotFoundError(null, this);
}
if (face is Face) {
// Проверка грани
return face._mesh == this;
} else {
// Проверка ID грани
if (_faces[face] != undefined) {
// По этому ID есть объект
if (_faces[face] is Face) {
// Объект является гранью
return true;
} else {
// ID некорректный
throw new InvalidIDError(face, this);
}
} else {
return false;
}
}
}
/**
* Получение поверхности объекта по ее идентификатору
*
* @param id идентификатор поверхности
*
* @return экземпляр поверхности с указанным идентификатором
*
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит поверхность с указанным идентификатором
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function getSurfaceById(id:Object):Surface {
if (id == null) {
throw new SurfaceNotFoundError(null, this);
}
if (_surfaces[id] == undefined) {
// Если нет поверхности с таким ID
throw new SurfaceNotFoundError(id, this);
} else {
if (_surfaces[id] is Surface) {
return _surfaces[id];
} else {
throw new InvalidIDError(id, this);
}
}
}
/**
* Получение идентификатора поверхности объекта.
*
* @param surface экземпляр поверхности
*
* @return идентификатор указанной поверхности
*
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
*/
public function getSurfaceId(surface:Surface):Object {
if (surface == null) {
throw new SurfaceNotFoundError(null, this);
}
if (surface._mesh != this) {
// Если поверхность не в меше
throw new SurfaceNotFoundError(surface, this);
}
for (var i:Object in _surfaces) {
if (_surfaces[i] == surface) {
return i;
}
}
return null;
}
/**
* Проверка наличия поверхности в объекте.
*
* @param surface экземпляр класса <code>Surface</code> или идентификатор поверхности
*
* @return <code>true</true>, если объект содержит указанную поверхность, иначе <code>false</code>
*
* @throws alternativa.engine3d.errors.SurfaceNotFoundError в качестве surface был передан null
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function hasSurface(surface:Object):Boolean {
if (surface == null) {
throw new SurfaceNotFoundError(null, this);
}
if (surface is Surface) {
// Проверка поверхности
return surface._mesh == this;
} else {
// Проверка ID поверхности
if (_surfaces[surface] != undefined) {
// По этому ID есть объект
if (_surfaces[surface] is Surface) {
// Объект является поверхностью
return true;
} else {
// ID некорректный
throw new InvalidIDError(surface, this);
}
} else {
return false;
}
}
}
/**
* @private
* @inheritDoc
*/
override alternativa3d function setScene(value:Scene3D):void {
if (_scene != value) {
var vertex:Vertex;
var face:Face;
var surface:Surface;
if (value != null) {
// Добавить вершины на сцену
for each (vertex in _vertices) {
vertex.addToScene(value);
}
// Добавить грани на сцену
for each (face in _faces) {
face.addToScene(value);
}
// Добавить поверхности на сцену
for each (surface in _surfaces) {
surface.addToScene(value);
}
} else {
// Удалить вершины из сцены
for each (vertex in _vertices) {
vertex.removeFromScene(_scene);
}
// Удалить грани из сцены
for each (face in _faces) {
face.removeFromScene(_scene);
}
// Удалить поверхности из сцены
for each (surface in _surfaces) {
surface.removeFromScene(_scene);
}
}
}
super.setScene(value);
}
/**
* @inheritDoc
*/
override protected function defaultName():String {
return "mesh" + ++counter;
}
/**
* @inheritDoc
*/
override public function toString():String {
return "[" + ObjectUtils.getClassName(this) + " " + _name + " vertices: " + _vertices.length + " faces: " + _faces.length + "]";
}
/**
* @inheritDoc
*/
protected override function createEmptyObject():Object3D {
return new Mesh();
}
/**
* @inheritDoc
*/
protected override function clonePropertiesFrom(source:Object3D):void {
super.clonePropertiesFrom(source);
var src:Mesh = Mesh(source);
var id:*;
var len:int;
var i:int;
// Копирование вершин
var vertexMap:Map = new Map(true);
for (id in src._vertices) {
var sourceVertex:Vertex = src._vertices[id];
vertexMap[sourceVertex] = createVertex(sourceVertex.x, sourceVertex.y, sourceVertex.z, id);
}
// Копирование граней
var faceMap:Map = new Map(true);
for (id in src._faces) {
var sourceFace:Face = src._faces[id];
len = sourceFace._vertices.length;
var faceVertices:Array = new Array(len);
for (i = 0; i < len; i++) {
faceVertices[i] = vertexMap[sourceFace._vertices[i]];
}
var newFace:Face = createFace(faceVertices, id);
newFace.aUV = sourceFace._aUV;
newFace.bUV = sourceFace._bUV;
newFace.cUV = sourceFace._cUV;
faceMap[sourceFace] = newFace;
}
// Копирование поверхностей
for (id in src._surfaces) {
var sourceSurface:Surface = src._surfaces[id];
var surfaceFaces:Array = sourceSurface._faces.toArray();
len = surfaceFaces.length;
for (i = 0; i < len; i++) {
surfaceFaces[i] = faceMap[surfaceFaces[i]];
}
var surface:Surface = createSurface(surfaceFaces, id);
var sourceMaterial:SurfaceMaterial = sourceSurface.material;
if (sourceMaterial != null) {
surface.material = SurfaceMaterial(sourceMaterial.clone());
}
}
}
}
}

View File

@@ -0,0 +1,708 @@
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;
/**
* Базовый класс для объектов, находящихся в сцене. Класс реализует иерархию объектов сцены, а также содержит сведения
* о трансформации объекта как единого целого.
*
* <p> Масштабирование, ориентация и положение объекта задаются в родительской системе координат. Результирующая
* локальная трансформация является композицией операций масштабирования, поворотов объекта относительно осей
* <code>X</code>, <code>Y</code>, <code>Z</code> и параллельного переноса центра объекта из начала координат.
* Операции применяются в порядке их перечисления.
*
* <p> Глобальная трансформация (в системе координат корневого объекта сцены) является композицией трансформаций
* самого объекта и всех его предков по иерархии объектов сцены.
*/
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;
}
/**
* Клонирование объекта. Для реализации собственного клонирования наследники должны переопределять методы
* <code>createEmptyObject()</code> и <code>clonePropertiesFrom()</code>.
*
* @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 любой дочерний объект с заданным именем или <code>null</code> в случае отсутствия таких объектов
*/
public function getChildByName(name:String):Object3D {
for (var key:* in _children) {
var child:Object3D = key;
if (child._name == name) {
return child;
}
}
return null;
}
}
}

View File

@@ -0,0 +1,125 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
import alternativa.types.Set;
use namespace alternativa3d;
/**
* @private
*/
public class Operation {
alternativa3d static const OBJECT_CALCULATE_TRANSFORMATION:uint = 0x01000000;
alternativa3d static const OBJECT_CALCULATE_MOBILITY:uint = 0x02000000;
alternativa3d static const VERTEX_CALCULATE_COORDS:uint = 0x03000000;
alternativa3d static const FACE_CALCULATE_NORMAL:uint = 0x04000000;
alternativa3d static const FACE_CALCULATE_UV:uint = 0x05000000;
alternativa3d static const FACE_UPDATE_PRIMITIVE:uint = 0x06000000;
alternativa3d static const SCENE_CALCULATE_BSP:uint = 0x07000000;
alternativa3d static const FACE_UPDATE_MATERIAL:uint = 0x08000000;
alternativa3d static const FACE_CALCULATE_FRAGMENTS_UV:uint = 0x09000000;
alternativa3d static const CAMERA_CALCULATE_MATRIX:uint = 0x0A000000;
alternativa3d static const CAMERA_CALCULATE_PLANES:uint = 0x0B000000;
alternativa3d static const CAMERA_RENDER:uint = 0x0C000000;
alternativa3d static const SCENE_CLEAR_PRIMITIVES:uint = 0x0D000000;
// Объект
alternativa3d var object:Object;
// Метод
alternativa3d var method:Function;
// Название метода
alternativa3d var name:String;
// Последствия
private var sequel:Operation;
private var sequels:Set;
// Приоритет операции
alternativa3d var priority:uint;
// Находится ли операция в очереди
alternativa3d var queued:Boolean = false;
public function Operation(name:String, object:Object = null, method:Function = null, priority:uint = 0) {
this.object = object;
this.method = method;
this.name = name;
this.priority = priority;
}
// Добавить последствие
alternativa3d function addSequel(operation:Operation):void {
if (sequel == null) {
if (sequels == null) {
sequel = operation;
} else {
sequels[operation] = true;
}
} else {
if (sequel != operation) {
sequels = new Set(true);
sequels[sequel] = true;
sequels[operation] = true;
sequel = null;
}
}
}
// Удалить последствие
alternativa3d function removeSequel(operation:Operation):void {
if (sequel == null) {
if (sequels != null) {
delete sequels[operation];
var key:*;
var single:Boolean = false;
for (key in sequels) {
if (single) {
single = false;
break;
}
single = true;
}
if (single) {
sequel = key;
sequels = null;
}
}
} else {
if (sequel == operation) {
sequel = null;
}
}
}
alternativa3d function collectSequels(collector:Array):void {
if (sequel == null) {
// Проверяем последствия
for (var key:* in sequels) {
var operation:Operation = key;
// Если операция ещё не в очереди
if (!operation.queued) {
// Добавляем её в очередь
collector.push(operation);
// Устанавливаем флаг очереди
operation.queued = true;
// Вызываем добавление в очередь её последствий
operation.collectSequels(collector);
}
}
} else {
if (!sequel.queued) {
collector.push(sequel);
sequel.queued = true;
sequel.collectSequels(collector);
}
}
}
public function toString():String {
return "[Operation " + (priority >>> 24) + "/" + (priority & 0xFFFFFF) + " " + object + "." + name + "]";
}
}
}

View File

@@ -0,0 +1,130 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
use namespace alternativa3d;
/**
* @private
* Примитивный полигон (примитив), хранящийся в узле BSP-дерева.
*/
public class PolyPrimitive {
/**
* @private
* Количество точек
*/
alternativa3d var num:uint;
/**
* @private
* Точки
*/
alternativa3d var points:Array = new Array();
/**
* @private
* UV-координаты
*/
alternativa3d var uvs:Array = new Array();
/**
* @private
* Грань
*/
alternativa3d var face:Face;
/**
* @private
* Родительский примитив
*/
alternativa3d var parent:PolyPrimitive;
/**
* @private
* Соседний примитив (при наличии родительского)
*/
alternativa3d var sibling:PolyPrimitive;
/**
* @private
* Фрагменты
*/
alternativa3d var backFragment:PolyPrimitive;
/**
* @private
*/
alternativa3d var frontFragment:PolyPrimitive;
/**
* @private
* Рассечения
*/
alternativa3d var splitTime1:Number;
/**
* @private
*/
alternativa3d var splitTime2:Number;
/**
* @private
* BSP-нода, в которой находится примитив
*/
alternativa3d var node:BSPNode;
/**
* @private
* Значения для расчёта качества сплиттера
*/
alternativa3d var splits:uint;
/**
* @private
*/
alternativa3d var disbalance:int;
/**
* @private
* Качество примитива как сплиттера (меньше - лучше)
*/
public var splitQuality:Number;
/**
* @private
* Приоритет в BSP-дереве. Чем ниже мобильность, тем примитив выше в дереве.
*/
public var mobility:int;
// Хранилище неиспользуемых примитивов
static private var collector:Array = new Array();
/**
* @private
* Создать примитив
*/
static alternativa3d function createPolyPrimitive():PolyPrimitive {
// Достаём примитив из коллектора
var primitive:PolyPrimitive = collector.pop();
// Если коллектор пуст, создаём новый примитив
if (primitive == null) {
primitive = new PolyPrimitive();
}
//trace(primitive.num, primitive.points.length, primitive.face, primitive.parent, primitive.sibling, primitive.fragment1, primitive.fragment2, primitive.node);
return primitive;
}
/**
* @private
* Кладёт примитив в коллектор для последующего реиспользования.
* Ссылка на грань и массивы точек зачищаются в этом методе.
* Ссылки на фрагменты (parent, sibling, back, front) должны быть зачищены перед запуском метода.
*
* Исключение:
* при сборке примитивов в сцене ссылки на back и front зачищаются после запуска метода.
*
* @param primitive примитив на реиспользование
*/
static alternativa3d function destroyPolyPrimitive(primitive:PolyPrimitive):void {
primitive.face = null;
for (var i:uint = 0; i < primitive.num; i++) {
primitive.points.pop();
primitive.uvs.pop();
}
collector.push(primitive);
}
/**
* Строковое представление объекта.
*/
public function toString():String {
return "[Primitive " + face._mesh._name + "]";
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,350 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
import alternativa.engine3d.errors.FaceExistsError;
import alternativa.engine3d.errors.FaceNotFoundError;
import alternativa.engine3d.errors.InvalidIDError;
import alternativa.engine3d.materials.SurfaceMaterial;
import alternativa.types.Set;
use namespace alternativa3d;
/**
* Поверхность &mdash; набор граней, объединённых в группу. Поверхности используются для установки материалов,
* визуализирующих грани объекта.
*/
public class Surface {
// Операции
/**
* @private
* Изменение набора граней
*/
alternativa3d var changeFacesOperation:Operation = new Operation("changeFaces", this);
/**
* @private
* Изменение материала
*/
alternativa3d var changeMaterialOperation:Operation = new Operation("changeMaterial", this);
/**
* @private
* Меш
*/
alternativa3d var _mesh:Mesh;
/**
* @private
* Материал
*/
alternativa3d var _material:SurfaceMaterial;
/**
* @private
* Грани
*/
alternativa3d var _faces:Set = new Set();
/**
* Создание экземпляра поверхности.
*/
public function Surface() {}
/**
* Добавление грани в поверхность.
*
* @param face экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор грани полигонального объекта
*
* @throws alternativa.engine3d.errors.FaceNotFoundError грань не найдена в полигональном объекте содержащем поверхность
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
* @throws alternativa.engine3d.errors.FaceExistsError поверхность уже содержит указанную грань
*
* @see Face
*/
public function addFace(face:Object):void {
var byLink:Boolean = face is Face;
// Проверяем на нахождение поверхности в меше
if (_mesh == null) {
throw new FaceNotFoundError(face, this);
}
// Проверяем на null
if (face == null) {
throw new FaceNotFoundError(null, this);
}
// Проверяем наличие грани в меше
if (byLink) {
// Если удаляем по ссылке
if (Face(face)._mesh != _mesh) {
// Если грань не в меше
throw new FaceNotFoundError(face, this);
}
} else {
// Если удаляем по ID
if (_mesh._faces[face] == undefined) {
// Если нет грани с таким ID
throw new FaceNotFoundError(face, this);
} else {
if (!(_mesh._faces[face] is Face)) {
throw new InvalidIDError(face, this);
}
}
}
// Находим грань
var f:Face = byLink ? Face(face) : _mesh._faces[face];
// Проверяем наличие грани в поверхности
if (_faces.has(f)) {
// Если грань уже в поверхности
throw new FaceExistsError(f, this);
}
// Проверяем грань на нахождение в другой поверхности
if (f._surface != null) {
// Удаляем её из той поверхности
f._surface._faces.remove(f);
f.removeFromSurface(f._surface);
}
// Добавляем грань в поверхность
_faces.add(f);
f.addToSurface(this);
// Отправляем операцию изменения набора граней
_mesh.addOperationToScene(changeFacesOperation);
}
/**
* Удаление грани из поверхности.
*
* @param face экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор грани полигонального объекта
*
* @throws alternativa.engine3d.errors.FaceNotFoundError поверхность не содержит указанную грань
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*
* @see Face
*/
public function removeFace(face:Object):void {
var byLink:Boolean = face is Face;
// Проверяем на нахождение поверхности в меше
if (_mesh == null) {
throw new FaceNotFoundError(face, this);
}
// Проверяем на null
if (face == null) {
throw new FaceNotFoundError(null, this);
}
// Проверяем наличие грани в меше
if (byLink) {
// Если удаляем по ссылке
if (Face(face)._mesh != _mesh) {
// Если грань не в меше
throw new FaceNotFoundError(face, this);
}
} else {
// Если удаляем по ID
if (_mesh._faces[face] == undefined) {
// Если нет грани с таким ID
throw new FaceNotFoundError(face, this);
} else {
if (!(_mesh._faces[face] is Face)) {
throw new InvalidIDError(face, this);
}
}
}
// Находим грань
var f:Face = byLink ? Face(face) : _mesh._faces[face];
// Проверяем наличие грани в поверхности
if (!_faces.has(f)) {
// Если грань не в поверхности
throw new FaceNotFoundError(f, this);
}
// Удаляем грань из поверхности
_faces.remove(f);
f.removeFromSurface(this);
// Отправляем операцию изменения набора граней
_mesh.addOperationToScene(changeFacesOperation);
}
/**
* Материал поверхности. При установке нового значения, устанавливаемый материал будет удалён из старой поверхности.
*/
public function get material():SurfaceMaterial {
return _material;
}
/**
* @private
*/
public function set material(value:SurfaceMaterial):void {
if (_material != value) {
// Если был материал
if (_material != null) {
// Удалить материал из поверхности
_material.removeFromSurface(this);
// Удалить материал из меша
if (_mesh != null) {
_material.removeFromMesh(_mesh);
// Удалить материал из сцены
if (_mesh._scene != null) {
_material.removeFromScene(_mesh._scene);
}
}
}
// Если новый материал
if (value != null) {
// Если материал был в другой поверхности
if (value._surface != null) {
// Удалить его оттуда
value._surface.material = null;
}
// Добавить материал в поверхность
value.addToSurface(this);
// Добавить материал в меш
if (_mesh != null) {
value.addToMesh(_mesh);
// Добавить материал в сцену
if (_mesh._scene != null) {
value.addToScene(_mesh._scene);
}
}
}
// Сохраняем материал
_material = value;
// Отправляем операцию изменения материала
addMaterialChangedOperationToScene();
}
}
/**
* Набор граней поверхности.
*/
public function get faces():Set {
return _faces.clone();
}
/**
* Полигональный объект, которому принадлежит поверхность.
*/
public function get mesh():Mesh {
return _mesh;
}
/**
* Идентификатор поверхности в полигональном объекте. Если поверхность не принадлежит ни одному объекту,
* значение идентификатора равно <code>null</code>.
*/
public function get id():Object {
return (_mesh != null) ? _mesh.getSurfaceId(this) : null;
}
/**
* @private
* Добавление в сцену.
*
* @param scene
*/
alternativa3d function addToScene(scene:Scene3D):void {
// Добавляем на сцену материал
if (_material != null) {
_material.addToScene(scene);
}
}
/**
* @private
* Удаление из сцены.
*
* @param scene
*/
alternativa3d function removeFromScene(scene:Scene3D):void {
// Удаляем все операции из очереди
scene.removeOperation(changeFacesOperation);
scene.removeOperation(changeMaterialOperation);
// Удаляем из сцены материал
if (_material != null) {
_material.removeFromScene(scene);
}
}
/**
* @private
* Добавление к мешу
* @param mesh
*/
alternativa3d function addToMesh(mesh:Mesh):void {
// Подписка на операции меша
// Добавляем в меш материал
if (_material != null) {
_material.addToMesh(mesh);
}
// Сохранить меш
_mesh = mesh;
}
/**
* @private
* Удаление из меша
*
* @param mesh
*/
alternativa3d function removeFromMesh(mesh:Mesh):void {
// Отписка от операций меша
// Удаляем из меша материал
if (_material != null) {
_material.removeFromMesh(mesh);
}
// Удалить ссылку на меш
_mesh = null;
}
/**
* @private
* Удаление граней
*/
alternativa3d function removeFaces():void {
for (var key:* in _faces) {
var face:Face = key;
_faces.remove(face);
face.removeFromSurface(this);
}
}
/**
* @private
* Изменение материала
*/
alternativa3d function addMaterialChangedOperationToScene():void {
if (_mesh != null) {
_mesh.addOperationToScene(changeMaterialOperation);
}
}
/**
* Строковое представление объекта.
*
* @return строковое представление объекта
*/
public function toString():String {
var length:uint = _faces.length;
var res:String = "[Surface ID:" + id + ((length > 0) ? " faces:" : "");
var i:uint = 0;
for (var key:* in _faces) {
var face:Face = key;
res += face.id + ((i < length - 1) ? ", " : "");
i++;
}
res += "]";
return res;
}
}
}

View File

@@ -0,0 +1,250 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
import alternativa.types.Matrix3D;
import alternativa.types.Point3D;
import alternativa.types.Set;
use namespace alternativa3d;
/**
* Вершина полигона в трёхмерном пространстве. Вершина хранит свои координаты, а также ссылки на
* полигональный объект и грани этого объекта, которым она принадлежит.
*/
final public class Vertex {
// Операции
/**
* @private
* Изменение локальных координат
*/
alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this);
/**
* @private
* Расчёт глобальных координат
*/
alternativa3d var calculateCoordsOperation:Operation = new Operation("calculateCoords", this, calculateCoords, Operation.VERTEX_CALCULATE_COORDS);
/**
* @private
* Меш
*/
alternativa3d var _mesh:Mesh;
/**
* @private
* Координаты точки
*/
alternativa3d var _coords:Point3D;
/**
* @private
* Грани
*/
alternativa3d var _faces:Set = new Set();
/**
* @private
* Координаты в сцене
*/
alternativa3d var globalCoords:Point3D = new Point3D();
/**
* Создание экземпляра вершины.
*
* @param x координата вершины по оси X
* @param y координата вершины по оси Y
* @param z координата вершины по оси Z
*/
public function Vertex(x:Number = 0, y:Number = 0, z:Number = 0) {
_coords = new Point3D(x, y, z);
// Изменение координат инициирует пересчёт глобальных координат
changeCoordsOperation.addSequel(calculateCoordsOperation);
}
/**
* Вызывается из операции calculateCoordsOperation для расчета глобальных координат вершины
*/
private function calculateCoords():void {
globalCoords.copy(_coords);
globalCoords.transform(_mesh.transformation);
}
/**
* @private
*/
public function set x(value:Number):void {
if (_coords.x != value) {
_coords.x = value;
if (_mesh != null) {
_mesh.addOperationToScene(changeCoordsOperation);
}
}
}
/**
* @private
*/
public function set y(value:Number):void {
if (_coords.y != value) {
_coords.y = value;
if (_mesh != null) {
_mesh.addOperationToScene(changeCoordsOperation);
}
}
}
/**
* @private
*/
public function set z(value:Number):void {
if (_coords.z != value) {
_coords.z = value;
if (_mesh != null) {
_mesh.addOperationToScene(changeCoordsOperation);
}
}
}
/**
* @private
*/
public function set coords(value:Point3D):void {
if (!_coords.equals(value)) {
_coords.copy(value);
if (_mesh != null) {
_mesh.addOperationToScene(changeCoordsOperation);
}
}
}
/**
* координата вершины по оси 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;
}
/**
* Координаты вершины.
*/
public function get coords():Point3D {
return _coords.clone();
}
/**
* Полигональный объект, которому принадлежит вершина.
*/
public function get mesh():Mesh {
return _mesh;
}
/**
* Множество граней, которым принадлежит вершина. Каждый элемент множества является объектом класса
* <code>altertnativa.engine3d.core.Face</code>.
*
* @see Face
*/
public function get faces():Set {
return _faces.clone();
}
/**
* Идентификатор вершины в полигональном объекте. Если вершина не принадлежит полигональному объекту, возвращается <code>null</code>.
*/
public function get id():Object {
return (_mesh != null) ? _mesh.getVertexId(this) : null;
}
/**
* @private
* @param scene
*/
alternativa3d function addToScene(scene:Scene3D):void {
// При добавлении на сцену расчитать глобальные координаты
scene.addOperation(calculateCoordsOperation);
}
/**
* @private
* @param scene
*/
alternativa3d function removeFromScene(scene:Scene3D):void {
// Удаляем все операции из очереди
scene.removeOperation(calculateCoordsOperation);
scene.removeOperation(changeCoordsOperation);
}
/**
* @private
* @param mesh
*/
alternativa3d function addToMesh(mesh:Mesh):void {
// Подписка на операции меша
mesh.changeCoordsOperation.addSequel(calculateCoordsOperation);
mesh.changeRotationOrScaleOperation.addSequel(calculateCoordsOperation);
// Сохранить меш
_mesh = mesh;
}
/**
* @private
* @param mesh
*/
alternativa3d function removeFromMesh(mesh:Mesh):void {
// Отписка от операций меша
mesh.changeCoordsOperation.removeSequel(calculateCoordsOperation);
mesh.changeRotationOrScaleOperation.removeSequel(calculateCoordsOperation);
// Удалить зависимые грани
for (var key:* in _faces) {
var face:Face = key;
mesh.removeFace(face);
}
// Удалить ссылку на меш
_mesh = null;
}
/**
* @private
* @param face
*/
alternativa3d function addToFace(face:Face):void {
// Подписка грани на операции
changeCoordsOperation.addSequel(face.calculateUVOperation);
changeCoordsOperation.addSequel(face.calculateNormalOperation);
// Добавить грань в список
_faces.add(face);
}
/**
* @private
* @param face
*/
alternativa3d function removeFromFace(face:Face):void {
// Отписка грани от операций
changeCoordsOperation.removeSequel(face.calculateUVOperation);
changeCoordsOperation.removeSequel(face.calculateNormalOperation);
// Удалить грань из списка
_faces.remove(face);
}
/**
* Строковое представление объекта.
*
* @return строковое представление объекта
*/
public function toString():String {
return "[Vertex ID:" + id + " " + _coords.x.toFixed(2) + ", " + _coords.y.toFixed(2) + ", " + _coords.z.toFixed(2) + "]";
}
}
}

View File

@@ -0,0 +1,65 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
import alternativa.types.Point3D;
import alternativa.types.Set;
use namespace alternativa3d;
/**
* @private
*/
public final class BSPNode {
// Родительская нода
alternativa3d var parent:BSPNode;
// Дочерние ветки
alternativa3d var front:BSPNode;
alternativa3d var back:BSPNode;
// Нормаль плоскости ноды
alternativa3d var normal:Point3D = new Point3D();
// Смещение плоскости примитива
alternativa3d var offset:Number;
// Минимальная мобильность ноды
alternativa3d var mobility:int = int.MAX_VALUE;
// Набор примитивов в ноде
alternativa3d var primitive:PolyPrimitive;
alternativa3d var backPrimitives:Set;
alternativa3d var frontPrimitives:Set;
// Хранилище неиспользуемых нод
static private var collector:Array = new Array();
// Создать ноду на основе примитива
static alternativa3d function createBSPNode(primitive:PolyPrimitive):BSPNode {
// Достаём ноду из коллектора
var node:BSPNode = collector.pop();
// Если коллектор пуст, создаём новую ноду
if (node == null) {
node = new BSPNode();
}
// Добавляем примитив в ноду
node.primitive = primitive;
// Сохраняем ноду
primitive.node = node;
// Сохраняем плоскость
node.normal.copy(primitive.face.globalNormal);
node.offset = primitive.face.globalOffset;
// Сохраняем мобильность
node.mobility = primitive.mobility;
return node;
}
// Удалить ноду, все ссылки должны быть почищены
static alternativa3d function destroyBSPNode(node:BSPNode):void {
//trace(node.back, node.front, node.parent, node.primitive, node.backPrimitives, node.frontPrimitives);
collector.push(node);
}
}
}

View File

@@ -0,0 +1,902 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
import alternativa.engine3d.display.Skin;
import alternativa.engine3d.display.View;
import alternativa.engine3d.materials.DrawPoint;
import alternativa.engine3d.materials.SurfaceMaterial;
import alternativa.types.Matrix3D;
import alternativa.types.Point3D;
import alternativa.types.Set;
import flash.geom.Matrix;
import flash.geom.Point;
import alternativa.utils.MathUtils;
use namespace alternativa3d;
/**
* Камера для отображения 3D-сцены на экране.
*
* <p> Направление камеры совпадает с её локальной осью Z, поэтому только что созданная камера смотрит вверх в системе
* координат родителя.
*
* <p> Для отображения видимой через камеру части сцены на экран, к камере должна быть подключёна область вывода &mdash;
* экземпляр класса <code>alternativa.engine3d.display.View</code>.
*
* @see alternativa.engine3d.display.View
*/
public class Camera3D extends Object3D {
/**
* @private
* Расчёт матрицы пространства камеры
*/
alternativa3d var calculateMatrixOperation:Operation = new Operation("calculateMatrix", this, calculateMatrix, Operation.CAMERA_CALCULATE_MATRIX);
/**
* @private
* Расчёт плоскостей отсечения
*/
alternativa3d var calculatePlanesOperation:Operation = new Operation("calculatePlanes", this, calculatePlanes, Operation.CAMERA_CALCULATE_PLANES);
/**
* @private
* Отрисовка
*/
alternativa3d var renderOperation:Operation = new Operation("render", this, render, Operation.CAMERA_RENDER);
// Инкремент количества объектов
private static var counter:uint = 0;
/**
* @private
* Поле зрения
*/
alternativa3d var _fov:Number = Math.PI/2;
/**
* @private
* Фокусное расстояние
*/
alternativa3d var focalLength:Number;
/**
* @private
* Перспективное искажение
*/
alternativa3d var focalDistortion:Number;
/**
* @private
* Флаги рассчитанности UV-матриц
*/
alternativa3d var uvMatricesCalculated:Set = new Set(true);
// Всмомогательные точки для расчёта UV-матриц
private var textureA:Point3D = new Point3D();
private var textureB:Point3D = new Point3D();
private var textureC:Point3D = new Point3D();
/**
* @private
* Вид из камеры
*/
alternativa3d var _view:View;
/**
* @private
* Режим отрисовки
*/
alternativa3d var _orthographic:Boolean = false;
private var fullDraw:Boolean;
// Масштаб
private var _zoom:Number = 1;
// Синус половинчатого угла обзора камеры
private var viewAngle:Number;
// Направление камеры
private var direction:Point3D = new Point3D(0, 0, 1);
// Обратная трансформация камеры
private var cameraMatrix:Matrix3D = new Matrix3D();
// Скины
private var firstSkin:Skin;
private var prevSkin:Skin;
private var currentSkin:Skin;
// Плоскости отсечения
private var leftPlane:Point3D = new Point3D();
private var rightPlane:Point3D = new Point3D();
private var topPlane:Point3D = new Point3D();
private var bottomPlane:Point3D = new Point3D();
private var leftOffset:Number;
private var rightOffset:Number;
private var topOffset:Number;
private var bottomOffset:Number;
// Вспомогательные массивы точек для отрисовки
private var points1:Array = new Array();
private var points2:Array = new Array();
/**
* Создание нового экземпляра камеры.
*
* @param name имя экземпляра
*/
public function Camera3D(name:String = null) {
super(name);
}
/**
* @private
*/
private function calculateMatrix():void {
// Расчёт матрицы пространства камеры
cameraMatrix.copy(transformation);
cameraMatrix.invert();
if (_orthographic) {
cameraMatrix.scale(_zoom, _zoom, _zoom);
}
// Направление камеры
direction.x = transformation.c;
direction.y = transformation.g;
direction.z = transformation.k;
direction.normalize();
}
/**
* @private
* Расчёт плоскостей отсечения
*/
private function calculatePlanes():void {
var halfWidth:Number = _view._width*0.5;
var halfHeight:Number = _view._height*0.5;
var aw:Number = transformation.a*halfWidth;
var ew:Number = transformation.e*halfWidth;
var iw:Number = transformation.i*halfWidth;
var bh:Number = transformation.b*halfHeight;
var fh:Number = transformation.f*halfHeight;
var jh:Number = transformation.j*halfHeight;
if (_orthographic) {
// Расчёт плоскостей отсечения в изометрии
aw /= _zoom;
ew /= _zoom;
iw /= _zoom;
bh /= _zoom;
fh /= _zoom;
jh /= _zoom;
// Левая плоскость
leftPlane.x = transformation.f*transformation.k - transformation.j*transformation.g;
leftPlane.y = transformation.j*transformation.c - transformation.b*transformation.k;
leftPlane.z = transformation.b*transformation.g - transformation.f*transformation.c;
leftOffset = (transformation.d - aw)*leftPlane.x + (transformation.h - ew)*leftPlane.y + (transformation.l - iw)*leftPlane.z;
// Правая плоскость
rightPlane.x = -leftPlane.x;
rightPlane.y = -leftPlane.y;
rightPlane.z = -leftPlane.z;
rightOffset = (transformation.d + aw)*rightPlane.x + (transformation.h + ew)*rightPlane.y + (transformation.l + iw)*rightPlane.z;
// Верхняя плоскость
topPlane.x = transformation.g*transformation.i - transformation.k*transformation.e;
topPlane.y = transformation.k*transformation.a - transformation.c*transformation.i;
topPlane.z = transformation.c*transformation.e - transformation.g*transformation.a;
topOffset = (transformation.d - bh)*topPlane.x + (transformation.h - fh)*topPlane.y + (transformation.l - jh)*topPlane.z;
// Нижняя плоскость
bottomPlane.x = -topPlane.x;
bottomPlane.y = -topPlane.y;
bottomPlane.z = -topPlane.z;
bottomOffset = (transformation.d + bh)*bottomPlane.x + (transformation.h + fh)*bottomPlane.y + (transformation.l + jh)*bottomPlane.z;
} else {
// Вычисляем расстояние фокуса
focalLength = Math.sqrt(_view._width*_view._width + _view._height*_view._height)*0.5/Math.tan(0.5*_fov);
// Вычисляем минимальное (однопиксельное) искажение перспективной коррекции
focalDistortion = 1/(focalLength*focalLength);
// Расчёт плоскостей отсечения в перспективе
var cl:Number = transformation.c*focalLength;
var gl:Number = transformation.g*focalLength;
var kl:Number = transformation.k*focalLength;
// Угловые вектора пирамиды видимости
var leftTopX:Number = -aw - bh + cl;
var leftTopY:Number = -ew - fh + gl;
var leftTopZ:Number = -iw - jh + kl;
var rightTopX:Number = aw - bh + cl;
var rightTopY:Number = ew - fh + gl;
var rightTopZ:Number = iw - jh + kl;
var leftBottomX:Number = -aw + bh + cl;
var leftBottomY:Number = -ew + fh + gl;
var leftBottomZ:Number = -iw + jh + kl;
var rightBottomX:Number = aw + bh + cl;
var rightBottomY:Number = ew + fh + gl;
var rightBottomZ:Number = iw + jh + kl;
// Левая плоскость
leftPlane.x = leftBottomY*leftTopZ - leftBottomZ*leftTopY;
leftPlane.y = leftBottomZ*leftTopX - leftBottomX*leftTopZ;
leftPlane.z = leftBottomX*leftTopY - leftBottomY*leftTopX;
leftOffset = transformation.d*leftPlane.x + transformation.h*leftPlane.y + transformation.l*leftPlane.z;
// Правая плоскость
rightPlane.x = rightTopY*rightBottomZ - rightTopZ*rightBottomY;
rightPlane.y = rightTopZ*rightBottomX - rightTopX*rightBottomZ;
rightPlane.z = rightTopX*rightBottomY - rightTopY*rightBottomX;
rightOffset = transformation.d*rightPlane.x + transformation.h*rightPlane.y + transformation.l*rightPlane.z;
// Верхняя плоскость
topPlane.x = leftTopY*rightTopZ - leftTopZ*rightTopY;
topPlane.y = leftTopZ*rightTopX - leftTopX*rightTopZ;
topPlane.z = leftTopX*rightTopY - leftTopY*rightTopX;
topOffset = transformation.d*topPlane.x + transformation.h*topPlane.y + transformation.l*topPlane.z;
// Нижняя плоскость
bottomPlane.x = rightBottomY*leftBottomZ - rightBottomZ*leftBottomY;
bottomPlane.y = rightBottomZ*leftBottomX - rightBottomX*leftBottomZ;
bottomPlane.z = rightBottomX*leftBottomY - rightBottomY*leftBottomX;
bottomOffset = transformation.d*bottomPlane.x + transformation.h*bottomPlane.y + transformation.l*bottomPlane.z;
// Расчёт угла конуса
var length:Number = Math.sqrt(leftTopX*leftTopX + leftTopY*leftTopY + leftTopZ*leftTopZ);
leftTopX /= length;
leftTopY /= length;
leftTopZ /= length;
length = Math.sqrt(rightTopX*rightTopX + rightTopY*rightTopY + rightTopZ*rightTopZ);
rightTopX /= length;
rightTopY /= length;
rightTopZ /= length;
length = Math.sqrt(leftBottomX*leftBottomX + leftBottomY*leftBottomY + leftBottomZ*leftBottomZ);
leftBottomX /= length;
leftBottomY /= length;
leftBottomZ /= length;
length = Math.sqrt(rightBottomX*rightBottomX + rightBottomY*rightBottomY + rightBottomZ*rightBottomZ);
rightBottomX /= length;
rightBottomY /= length;
rightBottomZ /= length;
viewAngle = leftTopX*direction.x + leftTopY*direction.y + leftTopZ*direction.z;
var dot:Number = rightTopX*direction.x + rightTopY*direction.y + rightTopZ*direction.z;
viewAngle = (dot < viewAngle) ? dot : viewAngle;
dot = leftBottomX*direction.x + leftBottomY*direction.y + leftBottomZ*direction.z;
viewAngle = (dot < viewAngle) ? dot : viewAngle;
dot = rightBottomX*direction.x + rightBottomY*direction.y + rightBottomZ*direction.z;
viewAngle = (dot < viewAngle) ? dot : viewAngle;
viewAngle = Math.sin(Math.acos(viewAngle));
}
}
/**
* @private
*/
private function render():void {
// Режим отрисовки
fullDraw = (calculateMatrixOperation.queued || calculatePlanesOperation.queued);
// Очистка рассчитанных текстурных матриц
uvMatricesCalculated.clear();
// Отрисовка
prevSkin = null;
currentSkin = firstSkin;
renderBSPNode(_scene.bsp);
// Удаление ненужных скинов
while (currentSkin != null) {
removeCurrentSkin();
}
}
/**
* @private
*/
private function renderBSPNode(node:BSPNode):void {
if (node != null) {
var primitive:*;
var normal:Point3D = node.normal;
var cameraAngle:Number = direction.x*normal.x + direction.y*normal.y + direction.z*normal.z;
var cameraOffset:Number;
if (!_orthographic) {
cameraOffset = globalCoords.x*normal.x + globalCoords.y*normal.y + globalCoords.z*normal.z - node.offset;
}
if (node.primitive != null) {
// В ноде только базовый примитив
if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) {
// Камера спереди ноды
if (_orthographic || cameraAngle < viewAngle) {
renderBSPNode(node.back);
drawSkin(node.primitive);
}
renderBSPNode(node.front);
} else {
// Камера сзади ноды
if (_orthographic || cameraAngle > -viewAngle) {
renderBSPNode(node.front);
}
renderBSPNode(node.back);
}
} else {
// В ноде несколько примитивов
if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) {
// Камера спереди ноды
if (_orthographic || cameraAngle < viewAngle) {
renderBSPNode(node.back);
for (primitive in node.frontPrimitives) {
drawSkin(primitive);
}
}
renderBSPNode(node.front);
} else {
// Камера сзади ноды
if (_orthographic || cameraAngle > -viewAngle) {
renderBSPNode(node.front);
for (primitive in node.backPrimitives) {
drawSkin(primitive);
}
}
renderBSPNode(node.back);
}
}
}
}
/**
* @private
* Отрисовка скина примитива
*/
private function drawSkin(primitive:PolyPrimitive):void {
if (!fullDraw && currentSkin != null && currentSkin.primitive == primitive && !_scene.changedPrimitives[primitive]) {
// Пропуск скина
prevSkin = currentSkin;
currentSkin = currentSkin.nextSkin;
} else {
// Проверка поверхности
var surface:Surface = primitive.face._surface;
if (surface == null) {
return;
}
// Проверка материала
var material:SurfaceMaterial = surface._material;
if (material == null || !material.canDraw(primitive)) {
return;
}
// Отсечение выходящих за окно просмотра частей
var i:uint;
var length:uint = primitive.num;
var primitivePoint:Point3D;
var primitiveUV:Point;
var point:DrawPoint;
var useUV:Boolean = !_orthographic && material.useUV && primitive.face.uvMatrixBase;
if (useUV) {
// Формируем список точек и UV-координат полигона
for (i = 0; i < length; i++) {
primitivePoint = primitive.points[i];
primitiveUV = primitive.uvs[i];
point = points1[i];
if (point == null) {
points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z, primitiveUV.x, primitiveUV.y);
} else {
point.x = primitivePoint.x;
point.y = primitivePoint.y;
point.z = primitivePoint.z;
point.u = primitiveUV.x;
point.v = primitiveUV.y;
}
}
} else {
// Формируем список точек полигона
for (i = 0; i < length; i++) {
primitivePoint = primitive.points[i];
point = points1[i];
if (point == null) {
points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z);
} else {
point.x = primitivePoint.x;
point.y = primitivePoint.y;
point.z = primitivePoint.z;
}
}
}
// Отсечение по левой стороне
length = clip(length, points1, points2, leftPlane, leftOffset, useUV);
if (length < 3) {
return;
}
// Отсечение по правой стороне
length = clip(length, points2, points1, rightPlane, rightOffset, useUV);
if (length < 3) {
return;
}
// Отсечение по верхней стороне
length = clip(length, points1, points2, topPlane, topOffset, useUV);
if (length < 3) {
return;
}
// Отсечение по нижней стороне
length = clip(length, points2, points1, bottomPlane, bottomOffset, useUV);
if (length < 3) {
return;
}
if (fullDraw || _scene.changedPrimitives[primitive]) {
// Если конец списка скинов
if (currentSkin == null) {
// Добавляем скин в конец
addCurrentSkin();
} else {
if (fullDraw || _scene.changedPrimitives[currentSkin.primitive]) {
// Очистка скина
currentSkin.material.clear(currentSkin);
} else {
// Вставка скина перед текущим
insertCurrentSkin();
}
}
// Переводим координаты в систему камеры
var x:Number;
var y:Number;
var z:Number;
for (i = 0; i < length; i++) {
point = points1[i];
x = point.x;
y = point.y;
z = point.z;
point.x = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d;
point.y = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h;
point.z = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l;
}
// Назначаем скину примитив и материал
currentSkin.primitive = primitive;
currentSkin.material = material;
material.draw(this, currentSkin, length, points1);
// Переключаемся на следующий скин
prevSkin = currentSkin;
currentSkin = currentSkin.nextSkin;
} else {
// Удаление ненужных скинов
while (currentSkin != null && _scene.changedPrimitives[currentSkin.primitive]) {
removeCurrentSkin();
}
// Переключение на следующий скин
if (currentSkin != null) {
prevSkin = currentSkin;
currentSkin = currentSkin.nextSkin;
}
}
}
}
/**
* @private
* Отсечение полигона плоскостью.
*/
private function clip(length:uint, points1:Array, points2:Array, plane:Point3D, offset:Number, calculateUV:Boolean):uint {
var i:uint;
var k:Number;
var index:uint = 0;
var point:DrawPoint;
var point1:DrawPoint;
var point2:DrawPoint;
var offset1:Number;
var offset2:Number;
point1 = points1[length - 1];
offset1 = plane.x*point1.x + plane.y*point1.y + plane.z*point1.z - offset;
if (calculateUV) {
for (i = 0; i < length; i++) {
point2 = points1[i];
offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset;
if (offset2 > 0) {
if (offset1 <= 0) {
k = offset2/(offset2 - offset1);
point = points2[index];
if (point == null) {
point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k);
points2[index] = point;
} else {
point.x = point2.x - (point2.x - point1.x)*k;
point.y = point2.y - (point2.y - point1.y)*k;
point.z = point2.z - (point2.z - point1.z)*k;
point.u = point2.u - (point2.u - point1.u)*k;
point.v = point2.v - (point2.v - point1.v)*k;
}
index++;
}
point = points2[index];
if (point == null) {
point = new DrawPoint(point2.x, point2.y, point2.z, point2.u, point2.v);
points2[index] = point;
} else {
point.x = point2.x;
point.y = point2.y;
point.z = point2.z;
point.u = point2.u;
point.v = point2.v;
}
index++;
} else {
if (offset1 > 0) {
k = offset2/(offset2 - offset1);
point = points2[index];
if (point == null) {
point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k);
points2[index] = point;
} else {
point.x = point2.x - (point2.x - point1.x)*k;
point.y = point2.y - (point2.y - point1.y)*k;
point.z = point2.z - (point2.z - point1.z)*k;
point.u = point2.u - (point2.u - point1.u)*k;
point.v = point2.v - (point2.v - point1.v)*k;
}
index++;
}
}
offset1 = offset2;
point1 = point2;
}
} else {
for (i = 0; i < length; i++) {
point2 = points1[i];
offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset;
if (offset2 > 0) {
if (offset1 <= 0) {
k = offset2/(offset2 - offset1);
point = points2[index];
if (point == null) {
point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k);
points2[index] = point;
} else {
point.x = point2.x - (point2.x - point1.x)*k;
point.y = point2.y - (point2.y - point1.y)*k;
point.z = point2.z - (point2.z - point1.z)*k;
}
index++;
}
point = points2[index];
if (point == null) {
point = new DrawPoint(point2.x, point2.y, point2.z);
points2[index] = point;
} else {
point.x = point2.x;
point.y = point2.y;
point.z = point2.z;
}
index++;
} else {
if (offset1 > 0) {
k = offset2/(offset2 - offset1);
point = points2[index];
if (point == null) {
point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k);
points2[index] = point;
} else {
point.x = point2.x - (point2.x - point1.x)*k;
point.y = point2.y - (point2.y - point1.y)*k;
point.z = point2.z - (point2.z - point1.z)*k;
}
index++;
}
}
offset1 = offset2;
point1 = point2;
}
}
return index;
}
/**
* @private
* Добавление текущего скина.
*/
private function addCurrentSkin():void {
currentSkin = Skin.createSkin();
_view.canvas.addChild(currentSkin);
if (prevSkin == null) {
firstSkin = currentSkin;
} else {
prevSkin.nextSkin = currentSkin;
}
}
/**
* @private
* Вставляем под текущий скин.
*/
private function insertCurrentSkin():void {
var skin:Skin = Skin.createSkin();
_view.canvas.addChildAt(skin, _view.canvas.getChildIndex(currentSkin));
skin.nextSkin = currentSkin;
if (prevSkin == null) {
firstSkin = skin;
} else {
prevSkin.nextSkin = skin;
}
currentSkin = skin;
}
/**
* @private
* Удаляет текущий скин.
*/
private function removeCurrentSkin():void {
// Сохраняем следующий
var next:Skin = currentSkin.nextSkin;
// Удаляем из канваса
_view.canvas.removeChild(currentSkin);
// Очистка скина
if (currentSkin.material != null) {
currentSkin.material.clear(currentSkin);
}
// Зачищаем ссылки
currentSkin.nextSkin = null;
currentSkin.primitive = null;
currentSkin.material = null;
// Удаляем
Skin.destroySkin(currentSkin);
// Следующий устанавливаем текущим
currentSkin = next;
// Устанавливаем связь от предыдущего скина
if (prevSkin == null) {
firstSkin = currentSkin;
} else {
prevSkin.nextSkin = currentSkin;
}
}
/**
* @private
*/
alternativa3d function calculateUVMatrix(face:Face, width:uint, height:uint):void {
// Расчёт точек базового примитива в координатах камеры
var point:Point3D = face.primitive.points[0];
textureA.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
textureA.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
point = face.primitive.points[1];
textureB.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
textureB.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
point = face.primitive.points[2];
textureC.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
textureC.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
// Находим AB и AC
var abx:Number = textureB.x - textureA.x;
var aby:Number = textureB.y - textureA.y;
var acx:Number = textureC.x - textureA.x;
var acy:Number = textureC.y - textureA.y;
// Расчёт текстурной матрицы
var uvMatrixBase:Matrix = face.uvMatrixBase;
var uvMatrix:Matrix = face.uvMatrix;
uvMatrix.a = (uvMatrixBase.a*abx + uvMatrixBase.b*acx)/width;
uvMatrix.b = (uvMatrixBase.a*aby + uvMatrixBase.b*acy)/width;
uvMatrix.c = -(uvMatrixBase.c*abx + uvMatrixBase.d*acx)/height;
uvMatrix.d = -(uvMatrixBase.c*aby + uvMatrixBase.d*acy)/height;
uvMatrix.tx = (uvMatrixBase.tx + uvMatrixBase.c)*abx + (uvMatrixBase.ty + uvMatrixBase.d)*acx + textureA.x + cameraMatrix.d;
uvMatrix.ty = (uvMatrixBase.tx + uvMatrixBase.c)*aby + (uvMatrixBase.ty + uvMatrixBase.d)*acy + textureA.y + cameraMatrix.h;
// Помечаем, как рассчитанную
uvMatricesCalculated[face] = true;
}
/**
* Поле вывода, в котором происходит отрисовка камеры.
*/
public function get view():View {
return _view;
}
/**
* @private
*/
public function set view(value:View):void {
if (value != _view) {
if (_view != null) {
_view.camera = null;
}
if (value != null) {
value.camera = this;
}
}
}
/**
* Включение режима аксонометрической проекции.
*
* @default false
*/
public function get orthographic():Boolean {
return _orthographic;
}
/**
* @private
*/
public function set orthographic(value:Boolean):void {
if (_orthographic != value) {
// Отправляем сигнал об изменении типа камеры
addOperationToScene(calculateMatrixOperation);
// Сохраняем новое значение
_orthographic = value;
}
}
/**
* Угол поля зрения в радианах в режиме перспективной проекции. При изменении FOV изменяется фокусное расстояние
* камеры по формуле <code>f = d/tan(fov/2)</code>, где <code>d</code> является половиной диагонали поля вывода.
* Угол зрения ограничен диапазоном 0-180 градусов.
*/
public function get fov():Number {
return _fov;
}
/**
* @private
*/
public function set fov(value:Number):void {
value = (value < 0) ? 0 : ((value > (Math.PI - 0.0001)) ? (Math.PI - 0.0001) : value);
if (_fov != value) {
// Если перспектива
if (!_orthographic) {
// Отправляем сигнал об изменении плоскостей отсечения
addOperationToScene(calculatePlanesOperation);
}
// Сохраняем новое значение
_fov = value;
}
}
/**
* Коэффициент увеличения изображения в режиме аксонометрической проекции.
*/
public function get zoom():Number {
return _zoom;
}
/**
* @private
*/
public function set zoom(value:Number):void {
value = (value < 0) ? 0 : value;
if (_zoom != value) {
// Если изометрия
if (_orthographic) {
// Отправляем сигнал об изменении zoom
addOperationToScene(calculateMatrixOperation);
}
// Сохраняем новое значение
_zoom = value;
}
}
/**
* @inheritDoc
*/
override protected function addToScene(scene:Scene3D):void {
super.addToScene(scene);
if (_view != null) {
// Отправляем операцию расчёта плоскостей отсечения
scene.addOperation(calculatePlanesOperation);
// Подписываемся на сигналы сцены
scene.changePrimitivesOperation.addSequel(renderOperation);
}
}
/**
* @inheritDoc
*/
override protected function removeFromScene(scene:Scene3D):void {
super.removeFromScene(scene);
// Удаляем все операции из очереди
scene.removeOperation(calculateMatrixOperation);
scene.removeOperation(calculatePlanesOperation);
scene.removeOperation(renderOperation);
if (_view != null) {
// Отписываемся от сигналов сцены
scene.changePrimitivesOperation.removeSequel(renderOperation);
}
}
/**
* @private
*/
alternativa3d function addToView(view:View):void {
// Сохраняем первый скин
firstSkin = (view.canvas.numChildren > 0) ? Skin(view.canvas.getChildAt(0)) : null;
// Подписка на свои операции
// При изменении камеры пересчёт матрицы
calculateTransformationOperation.addSequel(calculateMatrixOperation);
// При изменении матрицы или FOV пересчёт плоскостей отсечения
calculateMatrixOperation.addSequel(calculatePlanesOperation);
// При изменении плоскостей перерисовка
calculatePlanesOperation.addSequel(renderOperation);
if (_scene != null) {
// Отправляем сигнал перерисовки
_scene.addOperation(calculateMatrixOperation);
// Подписываемся на сигналы сцены
_scene.changePrimitivesOperation.addSequel(renderOperation);
}
// Сохраняем вид
_view = view;
}
/**
* @private
*/
alternativa3d function removeFromView(view:View):void {
// Сброс ссылки на первый скин
firstSkin = null;
// Отписка от своих операций
// При изменении камеры пересчёт матрицы
calculateTransformationOperation.removeSequel(calculateMatrixOperation);
// При изменении матрицы или FOV пересчёт плоскостей отсечения
calculateMatrixOperation.removeSequel(calculatePlanesOperation);
// При изменении плоскостей перерисовка
calculatePlanesOperation.removeSequel(renderOperation);
if (_scene != null) {
// Удаляем все операции из очереди
_scene.removeOperation(calculateMatrixOperation);
_scene.removeOperation(calculatePlanesOperation);
_scene.removeOperation(renderOperation);
// Отписываемся от сигналов сцены
_scene.changePrimitivesOperation.removeSequel(renderOperation);
}
// Удаляем ссылку на вид
_view = null;
}
/**
* @inheritDoc
*/
override protected function defaultName():String {
return "camera" + ++counter;
}
/**
* @inheritDoc
*/
protected override function createEmptyObject():Object3D {
return new Camera3D();
}
/**
* @inheritDoc
*/
protected override function clonePropertiesFrom(source:Object3D):void {
super.clonePropertiesFrom(source);
var src:Camera3D = Camera3D(source);
orthographic = src._orthographic;
zoom = src._zoom;
fov = src._fov;
}
}
}

View File

@@ -0,0 +1,921 @@
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 массив объектов типа <code>alternativa.engine3d.core.Vertex</code>, задающий вершины грани в
* порядке обхода лицевой стороны грани против часовой стрелки.
*
* @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);
}
}
/**
* Массив вершин грани, представленных объектами класса <code>alternativa.engine3d.core.Vertex</code>.
*
* @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;
}
/**
* Идентификатор грани в полигональном объекте. В случае, если грань не принадлежит ни одному объекту, идентификатор
* имеет значение <code>null</code>.
*/
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;
}
}
}

View File

@@ -0,0 +1,985 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
import alternativa.engine3d.errors.FaceExistsError;
import alternativa.engine3d.errors.FaceNeedMoreVerticesError;
import alternativa.engine3d.errors.FaceNotFoundError;
import alternativa.engine3d.errors.InvalidIDError;
import alternativa.engine3d.errors.SurfaceExistsError;
import alternativa.engine3d.errors.SurfaceNotFoundError;
import alternativa.engine3d.errors.VertexExistsError;
import alternativa.engine3d.errors.VertexNotFoundError;
import alternativa.engine3d.materials.SurfaceMaterial;
import alternativa.types.Map;
import alternativa.utils.ObjectUtils;
import flash.geom.Point;
use namespace alternativa3d;
/**
* Полигональный объект &mdash; базовый класс для трёхмерных объектов, состоящих из граней-полигонов. Объект
* содержит в себе наборы вершин, граней и поверхностей.
*/
public class Mesh extends Object3D {
// Инкремент количества объектов
private static var counter:uint = 0;
// Инкременты для идентификаторов вершин, граней и поверхностей
private var vertexIDCounter:uint = 0;
private var faceIDCounter:uint = 0;
private var surfaceIDCounter:uint = 0;
/**
* @private
* Список вершин
*/
alternativa3d var _vertices:Map = new Map();
/**
* @private
* Список граней
*/
alternativa3d var _faces:Map = new Map();
/**
* @private
* Список поверхностей
*/
alternativa3d var _surfaces:Map = new Map();
/**
* Создание экземпляра полигонального объекта.
*
* @param name имя экземпляра
*/
public function Mesh(name:String = null) {
super(name);
}
/**
* Добавление новой вершины к объекту.
*
* @param x координата X в локальной системе координат объекта
* @param y координата Y в локальной системе координат объекта
* @param z координата Z в локальной системе координат объекта
* @param id идентификатор вершины. Если указано значение <code>null</code>, идентификатор будет
* сформирован автоматически.
*
* @return экземпляр добавленной вершины
*
* @throws alternativa.engine3d.errors.VertexExistsError объект уже содержит вершину с указанным идентификатором
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function createVertex(x:Number = 0, y:Number = 0, z:Number = 0, id:Object = null):Vertex {
// Проверяем ID
if (id != null) {
// Если уже есть вершина с таким ID
if (_vertices[id] != undefined) {
if (_vertices[id] is Vertex) {
throw new VertexExistsError(id, this);
} else {
throw new InvalidIDError(id, this);
}
}
} else {
// Ищем первый свободный
while (_vertices[vertexIDCounter] != undefined) {
vertexIDCounter++;
}
id = vertexIDCounter;
}
// Создаём вершину
var v:Vertex = new Vertex(x, y, z);
// Добавляем вершину на сцену
if (_scene != null) {
v.addToScene(_scene);
}
// Добавляем вершину в меш
v.addToMesh(this);
_vertices[id] = v;
return v;
}
/**
* Удаление вершины из объекта. При удалении вершины из объекта также удаляются все грани, которым принадлежит данная вершина.
*
* @param vertex экземпляр класса <code>alternativa.engine3d.core.Vertex</code> или идентификатор удаляемой вершины
*
* @return экземпляр удалённой вершины
*
* @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function removeVertex(vertex:Object):Vertex {
var byLink:Boolean = vertex is Vertex;
// Проверяем на null
if (vertex == null) {
throw new VertexNotFoundError(null, this);
}
// Проверяем наличие вершины в меше
if (byLink) {
// Если удаляем по ссылке
if (Vertex(vertex)._mesh != this) {
// Если вершина не в меше
throw new VertexNotFoundError(vertex, this);
}
} else {
// Если удаляем по ID
if (_vertices[vertex] == undefined) {
// Если нет вершины с таким ID
throw new VertexNotFoundError(vertex, this);
} else if (!(_vertices[vertex] is Vertex)) {
// По этому id не вершина
throw new InvalidIDError(vertex, this);
}
}
// Находим вершину и её ID
var v:Vertex = byLink ? Vertex(vertex) : _vertices[vertex];
var id:Object = byLink ? getVertexId(Vertex(vertex)) : vertex;
// Удаляем вершину из сцены
if (_scene != null) {
v.removeFromScene(_scene);
}
// Удаляем вершину из меша
v.removeFromMesh(this);
delete _vertices[id];
return v;
}
/**
* Добавление грани к объекту. В результате выполнения метода в объекте появляется новая грань, не привязанная
* ни к одной поверхности.
*
* @param vertices массив вершин грани, указанных в порядке обхода лицевой стороны грани против часовой
* стрелки. Каждый элемент массива может быть либо экземпляром класса <code>alternativa.engine3d.core.Vertex</code>,
* либо идентификатором в наборе вершин объекта. В обоих случаях объект должен содержать указанную вершину.
* @param id идентификатор грани. Если указано значение <code>null</code>, идентификатор будет
* сформирован автоматически.
*
* @return экземпляр добавленной грани
*
* @throws alternativa.engine3d.errors.FaceNeedMoreVerticesError в качестве массива вершин был передан
* <code>null</code>, либо количество вершин в массиве меньше трёх
* @throws alternativa.engine3d.errors.FaceExistsError объект уже содержит грань с заданным идентификатором
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
* @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит какую-либо вершину из входного массива
*
* @see Vertex
*/
public function createFace(vertices:Array, id:Object = null):Face {
// Проверяем на null
if (vertices == null) {
throw new FaceNeedMoreVerticesError(this);
}
// Проверяем ID
if (id != null) {
// Если уже есть грань с таким ID
if (_faces[id] != undefined) {
if (_faces[id] is Face) {
throw new FaceExistsError(id, this);
} else {
throw new InvalidIDError(id, this);
}
}
} else {
// Ищем первый свободный ID
while (_faces[faceIDCounter] != undefined) {
faceIDCounter++;
}
id = faceIDCounter;
}
// Проверяем количество точек
var length:uint = vertices.length;
if (length < 3) {
throw new FaceNeedMoreVerticesError(this, length);
}
// Проверяем и формируем список вершин
var v:Array = new Array();
var vertex:Vertex;
for (var i:uint = 0; i < length; i++) {
if (vertices[i] is Vertex) {
// Если работаем со ссылками
vertex = vertices[i];
if (vertex._mesh != this) {
// Если вершина не в меше
throw new VertexNotFoundError(vertices[i], this);
}
} else {
// Если работаем с ID
if (_vertices[vertices[i]] == null) {
// Если нет вершины с таким ID
throw new VertexNotFoundError(vertices[i], this);
} else if (!(_vertices[vertices[i]] is Vertex)) {
// Если id зарезервировано
throw new InvalidIDError(vertices[i],this);
}
vertex = _vertices[vertices[i]];
}
v.push(vertex);
}
// Создаём грань
var f:Face = new Face(v);
// Добавляем грань на сцену
if (_scene != null) {
f.addToScene(_scene);
}
// Добавляем грань в меш
f.addToMesh(this);
_faces[id] = f;
return f;
}
/**
* Удаление грани из объекта. Грань также удаляется из поверхности объекта, которой она принадлежит.
*
* @param экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор удаляемой грани
*
* @return экземпляр удалённой грани
*
* @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function removeFace(face:Object):Face {
var byLink:Boolean = face is Face;
// Проверяем на null
if (face == null) {
throw new FaceNotFoundError(null, this);
}
// Проверяем наличие грани в меше
if (byLink) {
// Если удаляем по ссылке
if (Face(face)._mesh != this) {
// Если грань не в меше
throw new FaceNotFoundError(face, this);
}
} else {
// Если удаляем по ID
if (_faces[face] == undefined) {
// Если нет грани с таким ID
throw new FaceNotFoundError(face, this);
} else if (!(_faces[face] is Face)) {
throw new InvalidIDError(face, this);
}
}
// Находим грань и её ID
var f:Face = byLink ? Face(face) : _faces[face] ;
var id:Object = byLink ? getFaceId(Face(face)) : face;
// Удаляем грань из сцены
if (_scene != null) {
f.removeFromScene(_scene);
}
// Удаляем вершины из грани
f.removeVertices();
// Удаляем грань из поверхности
if (f._surface != null) {
f._surface._faces.remove(f);
f.removeFromSurface(f._surface);
}
// Удаляем грань из меша
f.removeFromMesh(this);
delete _faces[id];
return f;
}
/**
* Добавление новой поверхности к объекту.
*
* @param faces набор граней, составляющих поверхность. Каждый элемент массива должен быть либо экземпляром класса
* <code>alternativa.engine3d.core.Face</code>, либо идентификатором грани. В обоих случаях объект должен содержать
* указанную грань. Если значение параметра равно <code>null</code>, то будет создана пустая поверхность. Если
* какая-либо грань содержится в другой поверхности, она будет перенесена в новую поверхность.
* @param id идентификатор новой поверхности. Если указано значение <code>null</code>, идентификатор будет
* сформирован автоматически.
*
* @return экземпляр добавленной поверхности
*
* @throws alternativa.engine3d.errors.SurfaceExistsError объект уже содержит поверхность с заданным идентификатором
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*
* @see Face
*/
public function createSurface(faces:Array = null, id:Object = null):Surface {
// Проверяем ID
if (id != null) {
// Если уже есть поверхность с таким ID
if (_surfaces[id] != undefined) {
if (_surfaces[id] is Surface) {
throw new SurfaceExistsError(id, this);
} else {
throw new InvalidIDError(id, this);
}
}
} else {
// Ищем первый свободный ID
while (_surfaces[surfaceIDCounter] != undefined) {
surfaceIDCounter++;
}
id = surfaceIDCounter;
}
// Создаём поверхность
var s:Surface = new Surface();
// Добавляем поверхность на сцену
if (_scene != null) {
s.addToScene(_scene);
}
// Добавляем поверхность в меш
s.addToMesh(this);
_surfaces[id] = s;
// Добавляем грани, если есть
if (faces != null) {
var length:uint = faces.length;
for (var i:uint = 0; i < length; i++) {
s.addFace(faces[i]);
}
}
return s;
}
/**
* Удаление поверхности объекта. Из удаляемой поверхности также удаляются все содержащиеся в ней грани.
*
* @param surface экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор удаляемой поверхности
*
* @return экземпляр удалённой поверхности
*
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function removeSurface(surface:Object):Surface {
var byLink:Boolean = surface is Surface;
// Проверяем на null
if (surface == null) {
throw new SurfaceNotFoundError(null, this);
}
// Проверяем наличие поверхности в меше
if (byLink) {
// Если удаляем по ссылке
if (Surface(surface)._mesh != this) {
// Если поверхность не в меше
throw new SurfaceNotFoundError(surface, this);
}
} else {
// Если удаляем по ID
if (_surfaces[surface] == undefined) {
// Если нет поверхности с таким ID
throw new SurfaceNotFoundError(surface, this);
} else if (!(_surfaces[surface] is Surface)) {
throw new InvalidIDError(surface, this);
}
}
// Находим поверхность и её ID
var s:Surface = byLink ? Surface(surface) : _surfaces[surface];
var id:Object = byLink ? getSurfaceId(Surface(surface)) : surface;
// Удаляем поверхность из сцены
if (_scene != null) {
s.removeFromScene(_scene);
}
// Удаляем грани из поверхности
s.removeFaces();
// Удаляем поверхность из меша
s.removeFromMesh(this);
delete _surfaces[id];
return s;
}
/**
* Добавление всех граней объекта в указанную поверхность.
*
* @param surface экземпляр класса <code>alternativa.engine3d.core.Surface</code> или идентификатор поверхности, в
* которую добавляются грани. Если задан идентификатор, и объект не содержит поверхность с таким идентификатором,
* будет создана новая поверхность.
*
* @param removeSurfaces удалять или нет пустые поверхности после переноса граней
*
* @return экземпляр поверхности, в которую перенесены грани
*
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанный экземпляр поверхности
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function moveAllFacesToSurface(surface:Object = null, removeSurfaces:Boolean = false):Surface {
var returnSurface:Surface;
var returnSurfaceId:Object;
if (surface is Surface) {
// Работаем с экземпляром Surface
if (surface._mesh == this) {
returnSurface = Surface(surface);
} else {
throw new SurfaceNotFoundError(surface, this);
}
} else {
// Работаем с идентификатором
if (_surfaces[surface] == undefined) {
// Поверхности еще нет
returnSurface = createSurface(null, surface);
returnSurfaceId = surface;
} else {
if (_surfaces[surface] is Surface) {
returnSurface = _surfaces[surface];
} else {
// _surfaces[surface] по идентификатору возвращает не Surface
throw new InvalidIDError(surface, this);
}
}
}
// Перемещаем все грани
for each (var face:Face in _faces) {
if (face._surface != returnSurface) {
returnSurface.addFace(face);
}
}
if (removeSurfaces) {
// Удаляем старые, теперь вручную - меньше проверок, но рискованно
if (returnSurfaceId == null) {
returnSurfaceId = getSurfaceId(returnSurface);
}
var newSurfaces:Map = new Map();
newSurfaces[returnSurfaceId] = returnSurface;
delete _surfaces[returnSurfaceId];
// Удаляем оставшиеся
for (var currentSurfaceId:* in _surfaces) {
// Удаляем поверхность из сцены
var currentSurface:Surface = _surfaces[currentSurfaceId];
if (_scene != null) {
currentSurface.removeFromScene(_scene);
}
// Удаляем поверхность из меша
currentSurface.removeFromMesh(this);
delete _surfaces[currentSurfaceId];
}
// Новый список граней
_surfaces = newSurfaces;
}
return returnSurface;
}
/**
* Установка материала для указанной поверхности.
*
* @param material материал, назначаемый поверхности. Один экземпляр SurfaceMaterial можно назначить только одной поверхности.
* @param surface экземпляр класса <code>alternativa.engine3d.core.Surface</code> или идентификатор поверхности
*
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*
* @see Surface
*/
public function setMaterialToSurface(material:SurfaceMaterial, surface:Object):void {
var byLink:Boolean = surface is Surface;
// Проверяем на null
if (surface == null) {
throw new SurfaceNotFoundError(null, this);
}
// Проверяем наличие поверхности в меше
if (byLink) {
// Если назначаем по ссылке
if (Surface(surface)._mesh != this) {
// Если поверхность не в меше
throw new SurfaceNotFoundError(surface, this);
}
} else {
// Если назначаем по ID
if (_surfaces[surface] == undefined) {
// Если нет поверхности с таким ID
throw new SurfaceNotFoundError(surface, this);
} else if (!(_surfaces[surface] is Surface)) {
throw new InvalidIDError(surface, this);
}
}
// Находим поверхность
var s:Surface = byLink ? Surface(surface) : _surfaces[surface];
// Назначаем материал
s.material = material;
}
/**
* Установка материала для всех поверхностей объекта. Для каждой поверхности устанавливается копия материала.
* При передаче <code>null</code> в качестве параметра происходит сброс материалов у всех поверхностей.
*
* @param material устанавливаемый материал
*/
public function cloneMaterialToAllSurfaces(material:SurfaceMaterial):void {
for each (var surface:Surface in _surfaces) {
surface.material = (material != null) ? SurfaceMaterial(material.clone()) : null;
}
}
/**
* Установка UV-координат для указанной грани объекта. Матрица преобразования UV-координат расчитывается по
* UV-координатам первых трёх вершин грани, поэтому для корректного текстурирования эти вершины должны образовывать
* невырожденный треугольник в UV-пространстве.
*
* @param aUV UV-координаты, соответствующие первой вершине грани
* @param bUV UV-координаты, соответствующие второй вершине грани
* @param cUV UV-координаты, соответствующие третьей вершине грани
* @param face экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор грани
*
* @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*
* @see Face
*/
public function setUVsToFace(aUV:Point, bUV:Point, cUV:Point, face:Object):void {
var byLink:Boolean = face is Face;
// Проверяем на null
if (face == null) {
throw new FaceNotFoundError(null, this);
}
// Проверяем наличие грани в меше
if (byLink) {
// Если назначаем по ссылке
if (Face(face)._mesh != this) {
// Если грань не в меше
throw new FaceNotFoundError(face, this);
}
} else {
// Если назначаем по ID
if (_faces[face] == undefined) {
// Если нет грани с таким ID
throw new FaceNotFoundError(face, this);
} else if (!(_faces[face] is Face)) {
throw new InvalidIDError(face, this);
}
}
// Находим грань
var f:Face = byLink ? Face(face) : _faces[face];
// Назначаем UV-координаты
f.aUV = aUV;
f.bUV = bUV;
f.cUV = cUV;
}
/**
* Набор вершин объекта. Ключами ассоциативного массива являются идентификаторы вершин, значениями - экземпляры вершин.
*/
public function get vertices():Map {
return _vertices.clone();
}
/**
* Набор граней объекта. Ключами ассоциативного массива являются идентификаторы граней, значениями - экземпляры граней.
*/
public function get faces():Map {
return _faces.clone();
}
/**
* Набор поверхностей объекта. Ключами ассоциативного массива являются идентификаторы поверхностей, значениями - экземпляры поверхностей.
*/
public function get surfaces():Map {
return _surfaces.clone();
}
/**
* Получение вершины объекта по её идентификатору.
*
* @param id идентификатор вершины
*
* @return экземпляр вершины с указанным идентификатором
*
* @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит вершину с указанным идентификатором
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function getVertexById(id:Object):Vertex {
if (id == null) {
throw new VertexNotFoundError(null, this);
}
if (_vertices[id] == undefined) {
// Если нет вершины с таким ID
throw new VertexNotFoundError(id, this);
} else {
if (_vertices[id] is Vertex) {
return _vertices[id];
} else {
throw new InvalidIDError(id, this);
}
}
}
/**
* Получение идентификатора вершины объекта.
*
* @param экземпляр вершины
*
* @return идентификатор указанной вершины
*
* @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину
*/
public function getVertexId(vertex:Vertex):Object {
if (vertex == null) {
throw new VertexNotFoundError(null, this);
}
if (vertex._mesh != this) {
// Если вершина не в меше
throw new VertexNotFoundError(vertex, this);
}
for (var i:Object in _vertices) {
if (_vertices[i] == vertex) {
return i;
}
}
throw new VertexNotFoundError(vertex, this);
}
/**
* Проверка наличия вершины в объекте.
*
* @param vertex экземпляр класса <code>alternativa.engine3d.core.Vertex</code> или идентификатор вершины
*
* @return <code>true</code>, если объект содержит указанную вершину, иначе <code>false</code>
*
* @throws alternativa.engine3d.errors.VertexNotFoundError в качестве vertex был передан null
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*
* @see Vertex
*/
public function hasVertex(vertex:Object):Boolean {
if (vertex == null) {
throw new VertexNotFoundError(null, this);
}
if (vertex is Vertex) {
// Проверка вершины
return vertex._mesh == this;
} else {
// Проверка ID вершины
if (_vertices[vertex] != undefined) {
// По этому ID есть объект
if (_vertices[vertex] is Vertex) {
// Объект является вершиной
return true;
} else {
// ID некорректный
throw new InvalidIDError(vertex, this);
}
} else {
return false;
}
}
}
/**
* Получение грани объекта по ее идентификатору.
*
* @param id идентификатор грани
*
* @return экземпляр грани с указанным идентификатором
*
* @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит грань с указанным идентификатором
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function getFaceById(id:Object):Face {
if (id == null) {
throw new FaceNotFoundError(null, this);
}
if (_faces[id] == undefined) {
// Если нет грани с таким ID
throw new FaceNotFoundError(id, this);
} else {
if (_faces[id] is Face) {
return _faces[id];
} else {
throw new InvalidIDError(id, this);
}
}
}
/**
* Получение идентификатора грани объекта.
*
* @param face экземпляр грани
*
* @return идентификатор указанной грани
*
* @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
*/
public function getFaceId(face:Face):Object {
if (face == null) {
throw new FaceNotFoundError(null, this);
}
if (face._mesh != this) {
// Если грань не в меше
throw new FaceNotFoundError(face, this);
}
for (var i:Object in _faces) {
if (_faces[i] == face) {
return i;
}
}
throw new FaceNotFoundError(face, this);
}
/**
* Проверка наличия грани в объекте.
*
* @param face экземпляр класса <code>Face</code> или идентификатор грани
*
* @return <code>true</code>, если объект содержит указанную грань, иначе <code>false</code>
*
* @throws alternativa.engine3d.errors.FaceNotFoundError в качестве face был указан null
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function hasFace(face:Object):Boolean {
if (face == null) {
throw new FaceNotFoundError(null, this);
}
if (face is Face) {
// Проверка грани
return face._mesh == this;
} else {
// Проверка ID грани
if (_faces[face] != undefined) {
// По этому ID есть объект
if (_faces[face] is Face) {
// Объект является гранью
return true;
} else {
// ID некорректный
throw new InvalidIDError(face, this);
}
} else {
return false;
}
}
}
/**
* Получение поверхности объекта по ее идентификатору
*
* @param id идентификатор поверхности
*
* @return экземпляр поверхности с указанным идентификатором
*
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит поверхность с указанным идентификатором
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function getSurfaceById(id:Object):Surface {
if (id == null) {
throw new SurfaceNotFoundError(null, this);
}
if (_surfaces[id] == undefined) {
// Если нет поверхности с таким ID
throw new SurfaceNotFoundError(id, this);
} else {
if (_surfaces[id] is Surface) {
return _surfaces[id];
} else {
throw new InvalidIDError(id, this);
}
}
}
/**
* Получение идентификатора поверхности объекта.
*
* @param surface экземпляр поверхности
*
* @return идентификатор указанной поверхности
*
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
*/
public function getSurfaceId(surface:Surface):Object {
if (surface == null) {
throw new SurfaceNotFoundError(null, this);
}
if (surface._mesh != this) {
// Если поверхность не в меше
throw new SurfaceNotFoundError(surface, this);
}
for (var i:Object in _surfaces) {
if (_surfaces[i] == surface) {
return i;
}
}
return null;
}
/**
* Проверка наличия поверхности в объекте.
*
* @param surface экземпляр класса <code>Surface</code> или идентификатор поверхности
*
* @return <code>true</true>, если объект содержит указанную поверхность, иначе <code>false</code>
*
* @throws alternativa.engine3d.errors.SurfaceNotFoundError в качестве surface был передан null
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*/
public function hasSurface(surface:Object):Boolean {
if (surface == null) {
throw new SurfaceNotFoundError(null, this);
}
if (surface is Surface) {
// Проверка поверхности
return surface._mesh == this;
} else {
// Проверка ID поверхности
if (_surfaces[surface] != undefined) {
// По этому ID есть объект
if (_surfaces[surface] is Surface) {
// Объект является поверхностью
return true;
} else {
// ID некорректный
throw new InvalidIDError(surface, this);
}
} else {
return false;
}
}
}
/**
* @private
* @inheritDoc
*/
override alternativa3d function setScene(value:Scene3D):void {
if (_scene != value) {
var vertex:Vertex;
var face:Face;
var surface:Surface;
if (value != null) {
// Добавить вершины на сцену
for each (vertex in _vertices) {
vertex.addToScene(value);
}
// Добавить грани на сцену
for each (face in _faces) {
face.addToScene(value);
}
// Добавить поверхности на сцену
for each (surface in _surfaces) {
surface.addToScene(value);
}
} else {
// Удалить вершины из сцены
for each (vertex in _vertices) {
vertex.removeFromScene(_scene);
}
// Удалить грани из сцены
for each (face in _faces) {
face.removeFromScene(_scene);
}
// Удалить поверхности из сцены
for each (surface in _surfaces) {
surface.removeFromScene(_scene);
}
}
}
super.setScene(value);
}
/**
* @inheritDoc
*/
override protected function defaultName():String {
return "mesh" + ++counter;
}
/**
* @inheritDoc
*/
override public function toString():String {
return "[" + ObjectUtils.getClassName(this) + " " + _name + " vertices: " + _vertices.length + " faces: " + _faces.length + "]";
}
/**
* @inheritDoc
*/
protected override function createEmptyObject():Object3D {
return new Mesh();
}
/**
* @inheritDoc
*/
protected override function clonePropertiesFrom(source:Object3D):void {
super.clonePropertiesFrom(source);
var src:Mesh = Mesh(source);
var id:*;
var len:int;
var i:int;
// Копирование вершин
var vertexMap:Map = new Map(true);
for (id in src._vertices) {
var sourceVertex:Vertex = src._vertices[id];
vertexMap[sourceVertex] = createVertex(sourceVertex.x, sourceVertex.y, sourceVertex.z, id);
}
// Копирование граней
var faceMap:Map = new Map(true);
for (id in src._faces) {
var sourceFace:Face = src._faces[id];
len = sourceFace._vertices.length;
var faceVertices:Array = new Array(len);
for (i = 0; i < len; i++) {
faceVertices[i] = vertexMap[sourceFace._vertices[i]];
}
var newFace:Face = createFace(faceVertices, id);
newFace.aUV = sourceFace._aUV;
newFace.bUV = sourceFace._bUV;
newFace.cUV = sourceFace._cUV;
faceMap[sourceFace] = newFace;
}
// Копирование поверхностей
for (id in src._surfaces) {
var sourceSurface:Surface = src._surfaces[id];
var surfaceFaces:Array = sourceSurface._faces.toArray();
len = surfaceFaces.length;
for (i = 0; i < len; i++) {
surfaceFaces[i] = faceMap[surfaceFaces[i]];
}
var surface:Surface = createSurface(surfaceFaces, id);
var sourceMaterial:SurfaceMaterial = sourceSurface.material;
if (sourceMaterial != null) {
surface.material = SurfaceMaterial(sourceMaterial.clone());
}
}
}
}
}

View File

@@ -0,0 +1,708 @@
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;
/**
* Базовый класс для объектов, находящихся в сцене. Класс реализует иерархию объектов сцены, а также содержит сведения
* о трансформации объекта как единого целого.
*
* <p> Масштабирование, ориентация и положение объекта задаются в родительской системе координат. Результирующая
* локальная трансформация является композицией операций масштабирования, поворотов объекта относительно осей
* <code>X</code>, <code>Y</code>, <code>Z</code> и параллельного переноса центра объекта из начала координат.
* Операции применяются в порядке их перечисления.
*
* <p> Глобальная трансформация (в системе координат корневого объекта сцены) является композицией трансформаций
* самого объекта и всех его предков по иерархии объектов сцены.
*/
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;
}
/**
* Клонирование объекта. Для реализации собственного клонирования наследники должны переопределять методы
* <code>createEmptyObject()</code> и <code>clonePropertiesFrom()</code>.
*
* @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 любой дочерний объект с заданным именем или <code>null</code> в случае отсутствия таких объектов
*/
public function getChildByName(name:String):Object3D {
for (var key:* in _children) {
var child:Object3D = key;
if (child._name == name) {
return child;
}
}
return null;
}
}
}

View File

@@ -0,0 +1,125 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
import alternativa.types.Set;
use namespace alternativa3d;
/**
* @private
*/
public class Operation {
alternativa3d static const OBJECT_CALCULATE_TRANSFORMATION:uint = 0x01000000;
alternativa3d static const OBJECT_CALCULATE_MOBILITY:uint = 0x02000000;
alternativa3d static const VERTEX_CALCULATE_COORDS:uint = 0x03000000;
alternativa3d static const FACE_CALCULATE_NORMAL:uint = 0x04000000;
alternativa3d static const FACE_CALCULATE_UV:uint = 0x05000000;
alternativa3d static const FACE_UPDATE_PRIMITIVE:uint = 0x06000000;
alternativa3d static const SCENE_CALCULATE_BSP:uint = 0x07000000;
alternativa3d static const FACE_UPDATE_MATERIAL:uint = 0x08000000;
alternativa3d static const FACE_CALCULATE_FRAGMENTS_UV:uint = 0x09000000;
alternativa3d static const CAMERA_CALCULATE_MATRIX:uint = 0x0A000000;
alternativa3d static const CAMERA_CALCULATE_PLANES:uint = 0x0B000000;
alternativa3d static const CAMERA_RENDER:uint = 0x0C000000;
alternativa3d static const SCENE_CLEAR_PRIMITIVES:uint = 0x0D000000;
// Объект
alternativa3d var object:Object;
// Метод
alternativa3d var method:Function;
// Название метода
alternativa3d var name:String;
// Последствия
private var sequel:Operation;
private var sequels:Set;
// Приоритет операции
alternativa3d var priority:uint;
// Находится ли операция в очереди
alternativa3d var queued:Boolean = false;
public function Operation(name:String, object:Object = null, method:Function = null, priority:uint = 0) {
this.object = object;
this.method = method;
this.name = name;
this.priority = priority;
}
// Добавить последствие
alternativa3d function addSequel(operation:Operation):void {
if (sequel == null) {
if (sequels == null) {
sequel = operation;
} else {
sequels[operation] = true;
}
} else {
if (sequel != operation) {
sequels = new Set(true);
sequels[sequel] = true;
sequels[operation] = true;
sequel = null;
}
}
}
// Удалить последствие
alternativa3d function removeSequel(operation:Operation):void {
if (sequel == null) {
if (sequels != null) {
delete sequels[operation];
var key:*;
var single:Boolean = false;
for (key in sequels) {
if (single) {
single = false;
break;
}
single = true;
}
if (single) {
sequel = key;
sequels = null;
}
}
} else {
if (sequel == operation) {
sequel = null;
}
}
}
alternativa3d function collectSequels(collector:Array):void {
if (sequel == null) {
// Проверяем последствия
for (var key:* in sequels) {
var operation:Operation = key;
// Если операция ещё не в очереди
if (!operation.queued) {
// Добавляем её в очередь
collector.push(operation);
// Устанавливаем флаг очереди
operation.queued = true;
// Вызываем добавление в очередь её последствий
operation.collectSequels(collector);
}
}
} else {
if (!sequel.queued) {
collector.push(sequel);
sequel.queued = true;
sequel.collectSequels(collector);
}
}
}
public function toString():String {
return "[Operation " + (priority >>> 24) + "/" + (priority & 0xFFFFFF) + " " + object + "." + name + "]";
}
}
}

View File

@@ -0,0 +1,130 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
use namespace alternativa3d;
/**
* @private
* Примитивный полигон (примитив), хранящийся в узле BSP-дерева.
*/
public class PolyPrimitive {
/**
* @private
* Количество точек
*/
alternativa3d var num:uint;
/**
* @private
* Точки
*/
alternativa3d var points:Array = new Array();
/**
* @private
* UV-координаты
*/
alternativa3d var uvs:Array = new Array();
/**
* @private
* Грань
*/
alternativa3d var face:Face;
/**
* @private
* Родительский примитив
*/
alternativa3d var parent:PolyPrimitive;
/**
* @private
* Соседний примитив (при наличии родительского)
*/
alternativa3d var sibling:PolyPrimitive;
/**
* @private
* Фрагменты
*/
alternativa3d var backFragment:PolyPrimitive;
/**
* @private
*/
alternativa3d var frontFragment:PolyPrimitive;
/**
* @private
* Рассечения
*/
alternativa3d var splitTime1:Number;
/**
* @private
*/
alternativa3d var splitTime2:Number;
/**
* @private
* BSP-нода, в которой находится примитив
*/
alternativa3d var node:BSPNode;
/**
* @private
* Значения для расчёта качества сплиттера
*/
alternativa3d var splits:uint;
/**
* @private
*/
alternativa3d var disbalance:int;
/**
* @private
* Качество примитива как сплиттера (меньше - лучше)
*/
public var splitQuality:Number;
/**
* @private
* Приоритет в BSP-дереве. Чем ниже мобильность, тем примитив выше в дереве.
*/
public var mobility:int;
// Хранилище неиспользуемых примитивов
static private var collector:Array = new Array();
/**
* @private
* Создать примитив
*/
static alternativa3d function createPolyPrimitive():PolyPrimitive {
// Достаём примитив из коллектора
var primitive:PolyPrimitive = collector.pop();
// Если коллектор пуст, создаём новый примитив
if (primitive == null) {
primitive = new PolyPrimitive();
}
//trace(primitive.num, primitive.points.length, primitive.face, primitive.parent, primitive.sibling, primitive.fragment1, primitive.fragment2, primitive.node);
return primitive;
}
/**
* @private
* Кладёт примитив в коллектор для последующего реиспользования.
* Ссылка на грань и массивы точек зачищаются в этом методе.
* Ссылки на фрагменты (parent, sibling, back, front) должны быть зачищены перед запуском метода.
*
* Исключение:
* при сборке примитивов в сцене ссылки на back и front зачищаются после запуска метода.
*
* @param primitive примитив на реиспользование
*/
static alternativa3d function destroyPolyPrimitive(primitive:PolyPrimitive):void {
primitive.face = null;
for (var i:uint = 0; i < primitive.num; i++) {
primitive.points.pop();
primitive.uvs.pop();
}
collector.push(primitive);
}
/**
* Строковое представление объекта.
*/
public function toString():String {
return "[Primitive " + face._mesh._name + "]";
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,350 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
import alternativa.engine3d.errors.FaceExistsError;
import alternativa.engine3d.errors.FaceNotFoundError;
import alternativa.engine3d.errors.InvalidIDError;
import alternativa.engine3d.materials.SurfaceMaterial;
import alternativa.types.Set;
use namespace alternativa3d;
/**
* Поверхность &mdash; набор граней, объединённых в группу. Поверхности используются для установки материалов,
* визуализирующих грани объекта.
*/
public class Surface {
// Операции
/**
* @private
* Изменение набора граней
*/
alternativa3d var changeFacesOperation:Operation = new Operation("changeFaces", this);
/**
* @private
* Изменение материала
*/
alternativa3d var changeMaterialOperation:Operation = new Operation("changeMaterial", this);
/**
* @private
* Меш
*/
alternativa3d var _mesh:Mesh;
/**
* @private
* Материал
*/
alternativa3d var _material:SurfaceMaterial;
/**
* @private
* Грани
*/
alternativa3d var _faces:Set = new Set();
/**
* Создание экземпляра поверхности.
*/
public function Surface() {}
/**
* Добавление грани в поверхность.
*
* @param face экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор грани полигонального объекта
*
* @throws alternativa.engine3d.errors.FaceNotFoundError грань не найдена в полигональном объекте содержащем поверхность
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
* @throws alternativa.engine3d.errors.FaceExistsError поверхность уже содержит указанную грань
*
* @see Face
*/
public function addFace(face:Object):void {
var byLink:Boolean = face is Face;
// Проверяем на нахождение поверхности в меше
if (_mesh == null) {
throw new FaceNotFoundError(face, this);
}
// Проверяем на null
if (face == null) {
throw new FaceNotFoundError(null, this);
}
// Проверяем наличие грани в меше
if (byLink) {
// Если удаляем по ссылке
if (Face(face)._mesh != _mesh) {
// Если грань не в меше
throw new FaceNotFoundError(face, this);
}
} else {
// Если удаляем по ID
if (_mesh._faces[face] == undefined) {
// Если нет грани с таким ID
throw new FaceNotFoundError(face, this);
} else {
if (!(_mesh._faces[face] is Face)) {
throw new InvalidIDError(face, this);
}
}
}
// Находим грань
var f:Face = byLink ? Face(face) : _mesh._faces[face];
// Проверяем наличие грани в поверхности
if (_faces.has(f)) {
// Если грань уже в поверхности
throw new FaceExistsError(f, this);
}
// Проверяем грань на нахождение в другой поверхности
if (f._surface != null) {
// Удаляем её из той поверхности
f._surface._faces.remove(f);
f.removeFromSurface(f._surface);
}
// Добавляем грань в поверхность
_faces.add(f);
f.addToSurface(this);
// Отправляем операцию изменения набора граней
_mesh.addOperationToScene(changeFacesOperation);
}
/**
* Удаление грани из поверхности.
*
* @param face экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор грани полигонального объекта
*
* @throws alternativa.engine3d.errors.FaceNotFoundError поверхность не содержит указанную грань
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
*
* @see Face
*/
public function removeFace(face:Object):void {
var byLink:Boolean = face is Face;
// Проверяем на нахождение поверхности в меше
if (_mesh == null) {
throw new FaceNotFoundError(face, this);
}
// Проверяем на null
if (face == null) {
throw new FaceNotFoundError(null, this);
}
// Проверяем наличие грани в меше
if (byLink) {
// Если удаляем по ссылке
if (Face(face)._mesh != _mesh) {
// Если грань не в меше
throw new FaceNotFoundError(face, this);
}
} else {
// Если удаляем по ID
if (_mesh._faces[face] == undefined) {
// Если нет грани с таким ID
throw new FaceNotFoundError(face, this);
} else {
if (!(_mesh._faces[face] is Face)) {
throw new InvalidIDError(face, this);
}
}
}
// Находим грань
var f:Face = byLink ? Face(face) : _mesh._faces[face];
// Проверяем наличие грани в поверхности
if (!_faces.has(f)) {
// Если грань не в поверхности
throw new FaceNotFoundError(f, this);
}
// Удаляем грань из поверхности
_faces.remove(f);
f.removeFromSurface(this);
// Отправляем операцию изменения набора граней
_mesh.addOperationToScene(changeFacesOperation);
}
/**
* Материал поверхности. При установке нового значения, устанавливаемый материал будет удалён из старой поверхности.
*/
public function get material():SurfaceMaterial {
return _material;
}
/**
* @private
*/
public function set material(value:SurfaceMaterial):void {
if (_material != value) {
// Если был материал
if (_material != null) {
// Удалить материал из поверхности
_material.removeFromSurface(this);
// Удалить материал из меша
if (_mesh != null) {
_material.removeFromMesh(_mesh);
// Удалить материал из сцены
if (_mesh._scene != null) {
_material.removeFromScene(_mesh._scene);
}
}
}
// Если новый материал
if (value != null) {
// Если материал был в другой поверхности
if (value._surface != null) {
// Удалить его оттуда
value._surface.material = null;
}
// Добавить материал в поверхность
value.addToSurface(this);
// Добавить материал в меш
if (_mesh != null) {
value.addToMesh(_mesh);
// Добавить материал в сцену
if (_mesh._scene != null) {
value.addToScene(_mesh._scene);
}
}
}
// Сохраняем материал
_material = value;
// Отправляем операцию изменения материала
addMaterialChangedOperationToScene();
}
}
/**
* Набор граней поверхности.
*/
public function get faces():Set {
return _faces.clone();
}
/**
* Полигональный объект, которому принадлежит поверхность.
*/
public function get mesh():Mesh {
return _mesh;
}
/**
* Идентификатор поверхности в полигональном объекте. Если поверхность не принадлежит ни одному объекту,
* значение идентификатора равно <code>null</code>.
*/
public function get id():Object {
return (_mesh != null) ? _mesh.getSurfaceId(this) : null;
}
/**
* @private
* Добавление в сцену.
*
* @param scene
*/
alternativa3d function addToScene(scene:Scene3D):void {
// Добавляем на сцену материал
if (_material != null) {
_material.addToScene(scene);
}
}
/**
* @private
* Удаление из сцены.
*
* @param scene
*/
alternativa3d function removeFromScene(scene:Scene3D):void {
// Удаляем все операции из очереди
scene.removeOperation(changeFacesOperation);
scene.removeOperation(changeMaterialOperation);
// Удаляем из сцены материал
if (_material != null) {
_material.removeFromScene(scene);
}
}
/**
* @private
* Добавление к мешу
* @param mesh
*/
alternativa3d function addToMesh(mesh:Mesh):void {
// Подписка на операции меша
// Добавляем в меш материал
if (_material != null) {
_material.addToMesh(mesh);
}
// Сохранить меш
_mesh = mesh;
}
/**
* @private
* Удаление из меша
*
* @param mesh
*/
alternativa3d function removeFromMesh(mesh:Mesh):void {
// Отписка от операций меша
// Удаляем из меша материал
if (_material != null) {
_material.removeFromMesh(mesh);
}
// Удалить ссылку на меш
_mesh = null;
}
/**
* @private
* Удаление граней
*/
alternativa3d function removeFaces():void {
for (var key:* in _faces) {
var face:Face = key;
_faces.remove(face);
face.removeFromSurface(this);
}
}
/**
* @private
* Изменение материала
*/
alternativa3d function addMaterialChangedOperationToScene():void {
if (_mesh != null) {
_mesh.addOperationToScene(changeMaterialOperation);
}
}
/**
* Строковое представление объекта.
*
* @return строковое представление объекта
*/
public function toString():String {
var length:uint = _faces.length;
var res:String = "[Surface ID:" + id + ((length > 0) ? " faces:" : "");
var i:uint = 0;
for (var key:* in _faces) {
var face:Face = key;
res += face.id + ((i < length - 1) ? ", " : "");
i++;
}
res += "]";
return res;
}
}
}

View File

@@ -0,0 +1,250 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
import alternativa.types.Matrix3D;
import alternativa.types.Point3D;
import alternativa.types.Set;
use namespace alternativa3d;
/**
* Вершина полигона в трёхмерном пространстве. Вершина хранит свои координаты, а также ссылки на
* полигональный объект и грани этого объекта, которым она принадлежит.
*/
final public class Vertex {
// Операции
/**
* @private
* Изменение локальных координат
*/
alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this);
/**
* @private
* Расчёт глобальных координат
*/
alternativa3d var calculateCoordsOperation:Operation = new Operation("calculateCoords", this, calculateCoords, Operation.VERTEX_CALCULATE_COORDS);
/**
* @private
* Меш
*/
alternativa3d var _mesh:Mesh;
/**
* @private
* Координаты точки
*/
alternativa3d var _coords:Point3D;
/**
* @private
* Грани
*/
alternativa3d var _faces:Set = new Set();
/**
* @private
* Координаты в сцене
*/
alternativa3d var globalCoords:Point3D = new Point3D();
/**
* Создание экземпляра вершины.
*
* @param x координата вершины по оси X
* @param y координата вершины по оси Y
* @param z координата вершины по оси Z
*/
public function Vertex(x:Number = 0, y:Number = 0, z:Number = 0) {
_coords = new Point3D(x, y, z);
// Изменение координат инициирует пересчёт глобальных координат
changeCoordsOperation.addSequel(calculateCoordsOperation);
}
/**
* Вызывается из операции calculateCoordsOperation для расчета глобальных координат вершины
*/
private function calculateCoords():void {
globalCoords.copy(_coords);
globalCoords.transform(_mesh.transformation);
}
/**
* @private
*/
public function set x(value:Number):void {
if (_coords.x != value) {
_coords.x = value;
if (_mesh != null) {
_mesh.addOperationToScene(changeCoordsOperation);
}
}
}
/**
* @private
*/
public function set y(value:Number):void {
if (_coords.y != value) {
_coords.y = value;
if (_mesh != null) {
_mesh.addOperationToScene(changeCoordsOperation);
}
}
}
/**
* @private
*/
public function set z(value:Number):void {
if (_coords.z != value) {
_coords.z = value;
if (_mesh != null) {
_mesh.addOperationToScene(changeCoordsOperation);
}
}
}
/**
* @private
*/
public function set coords(value:Point3D):void {
if (!_coords.equals(value)) {
_coords.copy(value);
if (_mesh != null) {
_mesh.addOperationToScene(changeCoordsOperation);
}
}
}
/**
* координата вершины по оси 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;
}
/**
* Координаты вершины.
*/
public function get coords():Point3D {
return _coords.clone();
}
/**
* Полигональный объект, которому принадлежит вершина.
*/
public function get mesh():Mesh {
return _mesh;
}
/**
* Множество граней, которым принадлежит вершина. Каждый элемент множества является объектом класса
* <code>altertnativa.engine3d.core.Face</code>.
*
* @see Face
*/
public function get faces():Set {
return _faces.clone();
}
/**
* Идентификатор вершины в полигональном объекте. Если вершина не принадлежит полигональному объекту, возвращается <code>null</code>.
*/
public function get id():Object {
return (_mesh != null) ? _mesh.getVertexId(this) : null;
}
/**
* @private
* @param scene
*/
alternativa3d function addToScene(scene:Scene3D):void {
// При добавлении на сцену расчитать глобальные координаты
scene.addOperation(calculateCoordsOperation);
}
/**
* @private
* @param scene
*/
alternativa3d function removeFromScene(scene:Scene3D):void {
// Удаляем все операции из очереди
scene.removeOperation(calculateCoordsOperation);
scene.removeOperation(changeCoordsOperation);
}
/**
* @private
* @param mesh
*/
alternativa3d function addToMesh(mesh:Mesh):void {
// Подписка на операции меша
mesh.changeCoordsOperation.addSequel(calculateCoordsOperation);
mesh.changeRotationOrScaleOperation.addSequel(calculateCoordsOperation);
// Сохранить меш
_mesh = mesh;
}
/**
* @private
* @param mesh
*/
alternativa3d function removeFromMesh(mesh:Mesh):void {
// Отписка от операций меша
mesh.changeCoordsOperation.removeSequel(calculateCoordsOperation);
mesh.changeRotationOrScaleOperation.removeSequel(calculateCoordsOperation);
// Удалить зависимые грани
for (var key:* in _faces) {
var face:Face = key;
mesh.removeFace(face);
}
// Удалить ссылку на меш
_mesh = null;
}
/**
* @private
* @param face
*/
alternativa3d function addToFace(face:Face):void {
// Подписка грани на операции
changeCoordsOperation.addSequel(face.calculateUVOperation);
changeCoordsOperation.addSequel(face.calculateNormalOperation);
// Добавить грань в список
_faces.add(face);
}
/**
* @private
* @param face
*/
alternativa3d function removeFromFace(face:Face):void {
// Отписка грани от операций
changeCoordsOperation.removeSequel(face.calculateUVOperation);
changeCoordsOperation.removeSequel(face.calculateNormalOperation);
// Удалить грань из списка
_faces.remove(face);
}
/**
* Строковое представление объекта.
*
* @return строковое представление объекта
*/
public function toString():String {
return "[Vertex ID:" + id + " " + _coords.x.toFixed(2) + ", " + _coords.y.toFixed(2) + ", " + _coords.z.toFixed(2) + "]";
}
}
}

View File

@@ -0,0 +1,17 @@
K 25
svn:wc:ra_dav:version-url
V 99
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/display
END
Skin.as
K 25
svn:wc:ra_dav:version-url
V 107
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/display/Skin.as
END
View.as
K 25
svn:wc:ra_dav:version-url
V 107
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/display/View.as
END

View File

@@ -0,0 +1,52 @@
8
dir
46043
http://svndev.alternativaplatform.com/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/display
http://svndev.alternativaplatform.com
2008-09-08T06:50:26.103978Z
468
mike
svn:special svn:externals svn:needs-lock
d9e2387a-1f3e-40e2-b57f-9df5970a2fa5
Skin.as
file
2010-10-28T04:31:16.000000Z
a49c915b68556525f014d6b2b1477b38
2008-09-08T06:50:26.103978Z
468
mike
View.as
file
2010-10-28T04:31:16.000000Z
d9cfe36eb0034482d153d4b0f47c7759
2008-08-29T11:41:21.451513Z
304
wolf

View File

@@ -0,0 +1 @@
8

View File

@@ -0,0 +1,66 @@
package alternativa.engine3d.display {
import alternativa.engine3d.*;
import alternativa.engine3d.core.PolyPrimitive;
import alternativa.engine3d.materials.SurfaceMaterial;
import flash.display.Graphics;
import flash.display.Sprite;
use namespace alternativa3d;
/**
* @private
* Контейнер, исползуемый материалами для отрисовки примитивов. Каждый примитив BSP-дерева рисуется в своём контейнере.
*/
public class Skin extends Sprite {
/**
* @private
* Графика скина (для быстрого доступа)
*/
alternativa3d var gfx:Graphics = graphics;
/**
* @private
* Ссылка на следующий скин
*/
alternativa3d var nextSkin:Skin;
/**
* @private
* Примитив
*/
alternativa3d var primitive:PolyPrimitive;
/**
* @private
* Материал, связанный со скином.
*/
alternativa3d var material:SurfaceMaterial;
// Хранилище неиспользуемых скинов
static private var collector:Array = new Array();
/**
* @private
* Создание скина.
*/
static alternativa3d function createSkin():Skin {
// Достаём скин из коллектора
var skin:Skin = collector.pop();
// Если коллектор пуст, создаём новый скин
if (skin == null) {
skin = new Skin();
}
return skin;
}
/**
* @private
* Удаление скина, все ссылки должны быть почищены.
*/
static alternativa3d function destroySkin(skin:Skin):void {
collector.push(skin);
}
}
}

View File

@@ -0,0 +1,186 @@
package alternativa.engine3d.display {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Face;
import flash.display.Sprite;
import flash.geom.Point;
use namespace alternativa3d;
/**
* Область для вывода изображения с камеры.
*/
public class View extends Sprite {
/**
* @private
* Область отрисовки спрайтов
*/
alternativa3d var canvas:Sprite;
private var _camera:Camera3D;
/**
* @private
* Ширина области вывода
*/
alternativa3d var _width:Number;
/**
* @private
* Высота области вывода
*/
alternativa3d var _height:Number;
/**
* Создание экземпляра области вывода.
*
* @param camera камера, изображение с которой должно выводиться
* @param width ширина области вывода
* @param height высота области вывода
*/
public function View(camera:Camera3D = null, width:Number = 0, height:Number = 0) {
canvas = new Sprite();
canvas.mouseEnabled = false;
canvas.mouseChildren = false;
canvas.tabEnabled = false;
canvas.tabChildren = false;
addChild(canvas);
this.camera = camera;
this.width = width;
this.height = height;
}
/**
* Камера с которой ведётся отображение.
*/
public function get camera():Camera3D {
return _camera;
}
/**
* @private
*/
public function set camera(value:Camera3D):void {
if (_camera != value) {
// Если была камера
if (_camera != null) {
// Удалить камеру
_camera.removeFromView(this);
}
// Если новая камера
if (value != null) {
// Если камера была в другом вьюпорте
if (value._view != null) {
// Удалить её оттуда
value._view.camera = null;
}
// Добавить камеру
value.addToView(this);
} else {
// Зачистка скинов
if (canvas.numChildren > 0) {
var skin:Skin = Skin(canvas.getChildAt(0));
while (skin != null) {
// Сохраняем следующий
var next:Skin = skin.nextSkin;
// Удаляем из канваса
canvas.removeChild(skin);
// Очистка скина
if (skin.material != null) {
skin.material.clear(skin);
}
// Зачищаем ссылки
skin.nextSkin = null;
skin.primitive = null;
skin.material = null;
// Удаляем
Skin.destroySkin(skin);
// Следующий устанавливаем текущим
skin = next;
}
}
}
// Сохраняем камеру
_camera = value;
}
}
/**
* Ширина области вывода в пикселях.
*/
override public function get width():Number {
return _width;
}
/**
* @private
*/
override public function set width(value:Number):void {
if (_width != value) {
_width = value;
canvas.x = _width*0.5;
if (_camera != null) {
camera.addOperationToScene(camera.calculatePlanesOperation);
}
}
}
/**
* Высота области вывода в пикселях.
*/
override public function get height():Number {
return _height;
}
/**
* @private
*/
override public function set height(value:Number):void {
if (_height != value) {
_height = value;
canvas.y = _height*0.5;
if (_camera != null) {
camera.addOperationToScene(camera.calculatePlanesOperation);
}
}
}
/**
* Метод возвращает грань, находящуюся под указанной точкой в области вывода.
*
* @param viewPoint координаты точки относительно области вывода
*
* @return ближайшая к камере грань под заданной точкой области вывода
*/
public function getFaceUnderPoint(viewPoint:Point):Face {
var p:Point = localToGlobal(viewPoint);
var objects:Array = canvas.getObjectsUnderPoint(p);
var skin:Skin = objects.pop() as Skin;
if (skin != null) {
return skin.primitive.face;
}
return null;
}
/**
* Метод возвращает грани, находящиеся под указанной точкой в области вывода.
*
* @param viewPoint координаты точки относительно области вывода
*
* @return массив граней, расположенных под заданной точкой области вывода. Первым элементом массива является самая дальняя грань.
*/
public function getFacesUnderPoint(viewPoint:Point):Array {
var p:Point = localToGlobal(viewPoint);
var objects:Array = canvas.getObjectsUnderPoint(p);
var res:Array = new Array();
var length:uint = objects.length;
for (var i:uint = 0; i < length; i++) {
var skin:Skin = objects[i];
res.push(skin.primitive.face);
}
return res;
}
}
}

View File

@@ -0,0 +1,66 @@
package alternativa.engine3d.display {
import alternativa.engine3d.*;
import alternativa.engine3d.core.PolyPrimitive;
import alternativa.engine3d.materials.SurfaceMaterial;
import flash.display.Graphics;
import flash.display.Sprite;
use namespace alternativa3d;
/**
* @private
* Контейнер, исползуемый материалами для отрисовки примитивов. Каждый примитив BSP-дерева рисуется в своём контейнере.
*/
public class Skin extends Sprite {
/**
* @private
* Графика скина (для быстрого доступа)
*/
alternativa3d var gfx:Graphics = graphics;
/**
* @private
* Ссылка на следующий скин
*/
alternativa3d var nextSkin:Skin;
/**
* @private
* Примитив
*/
alternativa3d var primitive:PolyPrimitive;
/**
* @private
* Материал, связанный со скином.
*/
alternativa3d var material:SurfaceMaterial;
// Хранилище неиспользуемых скинов
static private var collector:Array = new Array();
/**
* @private
* Создание скина.
*/
static alternativa3d function createSkin():Skin {
// Достаём скин из коллектора
var skin:Skin = collector.pop();
// Если коллектор пуст, создаём новый скин
if (skin == null) {
skin = new Skin();
}
return skin;
}
/**
* @private
* Удаление скина, все ссылки должны быть почищены.
*/
static alternativa3d function destroySkin(skin:Skin):void {
collector.push(skin);
}
}
}

View File

@@ -0,0 +1,186 @@
package alternativa.engine3d.display {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Face;
import flash.display.Sprite;
import flash.geom.Point;
use namespace alternativa3d;
/**
* Область для вывода изображения с камеры.
*/
public class View extends Sprite {
/**
* @private
* Область отрисовки спрайтов
*/
alternativa3d var canvas:Sprite;
private var _camera:Camera3D;
/**
* @private
* Ширина области вывода
*/
alternativa3d var _width:Number;
/**
* @private
* Высота области вывода
*/
alternativa3d var _height:Number;
/**
* Создание экземпляра области вывода.
*
* @param camera камера, изображение с которой должно выводиться
* @param width ширина области вывода
* @param height высота области вывода
*/
public function View(camera:Camera3D = null, width:Number = 0, height:Number = 0) {
canvas = new Sprite();
canvas.mouseEnabled = false;
canvas.mouseChildren = false;
canvas.tabEnabled = false;
canvas.tabChildren = false;
addChild(canvas);
this.camera = camera;
this.width = width;
this.height = height;
}
/**
* Камера с которой ведётся отображение.
*/
public function get camera():Camera3D {
return _camera;
}
/**
* @private
*/
public function set camera(value:Camera3D):void {
if (_camera != value) {
// Если была камера
if (_camera != null) {
// Удалить камеру
_camera.removeFromView(this);
}
// Если новая камера
if (value != null) {
// Если камера была в другом вьюпорте
if (value._view != null) {
// Удалить её оттуда
value._view.camera = null;
}
// Добавить камеру
value.addToView(this);
} else {
// Зачистка скинов
if (canvas.numChildren > 0) {
var skin:Skin = Skin(canvas.getChildAt(0));
while (skin != null) {
// Сохраняем следующий
var next:Skin = skin.nextSkin;
// Удаляем из канваса
canvas.removeChild(skin);
// Очистка скина
if (skin.material != null) {
skin.material.clear(skin);
}
// Зачищаем ссылки
skin.nextSkin = null;
skin.primitive = null;
skin.material = null;
// Удаляем
Skin.destroySkin(skin);
// Следующий устанавливаем текущим
skin = next;
}
}
}
// Сохраняем камеру
_camera = value;
}
}
/**
* Ширина области вывода в пикселях.
*/
override public function get width():Number {
return _width;
}
/**
* @private
*/
override public function set width(value:Number):void {
if (_width != value) {
_width = value;
canvas.x = _width*0.5;
if (_camera != null) {
camera.addOperationToScene(camera.calculatePlanesOperation);
}
}
}
/**
* Высота области вывода в пикселях.
*/
override public function get height():Number {
return _height;
}
/**
* @private
*/
override public function set height(value:Number):void {
if (_height != value) {
_height = value;
canvas.y = _height*0.5;
if (_camera != null) {
camera.addOperationToScene(camera.calculatePlanesOperation);
}
}
}
/**
* Метод возвращает грань, находящуюся под указанной точкой в области вывода.
*
* @param viewPoint координаты точки относительно области вывода
*
* @return ближайшая к камере грань под заданной точкой области вывода
*/
public function getFaceUnderPoint(viewPoint:Point):Face {
var p:Point = localToGlobal(viewPoint);
var objects:Array = canvas.getObjectsUnderPoint(p);
var skin:Skin = objects.pop() as Skin;
if (skin != null) {
return skin.primitive.face;
}
return null;
}
/**
* Метод возвращает грани, находящиеся под указанной точкой в области вывода.
*
* @param viewPoint координаты точки относительно области вывода
*
* @return массив граней, расположенных под заданной точкой области вывода. Первым элементом массива является самая дальняя грань.
*/
public function getFacesUnderPoint(viewPoint:Point):Array {
var p:Point = localToGlobal(viewPoint);
var objects:Array = canvas.getObjectsUnderPoint(p);
var res:Array = new Array();
var length:uint = objects.length;
for (var i:uint = 0; i < length; i++) {
var skin:Skin = objects[i];
res.push(skin.primitive.face);
}
return res;
}
}
}

View File

@@ -0,0 +1,83 @@
K 25
svn:wc:ra_dav:version-url
V 98
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/errors
END
InvalidIDError.as
K 25
svn:wc:ra_dav:version-url
V 116
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/errors/InvalidIDError.as
END
ObjectNotFoundError.as
K 25
svn:wc:ra_dav:version-url
V 121
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/errors/ObjectNotFoundError.as
END
SurfaceNotFoundError.as
K 25
svn:wc:ra_dav:version-url
V 122
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/errors/SurfaceNotFoundError.as
END
ObjectExistsError.as
K 25
svn:wc:ra_dav:version-url
V 119
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/errors/ObjectExistsError.as
END
SurfaceExistsError.as
K 25
svn:wc:ra_dav:version-url
V 120
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/errors/SurfaceExistsError.as
END
Object3DNotFoundError.as
K 25
svn:wc:ra_dav:version-url
V 123
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/errors/Object3DNotFoundError.as
END
VertexNotFoundError.as
K 25
svn:wc:ra_dav:version-url
V 121
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/errors/VertexNotFoundError.as
END
FaceNotFoundError.as
K 25
svn:wc:ra_dav:version-url
V 119
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/errors/FaceNotFoundError.as
END
Engine3DError.as
K 25
svn:wc:ra_dav:version-url
V 115
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/errors/Engine3DError.as
END
Object3DHierarchyError.as
K 25
svn:wc:ra_dav:version-url
V 124
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/errors/Object3DHierarchyError.as
END
VertexExistsError.as
K 25
svn:wc:ra_dav:version-url
V 119
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/errors/VertexExistsError.as
END
FaceExistsError.as
K 25
svn:wc:ra_dav:version-url
V 117
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/errors/FaceExistsError.as
END
FaceNeedMoreVerticesError.as
K 25
svn:wc:ra_dav:version-url
V 127
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/errors/FaceNeedMoreVerticesError.as
END

View File

@@ -0,0 +1,184 @@
8
dir
46043
http://svndev.alternativaplatform.com/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/errors
http://svndev.alternativaplatform.com
2008-08-25T13:44:47.077292Z
176
int
svn:special svn:externals svn:needs-lock
d9e2387a-1f3e-40e2-b57f-9df5970a2fa5
InvalidIDError.as
file
2010-10-28T04:31:16.000000Z
bdff3ad52feb719e00dc92389c9e5e1e
2008-08-25T13:44:47.077292Z
176
int
ObjectNotFoundError.as
file
2010-10-28T04:31:16.000000Z
6dd11fc2d6176278fb21fb7172f9ec0f
2008-08-25T13:44:47.077292Z
176
int
SurfaceNotFoundError.as
file
2010-10-28T04:31:16.000000Z
420e1e6740301ff2bb70e1a0ae085a75
2008-08-25T13:44:47.077292Z
176
int
ObjectExistsError.as
file
2010-10-28T04:31:16.000000Z
a25dfccfb0211e9199fbe392e5f0ff08
2008-08-25T13:44:47.077292Z
176
int
SurfaceExistsError.as
file
2010-10-28T04:31:16.000000Z
ac6477b7def1b87a76360823782d1dca
2008-08-25T13:44:47.077292Z
176
int
Object3DNotFoundError.as
file
2010-10-28T04:31:16.000000Z
7ab91f78c0b7e42b0ce116e938e62c66
2008-08-25T13:44:47.077292Z
176
int
VertexNotFoundError.as
file
2010-10-28T04:31:16.000000Z
2cf926b6a3bedb6035162d2cc1a14201
2008-08-25T13:44:47.077292Z
176
int
FaceNotFoundError.as
file
2010-10-28T04:31:16.000000Z
6983fcb77a1b337338f6452e35f504bf
2008-08-25T13:44:47.077292Z
176
int
Engine3DError.as
file
2010-10-28T04:31:16.000000Z
47a129612e42512ac074b5273ce9be5d
2008-08-25T13:44:47.077292Z
176
int
Object3DHierarchyError.as
file
2010-10-28T04:31:16.000000Z
46b5ed9fa3674c3ec123b6b0b38e5fa6
2008-08-25T13:44:47.077292Z
176
int
VertexExistsError.as
file
2010-10-28T04:31:16.000000Z
93465bfa7fb4dbaca1323d2e09c1c645
2008-08-25T13:44:47.077292Z
176
int
FaceExistsError.as
file
2010-10-28T04:31:16.000000Z
6e778f9e94447b3c427f84bad486ae59
2008-08-25T13:44:47.077292Z
176
int
FaceNeedMoreVerticesError.as
file
2010-10-28T04:31:16.000000Z
35781dbd32ece10c9efca8ab9749c293
2008-08-25T13:44:47.077292Z
176
int

View File

@@ -0,0 +1 @@
8

View File

@@ -0,0 +1,27 @@
package alternativa.engine3d.errors {
import alternativa.utils.TextUtils;
/**
* Базовый класс для ошибок 3d-engine.
*/
public class Engine3DError extends Error {
/**
* Источник ошибки - объект в котором произошла ошибка.
*/
public var source:Object;
/**
* Создание экземпляра класса.
*
* @param message описание ошибки
* @param source источник ошибки
*/
public function Engine3DError(message:String = "", source:Object = null) {
super(message);
this.source = source;
this.name = "Engine3DError";
}
}
}

View File

@@ -0,0 +1,35 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Mesh;
import alternativa.utils.TextUtils;
import alternativa.engine3d.core.Surface;
/**
* Ошибка, возникающая при попытке добавить в какой-либо объект грань, уже содержащуюся в данном объекте.
*/
public class FaceExistsError extends ObjectExistsError {
/**
* Создание экземпляра класса.
*
* @param face экземпляр или идентификатор грани, которая уже содержится в объекте
* @param source источник ошибки
*/
public function FaceExistsError(face:Object = null, source:Object = null) {
var message:String;
if (source is Mesh) {
message = "Mesh ";
} else if (source is Surface) {
message = "Surface ";
}
if (face is Face) {
message += "%1. Face %2 already exists.";
} else {
message += "%1. Face with ID '%2' already exists.";
}
super(TextUtils.insertVars(message, source, face), face, source);
this.name = "FaceExistsError";
}
}
}

View File

@@ -0,0 +1,29 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Mesh;
import alternativa.utils.TextUtils;
/**
* Ошибка, обозначающая недостаточное количество вершин для создания грани.
* Для создания грани должно быть указано не менее трех вершин.
*/
public class FaceNeedMoreVerticesError extends Engine3DError {
/**
* Количество переданных для создания грани вершин
*/
public var count:uint;
/**
* Создание экземпляра класса.
*
* @param mesh объект, в котором произошла ошибка
* @param count количество вершин, переданное для создания грани
*/
public function FaceNeedMoreVerticesError(mesh:Mesh = null, count:uint = 0) {
super(TextUtils.insertVars("Mesh %1. %2 vertices not enough for face creation.", mesh, count), mesh);
this.count = count;
this.name = "FaceNeedMoreVerticesError";
}
}
}

View File

@@ -0,0 +1,34 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Mesh;
import alternativa.utils.TextUtils;
/**
* Ошибка, возникающая, если грань не найдена в объекте.
*/
public class FaceNotFoundError extends ObjectNotFoundError {
/**
* Создание экземпляра класса.
*
* @param face экземпляр или идентификатор грани
* @param source объект, в котором произошла ошибка
*/
public function FaceNotFoundError(face:Object = null, source:Object = null) {
var message:String;
if (source is Mesh) {
message = "Mesh ";
} else {
message = "Surface ";
}
if (face is Face) {
message += "%1. Face %2 not found.";
} else {
message += "%1. Face with ID '%2' not found.";
}
super(TextUtils.insertVars(message, source, face), face, source);
this.name = "FaceNotFoundError";
}
}
}

View File

@@ -0,0 +1,34 @@
package alternativa.engine3d.errors {
import alternativa.utils.TextUtils;
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.Surface;
/**
* Ошибка, обозначающая, что идентификатор зарезервирован и не может быть использован.
*/
public class InvalidIDError extends Engine3DError {
/**
* Зарезервированный идентификатор
*/
public var id:Object;
/**
* Создание экземпляра класса.
*
* @param id идентификатор
* @param source объект, в котором произошла ошибка
*/
public function InvalidIDError(id:Object = null, source:Object = null) {
var message:String;
if (source is Mesh) {
message = "Mesh %2. ";
} else if (source is Surface) {
message = "Surface %2. ";
}
super(TextUtils.insertVars(message + "ID %1 is reserved and cannot be used", [id, source]), source);
this.id = id;
this.name = "InvalidIDError";
}
}
}

View File

@@ -0,0 +1,29 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Object3D;
import alternativa.utils.TextUtils;
/**
* Ошибка, связанная с нарушением иерархии объектов сцены.
*/
public class Object3DHierarchyError extends Engine3DError
{
/**
* Объект сцены, нарушающий иерархию
*/
public var object:Object3D;
/**
* Создание экземпляра класса.
*
* @param object объект, нарушающий иерархию
* @param source источник ошибки
*/
public function Object3DHierarchyError(object:Object3D = null, source:Object3D = null) {
super(TextUtils.insertVars("Object3D %1. Object %2 cannot be added", source, object), source);
this.object = object;
this.name = "Object3DHierarchyError";
}
}
}

View File

@@ -0,0 +1,22 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Object3D;
import alternativa.utils.TextUtils;
/**
* Ошибка, возникающая, когда объект сцены не был найден в списке связанных с необходимым объектом сцены.
*/
public class Object3DNotFoundError extends ObjectNotFoundError {
/**
* Создание экземпляра класса.
*
* @param object ненайденный объект сцены
* @param source объект сцены, в котором произошла ошибка
*/
public function Object3DNotFoundError(object:Object3D = null, source:Object3D = null) {
super(TextUtils.insertVars("Object3D %1. Object %2 not in child list", source, object), object, source);
this.name = "Object3DNotFoundError";
}
}
}

View File

@@ -0,0 +1,26 @@
package alternativa.engine3d.errors {
/**
* Ошибка, обозначающая, что объект уже присутствует в контейнере.
*/
public class ObjectExistsError extends Engine3DError {
/**
* Экземпляр или идентификатор объекта, который уже присутствует в контейнере
*/
public var object:Object;
/**
* Создание экземпляра класса.
*
* @param message описание ошибки
* @param object объект, который уже присутствует в контейнере
* @param source объект, вызвавший ошибку
*/
public function ObjectExistsError(message:String = "", object:Object = null, source:Object = null) {
super(message, source);
this.object = object;
this.name = "ObjectExistsError";
}
}
}

View File

@@ -0,0 +1,26 @@
package alternativa.engine3d.errors {
/**
* Необходимый объект не был найден в контейнере.
*/
public class ObjectNotFoundError extends Engine3DError {
/**
* Объект, который отсутствует в контейнере.
*/
public var object:Object;
/**
* Создание экземпляра класса.
*
* @param message описание ошибки
* @param object отсутствующий объект
* @param source объект, вызвавший ошибку
*/
public function ObjectNotFoundError(message:String = "", object:Object = null, source:Object = null) {
super(message, source);
this.object = object;
this.name = "ObjectNotFoundError";
}
}
}

View File

@@ -0,0 +1,22 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Mesh;
import alternativa.utils.TextUtils;
/**
* Ошибка, обозначающая, что поверхность уже присутствует в контейнере.
*/
public class SurfaceExistsError extends ObjectExistsError {
/**
* Создание экземпляра класса.
*
* @param surface поверхность, которая уже присутствует в контейнере
* @param mesh объект, вызвавший ошибку
*/
public function SurfaceExistsError(surface:Object = null, mesh:Mesh = null) {
super(TextUtils.insertVars("Mesh %1. Surface with ID '%2' already exists.", mesh, surface), surface, mesh);
this.name = "SurfaceExistsError";
}
}
}

View File

@@ -0,0 +1,31 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.Surface;
import alternativa.utils.TextUtils;
/**
* Ошибка, обозначающая, что поверхность не найдена в контейнере.
*/
public class SurfaceNotFoundError extends ObjectNotFoundError {
/**
* Создание экземпляра класса.
*
* @param surface поверхность, которая отсутствует в объекте
* @param mesh объект, который вызвал ошибку
*/
public function SurfaceNotFoundError(surface:Object = null, mesh:Mesh = null) {
if (mesh == null) {
}
if (surface is Surface) {
message = "Mesh %1. Surface %2 not found.";
} else {
message = "Mesh %1. Surface with ID '%2' not found.";
}
super(TextUtils.insertVars(message, mesh, surface), surface, mesh);
this.name = "SurfaceNotFoundError";
}
}
}

View File

@@ -0,0 +1,22 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Mesh;
import alternativa.utils.TextUtils;
/**
* Ошибка, обозначающая, что вершина уже содержится в объекте.
*/
public class VertexExistsError extends ObjectExistsError {
/**
* Создание экземпляра класса.
*
* @param vertex вершина, которая уже есть в объекте
* @param mesh объект, вызвавший ошибку
*/
public function VertexExistsError(vertex:Object = null, mesh:Mesh = null) {
super(TextUtils.insertVars("Mesh %1. Vertex with ID '%2' already exists.", mesh, vertex), vertex, mesh);
this.name = "VertexExistsError";
}
}
}

View File

@@ -0,0 +1,28 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.Vertex;
import alternativa.utils.TextUtils;
/**
* Ошибка, обозначающая, что вершина не найдена в объекте.
*/
public class VertexNotFoundError extends ObjectNotFoundError {
/**
* Создание экземпляра класса.
*
* @param vertex вершина, которая не найдена в объекте
* @param mesh объект, вызвавший ошибку
*/
public function VertexNotFoundError(vertex:Object = null, mesh:Mesh = null) {
if (vertex is Vertex) {
message = "Mesh %1. Vertex %2 not found.";
} else {
message = "Mesh %1. Vertex with ID '%2' not found.";
}
super(TextUtils.insertVars(message, mesh, vertex), vertex, mesh);
this.name = "VertexNotFoundError";
}
}
}

View File

@@ -0,0 +1,27 @@
package alternativa.engine3d.errors {
import alternativa.utils.TextUtils;
/**
* Базовый класс для ошибок 3d-engine.
*/
public class Engine3DError extends Error {
/**
* Источник ошибки - объект в котором произошла ошибка.
*/
public var source:Object;
/**
* Создание экземпляра класса.
*
* @param message описание ошибки
* @param source источник ошибки
*/
public function Engine3DError(message:String = "", source:Object = null) {
super(message);
this.source = source;
this.name = "Engine3DError";
}
}
}

View File

@@ -0,0 +1,35 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Mesh;
import alternativa.utils.TextUtils;
import alternativa.engine3d.core.Surface;
/**
* Ошибка, возникающая при попытке добавить в какой-либо объект грань, уже содержащуюся в данном объекте.
*/
public class FaceExistsError extends ObjectExistsError {
/**
* Создание экземпляра класса.
*
* @param face экземпляр или идентификатор грани, которая уже содержится в объекте
* @param source источник ошибки
*/
public function FaceExistsError(face:Object = null, source:Object = null) {
var message:String;
if (source is Mesh) {
message = "Mesh ";
} else if (source is Surface) {
message = "Surface ";
}
if (face is Face) {
message += "%1. Face %2 already exists.";
} else {
message += "%1. Face with ID '%2' already exists.";
}
super(TextUtils.insertVars(message, source, face), face, source);
this.name = "FaceExistsError";
}
}
}

View File

@@ -0,0 +1,29 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Mesh;
import alternativa.utils.TextUtils;
/**
* Ошибка, обозначающая недостаточное количество вершин для создания грани.
* Для создания грани должно быть указано не менее трех вершин.
*/
public class FaceNeedMoreVerticesError extends Engine3DError {
/**
* Количество переданных для создания грани вершин
*/
public var count:uint;
/**
* Создание экземпляра класса.
*
* @param mesh объект, в котором произошла ошибка
* @param count количество вершин, переданное для создания грани
*/
public function FaceNeedMoreVerticesError(mesh:Mesh = null, count:uint = 0) {
super(TextUtils.insertVars("Mesh %1. %2 vertices not enough for face creation.", mesh, count), mesh);
this.count = count;
this.name = "FaceNeedMoreVerticesError";
}
}
}

View File

@@ -0,0 +1,34 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Mesh;
import alternativa.utils.TextUtils;
/**
* Ошибка, возникающая, если грань не найдена в объекте.
*/
public class FaceNotFoundError extends ObjectNotFoundError {
/**
* Создание экземпляра класса.
*
* @param face экземпляр или идентификатор грани
* @param source объект, в котором произошла ошибка
*/
public function FaceNotFoundError(face:Object = null, source:Object = null) {
var message:String;
if (source is Mesh) {
message = "Mesh ";
} else {
message = "Surface ";
}
if (face is Face) {
message += "%1. Face %2 not found.";
} else {
message += "%1. Face with ID '%2' not found.";
}
super(TextUtils.insertVars(message, source, face), face, source);
this.name = "FaceNotFoundError";
}
}
}

View File

@@ -0,0 +1,34 @@
package alternativa.engine3d.errors {
import alternativa.utils.TextUtils;
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.Surface;
/**
* Ошибка, обозначающая, что идентификатор зарезервирован и не может быть использован.
*/
public class InvalidIDError extends Engine3DError {
/**
* Зарезервированный идентификатор
*/
public var id:Object;
/**
* Создание экземпляра класса.
*
* @param id идентификатор
* @param source объект, в котором произошла ошибка
*/
public function InvalidIDError(id:Object = null, source:Object = null) {
var message:String;
if (source is Mesh) {
message = "Mesh %2. ";
} else if (source is Surface) {
message = "Surface %2. ";
}
super(TextUtils.insertVars(message + "ID %1 is reserved and cannot be used", [id, source]), source);
this.id = id;
this.name = "InvalidIDError";
}
}
}

View File

@@ -0,0 +1,29 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Object3D;
import alternativa.utils.TextUtils;
/**
* Ошибка, связанная с нарушением иерархии объектов сцены.
*/
public class Object3DHierarchyError extends Engine3DError
{
/**
* Объект сцены, нарушающий иерархию
*/
public var object:Object3D;
/**
* Создание экземпляра класса.
*
* @param object объект, нарушающий иерархию
* @param source источник ошибки
*/
public function Object3DHierarchyError(object:Object3D = null, source:Object3D = null) {
super(TextUtils.insertVars("Object3D %1. Object %2 cannot be added", source, object), source);
this.object = object;
this.name = "Object3DHierarchyError";
}
}
}

View File

@@ -0,0 +1,22 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Object3D;
import alternativa.utils.TextUtils;
/**
* Ошибка, возникающая, когда объект сцены не был найден в списке связанных с необходимым объектом сцены.
*/
public class Object3DNotFoundError extends ObjectNotFoundError {
/**
* Создание экземпляра класса.
*
* @param object ненайденный объект сцены
* @param source объект сцены, в котором произошла ошибка
*/
public function Object3DNotFoundError(object:Object3D = null, source:Object3D = null) {
super(TextUtils.insertVars("Object3D %1. Object %2 not in child list", source, object), object, source);
this.name = "Object3DNotFoundError";
}
}
}

View File

@@ -0,0 +1,26 @@
package alternativa.engine3d.errors {
/**
* Ошибка, обозначающая, что объект уже присутствует в контейнере.
*/
public class ObjectExistsError extends Engine3DError {
/**
* Экземпляр или идентификатор объекта, который уже присутствует в контейнере
*/
public var object:Object;
/**
* Создание экземпляра класса.
*
* @param message описание ошибки
* @param object объект, который уже присутствует в контейнере
* @param source объект, вызвавший ошибку
*/
public function ObjectExistsError(message:String = "", object:Object = null, source:Object = null) {
super(message, source);
this.object = object;
this.name = "ObjectExistsError";
}
}
}

View File

@@ -0,0 +1,26 @@
package alternativa.engine3d.errors {
/**
* Необходимый объект не был найден в контейнере.
*/
public class ObjectNotFoundError extends Engine3DError {
/**
* Объект, который отсутствует в контейнере.
*/
public var object:Object;
/**
* Создание экземпляра класса.
*
* @param message описание ошибки
* @param object отсутствующий объект
* @param source объект, вызвавший ошибку
*/
public function ObjectNotFoundError(message:String = "", object:Object = null, source:Object = null) {
super(message, source);
this.object = object;
this.name = "ObjectNotFoundError";
}
}
}

View File

@@ -0,0 +1,22 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Mesh;
import alternativa.utils.TextUtils;
/**
* Ошибка, обозначающая, что поверхность уже присутствует в контейнере.
*/
public class SurfaceExistsError extends ObjectExistsError {
/**
* Создание экземпляра класса.
*
* @param surface поверхность, которая уже присутствует в контейнере
* @param mesh объект, вызвавший ошибку
*/
public function SurfaceExistsError(surface:Object = null, mesh:Mesh = null) {
super(TextUtils.insertVars("Mesh %1. Surface with ID '%2' already exists.", mesh, surface), surface, mesh);
this.name = "SurfaceExistsError";
}
}
}

View File

@@ -0,0 +1,31 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.Surface;
import alternativa.utils.TextUtils;
/**
* Ошибка, обозначающая, что поверхность не найдена в контейнере.
*/
public class SurfaceNotFoundError extends ObjectNotFoundError {
/**
* Создание экземпляра класса.
*
* @param surface поверхность, которая отсутствует в объекте
* @param mesh объект, который вызвал ошибку
*/
public function SurfaceNotFoundError(surface:Object = null, mesh:Mesh = null) {
if (mesh == null) {
}
if (surface is Surface) {
message = "Mesh %1. Surface %2 not found.";
} else {
message = "Mesh %1. Surface with ID '%2' not found.";
}
super(TextUtils.insertVars(message, mesh, surface), surface, mesh);
this.name = "SurfaceNotFoundError";
}
}
}

View File

@@ -0,0 +1,22 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Mesh;
import alternativa.utils.TextUtils;
/**
* Ошибка, обозначающая, что вершина уже содержится в объекте.
*/
public class VertexExistsError extends ObjectExistsError {
/**
* Создание экземпляра класса.
*
* @param vertex вершина, которая уже есть в объекте
* @param mesh объект, вызвавший ошибку
*/
public function VertexExistsError(vertex:Object = null, mesh:Mesh = null) {
super(TextUtils.insertVars("Mesh %1. Vertex with ID '%2' already exists.", mesh, vertex), vertex, mesh);
this.name = "VertexExistsError";
}
}
}

View File

@@ -0,0 +1,28 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.Vertex;
import alternativa.utils.TextUtils;
/**
* Ошибка, обозначающая, что вершина не найдена в объекте.
*/
public class VertexNotFoundError extends ObjectNotFoundError {
/**
* Создание экземпляра класса.
*
* @param vertex вершина, которая не найдена в объекте
* @param mesh объект, вызвавший ошибку
*/
public function VertexNotFoundError(vertex:Object = null, mesh:Mesh = null) {
if (vertex is Vertex) {
message = "Mesh %1. Vertex %2 not found.";
} else {
message = "Mesh %1. Vertex with ID '%2' not found.";
}
super(TextUtils.insertVars(message, mesh, vertex), vertex, mesh);
this.name = "VertexNotFoundError";
}
}
}

View File

@@ -0,0 +1,35 @@
K 25
svn:wc:ra_dav:version-url
V 99
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/loaders
END
Loader3DS.as
K 25
svn:wc:ra_dav:version-url
V 112
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/loaders/Loader3DS.as
END
LoaderOBJ.as
K 25
svn:wc:ra_dav:version-url
V 112
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/loaders/LoaderOBJ.as
END
LoaderMTL.as
K 25
svn:wc:ra_dav:version-url
V 112
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/loaders/LoaderMTL.as
END
MTLTextureMapInfo.as
K 25
svn:wc:ra_dav:version-url
V 120
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/loaders/MTLTextureMapInfo.as
END
MaterialInfo.as
K 25
svn:wc:ra_dav:version-url
V 115
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/loaders/MaterialInfo.as
END

View File

@@ -0,0 +1,88 @@
8
dir
46043
http://svndev.alternativaplatform.com/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/loaders
http://svndev.alternativaplatform.com
2008-09-08T06:50:26.103978Z
468
mike
svn:special svn:externals svn:needs-lock
d9e2387a-1f3e-40e2-b57f-9df5970a2fa5
Loader3DS.as
file
2010-10-28T04:31:16.000000Z
00968101ff4527373d553d422e7330fb
2008-09-08T06:50:26.103978Z
468
mike
LoaderOBJ.as
file
2010-10-28T04:31:16.000000Z
fbea6bb68b725694c17d00505a0095e6
2008-09-08T06:50:26.103978Z
468
mike
LoaderMTL.as
file
2010-10-28T04:31:16.000000Z
8323b7d5fbc1556a49d9fd6d8af752c1
2008-09-08T06:50:26.103978Z
468
mike
MTLTextureMapInfo.as
file
2010-10-28T04:31:16.000000Z
42806a924c6d67153cb53141ba5e2737
2008-08-25T13:44:47.077292Z
176
int
MaterialInfo.as
file
2010-10-28T04:31:16.000000Z
8010397834f3e9cd80b825cbbf4983ec
2008-08-25T13:44:47.077292Z
176
int

View File

@@ -0,0 +1 @@
8

View File

@@ -0,0 +1,285 @@
package alternativa.engine3d.loaders {
import alternativa.engine3d.*;
import alternativa.types.Map;
import alternativa.utils.ColorUtils;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.geom.Point;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.system.LoaderContext;
use namespace alternativa3d;
/**
* @private
* Загрузчик библиотеки материалов из файлов в формате MTL material format (Lightwave, OBJ).
* <p>
* На данный момент обеспечивается загрузка цвета, прозрачности и диффузной текстуры материала.
*/
internal class LoaderMTL extends EventDispatcher {
private static const COMMENT_CHAR:String = "#";
private static const CMD_NEW_MATERIAL:String = "newmtl";
private static const CMD_DIFFUSE_REFLECTIVITY:String = "Kd";
private static const CMD_DISSOLVE:String = "d";
private static const CMD_MAP_DIFFUSE:String = "map_Kd";
private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/;
private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/;
private static const REGEXP_SPLIT_LINE:RegExp = /\s+/;
// Загрузчик файла MTL
private var fileLoader:URLLoader;
// Загрузчик файлов текстур
private var bitmapLoader:Loader;
// Контекст загрузки для bitmapLoader
private var loaderContext:LoaderContext;
// Базовый URL файла MTL
private var baseUrl:String;
// Библиотека загруженных материалов
private var _library:Map;
// Список материалов, имеющих диффузные текстуры
private var diffuseMaps:Map;
// Имя текущего материала
private var materialName:String;
// параметры текущего материала
private var currentMaterialInfo:MaterialInfo = new MaterialInfo();
alternativa3d static var stubBitmapData:BitmapData;
/**
* Создаёт новый экземпляр класса.
*/
public function LoaderMTL() {
}
/**
* Прекращение текущей загрузки.
*/
public function close():void {
try {
fileLoader.close();
} catch (e:Error) {
}
}
/**
* Библиотека материалов. Ключами являются наименования материалов, значениями -- объекты, наследники класса
* <code>alternativa.engine3d.loaders.MaterialInfo</code>.
* @see alternativa.engine3d.loaders.MaterialInfo
*/
public function get library():Map {
return _library;
}
/**
* Метод выполняет загрузку файла материалов, разбор его содержимого, загрузку текстур при необходимости и
* формирование библиотеки материалов. После окончания работы метода посылается сообщение
* <code>Event.COMPLETE</code> и становится доступна библиотека материалов через свойство <code>library</code>.
* <p>
* При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения <code>IOErrorEvent.IO_ERROR</code> и
* <code>SecurityErrorEvent.SECURITY_ERROR</code> соответственно.
* <p>
* Если происходит ошибка при загрузке файла текстуры, то соответствующая текстура заменяется на текстуру-заглушку.
* <p>
* @param url URL MTL-файла
* @param loaderContext LoaderContext для загрузки файлов текстур
*
* @see #library
*/
public function load(url:String, loaderContext:LoaderContext = null):void {
this.loaderContext = loaderContext;
baseUrl = url.substring(0, url.lastIndexOf("/") + 1);
if (fileLoader == null) {
fileLoader = new URLLoader();
fileLoader.addEventListener(Event.COMPLETE, parseMTLFile);
fileLoader.addEventListener(IOErrorEvent.IO_ERROR, onError);
fileLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
bitmapLoader = new Loader();
bitmapLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onBitmapLoadComplete);
bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onBitmapLoadComplete);
bitmapLoader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onBitmapLoadComplete);
}
try {
fileLoader.close();
bitmapLoader.close();
} catch (e:Error) {
// Пропуск ошибки при попытке закрытия неактивных загрузчиков
}
fileLoader.load(new URLRequest(url));
}
/**
* Разбор содержимого загруженного файла материалов.
*/
private function parseMTLFile(e:Event = null):void {
var lines:Array = fileLoader.data.split(REGEXP_SPLIT_FILE);
_library = new Map();
diffuseMaps = new Map();
for each (var line:String in lines) {
parseLine(line);
}
defineMaterial();
if (diffuseMaps.isEmpty()) {
// Текстур нет, загрузка окончена
dispatchEvent(new Event(Event.COMPLETE));
} else {
// Загрузка файлов текстур
loadNextBitmap();
}
}
/**
* Разбор строки файла.
*
* @param line строка файла
*/
private function parseLine(line:String):void {
line = line.replace(REGEXP_TRIM,"$1")
if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) {
return;
}
var parts:Array = line.split(REGEXP_SPLIT_LINE);
switch (parts[0]) {
case CMD_NEW_MATERIAL:
defineMaterial(parts);
break;
case CMD_DIFFUSE_REFLECTIVITY:
readDiffuseReflectivity(parts);
break;
case CMD_DISSOLVE:
readAlpha(parts);
break;
case CMD_MAP_DIFFUSE:
parseDiffuseMapLine(parts);
break;
}
}
/**
* Определение нового материала.
*/
private function defineMaterial(parts:Array = null):void {
if (materialName != null) {
_library[materialName] = currentMaterialInfo;
}
if (parts != null) {
materialName = parts[1];
currentMaterialInfo = new MaterialInfo();
}
}
/**
* Чтение коэффициентов диффузного отражения. Считываются только коэффициенты, заданные в формате r g b. Для текущей
* версии движка данные коэффициенты преобразуются в цвет материала.
*/
private function readDiffuseReflectivity(parts:Array):void {
var r:Number = Number(parts[1]);
// Проверка, заданы ли коэффициенты в виде r g b
if (!isNaN(r)) {
var g:Number = Number(parts[2]);
var b:Number = Number(parts[3]);
currentMaterialInfo.color = ColorUtils.rgb(255 * r, 255 * g, 255 * b);
}
}
/**
* Чтение коэффициента непрозрачности. Считывается только коэффициент, заданный числом
* (не поддерживается параметр -halo).
*/
private function readAlpha(parts:Array):void {
var alpha:Number = Number(parts[1]);
if (!isNaN(alpha)) {
currentMaterialInfo.alpha = alpha;
}
}
/**
* Разбор строки, задающей текстурную карту для диффузного отражения.
*/
private function parseDiffuseMapLine(parts:Array):void {
var info:MTLTextureMapInfo = MTLTextureMapInfo.parse(parts);
diffuseMaps[materialName] = info;
}
/**
* Загрузка файла следующей текстуры.
*/
private function loadNextBitmap():void {
// Установка имени текущего текстурного материала, для которого выполняется загрузка текстуры
for (materialName in diffuseMaps) {
break;
}
bitmapLoader.load(new URLRequest(baseUrl + diffuseMaps[materialName].fileName), loaderContext);
}
/**
*
*/
private function createStubBitmap():void {
if (stubBitmapData == null) {
var size:uint = 10;
stubBitmapData = new BitmapData(size, size, false, 0);
for (var i:uint = 0; i < size; i++) {
for (var j:uint = 0; j < size; j+=2) {
stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF);
}
}
}
}
/**
* Обработка результата загрузки файла текстуры.
*/
private function onBitmapLoadComplete(e:Event):void {
var bmd:BitmapData;
if (e is ErrorEvent) {
if (stubBitmapData == null) {
createStubBitmap();
}
bmd = stubBitmapData;
} else {
bmd = Bitmap(bitmapLoader.content).bitmapData;
}
var mtlInfo:MTLTextureMapInfo = diffuseMaps[materialName];
delete diffuseMaps[materialName];
var info:MaterialInfo = _library[materialName];
info.bitmapData = bmd;
info.repeat = mtlInfo.repeat;
info.mapOffset = new Point(mtlInfo.offsetU, mtlInfo.offsetV);
info.mapSize = new Point(mtlInfo.sizeU, mtlInfo.sizeV);
info.textureFileName = mtlInfo.fileName;
if (diffuseMaps.isEmpty()) {
dispatchEvent(new Event(Event.COMPLETE));
} else {
loadNextBitmap();
}
}
/**
*
* @param e
*/
private function onError(e:IOErrorEvent):void {
dispatchEvent(e);
}
}
}

View File

@@ -0,0 +1,509 @@
package alternativa.engine3d.loaders {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Surface;
import alternativa.engine3d.core.Vertex;
import alternativa.engine3d.materials.FillMaterial;
import alternativa.engine3d.materials.TextureMaterial;
import alternativa.engine3d.materials.TextureMaterialPrecision;
import alternativa.types.Map;
import alternativa.types.Point3D;
import alternativa.types.Texture;
import flash.display.BlendMode;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.geom.Point;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.system.LoaderContext;
use namespace alternativa3d;
/**
* Загрузчик моделей из файла в формате OBJ. Так как OBJ не поддерживает иерархию объектов, все загруженные
* модели помещаются в один контейнер <code>Object3D</code>.
* <p>
* Поддерживаюся следующие команды формата OBJ:
* <p>
* <table border="1" style="border-collapse: collapse">
* <tr>
* <th width="30%">Команда</th>
* <th>Описание</th>
* <th>Действие</th></tr>
* <tr>
* <td>o object_name</td>
* <td>Объявление нового объекта с именем object_name</td>
* <td>Если для текущего объекта были определены грани, то команда создаёт новый текущий объект с указанным именем,
* иначе у текущего объекта просто меняется имя на указанное.</td>
* </tr>
* <tr>
* <td>v x y z</td>
* <td>Объявление вершины с координатами x y z</td>
* <td>Вершина помещается в общий список вершин сцены для дальнейшего использования</td>
* </tr>
* <tr>
* <td>vt u [v]</td>
* <td>Объявление текстурной вершины с координатами u v</td>
* <td>Вершина помещается в общий список текстурных вершин сцены для дальнейшего использования</td>
* </tr>
* <tr>
* <td>f v0[/vt0] v1[/vt1] ... vN[/vtN]</td>
* <td>Объявление грани, состоящей из указанных вершин и опционально имеющую заданные текстурные координаты для вершин.</td>
* <td>Грань добавляется к текущему активному объекту. Если есть активный материал, то грань также добавляется в поверхность
* текущего объекта, соответствующую текущему материалу.</td>
* </tr>
* <tr>
* <td>usemtl material_name</td>
* <td>Установка текущего материала с именем material_name</td>
* <td>С момента установки текущего материала все грани, создаваемые в текущем объекте будут помещаться в поверхность,
* соотвествующую этому материалу и имеющую идентификатор, совпадающий с его именем.</td>
* </tr>
* <tr>
* <td>mtllib file1 file2 ...</td>
* <td>Объявление файлов, содержащих определения материалов</td>
* <td>Выполняется загрузка файлов и формирование библиотеки материалов</td>
* </tr>
* </table>
*
* <p>
* Пример использования:
* <pre>
* var loader:LoaderOBJ = new LoaderOBJ();
* loader.addEventListener(Event.COMPLETE, onLoadingComplete);
* loader.load("foo.obj");
*
* function onLoadingComplete(e:Event):void {
* scene.root.addChild(e.target.content);
* }
* </pre>
*/
public class LoaderOBJ extends EventDispatcher {
private static const COMMENT_CHAR:String = "#";
private static const CMD_OBJECT_NAME:String = "o";
private static const CMD_GROUP_NAME:String = "g";
private static const CMD_VERTEX:String = "v";
private static const CMD_TEXTURE_VERTEX:String = "vt";
private static const CMD_FACE:String = "f";
private static const CMD_MATERIAL_LIB:String = "mtllib";
private static const CMD_USE_MATERIAL:String = "usemtl";
private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/;
private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/;
private static const REGEXP_SPLIT_LINE:RegExp = /\s+/;
private var basePath:String;
private var objLoader:URLLoader;
private var mtlLoader:LoaderMTL;
private var loaderContext:LoaderContext;
private var loadMaterials:Boolean;
// Объект, содержащий все определённые в obj файле объекты
private var _content:Object3D;
// Текущий конструируемый объект
private var currentObject:Mesh;
// Стартовый индекс вершины в глобальном массиве вершин для текущего объекта
private var vIndexStart:int = 0;
// Стартовый индекс текстурной вершины в глобальном массиве текстурных вершин для текущего объекта
private var vtIndexStart:int = 0;
// Глобальный массив вершин, определённых во входном файле
private var globalVertices:Array;
// Глобальный массив текстурных вершин, определённых во входном файле
private var globalTextureVertices:Array;
// Имя текущего активного материала. Если значение равно null, то активного материала нет.
private var currentMaterialName:String;
// Массив граней текущего объекта, которым назначен текущий материал
private var materialFaces:Array;
// Массив имён файлов, содержащих определения материалов
private var materialFileNames:Array;
private var currentMaterialFileIndex:int;
private var materialLibrary:Map;
/**
* Сглаживание текстур при увеличении масштаба.
*
* @see alternativa.engine3d.materials.TextureMaterial
*/
public var smooth:Boolean = false;
/**
* Режим наложения цвета для создаваемых текстурных материалов.
*
* @see alternativa.engine3d.materials.TextureMaterial
*/
public var blendMode:String = BlendMode.NORMAL;
/**
* Точность перспективной коррекции для создаваемых текстурных материалов.
*
* @see alternativa.engine3d.materials.TextureMaterial
*/
public var precision:Number = TextureMaterialPrecision.MEDIUM;
/**
* Устанавливаемый уровень мобильности загруженных объектов.
*/
public var mobility:int = 0;
/**
* При установленном значении <code>true</code> выполняется преобразование координат геометрических вершин посредством
* поворота на 90 градусов относительно оси X. Смысл флага в преобразовании системы координат, в которой вверх направлена
* ось <code>Y</code>, в систему координат, использующуюся в Alternativa3D (вверх направлена ось <code>Z</code>).
*/
public var rotateModel:Boolean;
/**
* Создаёт новый экземпляр загрузчика.
*/
public function LoaderOBJ() {
}
/**
* Контейнер, содержащий все загруженные из OBJ-файла модели.
*/
public function get content():Object3D {
return _content;
}
/**
* Прекращение текущей загрузки.
*/
public function close():void {
try {
objLoader.close();
} catch (e:Error) {
}
mtlLoader.close();
}
/**
* Загрузка сцены из OBJ-файла по указанному адресу. По окончании загрузки посылается сообщение <code>Event.COMPLETE</code>,
* после чего контейнер с загруженными объектами становится доступным через свойство <code>content</code>.
* <p>
* При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения <code>IOErrorEvent.IO_ERROR</code> и
* <code>SecurityErrorEvent.SECURITY_ERROR</code> соответственно.
* <p>
* @param url URL OBJ-файла
* @param loadMaterials флаг загрузки материалов. Если указано значение <code>true</code>, будут обработаны все файлы
* материалов, указанные в исходном OBJ-файле.
* @param context LoaderContext для загрузки файлов текстур
*
* @see #content
*/
public function load(url:String, loadMaterials:Boolean = true, context:LoaderContext = null):void {
_content = null;
this.loadMaterials = loadMaterials;
this.loaderContext = context;
basePath = url.substring(0, url.lastIndexOf("/") + 1);
if (objLoader == null) {
objLoader = new URLLoader();
objLoader.addEventListener(Event.COMPLETE, onObjLoadComplete);
objLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError);
objLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError);
} else {
close();
}
objLoader.load(new URLRequest(url));
}
/**
* Обработка окончания загрузки obj файла.
*
* @param e
*/
private function onObjLoadComplete(e:Event):void {
parse(objLoader.data);
}
/**
* Обработка ошибки при загрузке.
*
* @param e
*/
private function onObjLoadError(e:ErrorEvent):void {
dispatchEvent(e);
}
/**
* Метод выполняет разбор данных, полученных из obj файла.
*
* @param s содержимое obj файла
* @param materialLibrary библиотека материалов
* @return объект, содержащий все трёхмерные объекты, определённые в obj файле
*/
private function parse(data:String):void {
_content = new Object3D();
currentObject = new Mesh();
currentObject.mobility = mobility;
_content.addChild(currentObject);
globalVertices = new Array();
globalTextureVertices = new Array();
materialFileNames = new Array();
var lines:Array = data.split(REGEXP_SPLIT_FILE);
for each (var line:String in lines) {
parseLine(line);
}
moveFacesToSurface();
// Вся геометрия загружена и сформирована. Выполняется загрузка информации о материалах.
if (loadMaterials && materialFileNames.length > 0) {
loadMaterialsLibrary();
} else {
dispatchEvent(new Event(Event.COMPLETE));
}
}
/**
*
*/
private function parseLine(line:String):void {
line = line.replace(REGEXP_TRIM,"$1");
if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) {
return;
}
var parts:Array = line.split(REGEXP_SPLIT_LINE);
switch (parts[0]) {
// Объявление нового объекта
case CMD_OBJECT_NAME:
defineObject(parts[1]);
break;
// Объявление вершины
case CMD_VERTEX:
globalVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3])));
break;
// Объявление текстурной вершины
case CMD_TEXTURE_VERTEX:
globalTextureVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3])));
break;
// Объявление грани
case CMD_FACE:
createFace(parts);
break;
case CMD_MATERIAL_LIB:
storeMaterialFileNames(parts);
break;
case CMD_USE_MATERIAL:
setNewMaterial(parts);
break;
}
}
/**
* Объявление нового объекта.
*
* @param objectName имя объекта
*/
private function defineObject(objectName:String):void {
if (currentObject.faces.length == 0) {
// Если у текущего объекта нет граней, то он остаётся текущим, но меняется имя
currentObject.name = objectName;
} else {
// Если у текущего объекта есть грани, то обявление нового имени создаёт новый объект
moveFacesToSurface();
currentObject = new Mesh(objectName);
currentObject.mobility = mobility;
_content.addChild(currentObject);
}
vIndexStart = globalVertices.length;
vtIndexStart = globalTextureVertices.length;
}
/**
* Создание грани в текущем объекте.
*
* @param parts массив, содержащий индексы вершин грани, начиная с элемента с индексом 1
*/
private function createFace(parts:Array):void {
// Стартовый индекс вершины в объекте для добавляемой грани
var startVertexIndex:int = currentObject.vertices.length;
// Создание вершин в объекте
var faceVertexCount:int = parts.length - 1;
var vtIndices:Array = new Array(3);
// Массив идентификаторов вершин грани
var faceVertices:Array = new Array(faceVertexCount);
for (var i:int = 0; i < faceVertexCount; i++) {
var indices:Array = parts[i + 1].split("/");
// Создание вершины
var vIdx:int = int(indices[0]);
// Если индекс положительный, то его значение уменьшается на единицу, т.к. в obj формате индексация начинается с 1.
// Если индекс отрицательный, то выполняется смещение на его значение назад от стартового глобального индекса вершин для текущего объекта.
var actualIndex:int = vIdx > 0 ? vIdx - 1 : vIndexStart + vIdx;
var vertex:Vertex = currentObject.vertices[actualIndex];
// Если вершины нет в объекте, она добавляется
if (vertex == null) {
var p:Point3D = globalVertices[actualIndex];
if (rotateModel) {
// В формате obj направление "вверх" совпадает с осью Y, поэтому выполняется поворот координат на 90 градусов по оси X
vertex = currentObject.createVertex(p.x, -p.z, p.y, actualIndex);
} else {
vertex = currentObject.createVertex(p.x, p.y, p.z, actualIndex);
}
}
faceVertices[i] = vertex;
// Запись индекса текстурной вершины
if (i < 3) {
vtIndices[i] = int(indices[1]);
}
}
// Создание грани
var face:Face = currentObject.createFace(faceVertices, currentObject.faces.length);
// Установка uv координат
if (vtIndices[0] != 0) {
p = globalTextureVertices[vtIndices[0] - 1];
face.aUV = new Point(p.x, p.y);
p = globalTextureVertices[vtIndices[1] - 1];
face.bUV = new Point(p.x, p.y);
p = globalTextureVertices[vtIndices[2] - 1];
face.cUV = new Point(p.x, p.y);
}
// Если есть активный материал, то грань заносится в массив для последующего формирования поверхности в объекте
if (currentMaterialName != null) {
materialFaces.push(face);
}
}
/**
* Загрузка библиотек материалов.
*
* @param parts массив, содержащий имена файлов материалов, начиная с элемента с индексом 1
*/
private function storeMaterialFileNames(parts:Array):void {
for (var i:int = 1; i < parts.length; i++) {
materialFileNames.push(parts[i]);
}
}
/**
* Установка нового текущего материала.
*
* @param parts массив, во втором элементе которого содержится имя материала
*/
private function setNewMaterial(parts:Array):void {
// Все сохранённые грани добавляются в соответствующую поверхность текущего объекта
moveFacesToSurface();
// Установка нового текущего материала
currentMaterialName = parts[1];
}
/**
* Добавление всех граней с текущим материалом в поверхность с идентификатором, совпадающим с именем материала.
*/
private function moveFacesToSurface():void {
if (currentMaterialName != null && materialFaces.length > 0) {
if (currentObject.hasSurface(currentMaterialName)) {
// При наличии поверхности с таким идентификатором, грани добавляются в неё
var surface:Surface = currentObject.getSurfaceById(currentMaterialName);
for each (var face:* in materialFaces) {
surface.addFace(face);
}
} else {
// При отсутствии поверхности с таким идентификатором, создатся новая поверхность
currentObject.createSurface(materialFaces, currentMaterialName);
}
}
materialFaces = [];
}
/**
* Загрузка материалов.
*/
private function loadMaterialsLibrary():void {
if (mtlLoader == null) {
mtlLoader = new LoaderMTL();
mtlLoader.addEventListener(Event.COMPLETE, onMaterialFileLoadComplete);
mtlLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError);
mtlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError);
}
materialLibrary = new Map();
currentMaterialFileIndex = -1;
loadNextMaterialFile();
}
/**
* Обработка успешной загрузки библиотеки материалов.
*/
private function onMaterialFileLoadComplete(e:Event):void {
materialLibrary.concat(mtlLoader.library);
// Загрузка следующего файла материалов
loadNextMaterialFile();
}
/**
*
*/
private function loadNextMaterialFile():void {
currentMaterialFileIndex++;
if (currentMaterialFileIndex == materialFileNames.length) {
setMaterials();
dispatchEvent(new Event(Event.COMPLETE));
} else {
mtlLoader.load(basePath + materialFileNames[currentMaterialFileIndex], loaderContext);
}
}
/**
* Установка материалов.
*/
private function setMaterials():void {
if (materialLibrary != null) {
for (var objectKey:* in _content.children) {
var object:Mesh = objectKey;
for (var surfaceKey:* in object.surfaces) {
var surface:Surface = object.surfaces[surfaceKey];
// Поверхности имеют идентификаторы, соответствующие именам материалов
var materialInfo:MaterialInfo = materialLibrary[surfaceKey];
if (materialInfo != null) {
if (materialInfo.bitmapData == null) {
surface.material = new FillMaterial(materialInfo.color, materialInfo.alpha, blendMode);
} else {
surface.material = new TextureMaterial(new Texture(materialInfo.bitmapData, materialInfo.textureFileName), materialInfo.alpha, materialInfo.repeat, (materialInfo.bitmapData != LoaderMTL.stubBitmapData) ? smooth : false, blendMode, -1, 0, precision);
transformUVs(surface, materialInfo.mapOffset, materialInfo.mapSize);
}
}
}
}
}
}
/**
* Метод выполняет преобразование UV-координат текстурированных граней. В связи с тем, что в формате MRL предусмотрено
* масштабирование и смещение текстурной карты в UV-пространстве, а в движке такой фунциональности нет, необходимо
* эмулировать преобразования текстуры преобразованием UV-координат граней. Преобразования выполняются исходя из предположения,
* что текстурное пространство сначала масштабируется относительно центра, а затем сдвигается на указанную величину
* смещения.
*
* @param surface поверхность, грани которой обрабатываюся
* @param mapOffset смещение текстурной карты. Значение mapOffset.x указывает смещение по U, значение mapOffset.y
* указывает смещение по V.
* @param mapSize коэффициенты масштабирования текстурной карты. Значение mapSize.x указывает коэффициент масштабирования
* по оси U, значение mapSize.y указывает коэффициент масштабирования по оси V.
*/
private function transformUVs(surface:Surface, mapOffset:Point, mapSize:Point):void {
for (var key:* in surface.faces) {
var face:Face = key;
var uv:Point = face.aUV;
if (uv != null) {
uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
face.aUV = uv;
uv = face.bUV;
uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
face.bUV = uv;
uv = face.cUV;
uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
face.cUV = uv;
}
}
}
}
}

View File

@@ -0,0 +1,120 @@
package alternativa.engine3d.loaders {
/**
* @private
* Класс содержит информацию о текстуре в формате MTL material format (Lightwave, OBJ) и функционал для разбора
* описания текстуры.
* Описание формата можно посмотреть по адресу: http://local.wasp.uwa.edu.au/~pbourke/dataformats/mtl/
*/
internal class MTLTextureMapInfo {
// Ассоциация параметров команды объявления текстуры и методов для их чтения
private static const optionReaders:Object = {
"-clamp": clampReader,
"-o": offsetReader,
"-s": sizeReader,
"-blendu": stubReader,
"-blendv": stubReader,
"-bm": stubReader,
"-boost": stubReader,
"-cc": stubReader,
"-imfchan": stubReader,
"-mm": stubReader,
"-t": stubReader,
"-texres": stubReader
};
// Смещение в текстурном пространстве
public var offsetU:Number = 0;
public var offsetV:Number = 0;
public var offsetW:Number = 0;
// Масштабирование текстурного пространства
public var sizeU:Number = 1;
public var sizeV:Number = 1;
public var sizeW:Number = 1;
// Флаг повторения текстуры
public var repeat:Boolean = true;
// Имя файла текстуры
public var fileName:String;
/**
* Метод выполняет разбор данных о текстуре.
*
* @param parts Данные о текстуре. Массив должен содержать части разделённой по пробелам входной строки MTL-файла.
* @return объект, содержащий данные о текстуре
*/
public static function parse(parts:Array):MTLTextureMapInfo {
var info:MTLTextureMapInfo = new MTLTextureMapInfo();
// Начальное значение индекса единица, т.к. первый элемент массива содержит тип текстуры
var index:int = 1;
var reader:Function;
// Чтение параметров текстуры
while ((reader = optionReaders[parts[index]]) != null) {
index = reader(index, parts, info);
}
// Если не было ошибок, последний элемент массива должен содержать имя файла текстуры
info.fileName = parts[index];
return info;
}
/**
* Читатель-заглушка. Пропускает все неподдерживаемые параметры.
*/
private static function stubReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
index++;
var maxIndex:int = parts.length - 1;
while ((MTLTextureMapInfo.optionReaders[parts[index]] == null) && (index < maxIndex)) {
index++;
}
return index;
}
/**
* Метод чтения параметров масштабирования текстурного пространства.
*/
private static function sizeReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
info.sizeU = Number(parts[index + 1]);
index += 2;
var value:Number = Number(parts[index]);
if (!isNaN(value)) {
info.sizeV = value;
index++;
value = Number(parts[index]);
if (!isNaN(value)) {
info.sizeW = value;
index++;
}
}
return index;
}
/**
* Метод чтения параметров смещения текстуры.
*/
private static function offsetReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
info.offsetU = Number(parts[index + 1]);
index += 2;
var value:Number = Number(parts[index]);
if (!isNaN(value)) {
info.offsetV = value;
index++;
value = Number(parts[index]);
if (!isNaN(value)) {
info.offsetW = value;
index++;
}
}
return index;
}
/**
* Метод чтения параметра повторения текстуры.
*/
private static function clampReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
info.repeat = parts[index + 1] == "off";
return index + 2;
}
}
}

View File

@@ -0,0 +1,20 @@
package alternativa.engine3d.loaders {
import flash.display.BitmapData;
import flash.geom.Point;
/**
* @private
* Класс содержит обобщённую информацию о материале.
*/
internal class MaterialInfo {
public var color:uint;
public var alpha:Number;
public var textureFileName:String;
public var bitmapData:BitmapData;
public var repeat:Boolean;
public var mapOffset:Point;
public var mapSize:Point;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,285 @@
package alternativa.engine3d.loaders {
import alternativa.engine3d.*;
import alternativa.types.Map;
import alternativa.utils.ColorUtils;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.geom.Point;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.system.LoaderContext;
use namespace alternativa3d;
/**
* @private
* Загрузчик библиотеки материалов из файлов в формате MTL material format (Lightwave, OBJ).
* <p>
* На данный момент обеспечивается загрузка цвета, прозрачности и диффузной текстуры материала.
*/
internal class LoaderMTL extends EventDispatcher {
private static const COMMENT_CHAR:String = "#";
private static const CMD_NEW_MATERIAL:String = "newmtl";
private static const CMD_DIFFUSE_REFLECTIVITY:String = "Kd";
private static const CMD_DISSOLVE:String = "d";
private static const CMD_MAP_DIFFUSE:String = "map_Kd";
private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/;
private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/;
private static const REGEXP_SPLIT_LINE:RegExp = /\s+/;
// Загрузчик файла MTL
private var fileLoader:URLLoader;
// Загрузчик файлов текстур
private var bitmapLoader:Loader;
// Контекст загрузки для bitmapLoader
private var loaderContext:LoaderContext;
// Базовый URL файла MTL
private var baseUrl:String;
// Библиотека загруженных материалов
private var _library:Map;
// Список материалов, имеющих диффузные текстуры
private var diffuseMaps:Map;
// Имя текущего материала
private var materialName:String;
// параметры текущего материала
private var currentMaterialInfo:MaterialInfo = new MaterialInfo();
alternativa3d static var stubBitmapData:BitmapData;
/**
* Создаёт новый экземпляр класса.
*/
public function LoaderMTL() {
}
/**
* Прекращение текущей загрузки.
*/
public function close():void {
try {
fileLoader.close();
} catch (e:Error) {
}
}
/**
* Библиотека материалов. Ключами являются наименования материалов, значениями -- объекты, наследники класса
* <code>alternativa.engine3d.loaders.MaterialInfo</code>.
* @see alternativa.engine3d.loaders.MaterialInfo
*/
public function get library():Map {
return _library;
}
/**
* Метод выполняет загрузку файла материалов, разбор его содержимого, загрузку текстур при необходимости и
* формирование библиотеки материалов. После окончания работы метода посылается сообщение
* <code>Event.COMPLETE</code> и становится доступна библиотека материалов через свойство <code>library</code>.
* <p>
* При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения <code>IOErrorEvent.IO_ERROR</code> и
* <code>SecurityErrorEvent.SECURITY_ERROR</code> соответственно.
* <p>
* Если происходит ошибка при загрузке файла текстуры, то соответствующая текстура заменяется на текстуру-заглушку.
* <p>
* @param url URL MTL-файла
* @param loaderContext LoaderContext для загрузки файлов текстур
*
* @see #library
*/
public function load(url:String, loaderContext:LoaderContext = null):void {
this.loaderContext = loaderContext;
baseUrl = url.substring(0, url.lastIndexOf("/") + 1);
if (fileLoader == null) {
fileLoader = new URLLoader();
fileLoader.addEventListener(Event.COMPLETE, parseMTLFile);
fileLoader.addEventListener(IOErrorEvent.IO_ERROR, onError);
fileLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
bitmapLoader = new Loader();
bitmapLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onBitmapLoadComplete);
bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onBitmapLoadComplete);
bitmapLoader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onBitmapLoadComplete);
}
try {
fileLoader.close();
bitmapLoader.close();
} catch (e:Error) {
// Пропуск ошибки при попытке закрытия неактивных загрузчиков
}
fileLoader.load(new URLRequest(url));
}
/**
* Разбор содержимого загруженного файла материалов.
*/
private function parseMTLFile(e:Event = null):void {
var lines:Array = fileLoader.data.split(REGEXP_SPLIT_FILE);
_library = new Map();
diffuseMaps = new Map();
for each (var line:String in lines) {
parseLine(line);
}
defineMaterial();
if (diffuseMaps.isEmpty()) {
// Текстур нет, загрузка окончена
dispatchEvent(new Event(Event.COMPLETE));
} else {
// Загрузка файлов текстур
loadNextBitmap();
}
}
/**
* Разбор строки файла.
*
* @param line строка файла
*/
private function parseLine(line:String):void {
line = line.replace(REGEXP_TRIM,"$1")
if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) {
return;
}
var parts:Array = line.split(REGEXP_SPLIT_LINE);
switch (parts[0]) {
case CMD_NEW_MATERIAL:
defineMaterial(parts);
break;
case CMD_DIFFUSE_REFLECTIVITY:
readDiffuseReflectivity(parts);
break;
case CMD_DISSOLVE:
readAlpha(parts);
break;
case CMD_MAP_DIFFUSE:
parseDiffuseMapLine(parts);
break;
}
}
/**
* Определение нового материала.
*/
private function defineMaterial(parts:Array = null):void {
if (materialName != null) {
_library[materialName] = currentMaterialInfo;
}
if (parts != null) {
materialName = parts[1];
currentMaterialInfo = new MaterialInfo();
}
}
/**
* Чтение коэффициентов диффузного отражения. Считываются только коэффициенты, заданные в формате r g b. Для текущей
* версии движка данные коэффициенты преобразуются в цвет материала.
*/
private function readDiffuseReflectivity(parts:Array):void {
var r:Number = Number(parts[1]);
// Проверка, заданы ли коэффициенты в виде r g b
if (!isNaN(r)) {
var g:Number = Number(parts[2]);
var b:Number = Number(parts[3]);
currentMaterialInfo.color = ColorUtils.rgb(255 * r, 255 * g, 255 * b);
}
}
/**
* Чтение коэффициента непрозрачности. Считывается только коэффициент, заданный числом
* (не поддерживается параметр -halo).
*/
private function readAlpha(parts:Array):void {
var alpha:Number = Number(parts[1]);
if (!isNaN(alpha)) {
currentMaterialInfo.alpha = alpha;
}
}
/**
* Разбор строки, задающей текстурную карту для диффузного отражения.
*/
private function parseDiffuseMapLine(parts:Array):void {
var info:MTLTextureMapInfo = MTLTextureMapInfo.parse(parts);
diffuseMaps[materialName] = info;
}
/**
* Загрузка файла следующей текстуры.
*/
private function loadNextBitmap():void {
// Установка имени текущего текстурного материала, для которого выполняется загрузка текстуры
for (materialName in diffuseMaps) {
break;
}
bitmapLoader.load(new URLRequest(baseUrl + diffuseMaps[materialName].fileName), loaderContext);
}
/**
*
*/
private function createStubBitmap():void {
if (stubBitmapData == null) {
var size:uint = 10;
stubBitmapData = new BitmapData(size, size, false, 0);
for (var i:uint = 0; i < size; i++) {
for (var j:uint = 0; j < size; j+=2) {
stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF);
}
}
}
}
/**
* Обработка результата загрузки файла текстуры.
*/
private function onBitmapLoadComplete(e:Event):void {
var bmd:BitmapData;
if (e is ErrorEvent) {
if (stubBitmapData == null) {
createStubBitmap();
}
bmd = stubBitmapData;
} else {
bmd = Bitmap(bitmapLoader.content).bitmapData;
}
var mtlInfo:MTLTextureMapInfo = diffuseMaps[materialName];
delete diffuseMaps[materialName];
var info:MaterialInfo = _library[materialName];
info.bitmapData = bmd;
info.repeat = mtlInfo.repeat;
info.mapOffset = new Point(mtlInfo.offsetU, mtlInfo.offsetV);
info.mapSize = new Point(mtlInfo.sizeU, mtlInfo.sizeV);
info.textureFileName = mtlInfo.fileName;
if (diffuseMaps.isEmpty()) {
dispatchEvent(new Event(Event.COMPLETE));
} else {
loadNextBitmap();
}
}
/**
*
* @param e
*/
private function onError(e:IOErrorEvent):void {
dispatchEvent(e);
}
}
}

View File

@@ -0,0 +1,509 @@
package alternativa.engine3d.loaders {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Surface;
import alternativa.engine3d.core.Vertex;
import alternativa.engine3d.materials.FillMaterial;
import alternativa.engine3d.materials.TextureMaterial;
import alternativa.engine3d.materials.TextureMaterialPrecision;
import alternativa.types.Map;
import alternativa.types.Point3D;
import alternativa.types.Texture;
import flash.display.BlendMode;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.geom.Point;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.system.LoaderContext;
use namespace alternativa3d;
/**
* Загрузчик моделей из файла в формате OBJ. Так как OBJ не поддерживает иерархию объектов, все загруженные
* модели помещаются в один контейнер <code>Object3D</code>.
* <p>
* Поддерживаюся следующие команды формата OBJ:
* <p>
* <table border="1" style="border-collapse: collapse">
* <tr>
* <th width="30%">Команда</th>
* <th>Описание</th>
* <th>Действие</th></tr>
* <tr>
* <td>o object_name</td>
* <td>Объявление нового объекта с именем object_name</td>
* <td>Если для текущего объекта были определены грани, то команда создаёт новый текущий объект с указанным именем,
* иначе у текущего объекта просто меняется имя на указанное.</td>
* </tr>
* <tr>
* <td>v x y z</td>
* <td>Объявление вершины с координатами x y z</td>
* <td>Вершина помещается в общий список вершин сцены для дальнейшего использования</td>
* </tr>
* <tr>
* <td>vt u [v]</td>
* <td>Объявление текстурной вершины с координатами u v</td>
* <td>Вершина помещается в общий список текстурных вершин сцены для дальнейшего использования</td>
* </tr>
* <tr>
* <td>f v0[/vt0] v1[/vt1] ... vN[/vtN]</td>
* <td>Объявление грани, состоящей из указанных вершин и опционально имеющую заданные текстурные координаты для вершин.</td>
* <td>Грань добавляется к текущему активному объекту. Если есть активный материал, то грань также добавляется в поверхность
* текущего объекта, соответствующую текущему материалу.</td>
* </tr>
* <tr>
* <td>usemtl material_name</td>
* <td>Установка текущего материала с именем material_name</td>
* <td>С момента установки текущего материала все грани, создаваемые в текущем объекте будут помещаться в поверхность,
* соотвествующую этому материалу и имеющую идентификатор, совпадающий с его именем.</td>
* </tr>
* <tr>
* <td>mtllib file1 file2 ...</td>
* <td>Объявление файлов, содержащих определения материалов</td>
* <td>Выполняется загрузка файлов и формирование библиотеки материалов</td>
* </tr>
* </table>
*
* <p>
* Пример использования:
* <pre>
* var loader:LoaderOBJ = new LoaderOBJ();
* loader.addEventListener(Event.COMPLETE, onLoadingComplete);
* loader.load("foo.obj");
*
* function onLoadingComplete(e:Event):void {
* scene.root.addChild(e.target.content);
* }
* </pre>
*/
public class LoaderOBJ extends EventDispatcher {
private static const COMMENT_CHAR:String = "#";
private static const CMD_OBJECT_NAME:String = "o";
private static const CMD_GROUP_NAME:String = "g";
private static const CMD_VERTEX:String = "v";
private static const CMD_TEXTURE_VERTEX:String = "vt";
private static const CMD_FACE:String = "f";
private static const CMD_MATERIAL_LIB:String = "mtllib";
private static const CMD_USE_MATERIAL:String = "usemtl";
private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/;
private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/;
private static const REGEXP_SPLIT_LINE:RegExp = /\s+/;
private var basePath:String;
private var objLoader:URLLoader;
private var mtlLoader:LoaderMTL;
private var loaderContext:LoaderContext;
private var loadMaterials:Boolean;
// Объект, содержащий все определённые в obj файле объекты
private var _content:Object3D;
// Текущий конструируемый объект
private var currentObject:Mesh;
// Стартовый индекс вершины в глобальном массиве вершин для текущего объекта
private var vIndexStart:int = 0;
// Стартовый индекс текстурной вершины в глобальном массиве текстурных вершин для текущего объекта
private var vtIndexStart:int = 0;
// Глобальный массив вершин, определённых во входном файле
private var globalVertices:Array;
// Глобальный массив текстурных вершин, определённых во входном файле
private var globalTextureVertices:Array;
// Имя текущего активного материала. Если значение равно null, то активного материала нет.
private var currentMaterialName:String;
// Массив граней текущего объекта, которым назначен текущий материал
private var materialFaces:Array;
// Массив имён файлов, содержащих определения материалов
private var materialFileNames:Array;
private var currentMaterialFileIndex:int;
private var materialLibrary:Map;
/**
* Сглаживание текстур при увеличении масштаба.
*
* @see alternativa.engine3d.materials.TextureMaterial
*/
public var smooth:Boolean = false;
/**
* Режим наложения цвета для создаваемых текстурных материалов.
*
* @see alternativa.engine3d.materials.TextureMaterial
*/
public var blendMode:String = BlendMode.NORMAL;
/**
* Точность перспективной коррекции для создаваемых текстурных материалов.
*
* @see alternativa.engine3d.materials.TextureMaterial
*/
public var precision:Number = TextureMaterialPrecision.MEDIUM;
/**
* Устанавливаемый уровень мобильности загруженных объектов.
*/
public var mobility:int = 0;
/**
* При установленном значении <code>true</code> выполняется преобразование координат геометрических вершин посредством
* поворота на 90 градусов относительно оси X. Смысл флага в преобразовании системы координат, в которой вверх направлена
* ось <code>Y</code>, в систему координат, использующуюся в Alternativa3D (вверх направлена ось <code>Z</code>).
*/
public var rotateModel:Boolean;
/**
* Создаёт новый экземпляр загрузчика.
*/
public function LoaderOBJ() {
}
/**
* Контейнер, содержащий все загруженные из OBJ-файла модели.
*/
public function get content():Object3D {
return _content;
}
/**
* Прекращение текущей загрузки.
*/
public function close():void {
try {
objLoader.close();
} catch (e:Error) {
}
mtlLoader.close();
}
/**
* Загрузка сцены из OBJ-файла по указанному адресу. По окончании загрузки посылается сообщение <code>Event.COMPLETE</code>,
* после чего контейнер с загруженными объектами становится доступным через свойство <code>content</code>.
* <p>
* При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения <code>IOErrorEvent.IO_ERROR</code> и
* <code>SecurityErrorEvent.SECURITY_ERROR</code> соответственно.
* <p>
* @param url URL OBJ-файла
* @param loadMaterials флаг загрузки материалов. Если указано значение <code>true</code>, будут обработаны все файлы
* материалов, указанные в исходном OBJ-файле.
* @param context LoaderContext для загрузки файлов текстур
*
* @see #content
*/
public function load(url:String, loadMaterials:Boolean = true, context:LoaderContext = null):void {
_content = null;
this.loadMaterials = loadMaterials;
this.loaderContext = context;
basePath = url.substring(0, url.lastIndexOf("/") + 1);
if (objLoader == null) {
objLoader = new URLLoader();
objLoader.addEventListener(Event.COMPLETE, onObjLoadComplete);
objLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError);
objLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError);
} else {
close();
}
objLoader.load(new URLRequest(url));
}
/**
* Обработка окончания загрузки obj файла.
*
* @param e
*/
private function onObjLoadComplete(e:Event):void {
parse(objLoader.data);
}
/**
* Обработка ошибки при загрузке.
*
* @param e
*/
private function onObjLoadError(e:ErrorEvent):void {
dispatchEvent(e);
}
/**
* Метод выполняет разбор данных, полученных из obj файла.
*
* @param s содержимое obj файла
* @param materialLibrary библиотека материалов
* @return объект, содержащий все трёхмерные объекты, определённые в obj файле
*/
private function parse(data:String):void {
_content = new Object3D();
currentObject = new Mesh();
currentObject.mobility = mobility;
_content.addChild(currentObject);
globalVertices = new Array();
globalTextureVertices = new Array();
materialFileNames = new Array();
var lines:Array = data.split(REGEXP_SPLIT_FILE);
for each (var line:String in lines) {
parseLine(line);
}
moveFacesToSurface();
// Вся геометрия загружена и сформирована. Выполняется загрузка информации о материалах.
if (loadMaterials && materialFileNames.length > 0) {
loadMaterialsLibrary();
} else {
dispatchEvent(new Event(Event.COMPLETE));
}
}
/**
*
*/
private function parseLine(line:String):void {
line = line.replace(REGEXP_TRIM,"$1");
if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) {
return;
}
var parts:Array = line.split(REGEXP_SPLIT_LINE);
switch (parts[0]) {
// Объявление нового объекта
case CMD_OBJECT_NAME:
defineObject(parts[1]);
break;
// Объявление вершины
case CMD_VERTEX:
globalVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3])));
break;
// Объявление текстурной вершины
case CMD_TEXTURE_VERTEX:
globalTextureVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3])));
break;
// Объявление грани
case CMD_FACE:
createFace(parts);
break;
case CMD_MATERIAL_LIB:
storeMaterialFileNames(parts);
break;
case CMD_USE_MATERIAL:
setNewMaterial(parts);
break;
}
}
/**
* Объявление нового объекта.
*
* @param objectName имя объекта
*/
private function defineObject(objectName:String):void {
if (currentObject.faces.length == 0) {
// Если у текущего объекта нет граней, то он остаётся текущим, но меняется имя
currentObject.name = objectName;
} else {
// Если у текущего объекта есть грани, то обявление нового имени создаёт новый объект
moveFacesToSurface();
currentObject = new Mesh(objectName);
currentObject.mobility = mobility;
_content.addChild(currentObject);
}
vIndexStart = globalVertices.length;
vtIndexStart = globalTextureVertices.length;
}
/**
* Создание грани в текущем объекте.
*
* @param parts массив, содержащий индексы вершин грани, начиная с элемента с индексом 1
*/
private function createFace(parts:Array):void {
// Стартовый индекс вершины в объекте для добавляемой грани
var startVertexIndex:int = currentObject.vertices.length;
// Создание вершин в объекте
var faceVertexCount:int = parts.length - 1;
var vtIndices:Array = new Array(3);
// Массив идентификаторов вершин грани
var faceVertices:Array = new Array(faceVertexCount);
for (var i:int = 0; i < faceVertexCount; i++) {
var indices:Array = parts[i + 1].split("/");
// Создание вершины
var vIdx:int = int(indices[0]);
// Если индекс положительный, то его значение уменьшается на единицу, т.к. в obj формате индексация начинается с 1.
// Если индекс отрицательный, то выполняется смещение на его значение назад от стартового глобального индекса вершин для текущего объекта.
var actualIndex:int = vIdx > 0 ? vIdx - 1 : vIndexStart + vIdx;
var vertex:Vertex = currentObject.vertices[actualIndex];
// Если вершины нет в объекте, она добавляется
if (vertex == null) {
var p:Point3D = globalVertices[actualIndex];
if (rotateModel) {
// В формате obj направление "вверх" совпадает с осью Y, поэтому выполняется поворот координат на 90 градусов по оси X
vertex = currentObject.createVertex(p.x, -p.z, p.y, actualIndex);
} else {
vertex = currentObject.createVertex(p.x, p.y, p.z, actualIndex);
}
}
faceVertices[i] = vertex;
// Запись индекса текстурной вершины
if (i < 3) {
vtIndices[i] = int(indices[1]);
}
}
// Создание грани
var face:Face = currentObject.createFace(faceVertices, currentObject.faces.length);
// Установка uv координат
if (vtIndices[0] != 0) {
p = globalTextureVertices[vtIndices[0] - 1];
face.aUV = new Point(p.x, p.y);
p = globalTextureVertices[vtIndices[1] - 1];
face.bUV = new Point(p.x, p.y);
p = globalTextureVertices[vtIndices[2] - 1];
face.cUV = new Point(p.x, p.y);
}
// Если есть активный материал, то грань заносится в массив для последующего формирования поверхности в объекте
if (currentMaterialName != null) {
materialFaces.push(face);
}
}
/**
* Загрузка библиотек материалов.
*
* @param parts массив, содержащий имена файлов материалов, начиная с элемента с индексом 1
*/
private function storeMaterialFileNames(parts:Array):void {
for (var i:int = 1; i < parts.length; i++) {
materialFileNames.push(parts[i]);
}
}
/**
* Установка нового текущего материала.
*
* @param parts массив, во втором элементе которого содержится имя материала
*/
private function setNewMaterial(parts:Array):void {
// Все сохранённые грани добавляются в соответствующую поверхность текущего объекта
moveFacesToSurface();
// Установка нового текущего материала
currentMaterialName = parts[1];
}
/**
* Добавление всех граней с текущим материалом в поверхность с идентификатором, совпадающим с именем материала.
*/
private function moveFacesToSurface():void {
if (currentMaterialName != null && materialFaces.length > 0) {
if (currentObject.hasSurface(currentMaterialName)) {
// При наличии поверхности с таким идентификатором, грани добавляются в неё
var surface:Surface = currentObject.getSurfaceById(currentMaterialName);
for each (var face:* in materialFaces) {
surface.addFace(face);
}
} else {
// При отсутствии поверхности с таким идентификатором, создатся новая поверхность
currentObject.createSurface(materialFaces, currentMaterialName);
}
}
materialFaces = [];
}
/**
* Загрузка материалов.
*/
private function loadMaterialsLibrary():void {
if (mtlLoader == null) {
mtlLoader = new LoaderMTL();
mtlLoader.addEventListener(Event.COMPLETE, onMaterialFileLoadComplete);
mtlLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError);
mtlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError);
}
materialLibrary = new Map();
currentMaterialFileIndex = -1;
loadNextMaterialFile();
}
/**
* Обработка успешной загрузки библиотеки материалов.
*/
private function onMaterialFileLoadComplete(e:Event):void {
materialLibrary.concat(mtlLoader.library);
// Загрузка следующего файла материалов
loadNextMaterialFile();
}
/**
*
*/
private function loadNextMaterialFile():void {
currentMaterialFileIndex++;
if (currentMaterialFileIndex == materialFileNames.length) {
setMaterials();
dispatchEvent(new Event(Event.COMPLETE));
} else {
mtlLoader.load(basePath + materialFileNames[currentMaterialFileIndex], loaderContext);
}
}
/**
* Установка материалов.
*/
private function setMaterials():void {
if (materialLibrary != null) {
for (var objectKey:* in _content.children) {
var object:Mesh = objectKey;
for (var surfaceKey:* in object.surfaces) {
var surface:Surface = object.surfaces[surfaceKey];
// Поверхности имеют идентификаторы, соответствующие именам материалов
var materialInfo:MaterialInfo = materialLibrary[surfaceKey];
if (materialInfo != null) {
if (materialInfo.bitmapData == null) {
surface.material = new FillMaterial(materialInfo.color, materialInfo.alpha, blendMode);
} else {
surface.material = new TextureMaterial(new Texture(materialInfo.bitmapData, materialInfo.textureFileName), materialInfo.alpha, materialInfo.repeat, (materialInfo.bitmapData != LoaderMTL.stubBitmapData) ? smooth : false, blendMode, -1, 0, precision);
transformUVs(surface, materialInfo.mapOffset, materialInfo.mapSize);
}
}
}
}
}
}
/**
* Метод выполняет преобразование UV-координат текстурированных граней. В связи с тем, что в формате MRL предусмотрено
* масштабирование и смещение текстурной карты в UV-пространстве, а в движке такой фунциональности нет, необходимо
* эмулировать преобразования текстуры преобразованием UV-координат граней. Преобразования выполняются исходя из предположения,
* что текстурное пространство сначала масштабируется относительно центра, а затем сдвигается на указанную величину
* смещения.
*
* @param surface поверхность, грани которой обрабатываюся
* @param mapOffset смещение текстурной карты. Значение mapOffset.x указывает смещение по U, значение mapOffset.y
* указывает смещение по V.
* @param mapSize коэффициенты масштабирования текстурной карты. Значение mapSize.x указывает коэффициент масштабирования
* по оси U, значение mapSize.y указывает коэффициент масштабирования по оси V.
*/
private function transformUVs(surface:Surface, mapOffset:Point, mapSize:Point):void {
for (var key:* in surface.faces) {
var face:Face = key;
var uv:Point = face.aUV;
if (uv != null) {
uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
face.aUV = uv;
uv = face.bUV;
uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
face.bUV = uv;
uv = face.cUV;
uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
face.cUV = uv;
}
}
}
}
}

View File

@@ -0,0 +1,120 @@
package alternativa.engine3d.loaders {
/**
* @private
* Класс содержит информацию о текстуре в формате MTL material format (Lightwave, OBJ) и функционал для разбора
* описания текстуры.
* Описание формата можно посмотреть по адресу: http://local.wasp.uwa.edu.au/~pbourke/dataformats/mtl/
*/
internal class MTLTextureMapInfo {
// Ассоциация параметров команды объявления текстуры и методов для их чтения
private static const optionReaders:Object = {
"-clamp": clampReader,
"-o": offsetReader,
"-s": sizeReader,
"-blendu": stubReader,
"-blendv": stubReader,
"-bm": stubReader,
"-boost": stubReader,
"-cc": stubReader,
"-imfchan": stubReader,
"-mm": stubReader,
"-t": stubReader,
"-texres": stubReader
};
// Смещение в текстурном пространстве
public var offsetU:Number = 0;
public var offsetV:Number = 0;
public var offsetW:Number = 0;
// Масштабирование текстурного пространства
public var sizeU:Number = 1;
public var sizeV:Number = 1;
public var sizeW:Number = 1;
// Флаг повторения текстуры
public var repeat:Boolean = true;
// Имя файла текстуры
public var fileName:String;
/**
* Метод выполняет разбор данных о текстуре.
*
* @param parts Данные о текстуре. Массив должен содержать части разделённой по пробелам входной строки MTL-файла.
* @return объект, содержащий данные о текстуре
*/
public static function parse(parts:Array):MTLTextureMapInfo {
var info:MTLTextureMapInfo = new MTLTextureMapInfo();
// Начальное значение индекса единица, т.к. первый элемент массива содержит тип текстуры
var index:int = 1;
var reader:Function;
// Чтение параметров текстуры
while ((reader = optionReaders[parts[index]]) != null) {
index = reader(index, parts, info);
}
// Если не было ошибок, последний элемент массива должен содержать имя файла текстуры
info.fileName = parts[index];
return info;
}
/**
* Читатель-заглушка. Пропускает все неподдерживаемые параметры.
*/
private static function stubReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
index++;
var maxIndex:int = parts.length - 1;
while ((MTLTextureMapInfo.optionReaders[parts[index]] == null) && (index < maxIndex)) {
index++;
}
return index;
}
/**
* Метод чтения параметров масштабирования текстурного пространства.
*/
private static function sizeReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
info.sizeU = Number(parts[index + 1]);
index += 2;
var value:Number = Number(parts[index]);
if (!isNaN(value)) {
info.sizeV = value;
index++;
value = Number(parts[index]);
if (!isNaN(value)) {
info.sizeW = value;
index++;
}
}
return index;
}
/**
* Метод чтения параметров смещения текстуры.
*/
private static function offsetReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
info.offsetU = Number(parts[index + 1]);
index += 2;
var value:Number = Number(parts[index]);
if (!isNaN(value)) {
info.offsetV = value;
index++;
value = Number(parts[index]);
if (!isNaN(value)) {
info.offsetW = value;
index++;
}
}
return index;
}
/**
* Метод чтения параметра повторения текстуры.
*/
private static function clampReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
info.repeat = parts[index + 1] == "off";
return index + 2;
}
}
}

View File

@@ -0,0 +1,20 @@
package alternativa.engine3d.loaders {
import flash.display.BitmapData;
import flash.geom.Point;
/**
* @private
* Класс содержит обобщённую информацию о материале.
*/
internal class MaterialInfo {
public var color:uint;
public var alpha:Number;
public var textureFileName:String;
public var bitmapData:BitmapData;
public var repeat:Boolean;
public var mapOffset:Point;
public var mapSize:Point;
}
}

View File

@@ -0,0 +1,53 @@
K 25
svn:wc:ra_dav:version-url
V 101
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/materials
END
DevMaterial.as
K 25
svn:wc:ra_dav:version-url
V 116
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/materials/DevMaterial.as
END
Material.as
K 25
svn:wc:ra_dav:version-url
V 113
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/materials/Material.as
END
TextureMaterial.as
K 25
svn:wc:ra_dav:version-url
V 120
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/materials/TextureMaterial.as
END
FillMaterial.as
K 25
svn:wc:ra_dav:version-url
V 117
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/materials/FillMaterial.as
END
WireMaterial.as
K 25
svn:wc:ra_dav:version-url
V 117
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/materials/WireMaterial.as
END
DrawPoint.as
K 25
svn:wc:ra_dav:version-url
V 114
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/materials/DrawPoint.as
END
SurfaceMaterial.as
K 25
svn:wc:ra_dav:version-url
V 120
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/materials/SurfaceMaterial.as
END
TextureMaterialPrecision.as
K 25
svn:wc:ra_dav:version-url
V 129
/!svn/ver/497/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/materials/TextureMaterialPrecision.as
END

View File

@@ -0,0 +1,124 @@
8
dir
46043
http://svndev.alternativaplatform.com/platform/clients/fp10/libraries/Alternativa3D/tags/5.4.1/alternativa/engine3d/materials
http://svndev.alternativaplatform.com
2008-09-08T06:50:26.103978Z
468
mike
svn:special svn:externals svn:needs-lock
d9e2387a-1f3e-40e2-b57f-9df5970a2fa5
DevMaterial.as
file
2010-10-28T04:31:16.000000Z
2c90564cfd20a00c9038a43690374794
2008-08-25T13:44:47.077292Z
176
int
Material.as
file
2010-10-28T04:31:16.000000Z
227c5a7229790ccabc0237cf44a202fc
2008-08-25T13:44:47.077292Z
176
int
TextureMaterial.as
file
2010-10-28T04:31:16.000000Z
9b48ec3656ca2db5ff3e5cb5744363fb
2008-09-08T06:50:26.103978Z
468
mike
FillMaterial.as
file
2010-10-28T04:31:16.000000Z
e33f75e92d1bd21a114dd5a4293dc0f3
2008-09-08T06:50:26.103978Z
468
mike
WireMaterial.as
file
2010-10-28T04:31:16.000000Z
49716fd06e5cb181e0886a7d63f1cd0f
2008-09-08T06:50:26.103978Z
468
mike
DrawPoint.as
file
2010-10-28T04:31:16.000000Z
f110bd43b42437479c009f63140fd20a
2008-09-08T06:50:26.103978Z
468
mike
SurfaceMaterial.as
file
2010-10-28T04:31:16.000000Z
def3c3e365f98998e0c5ba0f10c3c5cf
2008-09-08T06:50:26.103978Z
468
mike
TextureMaterialPrecision.as
file
2010-10-28T04:31:16.000000Z
c0cfbffad39af9bef6a7441a8bca7ddb
2008-09-08T06:50:26.103978Z
468
mike

View File

@@ -0,0 +1 @@
8

View File

@@ -0,0 +1,198 @@
package alternativa.engine3d.materials {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.display.Skin;
import flash.display.BlendMode;
import flash.display.Graphics;
import alternativa.utils.ColorUtils;
import alternativa.engine3d.core.PolyPrimitive;
import alternativa.engine3d.core.BSPNode;
use namespace alternativa3d;
/**
* @private
* Материал, заполняющий грань сплошной заливкой цветом в соответствии с уровнем мобильности. Помимо заливки материал может рисовать границу
* полигона линией заданной толщины и цвета.
*/
public class DevMaterial extends SurfaceMaterial {
/**
* @private
* Цвет
*/
alternativa3d var _color:uint;
/**
* @private
* Толщина линий обводки
*/
alternativa3d var _wireThickness:Number;
/**
* @private
* Цвет линий обводки
*/
alternativa3d var _wireColor:uint;
/**
* Создание экземпляра класса.
*
* @param color цвет заливки
* @param alpha прозрачность
* @param blendMode режим наложения цвета
* @param wireThickness толщина линии обводки
* @param wireColor цвет линии обводки
*/
public function DevMaterial(color:uint = 0xFFFFFF, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) {
super(alpha, blendMode);
_color = color;
_wireThickness = wireThickness;
_wireColor = wireColor;
}
/**
* @private
*
* @param camera
* @param skin
* @param length
* @param points
*/
override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
skin.alpha = _alpha;
skin.blendMode = _blendMode;
var i:uint;
var point:DrawPoint;
var gfx:Graphics = skin.gfx;
/*
//Мобильность
var param:int = skin.primitive.mobility*10;
*/
/*
// Уровень распиленности
var param:int = 0;
var prm:PolyPrimitive = skin.primitive;
while (prm != null) {
prm = prm.parent;
param++;
}
param *= 10;
*/
// Уровень в BSP-дереве
var param:int = 0;
var node:BSPNode = skin.primitive.node;
while (node != null) {
node = node.parent;
param++;
}
param *= 5;
var c:uint = ColorUtils.rgb(param, param, param);
if (camera._orthographic) {
gfx.beginFill(c);
if (_wireThickness >= 0) {
gfx.lineStyle(_wireThickness, _wireColor);
}
point = points[0];
gfx.moveTo(point.x, point.y);
for (i = 1; i < length; i++) {
point = points[i];
gfx.lineTo(point.x, point.y);
}
if (_wireThickness >= 0) {
point = points[0];
gfx.lineTo(point.x, point.y);
}
} else {
gfx.beginFill(c);
if (_wireThickness >= 0) {
gfx.lineStyle(_wireThickness, _wireColor);
}
point = points[0];
var perspective:Number = camera.focalLength/point.z;
gfx.moveTo(point.x*perspective, point.y*perspective);
for (i = 1; i < length; i++) {
point = points[i];
perspective = camera.focalLength/point.z;
gfx.lineTo(point.x*perspective, point.y*perspective);
}
if (_wireThickness >= 0) {
point = points[0];
perspective = camera.focalLength/point.z;
gfx.lineTo(point.x*perspective, point.y*perspective);
}
}
}
/**
* Цвет заливки.
*/
public function get color():uint {
return _color;
}
/**
* @private
*/
public function set color(value:uint):void {
if (_color != value) {
_color = value;
if (_surface != null) {
_surface.addMaterialChangedOperationToScene();
}
}
}
/**
* Толщина линии обводки. Если значение отрицательное, то отрисовка линии не выполняется.
*/
public function get wireThickness():Number {
return _wireThickness;
}
/**
* @private
*/
public function set wireThickness(value:Number):void {
if (_wireThickness != value) {
_wireThickness = value;
if (_surface != null) {
_surface.addMaterialChangedOperationToScene();
}
}
}
/**
* Цвет линии обводки.
*/
public function get wireColor():uint {
return _wireColor;
}
/**
* @private
*/
public function set wireColor(value:uint):void {
if (_wireColor != value) {
_wireColor = value;
if (_surface != null) {
_surface.addMaterialChangedOperationToScene();
}
}
}
/**
* @inheritDoc
*/
override public function clone():Material {
var res:DevMaterial = new DevMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor);
return res;
}
}
}

View File

@@ -0,0 +1,45 @@
package alternativa.engine3d.materials {
/**
* @private
* Точка, подготовленная к отрисовке.
*/
public final class DrawPoint {
/**
* Координата X в системе координат камеры.
*/
public var x:Number;
/**
* Координата Y в системе координат камеры.
*/
public var y:Number;
/**
* Координата Z в системе координат камеры.
*/
public var z:Number;
/**
* Координата U в текстурном пространстве.
*/
public var u:Number;
/**
* Координата V в текстурном пространстве.
*/
public var v:Number;
/**
* Создаёт новый экземпляр класса.
*
* @param x координата X в системе координат камеры
* @param y координата Y в системе координат камеры
* @param z координата Z в системе координат камеры
* @param u координата U в текстурном пространстве
* @param v координата V в текстурном пространстве
*/
public function DrawPoint(x:Number, y:Number, z:Number, u:Number = 0, v:Number = 0) {
this.x = x;
this.y = y;
this.z = z;
this.u = u;
this.v = v;
}
}
}

View File

@@ -0,0 +1,163 @@
package alternativa.engine3d.materials {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.display.Skin;
import flash.display.BlendMode;
import flash.display.Graphics;
use namespace alternativa3d;
/**
* Материал, заполняющий грань сплошной одноцветной заливкой. Помимо заливки цветом, материал может рисовать границу
* грани линией заданной толщины и цвета.
*/
public class FillMaterial extends SurfaceMaterial {
/**
* @private
* Цвет
*/
alternativa3d var _color:uint;
/**
* @private
* Толщина линий обводки
*/
alternativa3d var _wireThickness:Number;
/**
* @private
* Цвет линий обводки
*/
alternativa3d var _wireColor:uint;
/**
* Создание экземпляра класса.
*
* @param color цвет заливки
* @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
* @param blendMode режим наложения цвета
* @param wireThickness толщина линии обводки
* @param wireColor цвет линии обводки
*/
public function FillMaterial(color:uint, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) {
super(alpha, blendMode);
_color = color;
_wireThickness = wireThickness;
_wireColor = wireColor;
}
/**
* @private
* @inheritDoc
*/
override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
skin.alpha = _alpha;
skin.blendMode = _blendMode;
var i:uint;
var point:DrawPoint;
var gfx:Graphics = skin.gfx;
if (camera._orthographic) {
gfx.beginFill(_color);
if (_wireThickness >= 0) {
gfx.lineStyle(_wireThickness, _wireColor);
}
point = points[0];
gfx.moveTo(point.x, point.y);
for (i = 1; i < length; i++) {
point = points[i];
gfx.lineTo(point.x, point.y);
}
if (_wireThickness >= 0) {
point = points[0];
gfx.lineTo(point.x, point.y);
}
} else {
gfx.beginFill(_color);
if (_wireThickness >= 0) {
gfx.lineStyle(_wireThickness, _wireColor);
}
point = points[0];
var perspective:Number = camera.focalLength/point.z;
gfx.moveTo(point.x*perspective, point.y*perspective);
for (i = 1; i < length; i++) {
point = points[i];
perspective = camera.focalLength/point.z;
gfx.lineTo(point.x*perspective, point.y*perspective);
}
if (_wireThickness >= 0) {
point = points[0];
perspective = camera.focalLength/point.z;
gfx.lineTo(point.x*perspective, point.y*perspective);
}
}
}
/**
* Цвет заливки.
*/
public function get color():uint {
return _color;
}
/**
* @private
*/
public function set color(value:uint):void {
if (_color != value) {
_color = value;
if (_surface != null) {
_surface.addMaterialChangedOperationToScene();
}
}
}
/**
* Толщина линии обводки. Если значение отрицательное, то обводка не рисуется.
*/
public function get wireThickness():Number {
return _wireThickness;
}
/**
* @private
*/
public function set wireThickness(value:Number):void {
if (_wireThickness != value) {
_wireThickness = value;
if (_surface != null) {
_surface.addMaterialChangedOperationToScene();
}
}
}
/**
* Цвет линии обводки.
*/
public function get wireColor():uint {
return _wireColor;
}
/**
* @private
*/
public function set wireColor(value:uint):void {
if (_wireColor != value) {
_wireColor = value;
if (_surface != null) {
_surface.addMaterialChangedOperationToScene();
}
}
}
/**
* @inheritDoc
*/
override public function clone():Material {
var res:FillMaterial = new FillMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor);
return res;
}
}
}

View File

@@ -0,0 +1,20 @@
package alternativa.engine3d.materials {
import alternativa.engine3d.*;
use namespace alternativa3d;
/**
* Базовый класс для материалов.
*/
public class Material {
/**
* Создание клона материала.
*
* @return клон материала
*/
public function clone():Material {
return new Material();
}
}
}

Some files were not shown because too many files have changed in this diff Show More