This commit is contained in:
Pyogenics
2024-09-28 17:29:26 +01:00
parent b442fdbe52
commit c58621fb99
244 changed files with 67857 additions and 0 deletions

View File

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

View File

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

View File

@@ -0,0 +1,894 @@
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 объект, используемый для получения событий мыши и клавиатуры
*
* @throws ArgumentError в качестве eventsSourceObject не может быть указан null
*/
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>Z</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.Z, 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;
}
/**
* Источник событий клавиатуры и мыши.
*
* @throws ArgumentError в качестве eventsSource не может быть указан null
*/
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,475 @@
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>
*
* <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>
* <p>
* Поворот мышью реализован следующим образом: в момент активации режима поворота (нажата левая кнопка мыши или
* соответствующая кнопка на клавиатуре) текущее положение курсора становится точкой, относительно которой определяются
* дальнейшие отклонения. Отклонение курсора по вертикали в пикселях, умноженное на коэффициент чувствительности мыши
* по вертикали даёт угловую скорость по тангажу. Отклонение курсора по горизонтали в пикселях, умноженное на коэффициент
* чувствительности мыши по горизонтали даёт угловую скорость по крену.
* </p>
*/
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;
/**
* Создаёт новый экземпляр контролллера.
*
* @param eventsSourceObject источник событий клавиатуры и мыши
*
* @throws ArgumentError в качестве eventsSourceObject не может быть указан null
*/
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>Z</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.Z, 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);
}
/**
* @inheritDoc
*/
override public function set enabled(value:Boolean):void {
super.enabled = value;
if (!value) {
velocity.reset();
_currentSpeed = 0;
}
}
/**
* @inheritDoc
*/
override protected function clearCommandFlags():void {
super.clearCommandFlags();
_rollLeft = false;
_rollRight = false;
}
}
}

View File

@@ -0,0 +1,888 @@
package alternativa.engine3d.controllers {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.physics.EllipsoidCollider;
import alternativa.types.Map;
import alternativa.types.Point3D;
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 источник событий клавиатуры и мыши
*
* @throws ArgumentError в качестве eventsSourceObject не может быть указан null
*/
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;
if (_enabled) {
if (_mouseEnabled) {
registerMouseListeners();
}
if (_keyboardEnabled) {
registerKeyboardListeners();
}
} else {
if (_mouseEnabled) {
unregisterMouseListeners();
setMouseLook(false);
}
if (_keyboardEnabled) {
unregisterKeyboardListeners();
}
}
}
/**
* Координаты контроллера. Координаты совпадают с координатами центра эллипсоида, используемого для определения
* столкновений. Координаты управляемого объекта могут не совпадать с координатами контроллера.
*
* @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;
setObjectCoords();
}
/**
* Объект, реализующий проверку столкновений для эллипсоида.
*/
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></p>
*/
public function processInput():void {
if (!_enabled || _object == null) {
return;
}
var frameTime:Number = getTimer() - lastFrameTime;
// Проверка в связи с возможным багом десятого плеера, ну и вообще на всякий случай
if (frameTime == 0) {
return;
}
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) {
if (_enabled) {
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 {
clearCommandFlags();
_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) {
if (_enabled) {
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 {
if (_object != null) {
_object.coords = _coords;
}
}
/**
* Индикатор режима увеличенной скорости.
*/
public function get accelerated():Boolean {
return _accelerate;
}
/**
* Метод сбрасывает флаги активных команд.
*/
protected function clearCommandFlags():void {
_forward = false;
_back = false;
_left = false;
_right = false;
_up = false;
_down = false;
_pitchUp = false;
_pitchDown = false;
_yawLeft = false;
_yawRight = false;
_accelerate = false;
_mouseLookActive = false;
}
}
}

View File

@@ -0,0 +1,570 @@
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>
*
* <p>Для всех объектов, за исключением <code>Camera3D</code>, направлением "вперёд" считается направление его оси
* <code>Y</code>, направлением "вверх" &mdash; направление оси <code>Z</code>. Для объектов класса
* <code>Camera3D</code> направление "вперёд" совпадает с направлением локальной оси <code>Z</code>, а направление
* "вверх" противоположно направлению локальной оси <code>Y</code>.</p>
*
* <p>Вне зависимости от того, включена проверка столкновений или нет, координаты при перемещении расчитываются для
* эллипсоида, параметры которого устанавливаются через свойство <code>collider</code>. Координаты управляемого
* объекта вычисляются исходя из положения центра эллипсоида и положения объекта на вертикальной оси эллипсоида,
* задаваемого параметром <code>objectZPosition</code>.</p>
*
* <p>Команда <code>ACTION_UP</code> в режиме ходьбы при ненулевой гравитации вызывает прыжок, в остальных случаях
* происходит движение вверх.</p>
*/
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;
/**
* Ограничение поворота объекта по поперечной оси
*
* @default Math.PI / 2
*
* @see minPitch
*/
public var maxPitch:Number = 0.5*Math.PI;
/**
* Ограничение поворота объекта по поперечной оси
*
* @default -Math.PI / 2
*
* @see maxPitch
*/
public var minPitch:Number = -0.5*Math.PI;
// Коэффициент эффективности управления перемещением при нахождении в воздухе в режиме ходьбы и нулевой гравитации.
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 _jumpLocked: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();
/**
* Создаёт новый экземпляр контролллера.
*
* @param eventsSourceObject источник событий клавиатуры и мыши
* @param object управляемый объект
*
* @throws ArgumentError в качестве eventsSourceObject не может быть указан null
*/
public function WalkController(eventSourceObject:DisplayObject, object:Object3D = null) {
super(eventSourceObject);
this.object = object;
}
/**
* Объект, на котором стоит эллипсоид при ненулевой гравитации.
*/
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>Z</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.Z, 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);
}
/**
* Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Взгляд вверх и вниз ограничен
* промежутком [minPitch, maxPitch] от горизонтали.
*
* @param frameTime длительность текущего кадра в секундах
*
* @see minPitch
* @see maxPitch
*/
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 > maxPitch) ? maxPitch : (rotX < minPitch) ? minPitch : rotX) - MathUtils.DEG90;
} else {
_object.rotationX = (rotX > maxPitch) ? maxPitch : (rotX < minPitch) ? minPitch : 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;
if (!_up) {
_jumpLocked = false;
}
}
var len:Number;
var x:Number;
var y:Number;
var z:Number;
// При наличии управляющих воздействий расчитывается приращение скорости
controlsActive = _forward || _back || _right || _left;
if (flyMode || gravity == 0) {
controlsActive ||= _up || _down;
} else {
}
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 && !_jumpLocked) {
velocity.z = jumpSpeed;
inJump = true;
_onGround = false;
_jumpLocked = true;
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 {
if (_object != null) {
_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;
}
/**
* @inheritDoc
*/
override public function set enabled(value:Boolean):void {
super.enabled = value;
if (!value) {
velocity.reset();
_currentSpeed = 0;
}
}
/**
* @inheritDoc
*/
override public function moveUp(value:Boolean):void {
super.moveUp(value);
if (!inJump && !value) {
_jumpLocked = false;
}
}
}
}

View File

@@ -0,0 +1,93 @@
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 splitter:Splitter;
// Передний и задний сектора (если есть сплиттер)
alternativa3d var frontSector:Sector;
alternativa3d var backSector:Sector;
// Тип примитива
alternativa3d var isSprite:Boolean;
// Родительская нода
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 create(primitive:PolyPrimitive):BSPNode {
var node:BSPNode;
if ((node = collector.pop()) == null) {
node = new BSPNode();
}
// Добавляем примитив в ноду
node.primitive = primitive;
// Сохраняем ноду
primitive.node = node;
// Если это спрайтовый примитив или сплиттеровый примитив
if (primitive.face == null) {
var sprimitive:SplitterPrimitive = primitive as SplitterPrimitive;
if (sprimitive == null) {
// SpritePrimitive
node.normal.x = 0;
node.normal.y = 0;
node.normal.z = 0;
node.offset = 0;
node.isSprite = true;
} else {
node.splitter = sprimitive.splitter;
node.normal.copy(sprimitive.splitter.normal);
node.offset = sprimitive.splitter.offset;
node.isSprite = false;
}
} else {
// Сохраняем плоскость
node.normal.copy(primitive.face.globalNormal);
node.offset = primitive.face.globalOffset;
node.isSprite = false;
}
// Сохраняем мобильность
node.mobility = primitive.mobility;
return node;
}
// Удалить ноду, все ссылки должны быть почищены
static alternativa3d function destroy(node:BSPNode):void {
//trace(node.back, node.front, node.parent, node.primitive, node.backPrimitives, node.frontPrimitives);
collector.push(node);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,904 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
import alternativa.types.Matrix3D;
import alternativa.types.Point3D;
import alternativa.types.Set;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.geom.Matrix;
import flash.geom.Point;
use namespace alternativa3d;
/**
* Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одной и той же гранью.
* Между нажатием и отпусканием кнопки могут происходить любые другие события.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.CLICK
*/
[Event(name="click", type = "alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь нажимает левую кнопку мыши над гранью.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN
*/
[Event(name="mouseDown", type = "alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь отпускает левую кнопку мыши над гранью.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP
*/
[Event(name="mouseUp", type = "alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь наводит курсор мыши на грань.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER
*/
[Event(name="mouseOver", type = "alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь уводит курсор мыши с грани.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT
*/
[Event(name="mouseOut", type = "alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь перемещает курсор мыши над гранью.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE
*/
[Event(name="mouseMove", type = "alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь вращает колесо мыши над гранью.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL
*/
[Event(name="mouseWheel", type = "alternativa.engine3d.events.MouseEvent3D")]
/**
* Грань, образованная тремя или более вершинами. Грани являются составными частями полигональных объектов. Каждая грань
* содержит информацию об объекте и поверхности, которым она принадлежит. Для обеспечения возможности наложения
* текстуры на грань, первым трём её вершинам могут быть заданы UV-координаты, на основании которых расчитывается
* матрица трансформации текстуры.
*
* <p> Класс реализует интерфейс <code>flash.events.IEventDispatcher</code> и может рассылать мышиные события, содержащие информацию
* о точке в трёхмерном пространстве, в которой произошло событие.</p>
*/
final public class Face implements IEventDispatcher {
// Погрешность определения вырожденной UV матрицы
private static const uvThreshold:Number = 1.0 / 2880;
// Операции
/**
* @private
* Расчёт глобальной нормали плоскости грани.
*/
alternativa3d var calculateNormalOperation:Operation = new Operation("calculateNormal", this, calculateNormal, Operation.FACE_CALCULATE_NORMAL);
/**
* @private
* Расчёт базовой UV матрицы, используется для расчета UV матрицы.
*/
alternativa3d var calculateBaseUVOperation:Operation = new Operation("calculateBaseUV", this, calculateBaseUV, Operation.FACE_CALCULATE_BASE_UV);
/**
* @private
* Расчёт UV матрицы.
*/
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
* Меш
*/
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:Matrix3D;
/**
* @private
* UV Матрица перевода текстурных координат в изометрическую камеру.
*/
alternativa3d var orthoTextureMatrix:Matrix;
/**
* @private
* Нормаль плоскости
*/
alternativa3d var globalNormal:Point3D = new Point3D();
/**
* @private
* Смещение плоскости
*/
alternativa3d var globalOffset:Number;
/**
* Флаг указывает, будет ли объект принимать мышиные события.
*/
public var mouseEnabled:Boolean = true;
/**
* Диспетчер событий.
*/
private var dispatcher:EventDispatcher;
/**
* Создание экземпляра грани.
*
* @param vertices массив объектов типа <code>alternativa.engine3d.core.Vertex</code>, задающий вершины грани в
* порядке обхода лицевой стороны грани против часовой стрелки.
*
* @see Vertex
*/
public function Face(vertices:Array) {
// Сохраняем вершины
_vertices = vertices;
_verticesCount = vertices.length;
// Создаём оригинальный примитив
primitive = PolyPrimitive.create();
primitive.face = this;
primitive.num = _verticesCount;
// Обрабатываем вершины
for (var i:uint = 0; i < _verticesCount; i++) {
var vertex:Vertex = vertices[i];
// Добавляем координаты вершины в примитив
primitive.points.push(vertex.globalCoords);
// Добавляем вершину в грань
vertex.addToFace(this);
}
// Расчёт нормали
calculateNormalOperation.addSequel(updatePrimitiveOperation);
// Расчет нормали заставляет пересчитаться UV матрицу
calculateNormalOperation.addSequel(calculateUVOperation);
// Расчет базововй UV матрицы инициирует расчет UV матрицы грани
calculateBaseUVOperation.addSequel(calculateUVOperation);
// Расчёт UV матрицы грани инициирует перерисовку
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.destroy(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-координат.
*/
private function calculateBaseUV():void {
// Расчёт 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 < uvThreshold && det > -uvThreshold) {
var len:Number;
if (abu < uvThreshold && abu > -uvThreshold && abv < uvThreshold && abv > -uvThreshold) {
if (acu < uvThreshold && acu > -uvThreshold && acv < uvThreshold && acv > -uvThreshold) {
// Оба вырождены
abu = uvThreshold;
acv = uvThreshold;
} else {
// Вырожден AB
len = Math.sqrt(acu*acu + acv*acv);
abu = uvThreshold*acv/len;
abv = -uvThreshold*acu/len;
}
} else {
if (acu < uvThreshold && acu > -uvThreshold && acv < uvThreshold && acv > -uvThreshold) {
//Вырожден AC
len = Math.sqrt(abu*abu + abv*abv);
acu = -uvThreshold*abv/len;
acv = uvThreshold*abu/len;
} else {
// Сонаправлены
len = Math.sqrt(abu*abu + abv*abv);
acu += uvThreshold*abv/len;
acv -= uvThreshold*abu/len;
}
}
// Пересчитываем определитель
det = abu*acv - abv*acu;
}
// Создаём матрицу
if (uvMatrixBase == null) {
uvMatrixBase = new Matrix();
orthoTextureMatrix = 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);
} else {
// Удаляем UV-матрицу
uvMatrixBase = null;
orthoTextureMatrix = null;
}
}
/**
* @private
* Расчет UV матрицы грани.
*/
private function calculateUV():void {
if (uvMatrixBase != null) {
if (uvMatrix == null) {
uvMatrix = new Matrix3D();
}
var a:Point3D = _vertices[0].globalCoords;
var b:Point3D = _vertices[1].globalCoords;
var c:Point3D = _vertices[2].globalCoords;
var abx:Number = b.x - a.x;
var aby:Number = b.y - a.y;
var abz:Number = b.z - a.z;
var acx:Number = c.x - a.x;
var acy:Number = c.y - a.y;
var acz:Number = c.z - a.z;
uvMatrix.a = abx*uvMatrixBase.a + acx*uvMatrixBase.b;
uvMatrix.b = abx*uvMatrixBase.c + acx*uvMatrixBase.d;
uvMatrix.c = globalNormal.x;
uvMatrix.d = abx*uvMatrixBase.tx + acx*uvMatrixBase.ty + a.x;
uvMatrix.e = aby*uvMatrixBase.a + acy*uvMatrixBase.b;
uvMatrix.f = aby*uvMatrixBase.c + acy*uvMatrixBase.d;
uvMatrix.g = globalNormal.y;
uvMatrix.h = aby*uvMatrixBase.tx + acy*uvMatrixBase.ty + a.y;
uvMatrix.i = abz*uvMatrixBase.a + acz*uvMatrixBase.b;
uvMatrix.j = abz*uvMatrixBase.c + acz*uvMatrixBase.d;
uvMatrix.k = globalNormal.z;
uvMatrix.l = abz*uvMatrixBase.tx + acz*uvMatrixBase.ty + a.z;
// Считаем invert
var _a:Number = uvMatrix.a;
var _b:Number = uvMatrix.b;
var _c:Number = uvMatrix.c;
var _d:Number = uvMatrix.d;
var _e:Number = uvMatrix.e;
var _f:Number = uvMatrix.f;
var _g:Number = uvMatrix.g;
var _h:Number = uvMatrix.h;
var _i:Number = uvMatrix.i;
var _j:Number = uvMatrix.j;
var _k:Number = uvMatrix.k;
var _l:Number = uvMatrix.l;
var det:Number = -_c*_f*_i + _b*_g*_i + _c*_e*_j - _a*_g*_j - _b*_e*_k + _a*_f*_k;
if (det != 0) {
uvMatrix.a = (-_g*_j + _f*_k)/det;
uvMatrix.b = (_c*_j - _b*_k)/det;
uvMatrix.c = (-_c*_f + _b*_g)/det;
uvMatrix.d = (_d*_g*_j - _c*_h*_j - _d*_f*_k + _b*_h*_k + _c*_f*_l - _b*_g*_l)/det;
uvMatrix.e = (_g*_i - _e*_k)/det;
uvMatrix.f = (-_c*_i + _a*_k)/det;
uvMatrix.g = (_c*_e - _a*_g)/det;
uvMatrix.h = (_c*_h*_i - _d*_g*_i + _d*_e*_k - _a*_h*_k - _c*_e*_l + _a*_g*_l)/det;
uvMatrix.i = (-_f*_i + _e*_j)/det;
uvMatrix.j = (_b*_i - _a*_j)/det;
uvMatrix.k = (-_b*_e + _a*_f)/det;
uvMatrix.l = (_d*_f*_i - _b*_h*_i - _d*_e*_j + _a*_h*_j + _b*_e*_l - _a*_f*_l)/det;
} else {
uvMatrix = null;
}
} else {
uvMatrix = null;
}
}
/**
* Массив вершин грани, представленных объектами класса <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(calculateBaseUVOperation);
}
}
} else {
_aUV = null;
if (_mesh != null) {
_mesh.addOperationToScene(calculateBaseUVOperation);
}
}
} else {
if (value != null) {
_aUV = value.clone();
if (_mesh != null) {
_mesh.addOperationToScene(calculateBaseUVOperation);
}
}
}
}
/**
* @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(calculateBaseUVOperation);
}
}
} else {
_bUV = null;
if (_mesh != null) {
_mesh.addOperationToScene(calculateBaseUVOperation);
}
}
} else {
if (value != null) {
_bUV = value.clone();
if (_mesh != null) {
_mesh.addOperationToScene(calculateBaseUVOperation);
}
}
}
}
/**
* @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(calculateBaseUVOperation);
}
}
} else {
_cUV = null;
if (_mesh != null) {
_mesh.addOperationToScene(calculateBaseUVOperation);
}
}
} else {
if (value != null) {
_cUV = value.clone();
if (_mesh != null) {
_mesh.addOperationToScene(calculateBaseUVOperation);
}
}
}
}
/**
* Нормаль в локальной системе координат.
*/
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();
// Удаляем вершину из грани
vertex.removeFromFace(this);
}
// Очищаем вершины в примитиве
primitive.points.length = 0;
// Обнуляем количество вершин
_verticesCount = 0;
}
/**
* @private
* Добавление грани на сцену
* @param scene
*/
alternativa3d function addToScene(scene:Scene3D):void {
// При добавлении на сцену рассчитываем плоскость и UV
scene.addOperation(calculateNormalOperation);
scene.addOperation(calculateBaseUVOperation);
// Подписываем сцену на операции
updatePrimitiveOperation.addSequel(scene.calculateBSPOperation);
updateMaterialOperation.addSequel(scene.changePrimitivesOperation);
}
/**
* @private
* Удаление грани из сцены
* @param scene
*/
alternativa3d function removeFromScene(scene:Scene3D):void {
// Удаляем все операции из очереди
scene.removeOperation(calculateBaseUVOperation);
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);
// При перемещении меша, пересчитать UV. При вращении вызовется calculateNormal и UV пересчитаются.
mesh.changeCoordsOperation.addSequel(calculateUVOperation);
mesh.changeRotationOrScaleOperation.addSequel(calculateNormalOperation);
mesh.calculateMobilityOperation.addSequel(updatePrimitiveOperation);
// Сохранить меш
_mesh = mesh;
}
/**
* @private
* Удаление грани из меша
* @param mesh
*/
alternativa3d function removeFromMesh(mesh:Mesh):void {
// Отписка от операций меша
mesh.changeCoordsOperation.removeSequel(updatePrimitiveOperation);
mesh.changeCoordsOperation.removeSequel(calculateUVOperation);
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;
}
/**
* Добавление обработчика события.
*
* @param type тип события
* @param listener обработчик события
* @param useCapture не используется,
* @param priority приоритет обработчика. Обработчики с большим приоритетом выполняются раньше. Обработчики с одинаковым приоритетом
* выполняются в порядке их добавления.
* @param useWeakReference флаг использования слабой ссылки для обработчика
*/
public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
if (dispatcher == null) {
dispatcher = new EventDispatcher(this);
}
useCapture = false;
dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
}
/**
* Рассылка события.
*
* @param event посылаемое событие
* @return false
*/
public function dispatchEvent(event:Event):Boolean {
if (dispatcher != null) {
dispatcher.dispatchEvent(event);
}
return false;
}
/**
* Проверка наличия зарегистрированных обработчиков события указанного типа.
*
* @param type тип события
* @return <code>true</code> если есть обработчики события указанного типа, иначе <code>false</code>
*/
public function hasEventListener(type:String):Boolean {
if (dispatcher != null) {
return dispatcher.hasEventListener(type);
}
return false;
}
/**
* Удаление обработчика события.
*
* @param type тип события
* @param listener обработчик события
* @param useCapture не используется
*/
public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void {
if (dispatcher != null) {
useCapture = false;
dispatcher.removeEventListener(type, listener, useCapture);
}
}
/**
*
*/
public function willTrigger(type:String):Boolean {
if (dispatcher != null) {
return dispatcher.willTrigger(type);
}
return false;
}
}
}

View File

@@ -0,0 +1,986 @@
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;
// Удаляем вершины из грани
f.removeVertices();
// Удаляем грань из поверхности
if (f._surface != null) {
f._surface._faces.remove(f);
f.removeFromSurface(f._surface);
}
// Удаляем грань из сцены
if (_scene != null) {
f.removeFromScene(_scene);
}
// Удаляем грань из меша
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
*/
override protected function createEmptyObject():Object3D {
return new Mesh();
}
/**
* @inheritDoc
*/
override protected 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,991 @@
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;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
use namespace alternativa3d;
/**
* Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одним и тем же объектом.
* Между нажатием и отпусканием кнопки могут происходить любые другие события.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.CLICK
*/
[Event (name="click", type="alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь нажимает левую кнопку мыши над объектом.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN
*/
[Event (name="mouseDown", type="alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь отпускает левую кнопку мыши над объектом.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP
*/
[Event (name="mouseUp", type="alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь наводит курсор мыши на объект.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER
*/
[Event (name="mouseOver", type="alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь уводит курсор мыши с объекта.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT
*/
[Event (name="mouseOut", type="alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь перемещает курсор мыши над объектом.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE
*/
[Event (name="mouseMove", type="alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь вращает колесо мыши над объектом.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL
*/
[Event (name="mouseWheel", type="alternativa.engine3d.events.MouseEvent3D")]
/**
* Базовый класс для объектов, находящихся в сцене. Класс реализует иерархию объектов сцены, а также содержит сведения
* о трансформации объекта как единого целого.
*
* <p> Масштабирование, ориентация и положение объекта задаются в родительской системе координат. Результирующая
* локальная трансформация является композицией операций масштабирования, поворотов объекта относительно осей
* <code>X</code>, <code>Y</code>, <code>Z</code> и параллельного переноса центра объекта из начала координат.
* Операции применяются в порядке их перечисления.</p>
*
* <p> Глобальная трансформация (в системе координат корневого объекта сцены) является композицией трансформаций
* самого объекта и всех его предков по иерархии объектов сцены.</p>
*
* <p> Класс реализует интерфейс <code>flash.events.IEventDispatcher</code> и может рассылать мышиные события, содержащие информацию
* о точке в трёхмерном пространстве, в которой произошло событие. На данный момент не реализованы capture и bubbling фазы рассылки
* событий.</p>
*/
public class Object3D implements IEventDispatcher {
/**
* @private
* Вспомогательная матрица
*/
alternativa3d static var matrix1:Matrix3D = new Matrix3D();
/**
* @private
* Вспомогательная матрица
*/
alternativa3d static var matrix2:Matrix3D = new Matrix3D();
/**
* @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();
/**
* Флаг указывает, будет ли объект принимать мышиные события.
*/
public var mouseEnabled:Boolean = true;
/**
* Диспетчер событий.
*/
private var dispatcher:EventDispatcher;
/**
* Создание экземпляра класса.
*
* @param name имя экземпляра
*/
public function Object3D(name:String = null) {
// Имя по-умолчанию
_name = (name != null) ? name : defaultName();
// Последствия операций
changeRotationOrScaleOperation.addSequel(calculateTransformationOperation);
changeCoordsOperation.addSequel(calculateTransformationOperation);
}
/**
* @private
* Расчёт трансформации
*/
alternativa3d 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 добавляемый объект
*
* @return добавленный объект
*
* @throws alternativa.engine3d.errors.Object3DHierarchyError нарушение иерархии объектов сцены
*/
public function addChild(child:Object3D):Object3D {
// Проверка на null
if (child == null) {
throw new Object3DHierarchyError(null, this);
}
// Проверка на наличие
if (child._parent == this) {
return child;
}
// Проверка на добавление к самому себе
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);
return child;
}
/**
* Удаляет указанный объект из списка детей.
*
* @param child удаляемый дочерний объект
*
* @return удаленный объект
*
* @throws alternativa.engine3d.errors.Object3DNotFoundError указанный объект не содержится в списке детей текущего объекта
*/
public function removeChild(child:Object3D):Object3D {
// Проверка на 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);
return child;
}
/**
* @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 имя дочернего объекта
* @param deep флаг углублённого поиска. Если задано значение <code>false</code>, поиск будет осуществляться только среди непосредственных
* дочерних объектов, иначе поиск будет выполняться по всему дереву дочерних объектов.
*
* @return любой дочерний объект с заданным именем или <code>null</code> в случае отсутствия таких объектов
*/
public function getChildByName(name:String, deep:Boolean = false):Object3D {
var key:*;
var child:Object3D;
for (key in _children) {
child = key;
if (child._name == name) {
return child;
}
}
if (deep) {
for (key in _children) {
child = key;
child = child.getChildByName(name, true);
if (child != null) {
return child;
}
}
}
return null;
}
/**
* Трансформирует точку из локальной системы координат объекта в систему координат сцены. Матрица трансформации корневого объекта сцены
* не учитывается, т.к. его система координат является системой координат сцены.
*
* @param point локальные координаты точки
* @param result в этот параметр будут записаны трансформированные координаты. Если передано значение <code>null</code>, то будет создан
* новый экземпляр класса <code>Point3D</code>.
*
* @return координаты точки в сцене или <code>null</code> в случае, если объект не находится в сцене
*/
public function localToGlobal(point:Point3D, result:Point3D = null):Point3D {
if (_scene == null) {
return null;
}
if (result == null) {
result = point.clone();
}
if (_parent == null) {
// Для корневого объекта трансформация единичная вне зависимости от матрицы
return result;
}
getTransformation(matrix2);
result.transform(matrix2);
return result;
}
/**
* Трансформирует точку из глобальной системы координат в локальную.
*
* @param point точка, заданная в глобальной системе координат.
* @param result в этот параметр будут записаны трансформированные координаты. Если передано значение <code>null</code>, то будет создан
* новый экземпляр класса <code>Point3D</code>.
*
* @return точка, трансформированная в локальную систему координат объекта или <code>null</code> в случае, если объект не находится в сцене.
*/
public function globalToLocal(point:Point3D, result:Point3D = null):Point3D {
if (_scene == null) {
return null;
}
if (result == null) {
result = point.clone();
}
if (_parent == null) {
// Для корневого объекта трансформация единичная вне зависимости от матрицы
return result;
}
getTransformation(matrix2);
matrix2.invert();
result.transform(matrix2);
return result;
}
/**
* Получает матрицу полной трансформации объекта.
*
* @return матрица полной трансформации, переводящая координаты из локальной системы объекта в систему координат сцены или <code>null</code> в случае,
* если объект не находится в сцене
*/
public function get transformation():Matrix3D {
if (_scene == null) {
return null;
}
var result:Matrix3D = new Matrix3D();
getTransformation(result);
return result;
}
/**
* Добавляет обработчик события.
*
* @param type тип события
* @param listener обработчик события
* @param useCapture не используется,
* @param priority приоритет обработчика. Обработчики с большим приоритетом выполняются раньше. Обработчики с одинаковым приоритетом
* выполняются в порядке их добавления.
* @param useWeakReference флаг использования слабой ссылки для обработчика
*/
public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
if (dispatcher == null) {
dispatcher = new EventDispatcher(this);
}
useCapture = false;
dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
}
/**
* Рассылает событие.
*
* @param event посылаемое событие
* @return false
*/
public function dispatchEvent(event:Event):Boolean {
if (dispatcher != null) {
dispatcher.dispatchEvent(event);
}
return false;
}
/**
* Проверяет наличие зарегистрированных обработчиков события указанного типа.
*
* @param type тип события
* @return <code>true</code> если есть обработчики события указанного типа, иначе <code>false</code>
*/
public function hasEventListener(type:String):Boolean {
if (dispatcher != null) {
return dispatcher.hasEventListener(type);
}
return false;
}
/**
* Удаляет обработчик события.
*
* @param type тип события
* @param listener обработчик события
* @param useCapture не используется
*/
public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void {
if (dispatcher != null) {
useCapture = false;
dispatcher.removeEventListener(type, listener, useCapture);
}
}
/**
*
*/
public function willTrigger(type:String):Boolean {
if (dispatcher != null) {
return dispatcher.willTrigger(type);
}
return false;
}
/**
* @private
* Получение матрицы трансформации объекта. Перед расчётом выполняется проверка необходимости пересчёта матрицы.
*
* @param matrix матрица, в которую записывается результат
*
* @return <code>true</code>, если был произведён пересчёт матрицы трансформации, <code>false</code>, если пересчёт не понадобился
*/
alternativa3d function getTransformation(matrix:Matrix3D):Boolean {
var rootObject:Object3D = _scene._root;
var topNonTransformedObject:Object3D;
var currentObject:Object3D = this;
// Поиск первого нетрансформированного объекта в дереве объектов
do {
if (currentObject.changeCoordsOperation.queued || currentObject.changeRotationOrScaleOperation.queued) {
topNonTransformedObject = currentObject._parent;
}
} while ((currentObject = currentObject._parent) != rootObject);
if (topNonTransformedObject != null) {
// Расчёт матрицы трансформации
matrix.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ);
currentObject = this;
while ((currentObject = currentObject._parent) != topNonTransformedObject) {
matrix1.toTransform(currentObject._coords.x, currentObject._coords.y, currentObject._coords.z, currentObject._rotationX, currentObject._rotationY, currentObject._rotationZ, currentObject._scaleX, currentObject._scaleY, currentObject._scaleZ);
matrix.combine(matrix1);
}
if (topNonTransformedObject != rootObject) {
matrix.combine(topNonTransformedObject._transformation);
}
return true;
}
matrix.copy(_transformation);
return false;
}
/**
* Вызывает заданную функцию для объекта и всех его детей, передавая текущий объект в качестве параметра.
*
* @param func функция вида Function(object:Object3D):void
*/
public function forEach(func:Function):void {
func.call(this, this);
for (var child:* in _children) {
Object3D(child).forEach(func);
}
}
}
}

View File

@@ -0,0 +1,131 @@
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_BASE_UV:uint = 0x04000000;
alternativa3d static const FACE_CALCULATE_NORMAL:uint = 0x05000000;
alternativa3d static const FACE_CALCULATE_UV:uint = 0x06000000;
alternativa3d static const FACE_UPDATE_PRIMITIVE:uint = 0x07000000;
alternativa3d static const SECTOR_UPDATE:uint = 0x08000000;
alternativa3d static const SPLITTER_UPDATE:uint = 0x09000000;
alternativa3d static const SCENE_CALCULATE_BSP:uint = 0x0A000000;
alternativa3d static const SECTOR_FIND_NODE:uint = 0x0B000000;
alternativa3d static const SPLITTER_CHANGE_STATE:uint = 0x0C000000;
alternativa3d static const SECTOR_CHANGE_VISIBLE:uint = 0x0D000000;
alternativa3d static const FACE_UPDATE_MATERIAL:uint = 0x0E000000;
alternativa3d static const SPRITE_UPDATE_MATERIAL:uint = 0x0F000000;
alternativa3d static const CAMERA_CALCULATE_MATRIX:uint = 0x10000000;
alternativa3d static const CAMERA_CALCULATE_PLANES:uint = 0x11000000;
alternativa3d static const CAMERA_RENDER:uint = 0x12000000;
alternativa3d static const SCENE_CLEAR_PRIMITIVES:uint = 0x13000000;
// Объект
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,123 @@
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
* Грань
*/
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
* 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;
/**
* @private
* Метод создает новый фрагмент этого примитива.
*/
alternativa3d function createFragment():PolyPrimitive {
var primitive:PolyPrimitive = create();
primitive.face = face;
primitive.mobility = mobility;
return primitive;
}
// Хранилище неиспользуемых примитивов
static private var collector:Array = new Array();
/**
* @private
* Создать примитив
*/
static alternativa3d function create():PolyPrimitive {
var primitive:PolyPrimitive;
if ((primitive = collector.pop()) != null) {
return primitive;
}
return new PolyPrimitive();
}
/**
* @private
* Кладёт примитив в коллектор для последующего реиспользования.
* Ссылка на грань и массивы точек зачищаются в этом методе.
* Ссылки на фрагменты (parent, sibling, back, front) должны быть зачищены перед запуском метода.
*
* Исключение:
* при сборке примитивов в сцене ссылки на back и front зачищаются после запуска метода.
*
* @param primitive примитив на реиспользование
*/
static alternativa3d function destroy(primitive:PolyPrimitive):void {
primitive.face = null;
primitive.points.length = 0;
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,321 @@
package alternativa.engine3d.core {
import alternativa.engine3d.alternativa3d;
import alternativa.types.Point3D;
import alternativa.types.Set;
use namespace alternativa3d;
/**
* Cектор предоставляет механизм управления видимостью частей сцены, который дополняет систему сплиттеров. Каждый сектор находится в какой-то
* из ветвей BSP-дерева, образованных сплиттерами. Также каждый сектор хранит множество других видимых из него секторов.
*
* <p>Если камера находится в дочерней ветке узла, образованного сплиттером, то видимость соседней относительно сплиттера ветки определяется
* следующим образом:
* <ul>
* <li>Если в ветке, в которой находится камера, не задан сектор, то видимость соседней ветки определяется состоянием сплиттера.</li>
* <li>Если в ветке, в которой находится камера, задан сектор, а в соседней ветке сектор не задан, то видимость соседней ветки определяется
* состоянием сплиттера.</li>
* <li>Если в обоих ветках заданы сектора, то видимость соседней ветки определяется состоянием сплиттера и взаимной видимостью секторов.
* То есть если сектора невидимы друг для друга, то даже при открытом сплиттере соседняя ветка дерева будет невидима для камеры.</li>
* </ul>
* </p>
*
* <p>Сектора задаются в свойстве <code>Scene.sectors</code>.</p>
*
* @see Scene3D#sectors
* @see Splitter
*/
public class Sector {
// Счетчик имен объекта
private static var counter:uint = 0;
/**
* @private
* Убрать из бсп дерева перед перестроением сплиттеров.
*/
alternativa3d var updateOperation:Operation = new Operation("removeSector", this, removeFromBSP, Operation.SECTOR_UPDATE);
/**
* @private
* Поиск сплиттеровой ноды в БСП дереве.
*/
alternativa3d var findNodeOperation:Operation = new Operation("addSector", this, addToBSP, Operation.SECTOR_FIND_NODE);
/**
* @private
* Изименение видимости.
*/
alternativa3d var changeVisibleOperation:Operation = new Operation("changeSectorVisibility", this, changeVisible, Operation.SECTOR_CHANGE_VISIBLE);
/**
* @private
* Список видимых секторов.
*/
alternativa3d var _visible:Set = new Set();
/**
* @private
* Координата x сектора.
*/
private var x:Number;
/**
* @private
* Координата y сектора.
*/
private var y:Number;
/**
* @private
* Координата z сектора.
*/
private var z:Number;
/**
* @private
*/
alternativa3d var _scene:Scene3D;
// Сплиттеровая нода
private var _node:BSPNode;
/**
* Имя сектора.
*/
public var name:String;
/**
* Создаёт новый экземпляр сектора. По координатам сектора определяется та дочерняя ветка сплиттера, для которой сектор задает видимость.
*
* @param x координата по оси X
* @param y координата по оси Y
* @param z координата по оси Z
* @param name имя сектора. При указании <code>null</code> используется автоматически сгенерированное имя.
*/
public function Sector(x:Number = 0, y:Number = 0, z:Number = 0, name:String = null) {
this.name = (name != null) ? name : "sector" + ++counter;
this.x = x;
this.y = y;
this.z = z;
_visible[this] = true;
// Обновление в дереве требует перевставки
updateOperation.addSequel(findNodeOperation);
// Обновление в дереве требует перерисовки
findNodeOperation.addSequel(changeVisibleOperation);
}
/**
* Добавляет сектора в список видимых. Видимость секторов взаимная, поэтому текущий сектор автоматически добавляется
* в список видимых для каждого из указанных секторов.
*
* @param sector сектор, добавляемый в список видимых
* @param sectors дополнительные сектора, добавляемые в список видимых
*
* @see #removeVisible()
*/
public function addVisible(sector:Sector, ...sectors):void {
sector._visible[this] = true;
_visible[sector] = true;
sector.markToChange();
var count:int = sectors.length;
for (var i:int = 0; i < count; i++) {
var sc:Sector = sectors[i];
sc._visible[this] = true;
_visible[sc] = true;
sc.markToChange();
}
markToChange();
}
/**
* Удаляет сектора из списка видимых. Видимость секторов взаимная, поэтому текущий сектор автоматически удаляется из списка видимости указанных секторов.
*
* @param sector сектор, удаляемый из списка видимости
* @param sectors дополнительные сектора, удаляемые из списка видимых
*
* @see #addVisible()
*/
public function removeVisible(sector:Sector, ...sectors):void {
if (_visible[sector] && sector != this) {
delete sector._visible[this];
sector.markToChange();
delete _visible[sector];
markToChange();
}
var count:int = sectors.length;
for (var i:int = 0; i < count; i++) {
var sc:Sector = sectors[i];
if (_visible[sc] && sc != this) {
delete sc._visible[this];
sc.markToChange();
delete _visible[sc];
markToChange();
}
}
}
/**
* Определяет потенциальную видимость заданного сектора.
*
* @param sector сектор, видимость которого проверяется
* @return <code>true</code>, если указанный сектор находится в списке видимости текущего, иначе <code>false</code>
*/
public function isVisible(sector:Sector):Boolean {
return _visible[sector];
}
/**
* Создаёт строковое представление объекта.
*
* @return строковое представление объекта
*/
public function toString():String {
var s:String = "[Sector " + name + " X:" + x.toFixed(3) + " Y:" + y.toFixed(3) + " Z:" + z.toFixed(3);
var sector:Sector;
var v:String = "";
for (var sc:* in _visible) {
if (sc != this) {
if (sector == null) {
sector = sc;
v = sector.name;
} else {
sector = sc;
v += " " + sector.name;
}
}
}
return (sector == null) ? s + "]" : s + " visible:[" + v + "]]";
}
/**
* @private
*/
alternativa3d function addToScene(scene:Scene3D):void {
_scene = scene;
// Перестройка сплиттеров перевставляет сектор
scene.updateSplittersOperation.addSequel(updateOperation);
// Изменение видимости вызывает перерисовку
changeVisibleOperation.addSequel(scene.changePrimitivesOperation);
// Поиск ноды
scene.addOperation(findNodeOperation);
}
/**
* @private
*/
alternativa3d function removeFromScene(scene:Scene3D):void {
scene.updateSplittersOperation.removeSequel(updateOperation);
changeVisibleOperation.removeSequel(scene.changePrimitivesOperation);
scene.removeOperation(findNodeOperation);
scene.removeOperation(changeVisibleOperation);
// Убираем сектор из бсп дерева
removeFromBSP();
_scene = null;
}
/**
* @private
* Установка приоритета сектора. Используется для сохранения последовательности добавления в дерево.
*/
alternativa3d function setLevel(level:int):void {
findNodeOperation.priority = (findNodeOperation.priority & 0xFF000000) | level;
}
/**
* @private
* Послать операцию обновления видимости на сцену
*/
alternativa3d function markToChange():void {
if (_scene != null) {
_scene.addOperation(changeVisibleOperation);
}
}
/**
* Убирает сектор из БСП дерева.
*/
private function removeFromBSP():void {
if (_node != null) {
if (_node.frontSector == this) {
_node.frontSector = null;
} else {
_node.backSector = null;
}
_node = null;
}
}
/**
* Поиск сплиттеровой ноды для этого сектора.
*/
private function addToBSP():void {
findSectorNode(_scene.bsp);
}
/**
* Рекурсивный поиск ноды сектора.
*/
private function findSectorNode(node:BSPNode):void {
if (node != null && node.splitter != null) {
var normal:Point3D = node.normal;
if (x*normal.x + y*normal.y + z*normal.z - node.offset >= 0) {
if (node.front == null || node.front.splitter == null) {
if (node.frontSector == null) {
node.frontSector = this;
_node = node;
}
} else {
findSectorNode(node.front);
}
} else {
if (node.back == null || node.back.splitter == null) {
if (node.backSector == null) {
node.backSector = this;
_node = node;
}
} else {
findSectorNode(node.back);
}
}
}
}
/**
* Отправляет примитивы текущего сектора на перерисовку.
*/
private function changeVisible():void {
if (_node != null) {
var primitive:*;
if (_node.frontSector == this) {
changeNode(_node.front);
} else {
changeNode(_node.back);
}
}
}
/**
* Отправляет на перерисовку ветку бсп дерева.
*/
private function changeNode(node:BSPNode):void {
if (node != null) {
if (node.primitive != null) {
_scene.changedPrimitives[node.primitive] = true;
} else {
var primitive:*;
for (primitive in node.frontPrimitives) {
_scene.changedPrimitives[primitive] = true;
}
for (primitive in node.backPrimitives) {
_scene.changedPrimitives[primitive] = true;
}
}
changeNode(node.back);
changeNode(node.front);
}
}
}
}

View File

@@ -0,0 +1,307 @@
package alternativa.engine3d.core {
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.errors.SplitterNeedMoreVerticesError;
import alternativa.types.Matrix3D;
import alternativa.types.Point3D;
use namespace alternativa3d;
/**
* Сплиттеры применяются для управления видимостью отдельных частей сцены. Сплиттеры находятся ближе к корню BSP-дерева, чем обычные полигоны.
* Таким образом, они образуют систему ветвей, каждая из которых может быть скрыта от своего соседа.
*
* <p>Например, пусть в сцене задан единственный сплиттер, а камера находится в левой ветке. При открытом сплиттере камера будет
* иметь возможность видеть содержимое правой ветки. Если же сплиттер закрыт, то содержимое правой ветки дерева не будет отображаться совсем.</p>
*
* <p>Глубина сплиттеров в сцене определяется порядком их расположения в массиве при задании свойства <code>Scene3D.splitters</code>. Каждый
* последующий сплиттер разделяет на части пространства, образованные предыдущими сплиттерами. При этом он, как и обычный полигон, может быть
* поделён на несколько частей в процессе встраивания в BSP-дерево.</p>
*
* <p>Для управления видимостью ветвей дерева, образованных системой сплиттеров, дополнительно могут быть использованы объекты класса
* <code>Sector</code>.</p>
*
* @see Scene3D#splitters
* @see Sector
*/
public class Splitter {
// Счетчик имен объекта
private static var counter:uint = 0;
/**
* Создает сплиттер из грани. Если объект, которому принадлежит грань, находится на сцене,
* используются глобальные координаты вершин в сцене, иначе используются локальные координаты вершин грани.
*
* @param face грань, которая будет использована для создания сплиттера
* @param name имя нового экземпляра сплиттера. Если указано значение <code>null</code>, имя будет выбрано автоматически.
*/
public static function createFromFace(face:Face, name:String = null):Splitter {
var src:Array = face._vertices;
var dest:Array = new Array();
var i:int;
if (face._mesh != null && face._mesh._scene != null) {
var m:Matrix3D = Object3D.matrix2;
face._mesh.getTransformation(m);
for (i = 0; i < face._verticesCount; i++) {
var p:Point3D = Vertex(src[i])._coords.clone();
p.transform(m);
dest[i] = p;
}
} else {
for (i = 0; i < face._verticesCount; i++) {
dest[i] = Vertex(src[i])._coords;
}
}
return new Splitter(dest, name);
}
/**
* @private
* Изменение состояния сплиттера в БСП дереве.
*/
alternativa3d var changeStateOperation:Operation = new Operation("changeSplitterState", this, changeState, Operation.SPLITTER_CHANGE_STATE);
/**
* @private
* Обновление примитива в сцене.
*/
alternativa3d var updatePrimitiveOperation:Operation = new Operation("updateSplitter", this, updatePrimitive, Operation.SPLITTER_UPDATE);
/**
* @private
* Состояние
*/
alternativa3d var _open:Boolean = true;
/**
* @private
* Примитив
*/
alternativa3d var primitive:SplitterPrimitive;
/**
* @private
* Нормаль
*/
alternativa3d var normal:Point3D = new Point3D();
/**
* @private
* Оффсет
*/
alternativa3d var offset:Number;
/**
* @private
* Сцена
*/
alternativa3d var _scene:Scene3D;
/**
* Имя объекта.
*/
public var name:String;
/**
* Создает экземпляр сплиттера.
*
* @param vertices массив координат вершин сплиттера. Плоскость, в которой расположены вершины,
* будет использована в качестве плоскости сплиттера.
*
* @param name имя объекта. Если указано значение <code>null</code>, имя будет выбрано автоматически.
*
* @throws alternativa.engine3d.errors.SplitterNeedMoreVerticesError для создания сплиттера было передано менее трех точек.
*/
public function Splitter(vertices:Array, name:String = null) {
var count:int = vertices.length;
if (count < 3) {
throw new SplitterNeedMoreVerticesError(count);
}
primitive = SplitterPrimitive.create();
primitive.mobility = int.MIN_VALUE;
primitive.splitter = this;
for (var i:int = 0; i < count; i++) {
primitive.points[i] = Point3D(vertices[i]).clone();
}
primitive.num = count;
calculatePlane();
this.name = (name != null) ? name : "splitter" + ++counter;
}
/**
* Список вершин сплиттера. Элементами являются объекты класса <code>Point3D</code>.
*/
public function get vertices():Array {
var res:Array = new Array().concat(primitive.points);
for (var i:int = 0; i < primitive.num; i++) {
res[i] = Point3D(res[i]).clone();
}
return res;
}
/**
* Состояние сплиттера. При закрытом состоянии сплиттера в камере не рисуются части сцены,
* расположенные в соседней ветке относительно данного сплиттера.
*/
public function get open():Boolean {
return _open;
}
/**
* @private
*/
public function set open(value:Boolean):void {
if (_open != value) {
_open = value;
if (_scene != null) {
_scene.addOperation(changeStateOperation);
}
}
}
/**
* Создаёт строковое представление объекта.
*
* @return строковое представление объекта
*/
public function toString():String {
return "[Splitter " + name + ((_open) ? " open]" : " closed]");
}
/**
* @private
* Добавление на сцену
*/
alternativa3d function addToScene(scene:Scene3D):void {
_scene = scene
// Изменение состояния вызывает перерисовку
changeStateOperation.addSequel(_scene.changePrimitivesOperation);
// Обновление сплиттеров в сцене вызывает перевставку
_scene.updateBSPOperation.addSequel(updatePrimitiveOperation);
}
/**
* @private
* Удаление из сцены
*/
alternativa3d function removeFromScene(scene:Scene3D):void {
changeStateOperation.removeSequel(scene.changePrimitivesOperation);
scene.updateBSPOperation.removeSequel(updatePrimitiveOperation);
scene.removeOperation(changeStateOperation);
// Удаляем примитив сплиттера из сцены
removePrimitive(primitive);
_scene = null
}
/**
* @private
* Расчет нормали и оффсета плоскости.
*/
private function calculatePlane():void {
// Вектор AB
var av:Point3D = primitive.points[0];
var bv:Point3D = primitive.points[1];
var abx:Number = bv.x - av.x;
var aby:Number = bv.y - av.y;
var abz:Number = bv.z - av.z;
// Вектор AC
var cv:Point3D = primitive.points[2];
var acx:Number = cv.x - av.x;
var acy:Number = cv.y - av.y;
var acz:Number = cv.z - av.z;
// Перпендикуляр к плоскости
normal.x = acz*aby - acy*abz;
normal.y = acx*abz - acz*abx;
normal.z = acy*abx - acx*aby;
// Нормализация перпендикуляра
normal.normalize();
offset = av.x*normal.x + av.y*normal.y + av.z*normal.z;
}
/**
* @private
* Помечает конечные примитивы на удаление.
*/
private function updatePrimitive():void {
removePrimitive(primitive);
}
/**
* @private
* Отправляет на перерисовку примитивы сплиттеровой ноды.
*/
private function changeState():void {
changePrimitiveNode(primitive);
}
/**
* @private
* Отправляет на перерисовку ноды сплиттера.
*/
private function changePrimitiveNode(primitive:PolyPrimitive):void {
if (primitive.backFragment == null) {
// Базовый примитив
changePrimitivesInNode(primitive.node.back);
changePrimitivesInNode(primitive.node.front);
} else {
// Примитив попилился на куски
changePrimitiveNode(primitive.backFragment);
changePrimitiveNode(primitive.frontFragment);
}
}
/**
* @private
* Отправляет на перерисовку ветку бсп дерева.
*/
private function changePrimitivesInNode(node:BSPNode):void {
if (node != null) {
if (node.primitive != null) {
_scene.changedPrimitives[node.primitive] = true;
} else {
var primitive:*;
for (primitive in node.frontPrimitives) {
_scene.changedPrimitives[primitive] = true;
}
for (primitive in node.backPrimitives) {
_scene.changedPrimitives[primitive] = true;
}
}
changePrimitivesInNode(node.back);
changePrimitivesInNode(node.front);
}
}
/**
* @private
* Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на удаление из сцены
*/
private function removePrimitive(primitive:PolyPrimitive):void {
if (primitive.backFragment != null) {
// Удаляем куски примитива
removePrimitive(primitive.backFragment);
removePrimitive(primitive.frontFragment);
primitive.backFragment = null;
primitive.frontFragment = null;
} else {
// Если примитив в BSP-дереве
if (primitive.node != null) {
primitive.node.splitter = null;
// Удаление примитива
_scene.removeBSPPrimitive(primitive);
}
}
if (primitive != this.primitive) {
primitive.parent = null;
primitive.sibling = null;
SplitterPrimitive.destroy(primitive as SplitterPrimitive);
}
}
}
}

View File

@@ -0,0 +1,61 @@
package alternativa.engine3d.core {
import alternativa.engine3d.alternativa3d;
use namespace alternativa3d;
/**
* @private
* Сплиттеровый примитив.
*/
public class SplitterPrimitive extends PolyPrimitive {
/**
* Сплиттер
*/
alternativa3d var splitter:Splitter;
/**
* @inheritDoc
*/
override alternativa3d function createFragment():PolyPrimitive {
var primitive:SplitterPrimitive = create();
primitive.splitter = splitter;
primitive.mobility = mobility;
return primitive;
}
// Хранилище неиспользуемых примитивов
static private var collector:Array = new Array();
/**
* @private
* Создать примитив
*/
static alternativa3d function create():SplitterPrimitive {
var primitive:SplitterPrimitive;
if ((primitive = collector.pop()) != null) {
return primitive;
}
return new SplitterPrimitive();
}
/**
* @private
* Кладёт примитив в коллектор для последующего реиспользования.
* Ссылка на грань и массивы точек зачищаются в этом методе.
* Ссылки на фрагменты (parent, sibling, back, front) должны быть зачищены перед запуском метода.
*
* Исключение:
* при сборке примитивов в сцене ссылки на back и front зачищаются после запуска метода.
*
* @param primitive примитив на реиспользование
*/
static alternativa3d function destroy(primitive:SplitterPrimitive):void {
primitive.splitter = null;
primitive.points.length = 0;
collector.push(primitive);
}
}
}

View File

@@ -0,0 +1,208 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
import alternativa.engine3d.materials.SpriteMaterial;
use namespace alternativa3d;
/**
* Объект представляет собой точку в трёхмерном пространстве. Объекту может быть назначен материал для вывода различных изображений в
* месте его нахождения.
*/
public class Sprite3D extends Object3D {
/**
* Счетчик имен объектов
*/
private static var counter:uint = 0;
/**
* @private
* Обновление материала.
*/
alternativa3d var updateMaterialOperation:Operation = new Operation("updateSpriteMaterial", this, updateMaterial, Operation.SPRITE_UPDATE_MATERIAL);
/**
* @private
* Примитив.
*/
alternativa3d var primitive:SpritePrimitive;
/**
* @private
* Материал.
*/
alternativa3d var _material:SpriteMaterial;
/**
* @private
* Размер материала.
*/
alternativa3d var _materialScale:Number;
/**
* Создание экземпляра спрайта.
*
* @param name имя спрайта
*/
public function Sprite3D(name:String = null) {
super(name);
// Создаем примитив спрайта
primitive = new SpritePrimitive();
primitive.sprite = this;
// В примитиве одна точка - координата спрайта
primitive.points = [this.globalCoords];
primitive.num = 1;
primitive.mobility = int.MAX_VALUE;
}
/**
* @private
* Расчет перемещения точки спрайта.
*/
override alternativa3d function calculateTransformation():void {
super.calculateTransformation();
// Произошло перемещение спрайта, необходимо перевставить точку в БСП
updatePrimitive();
if (changeRotationOrScaleOperation.queued) {
// Считаем размер материала
// Считается для любого материала, без отдельных операций
var a:Number = _transformation.a;
var b:Number = _transformation.b;
var c:Number = _transformation.c;
var e:Number = _transformation.e;
var f:Number = _transformation.f;
var g:Number = _transformation.g;
var i:Number = _transformation.i;
var j:Number = _transformation.j;
var k:Number = _transformation.k;
_materialScale = (Math.sqrt(a*a + e*e + i*i) + Math.sqrt(b*b + f*f + j*j) + Math.sqrt(c*c + g*g + k*k))/3;
}
}
/**
* Перевставка точки спрайта в БСП дереве.
*/
private function updatePrimitive():void {
// Если примитив в BSP-дереве
if (primitive.node != null) {
// Удаление примитива
_scene.removeBSPPrimitive(primitive);
}
_scene.addPrimitives.push(primitive);
}
/**
* Перерисовка скинов спрайта.
*/
private function updateMaterial():void {
if (!calculateTransformationOperation.queued) {
_scene.changedPrimitives[primitive] = true;
}
}
/**
* @inheritDoc
*/
override protected function addToScene(scene:Scene3D):void {
super.addToScene(scene);
// Подписываем сцену на операции
calculateTransformationOperation.addSequel(scene.calculateBSPOperation);
updateMaterialOperation.addSequel(scene.changePrimitivesOperation);
// Добавляем на сцену материал
if (_material != null) {
_material.addToScene(scene);
}
}
/**
* @inheritDoc
*/
override protected function removeFromScene(scene:Scene3D):void {
// Удаляем все операции из очереди
scene.removeOperation(updateMaterialOperation);
// Если примитив в BSP-дереве
if (primitive.node != null) {
// Удаляем примитив из сцены
scene.removeBSPPrimitive(primitive);
}
// Посылаем операцию сцены на расчёт BSP
scene.addOperation(scene.calculateBSPOperation);
// Отписываем сцену от операций
calculateTransformationOperation.removeSequel(scene.calculateBSPOperation);
updateMaterialOperation.removeSequel(scene.changePrimitivesOperation);
// Удаляем из сцены материал
if (_material != null) {
_material.removeFromScene(scene);
}
super.removeFromScene(scene);
}
/**
* @private
* Изменение материала.
*/
alternativa3d function addMaterialChangedOperationToScene():void {
if (_scene != null) {
_scene.addOperation(updateMaterialOperation);
}
}
/**
* Материал для отображения спрайта.
*/
public function get material():SpriteMaterial {
return _material;
}
/**
* @private
*/
public function set material(value:SpriteMaterial):void {
if (_material != value) {
if (_material != null) {
_material.removeFromSprite(this);
if (_scene != null) {
_material.removeFromScene(_scene);
}
}
if (value != null) {
if (value._sprite != null) {
value._sprite.material = null;
}
value.addToSprite(this);
if (_scene != null) {
value.addToScene(_scene);
}
}
_material = value;
// Отправляем операцию изменения материала
addMaterialChangedOperationToScene();
}
}
/**
* Имя объекта по умолчанию.
*
* @return имя объекта по умолчанию
*/
override protected function defaultName():String {
return "sprite" + ++counter;
}
/**
* @inheritDoc
*/
protected override function createEmptyObject():Object3D {
return new Sprite3D();
}
/**
* @inheritDoc
*/
protected override function clonePropertiesFrom(source:Object3D):void {
super.clonePropertiesFrom(source);
material = (source as Sprite3D).material.clone() as SpriteMaterial;
}
}
}

View File

@@ -0,0 +1,33 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
use namespace alternativa3d;
/**
* @private
* Спрайтовый примитив.
*/
public class SpritePrimitive extends PolyPrimitive {
/**
* @private
* Спрайт, которому принадлежит примитив.
*/
alternativa3d var sprite:Sprite3D;
/**
* @private
* Параметр используется для сортировки примитивов в камере.
*/
alternativa3d var screenDepth:Number;
/**
* @private
* Строковое представление объекта.
*/
override public function toString():String {
return "[SpritePrimitive " + sprite.toString() + "]";
}
}
}

View File

@@ -0,0 +1,479 @@
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;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
use namespace alternativa3d;
/**
* Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одной и той же поверхностью.
* Между нажатием и отпусканием кнопки могут происходить любые другие события.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.CLICK
*/
[Event (name="click", type="alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь нажимает левую кнопку мыши над поверхностью.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN
*/
[Event (name="mouseDown", type="alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь отпускает левую кнопку мыши над поверхностью.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP
*/
[Event (name="mouseUp", type="alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь наводит курсор мыши на поверхность.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER
*/
[Event (name="mouseOver", type="alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь уводит курсор мыши с поверхности.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT
*/
[Event (name="mouseOut", type="alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь перемещает курсор мыши над поверхностью.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE
*/
[Event (name="mouseMove", type="alternativa.engine3d.events.MouseEvent3D")]
/**
* Событие рассылается когда пользователь вращает колесо мыши над поверхностью.
*
* @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL
*/
[Event (name="mouseWheel", type="alternativa.engine3d.events.MouseEvent3D")]
/**
* Поверхность &mdash; набор граней, объединённых в группу. Поверхности используются для установки материалов,
* визуализирующих грани объекта.
*
* <p> Класс реализует интерфейс <code>flash.events.IEventDispatcher</code> и может рассылать мышиные события, содержащие информацию
* о точке в трёхмерном пространстве, в которой произошло событие.</p>
*/
public class Surface implements IEventDispatcher {
// Операции
/**
* @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 var mouseEnabled:Boolean = true;
/**
* Диспетчер событий.
*/
private var dispatcher:EventDispatcher;
/**
* Создание экземпляра поверхности.
*/
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;
}
/**
* Добавление обработчика события.
*
* @param type тип события
* @param listener обработчик события
* @param useCapture не используется,
* @param priority приоритет обработчика. Обработчики с большим приоритетом выполняются раньше. Обработчики с одинаковым приоритетом
* выполняются в порядке их добавления.
* @param useWeakReference флаг использования слабой ссылки для обработчика
*/
public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
if (dispatcher == null) {
dispatcher = new EventDispatcher(this);
}
useCapture = false;
dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
}
/**
* Рассылка события.
*
* @param event посылаемое событие
* @return false
*/
public function dispatchEvent(event:Event):Boolean {
if (dispatcher != null) {
dispatcher.dispatchEvent(event);
}
return false;
}
/**
* Проверка наличия зарегистрированных обработчиков события указанного типа.
*
* @param type тип события
* @return <code>true</code> если есть обработчики события указанного типа, иначе <code>false</code>
*/
public function hasEventListener(type:String):Boolean {
if (dispatcher != null) {
return dispatcher.hasEventListener(type);
}
return false;
}
/**
* Удаление обработчика события.
*
* @param type тип события
* @param listener обработчик события
* @param useCapture не используется
*/
public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void {
if (dispatcher != null) {
useCapture = false;
dispatcher.removeEventListener(type, listener, useCapture);
}
}
/**
*
*/
public function willTrigger(type:String):Boolean {
if (dispatcher != null) {
return dispatcher.willTrigger(type);
}
return false;
}
}
}

View File

@@ -0,0 +1,251 @@
package alternativa.engine3d.core {
import alternativa.engine3d.*;
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.display {
import alternativa.engine3d.*;
import alternativa.engine3d.core.PolyPrimitive;
import alternativa.engine3d.materials.Material;
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:Material;
// Хранилище неиспользуемых скинов
static private var collector:Array = new Array();
/**
* @private
* Создание скина.
*/
static alternativa3d function createSkin():Skin {
var skin:Skin;
if ((skin = collector.pop()) != null) {
return skin;
}
return new Skin();
}
/**
* @private
* Удаление скина, все ссылки должны быть почищены.
*/
static alternativa3d function destroySkin(skin:Skin):void {
collector.push(skin);
}
}
}

View File

@@ -0,0 +1,892 @@
package alternativa.engine3d.display {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Sprite3D;
import alternativa.engine3d.core.SpritePrimitive;
import alternativa.engine3d.core.Surface;
import alternativa.engine3d.events.MouseEvent3D;
import alternativa.types.Matrix3D;
import alternativa.types.Point3D;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
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;
/**
* @private
* Флаг интерактивности вьюпорта. В интерактивном режиме вьюпорт принимает события мыши и транслирует их
* в подсистему трёхмерных событий, которая, в свою очередь, преобразует двумерный клик в трёхмерный и рассылает
* события всем подписчикам.
*/
alternativa3d var _interactive:Boolean;
// Грань под курсором
private var faceUnderPoint:Face;
private var objectUnderPoint:Object3D;
private var lastMouseEvent:MouseEvent;
private var stagePoint:Point = new Point();
// Текущая грань
private var currentFace:Face;
// Текущая поверхность
private var currentSurface:Surface;
// Текущий объект
private var currentObject:Object3D;
// Грань, на которой было событие MOUSE_DOWN
private var pressedFace:Face;
// Поверхность, на которой было событие MOUSE_DOWN
private var pressedSurface:Surface;
// Объект, на котором было событие MOUSE_DOWN
private var pressedObject:Object3D;
// Направляющий вектор проецирующей прямой в камере
private var lineVector:Point3D = new Point3D();
// Вспомогательная переменная для хранения точки проецирующей прямой в ортографической камере
private var linePoint:Point3D = new Point3D();
// Точка на объекте под курсором в глобальной системе координат.
private var globalCursor3DCoords:Point3D = new Point3D();
// Координаты курсора в системе координат объекта
private var localCursor3DCoords:Point3D = new Point3D();
// UV-координаты в грани под курсором
private var uvPoint:Point = new Point();
// Вспомогательная матрица
private var inverseMatrix:Matrix3D = new Matrix3D();
/**
* Создаёт новый экземпляр вьюпорта.
*
* @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;
addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);
}
/**
* Камера, с которой выводится изображение.
*/
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 координаты точки относительно вьюпорта. Верхнему левому углу соотвествуют координаты (0, 0).
*
* @return ближайший к камере объект под заданной точкой вьюпорта, либо <code>null</code>,
* если под указанной точкой нет объектов или вьюпорт не помещён на Stage.
* Объект может быть гранью (Face) или спрайтом (Sprite3D).
*/
public function getObjectUnderPoint(viewPoint:Point):Object {
if (stage == null) {
return null;
}
var stagePoint:Point = localToGlobal(viewPoint);
var objects:Array = stage.getObjectsUnderPoint(stagePoint);
var skin:Skin;
for (var i:int = objects.length - 1; i >= 0; i--) {
skin = objects[i] as Skin;
if (skin != null && skin.parent.parent == this) {
return skin.primitive.face != null ? skin.primitive.face : (skin.primitive as SpritePrimitive).sprite;
}
}
return null;
}
/**
* Возвращает объекты, находящиеся под указанной точкой вьюпорта.
*
* @param viewPoint координаты точки относительно вьюпорта. Верхнему левому углу соотвествуют координаты (0, 0).
*
* @return массив объектов, расположенных под заданной точкой вьюпорта. Первым элементом массива является самый дальний объект.
* Объектами могут быть грани (Face) или спрайты (Sprite3D). Если под указанной точкой нет ни одного объекта, массив будет пустым.
* Если вьюпорт не помещен на Stage, возвращается <code>null</code>.
*/
override public function getObjectsUnderPoint(viewPoint:Point):Array {
if (stage == null) {
return null;
}
var stagePoint:Point = localToGlobal(viewPoint);
var objects:Array = stage.getObjectsUnderPoint(stagePoint);
var res:Array = new Array();
var length:uint = objects.length;
for (var i:uint = 0; i < length; i++) {
var skin:Skin = objects[i] as Skin;
if (skin != null && skin.parent.parent == this) {
if (skin.primitive.face != null) {
res.push(skin.primitive.face);
} else {
res.push((skin.primitive as SpritePrimitive).sprite);
}
}
}
return res;
}
/**
* Проецирует заданную глобальными координатами точку на плоскость вьюпорта.
*
* @param point глобальные координаты проецируемой точки
*
* @return объект <code>Point3D</code>, содержащий координаты проекции точки относительно левого верхнего угла вьюпорта и z-координату
* точки в системе координат камеры. Если вьюпорту не назначена камера или камера не находится в сцене, возвращается <code>null</code>.
*/
public function projectPoint(point:Point3D):Point3D {
if (_camera == null || _camera._scene == null) {
return null;
}
var cameraMatrix:Matrix3D = Object3D.matrix2;
var focalLength:Number = _camera._focalLength;;
var zoom:Number;
// Вычисление матрицы трансформации камеры
if (_camera.getTransformation(cameraMatrix)) {
// Матрица была пересчитана заново
cameraMatrix.invert();
if (_camera._orthographic) {
// Учёт масштабирования в ортографической камере
zoom = _camera.zoom;
cameraMatrix.scale(zoom, zoom, zoom);
}
} else {
// Пересчёта не потребовалось, проверяем изменение зума
if (_camera._orthographic && _camera.calculateMatrixOperation.queued) {
cameraMatrix.invert();
zoom = _camera.zoom;
cameraMatrix.scale(zoom, zoom, zoom);
} else {
// Зум не менялся или перспективный режим, просто копируем обратную матрицу
cameraMatrix = _camera.cameraMatrix;
}
}
// Расчёт фокусного расстояния
if (!_camera._orthographic && _camera.calculatePlanesOperation.queued) {
focalLength = 0.5 * Math.sqrt(_height * _height + _width * _width) / Math.tan(0.5 * _camera._fov);
}
// Координаты точки в системе координат камеры
var x:Number = cameraMatrix.a * point.x + cameraMatrix.b * point.y + cameraMatrix.c * point.z + cameraMatrix.d;
var y:Number = cameraMatrix.e * point.x + cameraMatrix.f * point.y + cameraMatrix.g * point.z + cameraMatrix.h;
var z:Number = cameraMatrix.i * point.x + cameraMatrix.j * point.y + cameraMatrix.k * point.z + cameraMatrix.l;
// Проекция точки на вьюпорт
if (_camera._orthographic) {
return new Point3D(x + (_width >> 1), y + (_height >> 1), z);
} else {
return new Point3D(x * focalLength / z + (_width >> 1), y * focalLength / z + (_height >> 1), z);
}
}
/**
* Интерактивность области вьюпорта. При включённой интерактивности возможно использование системы мышиных событий.
*
* @default false
*/
public function get interactive():Boolean {
return _interactive;
}
/**
* @private
*/
public function set interactive(value:Boolean):void {
if (_interactive == value) {
return;
}
_interactive = value;
if (_interactive) {
addEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent);
addEventListener(MouseEvent.MOUSE_UP, onMouseEvent);
addEventListener(MouseEvent.MOUSE_MOVE, onMouseEvent);
addEventListener(MouseEvent.MOUSE_WHEEL, onMouseEvent);
addEventListener(MouseEvent.MOUSE_OUT, onMouseEvent);
} else {
removeEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent);
removeEventListener(MouseEvent.MOUSE_UP, onMouseEvent);
removeEventListener(MouseEvent.MOUSE_MOVE, onMouseEvent);
removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseEvent);
removeEventListener(MouseEvent.MOUSE_OUT, onMouseEvent);
if (stage != null) {
stage.removeEventListener(MouseEvent.MOUSE_UP, stageMouseUp);
}
pressedFace = currentFace = null;
pressedSurface = currentSurface = null;
pressedObject = currentObject = null;
}
}
/**
* Проецирует двумерную точку во вьюпорте на заданную плоскость в трёхмерном пространстве.
*
* @param viewPoint координаты точки во вьюпорте. Верхнему левому углу соответствуют координаты (0, 0).
* @param planeNormal нормаль плоскости в глобальной системе координат, на которую проецируется точка
* @param planeOffset смещение плоскости в глобальной системе координат, на которую проецируется точка
* @param result результат проецирования будет записан в этот параметр. Если в качестве значения будет указано <code>null</code>, метод
* создаст новый экземпляр <code>Point3D</code> и вернёт результат в нём.
* @return переданный в параметре result экземпляр <code>Point3D</code> или новый экземпляр, если значение result равно <code>null</code>. Если возможно бесконечное
* количество решений (линия зрения параллельна заданной плоскости), то результат содержит значения NaN.
* Если вьюпорту не назначена камера или камера не находится в сцене, возвращается <code>null</code>.
*/
public function projectViewPointToPlane(viewPoint:Point, planeNormal:Point3D, planeOffset:Number, result:Point3D = null):Point3D {
if (_camera == null || _camera._scene == null) {
return null;
}
if (result == null) {
result = new Point3D();
}
calculateRayOriginAndVector(viewPoint.x - (_width >> 1), viewPoint.y - (_height >> 1), linePoint, lineVector, true);
if (!calculateLineAndPlaneIntersection(linePoint, lineVector, planeNormal, planeOffset, result)) {
result.reset(NaN, NaN, NaN);
}
return result;
}
/**
* Вычисляет координаты точки в системе координат камеры, связанной с вьюпортом. Если камера в режиме перспективной
* проекции, то метод вычислит координаты точки, лежащей на прямой, проходящей через начало координат камеры и указанную точку
* вьюпорта. Если камера в режиме ортографической проекции, то метод вычислит координаты точки, лежащей на прямой,
* перпендикулярной фокальной плоскости камеры и проходящей через указанную точку вьюпорта.
*
* @param viewPoint координаты точки во вьюпорте. Верхнему левому углу соответствуют координаты (0, 0).
* @param depth глубина точки в камере &mdash; координата Z в системе координат камеры
* @param result результат будет записан в этот параметр. Если в качестве значения будет указано <code>null</code>, метод
* создаст новый экземпляр <code>Point3D</code> и вернёт результат в нём.
* @return координаты точки в системе координат камеры или <code>null</code>, если с вьюпортом не связана камера
*/
public function get3DCoords(viewPoint:Point, depth:Number, result:Point3D = null):Point3D {
if (_camera == null) {
return null;
}
if (result == null) {
result = new Point3D();
}
if (_camera._orthographic) {
result.x = (viewPoint.x - (_width >> 1))/camera._zoom;
result.y = (viewPoint.y - (_height >> 1))/camera._zoom;
} else {
var k:Number = depth/_camera.focalLength;
result.x = (viewPoint.x - (_width >> 1))*k;
result.y = (viewPoint.y - (_height >> 1))*k;
}
result.z = depth;
return result;
}
/**
*
*/
private function onRemovedFromStage(e:Event):void {
interactive = false;
}
/**
* Сброс нажатых объектов при отпускании кнопки мыши вне вьюпорта.
*/
private function stageMouseUp(e:MouseEvent):void {
if (stage == null) {
return;
}
pressedFace = null;
pressedSurface = null;
pressedObject = null;
stage.removeEventListener(MouseEvent.MOUSE_UP, stageMouseUp);
}
/**
* Метод находит интерактивный объект (Object3D) и интерактивную грань, если возможно, находящиеся под указанной точкой в области вывода.
*
* @param pointX X-координата точки относительно области вывода
* @param pointY Y-координата точки относительно области вывода
*/
private function getInteractiveObjectUnderPoint(pointX:Number, pointY:Number):void {
if (stage == null) {
return;
}
faceUnderPoint = null;
objectUnderPoint = null;
stagePoint.x = pointX;
stagePoint.y = pointY;
var objects:Array = stage.getObjectsUnderPoint(stagePoint);
var skin:Skin;
for (var i:int = objects.length - 1; i >= 0; i--) {
skin = objects[i] as Skin;
if (skin != null && skin.parent.parent == this) {
if (skin.primitive.face != null) {
// Скин, содержащий PolyPrimitive
if (skin.primitive.face._mesh.mouseEnabled) {
faceUnderPoint = skin.primitive.face;
objectUnderPoint = faceUnderPoint._mesh;
return;
}
} else {
// Скин, содержащий SpritePrimitive
var sprite:Sprite3D = (skin.primitive as SpritePrimitive).sprite;
if (sprite.mouseEnabled) {
objectUnderPoint = sprite;
return;
}
}
}
}
}
/**
* Вычисление свойств точки объекта, находящегося под указанной точкой фокусной плоскости камеры. Метод расчитывает глобальные и локальные
* 3D-координаты точки, а также её UV-координаты.
*
* @param canvasX
* @param canvasY
*/
private function getInteractiveObjectPointProperties(canvasX:Number, canvasY:Number):void {
if (objectUnderPoint == null) {
return;
}
calculateRayOriginAndVector(canvasX, canvasY, linePoint, lineVector);
// Вычисление глобальных координат точки пересечения проецирующей прямой и плоскости объекта
var normal:Point3D;
var offset:Number;
if (faceUnderPoint != null) {
// Работаем с гранью
normal = faceUnderPoint.globalNormal;
offset = faceUnderPoint.globalOffset;
} else {
// Работаем со спрайтом
normal = lineVector.clone();
normal.invert();
globalCursor3DCoords.copy(objectUnderPoint._coords);
globalCursor3DCoords.transform(objectUnderPoint._transformation);
offset = globalCursor3DCoords.dot(normal);
}
calculateLineAndPlaneIntersection(linePoint, lineVector, normal, offset, globalCursor3DCoords);
// Вычисление локальных координат точки пересечения
inverseMatrix.copy((faceUnderPoint != null ? faceUnderPoint._mesh : objectUnderPoint)._transformation);
inverseMatrix.invert();
localCursor3DCoords.copy(globalCursor3DCoords);
localCursor3DCoords.transform(inverseMatrix);
// Вычисление UV-координат
if (faceUnderPoint != null) {
var uv:Point = faceUnderPoint.getUV(localCursor3DCoords);
if (uv != null) {
uvPoint.x = uv.x;
uvPoint.y = uv.y;
} else {
uvPoint.x = NaN;
uvPoint.y = NaN;
}
}
}
/**
*
*/
private function createSimpleMouseEvent3D(type:String, object:Object3D, surface:Surface, face:Face):MouseEvent3D {
var altKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.altKey;
var ctrlKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.ctrlKey;
var shiftKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.shiftKey;
var delta:int = lastMouseEvent == null ? 0 : lastMouseEvent.delta;
return new MouseEvent3D(type, this, object, surface, face,
NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN,
altKey, ctrlKey, shiftKey, delta);
}
/**
*
*/
private function createFullMouseEvent3D(type:String, object:Object3D, surface:Surface, face:Face):MouseEvent3D {
var altKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.altKey;
var ctrlKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.ctrlKey;
var shiftKey:Boolean = lastMouseEvent == null ? false : lastMouseEvent.shiftKey;
var delta:int = lastMouseEvent == null ? 0 : lastMouseEvent.delta;
return new MouseEvent3D(type, this, object, surface, face,
globalCursor3DCoords.x, globalCursor3DCoords.y, globalCursor3DCoords.z, localCursor3DCoords.x, localCursor3DCoords.y, localCursor3DCoords.z, uvPoint.x, uvPoint.y,
altKey, ctrlKey, shiftKey, delta);
}
/**
* Обработка мышиного события на вьюпорте и передача его в систему трёхмерных событий.
*/
private function onMouseEvent(e:MouseEvent):void {
if (stage == null) {
return;
}
// Сохранение события для использования в функциях создания MouseEvent3D
lastMouseEvent = e;
// Получение объекта под курсором и свойств точки на этом объекте
getInteractiveObjectUnderPoint(stage.mouseX, stage.mouseY);
getInteractiveObjectPointProperties(mouseX - (_width >> 1), mouseY - (_height >> 1));
// Обработка события
switch (e.type) {
case MouseEvent.MOUSE_MOVE:
processMouseMove();
break;
case MouseEvent.MOUSE_OUT:
stage.addEventListener(MouseEvent.MOUSE_UP, stageMouseUp);
checkMouseOverOut();
break;
case MouseEvent.MOUSE_DOWN:
processMouseDown();
break;
case MouseEvent.MOUSE_UP:
processMouseUp();
break;
case MouseEvent.MOUSE_WHEEL:
processMouseWheel();
break;
}
lastMouseEvent = null;
}
/**
* Обработка нажатия кнопки мыши во вьюпорте. Генерируются события: MouseEvent3D.MOUSE_DOWN.
*/
private function processMouseDown():void {
if (objectUnderPoint == null) {
return;
}
if (faceUnderPoint != null) {
currentFace = faceUnderPoint;
currentSurface = faceUnderPoint._surface;
} else {
currentFace = null;
currentSurface = null;
}
currentObject = pressedObject = objectUnderPoint;
var evt:MouseEvent3D;
if (currentFace != null && currentFace.mouseEnabled) {
pressedFace = currentFace;
evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_DOWN, currentObject, currentSurface, currentFace);
currentFace.dispatchEvent(evt);
}
if (currentSurface != null && currentSurface.mouseEnabled) {
pressedSurface = currentSurface;
evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_DOWN, currentObject, currentSurface, currentFace);
currentSurface.dispatchEvent(evt);
}
evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_DOWN, currentObject, currentSurface, currentFace);
currentObject.dispatchEvent(evt);
}
/**
* Обработка отжатия кнопки мыши во вьюпорте. Генерируются события: MouseEvent3D.MOUSE_UP, MouseEvent3D.CLICK.
*/
private function processMouseUp():void {
if (objectUnderPoint == null) {
pressedFace = null;
pressedSurface = null;
pressedObject = null;
return;
}
if (faceUnderPoint != null) {
currentFace = faceUnderPoint;
currentSurface = faceUnderPoint._surface;
} else {
currentFace = null;
currentSurface = null;
}
currentObject = objectUnderPoint;
var evt:MouseEvent3D;
// MouseEvent3D.MOUSE_UP
if (currentFace != null && currentFace.mouseEnabled) {
evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_UP, currentObject, currentSurface, currentFace);
currentFace.dispatchEvent(evt);
}
if (currentSurface != null && currentSurface.mouseEnabled) {
evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_UP, currentObject, currentSurface, currentFace);
currentSurface.dispatchEvent(evt);
}
evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_UP, currentObject, currentSurface, currentFace);
currentObject.dispatchEvent(evt);
// MouseEvent3D.CLICK
if (currentFace != null && currentFace == pressedFace && currentFace.mouseEnabled) {
evt = createFullMouseEvent3D(MouseEvent3D.CLICK, currentObject, currentSurface, currentFace);
currentFace.dispatchEvent(evt);
}
if (currentSurface != null && currentSurface == pressedSurface && currentSurface.mouseEnabled) {
evt = createFullMouseEvent3D(MouseEvent3D.CLICK, currentObject, currentSurface, currentFace);
currentSurface.dispatchEvent(evt);
}
if (currentObject == pressedObject) {
evt = createFullMouseEvent3D(MouseEvent3D.CLICK, currentObject, currentSurface, currentFace);
currentObject.dispatchEvent(evt);
}
pressedFace = null;
pressedSurface = null;
pressedObject = null;
}
/**
* Обработка вращения колеса мыши во вьюпорте. Генерируются события: MouseEvent3D.MOUSE_WHEEL.
*/
private function processMouseWheel():void {
if (objectUnderPoint == null) {
return;
}
var evt:MouseEvent3D;
if (faceUnderPoint != null) {
currentFace = faceUnderPoint;
currentSurface = faceUnderPoint._surface;
if (currentFace.mouseEnabled) {
evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_WHEEL, currentObject, currentSurface, currentFace);
currentFace.dispatchEvent(evt);
}
if (currentSurface.mouseEnabled) {
evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_WHEEL, currentObject, currentSurface, currentFace);
currentSurface.dispatchEvent(evt);
}
} else {
currentFace = null;
currentSurface = null;
}
currentObject = objectUnderPoint;
evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_WHEEL, currentObject, currentSurface, currentFace);
currentObject.dispatchEvent(evt);
}
/**
* @private
* Метод проверяет наличиче событий MOUSE_OVER, MOUSE_OUT для объектов сцены, их поверхностей и граней.
*
* @param checkObject флаг необходимости предварительно получить объект под курсором. Используется при вызове метода из функции отрисовки камеры.
*/
alternativa3d function checkMouseOverOut(checkObject:Boolean = false):void {
if (stage == null) {
return;
}
if (checkObject) {
getInteractiveObjectUnderPoint(stage.mouseX, stage.mouseY);
getInteractiveObjectPointProperties(mouseX - (_width >> 1), mouseY - (_height >> 1));
}
var evt:MouseEvent3D;
if (objectUnderPoint == null) {
// Мышь ушла с объекта, генерируются события MOUSE_OUT
if (currentFace != null) {
// MOUSE_OUT для грани
if (currentFace.mouseEnabled) {
evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
currentFace.dispatchEvent(evt);
}
// MOUSE_OUT для поверхности
if (currentSurface.mouseEnabled) {
evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
currentSurface.dispatchEvent(evt);
}
}
if (currentObject != null) {
// MOUSE_OUT для объекта
evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
currentObject.dispatchEvent(evt);
}
currentFace = null;
currentSurface = null;
currentObject = null;
} else {
// Мышь на каком-то объекте
var surface:Surface;
var faceChanged:Boolean;
var surfaceChanged:Boolean;
var objectChanged:Boolean;
if (faceUnderPoint != null) {
surface = faceUnderPoint._surface;
}
//
if (faceUnderPoint != currentFace) {
// MOUSE_OUT для грани
if (currentFace != null && currentFace.mouseEnabled) {
evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
currentFace.dispatchEvent(evt);
}
faceChanged = true;
// MOUSE_OUT для поверхности
if (surface != currentSurface) {
if (currentSurface != null && currentSurface.mouseEnabled) {
evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
currentSurface.dispatchEvent(evt);
}
surfaceChanged = true;
}
}
// MOUSE_OUT для объекта
if (objectUnderPoint != currentObject) {
if (currentObject != null) {
evt = createSimpleMouseEvent3D(MouseEvent3D.MOUSE_OUT, currentObject, currentSurface, currentFace);
currentObject.dispatchEvent(evt);
}
objectChanged = true;
}
currentFace = faceUnderPoint;
currentSurface = surface;
currentObject = objectUnderPoint;
if (currentFace != null) {
// MOUSE_OVER для грани
if (faceChanged && currentFace.mouseEnabled) {
evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_OVER, currentObject, currentSurface, currentFace);
currentFace.dispatchEvent(evt);
}
// MOUSE_OVER для поверхности
if (surfaceChanged && currentSurface.mouseEnabled) {
evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_OVER, currentObject, currentSurface, currentFace);
currentSurface.dispatchEvent(evt);
}
}
// MOUSE_OVER для объекта
if (objectChanged) {
evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_OVER, currentObject, currentSurface, currentFace);
currentObject.dispatchEvent(evt);
}
}
}
/**
* Обработчик движения мыши.
*/
private function processMouseMove():void {
// Запуск проверки на наличие событий MOUSE_OVER и MOUSE_OUT
checkMouseOverOut();
// Генерация событий MOUSE_MOVE
var evt:MouseEvent3D;
if (currentFace != null) {
// Мышь на каком-то объекте
if (currentFace.mouseEnabled) {
evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_MOVE, currentObject, currentSurface, currentFace);
currentFace.dispatchEvent(evt);
}
if (currentSurface.mouseEnabled) {
evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_MOVE, currentObject, currentSurface, currentFace);
currentSurface.dispatchEvent(evt);
}
}
if (currentObject != null) {
evt = createFullMouseEvent3D(MouseEvent3D.MOUSE_MOVE, currentObject, currentSurface, currentFace);
currentObject.dispatchEvent(evt);
}
}
/**
* @private
* Вычисляет точку пересечения прямой и плоскости.
*
* @param linePoint точка, лежащая на прямой
* @param lineVector направляющий вектор прямой
* @param planeNormal нормаль плоскости
* @param planeOffset смещение плоскости
* @param result переменная для сохранения координат точки пересечения
*
* @return <code>true</code> если прямая и плоскость пересекаются, иначе <code>false</code>
*/
private function calculateLineAndPlaneIntersection(linePoint:Point3D, lineVector:Point3D, planeNormal:Point3D, planeOffset:Number, result:Point3D):Boolean {
var dot:Number = planeNormal.x*lineVector.x + planeNormal.y*lineVector.y + planeNormal.z*lineVector.z;
if (dot < 1E-8 && dot > -1E-8) {
// Прямая и плосоксть параллельны
return false;
}
var k:Number = (planeOffset - linePoint.x*planeNormal.x - linePoint.y*planeNormal.y - linePoint.z*planeNormal.z)/dot;
result.x = linePoint.x + k*lineVector.x;
result.y = linePoint.y + k*lineVector.y;
result.z = linePoint.z + k*lineVector.z;
return true;
}
/**
* Вычисляет точку и направляющий вектор для луча зрения, проходящего через заданную точку вьюпорта.
*
* @param rayOrigin сюда будут записаны глобальные координаты начальной точки луча
* @param rayVector сюда будут записаны глобальные координаты направляющего вектора
* @param calculate определяет необходимо ли вычислять актуальные значения объекта при вызове метода
*/
private function calculateRayOriginAndVector(canvasX:Number, canvasY:Number, rayOrigin:Point3D, rayVector:Point3D, calculate:Boolean = false):void {
var x:Number;
var y:Number;
var z:Number;
// Вычисление направляющего вектора и точки проецирующей прямой в глобальном пространстве
// Вычисление матрицы трансформации камеры
var m:Matrix3D;
if (calculate) {
m = Object3D.matrix2;
_camera.getTransformation(m);
} else {
m = _camera._transformation;
}
if (_camera._orthographic) {
// Координаты точки на луче
x = canvasX/_camera.zoom;
y = canvasY/_camera.zoom;
rayOrigin.x = m.a*x + m.b*y + m.d;
rayOrigin.y = m.e*x + m.f*y + m.h;
rayOrigin.z = m.i*x + m.j*y + m.l;
// Координаты локального направляющего вектора
x = y = 0;
z = 1;
} else {
// Координаты точки на луче
rayOrigin.x = m.d;
rayOrigin.y = m.h;
rayOrigin.z = m.l;
// Координаты локального направляющего вектора
x = canvasX;
y = canvasY;
if (calculate) {
z = _camera.focalLength;
} else {
z = _camera._focalLength;
}
}
// Направляющий вектор в глобальном пространстве
lineVector.x = x*m.a + y*m.b + z*m.c;
lineVector.y = x*m.e + y*m.f + z*m.g;
lineVector.z = x*m.i + y*m.j + z*m.k;
}
}
}

View File

@@ -0,0 +1,25 @@
package alternativa.engine3d.errors {
/**
* Базовый класс для ошибок 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,24 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Scene3D;
import alternativa.engine3d.core.Sector;
import alternativa.utils.TextUtils;
/**
* Ошибка, возникающая при попытке добавить на сцену сектор, расположенный в другой сцене.
*/
public class SectorInOtherSceneError extends Engine3DError {
/**
* Создание экземпляра класса.
*
* @param sector экземпляр сектора, расположенный в другой сцене
* @param source сцена, из которой было вызвано исключение.
*/
public function SectorInOtherSceneError(sector:Sector = null, source:Scene3D = null) {
super(TextUtils.insertVars("%1. Sector %2 is aready situated in the other scene", source, sector), source);
this.name = "SectorInOtherSceneError";
}
}
}

View File

@@ -0,0 +1,24 @@
package alternativa.engine3d.errors {
import alternativa.engine3d.core.Scene3D;
import alternativa.engine3d.core.Splitter;
import alternativa.utils.TextUtils;
/**
* Ошибка, возникающая при попытке добавить на сцену сплиттер, расположенный в другой сцене.
*/
public class SplitterInOtherSceneError extends Engine3DError {
/**
* Создание экземпляра класса.
*
* @param splitter экземпляр сплиттера, расположенный в другой сцене
* @param source сцена, из которой было вызвано исключение.
*/
public function SplitterInOtherSceneError(splitter:Splitter = null, source:Scene3D = null) {
super(TextUtils.insertVars("%1. Splitter %2 is aready situated in the other scene", source, splitter), source);
this.name = "SplitterInOtherSceneError";
}
}
}

View File

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

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,174 @@
package alternativa.engine3d.events {
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Surface;
import alternativa.engine3d.display.View;
import flash.events.Event;
/**
* Событие, возникающее при взаимодействии мыши с объектами сцены.
*/
public class MouseEvent3D extends Event {
/**
* Значение свойства <code>type</code> для объекта события <code>click</code>.
* @eventType click
*/
public static const CLICK:String = "click";
/**
* Значение свойства <code>type</code> для объекта события <code>mouseDown</code>.
* @eventType mouseDown
*/
public static const MOUSE_DOWN:String = "mouseDown";
/**
* Значение свойства <code>type</code> для объекта события <code>mouseUp</code>.
* @eventType mouseUp
*/
public static const MOUSE_UP:String = "mouseUp";
/**
* Значение свойства <code>type</code> для объекта события <code>mouseOver</code>.
* @eventType mouseOver
*/
public static const MOUSE_OVER:String = "mouseOver";
/**
* Значение свойства <code>type</code> для объекта события <code>mouseOut</code>.
* @eventType mouseOut
*/
public static const MOUSE_OUT:String = "mouseOut";
/**
* Значение свойства <code>type</code> для объекта события <code>mouseMove</code>.
* @eventType mouseMove
*/
public static const MOUSE_MOVE:String = "mouseMove";
/**
* Значение свойства <code>type</code> для объекта события <code>mouseWheel</code>.
* @eventType mouseWheel
*/
public static const MOUSE_WHEEL:String = "mouseWheel";
/**
* Объект сцены, с которым связано событие.
*/
public var object:Object3D;
/**
* Поверхность объекта сцены, с которой связано событие.
*/
public var surface:Surface;
/**
* Грань объекта сцены, с которой связано событие.
*/
public var face:Face;
/**
* Область вывода, в которой произошло событие.
*/
public var view:View;
/**
* X-координата мышиного курсора в сцене.
*/
public var globalX:Number;
/**
* Y-координата мышиного курсора в сцене.
*/
public var globalY:Number;
/**
* Z-координата мышиного курсора в сцене.
*/
public var globalZ:Number;
/**
* X-координата мышиного курсора в системе координат объекта.
*/
public var localX:Number;
/**
* Y-координата мышиного курсора в системе координат объекта.
*/
public var localY:Number;
/**
* Z-координата мышиного курсора в системе координат объекта.
*/
public var localZ:Number;
/**
* Текстурная координата U в точке нахождения мышиного курсора. При отсутствии текстурных координат у грани, поле содержит значение <code>NaN</code>.
*/
public var u:Number;
/**
* Текстурная координата V в точке нахождения мышиного курсора. При отсутствии текстурных координат у грани, поле содержит значение <code>NaN</code>.
*/
public var v:Number;
/**
* Индикатор нажатой (<code>true</code>) или отпущенной (<code>false</code>) клавиши Alt.
*/
public var altKey:Boolean;
/**
* Индикатор нажатой (<code>true</code>) или отпущенной (<code>false</code>) клавиши Control.
*/
public var ctrlKey:Boolean;
/**
* Индикатор нажатой (<code>true</code>) или отпущенной (<code>false</code>) клавиши Shift.
*/
public var shiftKey:Boolean;
/**
* Количество линий прокрутки при вращении колеса мыши.
*/
public var delta:int;
/**
* Создаёт новый экземпляр события.
*
* @param type тип события
* @param view область вывода, в которой произошло событие
* @param object объект сцены, с которым связано событие
* @param surface поверхность, с которой связано событие
* @param face грань, с которой связано событие
* @param globalX X-координата мышиного курсора в сцене
* @param globalY Y-координата мышиного курсора в сцене
* @param globalZ Z-координата мышиного курсора в сцене
* @param localX X-координата мышиного курсора в системе координат объекта
* @param localY Y-координата мышиного курсора в системе координат объекта
* @param localZ Z-координата мышиного курсора в системе координат объекта
* @param u текстурная координата U в точке нахождения мышиного курсора
* @param v текстурная координата V в точке нахождения мышиного курсора
*/
public function MouseEvent3D(type:String, view:View, object:Object3D, surface:Surface, face:Face, globalX:Number = NaN, globalY:Number = NaN, globalZ:Number = NaN, localX:Number = NaN, localY:Number = NaN, localZ:Number = NaN, u:Number = NaN, v:Number = NaN, altKey:Boolean = false, ctrlKey:Boolean = false, shiftKey:Boolean = false, delta:int = 0) {
super(type);
this.view = view;
this.object = object;
this.surface = surface;
this.face = face;
this.globalX = globalX;
this.globalY = globalY;
this.globalZ = globalZ;
this.localX = localX;
this.localY = localY;
this.localZ = localZ;
this.u = u;
this.v = v;
this.altKey = altKey;
this.ctrlKey = ctrlKey;
this.shiftKey = shiftKey;
this.delta = delta;
}
/**
* Получение строкового представления объекта.
*
* @return строковое представление объекта
*/
override public function toString():String {
return formatToString("MouseEvent3D", "object", "surface", "face", "globalX", "globalY", "globalZ", "localX", "localY", "localZ", "u", "v", "delta", "altKey", "ctrlKey", "shiftKey");
}
/**
* Возвращает клон объекта.
*
* @return клон события
*/
override public function clone():Event {
return new MouseEvent3D(type, view, object, surface, face, globalX, globalY, globalZ, localX, localY, localZ, u, v, altKey, ctrlKey, shiftKey, delta);
}
}
}

View File

@@ -0,0 +1,269 @@
package alternativa.engine3d.loaders {
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.loaders.events.LoaderEvent;
import alternativa.engine3d.loaders.events.LoaderProgressEvent;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.system.LoaderContext;
import flash.utils.ByteArray;
/**
* Рассылается в начале очередного этапа загрузки сцены.
*
* @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_START
*/
[Event (name="loadingStart", type="alternativa.engine3d.loaders.events.LoaderEvent")]
/**
* Рассылается в процессе получения данных во время загрузки.
*
* @eventType alternativa.engine3d.loaders.events.LoaderProgressEvent.LOADING_PROGRESS
*/
[Event (name="loadingProgress", type="alternativa.engine3d.loaders.events.LoaderProgressEvent")]
/**
* Рассылается после окончания очередного этапа загрузки сцены.
*
* @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_COMPLETE
*/
[Event (name="loadingComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")]
/**
* Рассылается после окончания загрузки сцены.
*
* @eventType flash.events.Event.COMPLETE
*/
[Event (name="complete", type="flash.events.Event")]
/**
* Рассылается, если в процессе загрузки возникает ошибка, которая прерывает загрузку.
*
* @eventType flash.events.IOErrorEvent.IO_ERROR
*/
[Event (name="ioError", type="flash.events.IOErrorEvent")]
/**
* Рассылается, если вызов <code>Loader3D.load()</code> нарушает текущие правила безопасности.
*
* @eventType flash.events.SecurityErrorEvent.SECURITY_ERROR
*/
[Event (name="securityError", type="flash.events.SecurityErrorEvent")]
/**
* Базовый класс загрузчиков. Реализует загрузку основного файла сцены и включает ряд методов, расширяя которые,
* наследники могут реализовывать специфическую функциональность для разбора сцены и загрузки дополнительных данных.
*/
public class Loader3D extends EventDispatcher {
/**
* Текущее состояние загрузчика. Показывает наличие активных процессов загрузки. В качестве значений используются объявленные в классе константы.
*/
protected var loaderState:int = Loader3DState.IDLE;
/**
* Базовый URL основного файла сцены, не включающий имя файла. Если путь не пустой, то он заканчивается символом "/".
*/
protected var baseURL:String;
/**
* LoaderContext для загрузки файлов текстур.
*/
protected var loaderContext:LoaderContext;
/**
* Контейнер, который должен содержать объекты, загруженные из файла сцены.
*/
protected var _content:Object3D;
/**
* Загрузчик основного файла сцены.
*/
private var mainLoader:URLLoader;
/**
* Создаёт новый экземпляр класса.
*/
public function Loader3D() {
super(this);
}
/**
* Контейнер, содержащий все загруженные из сцены объекты.
*/
public final function get content():Object3D {
return _content;
}
/**
* Загружает сцену из файла с заданным URL. Метод отменяет текущую загрузку.
* Если файл успешно загружен, вызывается метод <code>parse()</code>, который выполняет разбор полученных данных.
*
* @param url URL файла со сценой
* @param context LoaderContext для загрузки файлов текстур
*
* @see #parse()
*/
public final function load(url:String, loaderContext:LoaderContext = null):void {
this.baseURL = url.substring(0, url.lastIndexOf("/") + 1);
this.loaderContext = loaderContext;
if (mainLoader == null) {
// Первоначальное создание загрузчика файла сцены
mainLoader = new URLLoader();
mainLoader.dataFormat = URLLoaderDataFormat.BINARY;
mainLoader.addEventListener(Event.COMPLETE, onMainLoadingComplete);
mainLoader.addEventListener(ProgressEvent.PROGRESS, onMainLoadingProgress);
mainLoader.addEventListener(IOErrorEvent.IO_ERROR, onMainLoadingError);
mainLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onMainLoadingError);
} else {
// Прекращение активной загрузки
close();
}
_content = null;
// Загрузка основного файла
setState(Loader3DState.LOADING_MAIN);
mainLoader.load(new URLRequest(url));
if (hasEventListener(LoaderEvent.LOADING_START)) {
dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_START, LoadingStage.MAIN_FILE));
}
}
/**
* Загружает сцену из массива бинарных данных.
* Для чтения и разбора данных из массива вызывается метод <code>parse()</code>.
*
* @param data данные сцены
* @param baseUrl базовый URL для файлов текстур
* @param loaderContext LoaderContext для загрузки файлов текстур
*
* @see #parse()
*/
public final function loadBytes(data:ByteArray, baseUrl:String = null, loaderContext:LoaderContext = null):void {
if (baseUrl == null) {
baseUrl = "";
} else if (baseUrl.length > 0 && baseUrl.charAt(baseUrl.length - 1) != "/") {
baseUrl += "/";
}
this.baseURL = baseUrl;
this.loaderContext = loaderContext;
close();
_content = null;
parse(data);
}
/**
* Прекращает текущую загрузку. Базовая реализация останавливает процесс загрузки основного файла сцены. Наследники должны расширять метод <code>closeInternal</code> для
* прекращения специфических процессов загрузки.
*
* @see #closeInternal()
*/
public final function close():void {
if (loaderState == Loader3DState.LOADING_MAIN) {
mainLoader.close();
}
closeInternal();
clean();
setState(Loader3DState.IDLE);
}
/**
* Очищает внутренние ссылки на загруженные данные, чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает
* во время процесса загрузки данных. Реализация метода удаляет сылку на контейнер с загруженными объектами и вызывает метод <code>unloadInternal()</code>.
*
* @see #unloadInternal()
*/
public final function unload():void {
if (loaderState != Loader3DState.IDLE) {
return;
}
_content = null;
unloadInternal();
}
/**
* Метод вызывается из <code>close()</code> и должен прекращать процессы загрузки, инициированные наследниками. Базовая реализация не делает ничего.
*
* @see #close()
*/
protected function closeInternal():void {
}
/**
* Вызывается из метода <code>unload()</code>. Наследники должны расширять метод для удаления внутренних ссылок на объекты. Базовая реализация не делает ничего.
*
* @see #unload()
*/
protected function unloadInternal():void {
}
/**
* Устанавливает внутреннее состояние загрузчика.
*
* @param state новое состояние
*/
protected function setState(state:int):void {
loaderState = state;
}
/**
* Наследники должны расширять метод, реализуя функционал по разбору данных. Базовая реализация не делает ничего.
*
* @param data данные трёхмерной сцены
*/
protected function parse(data:ByteArray):void {
}
/**
* Обрабатывает ошибку, возникшую при загрузке основного файла. Базовая реализация устанавливает состояние загрузчика в <code>STATE_IDLE</code> и рассылает
* полученное событие.
*
* @param e событие, описывающего ошибку
*/
protected function onMainLoadingError(e:ErrorEvent):void {
setState(Loader3DState.IDLE);
dispatchEvent(e);
}
/**
* Метод должен вызываться после того как все данные сцены успешно загружены. Метод переводит загрузчик в состояние ожидания, вызывает метод очистки
* <code>clean()</code> и рассылает событие <code>Event.COMPLETE</code>.
*
* @see #clean()
*/
protected final function complete():void {
setState(Loader3DState.IDLE);
clean();
if (hasEventListener(Event.COMPLETE)) {
dispatchEvent(new Event(Event.COMPLETE));
}
}
/**
* Метод должен очищать внутренние вспомогательные переменные. Вызывается из метода <code>complete</code>. Базовая реализация не делает ничего, наследники должны расширять
* метод при необходимости.
*
* @see #complete()
*/
protected function clean():void {
}
/**
* Обработчик успешной загрузки основного файла. Запускает функцию parse() для разбора данных.
*/
private function onMainLoadingComplete(e:Event):void {
setState(Loader3DState.IDLE);
if (hasEventListener(LoaderEvent.LOADING_COMPLETE)) {
dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_COMPLETE, LoadingStage.MAIN_FILE));
}
parse(mainLoader.data);
}
/**
* Рассылает событие прогресса загрузки основного файла.
*/
private function onMainLoadingProgress(e:ProgressEvent):void {
if (hasEventListener(LoaderProgressEvent.LOADING_PROGRESS)) {
dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADING_PROGRESS, LoadingStage.MAIN_FILE, 1, 0, e.bytesLoaded, e.bytesTotal));
}
}
}
}

View File

@@ -0,0 +1,262 @@
package alternativa.engine3d.loaders {
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Surface;
import alternativa.engine3d.loaders.events.LoaderEvent;
import alternativa.engine3d.loaders.events.LoaderProgressEvent;
import alternativa.engine3d.materials.TextureMaterial;
import alternativa.types.Texture;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.utils.ByteArray;
use namespace alternativa3d;
/**
* Загрузчик сцен в формате 3DS.
* <p>
* Класс предоставляет возможность загрузить 3DS-данные из сети или из бинарного массива. Полученные данные разбираются с помощью класса
* <code>Parser3DS</code>, после чего автоматически загружаются текстуры, используемые в сцене, которые затем назначаются текстурным материалам.
* Если при загрузке текстуры происходит ошибка (например, файл отсутствует), то текстура заменяется текстурой-заглушкой и рассылается
* сообщение <code>IOErrorEvent</code>.
* </p>
* <p>
* Перед загрузкой данных можно установить ряд свойств, влияющих на создаваемые текстурные материалы.
* </p>
*
* @see alternativa.engine3d.loaders.Parser3DS
*/
public class Loader3DS extends Loader3D {
/**
* Если указано значение <code>false</code>, то материалы загружаться не будут.
*
* @default true
*/
public var loadMaterials:Boolean = true;
private var bitmapLoader:TextureMapsBatchLoader;
private var parser:Parser3DS;
/**
* Создаёт новый экземпляр класса.
*/
public function Loader3DS() {
super();
parser = new Parser3DS();
}
/**
* Значение свойства <code>repeat</code> для текстурных материалов.
*
* @default true
* @see alternativa.engine3d.materials.TextureMaterial
*/
public function get repeat():Boolean {
return parser.repeat;
}
/**
* @private
*/
public function set repeat(value:Boolean):void {
parser.repeat = value;
}
/**
* Значение свойства <code>smooth</code> для текстурных материалов.
*
* @default false
* @see alternativa.engine3d.materials.TextureMaterial#smooth
*/
public function get smooth():Boolean {
return parser.smooth;
}
/**
* @private
*/
public function set smooth(value:Boolean):void {
parser.smooth = value;
}
/**
* Значение свойства <code>blendMode</code> для текстурных материалов.
*
* @default BlendMode.NORMAL
* @see alternativa.engine3d.materials.Material#blendMode
*/
public function get blendMode():String {
return parser.blendMode;
}
/**
* @private
*/
public function set blendMode(value:String):void {
parser.blendMode = value;
}
/**
* Значение свойства <code>precision</code> для текстурных материалов.
*
* @default TextureMaterialPrecision.MEDIUM
* @see alternativa.engine3d.materials.TextureMaterial#precision
* @see alternativa.engine3d.materials.TextureMaterialPrecision
*/
public function get precision():Number {
return parser.precision;
}
/**
* @private
*/
public function set precision(value:Number):void {
parser.precision = value;
}
/**
* Коэффициент пересчёта единиц измерения сцены. Размеры создаваемых объектов будут умножены на заданное значение.
* @default 1
*/
public function get scale():Number {
return parser.scale;
}
/**
* @private
*/
public function set scale(value:Number):void {
parser.scale = value;
}
/**
* Уровень мобильности для загруженных объектов.
* @default 0
* @see alternativa.engine3d.core.Object3D#mobility
*/
public function get mobility():int {
return parser.mobility;
}
/**
* @private
*/
public function set mobility(value:int):void {
parser.mobility = value;
}
/**
* Выполняет действия для прекращения текущей загрузки.
*/
override protected function closeInternal():void {
super.closeInternal();
if (loaderState == Loader3DState.LOADING_TEXTURE) {
bitmapLoader.close();
}
}
/**
* @inheritDoc
*/
override protected function unloadInternal():void {
if (bitmapLoader != null) {
bitmapLoader.unload();
}
}
/**
* Разбирает загруженные данные.
*
* @param data данные в формате 3DS
*/
override protected function parse(data:ByteArray):void {
parser.parse(data);
_content = parser.content;
if (loadMaterials && parser.textureMaterials != null) {
loadTextures();
} else {
complete();
}
}
/**
* Запускает процесс загрузки текстур.
*/
private function loadTextures():void {
if (bitmapLoader == null) {
bitmapLoader = new TextureMapsBatchLoader();
bitmapLoader.addEventListener(LoaderEvent.LOADING_START, onTextureLoadingStart);
bitmapLoader.addEventListener(LoaderEvent.LOADING_COMPLETE, onTextureLoadingComplete);
bitmapLoader.addEventListener(LoaderProgressEvent.LOADING_PROGRESS, onTextureLoadingProgress);
bitmapLoader.addEventListener(Event.COMPLETE, onTextureMaterialsLoadingComplete);
bitmapLoader.addEventListener(IOErrorEvent.IO_ERROR, onTextureLoadingError);
}
setState(Loader3DState.LOADING_TEXTURE);
bitmapLoader.load(baseURL, parser.textureMaterials, loaderContext);
}
/**
* Обрабатывает сообщение об ошибке загрузки текстуры.
*/
private function onTextureLoadingError(e:IOErrorEvent):void {
dispatchEvent(e);
}
/**
* Обрабатывает сообщение о начале загрузки очередной текстуры.
*/
private function onTextureLoadingStart(e:LoaderEvent):void {
if (hasEventListener(e.type)) {
dispatchEvent(e);
}
}
/**
* Обрабатывает сообщение об окончании загрузки очередного файла текстуры.
*/
private function onTextureLoadingComplete(e:LoaderEvent):void {
if (hasEventListener(e.type)) {
dispatchEvent(e);
}
}
/**
* Рассылает событие прогресса загрузки очередного файла текстуры.
*/
private function onTextureLoadingProgress(e:LoaderProgressEvent):void {
if (hasEventListener(e.type)) {
dispatchEvent(e);
}
}
/**
* Устанавливает текстуры для материалов после завершения загрузки всех текстур.
*/
private function onTextureMaterialsLoadingComplete(e:Event):void {
parser.content.forEach(setTextures);
complete();
}
/**
* Устанавливает текстуры для текстурных материалов объекта.
*/
private function setTextures(object:Object3D):void {
var mesh:Mesh = object as Mesh;
if (mesh != null) {
for (var key:String in mesh._surfaces) {
var textureMapsInfo:TextureMapsInfo = parser.textureMaterials[key];
if (textureMapsInfo != null) {
var texture:Texture = new Texture(bitmapLoader.textures[key], textureMapsInfo.diffuseMapFileName);
var s:Surface = mesh._surfaces[key];
TextureMaterial(s.material).texture = texture;
}
}
}
}
}
}

View File

@@ -0,0 +1,15 @@
package alternativa.engine3d.loaders {
/**
* @private
* Возможные состояния загрузчиков.
*/
public final class Loader3DState {
public static const IDLE:int = 0;
public static const LOADING_MAIN:int = 1;
public static const LOADING_TEXTURE:int = 2;
public static const LOADING_LIBRARY:int = 3;
}
}

View File

@@ -0,0 +1,257 @@
package alternativa.engine3d.loaders {
import alternativa.engine3d.*;
import alternativa.types.Map;
import alternativa.utils.ColorUtils;
import flash.display.BitmapData;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.geom.Point;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.system.LoaderContext;
use namespace alternativa3d;
/**
* Событие рассылается после начала загрузки.
*
* @eventType flash.events.Event.OPEN
*/
[Event (name="open", type="flash.events.Event")]
/**
* Событие рассылается в процессе получения данных во время загрузки.
*
* @eventType flash.events.ProgressEvent.PROGRESS
*/
[Event (name="progress", type="flash.events.ProgressEvent")]
/**
* Событие рассылается после завершении загрузки.
*
* @eventType flash.events.Event.COMPLETE
*/
[Event (name="complete", type="flash.events.Event")]
/**
* Событие рассылается при ошибке загрузки.
*
* @eventType flash.events.IOErrorEvent.IO_ERROR
*/
[Event (name="ioError", type="flash.events.IOErrorEvent")]
/**
* Событие рассылается при нарушении безопасности.
*
* @eventType flash.events.SecurityErrorEvent.SECURITY_ERROR
*/
[Event (name="securityError", type="flash.events.SecurityErrorEvent")]
/**
* @private
* Загрузчик библиотеки материалов из файлов в формате MTL material format (Lightwave, OBJ).
* <p>
* На данный момент обеспечивается загрузка цвета, прозрачности и диффузной текстуры материала.
* </p>
*/
public 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 CMD_MAP_DISSOLVE:String = "map_d";
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 static const STATE_IDLE:int = 0;
private static const STATE_LOADING:int = 1;
// Загрузчик файла MTL
private var mtlFileLoader:URLLoader;
// Библиотека загруженных материалов
private var _library:Map;
// Имя текущего материала
private var materialName:String;
// параметры текущего материала
private var currentMaterialInfo:MTLMaterialInfo = new MTLMaterialInfo();
private var loaderState:int = STATE_IDLE;
/**
* Создаёт новый экземпляр класса.
*/
public function LoaderMTL() {
}
/**
* Прекращение текущей загрузки.
*/
public function close():void {
if (loaderState == STATE_LOADING) {
mtlFileLoader.close();
}
loaderState = STATE_IDLE;
}
/**
* Метод очищает внутренние ссылки на загруженные данные чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает
* во время загрузки.
*/
public function unload():void {
if (loaderState == STATE_IDLE) {
_library = null;
}
}
/**
* Библиотека материалов. Ключами являются наименования материалов, значениями -- объекты, наследники класса
* <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> и
* </p>
* <code>SecurityErrorEvent.SECURITY_ERROR</code> соответственно.
* <p>
* Если происходит ошибка при загрузке файла текстуры, то соответствующая текстура заменяется на текстуру-заглушку.
* </p>
* <p></p>
* @param url URL MTL-файла
* @param loaderContext LoaderContext для загрузки файлов текстур
*
* @see #library
*/
public function load(url:String):void {
if (mtlFileLoader == null) {
mtlFileLoader = new URLLoader();
mtlFileLoader.addEventListener(Event.OPEN, retranslateEvent);
mtlFileLoader.addEventListener(ProgressEvent.PROGRESS, retranslateEvent);
mtlFileLoader.addEventListener(Event.COMPLETE, parseMTLFile);
mtlFileLoader.addEventListener(IOErrorEvent.IO_ERROR, onError);
mtlFileLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
} else {
close();
}
loaderState = STATE_LOADING;
mtlFileLoader.load(new URLRequest(url));
}
/**
*
*/
private function retranslateEvent(e:Event):void {
dispatchEvent(e);
}
/**
* Обработка ошибки загрузки MTL-файла.
*/
private function onError(e:Event):void {
loaderState = STATE_IDLE;
dispatchEvent(e);
}
/**
* Разбор содержимого загруженного файла материалов.
*/
private function parseMTLFile(e:Event = null):void {
loaderState = STATE_IDLE;
parse(mtlFileLoader.data);
complete();
}
/**
*
*/
public function parse(data:String):void {
_library = new Map();
data.split(REGEXP_SPLIT_FILE).forEach(parseLine);
}
/**
* Разбор строки файла.
*
* @param line строка файла
*/
private function parseLine(line:String, index:int, lines:Array):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:
currentMaterialInfo.diffuseMapInfo = MTLTextureMapInfo.parse(parts);
break;
case CMD_MAP_DISSOLVE:
currentMaterialInfo.dissolveMapInfo = MTLTextureMapInfo.parse(parts);
break;
}
}
/**
* Определение нового материала.
*/
private function defineMaterial(parts:Array = null):void {
materialName = parts[1];
currentMaterialInfo = new MTLMaterialInfo();
_library[materialName] = currentMaterialInfo;
}
/**
* Чтение коэффициентов диффузного отражения. Считываются только коэффициенты, заданные в формате 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 complete():void {
loaderState = STATE_IDLE;
dispatchEvent(new Event(Event.COMPLETE));
}
}
}

View File

@@ -0,0 +1,423 @@
package alternativa.engine3d.loaders {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.Surface;
import alternativa.engine3d.loaders.events.LoaderEvent;
import alternativa.engine3d.loaders.events.LoaderProgressEvent;
import alternativa.engine3d.materials.FillMaterial;
import alternativa.engine3d.materials.TextureMaterial;
import alternativa.engine3d.materials.TextureMaterialPrecision;
import alternativa.types.Map;
import alternativa.types.Texture;
import flash.display.BlendMode;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.geom.Point;
import flash.utils.ByteArray;
use namespace alternativa3d;
/**
* Загрузчик моделей из файла в формате OBJ. Загрузчик использует класс <code>ParserOBJ</code> для создания объектов из OBJ-файла, после чего выполняет загрузку
* библиотек материалов, создаёт материалы и назначает их объектам.
* <p>
* При загрузке текстурных материалов учитывается наличие карт прозрачности.
* </p>
*
* @see alternativa.engine3d.loaders.ParserOBJ
*/
public class LoaderOBJ extends Loader3D {
/**
* Если указано значение <code>false</code>, то материалы загружаться не будут.
*
* @default true
*/
public var loadMaterials:Boolean = true;
/**
* Значение свойства <code>smooth</code> для текстурных материалов.
*
* @default false
* @see alternativa.engine3d.materials.TextureMaterial#smooth
*/
public var smooth:Boolean = false;
/**
* Значение свойства <code>blendMode</code> для текстурных материалов.
*
* @default BlendMode.NORMAL
* @see alternativa.engine3d.materials.Material#blendMode
*/
public var blendMode:String = BlendMode.NORMAL;
/**
* Значение свойства <code>precision</code> для текстурных материалов.
*
* @default TextureMaterialPrecision.MEDIUM
* @see alternativa.engine3d.materials.TextureMaterial#precision
* @see alternativa.engine3d.materials.TextureMaterialPrecision
*/
public var precision:Number = TextureMaterialPrecision.MEDIUM;
private static const STATE_LOADING_LIBRARY:int = 3;
private var mtlLoader:LoaderMTL;
private var materialsLibrary:Map;
private var uv:Point = new Point();
private var parser:ParserOBJ;
private var currentMaterialFileIndex:int;
private var numMaterialFiles:int;
private var textureMaterials:Map;
private var bitmapsLoader:TextureMapsBatchLoader;
/**
* Создаёт новый экземпляр класса.
*/
public function LoaderOBJ() {
super();
parser = new ParserOBJ();
}
/**
* Коэффициент пересчёта единиц измерения сцены. Размеры создаваемых объектов будут умножены на заданное значение.
* @default 1
*/
public function get scale():Number {
return parser.scale;
}
/**
* @private
*/
public function set scale(value:Number):void {
parser.scale = value;
}
/**
* При установленном значении <code>true</code> объекты будут определяться не ключевым словом "o", а словом "g". Это может быть полезно, т.к. некоторые OBJ-експортёры могут
* использовать слово "g" для определения объектов в OBJ-файле.
*
* @default false
*/
public function get objectsAsGroups():Boolean {
return parser.objectsAsGroups;
}
/**
* @private
*/
public function set objectsAsGroups(value:Boolean):void {
parser.objectsAsGroups = value;
}
/**
* При установленном значении <code>true</code> выполняется преобразование координат геометрических вершин посредством
* поворота на 90 градусов относительно оси X. Смысл флага в преобразовании системы координат, в которой направление вверх определяется осью <code>Y</code>,
* в систему координат, использующуюся в Alternativa3D (вверх направлена ось <code>Z</code>).
*
* @default false
*/
public function get rotateModel():Boolean {
return parser.rotateModel;
}
/**
* @private
*/
public function set rotateModel(value:Boolean):void {
parser.rotateModel = value;
}
/**
* Уровень мобильности для загруженных объектов.
* @default 0
* @see alternativa.engine3d.core.Object3D#mobility
*/
public function get mobility():int {
return parser.mobility;
}
/**
* @private
*/
public function set mobility(value:int):void {
parser.mobility = value;
}
/**
* @inheritDoc
*/
override protected function closeInternal():void {
super.closeInternal();
if (loaderState == Loader3DState.LOADING_LIBRARY) {
mtlLoader.close();
}
}
/**
* Метод очищает внутренние ссылки на загруженные данные, чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает
* во время загрузки.
*/
override protected function unloadInternal():void {
if (mtlLoader != null) {
mtlLoader.unload();
}
if (bitmapsLoader != null) {
bitmapsLoader.unload();
}
}
/**
* @inheritDoc
*/
override protected function clean():void {
super.clean();
loaderContext = null;
textureMaterials = null;
}
/**
* Метод выполняет разбор данных, полученных из OBJ-файла.
*
* @param s содержимое OBJ-файла
* @param materialLibrary библиотека материалов
* @return объект, содержащий все трёхмерные объекты, определённые в OBJ-файле
*/
override protected function parse(data:ByteArray):void {
parser.parse(data.toString());
// После разбора файла имеем дерево 3д-объектов и массив с именами файлов библиотек материалов
_content = parser.content;
// Загружаем библиотеки материалов или заканчиваем работу, если материалов нет
numMaterialFiles = parser.materialFileNames.length;
if (loadMaterials && numMaterialFiles > 0) {
loadMaterialsLibrary();
} else {
complete();
}
}
/**
* Загружает библиотеки материалов.
*
* @param materialFileNames массив с именами файлов библиотек материалов
*/
private function loadMaterialsLibrary():void {
loaderState = Loader3DState.LOADING_LIBRARY;
if (mtlLoader == null) {
mtlLoader = new LoaderMTL();
mtlLoader.addEventListener(Event.OPEN, onMaterialLibLoadingStart);
mtlLoader.addEventListener(ProgressEvent.PROGRESS, onMaterialLibLoadingProgress);
mtlLoader.addEventListener(Event.COMPLETE, onMaterialLibLoadingComplete);
mtlLoader.addEventListener(IOErrorEvent.IO_ERROR, onMaterialLibLoadingError);
mtlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onMaterialLibLoadingError);
}
materialsLibrary = new Map();
currentMaterialFileIndex = -1;
loadNextMaterialFile();
}
/**
* Обрабатывает ошибки при загрузке библиотеки материалов.
*/
private function onMaterialLibLoadingError(e:ErrorEvent):void {
close();
dispatchEvent(e);
}
/**
*
*/
private function onMaterialLibLoadingStart(e:Event):void {
dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_START, LoadingStage.MATERIAL_LIBRARY));
}
/**
*
*/
private function onMaterialLibLoadingProgress(e:ProgressEvent):void {
dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADING_PROGRESS, LoadingStage.MATERIAL_LIBRARY, numMaterialFiles, currentMaterialFileIndex, e.bytesLoaded, e.bytesTotal));
}
/**
* Обработка успешной загрузки библиотеки материалов.
*/
private function onMaterialLibLoadingComplete(e:Event):void {
dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_COMPLETE, LoadingStage.MATERIAL_LIBRARY));
// Слияние загруженной библиотеки с уже имеющейся
materialsLibrary.concat(mtlLoader.library);
// Загрузка следующего файла материалов
loadNextMaterialFile();
}
/**
* Загрузка и разбор очередного файла материалов.
*/
private function loadNextMaterialFile():void {
currentMaterialFileIndex++;
if (currentMaterialFileIndex == numMaterialFiles) {
// Все материалы загружены, проверяется наличие текстурных материалов и выполняется их загрузка
checkMaterials();
} else {
mtlLoader.load(baseURL + parser.materialFileNames[currentMaterialFileIndex]);
}
}
/**
*
*/
private function checkMaterials():void {
collectTextureMaterialNames();
if (textureMaterials != null) {
loadTextures();
} else {
setMaterials();
complete();
}
}
/**
* Запускает процесс загрузки текстур.
*/
private function loadTextures():void {
if (bitmapsLoader == null) {
bitmapsLoader = new TextureMapsBatchLoader();
bitmapsLoader.addEventListener(LoaderEvent.LOADING_START, onTextureLoadingStart);
bitmapsLoader.addEventListener(LoaderEvent.LOADING_COMPLETE, onTextureLoadingComplete);
bitmapsLoader.addEventListener(LoaderProgressEvent.LOADING_PROGRESS, onTextureLoadingProgress);
bitmapsLoader.addEventListener(Event.COMPLETE, onTextureMaterialsLoadingComplete);
bitmapsLoader.addEventListener(IOErrorEvent.IO_ERROR, onTextureLoadingError);
}
setState(Loader3DState.LOADING_TEXTURE);
bitmapsLoader.load(baseURL, textureMaterials, loaderContext);
}
/**
* Обрабатывает неудачную загрузку текстуры.
*/
private function onTextureLoadingError(e:IOErrorEvent):void {
dispatchEvent(e);
}
/**
* Обрабатывает начало загрузки очередного файла текстуры.
*/
private function onTextureLoadingStart(e:Event):void {
dispatchEvent(e);
}
/**
* Обрабатывает окончание загрузки очередного файла текстуры.
*/
private function onTextureLoadingComplete(e:Event):void {
dispatchEvent(e);
}
/**
* Рассылает событие прогресса загрузки файла текстуры.
*/
private function onTextureLoadingProgress(e:LoaderProgressEvent):void {
dispatchEvent(e);
}
/**
* Обрабатывает завершение загрузки текстур материала.
*/
private function onTextureMaterialsLoadingComplete(e:Event):void {
setMaterials();
complete();
}
/**
*
*/
private function collectTextureMaterialNames():void {
for (var materialName:String in materialsLibrary) {
var info:MTLMaterialInfo = materialsLibrary[materialName];
if (info.diffuseMapInfo != null) {
if (textureMaterials == null) {
textureMaterials = new Map();
}
var textureMapsInfo:TextureMapsInfo = new TextureMapsInfo();
textureMapsInfo.diffuseMapFileName = info.diffuseMapInfo.fileName;
if (info.dissolveMapInfo != null) {
textureMapsInfo.opacityMapFileName = info.dissolveMapInfo.fileName;
}
textureMaterials.add(materialName, textureMapsInfo);
}
}
}
/**
* Устанавливает материалы.
*/
private function setMaterials():void {
if (materialsLibrary != 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:MTLMaterialInfo = materialsLibrary[surfaceKey];
if (materialInfo != null) {
if (materialInfo.diffuseMapInfo == null) {
surface.material = new FillMaterial(materialInfo.color, materialInfo.alpha, blendMode);
} else {
var texture:Texture = new Texture(bitmapsLoader.textures[surfaceKey], materialInfo.diffuseMapInfo.fileName);
surface.material = new TextureMaterial(texture, materialInfo.alpha, materialInfo.diffuseMapInfo.repeat, smooth, blendMode, -1, 0, precision);
transformUVs(surface, new Point(materialInfo.diffuseMapInfo.offsetU, materialInfo.diffuseMapInfo.offsetV), new Point(materialInfo.diffuseMapInfo.sizeU, materialInfo.diffuseMapInfo.sizeV));
}
}
}
}
}
}
/**
* Метод выполняет преобразование UV-координат текстурированных граней. В связи с тем, что в формате MTL предусмотрено
* масштабирование и смещение текстурной карты в 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;
if (face._aUV) {
uv.x = face._aUV.x;
uv.y = face._aUV.y;
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.x = face._bUV.x;
uv.y = face._bUV.y;
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.x = face._cUV.x;
uv.y = face._cUV.y;
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,20 @@
package alternativa.engine3d.loaders {
/**
* Набор констант, которые описывают этапы загрузки.
*/
public final class LoadingStage {
/**
* Обозначает загрузку основного файла.
*/
public static const MAIN_FILE:int = 0;
/**
* Обозначает загрузку текстур.
*/
public static const TEXTURE:int = 1;
/**
* Обозначает загрузку библиотек материалов.
*/
public static const MATERIAL_LIBRARY:int = 2;
}
}

View File

@@ -0,0 +1,16 @@
package alternativa.engine3d.loaders {
import flash.display.BitmapData;
import flash.geom.Point;
/**
* @private
* Класс содержит обобщённую информацию о материале.
*/
public class MTLMaterialInfo {
public var color:uint;
public var alpha:Number;
public var diffuseMapInfo:MTLTextureMapInfo;
public var dissolveMapInfo:MTLTextureMapInfo;
}
}

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/
*/
public 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;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,374 @@
package alternativa.engine3d.loaders {
import alternativa.engine3d.alternativa3d;
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.types.Point3D;
import flash.geom.Point;
use namespace alternativa3d;
/**
* Класс позволяет разобрать данные в формате OBJ. После обработки данных контейнер с трёхмерными объектами становится доступным через свойство <code>content</code>, а свойство
* <code>materialFileNames</code> содержит список имён файлов библиотек материалов. Каждый созданный объект содержит поверхности, идентификаторы которых совпадают с именами
* назначенных материалов, но сами материалы поверхностей не создаются, т.к. они будут известны только после загрузки библиотек материалов.
* <p>
* При разборе данных распознаются следующие ключевые слова формата OBJ:
* </p>
* <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>
*/
public class ParserOBJ {
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 objectKey:String = CMD_OBJECT_NAME;
// Контейнер, содержащий все определённые в 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 _mobility:int;
// Коэффициент пересчёта единиц измерения сцены. Размеры создаваемых объектов будут умножены на заданное значение.
private var _scale:Number = 1;
// Флаг поворота объектов на 90 градусов по оси X
private var _rotateModel:Boolean;
/**
* Создаёт новый экземпляр класса.
*/
public function ParserOBJ() {
}
/**
* Коэффициент пересчёта единиц измерения сцены. Размеры создаваемых объектов будут умножены на заданное значение.
* @default 1
*/
public function get scale():Number {
return _scale;
}
/**
* @private
*/
public function set scale(value:Number):void {
_scale = value;
}
/**
* При установленном значении <code>true</code> объекты будут определяться не ключевым словом "o", а словом "g". Это может быть полезно, т.к. некоторые OBJ-експортёры могут
* использовать слово "g" для определения объектов в OBJ-файле.
*
* @default false
*/
public function get objectsAsGroups():Boolean {
return objectKey == CMD_GROUP_NAME;
}
/**
* @private
*/
public function set objectsAsGroups(value:Boolean):void {
objectKey = value ? CMD_GROUP_NAME : CMD_OBJECT_NAME;
}
/**
* Уровень мобильности для загруженных объектов.
* @default 0
* @see alternativa.engine3d.core.Object3D#mobility
*/
public function get mobility():int {
return _mobility;
}
/**
* @private
*/
public function set mobility(value:int):void {
_mobility = value;
}
/**
* При установленном значении <code>true</code> выполняется преобразование координат геометрических вершин посредством
* поворота на 90 градусов относительно оси X. Смысл флага в преобразовании системы координат, в которой направление вверх определяется осью <code>Y</code>,
* в систему координат, использующуюся в Alternativa3D (вверх направлена ось <code>Z</code>).
*
* @default false
*/
public function get rotateModel():Boolean {
return _rotateModel;
}
/**
* @private
*/
public function set rotateModel(value:Boolean):void {
_rotateModel = value;
}
/**
* Контейнер, содержащий все определённые в OBJ-файле объекты.
*/
public function get content():Object3D {
return _content;
}
/**
* Массив имён файлов, содержащих библиотеки материалов.
*/
public function get materialFileNames():Array {
return _materialFileNames;
}
/**
* Разбирает содержимое OBJ-файла и формирует объекты.
*
* @param data данные OBJ-файла
*/
public function parse(data:String):void {
globalVertices = new Array();
globalTextureVertices = new Array();
_materialFileNames = new Array();
_content = new Object3D();
createNewObject("");
var lines:Array = data.split(REGEXP_SPLIT_FILE);
lines.forEach(parseLine);
moveFacesToSurface();
}
/**
*
*/
private function createNewObject(objectName:String):void {
currentObject = new Mesh(objectName);
currentObject.mobility = _mobility;
_content.addChild(currentObject);
}
/**
* Разбирает строку входного файла.
*/
private function parseLine(line:String, index:int, lines:Array):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 objectKey:
defineObject(parts[1]);
break;
// Объявление вершины
case CMD_VERTEX:
globalVertices.push(new Point3D(Number(parts[1])*_scale, Number(parts[2])*_scale, Number(parts[3])*_scale));
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();
createNewObject(objectName);
}
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);
}
}
if (materialFaces == null) {
materialFaces = new Array();
} else {
materialFaces.length = 0;
}
}
}
}

View File

@@ -0,0 +1,218 @@
package alternativa.engine3d.loaders {
import alternativa.engine3d.loaders.events.LoaderEvent;
import alternativa.engine3d.loaders.events.LoaderProgressEvent;
import alternativa.types.Map;
import flash.display.BitmapData;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.system.LoaderContext;
/**
* Событие рассылается в начале очередного этапа загрузки сцены.
*
* @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_START
*/
[Event (name="loadingStart", type="alternativa.engine3d.loaders.events.LoaderEvent")]
/**
* Событие рассылается в процессе получения данных во время загрузки.
*
* @eventType alternativa.engine3d.loaders.events.LoaderProgressEvent.LOADING_PROGRESS
*/
[Event (name="loadingProgress", type="alternativa.engine3d.loaders.events.LoaderProgressEvent")]
/**
* Событие рассылается после окончания очередного этапа загрузки сцены.
*
* @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_COMPLETE
*/
[Event (name="loadingComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")]
/**
* Событие рассылается после окончания загрузки сцены.
*
* @eventType flash.events.Event.COMPLETE
*/
[Event (name="complete", type="flash.events.Event")]
/**
* Тип события, рассылаемого при возникновении ошибки загрузки текстуры.
*
* @eventType flash.events.IOErrorEvent.IO_ERROR
*/
[Event (name="ioError", type="flash.events.IOErrorEvent")]
/**
* @private
* Пакетный загрузчик текстур. Используется загрузчиками внешних сцен для получения битмапов текстур материалов.
*/
public class TextureMapsBatchLoader extends EventDispatcher {
/**
* Текстура-заглушка для замены незагруженных текстур.
*/
public static var stubBitmapData:BitmapData;
// Загрузчик файлов текстур.
private var loader:TextureMapsLoader;
// Контекст безопасности загрузчика.
private var loaderContext:LoaderContext;
// Базовый URL файлов текстур.
private var baseUrl:String;
// Пакет с описанием текстур материалов.
private var batch:Map;
// Список имён материалов.
private var materialNames:Array;
// Общее количество файлов текстур.
private var totalFiles:int;
// Номер текущего
private var currFileIndex:int;
// Индекс текущего материала.
private var materialIndex:int;
// Результирующий список битмапов для каждого материала.
private var _textures:Map;
/**
* Создаёт новый экземпляр загрузчика.
*/
public function TextureMapsBatchLoader() {
}
/**
* Результирующий список битмапов для каждого материала. Ключами являются имена материалов, значениями -- объекты класса BitmapData.
*/
public function get textures():Map {
return _textures;
}
/**
* Метод для получения текстуры-заглушки.
*
* @return текстура-заглушка для замещения незагруженных текстур
*/
private function getStubBitmapData():BitmapData {
if (stubBitmapData == null) {
var size:uint = 20;
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);
}
}
}
return stubBitmapData;
}
/**
* Прекращает текущую загрузку.
*/
public function close():void {
if (loader != null) {
loader.close();
}
}
/**
* Очищает внутренние ссылки на объекты.
*/
private function clean():void {
loaderContext = null;
batch = null;
materialNames = null;
}
/**
* Очищает ссылку на загруженный список текстур материалов.
*/
public function unload():void {
_textures = null;
}
/**
* Загружает текстуры для материалов.
*
* @param baseURL базовый URL файлов текстур
* @param batch массив соответствий имён текстурных материалов и их текстур, описываемых объектами класса TextureMapsInfo
* @param loaderContext LoaderContext для загрузки файлов текстур
*/
public function load(baseURL:String, batch:Map, loaderContext:LoaderContext):void {
this.baseUrl = baseURL;
this.batch = batch;
this.loaderContext = loaderContext;
if (loader == null) {
loader = new TextureMapsLoader();
loader.addEventListener(LoaderEvent.LOADING_START, onTextureLoadingStart);
loader.addEventListener(LoaderEvent.LOADING_COMPLETE, onTextureLoadingComplete);
loader.addEventListener(ProgressEvent.PROGRESS, onProgress);
loader.addEventListener(Event.COMPLETE, onMaterialTexturesLoadingComplete);
loader.addEventListener(IOErrorEvent.IO_ERROR, onMaterialTexturesLoadingComplete);
} else {
close();
}
// Получение массива имён материалов и подсчёт количества файлов текстур
totalFiles = 0;
materialNames = new Array();
for (var materialName:String in batch) {
materialNames.push(materialName);
var info:TextureMapsInfo = batch[materialName];
totalFiles += info.opacityMapFileName == null ? 1 : 2;
}
// Старт загрузки
currFileIndex = 0;
materialIndex = 0;
_textures = new Map();
loadNextTextureFile();
}
/**
* Загружает очередной файл с текстурой.
*/
private function loadNextTextureFile():void {
var info:TextureMapsInfo = batch[materialNames[materialIndex]];
loader.load(baseUrl + info.diffuseMapFileName, info.opacityMapFileName == null ? null : baseUrl + info.opacityMapFileName, loaderContext);
}
/**
* Ретранслирует событие начала загрузки текстуры.
*/
private function onTextureLoadingStart(e:Event):void {
dispatchEvent(e);
}
/**
* Ретранслирует событие окончания загрузки текстуры.
*/
private function onTextureLoadingComplete(e:Event):void {
dispatchEvent(e);
currFileIndex++;
}
/**
* Рассылает событие прогресса загрузки файлов.
*/
private function onProgress(e:ProgressEvent):void {
dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADING_PROGRESS, LoadingStage.TEXTURE, totalFiles, currFileIndex, e.bytesLoaded, e.bytesTotal));
}
/**
* Обрабатывает завершение загрузки текстуры материала.
*/
private function onMaterialTexturesLoadingComplete(e:Event):void {
// В зависимости от полученного события устанавливается загруженное изображение или битмап-заглушка
if (e is IOErrorEvent) {
_textures.add(materialNames[materialIndex], getStubBitmapData());
dispatchEvent(e);
} else {
_textures.add(materialNames[materialIndex], loader.bitmapData);
}
if ((++materialIndex) == materialNames.length) {
// Загружены текстуры для всех материалов, отправляется сообщение о завершении
clean();
dispatchEvent(new Event(Event.COMPLETE));
} else {
loadNextTextureFile();
}
}
}
}

View File

@@ -0,0 +1,21 @@
package alternativa.engine3d.loaders {
/**
* Структура для хранения имён файла диффузной текстуры и файла карты прозрачности.
*/
public class TextureMapsInfo {
/**
* Имя файла диффузной текстуры.
*/
public var diffuseMapFileName:String;
/**
* Имя файла карты прозрачности.
*/
public var opacityMapFileName:String;
public function TextureMapsInfo(diffuseMapFileName:String = null, opacityMapFileName:String = null) {
this.diffuseMapFileName = diffuseMapFileName;
this.opacityMapFileName = opacityMapFileName;
}
}
}

View File

@@ -0,0 +1,215 @@
package alternativa.engine3d.loaders {
import alternativa.engine3d.loaders.events.LoaderEvent;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BitmapDataChannel;
import flash.display.BlendMode;
import flash.display.Loader;
import flash.errors.IOError;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.net.URLRequest;
import flash.system.LoaderContext;
/**
* Событие рассылается в начале загрузки каждой текстуры.
*
* @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_START
*/
[Event (name="loadingStart", type="alternativa.engine3d.loaders.events.LoaderEvent")]
/**
* Событие рассылается после окончания загрузки каждой текстуры.
*
* @eventType alternativa.engine3d.loaders.events.LoaderEvent.LOADING_COMPLETE
*/
[Event (name="loadingComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")]
/**
* Событие рассылается после завершения загрузки.
*
* @eventType flash.events.Event.COMPLETE
*/
[Event (name="complete", type="flash.events.Event")]
/**
* Событие рассылается при возникновении ошибки, приводящей к прерыванию загрузки.
*
* @eventType flash.events.IOErrorEvent.IO_ERROR
*/
[Event (name="ioError", type="flash.events.IOErrorEvent")]
/**
* Событие рассылается в процессе получения данных во время загрузки.
*
* @eventType flash.events.ProgressEvent.PROGRESS
*/
[Event (name="progress", type="flash.events.ProgressEvent")]
/**
* Загрузчик битмапов диффузной текстуры и карты прозрачности.
* @private
*/
public class TextureMapsLoader extends EventDispatcher {
private static const STATE_IDLE:int = 0;
private static const STATE_LOADING_DIFFUSE_MAP:int = 1;
private static const STATE_LOADING_ALPHA_MAP:int = 2;
private var _bitmapData:BitmapData;
private var bitmapLoader:Loader;
private var alphaTextureUrl:String;
private var loaderContext:LoaderContext;
private var loaderState:int = STATE_IDLE;
/**
* Создаёт новый экземпляр класса. Если параметр <code>diffuseTextureUrl</code> не равен <code>null</code>, конструктор запускает процесс
* загрузки.
*
* @param diffuseTextureUrl URL файла диффузной карты
* @param alphaTextureUrl URL файла карты прозрачности
* @param loaderContext LoaderContext, используемый для загрузки файлов
*/
public function TextureMapsLoader(diffuseTextureUrl:String = null, alphaTextureUrl:String = null, loaderContext:LoaderContext = null) {
if (diffuseTextureUrl != null) {
load(diffuseTextureUrl, alphaTextureUrl, loaderContext);
}
}
/**
* Загружает текстурные карты. Если помимо файла диффузной текстуры указан файл карты прозрачности, результирующая текстура будет получена из диффузной карты путём заполнения
* её альфа-канала на основе карты прозрачности. Карта прозрачности должна быть задана оттенками серого, при этом белый цвет должен задавать полностью непрозрачную область.
* <p>
* После завершения загрузки текстура становится доступной через свойство <code>bitmapData</code>.
* </p>
*
* @param diffuseTextureUrl URL файла диффузной карты
* @param alphaTextureUrl URL файла карты прозрачности
* @param loaderContext LoaderContext, используемый для загрузки файлов
*
* @see #bitmapData
*/
public function load(diffuseTextureUrl:String, alphaTextureUrl:String = null, loaderContext:LoaderContext = null):void {
this.alphaTextureUrl = alphaTextureUrl;
this.loaderContext = loaderContext;
if (bitmapLoader == null) {
bitmapLoader = new Loader();
bitmapLoader.contentLoaderInfo.addEventListener(Event.OPEN, onOpen);
bitmapLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
bitmapLoader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onProgress);
bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoadError);
} else {
close();
}
startLoading(STATE_LOADING_DIFFUSE_MAP, diffuseTextureUrl);
}
/**
*
*/
private function onOpen(e:Event):void {
dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_START, LoadingStage.TEXTURE));
}
/**
*
*/
private function onProgress(e:Event):void {
dispatchEvent(e);
}
/**
* Запускает загрузку файла текстуры.
*
* @param state фаза загрузки
* @param url URL загружаемого файла
*/
private function startLoading(state:int, url:String):void {
loaderState = state;
bitmapLoader.load(new URLRequest(url), loaderContext);
}
/**
*
*/
private function onComplete(e:Event):void {
dispatchEvent(new LoaderEvent(LoaderEvent.LOADING_COMPLETE, LoadingStage.TEXTURE));
switch (loaderState) {
case STATE_LOADING_DIFFUSE_MAP:
// Загрузилась диффузная текстура. При необходимости загружается карта прозрачности.
_bitmapData = Bitmap(bitmapLoader.content).bitmapData;
if (alphaTextureUrl != null) {
startLoading(STATE_LOADING_ALPHA_MAP, alphaTextureUrl);
} else {
complete();
}
break;
case STATE_LOADING_ALPHA_MAP:
// Загрузилась карта прозрачности. Выполняется копирование прозрачности в альфа-канал диффузной текстуры.
var tmpBmp:BitmapData = _bitmapData;
_bitmapData = new BitmapData(_bitmapData.width, _bitmapData.height, true, 0);
_bitmapData.copyPixels(tmpBmp, tmpBmp.rect, new Point());
var alpha:BitmapData = Bitmap(bitmapLoader.content).bitmapData;
if (_bitmapData.width != alpha.width || _bitmapData.height != alpha.height) {
tmpBmp.draw(alpha, new Matrix(_bitmapData.width/alpha.width, 0, 0, _bitmapData.height/alpha.height), null, BlendMode.NORMAL, null, true);
alpha.dispose();
alpha = tmpBmp;
}
_bitmapData.copyChannel(alpha, alpha.rect, new Point(), BitmapDataChannel.RED, BitmapDataChannel.ALPHA);
alpha.dispose();
complete();
break;
}
}
/**
*
*/
private function onLoadError(e:IOErrorEvent):void {
loaderState = STATE_IDLE;
dispatchEvent(e);
}
/**
*
*/
private function complete():void {
loaderState = STATE_IDLE;
bitmapLoader.unload();
dispatchEvent(new Event(Event.COMPLETE));
}
/**
* Загруженная текстура.
*/
public function get bitmapData():BitmapData {
return _bitmapData;
}
/**
* Прекращает текущую загрузку.
*/
public function close():void {
if (loaderState != STATE_IDLE) {
loaderState = STATE_IDLE;
bitmapLoader.close();
}
unload();
}
/**
* Очищает внутренние ссылки на загруженные объекты.
*/
public function unload():void {
if (loaderState == STATE_IDLE) {
if (bitmapLoader != null) {
bitmapLoader.unload();
}
loaderContext = null;
_bitmapData = null;
}
}
}
}

View File

@@ -0,0 +1,60 @@
package alternativa.engine3d.loaders.events {
import flash.events.Event;
/**
* Рассылается загрузчиками на различных этапах загрузки.
*/
public class LoaderEvent extends Event {
/**
* Значение свойства <code>type</code> для события <code>loadingStart</code>.
* @eventType loadingStart
*/
public static const LOADING_START:String = "loadingStart";
/**
* Значение свойства <code>type</code> для события <code>loadingComplete</code>.
* @eventType loadingComplete
*/
public static const LOADING_COMPLETE:String = "loadingComplete";
// Этап загрузки
private var _loadingStage:int;
/**
* Создаёт новый экземпляр объекта.
*
* @param type тип события
* @param loadingStage этап загрузки
*/
public function LoaderEvent(type:String, loadingStage:int) {
super(type);
_loadingStage = loadingStage;
}
/**
* Этап загрузки. Может принимать значения констант, описанных в классе <code>LoadingStage</code>.
*
* @see alternativa.engine3d.loaders.LoadingStage
*/
public function get loadingStage():int {
return _loadingStage;
}
/**
* Создаёт клон объекта.
*
* @return клонированный объект
*/
override public function clone():Event {
return new LoaderEvent(type, _loadingStage);
}
/**
* Создаёт строковое представление объекта.
*
* @return строковое представление объекта
*/
override public function toString():String {
return "[LoaderEvent type=\"" + type + "\", loadingStage=" + _loadingStage + "]";
}
}
}

View File

@@ -0,0 +1,84 @@
package alternativa.engine3d.loaders.events {
import flash.events.Event;
import flash.events.ProgressEvent;
/**
* Рассылается загрузчиками для отображения прогресса загрузки.
* Свойства <code>bytesLoaded</code> и <code>bytesTotal</code> показывают значения для текущего загружаемого элемента.
*/
public class LoaderProgressEvent extends ProgressEvent {
/**
* Значение свойства <code>type</code> для события <code>loadingProgress</code>.
* @eventType loadingProgress
*/
public static const LOADING_PROGRESS:String = "loadingProgress";
// Этап загрузки сцены
private var _loadingStage:int;
// Общее количество загружаемых элементов на текущем этапе загрузки
private var _totalItems:int;
// Номер элемента на текущем этапе загрузки сцены, с которым связано событие. Нумерация начинается с нуля.
private var _currentItem:int;
/**
* Создаёт новый экземпляр события.
*
* @param type тип события
* @param loadingStage этап загрузки сцены, в качестве значения параметра могут быть использованы константы класса <code>LoadingStage</code>
* @param totalItems общее количество загружаемых элементов на текущем этапе загрузки
* @param currentItem номер элемента на текущем этапе загрузки, с которым связано событие. Нумерация начинается с нуля.
* @param bytesLoaded количество загруженных байтов текущего элемента
* @param bytesTotal общее количество байтов текущего элемента
*
* @see alternativa.engine3d.loaders.LoadingStage
*/
public function LoaderProgressEvent(type:String, loadingStage:int, totalItems:int, currentItem:int, bytesLoaded:uint = 0, bytesTotal:uint = 0) {
super(type, false, false, bytesLoaded, bytesTotal);
_loadingStage = loadingStage;
_totalItems = totalItems;
_currentItem = currentItem;
}
/**
* Этап загрузки сцены. В качестве значения параметра могут быть использованы константы класса <code>LoadingStage</code>.
*
* @see alternativa.engine3d.loaders.LoadingStage
*/
public function get loadingStage():int {
return _loadingStage;
}
/**
* Общее количество загружаемых элементов на текущем этапе загрузки.
*/
public function get totalItems():int {
return _totalItems;
}
/**
* Номер элемента на текущем этапе загрузки, с которым связано событие. Нумерация начинается с нуля.
*/
public function get currentItem():int {
return _currentItem;
}
/**
* Создаёт клон объекта.
*
* @return клонированный объект
*/
override public function clone():Event {
return new LoaderProgressEvent(type, _loadingStage, _totalItems, _currentItem, bytesLoaded, bytesTotal);
}
/**
* Создаёт строковое представление объекта.
*
* @return строковое представление объекта
*/
override public function toString():String {
return "[LoaderProgressEvent type=\"" + type + "\", loadingStage=" + _loadingStage + ", totalItems=" + _totalItems + ", currentItem=" + _currentItem + ", bytesTotal=" + bytesTotal + ", bytesLoaded=" + bytesLoaded + "]";
}
}
}

View File

@@ -0,0 +1,504 @@
package alternativa.engine3d.materials {
import alternativa.engine3d.*;
import alternativa.engine3d.core.BSPNode;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.PolyPrimitive;
import alternativa.engine3d.core.Vertex;
import alternativa.engine3d.display.Skin;
import alternativa.types.Matrix3D;
import alternativa.types.Point3D;
import alternativa.utils.ColorUtils;
import flash.display.BlendMode;
import flash.display.Graphics;
use namespace alternativa3d;
/**
* Материал, раскрашивающий грани оттенками заданного цвета в зависимости от значения свойства <code>parameterType</code>.
*/
public class DevMaterial extends SurfaceMaterial {
/**
* Значение свойства <code>parameterType</code> для отображения глубины полигона в BSP-дереве. Глубина кодируется оттенком базового цвета материала. Оттенок
* получается покомпонентным умножением базового цвета на отношение значения уровня полигона в дереве к максимальному значению, задаваемому
* свойством <code>maxParameterValue</code>. Уровни нумеруются начиная от корня дерева, таким образом, чем глубже в дереве расположен
* полигон, тем он будет светлее.
*/
public static const BSP_DEPTH:int = 0;
/**
* Значение свойства <code>parameterType</code> для отображения мобильности полигона. Мобильность кодируется оттенком базового цвета материала. Оттенок
* получается покомпонентным умножением базового цвета на коэффициент, характеризующий положение мобильности полигона на отрезке,
* задаваемом свойствами <code>minMobility</code> и <code>maxMobility</code>. Более мобильные полигоны имеют более светлый оттенок.
*/
public static const MOBILITY:int = 1;
/**
* Значение свойства <code>parameterType</code> для отображения количества фрагментов грани, которой принадлежит отрисовываемый полигон. Количество фрагментов кодируется
* оттенком базового цвета материала. Оттенок получается покомпонентным умножением базового цвета на отношние количества фрагментов текущей грани
* к максимальному значению, задаваемому свойством <code>maxParameterValue</code>. Чем больше грань фрагментирована, тем она светлее.
*/
public static const FRAGMENTATION:int = 2;
/**
* Значение свойства <code>parameterType</code> для отображения граней с отсутствующими UV-координатами. Такие грани отображаются красной заливкой.
*/
public static const NO_UV_MAPPING:int = 3;
/**
* Значение свойства <code>parameterType</code> для отображения вырожденных полигонов. Вырожденные полигоны отображаются красной заливкой с красной обводкой толщиной пять
* пикселей. Для лучшей видимости таких полигонов можно сделать материал полупрозрачным.
*/
public static const DEGENERATE_POLY:int = 4;
/**
* Значение свойства <code>parameterType</code> для отображения неплоских полигонов.
* Значение <code>maxParameterValue</code> в этом режиме определяет максимальное отклонение в геометрии полигона от абсолютно плоского для признания его неплоским.
* Неплоские полигоны отображаются красной заливкой с красной обводкой толщиной пять
* пикселей. Для лучшей видимости таких полигонов можно сделать материал полупрозрачным.
*/
public static const NON_PLANAR_POLY:int = 5;
private static const point1:Point3D = new Point3D();
private static const point2:Point3D = new Point3D();
private var _parameterType:int = BSP_DEPTH;
private var _showNormals:Boolean;
private var _normalsColor:uint = 0x00FFFF;
private var _minMobility:int = 0;
private var _maxMobility:int = 255;
private var _maxParameterValue:Number = 20;
private var currentColor:int;
private var currentWireThickness:Number;
private var currentWireColor:uint;
/**
* @private
* Цвет
*/
alternativa3d var _color:uint;
/**
* @private
* Толщина линий обводки
*/
alternativa3d var _wireThickness:Number;
/**
* @private
* Цвет линий обводки
*/
alternativa3d var _wireColor:uint;
/**
* Создание экземпляра класса.
*
* @param parameterType тип отображаемого параметра
* @param color цвет заливки
* @param maxParameterValue максимальное значение отображаемого параметра
* @param showNormals включение режима отображения нормалей
* @param normalsColor цвет нормалей
* @param minMobility начало интервала мобильности
* @param maxMobility окончание интервала мобильности
* @param alpha прозрачность
* @param blendMode режим наложения цвета
* @param wireThickness толщина линии обводки
* @param wireColor цвет линии обводки
*/
public function DevMaterial(parameterType:uint = 0, color:uint = 0xFFFFFF, maxParameterValue:Number = 20, showNormals:Boolean = false, normalsColor:uint = 0x00FFFF, minMobility:int = 0, maxMobility:int = 255, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) {
super(alpha, blendMode);
_parameterType = parameterType;
_color = color;
_maxParameterValue = maxParameterValue;
_showNormals = showNormals;
_normalsColor = normalsColor;
_minMobility = minMobility;
_maxMobility = maxMobility;
_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;
var perspective:Number;
setDrawingParameters(skin);
if (currentColor > -1) {
gfx.beginFill(currentColor);
}
if (currentWireThickness >= 0) {
gfx.lineStyle(currentWireThickness, currentWireColor);
}
point = points[0];
var minX:Number;
var maxX:Number;
var minY:Number;
var maxY:Number;
if (camera._orthographic) {
minX = point.x;
maxX = minX;
minY = point.y;
maxY = minY;
gfx.moveTo(minX, minY);
for (i = 1; i < length; i++) {
point = points[i];
if (point.x > maxX) {
maxX = point.x;
} else if (point.x < minX) {
minX = point.x;
}
if (point.y > maxY) {
maxY = point.y;
} else if (point.y < minY) {
minY = point.y;
}
gfx.lineTo(point.x, point.y);
}
if (currentWireThickness >= 0) {
point = points[0];
if ((maxX - minX) > 0.125 && (maxY - minY) > 0.125) {
gfx.lineTo(point.x, point.y);
} else {
// Очень маленький полигон
gfx.drawRect(point.x - 0.5, point.y - 0.5, 1, 1);
}
}
} else {
perspective = camera._focalLength/point.z;
minX = point.x*perspective;
maxX = minX;
minY = point.y*perspective;
maxY = minY;
gfx.moveTo(minX, minY);
for (i = 1; i < length; i++) {
point = points[i];
perspective = camera._focalLength/point.z;
var x:Number = point.x*perspective;
var y:Number = point.y*perspective;
if (x > maxX) {
maxX = x;
} else if (x < minX) {
minX = x;
}
if (y > maxY) {
maxY = y;
} else if (y < minY) {
minY = y;
}
gfx.lineTo(x, y);
}
if (currentWireThickness >= 0) {
point = points[0];
perspective = camera._focalLength/point.z;
if ((maxX - minX) > 0.125 && (maxY - minY) > 0.125) {
gfx.lineTo(point.x*perspective, point.y*perspective);
} else {
// Очень маленький полигон
gfx.drawRect(point.x*perspective - 0.5, point.y*perspective - 0.5, 1, 1);
}
}
}
// Отрисовка нормали
if (_showNormals) {
point1.reset();
for (i = 0; i < length; i++) {
point = points[i];
point1.x += point.x;
point1.y += point.y;
point1.z += point.z;
}
point1.multiply(1/length);
var multiplyer:Number = 10;
var normal:Point3D = skin.primitive.face.globalNormal;
var m:Matrix3D = camera.cameraMatrix;
point2.x = (normal.x*m.a + normal.y*m.b + normal.z*m.c)*multiplyer + point1.x;
point2.y = (normal.x*m.e + normal.y*m.f + normal.z*m.g)*multiplyer + point1.y;
point2.z = (normal.x*m.i + normal.y*m.j + normal.z*m.k)*multiplyer + point1.z;
if (camera._orthographic) {
gfx.moveTo(point1.x, point1.y);
gfx.lineStyle(0, _normalsColor);
gfx.lineTo(point2.x, point2.y);
} else {
perspective = camera._focalLength/point1.z;
gfx.moveTo(point1.x*perspective, point1.y*perspective);
gfx.lineStyle(0, _normalsColor);
perspective = camera._focalLength/point2.z;
gfx.lineTo(point2.x*perspective, point2.y*perspective);
}
gfx.lineStyle();
}
}
/**
* Установка параметров отрисовки.
*/
private function setDrawingParameters(skin:Skin):void {
currentColor = _color;
currentWireColor = _wireColor;
currentWireThickness = _wireThickness;
var param:int = 0;
var i:int;
switch (_parameterType) {
case BSP_DEPTH:
// Глубина вложенности в BSP-tree
var node:BSPNode = skin.primitive.node;
while (node != null) {
node = node.parent;
param++;
}
currentColor = ColorUtils.multiply(_color, param/_maxParameterValue);
break;
case MOBILITY:
// Мобильность
var value:Number = (skin.primitive.mobility - _minMobility)/(_maxMobility - _minMobility);
if (value < 0) {
value = 0;
}
currentColor = ColorUtils.multiply(_color, value);
break;
case FRAGMENTATION:
// Степень фрагментирования
currentColor = ColorUtils.multiply(_color, calculateFragments(skin.primitive.face.primitive)/_maxParameterValue);
break;
case NO_UV_MAPPING:
// Отсутствие UV
if (skin.primitive.face.uvMatrix == null) {
currentColor = 0xFF0000;
}
break;
case DEGENERATE_POLY:
// Вырожденные полигоны
var face:Face = skin.primitive.face;
var point0:Point3D = face._vertices[0]._coords;
point1.copy(face._vertices[1]._coords);
point2.copy(face._vertices[2]._coords);
point2.subtract(point1);
point1.subtract(point0);
var crossX:Number = point1.y*point2.z - point1.z*point2.y;
var crossY:Number = point1.z*point2.x - point1.x*point2.z;
var crossZ:Number = point1.x*point2.y - point1.y*point2.x;
var sum:Number = crossX*crossX + crossY*crossY + crossZ*crossZ;
for (i = 3; i < face._verticesCount; i++) {
point1.copy(face._vertices[i - 1]._coords);
point2.copy(face._vertices[i]._coords);
point2.subtract(point1);
point1.subtract(point0);
crossX = point1.y*point2.z - point1.z*point2.y;
crossY = point1.z*point2.x - point1.x*point2.z;
crossZ = point1.x*point2.y - point1.y*point2.x;
sum += crossX*crossX + crossY*crossY + crossZ*crossZ;
}
if (sum < 0.001) {
currentColor = 0xFF0000;
currentWireColor = 0xFF0000;
currentWireThickness = 5;
}
break;
case NON_PLANAR_POLY:
// Неплоские полигоны
face = skin.primitive.face;
var normal:Point3D = face.globalNormal;
var offset:Number = face.globalOffset;
for (i = 3; i < face._verticesCount; i++) {
var vertex:Point3D = (face._vertices[i] as Vertex).globalCoords;
var distance:Number = vertex.x*normal.x + vertex.y*normal.y + vertex.z*normal.z - offset;
if (distance > _maxParameterValue || distance < -_maxParameterValue) {
currentColor = 0xFF0000;
currentWireColor = 0xFF0000;
currentWireThickness = -1;
return;
}
}
break;
}
}
/**
* Расчёт количества фрагментов грани примитива.
*/
private function calculateFragments(primitive:PolyPrimitive):int {
if (primitive.frontFragment == null) {
return 1;
}
return calculateFragments(primitive.frontFragment) + calculateFragments(primitive.backFragment);
}
/**
* Цвет заливки.
*/
public function get color():uint {
return _color;
}
/**
* @private
*/
public function set color(value:uint):void {
if (_color != value) {
_color = value;
markToChange();
}
}
/**
* Толщина линии обводки. Если значение отрицательное, то отрисовка линии не выполняется.
*/
public function get wireThickness():Number {
return _wireThickness;
}
/**
* @private
*/
public function set wireThickness(value:Number):void {
if (_wireThickness != value) {
_wireThickness = value;
markToChange();
}
}
/**
* Цвет линии обводки.
*/
public function get wireColor():uint {
return _wireColor;
}
/**
* @private
*/
public function set wireColor(value:uint):void {
if (_wireColor != value) {
_wireColor = value;
markToChange();
}
}
/**
* @inheritDoc
*/
override public function clone():Material {
var res:DevMaterial = new DevMaterial(_parameterType, _color, _maxParameterValue, _showNormals, _normalsColor, _minMobility, _maxMobility, _alpha, _blendMode, _wireThickness, _wireColor);
return res;
}
/**
* Тип отображаемого параметра.
*/
public function set parameterType(value:int):void {
if (_parameterType != value) {
_parameterType = value;
markToChange();
}
}
/**
* @private
*/
public function get parameterType():int {
return _parameterType;
}
/**
* Включение режима отображения нормалей.
*/
public function set showNormals(value:Boolean):void {
if (_showNormals != value) {
_showNormals = value;
markToChange();
}
}
/**
* @private
*/
public function get showNormals():Boolean {
return _showNormals;
}
/**
* Цвет нормалей.
*
* @default 0x00FFFF
*/
public function set normalsColor(value:uint):void {
if (_normalsColor != value) {
_normalsColor = value;
markToChange();
}
}
/**
* @private
*/
public function get normalsColor():uint {
return _normalsColor;
}
/**
* Начало интервала мобильности.
*/
public function set minMobility(value:int):void {
if (_minMobility != value) {
_minMobility = value;
markToChange();
}
}
/**
* @private
*/
public function get minMobility():int {
return _minMobility;
}
/**
* Окончание интервала мобильности.
*/
public function set maxMobility(value:int):void {
if (_maxMobility != value) {
_maxMobility = value;
markToChange();
}
}
/**
* @private
*/
public function get maxMobility():int {
return _maxMobility;
}
/**
* Максимальное значение отображаемого параметра.
*/
public function set maxParameterValue(value:Number):void {
if (_maxParameterValue != value) {
_maxParameterValue = value;
markToChange();
}
}
/**
* @private
*/
public function get maxParameterValue():Number {
return _maxParameterValue;
}
}
}

View File

@@ -0,0 +1,47 @@
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,160 @@
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;
markToChange();
}
}
/**
* Толщина линии обводки. Если значение отрицательное, то обводка не рисуется.
*/
public function get wireThickness():Number {
return _wireThickness;
}
/**
* @private
*/
public function set wireThickness(value:Number):void {
if (_wireThickness != value) {
_wireThickness = value;
markToChange();
}
}
/**
* Цвет линии обводки.
*/
public function get wireColor():uint {
return _wireColor;
}
/**
* @private
*/
public function set wireColor(value:uint):void {
if (_wireColor != value) {
_wireColor = value;
markToChange();
}
}
/**
* @inheritDoc
*/
override public function clone():Material {
var res:FillMaterial = new FillMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor);
return res;
}
}
}

View File

@@ -0,0 +1,94 @@
package alternativa.engine3d.materials {
import alternativa.engine3d.*;
import alternativa.engine3d.display.Skin;
use namespace alternativa3d;
/**
* Базовый класс для материалов.
*/
public class Material {
/**
* @private
* Альфа.
*/
alternativa3d var _alpha:Number;
/**
* @private
* Режим наложения цвета.
*/
alternativa3d var _blendMode:String;
/**
* Создание экземпляра класса.
*
* @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
* @param blendMode режим наложения цвета
*/
public function Material(alpha:Number, blendMode:String) {
_alpha = alpha;
_blendMode = blendMode;
}
/**
* Коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
*/
public function get alpha():Number {
return _alpha;
}
/**
* @private
*/
public function set alpha(value:Number):void {
if (_alpha != value) {
_alpha = value;
markToChange();
}
}
/**
* Режим наложения цвета.
*/
public function get blendMode():String {
return _blendMode;
}
/**
* @private
*/
public function set blendMode(value:String):void {
if (_blendMode != value) {
_blendMode = value;
markToChange();
}
}
/**
* Отметить материал на перерисовку.
*/
protected function markToChange():void {}
/**
* @private
* Метод очищает переданный скин (нарисованную графику, дочерние объекты и т.д.).
*
* @param skin скин для очистки
*/
alternativa3d function clear(skin:Skin):void {
skin.gfx.clear();
}
/**
* Создание клона материала.
*
* @return клон материала
*/
public function clone():Material {
return new Material(_alpha, _blendMode);
}
}
}

View File

@@ -0,0 +1,226 @@
package alternativa.engine3d.materials {
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Surface;
import alternativa.engine3d.events.MouseEvent3D;
import alternativa.types.Texture;
import flash.display.BitmapData;
import flash.display.BlendMode;
import flash.display.MovieClip;
import flash.events.Event;
import flash.geom.Matrix;
import flash.geom.Rectangle;
use namespace alternativa3d;
/**
* Материал, позволяющий использовать мувиклип в качестве интерактивной текстуры.
* <p>
* При использовании материала следует учитывать тот факт, что для перерисовки текстуры мувиклипу назначается обработчик события
* ENTER_FRAME. Если ссылка на материал будет потеряна, обработчик всё равно будет выполняться. Поэтому при окончании работы с
* материалом следует установить свойству <code>movieClip</code> значение <code>null</code> для отключения обработчика.
* </p>
*/
public class MovieClipMaterial extends TextureMaterial {
private var _movieClip:MovieClip;
private var texWidth:uint;
private var texHeight:uint;
private var _clearRect:Rectangle;
private var _clipRect:Rectangle;
private var _matrix:Matrix;
private var _refreshRate:int;
private var _refreshCounter:int;
/**
* Цвет, которым заливается текстура материала перед отрисовкой мувиклипа.
*/
public var fillColor:uint;
/**
* Создаёт новый экземпляр материала.
*
* @param movieClip мувиклип, используемый в качестве текстуры
* @param textureWidth ширина текстуры материала
* @param textureHeight высота текстуры материала
* @param clipRect область мувиклипа, отрисовываемая в текстуру материала. Если передано значение <code>null</code>, будет отрисовываться вся область мувиклипа.
* @param matrix матрица трансформации мувиклипа при отрисовке. Если передано значение <code>null</code>, трансформация выполняться не будет.
* @param smooth сглаживание текстуры при увеличении масштаба
* @param precision точность перспективной коррекции. Может быть задана одной из констант класса
* <code>TextureMaterialPrecision</code> или числом типа Number. Во втором случае, чем ближе заданное значение к единице, тем более
* качественная перспективная коррекция будет выполнена, и тем больше времени будет затрачено на расчёт кадра.
* @param fillColor цвет, которым заливается текстура материала перед отрисовкой мувиклипа
* @param refreshRate частота обновления текстуры материала. Единица означает перерисовку каждый кадр, двойка &mdash; каждые два кадра и так далее
*
* @see TextureMaterialPrecision
*/
public function MovieClipMaterial(movieClip:MovieClip, textureWidth:uint, textureHeight:uint, clipRect:Rectangle = null, matrix:Matrix = null, smooth:Boolean = false, precision:Number = 10, fillColor:uint = 0, refreshRate:int = 1) {
texWidth = textureWidth;
texHeight = textureHeight;
_clearRect = new Rectangle(0, 0, texWidth, texHeight);
if (clipRect != null) {
_clipRect = clipRect.clone();
}
if (matrix != null) {
_matrix = matrix.clone();
}
_texture = new Texture(new BitmapData(texWidth, texWidth));
this.refreshRate = refreshRate;
this.movieClip = movieClip;
this.fillColor = fillColor;
super(_texture, 1, false, smooth, BlendMode.NORMAL, -1, 0, precision);
}
/**
* Мувиклип, используемый в качестве текстуры.
* <p>
* При установке свойства указанному мувиклипу назначается обработчик события ENTER_FRAME. Во избежании утечки памяти, при прекращении работы с материалом
* следует устанавливать данному свойству значение <code>null</code> для отключения обработчика.
* </p>
*/
public function get movieClip():MovieClip {
return _movieClip;
}
/**
* @private
*/
public function set movieClip(value:MovieClip):void {
if (value != _movieClip) {
if (_movieClip != null) {
_movieClip.removeEventListener(Event.ENTER_FRAME, redraw);
}
_movieClip = value;
if (_movieClip != null) {
_movieClip.addEventListener(Event.ENTER_FRAME, redraw);
} else {
_texture.bitmapData.fillRect(_clearRect, fillColor);
}
}
}
/**
* @private
*/
private function redraw(e:Event):void {
if (_movieClip != null) {
_refreshCounter++;
if (_refreshCounter == _refreshRate) {
_refreshCounter = 0;
_texture.bitmapData.fillRect(_clearRect, fillColor);
_texture.bitmapData.draw(_movieClip, _matrix, null, BlendMode.NORMAL, _clearRect, _smooth);
}
}
}
/**
* @private
*/
override alternativa3d function addToSurface(surface:Surface):void {
super.addToSurface(surface);
// Устанавливаем обработчики мышиных событий, которые будут обеспечивать корректное позиционирование мувиклипа
surface.addEventListener(MouseEvent3D.MOUSE_MOVE, onSurfaceMouseMove);
surface.addEventListener(MouseEvent3D.MOUSE_OVER, onSurfaceMouseOverOut);
surface.addEventListener(MouseEvent3D.MOUSE_OUT, onSurfaceMouseOverOut);
}
/**
* @private
*/
override alternativa3d function removeFromSurface(surface:Surface):void {
super.removeFromSurface(surface);
// Удаляем обработчики мышиных событий
surface.removeEventListener(MouseEvent3D.MOUSE_MOVE, onSurfaceMouseMove);
surface.removeEventListener(MouseEvent3D.MOUSE_OVER, onSurfaceMouseOverOut);
surface.removeEventListener(MouseEvent3D.MOUSE_OUT, onSurfaceMouseOverOut);
}
/**
* @private
*/
private function onSurfaceMouseMove(e:MouseEvent3D):void {
if (_movieClip != null) {
// При перемещении мыши над гранью позиционируем мувиклип так, чтобы совместить точку грани под курсором мыши с соотвествующей точкой на мувиклипе
_movieClip.x = e.view.mouseX - texWidth*e.u;
_movieClip.y = e.view.mouseY - texHeight*(1 - e.v);
}
}
/**
* @private
*/
private function onSurfaceMouseOverOut(e:MouseEvent3D):void {
if (_movieClip != null) {
// Удаляем мувиклип с вьюпорта при уходе мыши с грани и добавляем при наведении мыши на грань.
// Это нужно, чтобы вьюпорт получал событие MOUSE_MOVE при перемещении мыши над мувиклипом.
if (e.type == MouseEvent3D.MOUSE_OVER) {
e.view.addChild(_movieClip);
_movieClip.alpha = 0;
} else {
if (_movieClip.parent == e.view) {
e.view.removeChild(_movieClip);
}
}
}
}
/**
* @private
*/
override public function set texture(value:Texture):void {
}
/**
* Частота обновления текстуры материала. Единица означает перерисовку каждый кадр, двойка &mdash; каждые два кадра и так далее.
*/
public function get refreshRate():int {
return _refreshRate;
}
/**
* @private
*/
public function set refreshRate(value:int):void {
_refreshRate = value > 0 ? value : 1;
}
/**
* Область мувиклипа, отрисовываемая в текстуру материала. При установленном значении <code>null</code> будет отрисовываться вся область мувиклипа.
*/
public function get clipRect():Rectangle {
return _clipRect == null ? null : _clipRect.clone();
}
/**
* @private
*/
public function set clipRect(value:Rectangle):void {
_clipRect = value;
}
/**
* Матрица трансформации мувиклипа при отрисовке. При установленном значении <code>null</code> трансформация выполняться не будет.
*/
public function get matrix():Matrix {
return _matrix == null ? null : _matrix.clone();
}
/**
* @private
*/
public function set matrix(value:Matrix):void {
_matrix = value;
}
/**
* Выполняет клонирование материала.
*
* @return копия материала
*/
override public function clone():Material {
return new MovieClipMaterial(_movieClip, texWidth, texHeight, _clipRect, _matrix, _smooth, _precision, fillColor, _refreshRate);
}
}
}

View File

@@ -0,0 +1,122 @@
package alternativa.engine3d.materials {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Scene3D;
import alternativa.engine3d.core.Sprite3D;
import alternativa.engine3d.display.Skin;
import alternativa.types.*;
import flash.display.BlendMode;
use namespace alternativa3d;
use namespace alternativatypes;
/**
* Базовый класс для материалов спрайтов.
*/
public class SpriteMaterial extends Material {
/**
* @private
* Спрайт.
*/
alternativa3d var _sprite:Sprite3D;
/**
* Создание экземпляра материала.
*
* @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
* @param blendMode режим наложения цвета
*/
public function SpriteMaterial(alpha:Number = 1, blendMode:String = BlendMode.NORMAL) {
super(alpha, blendMode);
}
/**
* Спрайт, которому назначен материал.
*/
public function get sprite():Sprite3D {
return _sprite;
}
/**
* @private
* Добавление на сцену.
*
* @param scene
*/
alternativa3d function addToScene(scene:Scene3D):void {}
/**
* @private
* Удаление из сцены.
*
* @param scene
*/
alternativa3d function removeFromScene(scene:Scene3D):void {}
/**
* @private
* Назначение спрайту.
*
* @param sprite спрайт
*/
alternativa3d function addToSprite(sprite:Sprite3D):void {
_sprite = sprite;
}
/**
* @private
* Удаление из спрайта.
*
* @param sprite спрайт
*/
alternativa3d function removeFromSprite(sprite:Sprite3D):void {
_sprite = null;
}
/**
* @private
* Метод определяет, может ли материал нарисовать спрайт. Метод используется в системе отрисовки сцены и должен использоваться
* наследниками для указания видимости связанной со спрайтом. Реализация по умолчанию возвращает
* <code>true</code>.
*
* @param camera камера через которую происходит отрисовка.
*
* @return <code>true</code>, если материал может отрисовать указанный примитив, иначе <code>false</code>
*/
alternativa3d function canDraw(camera:Camera3D):Boolean {
return true;
}
/**
* @private
* Метод выполняет отрисовку в заданный скин.
*
* @param camera камера, вызвавшая метод
* @param skin скин, в котором нужно отрисовать
*/
alternativa3d function draw(camera:Camera3D, skin:Skin):void {
skin.alpha = _alpha;
skin.blendMode = _blendMode;
}
/**
* @inheritDoc
*/
override protected function markToChange():void {
if (_sprite != null) {
_sprite.addMaterialChangedOperationToScene();
}
}
/**
* @inheritDoc
*/
override public function clone():Material {
return new SpriteMaterial(_alpha, _blendMode);
}
}
}

View File

@@ -0,0 +1,243 @@
package alternativa.engine3d.materials {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.display.Skin;
import alternativa.types.*;
import flash.display.BlendMode;
import flash.geom.Matrix;
import flash.geom.Rectangle;
use namespace alternativa3d;
use namespace alternativatypes;
/**
* Материал, отображающий заданную текстуру в точке нахождения спрайта. Текстура отображается так, как если бы она находилась в плоскости,
* параллельной плоскости области вывода камеры, а верхний край текстуры был параллелен верхнему краю области вывода. При отрисовке изображения
* начало координат текстуры в области вывода совпадает с проекцией точки спрайта. По умолчанию начало координат текстуры перенесено в её центр.
*/
public class SpriteTextureMaterial extends SpriteMaterial {
/**
* @private
* Вспомогательный прямоугольник, используется при отрисовке для хранения параметров отрисовки.
*/
private static var drawRect:Rectangle = new Rectangle();
/**
* @private
* Матрица, используемая для отрисовки текстуры спрайта
*/
private static var textureMatrix:Matrix = new Matrix();
/**
* @private
* Текстура
*/
alternativa3d var _texture:Texture;
/**
* @private
* Сглаженность текстуры
*/
alternativa3d var _smooth:Boolean;
/**
* @private
* Смещение начала координат по оси X
*/
alternativa3d var _originX:Number;
/**
* @private
* Смещение начала координат по оси Y
*/
alternativa3d var _originY:Number;
/**
* Создание экземпляра класса.
*
* @param texture текстура для отображения
* @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
* @param smooth сглаживание текстуры
* @param blendMode режим наложения цвета
* @param originX относительное смещение начала координат в текстуре по оси X
* @param originY относительное смещение начала координат в текстуре по оси Y
*/
public function SpriteTextureMaterial(texture:Texture, alpha:Number = 1, smooth:Boolean = false, blendMode:String = BlendMode.NORMAL, originX:Number = 0.5, originY:Number = 0.5) {
super(alpha, blendMode);
_texture = texture;
_smooth = smooth;
_originX = originX;
_originY = originY;
}
/**
* @private
* @inheritDoc
*/
override alternativa3d function canDraw(camera:Camera3D):Boolean {
if (_texture == null) {
return false;
}
// Переводим координаты в систему камеры
var cameraMatrix:Matrix3D = camera.cameraMatrix;
var x:Number = _sprite.globalCoords.x;
var y:Number = _sprite.globalCoords.y;
var z:Number = _sprite.globalCoords.z;
var pointX:Number = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d;
var pointY:Number = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h;
var pointZ:Number = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l;
var w:Number;
var h:Number;
if (camera._orthographic) {
if ((camera._nearClipping && pointZ < camera._nearClippingDistance) || (camera._farClipping && pointZ > camera._farClippingDistance)) {
return false;
}
w = _texture._width*camera._zoom*_sprite._materialScale;
h = _texture._height*camera._zoom*_sprite._materialScale;
x = pointX - w*_originX;
y = pointY - h*_originY;
} else {
if ((pointZ <= 0) || (camera._nearClipping && pointZ < camera._nearClippingDistance) || (camera._farClipping && pointZ > camera._farClippingDistance)) {
return false;
}
var perspective:Number = camera._focalLength/pointZ;
w = _texture._width*perspective*_sprite._materialScale;
h = _texture._height*perspective*_sprite._materialScale;
x = pointX*perspective - w*_originX;
y = pointY*perspective - h*_originY;
}
var halfW:Number = camera._view._width*0.5;
var halfH:Number = camera._view._height*0.5;
if (camera._viewClipping && (x >= halfW || y >= halfH || x + w <= -halfW || y + h <= -halfH)) {
return false;
}
textureMatrix.a = w/_texture._width;
textureMatrix.d = h/_texture._height;
textureMatrix.tx = x;
textureMatrix.ty = y;
if (camera._viewClipping) {
if (x < -halfW) {
w -= -halfW - x;
x = -halfW;
}
if (x + w > halfW) {
w = halfW - x;
}
if (y < -halfH) {
h -= -halfH - y;
y = -halfH;
}
if (y + h > halfH) {
h = halfH - y;
}
}
drawRect.x = x;
drawRect.y = y;
drawRect.width = w;
drawRect.height = h;
return true;
}
/**
* @private
* @inheritDoc
*/
override alternativa3d function draw(camera:Camera3D, skin:Skin):void {
skin.alpha = _alpha;
skin.blendMode = _blendMode;
skin.gfx.beginBitmapFill(_texture._bitmapData, textureMatrix, false, _smooth);
skin.gfx.drawRect(drawRect.x, drawRect.y, drawRect.width, drawRect.height);
}
/**
* Текстура.
*/
public function get texture():Texture {
return _texture;
}
/**
* @private
*/
public function set texture(value:Texture):void {
if (_texture != value) {
_texture = value;
markToChange();
}
}
/**
* Сглаживание текстуры.
*/
public function get smooth():Boolean {
return _smooth;
}
/**
* @private
*/
public function set smooth(value:Boolean):void {
if (_smooth != value) {
_smooth = value;
markToChange();
}
}
/**
* Относительное смещение начала координат в текстуре по оси X.
*
* @default 0.5
*/
public function get originX():Number {
return _originX;
}
/**
* @private
*/
public function set originX(value:Number):void {
if (_originX != value) {
_originX = value;
markToChange();
}
}
/**
* Относительное смещение начала координат в текстуре по оси Y.
*
* @default 0.5
*/
public function get originY():Number {
return _originY;
}
/**
* @private
*/
public function set originY(value:Number):void {
if (_originY != value) {
_originY = value;
markToChange();
}
}
/**
* @inheritDoc
*/
override public function clone():Material {
return new SpriteTextureMaterial(_texture, _alpha, _smooth, _blendMode, _originX, _originY);
}
}
}

View File

@@ -0,0 +1,150 @@
package alternativa.engine3d.materials {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.PolyPrimitive;
import alternativa.engine3d.core.Scene3D;
import alternativa.engine3d.core.Surface;
import alternativa.engine3d.display.Skin;
import flash.display.BlendMode;
use namespace alternativa3d;
/**
* Базовый класс для материалов полигональных поверхностей.
*/
public class SurfaceMaterial extends Material {
/**
* @private
* Поверхность
*/
alternativa3d var _surface:Surface;
/**
* @private
* Флаг, определяет использует ли материал UV-координаты в грани
*/
alternativa3d var useUV:Boolean = false;
/**
* Создание экземпляра класса.
*
* @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
* @param blendMode режим наложения цвета
*/
public function SurfaceMaterial(alpha:Number = 1, blendMode:String = BlendMode.NORMAL) {
super(alpha, blendMode);
}
/**
* Поверхность, которой назначен материал.
*/
public function get surface():Surface {
return _surface;
}
/**
* @private
* Добавление на сцену
*
* @param scene
*/
alternativa3d function addToScene(scene:Scene3D):void {}
/**
* @private
* Удаление из сцены
*
* @param scene
*/
alternativa3d function removeFromScene(scene:Scene3D):void {}
/**
* @private
* Добавление к мешу
*
* @param mesh
*/
alternativa3d function addToMesh(mesh:Mesh):void {}
/**
* @private
* Удаление из меша
*
* @param mesh
*/
alternativa3d function removeFromMesh(mesh:Mesh):void {}
/**
* @private
* Добавление на поверхность
*
* @param surface
*/
alternativa3d function addToSurface(surface:Surface):void {
// Сохраняем поверхность
_surface = surface;
}
/**
* @private
* Удаление с поверхности
*
* @param surface
*/
alternativa3d function removeFromSurface(surface:Surface):void {
// Удаляем ссылку на поверхность
_surface = null;
}
/**
* @inheritDoc
*/
override protected function markToChange():void {
if (_surface != null) {
_surface.addMaterialChangedOperationToScene();
}
}
/**
* @private
* Метод определяет, может ли материал нарисовать указанный примитив. Метод используется в системе отрисовки сцены и должен использоваться
* наследниками для указания видимости связанной с материалом поверхности или отдельного примитива. Реализация по умолчанию возвращает
* <code>true</code>.
*
* @param primitive примитив для проверки
*
* @return <code>true</code>, если материал может отрисовать указанный примитив, иначе <code>false</code>
*/
alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
return true;
}
/**
* @private
* Метод выполняет отрисовку в заданный скин.
*
* @param camera камера, вызвавшая метод
* @param skin скин, в котором нужно рисовать
* @param length длина массива points
* @param points массив точек, определяющих отрисовываемый полигон. Каждый элемент массива является объектом класса
* <code>alternativa.engine3d.materials.DrawPoint</code>
*
* @see DrawPoint
*/
alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
skin.alpha = _alpha;
skin.blendMode = _blendMode;
}
/**
* @inheritDoc
*/
override public function clone():Material {
return new SurfaceMaterial(_alpha, _blendMode);
}
}
}

View File

@@ -0,0 +1,356 @@
package alternativa.engine3d.materials {
import __AS3__.vec.Vector;
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.PolyPrimitive;
import alternativa.engine3d.display.Skin;
import alternativa.types.*;
import flash.display.BitmapData;
import flash.display.BlendMode;
import flash.geom.Matrix;
import flash.display.BitmapData;
import flash.display.Graphics;
use namespace alternativa3d;
use namespace alternativatypes;
/**
* Материал, заполняющий полигон текстурой. Помимо наложения текстуры, материал может рисовать границу полигона линией
* заданной толщины и цвета.
*/
public class TextureMaterial extends SurfaceMaterial {
private static var stubBitmapData:BitmapData;
private static var stubMatrix:Matrix;
private var gfx:Graphics;
private var textureMatrix:Matrix = new Matrix();
private var focalLength:Number;
private var distortion:Number;
/**
* @private
* Текстура
*/
alternativa3d var _texture:Texture;
/**
* @private
* Повтор текстуры
*/
alternativa3d var _repeat:Boolean;
/**
* @private
* Сглаженность текстуры
*/
alternativa3d var _smooth:Boolean;
/**
* @private
* Точность перспективной коррекции
*/
alternativa3d var _precision:Number;
/**
* @private
* Толщина линий обводки
*/
alternativa3d var _wireThickness:Number;
/**
* @private
* Цвет линий обводки
*/
alternativa3d var _wireColor:uint;
/**
* Создание экземпляра текстурного материала.
*
* @param texture текстура материала
* @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
* @param repeat повтор текстуры при заполнении
* @param smooth сглаживание текстуры при увеличении масштаба
* @param blendMode режим наложения цвета
* @param wireThickness толщина линии обводки
* @param wireColor цвет линии обводки
* @param precision точность перспективной коррекции. Может быть задана одной из констант класса
* <code>TextureMaterialPrecision</code> или числом типа Number. Во втором случае, чем ближе заданное значение к единице, тем более
* качественная перспективная коррекция будет выполнена, и тем больше времени будет затрачено на расчёт кадра.
*
* @see TextureMaterialPrecision
*/
public function TextureMaterial(texture:Texture, alpha:Number = 1, repeat:Boolean = true, smooth:Boolean = false, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0, precision:Number = TextureMaterialPrecision.MEDIUM) {
super(alpha, blendMode);
_texture = texture;
_repeat = repeat;
_smooth = smooth;
_wireThickness = wireThickness;
_wireColor = wireColor;
_precision = precision;
useUV = true;
}
/**
* @private
* @inheritDoc
*/
override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
return _texture != null;
}
/**
* @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;
gfx = skin.gfx;
// Проверка на нулевую UV-матрицу
if (skin.primitive.face.uvMatrixBase == null) {
if (stubBitmapData == null) {
// Создание текстуры-заглушки
stubBitmapData = new BitmapData(2, 2, false, 0);
stubBitmapData.setPixel(0, 0, 0xFF00FF);
stubBitmapData.setPixel(1, 1, 0xFF00FF);
stubMatrix = new Matrix(10, 0, 0, 10, 0, 0);
}
gfx.beginBitmapFill(stubBitmapData, stubMatrix);
if (camera._orthographic) {
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 {
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);
}
}
return;
}
if (camera._orthographic) {
// Расчитываем матрицу наложения текстуры
var face:Face = skin.primitive.face;
// Если матрица не расчитана, считаем
if (!camera.uvMatricesCalculated[face]) {
camera.calculateUVMatrix(face, _texture._width, _texture._height);
}
gfx.beginBitmapFill(_texture._bitmapData, face.orthoTextureMatrix, _repeat, _smooth);
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 {
// Отрисовка
focalLength = camera._focalLength;
//distortion = camera.focalDistortion*_precision;
var front:int = 0;
var back:int = length - 1;
var newFront:int = 1;
var newBack:int = (back > 0) ? (back - 1) : (length - 1);
var direction:Boolean = true;
var a:DrawPoint = points[back];
var b:DrawPoint;
var c:DrawPoint = points[front];
var drawVertices:Vector.<Number> = new Vector.<Number>();
var drawUVTs:Vector.<Number> = new Vector.<Number>();
for (i = 0; i < length; i++) {
var p:DrawPoint = points[i];
var t:Number = focalLength/p.z;
drawVertices[i << 1] = p.x*t;
drawVertices[(i << 1) + 1] = p.y*t;
drawUVTs.push(p.u, 1 - p.v, t);
}
var drawIndices:Vector.<int> = new Vector.<int>();
while (front != newBack) {
if (direction) {
/* a = points[front];
b = points[newFront];
c = points[back];
*/
drawIndices.push(front, newFront, back);
front = newFront;
newFront = (front < length - 1) ? (front + 1) : 0;
} else {
/* a = points[newBack];
b = points[back];
c = points[front];
*/
drawIndices.push(newBack, back, front);
back = newBack;
newBack = (back > 0) ? (back - 1) : (length - 1);
}
direction = !direction;
}
gfx.beginBitmapFill(_texture.bitmapData, null, _repeat, _smooth);
if (_wireThickness >= 0) {
gfx.lineStyle(_wireThickness, _wireColor);
}
gfx.drawTriangles(drawVertices, drawIndices, drawUVTs);
}
}
/**
* Текстура материала. Материал не выполняет никаких действий по отрисовке, если не задана текстура.
*/
public function get texture():Texture {
return _texture;
}
/**
* @private
*/
public function set texture(value:Texture):void {
if (_texture != value) {
_texture = value;
markToChange();
}
}
/**
* Повтор текстуры при заливке. Более подробную информацию можно найти в описании метода
* <code>flash.display.Graphics#beginBitmapFill()</code>.
*/
public function get repeat():Boolean {
return _repeat;
}
/**
* @private
*/
public function set repeat(value:Boolean):void {
if (_repeat != value) {
_repeat = value;
markToChange();
}
}
/**
* Сглаживание текстуры при увеличении масштаба. Более подробную информацию можно найти в описании метода
* <code>flash.display.Graphics#beginBitmapFill()</code>.
*/
public function get smooth():Boolean {
return _smooth;
}
/**
* @private
*/
public function set smooth(value:Boolean):void {
if (_smooth != value) {
_smooth = 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;
markToChange();
}
}
/**
* Цвет линии обводки полигона.
*/
public function get wireColor():uint {
return _wireColor;
}
/**
* @private
*/
public function set wireColor(value:uint):void {
if (_wireColor != value) {
_wireColor = value;
markToChange();
}
}
/**
* Точность перспективной коррекции.
*/
public function get precision():Number {
return _precision;
}
/**
* @private
*/
public function set precision(value:Number):void {
if (_precision != value) {
_precision = value;
markToChange();
}
}
/**
* @inheritDoc
*/
override public function clone():Material {
var res:TextureMaterial = new TextureMaterial(_texture, _alpha, _repeat, _smooth, _blendMode, _wireThickness, _wireColor, _precision);
return res;
}
}
}

View File

@@ -0,0 +1,40 @@
package alternativa.engine3d.materials {
/**
* Класс содержит константы точности перспективной коррекции текстурного материала.
*
* @see TextureMaterial
*/
public class TextureMaterialPrecision {
/**
* Адаптивная триангуляция не будет выполняться, только простая триангуляция.
*/
public static const NONE:Number = -1;
/**
* Очень низкое качество адаптивной триангуляции.
*/
public static const VERY_LOW:Number = 50;
/**
* Низкое качество адаптивной триангуляции.
*/
public static const LOW:Number = 25;
/**
* Среднее качество адаптивной триангуляции.
*/
public static const MEDIUM:Number = 10;
/**
* Высокое качество адаптивной триангуляции.
*/
public static const HIGH:Number = 6;
/**
* Очень высокое качество адаптивной триангуляции.
*/
public static const VERY_HIGH:Number = 3;
/**
* Максимальное качество адаптивной триангуляции.
*/
public static const BEST:Number = 1;
}
}

View File

@@ -0,0 +1,127 @@
package alternativa.engine3d.materials {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.PolyPrimitive;
import alternativa.engine3d.display.Skin;
import flash.display.BlendMode;
import flash.display.Graphics;
use namespace alternativa3d;
/**
* Материал для рисования рёбер полигонов.
*/
public class WireMaterial extends SurfaceMaterial {
/**
* @private
* Цвет
*/
alternativa3d var _color:uint;
/**
* @private
* Толщина линий
*/
alternativa3d var _thickness:Number;
/**
* Создание экземпляра класса.
*
* @param thickness толщина линий
* @param color цвет линий
* @param alpha коэффициент непрозрачности линий. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
* @param blendMode режим наложения цвета
*/
public function WireMaterial(thickness:Number = 0, color:uint = 0, alpha:Number = 1, blendMode:String = BlendMode.NORMAL) {
super(alpha, blendMode);
_color = color;
_thickness = thickness;
}
/**
* @private
* @inheritDoc
*/
override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
return _thickness >= 0;
}
/**
* @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.lineStyle(_thickness, _color);
point = points[length - 1];
gfx.moveTo(point.x, point.y);
for (i = 0; i < length; i++) {
point = points[i];
gfx.lineTo(point.x, point.y);
}
} else {
// Отрисовка
gfx.lineStyle(_thickness, _color);
point = points[length - 1];
var perspective:Number = camera._focalLength/point.z;
gfx.moveTo(point.x*perspective, point.y*perspective);
for (i = 0; i < length; i++) {
point = points[i];
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;
markToChange();
}
}
/**
* Толщина линий. Если толщина отрицательная, то отрисовка не выполняется.
*/
public function get thickness():Number {
return _thickness;
}
/**
* @private
*/
public function set thickness(value:Number):void {
if (_thickness != value) {
_thickness = value;
markToChange();
}
}
/**
* @inheritDoc
*/
override public function clone():Material {
return new WireMaterial(_thickness, _color, _alpha, _blendMode);
}
}
}

View File

@@ -0,0 +1,32 @@
package alternativa.engine3d.physics {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Face;
import alternativa.types.Point3D;
use namespace alternativa3d;
/**
* Параметры столкновения эллипсоида с гранью объекта. Плоскостью столкновения является касательная к
* эллипсоиду плоскость, проходящая через точку столкновения с гранью.
*/
public class Collision {
/**
* Грань, с которой произошло столкновение.
*/
public var face:Face;
/**
* Нормаль плоскости столкновения.
*/
public var normal:Point3D;
/**
* Смещение плоскости столкновения.
*/
public var offset:Number;
/**
* Координаты точки столкновения.
*/
public var point:Point3D;
}
}

View File

@@ -0,0 +1,62 @@
package alternativa.engine3d.physics {
import alternativa.engine3d.*;
import alternativa.engine3d.core.BSPNode;
use namespace alternativa3d;
/**
* @private
*/
public class CollisionPlane {
// Узел BSP дерева, который содержит плоскость
public var node:BSPNode;
// Индикатор положения объекта относительно плоскости (спереди или сзади)
public var infront:Boolean;
// Расстояние до плоскости в начальной точке (всегда положительное)
public var sourceOffset:Number;
// Расстояние до плоскости в конечной точке
public var destinationOffset:Number;
// Хранилище неиспользуемых плоскостей
static private var collector:Array = new Array();
/**
* Создание плоскости
*
* @param node
* @param infront
* @param sourceOffset
* @param destinationOffset
* @return
*/
static alternativa3d function createCollisionPlane(node:BSPNode, infront:Boolean, sourceOffset:Number, destinationOffset:Number):CollisionPlane {
// Достаём плоскость из коллектора
var plane:CollisionPlane = collector.pop();
// Если коллектор пуст, создаём новую плоскость
if (plane == null) {
plane = new CollisionPlane();
}
plane.node = node;
plane.infront = infront;
plane.sourceOffset = sourceOffset;
plane.destinationOffset = destinationOffset;
return plane;
}
/**
* Удаление плоскости, все ссылки должны быть почищены
*
* @param plane
*/
static alternativa3d function destroyCollisionPlane(plane:CollisionPlane):void {
plane.node = null;
collector.push(plane);
}
}
}

View File

@@ -0,0 +1,20 @@
package alternativa.engine3d.physics {
/**
* Константы, определяющие режим учёта объектов сцены, заданных в множестве <code>EllipsoidCollider.collisionSet</code>
* при определении столкновений.
*
* @see EllipsoidCollider#collisionSet
*/
public class CollisionSetMode {
/**
* Грани объектов игнорируются при определении столкновений.
*/
static public const EXCLUDE:int = 1;
/**
* Учитываются только столкновения с гранями, принадлежащим перечисленным в множестве объектам.
*/
static public const INCLUDE:int = 2;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,328 @@
package alternativa.engine3d.primitives {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Surface;
import flash.geom.Point;
use namespace alternativa3d;
/**
* Прямоугольный параллелепипед.
* <p>Параллелепипед содержит шесть поверхностей с идентификаторами <code>"front"</code>, <code>"back"</code>, <code>"left"</code>,
* <code>"right"</code>, <code>"top"</code>, <code>"bottom"</code>, на каждую из которых может быть установлен свой материал.
* </p>
*/
public class Box extends Mesh {
// Инкремент количества объектов
private static var counter:uint = 0;
/**
* Создание нового параллелепипеда.
*
* @param width ширина. Размерность по оси X. Не может быть меньше нуля.
* @param length длина. Размерность по оси Y. Не может быть меньше нуля.
* @param height высота. Размерность по оси Z. Не может быть меньше нуля.
* @param widthSegments количество сегментов по ширине
* @param lengthSegments количество сегментов по длине
* @param heightSegments количество сегментов по по высоте
* @param reverse задает направление нормалей граней. Если указано значение <code>true</code>, то нормали будут направлены внутрь фигуры.
* @param triangulate флаг триангуляции. Если указано значение <code>true</code>, четырехугольники в параллелепипеде будут триангулированы.
*/
public function Box(width:Number = 100, length:Number = 100, height:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false, triangulate:Boolean = false) {
super();
if ((widthSegments == 0) || (lengthSegments == 0) || (heightSegments == 0)) {
return;
}
width = (width < 0)? 0 : width;
length = (length < 0)? 0 : length;
height = (height < 0)? 0 : height;
var wh:Number = width/2;
var lh:Number = length/2;
var hh:Number = height/2;
var ws:Number = width/widthSegments;
var ls:Number = length/lengthSegments;
var hs:Number = height/heightSegments;
var x:int;
var y:int;
var z:int;
// Создание точек
for (x = 0; x <= widthSegments; x++) {
for (y = 0; y <= lengthSegments; y++) {
for (z = 0; z <= heightSegments; z++) {
if (x == 0 || x == widthSegments || y == 0 || y == lengthSegments || z == 0 || z == heightSegments) {
createVertex(x*ws - wh, y*ls - lh, z*hs - hh, x + "_" + y + "_" + z);
}
}
}
}
// Создание поверхностей
var front:Surface = createSurface(null, "front");
var back:Surface = createSurface(null, "back");
var left:Surface = createSurface(null, "left");
var right:Surface = createSurface(null, "right");
var top:Surface = createSurface(null, "top");
var bottom:Surface = createSurface(null, "bottom");
// Создание граней
var wd:Number = 1/widthSegments;
var ld:Number = 1/lengthSegments;
var hd:Number = 1/heightSegments;
var faceId:String;
// Для оптимизаций UV при триангуляции
var aUV:Point;
var cUV:Point;
// Построение верхней грани
for (y = 0; y < lengthSegments; y++) {
for (x = 0; x < widthSegments; x++) {
faceId = "top_"+x+"_"+y;
if (reverse) {
if (triangulate) {
aUV = new Point(x*wd, (lengthSegments - y)*ld);
cUV = new Point((x + 1)*wd, (lengthSegments - y - 1)*ld);
createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":1");
setUVsToFace(aUV, new Point(x*wd, (lengthSegments - y - 1)*ld), cUV, faceId + ":1");
createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":0");
setUVsToFace(cUV, new Point((x + 1)*wd, (lengthSegments - y)*ld), aUV, faceId + ":0");
} else {
createFace([x + "_" + y + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments], faceId);
setUVsToFace(new Point(x*wd, (lengthSegments - y)*ld), new Point(x*wd, (lengthSegments - y - 1)*ld), new Point((x + 1)*wd, (lengthSegments - y - 1)*ld), faceId);
}
} else {
if (triangulate) {
aUV = new Point(x*wd, y*ld);
cUV = new Point((x + 1)*wd, (y + 1)*ld);
createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments], faceId + ":0");
setUVsToFace(aUV, new Point((x + 1)*wd, y*ld), cUV, faceId + ":0");
createFace([(x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments, x + "_" + y + "_" + heightSegments], faceId + ":1");
setUVsToFace(cUV, new Point(x*wd, (y + 1)*ld), aUV, faceId + ":1");
} else {
createFace([x + "_" + y + "_" + heightSegments, (x + 1) + "_" + y + "_" + heightSegments, (x + 1) + "_" + (y + 1) + "_" + heightSegments, x + "_" + (y + 1) + "_" + heightSegments], faceId);
setUVsToFace(new Point(x*wd, y*ld), new Point((x + 1)*wd, y*ld), new Point((x + 1)*wd, (y + 1)*ld), faceId);
}
}
if (triangulate) {
top.addFace(faceId + ":0");
top.addFace(faceId + ":1");
} else {
top.addFace(faceId);
}
}
}
// Построение нижней грани
for (y = 0; y < lengthSegments; y++) {
for (x = 0; x < widthSegments; x++) {
faceId = "bottom_" + x + "_" + y;
if (reverse) {
if (triangulate) {
aUV = new Point((widthSegments - x)*wd, (lengthSegments - y)*ld);
cUV = new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld);
createFace([x + "_" + y + "_" + 0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":0");
setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), cUV, faceId + ":0");
createFace([(x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0, x + "_" + y + "_" + 0], faceId + ":1");
setUVsToFace(cUV, new Point((widthSegments - x)*wd, (lengthSegments - y - 1)*ld), aUV, faceId + ":1");
} else {
createFace([x + "_" + y + "_"+0, (x + 1) + "_" + y + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, x + "_" + (y + 1) + "_" + 0], faceId);
setUVsToFace(new Point((widthSegments - x)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y)*ld), new Point((widthSegments - x - 1)*wd, (lengthSegments - y - 1)*ld), faceId);
}
} else {
if (triangulate) {
aUV = new Point((widthSegments - x)*wd, y*ld);
cUV = new Point((widthSegments - x - 1)*wd, (y + 1)*ld);
createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0], faceId + ":1");
setUVsToFace(aUV, new Point((widthSegments - x)*wd, (y + 1)*ld), cUV, faceId + ":1");
createFace([(x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0, x + "_" + y + "_" + 0], faceId + ":0");
setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, y*ld), aUV, faceId + ":0");
} else {
createFace([x + "_" + y + "_" + 0, x + "_" + (y + 1) +"_" + 0, (x + 1) + "_" + (y + 1) + "_" + 0, (x + 1) + "_" + y + "_" + 0], faceId);
setUVsToFace(new Point((widthSegments - x)*wd, y*ld), new Point((widthSegments - x)*wd, (y + 1)*ld), new Point((widthSegments - x - 1)*wd, (y + 1)*ld), faceId);
}
}
if (triangulate) {
bottom.addFace(faceId + ":0");
bottom.addFace(faceId + ":1");
} else {
bottom.addFace(faceId);
}
}
}
// Построение фронтальной грани
for (z = 0; z < heightSegments; z++) {
for (x = 0; x < widthSegments; x++) {
faceId = "front_"+x+"_"+z;
if (reverse) {
if (triangulate) {
aUV = new Point((widthSegments - x)*wd, z*hd);
cUV = new Point((widthSegments - x - 1)*wd, (z + 1)*hd);
createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":1");
setUVsToFace(aUV, new Point((widthSegments - x)*wd, (z + 1)*hd), cUV, faceId + ":1");
createFace([(x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z, x + "_" + 0 + "_" + z], faceId + ":0");
setUVsToFace(cUV, new Point((widthSegments - x - 1)*wd, z*hd), aUV, faceId + ":0");
} else {
createFace([x + "_" + 0 + "_" + z, x + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + (z + 1), (x + 1) + "_" + 0 + "_" + z], faceId);
setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId);
}
} else {
if (triangulate) {
aUV = new Point(x*wd, z*hd);
cUV = new Point((x + 1)*wd, (z + 1)*hd);
createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1)], faceId + ":0");
setUVsToFace(aUV, new Point((x + 1)*wd, z*hd), cUV, faceId + ":0");
createFace([(x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + z], faceId + ":1");
setUVsToFace(cUV, new Point(x*wd, (z + 1)*hd), aUV, faceId + ":1");
} else {
createFace([x + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + z, (x + 1) + "_" + 0 + "_" + (z + 1), x + "_" + 0 + "_" + (z + 1)], faceId);
setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId);
}
}
if (triangulate) {
front.addFace(faceId + ":0");
front.addFace(faceId + ":1");
} else {
front.addFace(faceId);
}
}
}
// Построение задней грани
for (z = 0; z < heightSegments; z++) {
for (x = 0; x < widthSegments; x++) {
faceId = "back_"+x+"_"+z;
if (reverse) {
if (triangulate) {
aUV = new Point(x * wd, (z + 1) * hd);
cUV = new Point((x + 1) * wd, z * hd);
createFace([x + "_" + lengthSegments+"_" + (z + 1), x + "_"+lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0");
setUVsToFace(aUV, new Point(x * wd, z * hd), cUV, faceId + ":0");
createFace([(x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId + ":1");
setUVsToFace(cUV, new Point((x + 1) * wd, (z + 1) * hd), aUV, faceId + ":1");
} else {
createFace([x + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + z, (x + 1) + "_" + lengthSegments + "_" + (z + 1), x + "_" + lengthSegments + "_" + (z + 1)], faceId);
setUVsToFace(new Point(x*wd, z*hd), new Point((x + 1)*wd, z*hd), new Point((x + 1)*wd, (z + 1)*hd), faceId);
}
} else {
if (triangulate) {
aUV = new Point((widthSegments - x)*wd, (z + 1)*hd);
cUV = new Point((widthSegments - x - 1)*wd, z*hd);
createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":0");
setUVsToFace(new Point((widthSegments - x)*wd, z*hd), aUV, cUV, faceId + ":0");
createFace([x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId + ":1");
setUVsToFace(aUV, new Point((widthSegments - x - 1)*wd, (z + 1)*hd), cUV, faceId + ":1");
} else {
createFace([x + "_" + lengthSegments + "_" + z, x + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + (z + 1), (x + 1) + "_" + lengthSegments + "_" + z], faceId);
setUVsToFace(new Point((widthSegments - x)*wd, z*hd), new Point((widthSegments - x)*wd, (z + 1)*hd), new Point((widthSegments - x - 1)*wd, (z + 1)*hd), faceId);
}
}
if (triangulate) {
back.addFace(faceId + ":0");
back.addFace(faceId + ":1");
} else {
back.addFace(faceId);
}
}
}
// Построение левой грани
for (y = 0; y < lengthSegments; y++) {
for (z = 0; z < heightSegments; z++) {
faceId = "left_" + y + "_" + z;
if (reverse) {
if (triangulate) {
aUV = new Point(y*ld, (z + 1)*hd);
cUV = new Point((y + 1)*ld, z*hd);
createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z], faceId + ":0");
setUVsToFace(aUV, new Point(y*ld, z*hd), cUV, faceId + ":0");
createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId + ":1");
setUVsToFace(cUV, new Point((y + 1)*ld, (z + 1)*hd), aUV, faceId + ":1");
} else {
createFace([0 + "_" + y + "_" + z, 0 + "_" + (y + 1) + "_" + z, 0 + "_" + (y + 1) + "_" + (z + 1), 0 + "_" + y + "_" + (z + 1)], faceId);
setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId);
}
} else {
if (triangulate) {
aUV = new Point((lengthSegments - y - 1)*ld, z*hd);
cUV = new Point((lengthSegments - y)*ld, (z + 1)*hd);
createFace([0 + "_" + (y + 1) + "_" + z, 0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1)], faceId + ":0");
setUVsToFace(aUV, new Point((lengthSegments - y)*ld, z*hd), cUV, faceId + ":0");
createFace([0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + (y + 1) + "_" + z], faceId + ":1");
setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), aUV, faceId + ":1");
} else {
createFace([0 + "_" + y + "_" + z, 0 + "_" + y + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + (z + 1), 0 + "_" + ((y + 1)) + "_" + z], faceId);
setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId);
}
}
if (triangulate) {
left.addFace(faceId + ":0");
left.addFace(faceId + ":1");
} else {
left.addFace(faceId);
}
}
}
// Построение правой грани
for (y = 0; y < lengthSegments; y++) {
for (z = 0; z < heightSegments; z++) {
faceId = "right_" + y + "_" + z;
if (reverse) {
if (triangulate) {
aUV = new Point((lengthSegments - y)*ld, z*hd);
cUV = new Point((lengthSegments - y - 1)*ld, (z + 1)*hd);
createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":1");
setUVsToFace(aUV, new Point((lengthSegments - y)*ld, (z + 1)*hd), cUV, faceId + ":1");
createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + y + "_" + z], faceId + ":0");
setUVsToFace(cUV, new Point((lengthSegments - y - 1)*ld, z*hd), aUV, faceId + ":0");
} else {
createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + (y + 1) + "_" + z], faceId);
setUVsToFace(new Point((lengthSegments - y)*ld, z*hd), new Point((lengthSegments - y)*ld, (z + 1)*hd), new Point((lengthSegments - y - 1)*ld, (z + 1)*hd), faceId);
}
} else {
if (triangulate) {
aUV = new Point(y*ld, z*hd);
cUV = new Point((y + 1)*ld, (z + 1)*hd);
createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1)], faceId + ":0");
setUVsToFace(aUV, new Point((y + 1)*ld, z*hd), cUV, faceId + ":0");
createFace([widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1), widthSegments + "_" + y + "_" + z], faceId + ":1");
setUVsToFace(cUV, new Point(y*ld, (z + 1)*hd), aUV, faceId + ":1");
} else {
createFace([widthSegments + "_" + y + "_" + z, widthSegments + "_" + (y + 1) + "_" + z, widthSegments + "_" + (y + 1) + "_" + (z + 1), widthSegments + "_" + y + "_" + (z + 1)], faceId);
setUVsToFace(new Point(y*ld, z*hd), new Point((y + 1)*ld, z*hd), new Point((y + 1)*ld, (z + 1)*hd), faceId);
}
}
if (triangulate) {
right.addFace(faceId + ":0");
right.addFace(faceId + ":1");
} else {
right.addFace(faceId);
}
}
}
}
/**
* @inheritDoc
*/
protected override function createEmptyObject():Object3D {
return new Box(0, 0, 0, 0);
}
/**
* @inheritDoc
*/
override protected function defaultName():String {
return "box" + ++counter;
}
}
}

View File

@@ -0,0 +1,277 @@
package alternativa.engine3d.primitives {
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.utils.MathUtils;
import flash.geom.Point;
use namespace alternativa3d;
/**
* Усеченный конус или цилиндр.
*/
public class Cone extends Mesh {
// Инкремент количества объектов
private static var counter:uint = 0;
/**
* Создает усеченный конус или цилиндр.
* <p>Различные значения параметров позволяют создавать различные примитивы.
* При установленном параметре <code>topRadius = 0</code> или <code>bottomRadius = 0</code> будет построен конус. При установленном </code>bottomRadius = topRadius</code> будет построен цилиндр.</p>
* <p>По умолчанию параметр <code>triangulate</code> установлен в <code>false</code> и на примитив не может быть наложена текстура.
* Только при установленном параметре <code>triangulate</code> в <code>true</code> это возможно.</p>
* <p>После создания примитив всегда содержит в себе поверхность <code>"side"</code>.
* При установленном параметре <code>bottomRadius</code> не равном нулю в примитиве создается поверхность <code>"bottom"</code>,
* при установленном параметре <code>topRadius</code> в примитиве создается поверхность <code>"top"</code>.
* На каждую из поверхностей может быть наложен свой материал</p>
*
* @param height высота примтива. Размерность по оси Z. Не может быть меньше нуля.
* @param bottomRadius нижний радиус примитива
* @param topRadius верхний радиус примтива
* @param heightSegments количество сегментов по высоте примитива
* @param radialSegments количество сегментов по радиусу примтива
* @param reverse задает направление нормалей. При значении <code>true</code> нормли будут направлены внутрь примитива.
* @param triangulate флаг триангуляции. При значении <code>true</code> все четырехугольные грани примитива будут триангулированы
* и появится возможность наложить на примитив текстуру.
*/
public function Cone(height:Number = 100, bottomRadius:Number = 100, topRadius:Number = 0, heightSegments:uint = 1, radialSegments:uint = 12, reverse:Boolean = false, triangulate:Boolean = false) {
if ((radialSegments < 3) || (heightSegments < 1) || (heightSegments == 1 && topRadius == 0 && bottomRadius == 0)) {
return;
}
height = (height < 0)? 0 : height;
bottomRadius = (bottomRadius < 0)? 0 : bottomRadius;
topRadius = (topRadius < 0)? 0 : topRadius;
const radialSegment:Number = MathUtils.DEG360/radialSegments;
const radiusSegment:Number = (bottomRadius - topRadius)/heightSegments;
const heightSegment:Number = height/heightSegments;
const halfHeight:Number = height*0.5
const uSegment:Number = 1/radialSegments;
const vSegment:Number = 1/heightSegments;
// Создание вершин
if (topRadius == 0 || triangulate) {
var poleUp:Vertex = createVertex(0, 0, halfHeight, "poleUp");
}
if (bottomRadius == 0 || triangulate) {
var poleDown:Vertex = createVertex(0, 0, -halfHeight, "poleDown");
}
var radial:uint;
var segment:uint;
var topSegment:uint = heightSegments - int(topRadius == 0);
var bottomSegment:uint = int(bottomRadius == 0) ;
for (segment = bottomSegment; segment <= topSegment; segment++) {
for (radial = 0; radial < radialSegments; radial++) {
var currentAngle:Number = radialSegment*radial;
var currentRadius:Number = bottomRadius - (radiusSegment*segment);
createVertex(Math.cos(currentAngle)*currentRadius, Math.sin(currentAngle)*currentRadius, heightSegment*segment - halfHeight, radial + "_" + segment);
}
}
// Создание граней и поверхности
var face:Face;
var points:Array;
var side:Surface = createSurface(null, "side");
if (topRadius == 0) {
// Создание граней у верхнего полюса
var prevRadial:uint = radialSegments - 1;
var centerUV:Point = new Point(0.5, 1);
var v:Number = topSegment*vSegment;
if (reverse) {
for (radial = 0; radial < radialSegments; radial++) {
face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], prevRadial + "_" + topSegment);
if (triangulate) {
setUVsToFace(centerUV, new Point(1 - (prevRadial + 1)*uSegment, v) , new Point(1 - prevRadial*uSegment, v), face);
}
side.addFace(face);
prevRadial = radial;
}
} else {
for (radial = 0; radial < radialSegments; radial++) {
face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], prevRadial + "_" + topSegment);
if (triangulate) {
setUVsToFace(centerUV, new Point(prevRadial*uSegment, v), new Point((prevRadial + 1)*uSegment, v), face);
}
side.addFace(face);
prevRadial = radial;
}
}
} else {
// Создание граней верхней крышки
var top:Surface = createSurface(null, "top");
if (triangulate) {
prevRadial = radialSegments - 1;
centerUV = new Point(0.5, 0.5);
var UV:Point;
var prevUV:Point;
if (reverse) {
prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5);
for (radial = 0; radial < radialSegments; radial++) {
face = createFace([poleUp, radial + "_" + topSegment, prevRadial + "_" + topSegment], "top_" + prevRadial);
currentAngle = radial * radialSegment;
UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5);
setUVsToFace(centerUV, UV, prevUV, face);
top.addFace(face);
prevUV = UV;
prevRadial = radial;
}
} else {
prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5);
for (radial = 0; radial < radialSegments; radial++) {
face = createFace([poleUp, prevRadial + "_" + topSegment, radial + "_" + topSegment], "top_" + prevRadial);
currentAngle = radial*radialSegment;
UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5);
setUVsToFace(centerUV, prevUV, UV, face);
top.addFace(face);
prevUV = UV;
prevRadial = radial;
}
}
} else {
points = new Array();
if (reverse) {
for (radial = (radialSegments - 1); radial < uint(-1); radial--) {
points.push(radial + "_" + topSegment);
}
} else {
for (radial = 0; radial < radialSegments; radial++) {
points.push(radial + "_" + topSegment);
}
}
top.addFace(createFace(points, "top"));
}
}
// Создание боковых граней
var face2:Face;
var aUV:Point;
var cUV:Point;
for (segment = bottomSegment; segment < topSegment; segment++) {
prevRadial = radialSegments - 1;
v = segment * vSegment;
for (radial = 0; radial < radialSegments; radial++) {
if (triangulate) {
if (reverse) {
face = createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":0");
face2 = createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1");
aUV = new Point(1 - (prevRadial + 1)*uSegment, v + vSegment)
cUV = new Point(1 - prevRadial*uSegment, v);
setUVsToFace(aUV, new Point(1 - (prevRadial + 1)*uSegment, v), cUV, face);
setUVsToFace(cUV, new Point(1 - prevRadial*uSegment, v + vSegment), aUV, face2);
} else {
face = createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0");
face2 = createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":1");
aUV = new Point(prevRadial*uSegment, v)
cUV = new Point((prevRadial + 1)*uSegment, v + vSegment);
setUVsToFace(aUV, new Point((prevRadial + 1)*uSegment, v), cUV, face);
setUVsToFace(cUV, new Point(prevRadial*uSegment, v + vSegment), aUV, face2);
}
side.addFace(face);
side.addFace(face2);
} else {
if (reverse) {
side.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment));
} else {
side.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1)], prevRadial + "_" + segment));
}
}
prevRadial = radial;
}
}
if (bottomRadius == 0) {
// Создание граней у нижнего полюса
prevRadial = radialSegments - 1;
centerUV = new Point(0.5, 0);
v = bottomSegment*vSegment;
if (reverse) {
for (radial = 0; radial < radialSegments; radial++) {
face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], prevRadial + "_0");
if (triangulate) {
setUVsToFace(centerUV, new Point(1 - prevRadial*uSegment, v), new Point(1 - (prevRadial + 1)*uSegment, v), face);
}
side.addFace(face);
prevRadial = radial;
}
} else {
for (radial = 0; radial < radialSegments; radial++) {
face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], prevRadial + "_0");
if (triangulate) {
setUVsToFace(centerUV, new Point((prevRadial + 1)*uSegment, v), new Point(prevRadial*uSegment, v), face);
}
side.addFace(face);
prevRadial = radial;
}
}
} else {
// Создание граней нижней крышки
var bottom:Surface = createSurface(null, "bottom");
if (triangulate) {
prevRadial = radialSegments - 1;
centerUV = new Point(0.5, 0.5);
if (reverse) {
prevUV = new Point(Math.cos(-radialSegment)*0.5 + 0.5, Math.sin(-radialSegment)*0.5 + 0.5);
for (radial = 0; radial < radialSegments; radial++) {
face = createFace([poleDown, prevRadial + "_" + bottomSegment, radial + "_" + bottomSegment], "bottom_" + prevRadial);
currentAngle = radial*radialSegment;
UV = new Point(Math.cos(currentAngle)*0.5 + 0.5, Math.sin(currentAngle)*0.5 + 0.5);
setUVsToFace(centerUV, prevUV, UV, face);
bottom.addFace(face);
prevUV = UV;
prevRadial = radial;
}
} else {
prevUV = new Point(0.5 - Math.cos(-radialSegment)*0.5, Math.sin(-radialSegment)*0.5 + 0.5);
for (radial = 0; radial < radialSegments; radial++) {
face = createFace([poleDown, radial + "_" + bottomSegment, prevRadial + "_" + bottomSegment], "bottom_" + prevRadial);
currentAngle = radial * radialSegment;
UV = new Point(0.5 - Math.cos(currentAngle)*0.5, Math.sin(currentAngle)*0.5 + 0.5);
setUVsToFace(centerUV, UV, prevUV, face);
bottom.addFace(face);
prevUV = UV;
prevRadial = radial;
}
}
} else {
points = new Array();
if (reverse) {
for (radial = 0; radial < radialSegments; radial++) {
points.push(radial + "_" + bottomSegment);
}
} else {
for (radial = (radialSegments - 1); radial < uint(-1); radial--) {
points.push(radial + "_" + bottomSegment);
}
}
bottom.addFace(createFace(points, "bottom"));
}
}
}
/**
* @inheritDoc
*/
protected override function createEmptyObject():Object3D {
return new Cone(0, 0, 0, 0);
}
/**
* @inheritDoc
*/
override protected function defaultName():String {
return "cone" + ++counter;
}
}
}

View File

@@ -0,0 +1,209 @@
package alternativa.engine3d.primitives {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Surface;
import flash.geom.Point;
use namespace alternativa3d;
/**
* Геоплоскость.
*/
public class GeoPlane extends Mesh {
// Инкремент количества объектов
private static var counter:uint = 0;
/**
* Создает геоплоскость.
* <p>Геоплоскость это плоскость с сетчатой структурой граней.</p>
* <p>Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров.
* При значении <code>reverse</code> установленном в <code>true</code> примитив будет содержать грань - <code>"back"</code>.
* При значении <code>reverse</code> установленном в <code>false</code> примитив будет содержать грань - <code>"front"</code>.
* Параметр <code>twoSided</code> указывает методу создать обе поверхности.</p>
*
* @param width ширина. Размерность по оси X. Не может быть меньше нуля.
* @param length длина. Размерность по оси Y. Не может быть меньше нуля.
* @param widthSegments количество сегментов по ширине
* @param lengthSegments количество сегментов по длине
* @param twoSided если значение параметра равно <code>true</code>, то создаётся двусторонняя поверхность
* @param reverse флаг инвертирования нормалей
*/
public function GeoPlane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false) {
super();
if ((widthSegments == 0) || (lengthSegments == 0)) {
return;
}
width = (width < 0)? 0 : width;
length = (length < 0)? 0 : length;
// Середина
var wh:Number = width/2;
var hh:Number = length/2;
// Размеры сегмента
var ws:Number = width/widthSegments;
var hs:Number = length/lengthSegments;
// Размеры UV-сегмента
var us:Number = 1/widthSegments;
var vs:Number = 1/lengthSegments;
// Создание точек
var x:uint;
var y:uint;
var frontUV:Array = new Array();
var backUV:Array = ((lengthSegments & 1) == 0) ? null : new Array();
for (y = 0; y <= lengthSegments; y++) {
frontUV[y] = new Array();
if (backUV != null) {
backUV[y] = new Array();
}
for (x = 0; x <= widthSegments; x++) {
if ((y & 1) == 0) {
// Если чётный ряд
createVertex(x*ws - wh, y*hs - hh, 0, y + "_" + x);
frontUV[y][x] = new Point(x*us, y*vs);
if (backUV != null) {
backUV[y][x] = new Point(x*us, 1 - y*vs);
}
} else {
// Если нечётный ряд
if (x == 0) {
// Первая точка
createVertex(-wh, y*hs - hh, 0, y + "_" + x);
frontUV[y][x] = new Point(0, y*vs);
if (backUV != null) {
backUV[y][x] = new Point(0, 1 - y*vs);
}
} else {
createVertex(x*ws - wh - ws/2, y*hs - hh, 0, y + "_" + x);
frontUV[y][x] = new Point((x - 0.5)*us, y*vs);
if (backUV != null) {
backUV[y][x] = new Point((x - 0.5)*us, 1 - y*vs);
}
if (x == widthSegments) {
// Последняя точка
createVertex(wh, y*hs - hh, 0, y + "_" + (x + 1));
frontUV[y][x + 1] = new Point(1, y*vs);
if (backUV != null) {
backUV[y][x + 1] = new Point(1, 1 - y*vs);
}
}
}
}
}
}
// Создание поверхностей
var front:Surface;
var back:Surface;
if (twoSided || !reverse) {
front = createSurface(null, "front");
}
if (twoSided || reverse) {
back = createSurface(null, "back");
}
// Создание полигонов
var face:Face;
for (y = 0; y < lengthSegments; y++) {
for (var n:uint = 0; n <= (widthSegments << 1); n++) {
x = n >> 1;
if ((y & 1) == 0) {
// Если чётный ряд
if ((n & 1) == 0) {
// Если остриём вверх
if (twoSided || !reverse) {
face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]);
setUVsToFace(frontUV[y][x], frontUV[y + 1][x + 1], frontUV[y + 1][x], face);
front.addFace(face);
}
if (twoSided || reverse) {
face = createFace([y + "_" + x, (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]);
if (backUV != null) {
setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y + 1][x + 1], face);
} else {
setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face);
}
back.addFace(face);
}
} else {
// Если остриём вниз
if (twoSided || !reverse) {
face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + (x + 1)]);
setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x + 1], face);
front.addFace(face);
}
if (twoSided || reverse) {
face = createFace([y + "_" + x, (y + 1) + "_" + (x + 1), y + "_" + (x + 1)]);
if (backUV != null) {
setUVsToFace(backUV[y][x], backUV[y + 1][x + 1], backUV[y][x + 1], face);
} else {
setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x + 1], frontUV[lengthSegments - y][x + 1], face);
}
back.addFace(face);
}
}
} else {
// Если нечётный ряд
if ((n & 1) == 0) {
// Если остриём вниз
if (twoSided || !reverse) {
face = createFace([y + "_" + x, y + "_" + (x + 1), (y + 1) + "_" + x]);
setUVsToFace(frontUV[y][x], frontUV[y][x + 1], frontUV[y + 1][x], face);
front.addFace(face);
}
if (twoSided || reverse) {
face = createFace([y + "_" + x, (y + 1) + "_" + x, y + "_" + (x + 1)]);
if (backUV != null) {
setUVsToFace(backUV[y][x], backUV[y + 1][x], backUV[y][x + 1], face);
} else {
setUVsToFace(frontUV[lengthSegments - y][x], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y][x + 1], face);
}
back.addFace(face);
}
} else {
// Если остриём вверх
if (twoSided || !reverse) {
face = createFace([y + "_" + (x + 1), (y + 1) + "_" + (x + 1), (y + 1) + "_" + x]);
setUVsToFace(frontUV[y][x+1], frontUV[y + 1][x + 1], frontUV[y + 1][x], face);
front.addFace(face);
}
if (twoSided || reverse) {
face = createFace([y + "_" + (x + 1), (y + 1) + "_" + x, (y + 1) + "_" + (x + 1)]);
if (backUV != null) {
setUVsToFace(backUV[y][x + 1], backUV[y + 1][x], backUV[y + 1][x + 1], face);
} else {
setUVsToFace(frontUV[lengthSegments - y][x + 1], frontUV[lengthSegments - y - 1][x], frontUV[lengthSegments - y - 1][x + 1], face);
}
back.addFace(face);
}
}
}
}
}
}
/**
* @inheritDoc
*/
protected override function createEmptyObject():Object3D {
return new GeoPlane(0, 0, 0);
}
/**
* @inheritDoc
*/
override protected function defaultName():String {
return "geoPlane" + ++counter;
}
}
}

View File

@@ -0,0 +1,334 @@
package alternativa.engine3d.primitives {
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.types.Point3D;
import alternativa.utils.MathUtils;
import flash.geom.Point;
use namespace alternativa3d;
/**
* Геосфера.
*/
public class GeoSphere extends Mesh {
// Инкремент количества объектов
private static var counter:uint = 0;
/**
* Создает геосферу.
* <p>Геосфера после создания содержит в себе одну поверхность с идентификатором по умолчанию.</p>
* <p>Текстурные координаты у геосферы не находятся в промежутке <code>[0, 1]</code>,
* поэтому для материала с текстурой необходимо устанавливать флаг repeat.</p>
*
* @param radius радиус геосферы. Не может быть меньше нуля.
* @param segments количество сегментов геосферы
* @param reverse флаг направления нормалей. При значении <code>true</code> нормали направлены внуть геосферы.
*/
public function GeoSphere(radius:Number = 100, segments:uint = 2, reverse:Boolean = false) {
if (segments == 0) {
return;
}
radius = (radius < 0)? 0 : radius;
const sections:uint = 20;
//var nfaces:uint = sections*segments*segments;
//var nverts:Number = nfaces/2 + 2;
var points:Array = new Array();
var i:uint;
var f:uint;
var theta:Number;
var sin:Number;
var cos:Number;
// z расстояние до нижней и верхней крышки полюса
var subz:Number = 4.472136E-001*radius;
// радиус на расстоянии subz
var subrad:Number = 2*subz;
points.push(createVertex(0, 0, radius, "poleUp"));
// Создание вершин верхней крышки
for (i = 0; i < 5; i++) {
theta = MathUtils.DEG360*i/5;
sin = Math.sin(theta);
cos = Math.cos(theta);
points.push(createVertex(subrad*cos, subrad*sin, subz));
}
// Создание вершин нижней крышки
for (i = 0; i < 5; i++) {
theta = MathUtils.DEG180*((i << 1) + 1)/5;
sin = Math.sin(theta);
cos = Math.cos(theta);
points.push(createVertex(subrad*cos, subrad*sin, -subz));
}
points.push(createVertex(0, 0, -radius, "poleDown"));
for (i = 1; i < 6; i++) {
interpolate(0, i, segments, points);
}
for (i = 1; i < 6; i++) {
interpolate(i, i % 5 + 1, segments, points);
}
for (i = 1; i < 6; i++) {
interpolate(i, i + 5, segments, points);
}
for (i = 1; i < 6; i++) {
interpolate(i, (i + 3) % 5 + 6, segments, points);
}
for (i = 1; i < 6; i++) {
interpolate(i + 5, i % 5 + 6, segments, points);
}
for (i = 6; i < 11; i++) {
interpolate(11, i, segments, points);
}
for (f = 0; f < 5; f++) {
for (i = 1; i <= segments - 2; i++) {
interpolate(12 + f*(segments - 1) + i, 12 + (f + 1) % 5*(segments - 1) + i, i + 1, points);
}
}
for (f = 0; f < 5; f++) {
for (i = 1; i <= segments - 2; i++) {
interpolate(12 + (f + 15)*(segments - 1) + i, 12 + (f + 10)*(segments - 1) + i, i + 1, points);
}
}
for (f = 0; f < 5; f++) {
for (i = 1; i <= segments - 2; i++) {
interpolate(12 + ((f + 1) % 5 + 15)*(segments - 1) + segments - 2 - i, 12 + (f + 10)*(segments - 1) + segments - 2 - i, i + 1, points);
}
}
for (f = 0; f < 5; f++) {
for (i = 1; i <= segments - 2; i++) {
interpolate(12 + ((f + 1) % 5 + 25)*(segments - 1) + i, 12 + (f + 25)*(segments - 1) + i, i + 1, points);
}
}
// Создание граней
var face:Face;
var surface:Surface = createSurface();
for (f = 0; f < sections; f++) {
for (var row:uint = 0; row < segments; row++) {
for (var column:uint = 0; column <= row; column++) {
var a:uint = findVertices(segments, f, row, column);
var b:uint = findVertices(segments, f, row + 1, column);
var c:uint = findVertices(segments, f, row + 1, column + 1);
var va:Vertex = points[a];
var vb:Vertex = points[b];
var vc:Vertex = points[c];
var aUV:Point;
var bUV:Point;
var cUV:Point;
var coordA:Point3D = va._coords;
var coordB:Point3D = vb._coords;
var coordC:Point3D = vc._coords;
if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) {
aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
} else {
aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
}
if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) {
bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
} else {
bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
}
if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) {
cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
} else {
cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
}
// полюс
if (a == 0 || a == 11) {
aUV.x = bUV.x + (cUV.x - bUV.x)*0.5;
}
if (b == 0 || b == 11) {
bUV.x = aUV.x + (cUV.x - aUV.x)*0.5;
}
if (c == 0 || c == 11) {
cUV.x = aUV.x + (bUV.x - aUV.x)*0.5;
}
if (reverse) {
face = createFace([va, vc, vb], (column << 1) + "_" + row + "_" + f);
aUV.x = 1 - aUV.x;
bUV.x = 1 - bUV.x;
cUV.x = 1 - cUV.x;
setUVsToFace(aUV, cUV, bUV, face);
} else {
face = createFace([va, vb, vc], (column << 1) + "_" + row + "_" + f);
setUVsToFace(aUV, bUV, cUV, face);
}
surface.addFace(face);
//trace(a + "_" + b + "_" + c);
if (column < row) {
b = findVertices(segments, f, row, column + 1);
var vd:Vertex = points[b];
coordB = vd._coords;
if (coordA.y >= 0 && (coordA.x < 0) && (coordB.y < 0 || coordC.y < 0)) {
aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 - 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
} else {
aUV = new Point(Math.atan2(coordA.y, coordA.x)/MathUtils.DEG360 + 0.5, Math.asin(coordA.z/radius)/MathUtils.DEG180 + 0.5);
}
if (coordB.y >= 0 && (coordB.x < 0) && (coordA.y < 0 || coordC.y < 0)) {
bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 - 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
} else {
bUV = new Point(Math.atan2(coordB.y, coordB.x)/MathUtils.DEG360 + 0.5, Math.asin(coordB.z/radius)/MathUtils.DEG180 + 0.5);
}
if (coordC.y >= 0 && (coordC.x < 0) && (coordA.y < 0 || coordB.y < 0)) {
cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 - 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
} else {
cUV = new Point(Math.atan2(coordC.y, coordC.x)/MathUtils.DEG360 + 0.5, Math.asin(coordC.z/radius)/MathUtils.DEG180 + 0.5);
}
if (a == 0 || a == 11) {
aUV.x = bUV.x + (cUV.x - bUV.x)*0.5;
}
if (b == 0 || b == 11) {
bUV.x = aUV.x + (cUV.x - aUV.x)*0.5;
}
if (c == 0 || c == 11) {
cUV.x = aUV.x + (bUV.x - aUV.x)*0.5;
}
if (reverse) {
face = createFace([va, vd, vc], ((column << 1) + 1) + "_" + row + "_" + f);
aUV.x = 1 - aUV.x;
bUV.x = 1 - bUV.x;
cUV.x = 1 - cUV.x;
setUVsToFace(aUV, bUV, cUV, face);
} else {
face = createFace([va, vc, vd], ((column << 1) + 1) + "_" + row + "_" + f);
setUVsToFace(aUV, cUV, bUV, face);
}
surface.addFace(face);
}
}
}
}
}
/* private function getUVSpherical(point:Point3D, radius:Number = 0, reverse:Boolean = false):Point {
if (radius == 0) {
radius = point.length;
}
if (reverse) {
var u:Number = 0.5 - Math.atan2(point.y, point.x)/MathUtils.DEG360;
} else {
u = Math.atan2(point.y, point.x)/MathUtils.DEG360 + 0.5;
}
return new Point(u, Math.asin(point.z/radius)/MathUtils.DEG180 + 0.5);
}
*/
private function interpolate(v1:uint, v2:uint, num:uint, points:Array):void {
if (num < 2) {
return;
}
var a:Vertex = Vertex(points[v1]);
var b:Vertex = Vertex(points[v2]);
var cos:Number = (a.x*b.x + a.y*b.y + a.z*b.z)/(a.x*a.x + a.y*a.y + a.z*a.z);
cos = (cos < -1) ? -1 : ((cos > 1) ? 1 : cos);
var theta:Number = Math.acos(cos);
var sin:Number = Math.sin(theta);
for (var e:uint = 1; e < num; e++) {
var theta1:Number = theta*e/num;
var theta2:Number = theta*(num - e)/num;
var st1:Number = Math.sin(theta1);
var st2:Number = Math.sin(theta2);
points.push(createVertex((a.x*st2 + b.x*st1)/sin, (a.y*st2 + b.y*st1)/sin, (a.z*st2 + b.z*st1)/sin));
}
}
private function findVertices(segments:uint, section:uint, row:uint, column:uint):uint {
if (row == 0) {
if (section < 5) {
return (0);
}
if (section > 14) {
return (11);
}
return (section - 4);
}
if (row == segments && column == 0) {
if (section < 5) {
return (section + 1);
}
if (section < 10) {
return ((section + 4) % 5 + 6);
}
if (section < 15) {
return ((section + 1) % 5 + 1);
}
return ((section + 1) % 5 + 6);
}
if (row == segments && column == segments) {
if (section < 5) {
return ((section + 1) % 5 + 1);
}
if (section < 10) {
return (section + 1);
}
if (section < 15) {
return (section - 9);
}
return (section - 9);
}
if (row == segments) {
if (section < 5) {
return (12 + (5 + section)*(segments - 1) + column - 1);
}
if (section < 10) {
return (12 + (20 + (section + 4) % 5)*(segments - 1) + column - 1);
}
if (section < 15) {
return (12 + (section - 5)*(segments - 1) + segments - 1 - column);
}
return (12 + (5 + section)*(segments - 1) + segments - 1 - column);
}
if (column == 0) {
if (section < 5) {
return (12 + section*(segments - 1) + row - 1);
}
if (section < 10) {
return (12 + (section % 5 + 15)*(segments - 1) + row - 1);
}
if (section < 15) {
return (12 + ((section + 1) % 5 + 15)*(segments - 1) + segments - 1 - row);
}
return (12 + ((section + 1) % 5 + 25)*(segments - 1) + row - 1);
}
if (column == row) {
if (section < 5) {
return (12 + (section + 1) % 5*(segments - 1) + row - 1);
}
if (section < 10) {
return (12 + (section % 5 + 10)*(segments - 1) + row - 1);
}
if (section < 15) {
return (12 + (section % 5 + 10)*(segments - 1) + segments - row - 1);
}
return (12 + (section % 5 + 25)*(segments - 1) + row - 1);
}
return (12 + 30*(segments - 1) + section*(segments - 1)*(segments - 2)/2 + (row - 1)*(row - 2)/2 + column - 1);
}
/**
* @inheritDoc
*/
protected override function createEmptyObject():Object3D {
return new GeoSphere(0, 0);
}
/**
* @inheritDoc
*/
override protected function defaultName():String {
return "geoSphere" + ++counter;
}
}
}

View File

@@ -0,0 +1,129 @@
package alternativa.engine3d.primitives {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Surface;
import flash.geom.Point;
use namespace alternativa3d;
/**
* Плоскость.
*/
public class Plane extends Mesh {
// Инкремент количества объектов
private static var counter:uint = 0;
/**
* Создает плоскость.
* <p>Примитив после создания содержит в cебе одну или две поверхности, в зависимости от значения параметров.
* При значении <code>reverse</code> установленном в <code>true</code> примитив будет содержать грань - <code>"back"</code>.
* При значении <code>reverse</code> установленном в <code>false</code> примитив будет содержать грань - <code>"front"</code>.
* Параметр <code>twoSided</code> указывает методу создать обе поверхности.</p>
*
* @param width ширина. Размерность по оси Х. Не может быть меньше нуля.
* @param length длина. Размерность по оси Y. Не может быть меньше нуля.
* @param widthSegments количество сегментов по ширине
* @param lengthSegments количество сегментов по длине
* @param twoSided если значении параметра равно <code>true</code>, то формируется двусторонняя плоскость
* @param reverse инвертирование нормалей
* @param triangulate флаг триангуляции. Если указано значение <code>true</code>, четырехугольники в плоскости будут триангулированы.
*/
public function Plane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false, triangulate:Boolean = false) {
super();
if ((widthSegments == 0) || (lengthSegments == 0)) {
return;
}
width = (width < 0)? 0 : width;
length = (length < 0)? 0 : length;
// Середина
var wh:Number = width/2;
var lh:Number = length/2;
// Размеры сегмента
var ws:Number = width/widthSegments;
var ls:Number = length/lengthSegments;
// Размеры UV-сегмента
var wd:Number = 1/widthSegments;
var ld:Number = 1/lengthSegments;
// Создание точек и UV
var x:int;
var y:int;
var uv:Array = new Array();
for (y = 0; y <= lengthSegments; y++) {
uv[y] = new Array();
for (x = 0; x <= widthSegments; x++) {
uv[y][x] = new Point(x*wd, y*ld);
createVertex(x*ws - wh, y*ls - lh, 0, x+"_"+y);
}
}
// Создание поверхностей
var front:Surface;
var back:Surface;
if (twoSided || !reverse) {
front = createSurface(null, "front");
}
if (twoSided || reverse) {
back = createSurface(null, "back");
}
// Создание полигонов
for (y = 0; y < lengthSegments; y++) {
for (x = 0; x < widthSegments; x++) {
if (twoSided || !reverse) {
if (triangulate) {
createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1)], "front" + x + "_" + y + ":0");
setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y + ":0");
createFace([(x + 1) + "_" + (y + 1), x + "_" + (y + 1), x + "_" + y], "front" + x + "_" + y + ":1");
setUVsToFace(uv[y + 1][x + 1], uv[y + 1][x], uv[y][x], "front" + x + "_" + y + ":1");
front.addFace("front" + x + "_" + y + ":0");
front.addFace("front" + x + "_" + y + ":1");
} else {
createFace([x + "_" + y, (x + 1) + "_" + y, (x + 1) + "_" + (y + 1), x + "_" + (y + 1)], "front" + x + "_" + y);
setUVsToFace(uv[y][x], uv[y][x + 1], uv[y + 1][x + 1], "front" + x + "_" + y);
front.addFace("front" + x + "_" + y);
}
}
if (twoSided || reverse) {
if (triangulate) {
createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1)], "back" + x + "_" + y + ":0");
setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y + ":0");
createFace([(x + 1) + "_" + (y + 1), (x + 1) + "_" + y, x + "_" + y], "back" + x + "_" + y + ":1");
setUVsToFace(uv[lengthSegments - y - 1][x + 1], uv[lengthSegments - y][x + 1], uv[lengthSegments - y][x], "back" + x + "_" + y + ":1");
back.addFace("back" + x + "_" + y + ":0");
back.addFace("back"+x+"_"+y + ":1");
} else {
createFace([x + "_" + y, x + "_" + (y + 1), (x + 1) + "_" + (y + 1), (x + 1) + "_" + y], "back" + x + "_" + y);
setUVsToFace(uv[lengthSegments - y][x], uv[lengthSegments - y - 1][x], uv[lengthSegments - y - 1][x + 1], "back" + x + "_" + y);
back.addFace("back" + x + "_" + y);
}
}
}
}
}
/**
* @inheritDoc
*/
protected override function createEmptyObject():Object3D {
return new Plane(0, 0, 0);
}
/**
* @inheritDoc
*/
override protected function defaultName():String {
return "plane" + ++counter;
}
}
}

View File

@@ -0,0 +1,157 @@
package alternativa.engine3d.primitives {
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.utils.MathUtils;
import flash.geom.Point;
use namespace alternativa3d;
/**
* Сфера.
*/
public class Sphere extends Mesh {
// Инкремент количества объектов
private static var counter:uint = 0;
/**
* Создает сферу.
* <p>После создания примитив содержит в себе одну поверхность с идентификатором по умолчанию.</p>
* <p>По умолчанию параметр <code>triangulate</code> установлен в <code>false</code> и на сферу нельзя наложить текстуру.
* Только при установленном <code>triangulate</code> в <code>true</code> это возможно.</p>
*
* @param radius Радиус сферы. Не может быть меньше нуля.
* @param radialSegments количество сегментов по экватору сферы
* @param heightSegments количество сегментов по высоте
* @param reverse флаг инвертирования нормалей. При параметре установленном в <code>true</code> нормали направлены внутрь сферы.
* @param triangulate флаг триангуляции. Если указано значение <code>true</code>, грани будут триангулированы,
* и будет возможно наложить на примитив текстуру.
*/
public function Sphere(radius:Number = 100, radialSegments:uint = 8, heightSegments:uint = 8, reverse:Boolean = false, triangulate:Boolean = false) {
if ((radialSegments < 3) || (heightSegments < 2)) {
return;
}
radius = (radius < 0)? 0 : radius;
var poleUp:Vertex = createVertex(0, 0, radius, "poleUp");
var poleDown:Vertex = createVertex(0, 0, -radius, "poleDown");
const radialAngle:Number = MathUtils.DEG360/radialSegments;
const heightAngle:Number = MathUtils.DEG360/(heightSegments << 1);
var radial:uint;
var segment:uint;
// Создание вершин
for (segment = 1; segment < heightSegments; segment++) {
var currentHeightAngle:Number = heightAngle*segment;
var segmentRadius:Number = Math.sin(currentHeightAngle)*radius;
var segmentZ:Number = Math.cos(currentHeightAngle)*radius;
for (radial = 0; radial < radialSegments; radial++) {
var currentRadialAngle:Number = radialAngle*radial;
createVertex(-Math.sin(currentRadialAngle)*segmentRadius, Math.cos(currentRadialAngle)*segmentRadius, segmentZ, radial + "_" + segment);
}
}
// Создание граней и поверхности
var surface:Surface = createSurface();
var prevRadial:uint = radialSegments - 1;
var lastSegmentString:String = "_" + (heightSegments - 1);
var uStep:Number = 1/radialSegments;
var vStep:Number = 1/heightSegments;
var face:Face;
// Для триангуляции
var aUV:Point;
var cUV:Point;
var u:Number;
if (reverse) {
for (radial = 0; radial < radialSegments; radial++) {
// Грани верхнего полюса
surface.addFace(createFace([poleUp, radial + "_1", prevRadial + "_1"], prevRadial + "_0"));
// Грани нижнего полюса
surface.addFace(createFace([radial + lastSegmentString, poleDown, prevRadial + lastSegmentString], prevRadial + lastSegmentString));
// Если включена триангуляция
if (triangulate) {
// Триангулируем середину и просчитываем маппинг
u = uStep*prevRadial;
setUVsToFace(new Point(1 - u, 1), new Point(1 - u - uStep, 1 - vStep), new Point(1 - u, 1 - vStep), prevRadial + "_0");
// Грани середки
for (segment = 1; segment < (heightSegments - 1); segment++) {
aUV = new Point(1 - u - uStep, 1 - (vStep*(segment + 1)));
cUV = new Point(1 - u, 1 - vStep*segment);
surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment], prevRadial + "_" + segment + ":0"));
surface.addFace(createFace([prevRadial + "_" + segment, radial + "_" + segment, radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":1"));
setUVsToFace(aUV, new Point(1 - u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0");
setUVsToFace(cUV, new Point(1 - u - uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1");
}
setUVsToFace(new Point(1 - u - uStep, vStep), new Point(1 - u, 0), new Point(1 - u, vStep), prevRadial + lastSegmentString);
} else {
// Просто создаем середину
// Грани середки
for (segment = 1; segment < (heightSegments - 1); segment++) {
surface.addFace(createFace([radial + "_" + (segment + 1), prevRadial + "_" + (segment + 1), prevRadial + "_" + segment, radial + "_" + segment], prevRadial + "_" + segment));
}
}
prevRadial = (radial == 0) ? 0 : prevRadial + 1;
}
} else {
for (radial = 0; radial < radialSegments; radial++) {
// Грани верхнего полюса
surface.addFace(createFace([poleUp, prevRadial + "_1", radial + "_1"], prevRadial + "_0"));
// Грани нижнего полюса
surface.addFace(createFace([prevRadial + lastSegmentString, poleDown, radial + lastSegmentString], prevRadial + lastSegmentString));
if (triangulate) {
u = uStep*prevRadial;
setUVsToFace(new Point(u, 1), new Point(u, 1 - vStep), new Point(u + uStep, 1 - vStep), prevRadial + "_0");
// Грани середки
for (segment = 1; segment < (heightSegments - 1); segment++) {
aUV = new Point(u, 1 - (vStep*segment));
cUV = new Point(u + uStep, 1 - vStep * (segment + 1));
surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1)], prevRadial + "_" + segment + ":0"));
surface.addFace(createFace([radial + "_" + (segment + 1), radial + "_" + segment, prevRadial + "_" + segment], prevRadial + "_" + segment + ":1"));
setUVsToFace(aUV, new Point(u, 1 - (vStep*(segment + 1))), cUV, prevRadial + "_" + segment + ":0");
setUVsToFace(cUV, new Point(u + uStep, 1 - (vStep*segment)), aUV, prevRadial + "_" + segment + ":1");
}
setUVsToFace(new Point(u, vStep), new Point(u, 0), new Point(u + uStep, vStep), prevRadial + lastSegmentString);
} else {
// Грани середки
for (segment = 1; segment < (heightSegments - 1); segment++) {
surface.addFace(createFace([prevRadial + "_" + segment, prevRadial + "_" + (segment + 1), radial + "_" + (segment + 1), radial + "_" + segment], prevRadial + "_" + segment));
}
}
prevRadial = (radial == 0) ? 0 : prevRadial + 1;
}
}
}
/**
* @inheritDoc
*/
protected override function createEmptyObject():Object3D {
return new Sphere(0, 0);
}
/**
* @inheritDoc
*/
override protected function defaultName():String {
return "sphere" + ++counter;
}
}
}

View File

@@ -0,0 +1,843 @@
package alternativa.utils {
import alternativa.engine3d.*;
import alternativa.engine3d.core.Face;
import alternativa.engine3d.core.Mesh;
import alternativa.engine3d.core.Surface;
import alternativa.engine3d.core.Vertex;
import alternativa.engine3d.materials.FillMaterial;
import alternativa.engine3d.materials.SurfaceMaterial;
import alternativa.engine3d.materials.TextureMaterial;
import alternativa.engine3d.materials.TextureMaterialPrecision;
import alternativa.engine3d.materials.WireMaterial;
import alternativa.types.*;
import flash.display.BlendMode;
import flash.geom.Point;
use namespace alternativa3d;
use namespace alternativatypes;
/**
* Утилиты для работы с Mesh-объектами.
*/
public class MeshUtils {
static private var verticesSort:Array = ["x", "y", "z"];
static private var verticesSortOptions:Array = [Array.NUMERIC, Array.NUMERIC, Array.NUMERIC];
/**
* Объединение нескольких Mesh-объектов. Объекты, переданные как аргументы метода, не изменяются.
*
* @param meshes объединяемые объекты класса <code>alternativa.engine3d.core.Mesh</code>
*
* @return новый Mesh-объект, содержащий результат объединения переданных Mesh-объектов
*/
static public function uniteMeshes(... meshes):Mesh {
var res:Mesh = new Mesh();
var length:uint = meshes.length;
var key:*;
var vertex:Vertex;
var face:Face;
var j:uint;
for (var i:uint = 0; i < length; i++) {
var mesh:Mesh = meshes[i];
var vertices:Map = mesh._vertices.clone();
for (key in vertices) {
vertex = vertices[key];
vertices[key] = res.createVertex(vertex.x, vertex.y, vertex.z);
}
var faces:Map = mesh._faces.clone();
for (key in faces) {
face = faces[key];
var faceVertices:Array = new Array().concat(face._vertices);
for (j = 0; j < face._verticesCount; j++) {
vertex = faceVertices[j];
faceVertices[j] = vertices[vertex.id];
}
faces[key] = res.createFace(faceVertices);
res.setUVsToFace(face._aUV, face._bUV, face._cUV, faces[key]);
}
for (key in mesh._surfaces) {
var surface:Surface = mesh._surfaces[key];
var surfaceFaces:Array = surface._faces.toArray();
var numFaces:uint = surfaceFaces.length;
for (j = 0; j < numFaces; j++) {
face = surfaceFaces[j];
surfaceFaces[j] = faces[face.id];
}
var newSurface:Surface = res.createSurface(surfaceFaces);
newSurface.material = SurfaceMaterial(surface.material.clone());
}
}
return res;
}
/**
* Слияние вершин Mesh-объекта с одинаковыми координатами. Равенство координат проверяется с учётом погрешности.
*
* @param mesh объект, вершины которого объединяются
* @param threshold погрешность измерения расстояний
*/
static public function autoWeldVertices(mesh:Mesh, threshold:Number = 0):void {
// Получаем список вершин меша и сортируем по координатам
var vertices:Array = mesh._vertices.toArray(true);
vertices.sortOn(verticesSort, verticesSortOptions);
// Поиск вершин с одинаковыми координатами
var weld:Map = new Map(true);
var vertex:Vertex;
var currentVertex:Vertex = vertices[0];
var length:uint = vertices.length;
var i:uint;
for (i = 1; i < length; i++) {
vertex = vertices[i];
if ((currentVertex.x - vertex.x <= threshold) && (currentVertex.x - vertex.x >= -threshold) && (currentVertex.y - vertex.y <= threshold) && (currentVertex.y - vertex.y >= -threshold) && (currentVertex.z - vertex.z <= threshold) && (currentVertex.z - vertex.z >= -threshold)) {
weld[vertex] = currentVertex;
} else {
currentVertex = vertex;
}
}
// Собираем грани объединяемых вершин
var faces:Set = new Set(true);
var keyVertex:*;
var keyFace:*;
for (keyVertex in weld) {
vertex = keyVertex;
for (keyFace in vertex._faces) {
faces[keyFace] = true;
}
}
// Заменяем грани
for (keyFace in faces) {
var face:Face = keyFace;
var id:Object = mesh.getFaceId(face);
var surface:Surface = face._surface;
var aUV:Point = face._aUV;
var bUV:Point = face._bUV;
var cUV:Point = face._cUV;
vertices = new Array().concat(face._vertices);
length = vertices.length;
for (i = 0; i < length; i++) {
vertex = weld[vertices[i]];
if (vertex != null) {
vertices[i] = vertex;
}
}
mesh.removeFace(face);
face = mesh.createFace(vertices, id);
if (surface != null) {
surface.addFace(face);
}
face.aUV = aUV;
face.bUV = bUV;
face.cUV = cUV;
}
// Удаляем вершины
for (keyVertex in weld) {
mesh.removeVertex(keyVertex);
}
}
/**
* Объединение соседних граней, образующих плоский выпуклый многоугольник.
*
* @param mesh объект, грани которого объединяются
* @param angleThreshold погрешность измерения углов
* @param uvThreshold погрешность измерения UV-координат
* @param ignoreLineJoints значение <code>true</code> запрещает объединения, в результате которых два соседних ребра оказываются на одной линии.
* Например, если флаг включен, два прямоугольника с общим ребром не объединятся.
*/
static public function autoWeldFaces(mesh:Mesh, angleThreshold:Number = 0, uvThreshold:Number = 0, ignoreLineJoints:Boolean = false):void {
angleThreshold = Math.cos(angleThreshold);
var digitThreshold:Number = 0.001;
var vertex:Vertex;
var face:Face;
var sibling:Face;
var key:*;
var i:uint;
var normal:Point3D;
// Формируем списки граней
var faces1:Set = new Set(true);
var faces2:Set = new Set(true);
// Формируем список нормалей
var normals:Map = new Map(true);
for each (face in mesh._faces.clone()) {
normal = new Point3D();
vertex = face._vertices[0];
var av:Point3D = vertex._coords;
vertex = face._vertices[1];
var abx:Number = vertex._coords.x - av.x;
var aby:Number = vertex._coords.y - av.y;
var abz:Number = vertex._coords.z - av.z;
vertex = face._vertices[2];
var acx:Number = vertex._coords.x - av.x;
var acy:Number = vertex._coords.y - av.y;
var acz:Number = vertex._coords.z - av.z;
normal.x = acz*aby - acy*abz;
normal.y = acx*abz - acz*abx;
normal.z = acy*abx - acx*aby;
var normalLength:Number = Math.sqrt(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z);
if (normalLength > digitThreshold) {
normal.x /= normalLength;
normal.y /= normalLength;
normal.z /= normalLength;
faces1[face] = true;
normals[face] = normal;
} else {
mesh.removeFace(face);
}
}
// Объединение
do {
// Флаг объединения
var weld:Boolean = false;
// Объединяем грани
while ((face = faces1.take()) != null) {
//var num:uint = face.num;
//var vertices:Array = face.vertices;
var currentWeld:Boolean = false;
// Проверка общих граней по точкам
// Проверка общих граней по рёбрам
// Перебираем точки грани
for (i = 0; (i < face._verticesCount) && !currentWeld; i++) {
var faceIndex1:uint = i;
var faceIndex2:uint;
var siblingIndex1:int;
var siblingIndex2:uint;
// Перебираем грани текущей точки
vertex = face._vertices[faceIndex1];
var vertexFaces:Set = vertex.faces;
for (key in vertexFaces) {
sibling = key;
// Если грань в списке на объединение и в одной поверхности
if (faces1[sibling] && face._surface == sibling._surface) {
faceIndex2 = (faceIndex1 < face._verticesCount - 1) ? (faceIndex1 + 1) : 0;
siblingIndex1 = sibling._vertices.indexOf(face._vertices[faceIndex2]);
// Если общее ребро
if (siblingIndex1 >= 0) {
// Если грани сонаправлены
normal = normals[face];
if (Point3D.dot(normal, normals[sibling]) >= angleThreshold) {
// Если в точках объединения нет перегибов
siblingIndex2 = (siblingIndex1 < sibling._verticesCount - 1) ? (siblingIndex1 + 1) : 0;
// Расширяем грани объединения
var i1:uint;
var i2:uint;
while (true) {
i1 = (faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1);
i2 = (siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0;
if (face._vertices[i1] == sibling._vertices[i2]) {
faceIndex1 = i1;
siblingIndex2 = i2;
} else {
break;
}
}
while (true) {
i1 = (faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0;
i2 = (siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1);
if (face._vertices[i1] == sibling._vertices[i2]) {
faceIndex2 = i1;
siblingIndex1 = i2;
} else {
break;
}
}
vertex = face._vertices[faceIndex1];
var a:Point3D = vertex.coords;
vertex = face._vertices[faceIndex2];
var b:Point3D = vertex.coords;
// Считаем первый перегиб
vertex = sibling._vertices[(siblingIndex2 < sibling._verticesCount - 1) ? (siblingIndex2 + 1) : 0];
var c:Point3D = vertex.coords;
vertex = face._vertices[(faceIndex1 > 0) ? (faceIndex1 - 1) : (face._verticesCount - 1)];
var d:Point3D = vertex.coords;
var cx:Number = c.x - a.x;
var cy:Number = c.y - a.y;
var cz:Number = c.z - a.z;
var dx:Number = d.x - a.x;
var dy:Number = d.y - a.y;
var dz:Number = d.z - a.z;
var crossX:Number = cy*dz - cz*dy;
var crossY:Number = cz*dx - cx*dz;
var crossZ:Number = cx*dy - cy*dx;
var zeroCross:Boolean = crossX < digitThreshold && crossX > -digitThreshold && crossY < digitThreshold && crossY > -digitThreshold && crossZ < digitThreshold && crossZ > -digitThreshold;
if (zeroCross && (cx*dx + cy*dy + cz*dz > 0 || ignoreLineJoints) || !zeroCross && crossX*normal.x + crossY*normal.y + crossZ*normal.z < 0) {
break;
}
// Считаем второй перегиб
vertex = face._vertices[(faceIndex2 < face._verticesCount - 1) ? (faceIndex2 + 1) : 0];
c = vertex.coords;
vertex = sibling._vertices[(siblingIndex1 > 0) ? (siblingIndex1 - 1) : (sibling._verticesCount - 1)];
d = vertex.coords;
cx = c.x - b.x;
cy = c.y - b.y;
cz = c.z - b.z;
dx = d.x - b.x;
dy = d.y - b.y;
dz = d.z - b.z;
crossX = cy*dz - cz*dy;
crossY = cz*dx - cx*dz;
crossZ = cx*dy - cy*dx;
zeroCross = crossX < digitThreshold && crossX > -digitThreshold && crossY < digitThreshold && crossY > -digitThreshold && crossZ < digitThreshold && crossZ > -digitThreshold;
if (zeroCross && (cx*dx + cy*dy + cz*dz > 0 || ignoreLineJoints) || !zeroCross && crossX*normal.x + crossY*normal.y + crossZ*normal.z < 0) {
break;
}
// Флаг наличия UV у обеих граней
var hasUV:Boolean = (face._aUV != null && face._bUV != null && face._cUV != null && sibling._aUV != null && sibling._bUV != null && sibling._cUV != null);
if (hasUV || (face._aUV == null && face._bUV == null && face._cUV == null && sibling._aUV == null && sibling._bUV == null && sibling._cUV == null)) {
// Если грани имеют UV, проверяем совместимость
if (hasUV) {
vertex = sibling._vertices[0];
var uv:Point = face.getUVFast(vertex.coords, normal);
if ((uv.x - sibling._aUV.x > uvThreshold) || (uv.x - sibling._aUV.x < -uvThreshold) || (uv.y - sibling._aUV.y > uvThreshold) || (uv.y - sibling._aUV.y < -uvThreshold)) {
break;
}
vertex = sibling._vertices[1];
uv = face.getUVFast(vertex.coords, normal);
if ((uv.x - sibling._bUV.x > uvThreshold) || (uv.x - sibling._bUV.x < -uvThreshold) || (uv.y - sibling._bUV.y > uvThreshold) || (uv.y - sibling._bUV.y < -uvThreshold)) {
break;
}
vertex = sibling._vertices[2];
uv = face.getUVFast(vertex.coords, normal);
if ((uv.x - sibling._cUV.x > uvThreshold) || (uv.x - sibling._cUV.x < -uvThreshold) || (uv.y - sibling._cUV.y > uvThreshold) || (uv.y - sibling._cUV.y < -uvThreshold)) {
break;
}
}
// Формируем новую грань
var newVertices:Array = new Array();
var n:uint = faceIndex2;
do {
newVertices.push(face._vertices[n]);
n = (n < face._verticesCount - 1) ? (n + 1) : 0;
} while (n != faceIndex1);
n = siblingIndex2;
do {
newVertices.push(sibling._vertices[n]);
n = (n < sibling._verticesCount - 1) ? (n + 1) : 0;
} while (n != siblingIndex1);
// Выбираем начальную точку
n = getBestBeginVertexIndex(newVertices);
for (var m:uint = 0; m < n; m++) {
newVertices.push(newVertices.shift());
}
// Заменяем грани новой
var surface:Surface = face._surface;
var newFace:Face = mesh.createFace(newVertices);
if (hasUV) {
newFace.aUV = face.getUVFast(newVertices[0].coords, normal);
newFace.bUV = face.getUVFast(newVertices[1].coords, normal);
newFace.cUV = face.getUVFast(newVertices[2].coords, normal);
}
if (surface != null) {
surface.addFace(newFace);
}
mesh.removeFace(face);
mesh.removeFace(sibling);
// Обновляем список нормалей
delete normals[sibling];
delete normals[face];
normals[newFace] = newFace.normal;
// Обновляем списки расчётов
delete faces1[sibling];
faces2[newFace] = true;
// Помечаем объединение
weld = true;
currentWeld = true;
break;
}
}
}
}
}
}
// Если не удалось объединить, переносим грань
faces2[face] = true;
}
// Меняем списки
var fs:Set = faces1;
faces1 = faces2;
faces2 = fs;
} while (weld);
removeIsolatedVertices(mesh);
removeUselessVertices(mesh);
}
/**
* Удаление вершин объекта, не принадлежащим ни одной грани.
*
* @param mesh объект, вершины которого удаляются
*/
static public function removeIsolatedVertices(mesh:Mesh):void {
for each (var vertex:Vertex in mesh._vertices.clone()) {
if (vertex._faces.isEmpty()) {
mesh.removeVertex(vertex);
}
}
}
/**
* Удаление вершин объекта, которые во всех своих гранях лежат на отрезке между предыдущей и следующей вершиной.
*
* @param mesh объект, вершины которого удаляются
*/
static public function removeUselessVertices(mesh:Mesh):void {
var digitThreshold:Number = 0.001;
var v:Vertex;
var key:*;
var face:Face;
var index:uint;
var length:uint;
for each (var vertex:Vertex in mesh._vertices.clone()) {
var useless:Boolean = true;
var indexes:Map = new Map(true);
for (key in vertex._faces) {
face = key;
length = face._vertices.length;
index = face._vertices.indexOf(vertex);
v = face._vertices[index];
var a:Point3D = v.coords;
v = face._vertices[(index < length - 1) ? (index + 1) : 0];
var b:Point3D = v.coords;
v = face._vertices[(index > 0) ? (index - 1) : (length - 1)];
var c:Point3D = v.coords;
var abx:Number = b.x - a.x;
var aby:Number = b.y - a.y;
var abz:Number = b.z - a.z;
var acx:Number = c.x - a.x;
var acy:Number = c.y - a.y;
var acz:Number = c.z - a.z;
var crossX:Number = aby*acz - abz*acy;
var crossY:Number = abz*acx - abx*acz;
var crossZ:Number = abx*acy - aby*acx;
if (crossX < digitThreshold && crossX > -digitThreshold && crossY < digitThreshold && crossY > -digitThreshold && crossZ < digitThreshold && crossZ > -digitThreshold) {
indexes[face] = index;
} else {
useless = false;
break;
}
}
if (useless && !indexes.isEmpty()) {
// Удаляем
for (key in indexes) {
var i:uint;
face = key;
index = indexes[face];
length = face._vertices.length;
var newVertices:Array = new Array();
for (i = 0; i < length; i++) {
if (i != index) {
newVertices.push(face._vertices[i]);
}
}
var n:uint = getBestBeginVertexIndex(newVertices);
for (i = 0; i < n; i++) {
newVertices.push(newVertices.shift());
}
var surface:Surface = face._surface;
var newFace:Face = mesh.createFace(newVertices);
if (face._aUV != null && face._bUV != null && face._cUV != null) {
var normal:Point3D = face.normal;
newFace.aUV = face.getUVFast(newVertices[0].coords, normal);
newFace.bUV = face.getUVFast(newVertices[1].coords, normal);
newFace.cUV = face.getUVFast(newVertices[2].coords, normal);
}
if (surface != null) {
surface.addFace(newFace);
}
mesh.removeFace(face);
}
mesh.removeVertex(vertex);
}
}
}
/**
* Удаление вырожденных граней.
*
* @param mesh объект, грани которого удаляются
*/
static public function removeSingularFaces(mesh:Mesh):void {
for each (var face:Face in mesh._faces.clone()) {
var normal:Point3D = face.normal;
if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
mesh.removeFace(face);
}
}
}
/**
* @private
* Находит наиболее подходящую первую точку.
* @param vertices
* @return
*/
static public function getBestBeginVertexIndex(vertices:Array):uint {
var bestIndex:uint = 0;
var num:uint = vertices.length;
if (num > 3) {
var maxCrossLength:Number = 0;
var v:Vertex = vertices[num - 1];
var c1:Point3D = v.coords;
v = vertices[0];
var c2:Point3D = v.coords;
var prevX:Number = c2.x - c1.x;
var prevY:Number = c2.y - c1.y;
var prevZ:Number = c2.z - c1.z;
for (var i:uint = 0; i < num; i++) {
c1 = c2;
v = vertices[(i < num - 1) ? (i + 1) : 0];
c2 = v.coords;
var nextX:Number = c2.x - c1.x;
var nextY:Number = c2.y - c1.y;
var nextZ:Number = c2.z - c1.z;
var crossX:Number = prevY*nextZ - prevZ*nextY;
var crossY:Number = prevZ*nextX - prevX*nextZ;
var crossZ:Number = prevX*nextY - prevY*nextX;
var crossLength:Number = crossX*crossX + crossY*crossY + crossZ*crossZ;
if (crossLength > maxCrossLength) {
maxCrossLength = crossLength;
bestIndex = i;
}
prevX = nextX;
prevY = nextY;
prevZ = nextZ;
}
// Берём предыдущий
bestIndex = (bestIndex > 0) ? (bestIndex - 1) : (num - 1);
}
return bestIndex;
}
/**
* Генерация AS-класса.
*
* @param mesh объект, на базе которого генерируется класс
* @param packageName имя пакета для генерируемого класса
* @return AS-класс в текстовом виде
*/
static public function generateClass(mesh:Mesh, packageName:String = ""):String {
var className:String = mesh._name.charAt(0).toUpperCase() + mesh._name.substr(1);
var header:String = "package" + ((packageName != "") ? (" " + packageName + " ") : " ") + "{\r\r";
var importSet:Object = new Object();
importSet["alternativa.engine3d.core.Mesh"] = true;
var materialSet:Map = new Map(true);
var materialName:String;
var materialNum:uint = 1;
var footer:String = "\t\t}\r\t}\r}";
var classHeader:String = "\tpublic class "+ className + " extends Mesh {\r\r";
var constructor:String = "\t\tpublic function " + className + "() {\r";
constructor += "\t\t\tsuper(\"" + mesh._name +"\");\r\r";
var newLine:Boolean = false;
if (mesh.mobility != 0) {
constructor += "\t\t\tmobility = " + mesh.mobility +";\r";
newLine = true;
}
if (mesh.x != 0 && mesh.y != 0 && mesh.z != 0) {
importSet["alternativa.types.Point3D"] = true;
constructor += "\t\t\tcoords = new Point3D(" + mesh.x + ", " + mesh.y + ", " + mesh.z +");\r";
newLine = true;
} else {
if (mesh.x != 0) {
constructor += "\t\t\tx = " + mesh.x + ";\r";
newLine = true;
}
if (mesh.y != 0) {
constructor += "\t\t\ty = " + mesh.y + ";\r";
newLine = true;
}
if (mesh.z != 0) {
constructor += "\t\t\tz = " + mesh.z + ";\r";
newLine = true;
}
}
if (mesh.rotationX != 0) {
constructor += "\t\t\trotationX = " + mesh.rotationX + ";\r";
newLine = true;
}
if (mesh.rotationY != 0) {
constructor += "\t\t\trotationY = " + mesh.rotationY + ";\r";
newLine = true;
}
if (mesh.rotationZ != 0) {
constructor += "\t\t\trotationZ = " + mesh.rotationZ + ";\r";
newLine = true;
}
if (mesh.scaleX != 1) {
constructor += "\t\t\tscaleX = " + mesh.scaleX + ";\r";
newLine = true;
}
if (mesh.scaleY != 1) {
constructor += "\t\t\tscaleY = " + mesh.scaleY + ";\r";
newLine = true;
}
if (mesh.scaleZ != 1) {
constructor += "\t\t\tscaleZ = " + mesh.scaleZ + ";\r";
newLine = true;
}
constructor += newLine ? "\r" : "";
function idToString(value:*):String {
return isNaN(value) ? ("\"" + value + "\"") : value;
}
function blendModeToString(value:String):String {
switch (value) {
case BlendMode.ADD: return "BlendMode.ADD";
case BlendMode.ALPHA: return "BlendMode.ALPHA";
case BlendMode.DARKEN: return "BlendMode.DARKEN";
case BlendMode.DIFFERENCE: return "BlendMode.DIFFERENCE";
case BlendMode.ERASE: return "BlendMode.ERASE";
case BlendMode.HARDLIGHT: return "BlendMode.HARDLIGHT";
case BlendMode.INVERT: return "BlendMode.INVERT";
case BlendMode.LAYER: return "BlendMode.LAYER";
case BlendMode.LIGHTEN: return "BlendMode.LIGHTEN";
case BlendMode.MULTIPLY: return "BlendMode.MULTIPLY";
case BlendMode.NORMAL: return "BlendMode.NORMAL";
case BlendMode.OVERLAY: return "BlendMode.OVERLAY";
case BlendMode.SCREEN: return "BlendMode.SCREEN";
case BlendMode.SUBTRACT: return "BlendMode.SUBTRACT";
default: return "BlendMode.NORMAL";
}
}
function colorToString(value:uint):String {
var hex:String = value.toString(16).toUpperCase();
var res:String = "0x";
var len:uint = 6 - hex.length;
for (var j:uint = 0; j < len; j++) {
res += "0";
}
res += hex;
return res;
}
var i:uint;
var length:uint;
var key:*;
var id:String;
var face:Face;
var surface:Surface;
newLine = false;
for (id in mesh._vertices) {
var vertex:Vertex = mesh._vertices[id];
var coords:Point3D = vertex.coords;
constructor += "\t\t\tcreateVertex(" + coords.x + ", " + coords.y + ", " + coords.z + ", " + idToString(id) + ");\r";
newLine = true;
}
constructor += newLine ? "\r" : "";
newLine = false;
for (id in mesh._faces) {
face = mesh._faces[id];
length = face._verticesCount;
constructor += "\t\t\tcreateFace(["
for (i = 0; i < length - 1; i++) {
constructor += idToString(mesh.getVertexId(face._vertices[i])) + ", ";
}
constructor += idToString(mesh.getVertexId(face._vertices[i])) + "], " + idToString(id) + ");\r";
if (face._aUV != null || face._bUV != null || face._cUV != null) {
importSet["flash.geom.Point"] = true;
constructor += "\t\t\tsetUVsToFace(new Point(" + face._aUV.x + ", " + face._aUV.y + "), new Point(" + face._bUV.x + ", " + face._bUV.y + "), new Point(" + face._cUV.x + ", " + face._cUV.y + "), " + idToString(id) + ");\r";
}
newLine = true;
}
constructor += newLine ? "\r" : "";
for (id in mesh._surfaces) {
surface = mesh._surfaces[id];
var facesStr:String = "";
for (key in surface._faces) {
facesStr += idToString(mesh.getFaceId(key)) + ", ";
}
constructor += "\t\t\tcreateSurface([" + facesStr.substr(0, facesStr.length - 2) + "], " + idToString(id) + ");\r";
if (surface.material != null) {
var material:String;
var defaultAlpha:Boolean = surface.material.alpha == 1;
var defaultBlendMode:Boolean = surface.material.blendMode == BlendMode.NORMAL;
if (surface.material is WireMaterial) {
importSet["alternativa.engine3d.materials.WireMaterial"] = true;
var defaultThickness:Boolean = WireMaterial(surface.material).thickness == 0;
var defaultColor:Boolean = WireMaterial(surface.material).color == 0;
material = "new WireMaterial(";
if (!defaultThickness || !defaultColor || !defaultAlpha || !defaultBlendMode) {
material += WireMaterial(surface.material).thickness;
if (!defaultColor || !defaultAlpha || !defaultBlendMode) {
material += ", " + colorToString(WireMaterial(surface.material).color);
if (!defaultAlpha || !defaultBlendMode) {
material += ", " + surface.material.alpha ;
if (!defaultBlendMode) {
importSet["flash.display.BlendMode"] = true;
material += ", " + blendModeToString(surface.material.blendMode);
}
}
}
}
}
var defaultWireThickness:Boolean;
var defaultWireColor:Boolean;
if (surface.material is FillMaterial) {
importSet["alternativa.engine3d.materials.FillMaterial"] = true;
defaultWireThickness = FillMaterial(surface.material).wireThickness < 0;
defaultWireColor = FillMaterial(surface.material).wireColor == 0;
material = "new FillMaterial(" + colorToString(FillMaterial(surface.material).color);
if (!defaultAlpha || !defaultBlendMode || !defaultWireThickness || !defaultWireColor) {
material += ", " + surface.material.alpha;
if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor) {
importSet["flash.display.BlendMode"] = true;
material += ", " + blendModeToString(surface.material.blendMode);
if (!defaultWireThickness || !defaultWireColor) {
material += ", " + FillMaterial(surface.material).wireThickness;
if (!defaultWireColor) {
material += ", " + colorToString(FillMaterial(surface.material).wireColor);
}
}
}
}
}
if (surface.material is TextureMaterial) {
importSet["alternativa.engine3d.materials.TextureMaterial"] = true;
var defaultRepeat:Boolean = TextureMaterial(surface.material).repeat;
var defaultSmooth:Boolean = !TextureMaterial(surface.material).smooth;
defaultWireThickness = TextureMaterial(surface.material).wireThickness < 0;
defaultWireColor = TextureMaterial(surface.material).wireColor == 0;
var defaultPrecision:Boolean = TextureMaterial(surface.material).precision == TextureMaterialPrecision.MEDIUM;
if (TextureMaterial(surface.material).texture == null) {
materialName = "null";
} else {
importSet["alternativa.types.Texture"] = true;
if (materialSet[TextureMaterial(surface.material).texture] == undefined) {
materialName = (TextureMaterial(surface.material).texture._name != null) ? TextureMaterial(surface.material).texture._name : "texture" + materialNum++;
materialSet[TextureMaterial(surface.material).texture] = materialName;
} else {
materialName = materialSet[TextureMaterial(surface.material).texture];
}
materialName = materialName.split(".")[0];
}
material = "new TextureMaterial(" + materialName;
if (!defaultAlpha || !defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
material += ", " + TextureMaterial(surface.material).alpha;
if (!defaultRepeat || !defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
material += ", " + TextureMaterial(surface.material).repeat;
if (!defaultSmooth || !defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
material += ", " + TextureMaterial(surface.material).smooth;
if (!defaultBlendMode || !defaultWireThickness || !defaultWireColor || !defaultPrecision) {
importSet["flash.display.BlendMode"] = true;
material += ", " + blendModeToString(surface.material.blendMode);
if (!defaultWireThickness || !defaultWireColor || !defaultPrecision) {
material += ", " + TextureMaterial(surface.material).wireThickness;
if (!defaultWireColor || !defaultPrecision) {
material += ", " + colorToString(TextureMaterial(surface.material).wireColor);
if (!defaultPrecision) {
material += ", " + TextureMaterial(surface.material).precision;
}
}
}
}
}
}
}
}
constructor += "\t\t\tsetMaterialToSurface(" + material + "), " + idToString(id) + ");\r";
}
}
var imports:String = "";
newLine = false;
var importArray:Array = new Array();
for (key in importSet) {
importArray.push(key);
}
importArray.sort();
length = importArray.length;
for (i = 0; i < length; i++) {
var pack:String = importArray[i];
var current:String = pack.substr(0, pack.indexOf("."));
imports += (current != prev && prev != null) ? "\r" : "";
imports += "\timport " + pack + ";\r";
var prev:String = current;
newLine = true;
}
imports += newLine ? "\r" : "";
var embeds:String = "";
newLine = false;
for each (materialName in materialSet) {
var materialClassName:String = materialName.split(".")[0];
var materialBmpName:String = materialClassName.charAt(0).toUpperCase() + materialClassName.substr(1);
embeds += "\t\t[Embed(source=\"" + materialName + "\")] private static const bmp" + materialBmpName + ":Class;\r";
embeds += "\t\tprivate static const " + materialClassName + ":Texture = new Texture(new bmp" + materialBmpName + "().bitmapData, \"" + materialName + "\");\r";
newLine = true;
}
embeds += newLine ? "\r" : "";
return header + imports + classHeader + embeds + constructor + footer;
}
}
}