mirror of
https://github.com/MapMakersAndProgrammers/alternativa3d-archive.git
synced 2025-10-26 09:49:07 -07:00
Add A3D5
This commit is contained in:
14
Alternativa3D5/5.3/alternativa/Alternativa3D.as
Normal file
14
Alternativa3D5/5.3/alternativa/Alternativa3D.as
Normal file
@@ -0,0 +1,14 @@
|
||||
package alternativa {
|
||||
|
||||
/**
|
||||
* Класс содержит информацию о версии библиотеки.
|
||||
* Также используется для интеграции библиотеки в среду разработки Adobe Flash.
|
||||
*/
|
||||
public class Alternativa3D {
|
||||
|
||||
/**
|
||||
* Версия библиотеки в формате: версия.подверсия.сборка
|
||||
*/
|
||||
public static const version:String = "5.3.0";
|
||||
}
|
||||
}
|
||||
3
Alternativa3D5/5.3/alternativa/engine3d/alternativa3d.as
Normal file
3
Alternativa3D5/5.3/alternativa/engine3d/alternativa3d.as
Normal file
@@ -0,0 +1,3 @@
|
||||
package alternativa.engine3d {
|
||||
public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d";
|
||||
}
|
||||
@@ -0,0 +1,888 @@
|
||||
package alternativa.engine3d.controllers {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.Camera3D;
|
||||
import alternativa.engine3d.physics.SphereCollider;
|
||||
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.Dictionary;
|
||||
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:SphereCollider;
|
||||
// Флаг необходимости проверки столкновений
|
||||
private var _checkCollisions:Boolean;
|
||||
// Радиус сферы для определения столкновений
|
||||
private var _collisionRadius:Number = 0;
|
||||
// Набор исключаемых из проверки столкновений объектов
|
||||
private var _collisionIgnoreSet:Set = new Set(true);
|
||||
// Флаг движения
|
||||
private var _isMoving:Boolean;
|
||||
|
||||
private var _onStartMoving:Function;
|
||||
private var _onStopMoving:Function;
|
||||
|
||||
/**
|
||||
* Создание экземпляра контроллера.
|
||||
*
|
||||
* @param eventsSourceObject объект, используемый для получения событий мыши и клавиатуры
|
||||
*/
|
||||
public function CameraController(eventsSourceObject:DisplayObject) {
|
||||
if (eventsSourceObject == null) {
|
||||
throw new ArgumentError("CameraController: eventsSource is null");
|
||||
}
|
||||
_eventsSource = eventsSourceObject;
|
||||
|
||||
actionBindings[ACTION_FORWARD] = forward;
|
||||
actionBindings[ACTION_BACK] = back;
|
||||
actionBindings[ACTION_LEFT] = left;
|
||||
actionBindings[ACTION_RIGHT] = right;
|
||||
actionBindings[ACTION_UP] = up;
|
||||
actionBindings[ACTION_DOWN] = down;
|
||||
actionBindings[ACTION_PITCH_UP] = pitchUp;
|
||||
actionBindings[ACTION_PITCH_DOWN] = pitchDown;
|
||||
actionBindings[ACTION_YAW_LEFT] = yawLeft;
|
||||
actionBindings[ACTION_YAW_RIGHT] = yawRight;
|
||||
actionBindings[ACTION_ACCELERATE] = accelerate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
|
||||
* <table border="1" style="border-collapse: collapse">
|
||||
* <tr><th>Клавиша</th><th>Действие</th></tr>
|
||||
* <tr><td>W</td><td>ACTION_FORWARD</td></tr>
|
||||
* <tr><td>S</td><td>ACTION_BACK</td></tr>
|
||||
* <tr><td>A</td><td>ACTION_LEFT</td></tr>
|
||||
* <tr><td>D</td><td>ACTION_RIGHT</td></tr>
|
||||
* <tr><td>SPACE</td><td>ACTION_UP</td></tr>
|
||||
* <tr><td>CONTROL</td><td>ACTION_DOWN</td></tr>
|
||||
* <tr><td>SHIFT</td><td>ACTION_ACCELERATE</td></tr>
|
||||
* <tr><td>UP</td><td>ACTION_PITCH_UP</td></tr>
|
||||
* <tr><td>DOWN</td><td>ACTION_PITCH_DOWN</td></tr>
|
||||
* <tr><td>LEFT</td><td>ACTION_YAW_LEFT</td></tr>
|
||||
* <tr><td>RIGHT</td><td>ACTION_YAW_RIGHT</td></tr>
|
||||
* </table>
|
||||
*/
|
||||
public function setDefaultBindings():void {
|
||||
unbindAll();
|
||||
bindKey(KeyboardUtils.W, ACTION_FORWARD);
|
||||
bindKey(KeyboardUtils.S, ACTION_BACK);
|
||||
bindKey(KeyboardUtils.A, ACTION_LEFT);
|
||||
bindKey(KeyboardUtils.D, ACTION_RIGHT);
|
||||
bindKey(KeyboardUtils.SPACE, ACTION_UP);
|
||||
bindKey(KeyboardUtils.CONTROL, ACTION_DOWN);
|
||||
bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
|
||||
bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
|
||||
bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT);
|
||||
bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT);
|
||||
bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Направление камеры на точку.
|
||||
*
|
||||
* @param point координаты точки направления камеры
|
||||
*/
|
||||
public function lookAt(point:Point3D):void {
|
||||
if (_camera == null) {
|
||||
return;
|
||||
}
|
||||
var dx:Number = point.x - _camera.x;
|
||||
var dy:Number = point.y - _camera.y;
|
||||
var dz:Number = point.z - _camera.z;
|
||||
_camera.rotationZ = -Math.atan2(dx, dy);
|
||||
_camera.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - MathUtils.DEG90;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback-функция, вызываемая при начале движения камеры.
|
||||
*/
|
||||
public function get onStartMoving():Function {
|
||||
return _onStartMoving;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set onStartMoving(value:Function):void {
|
||||
_onStartMoving = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback-функция, вызываемая при прекращении движения камеры.
|
||||
*/
|
||||
public function get onStopMoving():Function {
|
||||
return _onStopMoving;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set onStopMoving(value:Function):void {
|
||||
_onStopMoving = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Набор объектов, исключаемых из проверки столкновений.
|
||||
*/
|
||||
public function get collisionIgnoreSet():Set {
|
||||
return _collisionIgnoreSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Источник событий клавиатуры и мыши.
|
||||
*/
|
||||
public function get eventsSource():DisplayObject {
|
||||
return _eventsSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set eventsSource(value:DisplayObject):void {
|
||||
if (_eventsSource != value) {
|
||||
if (value == null) {
|
||||
throw new ArgumentError("CameraController: eventsSource is null");
|
||||
}
|
||||
if (_controlsEnabled) {
|
||||
unregisterEventsListeners();
|
||||
}
|
||||
_eventsSource = value;
|
||||
if (_controlsEnabled) {
|
||||
registerEventListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ассоциированная камера.
|
||||
*/
|
||||
public function get camera():Camera3D {
|
||||
return _camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set camera(value:Camera3D):void {
|
||||
if (_camera != value) {
|
||||
_camera = value;
|
||||
if (value == null) {
|
||||
controlsEnabled = false;
|
||||
} else {
|
||||
createCollider();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Режим движения камеры. Если значение равно <code>true</code>, то перемещения камеры происходят относительно
|
||||
* локальной системы координат, иначе относительно глобальной.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
public function get moveLocal():Boolean {
|
||||
return _moveLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set moveLocal(value:Boolean):void {
|
||||
_moveLocal = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Включение режима проверки столкновений.
|
||||
*/
|
||||
public function get checkCollisions():Boolean {
|
||||
return _checkCollisions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set checkCollisions(value:Boolean):void {
|
||||
_checkCollisions = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Радиус сферы для определения столкновений.
|
||||
*
|
||||
* @default 0
|
||||
*/
|
||||
public function get collisionRadius():Number {
|
||||
return _collisionRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set collisionRadius(value:Number):void {
|
||||
_collisionRadius = value;
|
||||
if (_collider != null) {
|
||||
_collider.sphereRadius = _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 SphereCollider(_camera.scene, _collisionRadius);
|
||||
_collider.offsetThreshold = 0.01;
|
||||
_collider.ignoreSet = _collisionIgnoreSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Чувствительность мыши — коэффициент умножения <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Alternativa3D5/5.3/alternativa/engine3d/core/BSPNode.as
Normal file
65
Alternativa3D5/5.3/alternativa/engine3d/core/BSPNode.as
Normal file
@@ -0,0 +1,65 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.types.Set;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public final class BSPNode {
|
||||
|
||||
// Родительская нода
|
||||
alternativa3d var parent:BSPNode;
|
||||
|
||||
// Дочерние ветки
|
||||
alternativa3d var front:BSPNode;
|
||||
alternativa3d var back:BSPNode;
|
||||
|
||||
// Нормаль плоскости ноды
|
||||
alternativa3d var normal:Point3D = new Point3D();
|
||||
|
||||
// Смещение плоскости примитива
|
||||
alternativa3d var offset:Number;
|
||||
|
||||
// Минимальная мобильность ноды
|
||||
alternativa3d var mobility:int = int.MAX_VALUE;
|
||||
|
||||
// Набор примитивов в ноде
|
||||
alternativa3d var primitive:PolyPrimitive;
|
||||
alternativa3d var backPrimitives:Set;
|
||||
alternativa3d var frontPrimitives:Set;
|
||||
|
||||
// Хранилище неиспользуемых нод
|
||||
static private var collector:Array = new Array();
|
||||
|
||||
// Создать ноду на основе примитива
|
||||
static alternativa3d function createBSPNode(primitive:PolyPrimitive):BSPNode {
|
||||
// Достаём ноду из коллектора
|
||||
var node:BSPNode = collector.pop();
|
||||
// Если коллектор пуст, создаём новую ноду
|
||||
if (node == null) {
|
||||
node = new BSPNode();
|
||||
}
|
||||
|
||||
// Добавляем примитив в ноду
|
||||
node.primitive = primitive;
|
||||
// Сохраняем ноду
|
||||
primitive.node = node;
|
||||
// Сохраняем плоскость
|
||||
node.normal.copy(primitive.face.globalNormal);
|
||||
node.offset = primitive.face.globalOffset;
|
||||
// Сохраняем мобильность
|
||||
node.mobility = primitive.mobility;
|
||||
return node;
|
||||
}
|
||||
|
||||
// Удалить ноду, все ссылки должны быть почищены
|
||||
static alternativa3d function destroyBSPNode(node:BSPNode):void {
|
||||
//trace(node.back, node.front, node.parent, node.primitive, node.backPrimitives, node.frontPrimitives);
|
||||
collector.push(node);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
902
Alternativa3D5/5.3/alternativa/engine3d/core/Camera3D.as
Normal file
902
Alternativa3D5/5.3/alternativa/engine3d/core/Camera3D.as
Normal file
@@ -0,0 +1,902 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.display.Skin;
|
||||
import alternativa.engine3d.display.View;
|
||||
import alternativa.engine3d.materials.DrawPoint;
|
||||
import alternativa.engine3d.materials.SurfaceMaterial;
|
||||
import alternativa.types.Matrix3D;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.types.Set;
|
||||
|
||||
import flash.geom.Matrix;
|
||||
import flash.geom.Point;
|
||||
import alternativa.utils.MathUtils;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Камера для отображения 3D-сцены на экране.
|
||||
*
|
||||
* <p> Направление камеры совпадает с её локальной осью Z, поэтому только что созданная камера смотрит вверх в системе
|
||||
* координат родителя.
|
||||
*
|
||||
* <p> Для отображения видимой через камеру части сцены на экран, к камере должна быть подключёна область вывода —
|
||||
* экземпляр класса <code>alternativa.engine3d.display.View</code>.
|
||||
*
|
||||
* @see alternativa.engine3d.display.View
|
||||
*/
|
||||
public class Camera3D extends Object3D {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт матрицы пространства камеры
|
||||
*/
|
||||
alternativa3d var calculateMatrixOperation:Operation = new Operation("calculateMatrix", this, calculateMatrix, Operation.CAMERA_CALCULATE_MATRIX);
|
||||
/**
|
||||
* @private
|
||||
* Расчёт плоскостей отсечения
|
||||
*/
|
||||
alternativa3d var calculatePlanesOperation:Operation = new Operation("calculatePlanes", this, calculatePlanes, Operation.CAMERA_CALCULATE_PLANES);
|
||||
/**
|
||||
* @private
|
||||
* Отрисовка
|
||||
*/
|
||||
alternativa3d var renderOperation:Operation = new Operation("render", this, render, Operation.CAMERA_RENDER);
|
||||
|
||||
// Инкремент количества объектов
|
||||
private static var counter:uint = 0;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Поле зрения
|
||||
*/
|
||||
alternativa3d var _fov:Number = Math.PI/2;
|
||||
/**
|
||||
* @private
|
||||
* Фокусное расстояние
|
||||
*/
|
||||
alternativa3d var focalLength:Number;
|
||||
/**
|
||||
* @private
|
||||
* Перспективное искажение
|
||||
*/
|
||||
alternativa3d var focalDistortion:Number;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Флаги рассчитанности UV-матриц
|
||||
*/
|
||||
alternativa3d var uvMatricesCalculated:Set = new Set(true);
|
||||
|
||||
// Всмомогательные точки для расчёта UV-матриц
|
||||
private var textureA:Point3D = new Point3D();
|
||||
private var textureB:Point3D = new Point3D();
|
||||
private var textureC:Point3D = new Point3D();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Вид из камеры
|
||||
*/
|
||||
alternativa3d var _view:View;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Режим отрисовки
|
||||
*/
|
||||
alternativa3d var _orthographic:Boolean = false;
|
||||
private var fullDraw:Boolean;
|
||||
|
||||
// Масштаб
|
||||
private var _zoom:Number = 1;
|
||||
|
||||
// Синус половинчатого угла обзора камеры
|
||||
private var viewAngle:Number;
|
||||
|
||||
// Направление камеры
|
||||
private var direction:Point3D = new Point3D(0, 0, 1);
|
||||
|
||||
// Обратная трансформация камеры
|
||||
private var cameraMatrix:Matrix3D = new Matrix3D();
|
||||
|
||||
// Скины
|
||||
private var firstSkin:Skin;
|
||||
private var prevSkin:Skin;
|
||||
private var currentSkin:Skin;
|
||||
|
||||
// Плоскости отсечения
|
||||
private var leftPlane:Point3D = new Point3D();
|
||||
private var rightPlane:Point3D = new Point3D();
|
||||
private var topPlane:Point3D = new Point3D();
|
||||
private var bottomPlane:Point3D = new Point3D();
|
||||
private var leftOffset:Number;
|
||||
private var rightOffset:Number;
|
||||
private var topOffset:Number;
|
||||
private var bottomOffset:Number;
|
||||
|
||||
// Вспомогательные массивы точек для отрисовки
|
||||
private var points1:Array = new Array();
|
||||
private var points2:Array = new Array();
|
||||
|
||||
/**
|
||||
* Создание нового экземпляра камеры.
|
||||
*
|
||||
* @param name имя экземпляра
|
||||
*/
|
||||
public function Camera3D(name:String = null) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private function calculateMatrix():void {
|
||||
// Расчёт матрицы пространства камеры
|
||||
cameraMatrix.copy(transformation);
|
||||
cameraMatrix.invert();
|
||||
if (_orthographic) {
|
||||
cameraMatrix.scale(_zoom, _zoom, _zoom);
|
||||
}
|
||||
// Направление камеры
|
||||
direction.x = transformation.c;
|
||||
direction.y = transformation.g;
|
||||
direction.z = transformation.k;
|
||||
direction.normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт плоскостей отсечения
|
||||
*/
|
||||
private function calculatePlanes():void {
|
||||
var halfWidth:Number = _view._width*0.5;
|
||||
var halfHeight:Number = _view._height*0.5;
|
||||
|
||||
var aw:Number = transformation.a*halfWidth;
|
||||
var ew:Number = transformation.e*halfWidth;
|
||||
var iw:Number = transformation.i*halfWidth;
|
||||
var bh:Number = transformation.b*halfHeight;
|
||||
var fh:Number = transformation.f*halfHeight;
|
||||
var jh:Number = transformation.j*halfHeight;
|
||||
if (_orthographic) {
|
||||
// Расчёт плоскостей отсечения в изометрии
|
||||
aw /= _zoom;
|
||||
ew /= _zoom;
|
||||
iw /= _zoom;
|
||||
bh /= _zoom;
|
||||
fh /= _zoom;
|
||||
jh /= _zoom;
|
||||
|
||||
// Левая плоскость
|
||||
leftPlane.x = transformation.f*transformation.k - transformation.j*transformation.g;
|
||||
leftPlane.y = transformation.j*transformation.c - transformation.b*transformation.k;
|
||||
leftPlane.z = transformation.b*transformation.g - transformation.f*transformation.c;
|
||||
leftOffset = (transformation.d - aw)*leftPlane.x + (transformation.h - ew)*leftPlane.y + (transformation.l - iw)*leftPlane.z;
|
||||
|
||||
// Правая плоскость
|
||||
rightPlane.x = -leftPlane.x;
|
||||
rightPlane.y = -leftPlane.y;
|
||||
rightPlane.z = -leftPlane.z;
|
||||
rightOffset = (transformation.d + aw)*rightPlane.x + (transformation.h + ew)*rightPlane.y + (transformation.l + iw)*rightPlane.z;
|
||||
|
||||
// Верхняя плоскость
|
||||
topPlane.x = transformation.g*transformation.i - transformation.k*transformation.e;
|
||||
topPlane.y = transformation.k*transformation.a - transformation.c*transformation.i;
|
||||
topPlane.z = transformation.c*transformation.e - transformation.g*transformation.a;
|
||||
topOffset = (transformation.d - bh)*topPlane.x + (transformation.h - fh)*topPlane.y + (transformation.l - jh)*topPlane.z;
|
||||
|
||||
// Нижняя плоскость
|
||||
bottomPlane.x = -topPlane.x;
|
||||
bottomPlane.y = -topPlane.y;
|
||||
bottomPlane.z = -topPlane.z;
|
||||
bottomOffset = (transformation.d + bh)*bottomPlane.x + (transformation.h + fh)*bottomPlane.y + (transformation.l + jh)*bottomPlane.z;
|
||||
} else {
|
||||
// Вычисляем расстояние фокуса
|
||||
focalLength = Math.sqrt(_view._width*_view._width + _view._height*_view._height)*0.5/Math.tan(0.5*_fov);
|
||||
// Вычисляем минимальное (однопиксельное) искажение перспективной коррекции
|
||||
focalDistortion = 1/(focalLength*focalLength);
|
||||
|
||||
// Расчёт плоскостей отсечения в перспективе
|
||||
var cl:Number = transformation.c*focalLength;
|
||||
var gl:Number = transformation.g*focalLength;
|
||||
var kl:Number = transformation.k*focalLength;
|
||||
|
||||
// Угловые вектора пирамиды видимости
|
||||
var leftTopX:Number = -aw - bh + cl;
|
||||
var leftTopY:Number = -ew - fh + gl;
|
||||
var leftTopZ:Number = -iw - jh + kl;
|
||||
var rightTopX:Number = aw - bh + cl;
|
||||
var rightTopY:Number = ew - fh + gl;
|
||||
var rightTopZ:Number = iw - jh + kl;
|
||||
var leftBottomX:Number = -aw + bh + cl;
|
||||
var leftBottomY:Number = -ew + fh + gl;
|
||||
var leftBottomZ:Number = -iw + jh + kl;
|
||||
var rightBottomX:Number = aw + bh + cl;
|
||||
var rightBottomY:Number = ew + fh + gl;
|
||||
var rightBottomZ:Number = iw + jh + kl;
|
||||
|
||||
// Левая плоскость
|
||||
leftPlane.x = leftBottomY*leftTopZ - leftBottomZ*leftTopY;
|
||||
leftPlane.y = leftBottomZ*leftTopX - leftBottomX*leftTopZ;
|
||||
leftPlane.z = leftBottomX*leftTopY - leftBottomY*leftTopX;
|
||||
leftOffset = transformation.d*leftPlane.x + transformation.h*leftPlane.y + transformation.l*leftPlane.z;
|
||||
|
||||
// Правая плоскость
|
||||
rightPlane.x = rightTopY*rightBottomZ - rightTopZ*rightBottomY;
|
||||
rightPlane.y = rightTopZ*rightBottomX - rightTopX*rightBottomZ;
|
||||
rightPlane.z = rightTopX*rightBottomY - rightTopY*rightBottomX;
|
||||
rightOffset = transformation.d*rightPlane.x + transformation.h*rightPlane.y + transformation.l*rightPlane.z;
|
||||
|
||||
// Верхняя плоскость
|
||||
topPlane.x = leftTopY*rightTopZ - leftTopZ*rightTopY;
|
||||
topPlane.y = leftTopZ*rightTopX - leftTopX*rightTopZ;
|
||||
topPlane.z = leftTopX*rightTopY - leftTopY*rightTopX;
|
||||
topOffset = transformation.d*topPlane.x + transformation.h*topPlane.y + transformation.l*topPlane.z;
|
||||
|
||||
// Нижняя плоскость
|
||||
bottomPlane.x = rightBottomY*leftBottomZ - rightBottomZ*leftBottomY;
|
||||
bottomPlane.y = rightBottomZ*leftBottomX - rightBottomX*leftBottomZ;
|
||||
bottomPlane.z = rightBottomX*leftBottomY - rightBottomY*leftBottomX;
|
||||
bottomOffset = transformation.d*bottomPlane.x + transformation.h*bottomPlane.y + transformation.l*bottomPlane.z;
|
||||
|
||||
|
||||
// Расчёт угла конуса
|
||||
var length:Number = Math.sqrt(leftTopX*leftTopX + leftTopY*leftTopY + leftTopZ*leftTopZ);
|
||||
leftTopX /= length;
|
||||
leftTopY /= length;
|
||||
leftTopZ /= length;
|
||||
length = Math.sqrt(rightTopX*rightTopX + rightTopY*rightTopY + rightTopZ*rightTopZ);
|
||||
rightTopX /= length;
|
||||
rightTopY /= length;
|
||||
rightTopZ /= length;
|
||||
length = Math.sqrt(leftBottomX*leftBottomX + leftBottomY*leftBottomY + leftBottomZ*leftBottomZ);
|
||||
leftBottomX /= length;
|
||||
leftBottomY /= length;
|
||||
leftBottomZ /= length;
|
||||
length = Math.sqrt(rightBottomX*rightBottomX + rightBottomY*rightBottomY + rightBottomZ*rightBottomZ);
|
||||
rightBottomX /= length;
|
||||
rightBottomY /= length;
|
||||
rightBottomZ /= length;
|
||||
|
||||
viewAngle = leftTopX*direction.x + leftTopY*direction.y + leftTopZ*direction.z;
|
||||
var dot:Number = rightTopX*direction.x + rightTopY*direction.y + rightTopZ*direction.z;
|
||||
viewAngle = (dot < viewAngle) ? dot : viewAngle;
|
||||
dot = leftBottomX*direction.x + leftBottomY*direction.y + leftBottomZ*direction.z;
|
||||
viewAngle = (dot < viewAngle) ? dot : viewAngle;
|
||||
dot = rightBottomX*direction.x + rightBottomY*direction.y + rightBottomZ*direction.z;
|
||||
viewAngle = (dot < viewAngle) ? dot : viewAngle;
|
||||
|
||||
viewAngle = Math.sin(Math.acos(viewAngle));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private function render():void {
|
||||
// Режим отрисовки
|
||||
fullDraw = (calculateMatrixOperation.queued || calculatePlanesOperation.queued);
|
||||
|
||||
// Очистка рассчитанных текстурных матриц
|
||||
uvMatricesCalculated.clear();
|
||||
|
||||
// Отрисовка
|
||||
prevSkin = null;
|
||||
currentSkin = firstSkin;
|
||||
renderBSPNode(_scene.bsp);
|
||||
|
||||
// Удаление ненужных скинов
|
||||
while (currentSkin != null) {
|
||||
removeCurrentSkin();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private function renderBSPNode(node:BSPNode):void {
|
||||
if (node != null) {
|
||||
var primitive:*;
|
||||
var normal:Point3D = node.normal;
|
||||
var cameraAngle:Number = direction.x*normal.x + direction.y*normal.y + direction.z*normal.z;
|
||||
var cameraOffset:Number;
|
||||
if (!_orthographic) {
|
||||
cameraOffset = globalCoords.x*normal.x + globalCoords.y*normal.y + globalCoords.z*normal.z - node.offset;
|
||||
}
|
||||
if (node.primitive != null) {
|
||||
// В ноде только базовый примитив
|
||||
if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) {
|
||||
// Камера спереди ноды
|
||||
if (_orthographic || cameraAngle < viewAngle) {
|
||||
renderBSPNode(node.back);
|
||||
drawSkin(node.primitive);
|
||||
}
|
||||
renderBSPNode(node.front);
|
||||
} else {
|
||||
// Камера сзади ноды
|
||||
if (_orthographic || cameraAngle > -viewAngle) {
|
||||
renderBSPNode(node.front);
|
||||
}
|
||||
renderBSPNode(node.back);
|
||||
}
|
||||
} else {
|
||||
// В ноде несколько примитивов
|
||||
if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) {
|
||||
// Камера спереди ноды
|
||||
if (_orthographic || cameraAngle < viewAngle) {
|
||||
renderBSPNode(node.back);
|
||||
for (primitive in node.frontPrimitives) {
|
||||
drawSkin(primitive);
|
||||
}
|
||||
}
|
||||
renderBSPNode(node.front);
|
||||
} else {
|
||||
// Камера сзади ноды
|
||||
if (_orthographic || cameraAngle > -viewAngle) {
|
||||
renderBSPNode(node.front);
|
||||
for (primitive in node.backPrimitives) {
|
||||
drawSkin(primitive);
|
||||
}
|
||||
}
|
||||
renderBSPNode(node.back);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Отрисовка скина примитива
|
||||
*/
|
||||
private function drawSkin(primitive:PolyPrimitive):void {
|
||||
if (!fullDraw && currentSkin != null && currentSkin.primitive == primitive && !_scene.changedPrimitives[primitive]) {
|
||||
// Пропуск скина
|
||||
prevSkin = currentSkin;
|
||||
currentSkin = currentSkin.nextSkin;
|
||||
} else {
|
||||
// Проверка поверхности
|
||||
var surface:Surface = primitive.face._surface;
|
||||
if (surface == null) {
|
||||
return;
|
||||
}
|
||||
// Проверка материала
|
||||
var material:SurfaceMaterial = surface._material;
|
||||
if (material == null || !material.canDraw(primitive)) {
|
||||
return;
|
||||
}
|
||||
// Отсечение выходящих за окно просмотра частей
|
||||
var i:uint;
|
||||
var length:uint = primitive.num;
|
||||
var primitivePoint:Point3D;
|
||||
var primitiveUV:Point;
|
||||
var point:DrawPoint;
|
||||
var useUV:Boolean = !_orthographic && material.useUV && primitive.face.uvMatrixBase;
|
||||
if (useUV) {
|
||||
// Формируем список точек и UV-координат полигона
|
||||
for (i = 0; i < length; i++) {
|
||||
primitivePoint = primitive.points[i];
|
||||
primitiveUV = primitive.uvs[i];
|
||||
point = points1[i];
|
||||
if (point == null) {
|
||||
points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z, primitiveUV.x, primitiveUV.y);
|
||||
} else {
|
||||
point.x = primitivePoint.x;
|
||||
point.y = primitivePoint.y;
|
||||
point.z = primitivePoint.z;
|
||||
point.u = primitiveUV.x;
|
||||
point.v = primitiveUV.y;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Формируем список точек полигона
|
||||
for (i = 0; i < length; i++) {
|
||||
primitivePoint = primitive.points[i];
|
||||
point = points1[i];
|
||||
if (point == null) {
|
||||
points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z);
|
||||
} else {
|
||||
point.x = primitivePoint.x;
|
||||
point.y = primitivePoint.y;
|
||||
point.z = primitivePoint.z;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Отсечение по левой стороне
|
||||
length = clip(length, points1, points2, leftPlane, leftOffset, useUV);
|
||||
if (length < 3) {
|
||||
return;
|
||||
}
|
||||
// Отсечение по правой стороне
|
||||
length = clip(length, points2, points1, rightPlane, rightOffset, useUV);
|
||||
if (length < 3) {
|
||||
return;
|
||||
}
|
||||
// Отсечение по верхней стороне
|
||||
length = clip(length, points1, points2, topPlane, topOffset, useUV);
|
||||
if (length < 3) {
|
||||
return;
|
||||
}
|
||||
// Отсечение по нижней стороне
|
||||
length = clip(length, points2, points1, bottomPlane, bottomOffset, useUV);
|
||||
if (length < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fullDraw || _scene.changedPrimitives[primitive]) {
|
||||
|
||||
// Если конец списка скинов
|
||||
if (currentSkin == null) {
|
||||
// Добавляем скин в конец
|
||||
addCurrentSkin();
|
||||
} else {
|
||||
if (fullDraw || _scene.changedPrimitives[currentSkin.primitive]) {
|
||||
// Очистка скина
|
||||
currentSkin.material.clear(currentSkin);
|
||||
} else {
|
||||
// Вставка скина перед текущим
|
||||
insertCurrentSkin();
|
||||
}
|
||||
}
|
||||
|
||||
// Переводим координаты в систему камеры
|
||||
var x:Number;
|
||||
var y:Number;
|
||||
var z:Number;
|
||||
for (i = 0; i < length; i++) {
|
||||
point = points1[i];
|
||||
x = point.x;
|
||||
y = point.y;
|
||||
z = point.z;
|
||||
point.x = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d;
|
||||
point.y = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h;
|
||||
point.z = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l;
|
||||
}
|
||||
|
||||
// Назначаем скину примитив и материал
|
||||
currentSkin.primitive = primitive;
|
||||
currentSkin.material = material;
|
||||
material.draw(this, currentSkin, length, points1);
|
||||
|
||||
// Переключаемся на следующий скин
|
||||
prevSkin = currentSkin;
|
||||
currentSkin = currentSkin.nextSkin;
|
||||
|
||||
} else {
|
||||
|
||||
// Удаление ненужных скинов
|
||||
while (currentSkin != null && _scene.changedPrimitives[currentSkin.primitive]) {
|
||||
removeCurrentSkin();
|
||||
}
|
||||
|
||||
// Переключение на следующий скин
|
||||
if (currentSkin != null) {
|
||||
prevSkin = currentSkin;
|
||||
currentSkin = currentSkin.nextSkin;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Отсечение полигона плоскостью.
|
||||
*/
|
||||
private function clip(length:uint, points1:Array, points2:Array, plane:Point3D, offset:Number, calculateUV:Boolean):uint {
|
||||
var i:uint;
|
||||
var k:Number;
|
||||
var index:uint = 0;
|
||||
var point:DrawPoint;
|
||||
var point1:DrawPoint;
|
||||
var point2:DrawPoint;
|
||||
var offset1:Number;
|
||||
var offset2:Number;
|
||||
|
||||
point1 = points1[length - 1];
|
||||
offset1 = plane.x*point1.x + plane.y*point1.y + plane.z*point1.z - offset;
|
||||
|
||||
if (calculateUV) {
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
|
||||
point2 = points1[i];
|
||||
offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset;
|
||||
|
||||
if (offset2 > 0) {
|
||||
if (offset1 <= 0) {
|
||||
k = offset2/(offset2 - offset1);
|
||||
point = points2[index];
|
||||
if (point == null) {
|
||||
point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k);
|
||||
points2[index] = point;
|
||||
} else {
|
||||
point.x = point2.x - (point2.x - point1.x)*k;
|
||||
point.y = point2.y - (point2.y - point1.y)*k;
|
||||
point.z = point2.z - (point2.z - point1.z)*k;
|
||||
point.u = point2.u - (point2.u - point1.u)*k;
|
||||
point.v = point2.v - (point2.v - point1.v)*k;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
point = points2[index];
|
||||
if (point == null) {
|
||||
point = new DrawPoint(point2.x, point2.y, point2.z, point2.u, point2.v);
|
||||
points2[index] = point;
|
||||
} else {
|
||||
point.x = point2.x;
|
||||
point.y = point2.y;
|
||||
point.z = point2.z;
|
||||
point.u = point2.u;
|
||||
point.v = point2.v;
|
||||
}
|
||||
index++;
|
||||
} else {
|
||||
if (offset1 > 0) {
|
||||
k = offset2/(offset2 - offset1);
|
||||
point = points2[index];
|
||||
if (point == null) {
|
||||
point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k);
|
||||
points2[index] = point;
|
||||
} else {
|
||||
point.x = point2.x - (point2.x - point1.x)*k;
|
||||
point.y = point2.y - (point2.y - point1.y)*k;
|
||||
point.z = point2.z - (point2.z - point1.z)*k;
|
||||
point.u = point2.u - (point2.u - point1.u)*k;
|
||||
point.v = point2.v - (point2.v - point1.v)*k;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
offset1 = offset2;
|
||||
point1 = point2;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
|
||||
point2 = points1[i];
|
||||
offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset;
|
||||
|
||||
if (offset2 > 0) {
|
||||
if (offset1 <= 0) {
|
||||
k = offset2/(offset2 - offset1);
|
||||
point = points2[index];
|
||||
if (point == null) {
|
||||
point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k);
|
||||
points2[index] = point;
|
||||
} else {
|
||||
point.x = point2.x - (point2.x - point1.x)*k;
|
||||
point.y = point2.y - (point2.y - point1.y)*k;
|
||||
point.z = point2.z - (point2.z - point1.z)*k;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
point = points2[index];
|
||||
if (point == null) {
|
||||
point = new DrawPoint(point2.x, point2.y, point2.z);
|
||||
points2[index] = point;
|
||||
} else {
|
||||
point.x = point2.x;
|
||||
point.y = point2.y;
|
||||
point.z = point2.z;
|
||||
}
|
||||
index++;
|
||||
} else {
|
||||
if (offset1 > 0) {
|
||||
k = offset2/(offset2 - offset1);
|
||||
point = points2[index];
|
||||
if (point == null) {
|
||||
point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k);
|
||||
points2[index] = point;
|
||||
} else {
|
||||
point.x = point2.x - (point2.x - point1.x)*k;
|
||||
point.y = point2.y - (point2.y - point1.y)*k;
|
||||
point.z = point2.z - (point2.z - point1.z)*k;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
offset1 = offset2;
|
||||
point1 = point2;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Добавление текущего скина.
|
||||
*/
|
||||
private function addCurrentSkin():void {
|
||||
currentSkin = Skin.createSkin();
|
||||
_view.canvas.addChild(currentSkin);
|
||||
if (prevSkin == null) {
|
||||
firstSkin = currentSkin;
|
||||
} else {
|
||||
prevSkin.nextSkin = currentSkin;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Вставляем под текущий скин.
|
||||
*/
|
||||
private function insertCurrentSkin():void {
|
||||
var skin:Skin = Skin.createSkin();
|
||||
_view.canvas.addChildAt(skin, _view.canvas.getChildIndex(currentSkin));
|
||||
skin.nextSkin = currentSkin;
|
||||
if (prevSkin == null) {
|
||||
firstSkin = skin;
|
||||
} else {
|
||||
prevSkin.nextSkin = skin;
|
||||
}
|
||||
currentSkin = skin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаляет текущий скин.
|
||||
*/
|
||||
private function removeCurrentSkin():void {
|
||||
// Сохраняем следующий
|
||||
var next:Skin = currentSkin.nextSkin;
|
||||
// Удаляем из канваса
|
||||
_view.canvas.removeChild(currentSkin);
|
||||
// Очистка скина
|
||||
if (currentSkin.material != null) {
|
||||
currentSkin.material.clear(currentSkin);
|
||||
}
|
||||
// Зачищаем ссылки
|
||||
currentSkin.nextSkin = null;
|
||||
currentSkin.primitive = null;
|
||||
currentSkin.material = null;
|
||||
// Удаляем
|
||||
Skin.destroySkin(currentSkin);
|
||||
// Следующий устанавливаем текущим
|
||||
currentSkin = next;
|
||||
// Устанавливаем связь от предыдущего скина
|
||||
if (prevSkin == null) {
|
||||
firstSkin = currentSkin;
|
||||
} else {
|
||||
prevSkin.nextSkin = currentSkin;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d function calculateUVMatrix(face:Face, width:uint, height:uint):void {
|
||||
|
||||
// Расчёт точек базового примитива в координатах камеры
|
||||
var point:Point3D = face.primitive.points[0];
|
||||
textureA.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
|
||||
textureA.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
|
||||
point = face.primitive.points[1];
|
||||
textureB.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
|
||||
textureB.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
|
||||
point = face.primitive.points[2];
|
||||
textureC.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
|
||||
textureC.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
|
||||
|
||||
// Находим AB и AC
|
||||
var abx:Number = textureB.x - textureA.x;
|
||||
var aby:Number = textureB.y - textureA.y;
|
||||
var acx:Number = textureC.x - textureA.x;
|
||||
var acy:Number = textureC.y - textureA.y;
|
||||
|
||||
// Расчёт текстурной матрицы
|
||||
var uvMatrixBase:Matrix = face.uvMatrixBase;
|
||||
var uvMatrix:Matrix = face.uvMatrix;
|
||||
uvMatrix.a = (uvMatrixBase.a*abx + uvMatrixBase.b*acx)/width;
|
||||
uvMatrix.b = (uvMatrixBase.a*aby + uvMatrixBase.b*acy)/width;
|
||||
uvMatrix.c = -(uvMatrixBase.c*abx + uvMatrixBase.d*acx)/height;
|
||||
uvMatrix.d = -(uvMatrixBase.c*aby + uvMatrixBase.d*acy)/height;
|
||||
uvMatrix.tx = (uvMatrixBase.tx + uvMatrixBase.c)*abx + (uvMatrixBase.ty + uvMatrixBase.d)*acx + textureA.x + cameraMatrix.d;
|
||||
uvMatrix.ty = (uvMatrixBase.tx + uvMatrixBase.c)*aby + (uvMatrixBase.ty + uvMatrixBase.d)*acy + textureA.y + cameraMatrix.h;
|
||||
|
||||
// Помечаем, как рассчитанную
|
||||
uvMatricesCalculated[face] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Поле вывода, в котором происходит отрисовка камеры.
|
||||
*/
|
||||
public function get view():View {
|
||||
return _view;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set view(value:View):void {
|
||||
if (value != _view) {
|
||||
if (_view != null) {
|
||||
_view.camera = null;
|
||||
}
|
||||
if (value != null) {
|
||||
value.camera = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Включение режима аксонометрической проекции.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
public function get orthographic():Boolean {
|
||||
return _orthographic;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set orthographic(value:Boolean):void {
|
||||
if (_orthographic != value) {
|
||||
// Отправляем сигнал об изменении типа камеры
|
||||
addOperationToScene(calculateMatrixOperation);
|
||||
// Сохраняем новое значение
|
||||
_orthographic = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Угол поля зрения в радианах в режиме перспективной проекции. При изменении FOV изменяется фокусное расстояние
|
||||
* камеры по формуле <code>f = d/tan(fov/2)</code>, где <code>d</code> является половиной диагонали поля вывода.
|
||||
* Угол зрения ограничен диапазоном 0-180 градусов.
|
||||
*/
|
||||
public function get fov():Number {
|
||||
return _fov;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set fov(value:Number):void {
|
||||
value = (value < 0) ? 0 : ((value > (Math.PI - 0.0001)) ? (Math.PI - 0.0001) : value);
|
||||
if (_fov != value) {
|
||||
// Если перспектива
|
||||
if (!_orthographic) {
|
||||
// Отправляем сигнал об изменении плоскостей отсечения
|
||||
addOperationToScene(calculatePlanesOperation);
|
||||
}
|
||||
// Сохраняем новое значение
|
||||
_fov = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Коэффициент увеличения изображения в режиме аксонометрической проекции.
|
||||
*/
|
||||
public function get zoom():Number {
|
||||
return _zoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set zoom(value:Number):void {
|
||||
value = (value < 0) ? 0 : value;
|
||||
if (_zoom != value) {
|
||||
// Если изометрия
|
||||
if (_orthographic) {
|
||||
// Отправляем сигнал об изменении zoom
|
||||
addOperationToScene(calculateMatrixOperation);
|
||||
}
|
||||
// Сохраняем новое значение
|
||||
_zoom = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override protected function addToScene(scene:Scene3D):void {
|
||||
super.addToScene(scene);
|
||||
if (_view != null) {
|
||||
// Отправляем операцию расчёта плоскостей отсечения
|
||||
scene.addOperation(calculatePlanesOperation);
|
||||
// Подписываемся на сигналы сцены
|
||||
scene.changePrimitivesOperation.addSequel(renderOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override protected function removeFromScene(scene:Scene3D):void {
|
||||
super.removeFromScene(scene);
|
||||
|
||||
// Удаляем все операции из очереди
|
||||
scene.removeOperation(calculateMatrixOperation);
|
||||
scene.removeOperation(calculatePlanesOperation);
|
||||
scene.removeOperation(renderOperation);
|
||||
|
||||
if (_view != null) {
|
||||
// Отписываемся от сигналов сцены
|
||||
scene.changePrimitivesOperation.removeSequel(renderOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d function addToView(view:View):void {
|
||||
// Сохраняем первый скин
|
||||
firstSkin = (view.canvas.numChildren > 0) ? Skin(view.canvas.getChildAt(0)) : null;
|
||||
|
||||
// Подписка на свои операции
|
||||
|
||||
// При изменении камеры пересчёт матрицы
|
||||
calculateTransformationOperation.addSequel(calculateMatrixOperation);
|
||||
// При изменении матрицы или FOV пересчёт плоскостей отсечения
|
||||
calculateMatrixOperation.addSequel(calculatePlanesOperation);
|
||||
// При изменении плоскостей перерисовка
|
||||
calculatePlanesOperation.addSequel(renderOperation);
|
||||
|
||||
if (_scene != null) {
|
||||
// Отправляем сигнал перерисовки
|
||||
_scene.addOperation(calculateMatrixOperation);
|
||||
// Подписываемся на сигналы сцены
|
||||
_scene.changePrimitivesOperation.addSequel(renderOperation);
|
||||
}
|
||||
|
||||
// Сохраняем вид
|
||||
_view = view;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d function removeFromView(view:View):void {
|
||||
// Сброс ссылки на первый скин
|
||||
firstSkin = null;
|
||||
|
||||
// Отписка от своих операций
|
||||
|
||||
// При изменении камеры пересчёт матрицы
|
||||
calculateTransformationOperation.removeSequel(calculateMatrixOperation);
|
||||
// При изменении матрицы или FOV пересчёт плоскостей отсечения
|
||||
calculateMatrixOperation.removeSequel(calculatePlanesOperation);
|
||||
// При изменении плоскостей перерисовка
|
||||
calculatePlanesOperation.removeSequel(renderOperation);
|
||||
|
||||
if (_scene != null) {
|
||||
// Удаляем все операции из очереди
|
||||
_scene.removeOperation(calculateMatrixOperation);
|
||||
_scene.removeOperation(calculatePlanesOperation);
|
||||
_scene.removeOperation(renderOperation);
|
||||
// Отписываемся от сигналов сцены
|
||||
_scene.changePrimitivesOperation.removeSequel(renderOperation);
|
||||
}
|
||||
// Удаляем ссылку на вид
|
||||
_view = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override protected function defaultName():String {
|
||||
return "camera" + ++counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected override function createEmptyObject():Object3D {
|
||||
return new Camera3D();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected override function clonePropertiesFrom(source:Object3D):void {
|
||||
super.clonePropertiesFrom(source);
|
||||
|
||||
var src:Camera3D = Camera3D(source);
|
||||
orthographic = src._orthographic;
|
||||
zoom = src._zoom;
|
||||
fov = src._fov;
|
||||
}
|
||||
}
|
||||
}
|
||||
921
Alternativa3D5/5.3/alternativa/engine3d/core/Face.as
Normal file
921
Alternativa3D5/5.3/alternativa/engine3d/core/Face.as
Normal file
@@ -0,0 +1,921 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.types.Set;
|
||||
|
||||
import flash.geom.Matrix;
|
||||
import flash.geom.Point;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Грань, образованная тремя или более вершинами. Грани являются составными частями полигональных объектов. Каждая грань
|
||||
* содержит информацию об объекте и поверхности, которым она принадлежит. Для обеспечения возможности наложения
|
||||
* текстуры на грань, первым трём её вершинам могут быть заданы UV-координаты, на основании которых расчитывается
|
||||
* матрица трансформации текстуры.
|
||||
*/
|
||||
final public class Face {
|
||||
// Операции
|
||||
/**
|
||||
* @private
|
||||
* Расчёт глобальной нормали плоскости грани.
|
||||
*/
|
||||
alternativa3d var calculateNormalOperation:Operation = new Operation("calculateNormal", this, calculateNormal, Operation.FACE_CALCULATE_NORMAL);
|
||||
/**
|
||||
* @private
|
||||
* Расчёт UV-координат (выполняется до трансформации, чтобы UV корректно разбились при построении BSP).
|
||||
*/
|
||||
alternativa3d var calculateUVOperation:Operation = new Operation("calculateUV", this, calculateUV, Operation.FACE_CALCULATE_UV);
|
||||
/**
|
||||
* @private
|
||||
* Обновление примитива в сцене.
|
||||
*/
|
||||
alternativa3d var updatePrimitiveOperation:Operation = new Operation("updatePrimitive", this, updatePrimitive, Operation.FACE_UPDATE_PRIMITIVE);
|
||||
/**
|
||||
* @private
|
||||
* Обновление материала.
|
||||
*/
|
||||
alternativa3d var updateMaterialOperation:Operation = new Operation("updateMaterial", this, updateMaterial, Operation.FACE_UPDATE_MATERIAL);
|
||||
/**
|
||||
* @private
|
||||
* Расчёт UV для фрагментов (выполняется после трансформации, если её не было).
|
||||
*/
|
||||
alternativa3d var calculateFragmentsUVOperation:Operation = new Operation("calculateFragmentsUV", this, calculateFragmentsUV, Operation.FACE_CALCULATE_FRAGMENTS_UV);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Меш
|
||||
*/
|
||||
alternativa3d var _mesh:Mesh;
|
||||
/**
|
||||
* @private
|
||||
* Поверхность
|
||||
*/
|
||||
alternativa3d var _surface:Surface;
|
||||
/**
|
||||
* @private
|
||||
* Вершины грани
|
||||
*/
|
||||
alternativa3d var _vertices:Array;
|
||||
/**
|
||||
* @private
|
||||
* Количество вершин
|
||||
*/
|
||||
alternativa3d var _verticesCount:uint;
|
||||
/**
|
||||
* @private
|
||||
* Примитив
|
||||
*/
|
||||
alternativa3d var primitive:PolyPrimitive;
|
||||
|
||||
// UV-координаты
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d var _aUV:Point;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d var _bUV:Point;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d var _cUV:Point;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Коэффициенты базовой UV-матрицы
|
||||
*/
|
||||
alternativa3d var uvMatrixBase:Matrix;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* UV Матрица перевода текстурных координат в изометрическую камеру.
|
||||
*/
|
||||
alternativa3d var uvMatrix:Matrix;
|
||||
/**
|
||||
* @private
|
||||
* Нормаль плоскости
|
||||
*/
|
||||
alternativa3d var globalNormal:Point3D = new Point3D();
|
||||
/**
|
||||
* @private
|
||||
* Смещение плоскости
|
||||
*/
|
||||
alternativa3d var globalOffset:Number;
|
||||
|
||||
/**
|
||||
* Создание экземпляра грани.
|
||||
*
|
||||
* @param vertices массив объектов типа <code>alternativa.engine3d.core.Vertex</code>, задающий вершины грани в
|
||||
* порядке обхода лицевой стороны грани против часовой стрелки.
|
||||
*
|
||||
* @see Vertex
|
||||
*/
|
||||
public function Face(vertices:Array) {
|
||||
// Сохраняем вершины
|
||||
_vertices = vertices;
|
||||
_verticesCount = vertices.length;
|
||||
|
||||
// Создаём оригинальный примитив
|
||||
primitive = PolyPrimitive.createPolyPrimitive();
|
||||
primitive.face = this;
|
||||
primitive.num = _verticesCount;
|
||||
|
||||
// Обрабатываем вершины
|
||||
for (var i:uint = 0; i < _verticesCount; i++) {
|
||||
var vertex:Vertex = vertices[i];
|
||||
// Добавляем координаты вершины в примитив
|
||||
primitive.points.push(vertex.globalCoords);
|
||||
// Добавляем пустые UV-координаты в примитив
|
||||
primitive.uvs.push(null);
|
||||
// Добавляем вершину в грань
|
||||
vertex.addToFace(this);
|
||||
}
|
||||
|
||||
// Расчёт нормали
|
||||
calculateNormalOperation.addSequel(updatePrimitiveOperation);
|
||||
|
||||
// Расчёт UV грани инициирует расчёт UV фрагментов и перерисовку
|
||||
calculateUVOperation.addSequel(calculateFragmentsUVOperation);
|
||||
calculateUVOperation.addSequel(updateMaterialOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт нормали в глобальных координатах
|
||||
*/
|
||||
private function calculateNormal():void {
|
||||
// Вектор AB
|
||||
var vertex:Vertex = _vertices[0];
|
||||
var av:Point3D = vertex.globalCoords;
|
||||
vertex = _vertices[1];
|
||||
var bv:Point3D = vertex.globalCoords;
|
||||
var abx:Number = bv.x - av.x;
|
||||
var aby:Number = bv.y - av.y;
|
||||
var abz:Number = bv.z - av.z;
|
||||
// Вектор AC
|
||||
vertex = _vertices[2];
|
||||
var cv:Point3D = vertex.globalCoords;
|
||||
var acx:Number = cv.x - av.x;
|
||||
var acy:Number = cv.y - av.y;
|
||||
var acz:Number = cv.z - av.z;
|
||||
// Перпендикуляр к плоскости
|
||||
globalNormal.x = acz*aby - acy*abz;
|
||||
globalNormal.y = acx*abz - acz*abx;
|
||||
globalNormal.z = acy*abx - acx*aby;
|
||||
// Нормализация перпендикуляра
|
||||
globalNormal.normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчитывает глобальное смещение плоскости грани.
|
||||
* Помечает конечные примитивы на удаление, а базовый на добавление в сцене.
|
||||
*/
|
||||
private function updatePrimitive():void {
|
||||
// Расчёт смещения
|
||||
var vertex:Vertex = _vertices[0];
|
||||
globalOffset = vertex.globalCoords.x*globalNormal.x + vertex.globalCoords.y*globalNormal.y + vertex.globalCoords.z*globalNormal.z;
|
||||
|
||||
removePrimitive(primitive);
|
||||
primitive.mobility = _mesh.inheritedMobility;
|
||||
_mesh._scene.addPrimitives.push(primitive);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на удаление из сцены
|
||||
*/
|
||||
private function removePrimitive(primitive:PolyPrimitive):void {
|
||||
if (primitive.backFragment != null) {
|
||||
removePrimitive(primitive.backFragment);
|
||||
removePrimitive(primitive.frontFragment);
|
||||
primitive.backFragment = null;
|
||||
primitive.frontFragment = null;
|
||||
if (primitive != this.primitive) {
|
||||
primitive.parent = null;
|
||||
primitive.sibling = null;
|
||||
PolyPrimitive.destroyPolyPrimitive(primitive);
|
||||
}
|
||||
} else {
|
||||
// Если примитив в BSP-дереве
|
||||
if (primitive.node != null) {
|
||||
// Удаление примитива
|
||||
_mesh._scene.removeBSPPrimitive(primitive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Пометка на перерисовку фрагментов грани.
|
||||
*/
|
||||
private function updateMaterial():void {
|
||||
if (!updatePrimitiveOperation.queued) {
|
||||
changePrimitive(primitive);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на перерисовку
|
||||
*/
|
||||
private function changePrimitive(primitive:PolyPrimitive):void {
|
||||
if (primitive.backFragment != null) {
|
||||
changePrimitive(primitive.backFragment);
|
||||
changePrimitive(primitive.frontFragment);
|
||||
} else {
|
||||
_mesh._scene.changedPrimitives[primitive] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт UV-матрицы на основании первых трёх UV-координат.
|
||||
* Расчёт UV-координат для оставшихся точек.
|
||||
*/
|
||||
private function calculateUV():void {
|
||||
var i:uint;
|
||||
// Расчёт UV-матрицы
|
||||
if (_aUV != null && _bUV != null && _cUV != null) {
|
||||
var abu:Number = _bUV.x - _aUV.x;
|
||||
var abv:Number = _bUV.y - _aUV.y;
|
||||
var acu:Number = _cUV.x - _aUV.x;
|
||||
var acv:Number = _cUV.y - _aUV.y;
|
||||
var det:Number = abu*acv - abv*acu;
|
||||
if (det != 0) {
|
||||
if (uvMatrixBase == null) {
|
||||
uvMatrixBase = new Matrix();
|
||||
uvMatrix = new Matrix();
|
||||
}
|
||||
uvMatrixBase.a = acv/det;
|
||||
uvMatrixBase.b = -abv/det;
|
||||
uvMatrixBase.c = -acu/det;
|
||||
uvMatrixBase.d = abu/det;
|
||||
uvMatrixBase.tx = -(uvMatrixBase.a*_aUV.x + uvMatrixBase.c*_aUV.y);
|
||||
uvMatrixBase.ty = -(uvMatrixBase.b*_aUV.x + uvMatrixBase.d*_aUV.y);
|
||||
|
||||
// Заполняем UV в базовом примитиве
|
||||
primitive.uvs[0] = _aUV;
|
||||
primitive.uvs[1] = _bUV;
|
||||
primitive.uvs[2] = _cUV;
|
||||
|
||||
// Расчёт недостающих UV
|
||||
if (_verticesCount > 3) {
|
||||
var a:Point3D = primitive.points[0];
|
||||
var b:Point3D = primitive.points[1];
|
||||
var c:Point3D = primitive.points[2];
|
||||
|
||||
var ab1:Number;
|
||||
var ab2:Number;
|
||||
var ac1:Number;
|
||||
var ac2:Number;
|
||||
var ad1:Number;
|
||||
var ad2:Number;
|
||||
var abk:Number;
|
||||
var ack:Number;
|
||||
|
||||
var uv:Point;
|
||||
var point:Point3D;
|
||||
|
||||
// Выбор наиболее подходящих осей для расчёта
|
||||
if (((globalNormal.x < 0) ? -globalNormal.x : globalNormal.x) > ((globalNormal.y < 0) ? -globalNormal.y : globalNormal.y)) {
|
||||
if (((globalNormal.x < 0) ? -globalNormal.x : globalNormal.x) > ((globalNormal.z < 0) ? -globalNormal.z : globalNormal.z)) {
|
||||
// Ось X
|
||||
ab1 = b.y - a.y;
|
||||
ab2 = b.z - a.z;
|
||||
ac1 = c.y - a.y;
|
||||
ac2 = c.z - a.z;
|
||||
det = ab1*ac2 - ac1*ab2;
|
||||
for (i = 3; i < _verticesCount; i++) {
|
||||
point = primitive.points[i];
|
||||
ad1 = point.y - a.y;
|
||||
ad2 = point.z - a.z;
|
||||
abk = (ad1*ac2 - ac1*ad2)/det;
|
||||
ack = (ab1*ad2 - ad1*ab2)/det;
|
||||
uv = primitive.uvs[i];
|
||||
if (uv == null) {
|
||||
uv = new Point();
|
||||
primitive.uvs[i] = uv;
|
||||
}
|
||||
uv.x = _aUV.x + abu*abk + acu*ack;
|
||||
uv.y = _aUV.y + abv*abk + acv*ack;
|
||||
}
|
||||
} else {
|
||||
// Ось Z
|
||||
ab1 = b.x - a.x;
|
||||
ab2 = b.y - a.y;
|
||||
ac1 = c.x - a.x;
|
||||
ac2 = c.y - a.y;
|
||||
det = ab1*ac2 - ac1*ab2;
|
||||
for (i = 3; i < _verticesCount; i++) {
|
||||
point = primitive.points[i];
|
||||
ad1 = point.x - a.x;
|
||||
ad2 = point.y - a.y;
|
||||
abk = (ad1*ac2 - ac1*ad2)/det;
|
||||
ack = (ab1*ad2 - ad1*ab2)/det;
|
||||
uv = primitive.uvs[i];
|
||||
if (uv == null) {
|
||||
uv = new Point();
|
||||
primitive.uvs[i] = uv;
|
||||
}
|
||||
uv.x = _aUV.x + abu*abk + acu*ack;
|
||||
uv.y = _aUV.y + abv*abk + acv*ack;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (((globalNormal.y < 0) ? -globalNormal.y : globalNormal.y) > ((globalNormal.z < 0) ? -globalNormal.z : globalNormal.z)) {
|
||||
// Ось Y
|
||||
ab1 = b.x - a.x;
|
||||
ab2 = b.z - a.z;
|
||||
ac1 = c.x - a.x;
|
||||
ac2 = c.z - a.z;
|
||||
det = ab1*ac2 - ac1*ab2;
|
||||
for (i = 3; i < _verticesCount; i++) {
|
||||
point = primitive.points[i];
|
||||
ad1 = point.x - a.x;
|
||||
ad2 = point.z - a.z;
|
||||
abk = (ad1*ac2 - ac1*ad2)/det;
|
||||
ack = (ab1*ad2 - ad1*ab2)/det;
|
||||
uv = primitive.uvs[i];
|
||||
if (uv == null) {
|
||||
uv = new Point();
|
||||
primitive.uvs[i] = uv;
|
||||
}
|
||||
uv.x = _aUV.x + abu*abk + acu*ack;
|
||||
uv.y = _aUV.y + abv*abk + acv*ack;
|
||||
}
|
||||
} else {
|
||||
// Ось Z
|
||||
ab1 = b.x - a.x;
|
||||
ab2 = b.y - a.y;
|
||||
ac1 = c.x - a.x;
|
||||
ac2 = c.y - a.y;
|
||||
det = ab1*ac2 - ac1*ab2;
|
||||
for (i = 3; i < _verticesCount; i++) {
|
||||
point = primitive.points[i];
|
||||
ad1 = point.x - a.x;
|
||||
ad2 = point.y - a.y;
|
||||
abk = (ad1*ac2 - ac1*ad2)/det;
|
||||
ack = (ab1*ad2 - ad1*ab2)/det;
|
||||
uv = primitive.uvs[i];
|
||||
if (uv == null) {
|
||||
uv = new Point();
|
||||
primitive.uvs[i] = uv;
|
||||
}
|
||||
uv.x = _aUV.x + abu*abk + acu*ack;
|
||||
uv.y = _aUV.y + abv*abk + acv*ack;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Удаляем UV-матрицу
|
||||
uvMatrixBase = null;
|
||||
uvMatrix = null;
|
||||
// Удаляем UV-координаты из базового примитива
|
||||
for (i = 0; i < _verticesCount; i++) {
|
||||
primitive.uvs[i] = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Удаляем UV-матрицу
|
||||
uvMatrixBase = null;
|
||||
uvMatrix = null;
|
||||
// Удаляем UV-координаты из базового примитива
|
||||
for (i = 0; i < _verticesCount; i++) {
|
||||
primitive.uvs[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт UV-координат для фрагментов примитива, если не было трансформации
|
||||
*/
|
||||
private function calculateFragmentsUV():void {
|
||||
// Если в этом цикле не было трансформации
|
||||
if (!updatePrimitiveOperation.queued) {
|
||||
if (uvMatrixBase != null) {
|
||||
// Рассчитываем UV в примитиве
|
||||
calculatePrimitiveUV(primitive);
|
||||
} else {
|
||||
// Удаляем UV в примитиве
|
||||
removePrimitiveUV(primitive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт UV для точек базового примитива.
|
||||
*
|
||||
* @param primitive
|
||||
*/
|
||||
private function calculatePrimitiveUV(primitive:PolyPrimitive):void {
|
||||
if (primitive.backFragment != null) {
|
||||
var points:Array = primitive.points;
|
||||
var backPoints:Array = primitive.backFragment.points;
|
||||
var frontPoints:Array = primitive.frontFragment.points;
|
||||
var uvs:Array = primitive.uvs;
|
||||
var backUVs:Array = primitive.backFragment.uvs;
|
||||
var frontUVs:Array = primitive.frontFragment.uvs;
|
||||
var index1:uint = 0;
|
||||
var index2:uint = 0;
|
||||
var point:Point3D;
|
||||
var uv:Point;
|
||||
var uv1:Point;
|
||||
var uv2:Point;
|
||||
var t:Number;
|
||||
var firstSplit:Boolean = true;
|
||||
for (var i:uint = 0; i < primitive.num; i++) {
|
||||
var split:Boolean = true;
|
||||
point = points[i];
|
||||
if (point == frontPoints[index2]) {
|
||||
if (frontUVs[index2] == null) {
|
||||
frontUVs[index2] = uvs[i];
|
||||
}
|
||||
split = false;
|
||||
index2++;
|
||||
}
|
||||
if (point == backPoints[index1]) {
|
||||
if (backUVs[index1] == null) {
|
||||
backUVs[index1] = uvs[i];
|
||||
}
|
||||
split = false;
|
||||
index1++;
|
||||
}
|
||||
|
||||
if (split) {
|
||||
uv1 = uvs[(i == 0) ? (primitive.num - 1) : (i - 1)];
|
||||
uv2 = uvs[i];
|
||||
t = (firstSplit) ? primitive.splitTime1 : primitive.splitTime2;
|
||||
uv = frontUVs[index2];
|
||||
if (uv == null) {
|
||||
uv = new Point(uv1.x + (uv2.x - uv1.x)*t, uv1.y + (uv2.y - uv1.y)*t);
|
||||
frontUVs[index2] = uv;
|
||||
backUVs[index1] = uv;
|
||||
} else {
|
||||
uv.x = uv1.x + (uv2.x - uv1.x)*t;
|
||||
uv.y = uv1.y + (uv2.y - uv1.y)*t;
|
||||
}
|
||||
firstSplit = false;
|
||||
index2++;
|
||||
index1++;
|
||||
if (point == frontPoints[index2]) {
|
||||
if (frontUVs[index2] == null) {
|
||||
frontUVs[index2] = uvs[i];
|
||||
}
|
||||
index2++;
|
||||
}
|
||||
if (point == backPoints[index1]) {
|
||||
if (backUVs[index1] == null) {
|
||||
backUVs[index1] = uvs[i];
|
||||
}
|
||||
index1++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Проверяем рассечение последнего ребра
|
||||
if (index2 < primitive.frontFragment.num) {
|
||||
uv1 = uvs[primitive.num - 1];
|
||||
uv2 = uvs[0];
|
||||
t = (firstSplit) ? primitive.splitTime1 : primitive.splitTime2;
|
||||
uv = frontUVs[index2];
|
||||
if (uv == null) {
|
||||
uv = new Point(uv1.x + (uv2.x - uv1.x)*t, uv1.y + (uv2.y - uv1.y)*t);
|
||||
frontUVs[index2] = uv;
|
||||
backUVs[index1] = uv;
|
||||
} else {
|
||||
uv.x = uv1.x + (uv2.x - uv1.x)*t;
|
||||
uv.y = uv1.y + (uv2.y - uv1.y)*t;
|
||||
}
|
||||
}
|
||||
|
||||
calculatePrimitiveUV(primitive.backFragment);
|
||||
calculatePrimitiveUV(primitive.frontFragment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление UV в примитиве и его фрагментах
|
||||
* @param primitive
|
||||
*/
|
||||
private function removePrimitiveUV(primitive:PolyPrimitive):void {
|
||||
// Очищаем список UV
|
||||
for (var i:uint = 0; i < primitive.num; i++) {
|
||||
primitive.uvs[i] = null;
|
||||
}
|
||||
// Если есть фрагменты, удаляем UV в них
|
||||
if (primitive.backFragment != null) {
|
||||
removePrimitiveUV(primitive.backFragment);
|
||||
removePrimitiveUV(primitive.frontFragment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Массив вершин грани, представленных объектами класса <code>alternativa.engine3d.core.Vertex</code>.
|
||||
*
|
||||
* @see Vertex
|
||||
*/
|
||||
public function get vertices():Array {
|
||||
return new Array().concat(_vertices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Количество вершин грани.
|
||||
*/
|
||||
public function get verticesCount():uint {
|
||||
return _verticesCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Полигональный объект, которому принадлежит грань.
|
||||
*/
|
||||
public function get mesh():Mesh {
|
||||
return _mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Поверхность, которой принадлежит грань.
|
||||
*/
|
||||
public function get surface():Surface {
|
||||
return _surface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Идентификатор грани в полигональном объекте. В случае, если грань не принадлежит ни одному объекту, идентификатор
|
||||
* имеет значение <code>null</code>.
|
||||
*/
|
||||
public function get id():Object {
|
||||
return (_mesh != null) ? _mesh.getFaceId(this) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* UV-координаты, соответствующие первой вершине грани.
|
||||
*/
|
||||
public function get aUV():Point {
|
||||
return (_aUV != null) ? _aUV.clone() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* UV-координаты, соответствующие второй вершине грани.
|
||||
*/
|
||||
public function get bUV():Point {
|
||||
return (_bUV != null) ? _bUV.clone() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* UV-координаты, соответствующие третьей вершине грани.
|
||||
*/
|
||||
public function get cUV():Point {
|
||||
return (_cUV != null) ? _cUV.clone() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set aUV(value:Point):void {
|
||||
if (_aUV != null) {
|
||||
if (value != null) {
|
||||
if (!_aUV.equals(value)) {
|
||||
_aUV.x = value.x;
|
||||
_aUV.y = value.y;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_aUV = null;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (value != null) {
|
||||
_aUV = value.clone();
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set bUV(value:Point):void {
|
||||
if (_bUV != null) {
|
||||
if (value != null) {
|
||||
if (!_bUV.equals(value)) {
|
||||
_bUV.x = value.x;
|
||||
_bUV.y = value.y;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_bUV = null;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (value != null) {
|
||||
_bUV = value.clone();
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set cUV(value:Point):void {
|
||||
if (_cUV != null) {
|
||||
if (value != null) {
|
||||
if (!_cUV.equals(value)) {
|
||||
_cUV.x = value.x;
|
||||
_cUV.y = value.y;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_cUV = null;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (value != null) {
|
||||
_cUV = value.clone();
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Нормаль в локальной системе координат.
|
||||
*/
|
||||
public function get normal():Point3D {
|
||||
var res:Point3D = new Point3D();
|
||||
var vertex:Vertex = _vertices[0];
|
||||
var av:Point3D = vertex.coords;
|
||||
vertex = _vertices[1];
|
||||
var bv:Point3D = vertex.coords;
|
||||
var abx:Number = bv.x - av.x;
|
||||
var aby:Number = bv.y - av.y;
|
||||
var abz:Number = bv.z - av.z;
|
||||
vertex = _vertices[2];
|
||||
var cv:Point3D = vertex.coords;
|
||||
var acx:Number = cv.x - av.x;
|
||||
var acy:Number = cv.y - av.y;
|
||||
var acz:Number = cv.z - av.z;
|
||||
res.x = acz*aby - acy*abz;
|
||||
res.y = acx*abz - acz*abx;
|
||||
res.z = acy*abx - acx*aby;
|
||||
if (res.x != 0 || res.y != 0 || res.z != 0) {
|
||||
var k:Number = Math.sqrt(res.x*res.x + res.y*res.y + res.z*res.z);
|
||||
res.x /= k;
|
||||
res.y /= k;
|
||||
res.z /= k;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Расчёт UV-координат для произвольной точки в системе координат объекта, которому принадлежит грань.
|
||||
*
|
||||
* @param point точка в плоскости грани, для которой производится расчёт UV-координат
|
||||
* @return UV-координаты заданной точки
|
||||
*/
|
||||
public function getUV(point:Point3D):Point {
|
||||
return getUVFast(point, normal);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт UV-координат для произвольной точки в локальной системе координат без расчёта
|
||||
* локальной нормали грани. Используется для оптимизации.
|
||||
*
|
||||
* @param point точка в плоскости грани, для которой производится расчёт UV-координат
|
||||
* @param normal нормаль плоскости грани в локальной системе координат
|
||||
* @return UV-координаты заданной точки
|
||||
*/
|
||||
alternativa3d function getUVFast(point:Point3D, normal:Point3D):Point {
|
||||
if (_aUV == null || _bUV == null || _cUV == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Выбор наиболее длинной оси нормали
|
||||
var dir:uint;
|
||||
if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.y < 0) ? -normal.y : normal.y)) {
|
||||
if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.z < 0) ? -normal.z : normal.z)) {
|
||||
dir = 0;
|
||||
} else {
|
||||
dir = 2;
|
||||
}
|
||||
} else {
|
||||
if (((normal.y < 0) ? -normal.y : normal.y) > ((normal.z < 0) ? -normal.z : normal.z)) {
|
||||
dir = 1;
|
||||
} else {
|
||||
dir = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Расчёт соотношения по векторам AB и AC
|
||||
var v:Vertex = _vertices[0];
|
||||
var a:Point3D = v._coords;
|
||||
v = _vertices[1];
|
||||
var b:Point3D = v._coords;
|
||||
v = _vertices[2];
|
||||
var c:Point3D = v._coords;
|
||||
|
||||
var ab1:Number = (dir == 0) ? (b.y - a.y) : (b.x - a.x);
|
||||
var ab2:Number = (dir == 2) ? (b.y - a.y) : (b.z - a.z);
|
||||
var ac1:Number = (dir == 0) ? (c.y - a.y) : (c.x - a.x);
|
||||
var ac2:Number = (dir == 2) ? (c.y - a.y) : (c.z - a.z);
|
||||
var det:Number = ab1*ac2 - ac1*ab2;
|
||||
|
||||
var ad1:Number = (dir == 0) ? (point.y - a.y) : (point.x - a.x);
|
||||
var ad2:Number = (dir == 2) ? (point.y - a.y) : (point.z - a.z);
|
||||
var abk:Number = (ad1*ac2 - ac1*ad2)/det;
|
||||
var ack:Number = (ab1*ad2 - ad1*ab2)/det;
|
||||
|
||||
// Интерполяция по UV первых точек
|
||||
var abu:Number = _bUV.x - _aUV.x;
|
||||
var abv:Number = _bUV.y - _aUV.y;
|
||||
var acu:Number = _cUV.x - _aUV.x;
|
||||
var acv:Number = _cUV.y - _aUV.y;
|
||||
|
||||
return new Point(_aUV.x + abu*abk + acu*ack, _aUV.y + abv*abk + acv*ack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Множество граней, имеющих общие рёбра с текущей гранью.
|
||||
*/
|
||||
public function get edgeJoinedFaces():Set {
|
||||
var res:Set = new Set(true);
|
||||
// Перебираем точки грани
|
||||
for (var i:uint = 0; i < _verticesCount; i++) {
|
||||
var a:Vertex = _vertices[i];
|
||||
var b:Vertex = _vertices[(i < _verticesCount - 1) ? (i + 1) : 0];
|
||||
|
||||
// Перебираем грани текущей точки
|
||||
for (var key:* in a._faces) {
|
||||
var face:Face = key;
|
||||
// Если это другая грань и у неё также есть следующая точка
|
||||
if (face != this && face._vertices.indexOf(b) >= 0) {
|
||||
// Значит у граней общее ребро
|
||||
res[face] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление всех вершин из грани.
|
||||
* Очистка базового примитива.
|
||||
*/
|
||||
alternativa3d function removeVertices():void {
|
||||
// Удалить вершины
|
||||
for (var i:uint = 0; i < _verticesCount; i++) {
|
||||
// Удаляем из списка
|
||||
var vertex:Vertex = _vertices.pop();
|
||||
// Удаляем координаты вершины из примитива
|
||||
primitive.points.pop();
|
||||
// Удаляем вершину из грани
|
||||
vertex.removeFromFace(this);
|
||||
}
|
||||
// Обнуляем количество вершин
|
||||
_verticesCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Добавление грани на сцену
|
||||
* @param scene
|
||||
*/
|
||||
alternativa3d function addToScene(scene:Scene3D):void {
|
||||
// При добавлении на сцену рассчитываем плоскость и UV
|
||||
scene.addOperation(calculateNormalOperation);
|
||||
scene.addOperation(calculateUVOperation);
|
||||
|
||||
// Подписываем сцену на операции
|
||||
updatePrimitiveOperation.addSequel(scene.calculateBSPOperation);
|
||||
updateMaterialOperation.addSequel(scene.changePrimitivesOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление грани из сцены
|
||||
* @param scene
|
||||
*/
|
||||
alternativa3d function removeFromScene(scene:Scene3D):void {
|
||||
// Удаляем все операции из очереди
|
||||
scene.removeOperation(calculateUVOperation);
|
||||
scene.removeOperation(calculateFragmentsUVOperation);
|
||||
scene.removeOperation(calculateNormalOperation);
|
||||
scene.removeOperation(updatePrimitiveOperation);
|
||||
scene.removeOperation(updateMaterialOperation);
|
||||
|
||||
// Удаляем примитивы из сцены
|
||||
removePrimitive(primitive);
|
||||
|
||||
// Посылаем операцию сцены на расчёт BSP
|
||||
scene.addOperation(scene.calculateBSPOperation);
|
||||
|
||||
// Отписываем сцену от операций
|
||||
updatePrimitiveOperation.removeSequel(scene.calculateBSPOperation);
|
||||
updateMaterialOperation.removeSequel(scene.changePrimitivesOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Добавление грани в меш
|
||||
* @param mesh
|
||||
*/
|
||||
alternativa3d function addToMesh(mesh:Mesh):void {
|
||||
// Подписка на операции меша
|
||||
mesh.changeCoordsOperation.addSequel(updatePrimitiveOperation);
|
||||
mesh.changeRotationOrScaleOperation.addSequel(calculateNormalOperation);
|
||||
mesh.calculateMobilityOperation.addSequel(updatePrimitiveOperation);
|
||||
// Сохранить меш
|
||||
_mesh = mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление грани из меша
|
||||
* @param mesh
|
||||
*/
|
||||
alternativa3d function removeFromMesh(mesh:Mesh):void {
|
||||
// Отписка от операций меша
|
||||
mesh.changeCoordsOperation.removeSequel(updatePrimitiveOperation);
|
||||
mesh.changeRotationOrScaleOperation.removeSequel(calculateNormalOperation);
|
||||
mesh.calculateMobilityOperation.removeSequel(updatePrimitiveOperation);
|
||||
// Удалить ссылку на меш
|
||||
_mesh = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Добавление к поверхности
|
||||
*
|
||||
* @param surface
|
||||
*/
|
||||
alternativa3d function addToSurface(surface:Surface):void {
|
||||
// Подписка поверхности на операции
|
||||
surface.changeMaterialOperation.addSequel(updateMaterialOperation);
|
||||
// Если при смене поверхности изменился материал
|
||||
if (_mesh != null && (_surface != null && _surface._material != surface._material || _surface == null && surface._material != null)) {
|
||||
// Отправляем сигнал смены материала
|
||||
_mesh.addOperationToScene(updateMaterialOperation);
|
||||
}
|
||||
// Сохранить поверхность
|
||||
_surface = surface;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление из поверхности
|
||||
*
|
||||
* @param surface
|
||||
*/
|
||||
alternativa3d function removeFromSurface(surface:Surface):void {
|
||||
// Отписка поверхности от операций
|
||||
surface.changeMaterialOperation.removeSequel(updateMaterialOperation);
|
||||
// Если был материал
|
||||
if (surface._material != null) {
|
||||
// Отправляем сигнал смены материала
|
||||
_mesh.addOperationToScene(updateMaterialOperation);
|
||||
}
|
||||
// Удалить ссылку на поверхность
|
||||
_surface = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Строковое представление объекта.
|
||||
*
|
||||
* @return строковое представление объекта
|
||||
*/
|
||||
public function toString():String {
|
||||
var res:String = "[Face ID:" + id + ((_verticesCount > 0) ? " vertices:" : "");
|
||||
for (var i:uint = 0; i < _verticesCount; i++) {
|
||||
var vertex:Vertex = _vertices[i];
|
||||
res += vertex.id + ((i < _verticesCount - 1) ? ", " : "");
|
||||
}
|
||||
res += "]";
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
981
Alternativa3D5/5.3/alternativa/engine3d/core/Mesh.as
Normal file
981
Alternativa3D5/5.3/alternativa/engine3d/core/Mesh.as
Normal file
@@ -0,0 +1,981 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Полигональный объект — базовый класс для трёхмерных объектов, состоящих из граней-полигонов. Объект
|
||||
* содержит в себе наборы вершин, граней и поверхностей.
|
||||
*/
|
||||
public class Mesh extends Object3D {
|
||||
|
||||
// Инкремент количества объектов
|
||||
private static var counter:uint = 0;
|
||||
|
||||
// Инкременты для идентификаторов вершин, граней и поверхностей
|
||||
private var vertexIDCounter:uint = 0;
|
||||
private var faceIDCounter:uint = 0;
|
||||
private var surfaceIDCounter:uint = 0;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Список вершин
|
||||
*/
|
||||
alternativa3d var _vertices:Map = new Map();
|
||||
/**
|
||||
* @private
|
||||
* Список граней
|
||||
*/
|
||||
alternativa3d var _faces:Map = new Map();
|
||||
/**
|
||||
* @private
|
||||
* Список поверхностей
|
||||
*/
|
||||
alternativa3d var _surfaces:Map = new Map();
|
||||
|
||||
/**
|
||||
* Создание экземпляра полигонального объекта.
|
||||
*
|
||||
* @param name имя экземпляра
|
||||
*/
|
||||
public function Mesh(name:String = null) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление новой вершины к объекту.
|
||||
*
|
||||
* @param x координата X в локальной системе координат объекта
|
||||
* @param y координата Y в локальной системе координат объекта
|
||||
* @param z координата Z в локальной системе координат объекта
|
||||
* @param id идентификатор вершины. Если указано значение <code>null</code>, идентификатор будет
|
||||
* сформирован автоматически.
|
||||
*
|
||||
* @return экземпляр добавленной вершины
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.VertexExistsError объект уже содержит вершину с указанным идентификатором
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function createVertex(x:Number = 0, y:Number = 0, z:Number = 0, id:Object = null):Vertex {
|
||||
// Проверяем ID
|
||||
if (id != null) {
|
||||
// Если уже есть вершина с таким ID
|
||||
if (_vertices[id] != undefined) {
|
||||
if (_vertices[id] is Vertex) {
|
||||
throw new VertexExistsError(id, this);
|
||||
} else {
|
||||
throw new InvalidIDError(id, this);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ищем первый свободный
|
||||
while (_vertices[vertexIDCounter] != undefined) {
|
||||
vertexIDCounter++;
|
||||
}
|
||||
id = vertexIDCounter;
|
||||
}
|
||||
|
||||
// Создаём вершину
|
||||
var v:Vertex = new Vertex(x, y, z);
|
||||
|
||||
// Добавляем вершину на сцену
|
||||
if (_scene != null) {
|
||||
v.addToScene(_scene);
|
||||
}
|
||||
|
||||
// Добавляем вершину в меш
|
||||
v.addToMesh(this);
|
||||
_vertices[id] = v;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление вершины из объекта. При удалении вершины из объекта также удаляются все грани, которым принадлежит данная вершина.
|
||||
*
|
||||
* @param vertex экземпляр класса <code>alternativa.engine3d.core.Vertex</code> или идентификатор удаляемой вершины
|
||||
*
|
||||
* @return экземпляр удалённой вершины
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function removeVertex(vertex:Object):Vertex {
|
||||
var byLink:Boolean = vertex is Vertex;
|
||||
|
||||
// Проверяем на null
|
||||
if (vertex == null) {
|
||||
throw new VertexNotFoundError(null, this);
|
||||
}
|
||||
|
||||
// Проверяем наличие вершины в меше
|
||||
if (byLink) {
|
||||
// Если удаляем по ссылке
|
||||
if (Vertex(vertex)._mesh != this) {
|
||||
// Если вершина не в меше
|
||||
throw new VertexNotFoundError(vertex, this);
|
||||
}
|
||||
} else {
|
||||
// Если удаляем по ID
|
||||
if (_vertices[vertex] == undefined) {
|
||||
// Если нет вершины с таким ID
|
||||
throw new VertexNotFoundError(vertex, this);
|
||||
} else if (!(_vertices[vertex] is Vertex)) {
|
||||
// По этому id не вершина
|
||||
throw new InvalidIDError(vertex, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Находим вершину и её ID
|
||||
var v:Vertex = byLink ? Vertex(vertex) : _vertices[vertex];
|
||||
var id:Object = byLink ? getVertexId(Vertex(vertex)) : vertex;
|
||||
|
||||
// Удаляем вершину из сцены
|
||||
if (_scene != null) {
|
||||
v.removeFromScene(_scene);
|
||||
}
|
||||
|
||||
// Удаляем вершину из меша
|
||||
v.removeFromMesh(this);
|
||||
delete _vertices[id];
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление грани к объекту. В результате выполнения метода в объекте появляется новая грань, не привязанная
|
||||
* ни к одной поверхности.
|
||||
*
|
||||
* @param vertices массив вершин грани, указанных в порядке обхода лицевой стороны грани против часовой
|
||||
* стрелки. Каждый элемент массива может быть либо экземпляром класса <code>alternativa.engine3d.core.Vertex</code>,
|
||||
* либо идентификатором в наборе вершин объекта. В обоих случаях объект должен содержать указанную вершину.
|
||||
* @param id идентификатор грани. Если указано значение <code>null</code>, идентификатор будет
|
||||
* сформирован автоматически.
|
||||
*
|
||||
* @return экземпляр добавленной грани
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.FaceNeedMoreVerticesError в качестве массива вершин был передан
|
||||
* <code>null</code>, либо количество вершин в массиве меньше трёх
|
||||
* @throws alternativa.engine3d.errors.FaceExistsError объект уже содержит грань с заданным идентификатором
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
* @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит какую-либо вершину из входного массива
|
||||
*
|
||||
* @see Vertex
|
||||
*/
|
||||
public function createFace(vertices:Array, id:Object = null):Face {
|
||||
|
||||
// Проверяем на null
|
||||
if (vertices == null) {
|
||||
throw new FaceNeedMoreVerticesError(this);
|
||||
}
|
||||
|
||||
// Проверяем ID
|
||||
if (id != null) {
|
||||
// Если уже есть грань с таким ID
|
||||
if (_faces[id] != undefined) {
|
||||
if (_faces[id] is Face) {
|
||||
throw new FaceExistsError(id, this);
|
||||
} else {
|
||||
throw new InvalidIDError(id, this);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ищем первый свободный ID
|
||||
while (_faces[faceIDCounter] != undefined) {
|
||||
faceIDCounter++;
|
||||
}
|
||||
id = faceIDCounter;
|
||||
}
|
||||
|
||||
// Проверяем количество точек
|
||||
var length:uint = vertices.length;
|
||||
if (length < 3) {
|
||||
throw new FaceNeedMoreVerticesError(this, length);
|
||||
}
|
||||
|
||||
// Проверяем и формируем список вершин
|
||||
var v:Array = new Array();
|
||||
var vertex:Vertex;
|
||||
for (var i:uint = 0; i < length; i++) {
|
||||
if (vertices[i] is Vertex) {
|
||||
// Если работаем со ссылками
|
||||
vertex = vertices[i];
|
||||
if (vertex._mesh != this) {
|
||||
// Если вершина не в меше
|
||||
throw new VertexNotFoundError(vertices[i], this);
|
||||
}
|
||||
} else {
|
||||
// Если работаем с ID
|
||||
if (_vertices[vertices[i]] == null) {
|
||||
// Если нет вершины с таким ID
|
||||
throw new VertexNotFoundError(vertices[i], this);
|
||||
} else if (!(_vertices[vertices[i]] is Vertex)) {
|
||||
// Если id зарезервировано
|
||||
throw new InvalidIDError(vertices[i],this);
|
||||
}
|
||||
vertex = _vertices[vertices[i]];
|
||||
}
|
||||
v.push(vertex);
|
||||
}
|
||||
|
||||
// Создаём грань
|
||||
var f:Face = new Face(v);
|
||||
|
||||
// Добавляем грань на сцену
|
||||
if (_scene != null) {
|
||||
f.addToScene(_scene);
|
||||
}
|
||||
|
||||
// Добавляем грань в меш
|
||||
f.addToMesh(this);
|
||||
_faces[id] = f;
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление грани из объекта. Грань также удаляется из поверхности объекта, которой она принадлежит.
|
||||
*
|
||||
* @param экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор удаляемой грани
|
||||
*
|
||||
* @return экземпляр удалённой грани
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function removeFace(face:Object):Face {
|
||||
var byLink:Boolean = face is Face;
|
||||
|
||||
// Проверяем на null
|
||||
if (face == null) {
|
||||
throw new FaceNotFoundError(null, this);
|
||||
}
|
||||
|
||||
// Проверяем наличие грани в меше
|
||||
if (byLink) {
|
||||
// Если удаляем по ссылке
|
||||
if (Face(face)._mesh != this) {
|
||||
// Если грань не в меше
|
||||
throw new FaceNotFoundError(face, this);
|
||||
}
|
||||
} else {
|
||||
// Если удаляем по ID
|
||||
if (_faces[face] == undefined) {
|
||||
// Если нет грани с таким ID
|
||||
throw new FaceNotFoundError(face, this);
|
||||
} else if (!(_faces[face] is Face)) {
|
||||
throw new InvalidIDError(face, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Находим грань и её ID
|
||||
var f:Face = byLink ? Face(face) : _faces[face] ;
|
||||
var id:Object = byLink ? getFaceId(Face(face)) : face;
|
||||
|
||||
// Удаляем грань из сцены
|
||||
if (_scene != null) {
|
||||
f.removeFromScene(_scene);
|
||||
}
|
||||
|
||||
// Удаляем вершины из грани
|
||||
f.removeVertices();
|
||||
|
||||
// Удаляем грань из поверхности
|
||||
if (f._surface != null) {
|
||||
f._surface._faces.remove(f);
|
||||
f.removeFromSurface(f._surface);
|
||||
}
|
||||
|
||||
// Удаляем грань из меша
|
||||
f.removeFromMesh(this);
|
||||
delete _faces[id];
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление новой поверхности к объекту.
|
||||
*
|
||||
* @param faces набор граней, составляющих поверхность. Каждый элемент массива должен быть либо экземпляром класса
|
||||
* <code>alternativa.engine3d.core.Face</code>, либо идентификатором грани. В обоих случаях объект должен содержать
|
||||
* указанную грань. Если значение параметра равно <code>null</code>, то будет создана пустая поверхность. Если
|
||||
* какая-либо грань содержится в другой поверхности, она будет перенесена в новую поверхность.
|
||||
* @param id идентификатор новой поверхности. Если указано значение <code>null</code>, идентификатор будет
|
||||
* сформирован автоматически.
|
||||
*
|
||||
* @return экземпляр добавленной поверхности
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.SurfaceExistsError объект уже содержит поверхность с заданным идентификатором
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*
|
||||
* @see Face
|
||||
*/
|
||||
public function createSurface(faces:Array = null, id:Object = null):Surface {
|
||||
// Проверяем ID
|
||||
if (id != null) {
|
||||
// Если уже есть поверхность с таким ID
|
||||
if (_surfaces[id] != undefined) {
|
||||
if (_surfaces[id] is Surface) {
|
||||
throw new SurfaceExistsError(id, this);
|
||||
} else {
|
||||
throw new InvalidIDError(id, this);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ищем первый свободный ID
|
||||
while (_surfaces[surfaceIDCounter] != undefined) {
|
||||
surfaceIDCounter++;
|
||||
}
|
||||
id = surfaceIDCounter;
|
||||
}
|
||||
|
||||
// Создаём поверхность
|
||||
var s:Surface = new Surface();
|
||||
|
||||
// Добавляем поверхность на сцену
|
||||
if (_scene != null) {
|
||||
s.addToScene(_scene);
|
||||
}
|
||||
|
||||
// Добавляем поверхность в меш
|
||||
s.addToMesh(this);
|
||||
_surfaces[id] = s;
|
||||
|
||||
// Добавляем грани, если есть
|
||||
if (faces != null) {
|
||||
var length:uint = faces.length;
|
||||
for (var i:uint = 0; i < length; i++) {
|
||||
s.addFace(faces[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление поверхности объекта. Из удаляемой поверхности также удаляются все содержащиеся в ней грани.
|
||||
*
|
||||
* @param surface экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор удаляемой поверхности
|
||||
*
|
||||
* @return экземпляр удалённой поверхности
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function removeSurface(surface:Object):Surface {
|
||||
var byLink:Boolean = surface is Surface;
|
||||
|
||||
// Проверяем на null
|
||||
if (surface == null) {
|
||||
throw new SurfaceNotFoundError(null, this);
|
||||
}
|
||||
|
||||
// Проверяем наличие поверхности в меше
|
||||
if (byLink) {
|
||||
// Если удаляем по ссылке
|
||||
if (Surface(surface)._mesh != this) {
|
||||
// Если поверхность не в меше
|
||||
throw new SurfaceNotFoundError(surface, this);
|
||||
}
|
||||
} else {
|
||||
// Если удаляем по ID
|
||||
if (_surfaces[surface] == undefined) {
|
||||
// Если нет поверхности с таким ID
|
||||
throw new SurfaceNotFoundError(surface, this);
|
||||
} else if (!(_surfaces[surface] is Surface)) {
|
||||
throw new InvalidIDError(surface, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Находим поверхность и её ID
|
||||
var s:Surface = byLink ? Surface(surface) : _surfaces[surface];
|
||||
var id:Object = byLink ? getSurfaceId(Surface(surface)) : surface;
|
||||
|
||||
// Удаляем поверхность из сцены
|
||||
if (_scene != null) {
|
||||
s.removeFromScene(_scene);
|
||||
}
|
||||
|
||||
// Удаляем грани из поверхности
|
||||
s.removeFaces();
|
||||
|
||||
// Удаляем поверхность из меша
|
||||
s.removeFromMesh(this);
|
||||
delete _surfaces[id];
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление всех граней объекта в указанную поверхность.
|
||||
*
|
||||
* @param surface экземпляр класса <code>alternativa.engine3d.core.Surface</code> или идентификатор поверхности, в
|
||||
* которую добавляются грани. Если задан идентификатор, и объект не содержит поверхность с таким идентификатором,
|
||||
* будет создана новая поверхность.
|
||||
*
|
||||
* @param removeSurfaces удалять или нет пустые поверхности после переноса граней
|
||||
*
|
||||
* @return экземпляр поверхности, в которую перенесены грани
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанный экземпляр поверхности
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function moveAllFacesToSurface(surface:Object = null, removeSurfaces:Boolean = false):Surface {
|
||||
var returnSurface:Surface;
|
||||
var returnSurfaceId:Object;
|
||||
if (surface is Surface) {
|
||||
// Работаем с экземпляром Surface
|
||||
if (surface._mesh == this) {
|
||||
returnSurface = Surface(surface);
|
||||
} else {
|
||||
throw new SurfaceNotFoundError(surface, this);
|
||||
}
|
||||
} else {
|
||||
// Работаем с идентификатором
|
||||
if (_surfaces[surface] == undefined) {
|
||||
// Поверхности еще нет
|
||||
returnSurface = createSurface(null, surface);
|
||||
returnSurfaceId = surface;
|
||||
} else {
|
||||
if (_surfaces[surface] is Surface) {
|
||||
returnSurface = _surfaces[surface];
|
||||
} else {
|
||||
// _surfaces[surface] по идентификатору возвращает не Surface
|
||||
throw new InvalidIDError(surface, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Перемещаем все грани
|
||||
for each (var face:Face in _faces) {
|
||||
if (face._surface != returnSurface) {
|
||||
returnSurface.addFace(face);
|
||||
}
|
||||
}
|
||||
if (removeSurfaces) {
|
||||
// Удаляем старые, теперь вручную - меньше проверок, но рискованно
|
||||
if (returnSurfaceId == null) {
|
||||
returnSurfaceId = getSurfaceId(returnSurface);
|
||||
}
|
||||
var newSurfaces:Map = new Map();
|
||||
newSurfaces[returnSurfaceId] = returnSurface;
|
||||
delete _surfaces[returnSurfaceId];
|
||||
// Удаляем оставшиеся
|
||||
for (var currentSurfaceId:* in _surfaces) {
|
||||
// Удаляем поверхность из сцены
|
||||
var currentSurface:Surface = _surfaces[currentSurfaceId];
|
||||
if (_scene != null) {
|
||||
currentSurface.removeFromScene(_scene);
|
||||
}
|
||||
// Удаляем поверхность из меша
|
||||
currentSurface.removeFromMesh(this);
|
||||
delete _surfaces[currentSurfaceId];
|
||||
}
|
||||
// Новый список граней
|
||||
_surfaces = newSurfaces;
|
||||
}
|
||||
return returnSurface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка материала для указанной поверхности.
|
||||
*
|
||||
* @param material материал, назначаемый поверхности. Один экземпляр SurfaceMaterial можно назначить только одной поверхности.
|
||||
* @param surface экземпляр класса <code>alternativa.engine3d.core.Surface</code> или идентификатор поверхности
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*
|
||||
* @see Surface
|
||||
*/
|
||||
public function setMaterialToSurface(material:SurfaceMaterial, surface:Object):void {
|
||||
var byLink:Boolean = surface is Surface;
|
||||
|
||||
// Проверяем на null
|
||||
if (surface == null) {
|
||||
throw new SurfaceNotFoundError(null, this);
|
||||
}
|
||||
|
||||
// Проверяем наличие поверхности в меше
|
||||
if (byLink) {
|
||||
// Если назначаем по ссылке
|
||||
if (Surface(surface)._mesh != this) {
|
||||
// Если поверхность не в меше
|
||||
throw new SurfaceNotFoundError(surface, this);
|
||||
}
|
||||
} else {
|
||||
// Если назначаем по ID
|
||||
if (_surfaces[surface] == undefined) {
|
||||
// Если нет поверхности с таким ID
|
||||
throw new SurfaceNotFoundError(surface, this);
|
||||
} else if (!(_surfaces[surface] is Surface)) {
|
||||
throw new InvalidIDError(surface, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Находим поверхность
|
||||
var s:Surface = byLink ? Surface(surface) : _surfaces[surface];
|
||||
|
||||
// Назначаем материал
|
||||
s.material = material;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка материала для всех поверхностей объекта. Для каждой поверхности устанавливается копия материала.
|
||||
* При передаче <code>null</code> в качестве параметра происходит сброс материалов у всех поверхностей.
|
||||
*
|
||||
* @param material устанавливаемый материал
|
||||
*/
|
||||
public function cloneMaterialToAllSurfaces(material:SurfaceMaterial):void {
|
||||
for each (var surface:Surface in _surfaces) {
|
||||
surface.material = (material != null) ? SurfaceMaterial(material.clone()) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка UV-координат для указанной грани объекта. Матрица преобразования UV-координат расчитывается по
|
||||
* UV-координатам первых трёх вершин грани, поэтому для корректного текстурирования эти вершины должны образовывать
|
||||
* невырожденный треугольник в UV-пространстве.
|
||||
*
|
||||
* @param aUV UV-координаты, соответствующие первой вершине грани
|
||||
* @param bUV UV-координаты, соответствующие второй вершине грани
|
||||
* @param cUV UV-координаты, соответствующие третьей вершине грани
|
||||
* @param face экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор грани
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*
|
||||
* @see Face
|
||||
*/
|
||||
public function setUVsToFace(aUV:Point, bUV:Point, cUV:Point, face:Object):void {
|
||||
var byLink:Boolean = face is Face;
|
||||
|
||||
// Проверяем на null
|
||||
if (face == null) {
|
||||
throw new FaceNotFoundError(null, this);
|
||||
}
|
||||
|
||||
// Проверяем наличие грани в меше
|
||||
if (byLink) {
|
||||
// Если назначаем по ссылке
|
||||
if (Face(face)._mesh != this) {
|
||||
// Если грань не в меше
|
||||
throw new FaceNotFoundError(face, this);
|
||||
}
|
||||
} else {
|
||||
// Если назначаем по ID
|
||||
if (_faces[face] == undefined) {
|
||||
// Если нет грани с таким ID
|
||||
throw new FaceNotFoundError(face, this);
|
||||
} else if (!(_faces[face] is Face)) {
|
||||
throw new InvalidIDError(face, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Находим грань
|
||||
var f:Face = byLink ? Face(face) : _faces[face];
|
||||
|
||||
// Назначаем UV-координаты
|
||||
f.aUV = aUV;
|
||||
f.bUV = bUV;
|
||||
f.cUV = cUV;
|
||||
}
|
||||
|
||||
/**
|
||||
* Набор вершин объекта. Ключами ассоциативного массива являются идентификаторы вершин, значениями - экземпляры вершин.
|
||||
*/
|
||||
public function get vertices():Map {
|
||||
return _vertices.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Набор граней объекта. Ключами ассоциативного массива являются идентификаторы граней, значениями - экземпляры граней.
|
||||
*/
|
||||
public function get faces():Map {
|
||||
return _faces.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Набор поверхностей объекта. Ключами ассоциативного массива являются идентификаторы поверхностей, значениями - экземпляры поверхностей.
|
||||
*/
|
||||
public function get surfaces():Map {
|
||||
return _surfaces.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение вершины объекта по её идентификатору.
|
||||
*
|
||||
* @param id идентификатор вершины
|
||||
*
|
||||
* @return экземпляр вершины с указанным идентификатором
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит вершину с указанным идентификатором
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function getVertexById(id:Object):Vertex {
|
||||
if (id == null) {
|
||||
throw new VertexNotFoundError(null, this);
|
||||
}
|
||||
if (_vertices[id] == undefined) {
|
||||
// Если нет вершины с таким ID
|
||||
throw new VertexNotFoundError(id, this);
|
||||
} else {
|
||||
if (_vertices[id] is Vertex) {
|
||||
return _vertices[id];
|
||||
} else {
|
||||
throw new InvalidIDError(id, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение идентификатора вершины объекта.
|
||||
*
|
||||
* @param экземпляр вершины
|
||||
*
|
||||
* @return идентификатор указанной вершины
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину
|
||||
*/
|
||||
public function getVertexId(vertex:Vertex):Object {
|
||||
if (vertex == null) {
|
||||
throw new VertexNotFoundError(null, this);
|
||||
}
|
||||
if (vertex._mesh != this) {
|
||||
// Если вершина не в меше
|
||||
throw new VertexNotFoundError(vertex, this);
|
||||
}
|
||||
for (var i:Object in _vertices) {
|
||||
if (_vertices[i] == vertex) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new VertexNotFoundError(vertex, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка наличия вершины в объекте.
|
||||
*
|
||||
* @param vertex экземпляр класса <code>alternativa.engine3d.core.Vertex</code> или идентификатор вершины
|
||||
*
|
||||
* @return <code>true</code>, если объект содержит указанную вершину, иначе <code>false</code>
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.VertexNotFoundError в качестве vertex был передан null
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*
|
||||
* @see Vertex
|
||||
*/
|
||||
public function hasVertex(vertex:Object):Boolean {
|
||||
if (vertex == null) {
|
||||
throw new VertexNotFoundError(null, this);
|
||||
}
|
||||
if (vertex is Vertex) {
|
||||
// Проверка вершины
|
||||
return vertex._mesh == this;
|
||||
} else {
|
||||
// Проверка ID вершины
|
||||
if (_vertices[vertex] != undefined) {
|
||||
// По этому ID есть объект
|
||||
if (_vertices[vertex] is Vertex) {
|
||||
// Объект является вершиной
|
||||
return true;
|
||||
} else {
|
||||
// ID некорректный
|
||||
throw new InvalidIDError(vertex, this);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение грани объекта по ее идентификатору.
|
||||
*
|
||||
* @param id идентификатор грани
|
||||
*
|
||||
* @return экземпляр грани с указанным идентификатором
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит грань с указанным идентификатором
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function getFaceById(id:Object):Face {
|
||||
if (id == null) {
|
||||
throw new FaceNotFoundError(null, this);
|
||||
}
|
||||
if (_faces[id] == undefined) {
|
||||
// Если нет грани с таким ID
|
||||
throw new FaceNotFoundError(id, this);
|
||||
} else {
|
||||
if (_faces[id] is Face) {
|
||||
return _faces[id];
|
||||
} else {
|
||||
throw new InvalidIDError(id, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение идентификатора грани объекта.
|
||||
*
|
||||
* @param face экземпляр грани
|
||||
*
|
||||
* @return идентификатор указанной грани
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
|
||||
*/
|
||||
public function getFaceId(face:Face):Object {
|
||||
if (face == null) {
|
||||
throw new FaceNotFoundError(null, this);
|
||||
}
|
||||
if (face._mesh != this) {
|
||||
// Если грань не в меше
|
||||
throw new FaceNotFoundError(face, this);
|
||||
}
|
||||
for (var i:Object in _faces) {
|
||||
if (_faces[i] == face) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new FaceNotFoundError(face, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка наличия грани в объекте.
|
||||
*
|
||||
* @param face экземпляр класса <code>Face</code> или идентификатор грани
|
||||
*
|
||||
* @return <code>true</code>, если объект содержит указанную грань, иначе <code>false</code>
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.FaceNotFoundError в качестве face был указан null
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function hasFace(face:Object):Boolean {
|
||||
if (face == null) {
|
||||
throw new FaceNotFoundError(null, this);
|
||||
}
|
||||
if (face is Face) {
|
||||
// Проверка грани
|
||||
return face._mesh == this;
|
||||
} else {
|
||||
// Проверка ID грани
|
||||
if (_faces[face] != undefined) {
|
||||
// По этому ID есть объект
|
||||
if (_faces[face] is Face) {
|
||||
// Объект является гранью
|
||||
return true;
|
||||
} else {
|
||||
// ID некорректный
|
||||
throw new InvalidIDError(face, this);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение поверхности объекта по ее идентификатору
|
||||
*
|
||||
* @param id идентификатор поверхности
|
||||
*
|
||||
* @return экземпляр поверхности с указанным идентификатором
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит поверхность с указанным идентификатором
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function getSurfaceById(id:Object):Surface {
|
||||
if (id == null) {
|
||||
throw new SurfaceNotFoundError(null, this);
|
||||
}
|
||||
if (_surfaces[id] == undefined) {
|
||||
// Если нет поверхности с таким ID
|
||||
throw new SurfaceNotFoundError(id, this);
|
||||
} else {
|
||||
if (_surfaces[id] is Surface) {
|
||||
return _surfaces[id];
|
||||
} else {
|
||||
throw new InvalidIDError(id, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение идентификатора поверхности объекта.
|
||||
*
|
||||
* @param surface экземпляр поверхности
|
||||
*
|
||||
* @return идентификатор указанной поверхности
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
|
||||
*/
|
||||
public function getSurfaceId(surface:Surface):Object {
|
||||
if (surface == null) {
|
||||
throw new SurfaceNotFoundError(null, this);
|
||||
}
|
||||
if (surface._mesh != this) {
|
||||
// Если поверхность не в меше
|
||||
throw new SurfaceNotFoundError(surface, this);
|
||||
}
|
||||
for (var i:Object in _surfaces) {
|
||||
if (_surfaces[i] == surface) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка наличия поверхности в объекте.
|
||||
*
|
||||
* @param surface экземпляр класса <code>Surface</code> или идентификатор поверхности
|
||||
*
|
||||
* @return <code>true</true>, если объект содержит указанную поверхность, иначе <code>false</code>
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.SurfaceNotFoundError в качестве surface был передан null
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function hasSurface(surface:Object):Boolean {
|
||||
if (surface == null) {
|
||||
throw new SurfaceNotFoundError(null, this);
|
||||
}
|
||||
if (surface is Surface) {
|
||||
// Проверка поверхности
|
||||
return surface._mesh == this;
|
||||
} else {
|
||||
// Проверка ID поверхности
|
||||
if (_surfaces[surface] != undefined) {
|
||||
// По этому ID есть объект
|
||||
if (_surfaces[surface] is Surface) {
|
||||
// Объект является поверхностью
|
||||
return true;
|
||||
} else {
|
||||
// ID некорректный
|
||||
throw new InvalidIDError(surface, this);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @inheritDoc
|
||||
*/
|
||||
override alternativa3d function setScene(value:Scene3D):void {
|
||||
if (_scene != value) {
|
||||
var vertex:Vertex;
|
||||
var face:Face;
|
||||
var surface:Surface;
|
||||
if (value != null) {
|
||||
// Добавить вершины на сцену
|
||||
for each (vertex in _vertices) {
|
||||
vertex.addToScene(value);
|
||||
}
|
||||
// Добавить грани на сцену
|
||||
for each (face in _faces) {
|
||||
face.addToScene(value);
|
||||
}
|
||||
// Добавить поверхности на сцену
|
||||
for each (surface in _surfaces) {
|
||||
surface.addToScene(value);
|
||||
}
|
||||
} else {
|
||||
// Удалить вершины из сцены
|
||||
for each (vertex in _vertices) {
|
||||
vertex.removeFromScene(_scene);
|
||||
}
|
||||
// Удалить грани из сцены
|
||||
for each (face in _faces) {
|
||||
face.removeFromScene(_scene);
|
||||
}
|
||||
// Удалить поверхности из сцены
|
||||
for each (surface in _surfaces) {
|
||||
surface.removeFromScene(_scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
super.setScene(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override protected function defaultName():String {
|
||||
return "mesh" + ++counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override public function toString():String {
|
||||
return "[" + ObjectUtils.getClassName(this) + " " + _name + " vertices: " + _vertices.length + " faces: " + _faces.length + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected override function createEmptyObject():Object3D {
|
||||
return new Mesh();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected override function clonePropertiesFrom(source:Object3D):void {
|
||||
super.clonePropertiesFrom(source);
|
||||
|
||||
var src:Mesh = Mesh(source);
|
||||
|
||||
var id:*;
|
||||
var len:int;
|
||||
var i:int;
|
||||
// Копирование вершин
|
||||
var vertexMap:Map = new Map(true);
|
||||
for (id in src._vertices) {
|
||||
var sourceVertex:Vertex = src._vertices[id];
|
||||
vertexMap[sourceVertex] = createVertex(sourceVertex.x, sourceVertex.y, sourceVertex.z, id);
|
||||
}
|
||||
|
||||
// Копирование граней
|
||||
var faceMap:Map = new Map(true);
|
||||
for (id in src._faces) {
|
||||
var sourceFace:Face = src._faces[id];
|
||||
len = sourceFace._vertices.length;
|
||||
var faceVertices:Array = new Array(len);
|
||||
for (i = 0; i < len; i++) {
|
||||
faceVertices[i] = vertexMap[sourceFace._vertices[i]];
|
||||
}
|
||||
var newFace:Face = createFace(faceVertices, id);
|
||||
newFace.aUV = sourceFace._aUV;
|
||||
newFace.bUV = sourceFace._bUV;
|
||||
newFace.cUV = sourceFace._cUV;
|
||||
faceMap[sourceFace] = newFace;
|
||||
}
|
||||
|
||||
// Копирование поверхностей
|
||||
for (id in src._surfaces) {
|
||||
var sourceSurface:Surface = src._surfaces[id];
|
||||
var surfaceFaces:Array = sourceSurface._faces.toArray();
|
||||
len = surfaceFaces.length;
|
||||
for (i = 0; i < len; i++) {
|
||||
surfaceFaces[i] = faceMap[surfaceFaces[i]];
|
||||
}
|
||||
createSurface(surfaceFaces, id).material = SurfaceMaterial(sourceSurface.material.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
708
Alternativa3D5/5.3/alternativa/engine3d/core/Object3D.as
Normal file
708
Alternativa3D5/5.3/alternativa/engine3d/core/Object3D.as
Normal file
@@ -0,0 +1,708 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.errors.Object3DHierarchyError;
|
||||
import alternativa.engine3d.errors.Object3DNotFoundError;
|
||||
import alternativa.types.Matrix3D;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.types.Set;
|
||||
import alternativa.utils.ObjectUtils;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Базовый класс для объектов, находящихся в сцене. Класс реализует иерархию объектов сцены, а также содержит сведения
|
||||
* о трансформации объекта как единого целого.
|
||||
*
|
||||
* <p> Масштабирование, ориентация и положение объекта задаются в родительской системе координат. Результирующая
|
||||
* локальная трансформация является композицией операций масштабирования, поворотов объекта относительно осей
|
||||
* <code>X</code>, <code>Y</code>, <code>Z</code> и параллельного переноса центра объекта из начала координат.
|
||||
* Операции применяются в порядке их перечисления.
|
||||
*
|
||||
* <p> Глобальная трансформация (в системе координат корневого объекта сцены) является композицией трансформаций
|
||||
* самого объекта и всех его предков по иерархии объектов сцены.
|
||||
*/
|
||||
public class Object3D {
|
||||
// Операции
|
||||
/**
|
||||
* @private
|
||||
* Поворот или масштабирование
|
||||
*/
|
||||
alternativa3d var changeRotationOrScaleOperation:Operation = new Operation("changeRotationOrScale", this);
|
||||
/**
|
||||
* @private
|
||||
* Перемещение
|
||||
*/
|
||||
alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this);
|
||||
/**
|
||||
* @private
|
||||
* Расчёт матрицы трансформации
|
||||
*/
|
||||
alternativa3d var calculateTransformationOperation:Operation = new Operation("calculateTransformation", this, calculateTransformation, Operation.OBJECT_CALCULATE_TRANSFORMATION);
|
||||
/**
|
||||
* @private
|
||||
* Изменение уровеня мобильности
|
||||
*/
|
||||
alternativa3d var calculateMobilityOperation:Operation = new Operation("calculateMobility", this, calculateMobility, Operation.OBJECT_CALCULATE_MOBILITY);
|
||||
|
||||
// Инкремент количества объектов
|
||||
private static var counter:uint = 0;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Наименование
|
||||
*/
|
||||
alternativa3d var _name:String;
|
||||
/**
|
||||
* @private
|
||||
* Сцена
|
||||
*/
|
||||
alternativa3d var _scene:Scene3D;
|
||||
/**
|
||||
* @private
|
||||
* Родительский объект
|
||||
*/
|
||||
alternativa3d var _parent:Object3D;
|
||||
/**
|
||||
* @private
|
||||
* Дочерние объекты
|
||||
*/
|
||||
alternativa3d var _children:Set = new Set();
|
||||
/**
|
||||
* @private
|
||||
* Уровень мобильности
|
||||
*/
|
||||
alternativa3d var _mobility:int = 0;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d var inheritedMobility:int;
|
||||
/**
|
||||
* @private
|
||||
* Координаты объекта относительно родителя
|
||||
*/
|
||||
alternativa3d var _coords:Point3D = new Point3D();
|
||||
/**
|
||||
* @private
|
||||
* Поворот объекта по оси X относительно родителя. Угол измеряется в радианах.
|
||||
*/
|
||||
alternativa3d var _rotationX:Number = 0;
|
||||
/**
|
||||
* @private
|
||||
* Поворот объекта по оси Y относительно родителя. Угол измеряется в радианах.
|
||||
*/
|
||||
alternativa3d var _rotationY:Number = 0;
|
||||
/**
|
||||
* @private
|
||||
* Поворот объекта по оси Z относительно родителя. Угол измеряется в радианах.
|
||||
*/
|
||||
alternativa3d var _rotationZ:Number = 0;
|
||||
/**
|
||||
* @private
|
||||
* Мастшаб объекта по оси X относительно родителя
|
||||
*/
|
||||
alternativa3d var _scaleX:Number = 1;
|
||||
/**
|
||||
* @private
|
||||
* Мастшаб объекта по оси Y относительно родителя
|
||||
*/
|
||||
alternativa3d var _scaleY:Number = 1;
|
||||
/**
|
||||
* @private
|
||||
* Мастшаб объекта по оси Z относительно родителя
|
||||
*/
|
||||
alternativa3d var _scaleZ:Number = 1;
|
||||
/**
|
||||
* @private
|
||||
* Полная матрица трансформации, переводящая координаты из локальной системы координат объекта в систему координат сцены
|
||||
*/
|
||||
alternativa3d var transformation:Matrix3D = new Matrix3D();
|
||||
/**
|
||||
* @private
|
||||
* Координаты в сцене
|
||||
*/
|
||||
alternativa3d var globalCoords:Point3D = new Point3D();
|
||||
|
||||
/**
|
||||
* Создание экземпляра класса.
|
||||
*
|
||||
* @param name имя экземпляра
|
||||
*/
|
||||
public function Object3D(name:String = null) {
|
||||
// Имя по-умолчанию
|
||||
_name = (name != null) ? name : defaultName();
|
||||
|
||||
// Последствия операций
|
||||
changeRotationOrScaleOperation.addSequel(calculateTransformationOperation);
|
||||
changeCoordsOperation.addSequel(calculateTransformationOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт трансформации
|
||||
*/
|
||||
private function calculateTransformation():void {
|
||||
if (changeRotationOrScaleOperation.queued) {
|
||||
// Если полная трансформация
|
||||
transformation.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ);
|
||||
if (_parent != null) {
|
||||
transformation.combine(_parent.transformation);
|
||||
}
|
||||
// Сохраняем глобальные координаты объекта
|
||||
globalCoords.x = transformation.d;
|
||||
globalCoords.y = transformation.h;
|
||||
globalCoords.z = transformation.l;
|
||||
} else {
|
||||
// Если только перемещение
|
||||
globalCoords.copy(_coords);
|
||||
if (_parent != null) {
|
||||
globalCoords.transform(_parent.transformation);
|
||||
}
|
||||
transformation.offset(globalCoords.x, globalCoords.y, globalCoords.z);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт общей мобильности
|
||||
*/
|
||||
private function calculateMobility():void {
|
||||
inheritedMobility = ((_parent != null) ? _parent.inheritedMobility : 0) + _mobility;
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление дочернего объекта. Добавляемый объект удаляется из списка детей предыдущего родителя.
|
||||
* Новой сценой дочернего объекта становится сцена родителя.
|
||||
*
|
||||
* @param child добавляемый объект
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.Object3DHierarchyError нарушение иерархии объектов сцены
|
||||
*/
|
||||
public function addChild(child:Object3D):void {
|
||||
|
||||
// Проверка на null
|
||||
if (child == null) {
|
||||
throw new Object3DHierarchyError(null, this);
|
||||
}
|
||||
|
||||
// Проверка на наличие
|
||||
if (child._parent == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка на добавление к самому себе
|
||||
if (child == this) {
|
||||
throw new Object3DHierarchyError(this, this);
|
||||
}
|
||||
|
||||
// Проверка на добавление родительского объекта
|
||||
if (child._scene == _scene) {
|
||||
// Если объект был в той же сцене, либо оба не были в сцене
|
||||
var parentObject:Object3D = _parent;
|
||||
while (parentObject != null) {
|
||||
if (child == parentObject) {
|
||||
throw new Object3DHierarchyError(child, this);
|
||||
return;
|
||||
}
|
||||
parentObject = parentObject._parent;
|
||||
}
|
||||
}
|
||||
|
||||
// Если объект был в другом объекте
|
||||
if (child._parent != null) {
|
||||
// Удалить его оттуда
|
||||
child._parent._children.remove(child);
|
||||
} else {
|
||||
// Если объект был корневым в сцене
|
||||
if (child._scene != null && child._scene._root == child) {
|
||||
child._scene.root = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем в список
|
||||
_children.add(child);
|
||||
// Указываем себя как родителя
|
||||
child.setParent(this);
|
||||
// Устанавливаем уровни
|
||||
child.setLevel((calculateTransformationOperation.priority & 0xFFFFFF) + 1);
|
||||
// Указываем сцену
|
||||
child.setScene(_scene);
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление дочернего объекта.
|
||||
*
|
||||
* @param child удаляемый дочерний объект
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.Object3DNotFoundError указанный объект не содержится в списке детей текущего объекта
|
||||
*/
|
||||
public function removeChild(child:Object3D):void {
|
||||
// Проверка на null
|
||||
if (child == null) {
|
||||
throw new Object3DNotFoundError(null, this);
|
||||
}
|
||||
// Проверка на наличие
|
||||
if (child._parent != this) {
|
||||
throw new Object3DNotFoundError(child, this);
|
||||
}
|
||||
// Убираем из списка
|
||||
_children.remove(child);
|
||||
// Удаляем ссылку на родителя
|
||||
child.setParent(null);
|
||||
// Удаляем ссылку на сцену
|
||||
child.setScene(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Установка родительского объекта.
|
||||
*
|
||||
* @param value родительский объект
|
||||
*/
|
||||
alternativa3d function setParent(value:Object3D):void {
|
||||
// Отписываемся от сигналов старого родителя
|
||||
if (_parent != null) {
|
||||
removeParentSequels();
|
||||
}
|
||||
// Сохранить родителя
|
||||
_parent = value;
|
||||
// Если устанавливаем родителя
|
||||
if (value != null) {
|
||||
// Подписка на сигналы родителя
|
||||
addParentSequels();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Установка новой сцены для объекта.
|
||||
*
|
||||
* @param value сцена
|
||||
*/
|
||||
alternativa3d function setScene(value:Scene3D):void {
|
||||
if (_scene != value) {
|
||||
// Если была сцена
|
||||
if (_scene != null) {
|
||||
// Удалиться из сцены
|
||||
removeFromScene(_scene);
|
||||
}
|
||||
// Если новая сцена
|
||||
if (value != null) {
|
||||
// Добавиться на сцену
|
||||
addToScene(value);
|
||||
}
|
||||
// Сохранить сцену
|
||||
_scene = value;
|
||||
} else {
|
||||
// Посылаем операцию трансформации
|
||||
addOperationToScene(changeRotationOrScaleOperation);
|
||||
// Посылаем операцию пересчёта мобильности
|
||||
addOperationToScene(calculateMobilityOperation);
|
||||
}
|
||||
// Установить эту сцену у дочерних объектов
|
||||
for (var key:* in _children) {
|
||||
var object:Object3D = key;
|
||||
object.setScene(_scene);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Установка уровня операции трансформации.
|
||||
*
|
||||
* @param value уровень операции трансформации
|
||||
*/
|
||||
alternativa3d function setLevel(value:uint):void {
|
||||
// Установить уровень операции трансформации и расчёта мобильности
|
||||
calculateTransformationOperation.priority = (calculateTransformationOperation.priority & 0xFF000000) | value;
|
||||
calculateMobilityOperation.priority = (calculateMobilityOperation.priority & 0xFF000000) | value;
|
||||
// Установить уровни у дочерних объектов
|
||||
for (var key:* in _children) {
|
||||
var object:Object3D = key;
|
||||
object.setLevel(value + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Подписка на сигналы родителя.
|
||||
*/
|
||||
private function addParentSequels():void {
|
||||
_parent.changeCoordsOperation.addSequel(changeCoordsOperation);
|
||||
_parent.changeRotationOrScaleOperation.addSequel(changeRotationOrScaleOperation);
|
||||
_parent.calculateMobilityOperation.addSequel(calculateMobilityOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление подписки на сигналы родителя.
|
||||
*/
|
||||
private function removeParentSequels():void {
|
||||
_parent.changeCoordsOperation.removeSequel(changeCoordsOperation);
|
||||
_parent.changeRotationOrScaleOperation.removeSequel(changeRotationOrScaleOperation);
|
||||
_parent.calculateMobilityOperation.removeSequel(calculateMobilityOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод вызывается при добавлении объекта на сцену. Наследники могут переопределять метод для выполнения
|
||||
* специфических действий.
|
||||
*
|
||||
* @param scene сцена, в которую добавляется объект
|
||||
*/
|
||||
protected function addToScene(scene:Scene3D):void {
|
||||
// При добавлении на сцену полная трансформация и расчёт мобильности
|
||||
scene.addOperation(changeRotationOrScaleOperation);
|
||||
scene.addOperation(calculateMobilityOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод вызывается при удалении объекта со сцены. Наследники могут переопределять метод для выполнения
|
||||
* специфических действий.
|
||||
*
|
||||
* @param scene сцена, из которой удаляется объект
|
||||
*/
|
||||
protected function removeFromScene(scene:Scene3D):void {
|
||||
// Удаляем все операции из очереди
|
||||
scene.removeOperation(changeRotationOrScaleOperation);
|
||||
scene.removeOperation(changeCoordsOperation);
|
||||
scene.removeOperation(calculateMobilityOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Имя объекта.
|
||||
*/
|
||||
public function get name():String {
|
||||
return _name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set name(value:String):void {
|
||||
_name = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сцена, которой принадлежит объект.
|
||||
*/
|
||||
public function get scene():Scene3D {
|
||||
return _scene;
|
||||
}
|
||||
|
||||
/**
|
||||
* Родительский объект.
|
||||
*/
|
||||
public function get parent():Object3D {
|
||||
return _parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Набор дочерних объектов.
|
||||
*/
|
||||
public function get children():Set {
|
||||
return _children.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Уровень мобильности. Результирующая мобильность объекта является суммой мобильностей объекта и всех его предков
|
||||
* по иерархии объектов в сцене. Результирующая мобильность влияет на положение объекта в BSP-дереве. Менее мобильные
|
||||
* объекты находятся ближе к корню дерева, чем более мобильные.
|
||||
*/
|
||||
public function get mobility():int {
|
||||
return _mobility;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set mobility(value:int):void {
|
||||
if (_mobility != value) {
|
||||
_mobility = value;
|
||||
addOperationToScene(calculateMobilityOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Координата X.
|
||||
*/
|
||||
public function get x():Number {
|
||||
return _coords.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Координата Y.
|
||||
*/
|
||||
public function get y():Number {
|
||||
return _coords.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Координата Z.
|
||||
*/
|
||||
public function get z():Number {
|
||||
return _coords.z;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set x(value:Number):void {
|
||||
if (_coords.x != value) {
|
||||
_coords.x = value;
|
||||
addOperationToScene(changeCoordsOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set y(value:Number):void {
|
||||
if (_coords.y != value) {
|
||||
_coords.y = value;
|
||||
addOperationToScene(changeCoordsOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set z(value:Number):void {
|
||||
if (_coords.z != value) {
|
||||
_coords.z = value;
|
||||
addOperationToScene(changeCoordsOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Координаты объекта.
|
||||
*/
|
||||
public function get coords():Point3D {
|
||||
return _coords.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set coords(value:Point3D):void {
|
||||
if (!_coords.equals(value)) {
|
||||
_coords.copy(value);
|
||||
addOperationToScene(changeCoordsOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Угол поворота вокруг оси X, заданный в радианах.
|
||||
*/
|
||||
public function get rotationX():Number {
|
||||
return _rotationX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Угол поворота вокруг оси Y, заданный в радианах.
|
||||
*/
|
||||
public function get rotationY():Number {
|
||||
return _rotationY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Угол поворота вокруг оси Z, заданный в радианах.
|
||||
*/
|
||||
public function get rotationZ():Number {
|
||||
return _rotationZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set rotationX(value:Number):void {
|
||||
if (_rotationX != value) {
|
||||
_rotationX = value;
|
||||
addOperationToScene(changeRotationOrScaleOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set rotationY(value:Number):void {
|
||||
if (_rotationY != value) {
|
||||
_rotationY = value;
|
||||
addOperationToScene(changeRotationOrScaleOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set rotationZ(value:Number):void {
|
||||
if (_rotationZ != value) {
|
||||
_rotationZ = value;
|
||||
addOperationToScene(changeRotationOrScaleOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Коэффициент масштабирования вдоль оси X.
|
||||
*/
|
||||
public function get scaleX():Number {
|
||||
return _scaleX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Коэффициент масштабирования вдоль оси Y.
|
||||
*/
|
||||
public function get scaleY():Number {
|
||||
return _scaleY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Коэффициент масштабирования вдоль оси Z.
|
||||
*/
|
||||
public function get scaleZ():Number {
|
||||
return _scaleZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set scaleX(value:Number):void {
|
||||
if (_scaleX != value) {
|
||||
_scaleX = value;
|
||||
addOperationToScene(changeRotationOrScaleOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set scaleY(value:Number):void {
|
||||
if (_scaleY != value) {
|
||||
_scaleY = value;
|
||||
addOperationToScene(changeRotationOrScaleOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set scaleZ(value:Number):void {
|
||||
if (_scaleZ != value) {
|
||||
_scaleZ = value;
|
||||
addOperationToScene(changeRotationOrScaleOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Строковое представление объекта.
|
||||
*
|
||||
* @return строковое представление объекта
|
||||
*/
|
||||
public function toString():String {
|
||||
return "[" + ObjectUtils.getClassName(this) + " " + _name + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Имя объекта по умолчанию.
|
||||
*
|
||||
* @return имя объекта по умолчанию
|
||||
*/
|
||||
protected function defaultName():String {
|
||||
return "object" + ++counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Добавление операции в очередь.
|
||||
*
|
||||
* @param operation добавляемая операция
|
||||
*/
|
||||
alternativa3d function addOperationToScene(operation:Operation):void {
|
||||
if (_scene != null) {
|
||||
_scene.addOperation(operation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление операции из очереди.
|
||||
*
|
||||
* @param operation удаляемая операция
|
||||
*/
|
||||
alternativa3d function removeOperationFromScene(operation:Operation):void {
|
||||
if (_scene != null) {
|
||||
_scene.removeOperation(operation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Создание пустого объекта без какой-либо внутренней структуры. Например, если некоторый геометрический примитив при
|
||||
* своём создании формирует набор вершин, граней и поверхностей, то этот метод не должен создавать вершины, грани и
|
||||
* поверхности. Данный метод используется в методе clone() и должен быть переопределён в потомках для получения
|
||||
* правильного объекта.
|
||||
*
|
||||
* @return новый пустой объект
|
||||
*/
|
||||
protected function createEmptyObject():Object3D {
|
||||
return new Object3D();
|
||||
}
|
||||
|
||||
/**
|
||||
* Копирование свойств объекта-источника. Данный метод используется в методе clone() и должен быть переопределён в
|
||||
* потомках для получения правильного объекта. Каждый потомок должен в переопределённом методе копировать только те
|
||||
* свойства, которые добавлены к базовому классу именно в нём. Копирование унаследованных свойств выполняется
|
||||
* вызовом super.clonePropertiesFrom(source).
|
||||
*
|
||||
* @param source объект, свойства которого копируются
|
||||
*/
|
||||
protected function clonePropertiesFrom(source:Object3D):void {
|
||||
_name = source._name;
|
||||
_mobility = source._mobility;
|
||||
_coords.x = source._coords.x;
|
||||
_coords.y = source._coords.y;
|
||||
_coords.z = source._coords.z;
|
||||
_rotationX = source._rotationX;
|
||||
_rotationY = source._rotationY;
|
||||
_rotationZ = source._rotationZ;
|
||||
_scaleX = source._scaleX;
|
||||
_scaleY = source._scaleY;
|
||||
_scaleZ = source._scaleZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Клонирование объекта. Для реализации собственного клонирования наследники должны переопределять методы
|
||||
* <code>createEmptyObject()</code> и <code>clonePropertiesFrom()</code>.
|
||||
*
|
||||
* @return клонированный экземпляр объекта
|
||||
*
|
||||
* @see #createEmptyObject()
|
||||
* @see #clonePropertiesFrom()
|
||||
*/
|
||||
public function clone():Object3D {
|
||||
var copy:Object3D = createEmptyObject();
|
||||
copy.clonePropertiesFrom(this);
|
||||
|
||||
// Клонирование детей
|
||||
for (var key:* in _children) {
|
||||
var child:Object3D = key;
|
||||
copy.addChild(child.clone());
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение дочернего объекта с заданным именем.
|
||||
*
|
||||
* @param name имя дочернего объекта
|
||||
* @return любой дочерний объект с заданным именем или <code>null</code> в случае отсутствия таких объектов
|
||||
*/
|
||||
public function getChildByName(name:String):Object3D {
|
||||
for (var key:* in _children) {
|
||||
var child:Object3D = key;
|
||||
if (child._name == name) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
125
Alternativa3D5/5.3/alternativa/engine3d/core/Operation.as
Normal file
125
Alternativa3D5/5.3/alternativa/engine3d/core/Operation.as
Normal file
@@ -0,0 +1,125 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.types.Set;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public class Operation {
|
||||
|
||||
alternativa3d static const OBJECT_CALCULATE_TRANSFORMATION:uint = 0x01000000;
|
||||
alternativa3d static const OBJECT_CALCULATE_MOBILITY:uint = 0x02000000;
|
||||
alternativa3d static const VERTEX_CALCULATE_COORDS:uint = 0x03000000;
|
||||
alternativa3d static const FACE_CALCULATE_NORMAL:uint = 0x04000000;
|
||||
alternativa3d static const FACE_CALCULATE_UV:uint = 0x05000000;
|
||||
alternativa3d static const FACE_UPDATE_PRIMITIVE:uint = 0x06000000;
|
||||
alternativa3d static const SCENE_CALCULATE_BSP:uint = 0x07000000;
|
||||
alternativa3d static const FACE_UPDATE_MATERIAL:uint = 0x08000000;
|
||||
alternativa3d static const FACE_CALCULATE_FRAGMENTS_UV:uint = 0x09000000;
|
||||
alternativa3d static const CAMERA_CALCULATE_MATRIX:uint = 0x0A000000;
|
||||
alternativa3d static const CAMERA_CALCULATE_PLANES:uint = 0x0B000000;
|
||||
alternativa3d static const CAMERA_RENDER:uint = 0x0C000000;
|
||||
alternativa3d static const SCENE_CLEAR_PRIMITIVES:uint = 0x0D000000;
|
||||
|
||||
// Объект
|
||||
alternativa3d var object:Object;
|
||||
|
||||
// Метод
|
||||
alternativa3d var method:Function;
|
||||
|
||||
// Название метода
|
||||
alternativa3d var name:String;
|
||||
|
||||
// Последствия
|
||||
private var sequel:Operation;
|
||||
private var sequels:Set;
|
||||
|
||||
// Приоритет операции
|
||||
alternativa3d var priority:uint;
|
||||
|
||||
// Находится ли операция в очереди
|
||||
alternativa3d var queued:Boolean = false;
|
||||
|
||||
public function Operation(name:String, object:Object = null, method:Function = null, priority:uint = 0) {
|
||||
this.object = object;
|
||||
this.method = method;
|
||||
this.name = name;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
// Добавить последствие
|
||||
alternativa3d function addSequel(operation:Operation):void {
|
||||
if (sequel == null) {
|
||||
if (sequels == null) {
|
||||
sequel = operation;
|
||||
} else {
|
||||
sequels[operation] = true;
|
||||
}
|
||||
} else {
|
||||
if (sequel != operation) {
|
||||
sequels = new Set(true);
|
||||
sequels[sequel] = true;
|
||||
sequels[operation] = true;
|
||||
sequel = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Удалить последствие
|
||||
alternativa3d function removeSequel(operation:Operation):void {
|
||||
if (sequel == null) {
|
||||
if (sequels != null) {
|
||||
delete sequels[operation];
|
||||
var key:*;
|
||||
var single:Boolean = false;
|
||||
for (key in sequels) {
|
||||
if (single) {
|
||||
single = false;
|
||||
break;
|
||||
}
|
||||
single = true;
|
||||
}
|
||||
if (single) {
|
||||
sequel = key;
|
||||
sequels = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (sequel == operation) {
|
||||
sequel = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alternativa3d function collectSequels(collector:Array):void {
|
||||
if (sequel == null) {
|
||||
// Проверяем последствия
|
||||
for (var key:* in sequels) {
|
||||
var operation:Operation = key;
|
||||
// Если операция ещё не в очереди
|
||||
if (!operation.queued) {
|
||||
// Добавляем её в очередь
|
||||
collector.push(operation);
|
||||
// Устанавливаем флаг очереди
|
||||
operation.queued = true;
|
||||
// Вызываем добавление в очередь её последствий
|
||||
operation.collectSequels(collector);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!sequel.queued) {
|
||||
collector.push(sequel);
|
||||
sequel.queued = true;
|
||||
sequel.collectSequels(collector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return "[Operation " + (priority >>> 24) + "/" + (priority & 0xFFFFFF) + " " + object + "." + name + "]";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public class PolyPrimitive {
|
||||
|
||||
// Количество точек
|
||||
alternativa3d var num:uint;
|
||||
// Точки
|
||||
alternativa3d var points:Array = new Array();
|
||||
// UV-координаты
|
||||
alternativa3d var uvs:Array = new Array();
|
||||
|
||||
// Грань
|
||||
alternativa3d var face:Face;
|
||||
// Родительский примитив
|
||||
alternativa3d var parent:PolyPrimitive;
|
||||
// Соседний примитив (при наличии родительского)
|
||||
alternativa3d var sibling:PolyPrimitive;
|
||||
|
||||
// Фрагменты
|
||||
alternativa3d var backFragment:PolyPrimitive;
|
||||
alternativa3d var frontFragment:PolyPrimitive;
|
||||
// Рассечения
|
||||
alternativa3d var splitTime1:Number;
|
||||
alternativa3d var splitTime2:Number;
|
||||
|
||||
// BSP-нода, в которой находится примитив
|
||||
alternativa3d var node:BSPNode;
|
||||
|
||||
// Значения для расчёта качества сплиттера
|
||||
alternativa3d var splits:uint;
|
||||
alternativa3d var disbalance:int;
|
||||
// Качество примитива как сплиттера (меньше - лучше)
|
||||
public var splitQuality:Number;
|
||||
|
||||
// Приоритет в BSP-дереве. Чем ниже мобильность, тем примитив выше в дереве.
|
||||
public var mobility:int;
|
||||
|
||||
// Хранилище неиспользуемых примитивов
|
||||
static private var collector:Array = new Array();
|
||||
|
||||
// Создать примитив
|
||||
static alternativa3d function createPolyPrimitive():PolyPrimitive {
|
||||
// Достаём примитив из коллектора
|
||||
var primitive:PolyPrimitive = collector.pop();
|
||||
// Если коллектор пуст, создаём новый примитив
|
||||
if (primitive == null) {
|
||||
primitive = new PolyPrimitive();
|
||||
}
|
||||
//trace(primitive.num, primitive.points.length, primitive.face, primitive.parent, primitive.sibling, primitive.fragment1, primitive.fragment2, primitive.node);
|
||||
return primitive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Кладёт примитив в коллектор для последующего реиспользования.
|
||||
* Ссылка на грань и массивы точек зачищаются в этом методе.
|
||||
* Ссылки на фрагменты (parent, sibling, back, front) должны быть зачищены перед запуском метода.
|
||||
*
|
||||
* Исключение:
|
||||
* при сборке примитивов в сцене ссылки на back и front зачищаются после запуска метода.
|
||||
*
|
||||
* @param primitive примитив на реиспользование
|
||||
*/
|
||||
static alternativa3d function destroyPolyPrimitive(primitive:PolyPrimitive):void {
|
||||
primitive.face = null;
|
||||
for (var i:uint = 0; i < primitive.num; i++) {
|
||||
primitive.points.pop();
|
||||
primitive.uvs.pop();
|
||||
}
|
||||
collector.push(primitive);
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return "[Primitive " + face._mesh._name + "]";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
1236
Alternativa3D5/5.3/alternativa/engine3d/core/Scene3D.as
Normal file
1236
Alternativa3D5/5.3/alternativa/engine3d/core/Scene3D.as
Normal file
File diff suppressed because it is too large
Load Diff
350
Alternativa3D5/5.3/alternativa/engine3d/core/Surface.as
Normal file
350
Alternativa3D5/5.3/alternativa/engine3d/core/Surface.as
Normal file
@@ -0,0 +1,350 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.errors.FaceExistsError;
|
||||
import alternativa.engine3d.errors.FaceNotFoundError;
|
||||
import alternativa.engine3d.errors.InvalidIDError;
|
||||
import alternativa.engine3d.materials.SurfaceMaterial;
|
||||
import alternativa.types.Set;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Поверхность — набор граней, объединённых в группу. Поверхности используются для установки материалов,
|
||||
* визуализирующих грани объекта.
|
||||
*/
|
||||
public class Surface {
|
||||
// Операции
|
||||
/**
|
||||
* @private
|
||||
* Изменение набора граней
|
||||
*/
|
||||
alternativa3d var changeFacesOperation:Operation = new Operation("changeFaces", this);
|
||||
/**
|
||||
* @private
|
||||
* Изменение материала
|
||||
*/
|
||||
alternativa3d var changeMaterialOperation:Operation = new Operation("changeMaterial", this);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Меш
|
||||
*/
|
||||
alternativa3d var _mesh:Mesh;
|
||||
/**
|
||||
* @private
|
||||
* Материал
|
||||
*/
|
||||
alternativa3d var _material:SurfaceMaterial;
|
||||
/**
|
||||
* @private
|
||||
* Грани
|
||||
*/
|
||||
alternativa3d var _faces:Set = new Set();
|
||||
|
||||
/**
|
||||
* Создание экземпляра поверхности.
|
||||
*/
|
||||
public function Surface() {}
|
||||
|
||||
/**
|
||||
* Добавление грани в поверхность.
|
||||
*
|
||||
* @param face экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор грани полигонального объекта
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.FaceNotFoundError грань не найдена в полигональном объекте содержащем поверхность
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
* @throws alternativa.engine3d.errors.FaceExistsError поверхность уже содержит указанную грань
|
||||
*
|
||||
* @see Face
|
||||
*/
|
||||
public function addFace(face:Object):void {
|
||||
var byLink:Boolean = face is Face;
|
||||
|
||||
// Проверяем на нахождение поверхности в меше
|
||||
if (_mesh == null) {
|
||||
throw new FaceNotFoundError(face, this);
|
||||
}
|
||||
|
||||
// Проверяем на null
|
||||
if (face == null) {
|
||||
throw new FaceNotFoundError(null, this);
|
||||
}
|
||||
|
||||
// Проверяем наличие грани в меше
|
||||
if (byLink) {
|
||||
// Если удаляем по ссылке
|
||||
if (Face(face)._mesh != _mesh) {
|
||||
// Если грань не в меше
|
||||
throw new FaceNotFoundError(face, this);
|
||||
}
|
||||
} else {
|
||||
// Если удаляем по ID
|
||||
if (_mesh._faces[face] == undefined) {
|
||||
// Если нет грани с таким ID
|
||||
throw new FaceNotFoundError(face, this);
|
||||
} else {
|
||||
if (!(_mesh._faces[face] is Face)) {
|
||||
throw new InvalidIDError(face, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Находим грань
|
||||
var f:Face = byLink ? Face(face) : _mesh._faces[face];
|
||||
|
||||
// Проверяем наличие грани в поверхности
|
||||
if (_faces.has(f)) {
|
||||
// Если грань уже в поверхности
|
||||
throw new FaceExistsError(f, this);
|
||||
}
|
||||
|
||||
// Проверяем грань на нахождение в другой поверхности
|
||||
if (f._surface != null) {
|
||||
// Удаляем её из той поверхности
|
||||
f._surface._faces.remove(f);
|
||||
f.removeFromSurface(f._surface);
|
||||
}
|
||||
|
||||
// Добавляем грань в поверхность
|
||||
_faces.add(f);
|
||||
f.addToSurface(this);
|
||||
|
||||
// Отправляем операцию изменения набора граней
|
||||
_mesh.addOperationToScene(changeFacesOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление грани из поверхности.
|
||||
*
|
||||
* @param face экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор грани полигонального объекта
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.FaceNotFoundError поверхность не содержит указанную грань
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*
|
||||
* @see Face
|
||||
*/
|
||||
public function removeFace(face:Object):void {
|
||||
var byLink:Boolean = face is Face;
|
||||
|
||||
// Проверяем на нахождение поверхности в меше
|
||||
if (_mesh == null) {
|
||||
throw new FaceNotFoundError(face, this);
|
||||
}
|
||||
|
||||
// Проверяем на null
|
||||
if (face == null) {
|
||||
throw new FaceNotFoundError(null, this);
|
||||
}
|
||||
|
||||
// Проверяем наличие грани в меше
|
||||
if (byLink) {
|
||||
// Если удаляем по ссылке
|
||||
if (Face(face)._mesh != _mesh) {
|
||||
// Если грань не в меше
|
||||
throw new FaceNotFoundError(face, this);
|
||||
}
|
||||
} else {
|
||||
// Если удаляем по ID
|
||||
if (_mesh._faces[face] == undefined) {
|
||||
// Если нет грани с таким ID
|
||||
throw new FaceNotFoundError(face, this);
|
||||
} else {
|
||||
if (!(_mesh._faces[face] is Face)) {
|
||||
throw new InvalidIDError(face, this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Находим грань
|
||||
var f:Face = byLink ? Face(face) : _mesh._faces[face];
|
||||
|
||||
// Проверяем наличие грани в поверхности
|
||||
if (!_faces.has(f)) {
|
||||
// Если грань не в поверхности
|
||||
throw new FaceNotFoundError(f, this);
|
||||
}
|
||||
|
||||
// Удаляем грань из поверхности
|
||||
_faces.remove(f);
|
||||
f.removeFromSurface(this);
|
||||
|
||||
// Отправляем операцию изменения набора граней
|
||||
_mesh.addOperationToScene(changeFacesOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Материал поверхности. При установке нового значения, устанавливаемый материал будет удалён из старой поверхности.
|
||||
*/
|
||||
public function get material():SurfaceMaterial {
|
||||
return _material;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set material(value:SurfaceMaterial):void {
|
||||
if (_material != value) {
|
||||
// Если был материал
|
||||
if (_material != null) {
|
||||
// Удалить материал из поверхности
|
||||
_material.removeFromSurface(this);
|
||||
// Удалить материал из меша
|
||||
if (_mesh != null) {
|
||||
_material.removeFromMesh(_mesh);
|
||||
// Удалить материал из сцены
|
||||
if (_mesh._scene != null) {
|
||||
_material.removeFromScene(_mesh._scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Если новый материал
|
||||
if (value != null) {
|
||||
// Если материал был в другой поверхности
|
||||
if (value._surface != null) {
|
||||
// Удалить его оттуда
|
||||
value._surface.material = null;
|
||||
}
|
||||
// Добавить материал в поверхность
|
||||
value.addToSurface(this);
|
||||
// Добавить материал в меш
|
||||
if (_mesh != null) {
|
||||
value.addToMesh(_mesh);
|
||||
// Добавить материал в сцену
|
||||
if (_mesh._scene != null) {
|
||||
value.addToScene(_mesh._scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Сохраняем материал
|
||||
_material = value;
|
||||
// Отправляем операцию изменения материала
|
||||
addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Набор граней поверхности.
|
||||
*/
|
||||
public function get faces():Set {
|
||||
return _faces.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Полигональный объект, которому принадлежит поверхность.
|
||||
*/
|
||||
public function get mesh():Mesh {
|
||||
return _mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Идентификатор поверхности в полигональном объекте. Если поверхность не принадлежит ни одному объекту,
|
||||
* значение идентификатора равно <code>null</code>.
|
||||
*/
|
||||
public function get id():Object {
|
||||
return (_mesh != null) ? _mesh.getSurfaceId(this) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Добавление в сцену.
|
||||
*
|
||||
* @param scene
|
||||
*/
|
||||
alternativa3d function addToScene(scene:Scene3D):void {
|
||||
// Добавляем на сцену материал
|
||||
if (_material != null) {
|
||||
_material.addToScene(scene);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление из сцены.
|
||||
*
|
||||
* @param scene
|
||||
*/
|
||||
alternativa3d function removeFromScene(scene:Scene3D):void {
|
||||
// Удаляем все операции из очереди
|
||||
scene.removeOperation(changeFacesOperation);
|
||||
scene.removeOperation(changeMaterialOperation);
|
||||
// Удаляем из сцены материал
|
||||
if (_material != null) {
|
||||
_material.removeFromScene(scene);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Добавление к мешу
|
||||
* @param mesh
|
||||
*/
|
||||
alternativa3d function addToMesh(mesh:Mesh):void {
|
||||
// Подписка на операции меша
|
||||
|
||||
// Добавляем в меш материал
|
||||
if (_material != null) {
|
||||
_material.addToMesh(mesh);
|
||||
}
|
||||
// Сохранить меш
|
||||
_mesh = mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление из меша
|
||||
*
|
||||
* @param mesh
|
||||
*/
|
||||
alternativa3d function removeFromMesh(mesh:Mesh):void {
|
||||
// Отписка от операций меша
|
||||
|
||||
// Удаляем из меша материал
|
||||
if (_material != null) {
|
||||
_material.removeFromMesh(mesh);
|
||||
}
|
||||
// Удалить ссылку на меш
|
||||
_mesh = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление граней
|
||||
*/
|
||||
alternativa3d function removeFaces():void {
|
||||
for (var key:* in _faces) {
|
||||
var face:Face = key;
|
||||
_faces.remove(face);
|
||||
face.removeFromSurface(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Изменение материала
|
||||
*/
|
||||
alternativa3d function addMaterialChangedOperationToScene():void {
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(changeMaterialOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Строковое представление объекта.
|
||||
*
|
||||
* @return строковое представление объекта
|
||||
*/
|
||||
public function toString():String {
|
||||
var length:uint = _faces.length;
|
||||
var res:String = "[Surface ID:" + id + ((length > 0) ? " faces:" : "");
|
||||
var i:uint = 0;
|
||||
for (var key:* in _faces) {
|
||||
var face:Face = key;
|
||||
res += face.id + ((i < length - 1) ? ", " : "");
|
||||
i++;
|
||||
}
|
||||
res += "]";
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
250
Alternativa3D5/5.3/alternativa/engine3d/core/Vertex.as
Normal file
250
Alternativa3D5/5.3/alternativa/engine3d/core/Vertex.as
Normal file
@@ -0,0 +1,250 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.types.Matrix3D;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.types.Set;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Вершина полигона в трёхмерном пространстве. Вершина хранит свои координаты, а также ссылки на
|
||||
* полигональный объект и грани этого объекта, которым она принадлежит.
|
||||
*/
|
||||
final public class Vertex {
|
||||
// Операции
|
||||
/**
|
||||
* @private
|
||||
* Изменение локальных координат
|
||||
*/
|
||||
alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this);
|
||||
/**
|
||||
* @private
|
||||
* Расчёт глобальных координат
|
||||
*/
|
||||
alternativa3d var calculateCoordsOperation:Operation = new Operation("calculateCoords", this, calculateCoords, Operation.VERTEX_CALCULATE_COORDS);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Меш
|
||||
*/
|
||||
alternativa3d var _mesh:Mesh;
|
||||
/**
|
||||
* @private
|
||||
* Координаты точки
|
||||
*/
|
||||
alternativa3d var _coords:Point3D;
|
||||
/**
|
||||
* @private
|
||||
* Грани
|
||||
*/
|
||||
alternativa3d var _faces:Set = new Set();
|
||||
/**
|
||||
* @private
|
||||
* Координаты в сцене
|
||||
*/
|
||||
alternativa3d var globalCoords:Point3D = new Point3D();
|
||||
|
||||
/**
|
||||
* Создание экземпляра вершины.
|
||||
*
|
||||
* @param x координата вершины по оси X
|
||||
* @param y координата вершины по оси Y
|
||||
* @param z координата вершины по оси Z
|
||||
*/
|
||||
public function Vertex(x:Number = 0, y:Number = 0, z:Number = 0) {
|
||||
_coords = new Point3D(x, y, z);
|
||||
|
||||
// Изменение координат инициирует пересчёт глобальных координат
|
||||
changeCoordsOperation.addSequel(calculateCoordsOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Вызывается из операции calculateCoordsOperation для расчета глобальных координат вершины
|
||||
*/
|
||||
private function calculateCoords():void {
|
||||
globalCoords.copy(_coords);
|
||||
globalCoords.transform(_mesh.transformation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set x(value:Number):void {
|
||||
if (_coords.x != value) {
|
||||
_coords.x = value;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(changeCoordsOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set y(value:Number):void {
|
||||
if (_coords.y != value) {
|
||||
_coords.y = value;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(changeCoordsOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set z(value:Number):void {
|
||||
if (_coords.z != value) {
|
||||
_coords.z = value;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(changeCoordsOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set coords(value:Point3D):void {
|
||||
if (!_coords.equals(value)) {
|
||||
_coords.copy(value);
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(changeCoordsOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* координата вершины по оси X.
|
||||
*/
|
||||
public function get x():Number {
|
||||
return _coords.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* координата вершины по оси Y.
|
||||
*/
|
||||
public function get y():Number {
|
||||
return _coords.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* координата вершины по оси Z.
|
||||
*/
|
||||
public function get z():Number {
|
||||
return _coords.z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Координаты вершины.
|
||||
*/
|
||||
public function get coords():Point3D {
|
||||
return _coords.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Полигональный объект, которому принадлежит вершина.
|
||||
*/
|
||||
public function get mesh():Mesh {
|
||||
return _mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Множество граней, которым принадлежит вершина. Каждый элемент множества является объектом класса
|
||||
* <code>altertnativa.engine3d.core.Face</code>.
|
||||
*
|
||||
* @see Face
|
||||
*/
|
||||
public function get faces():Set {
|
||||
return _faces.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Идентификатор вершины в полигональном объекте. Если вершина не принадлежит полигональному объекту, возвращается <code>null</code>.
|
||||
*/
|
||||
public function get id():Object {
|
||||
return (_mesh != null) ? _mesh.getVertexId(this) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param scene
|
||||
*/
|
||||
alternativa3d function addToScene(scene:Scene3D):void {
|
||||
// При добавлении на сцену расчитать глобальные координаты
|
||||
scene.addOperation(calculateCoordsOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param scene
|
||||
*/
|
||||
alternativa3d function removeFromScene(scene:Scene3D):void {
|
||||
// Удаляем все операции из очереди
|
||||
scene.removeOperation(calculateCoordsOperation);
|
||||
scene.removeOperation(changeCoordsOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param mesh
|
||||
*/
|
||||
alternativa3d function addToMesh(mesh:Mesh):void {
|
||||
// Подписка на операции меша
|
||||
mesh.changeCoordsOperation.addSequel(calculateCoordsOperation);
|
||||
mesh.changeRotationOrScaleOperation.addSequel(calculateCoordsOperation);
|
||||
// Сохранить меш
|
||||
_mesh = mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param mesh
|
||||
*/
|
||||
alternativa3d function removeFromMesh(mesh:Mesh):void {
|
||||
// Отписка от операций меша
|
||||
mesh.changeCoordsOperation.removeSequel(calculateCoordsOperation);
|
||||
mesh.changeRotationOrScaleOperation.removeSequel(calculateCoordsOperation);
|
||||
// Удалить зависимые грани
|
||||
for (var key:* in _faces) {
|
||||
var face:Face = key;
|
||||
mesh.removeFace(face);
|
||||
}
|
||||
// Удалить ссылку на меш
|
||||
_mesh = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param face
|
||||
*/
|
||||
alternativa3d function addToFace(face:Face):void {
|
||||
// Подписка грани на операции
|
||||
changeCoordsOperation.addSequel(face.calculateUVOperation);
|
||||
changeCoordsOperation.addSequel(face.calculateNormalOperation);
|
||||
// Добавить грань в список
|
||||
_faces.add(face);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param face
|
||||
*/
|
||||
alternativa3d function removeFromFace(face:Face):void {
|
||||
// Отписка грани от операций
|
||||
changeCoordsOperation.removeSequel(face.calculateUVOperation);
|
||||
changeCoordsOperation.removeSequel(face.calculateNormalOperation);
|
||||
// Удалить грань из списка
|
||||
_faces.remove(face);
|
||||
}
|
||||
|
||||
/**
|
||||
* Строковое представление объекта.
|
||||
*
|
||||
* @return строковое представление объекта
|
||||
*/
|
||||
public function toString():String {
|
||||
return "[Vertex ID:" + id + " " + _coords.x.toFixed(2) + ", " + _coords.y.toFixed(2) + ", " + _coords.z.toFixed(2) + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Alternativa3D5/5.3/alternativa/engine3d/display/Skin.as
Normal file
68
Alternativa3D5/5.3/alternativa/engine3d/display/Skin.as
Normal file
@@ -0,0 +1,68 @@
|
||||
package alternativa.engine3d.display {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.Camera3D;
|
||||
import alternativa.engine3d.core.Face;
|
||||
import alternativa.engine3d.core.Operation;
|
||||
import alternativa.engine3d.core.PolyPrimitive;
|
||||
import alternativa.engine3d.materials.SurfaceMaterial;
|
||||
|
||||
import flash.display.Graphics;
|
||||
import flash.display.Sprite;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public class Skin extends Sprite {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Графика скина (для быстрого доступа)
|
||||
*/
|
||||
alternativa3d var gfx:Graphics = graphics;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Ссылка на следующий скин
|
||||
*/
|
||||
alternativa3d var nextSkin:Skin;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Примитив
|
||||
*/
|
||||
alternativa3d var primitive:PolyPrimitive;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Материал
|
||||
*/
|
||||
alternativa3d var material:SurfaceMaterial;
|
||||
|
||||
// Хранилище неиспользуемых скинов
|
||||
static private var collector:Array = new Array();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Создание скина.
|
||||
*/
|
||||
static alternativa3d function createSkin():Skin {
|
||||
// Достаём скин из коллектора
|
||||
var skin:Skin = collector.pop();
|
||||
// Если коллектор пуст, создаём новый скин
|
||||
if (skin == null) {
|
||||
skin = new Skin();
|
||||
}
|
||||
return skin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление скина, все ссылки должны быть почищены.
|
||||
*/
|
||||
static alternativa3d function destroySkin(skin:Skin):void {
|
||||
collector.push(skin);
|
||||
}
|
||||
}
|
||||
}
|
||||
149
Alternativa3D5/5.3/alternativa/engine3d/display/View.as
Normal file
149
Alternativa3D5/5.3/alternativa/engine3d/display/View.as
Normal file
@@ -0,0 +1,149 @@
|
||||
package alternativa.engine3d.display {
|
||||
import alternativa.engine3d.*;
|
||||
|
||||
import flash.display.Sprite;
|
||||
import flash.geom.Rectangle;
|
||||
import alternativa.engine3d.core.Camera3D;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Область для вывода изображения с камеры.
|
||||
*/
|
||||
public class View extends Sprite {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Область отрисовки спрайтов
|
||||
*/
|
||||
alternativa3d var canvas:Sprite;
|
||||
|
||||
private var _camera:Camera3D;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Ширина области вывода
|
||||
*/
|
||||
alternativa3d var _width:Number;
|
||||
/**
|
||||
* @private
|
||||
* Высота области вывода
|
||||
*/
|
||||
alternativa3d var _height:Number;
|
||||
|
||||
/**
|
||||
* Создание экземпляра области вывода.
|
||||
*
|
||||
* @param camera камера, изображение с которой должно выводиться
|
||||
* @param width ширина области вывода
|
||||
* @param height высота области вывода
|
||||
*/
|
||||
public function View(camera:Camera3D = null, width:Number = 0, height:Number = 0) {
|
||||
canvas = new Sprite();
|
||||
canvas.mouseEnabled = false;
|
||||
canvas.mouseChildren = false;
|
||||
canvas.tabEnabled = false;
|
||||
canvas.tabChildren = false;
|
||||
addChild(canvas);
|
||||
|
||||
this.camera = camera;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Камера с которой ведётся отображение.
|
||||
*/
|
||||
public function get camera():Camera3D {
|
||||
return _camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set camera(value:Camera3D):void {
|
||||
if (_camera != value) {
|
||||
// Если была камера
|
||||
if (_camera != null) {
|
||||
// Удалить камеру
|
||||
_camera.removeFromView(this);
|
||||
}
|
||||
// Если новая камера
|
||||
if (value != null) {
|
||||
// Если камера была в другом вьюпорте
|
||||
if (value._view != null) {
|
||||
// Удалить её оттуда
|
||||
value._view.camera = null;
|
||||
}
|
||||
// Добавить камеру
|
||||
value.addToView(this);
|
||||
} else {
|
||||
// Зачистка скинов
|
||||
if (canvas.numChildren > 0) {
|
||||
var skin:Skin = Skin(canvas.getChildAt(0));
|
||||
while (skin != null) {
|
||||
// Сохраняем следующий
|
||||
var next:Skin = skin.nextSkin;
|
||||
// Удаляем из канваса
|
||||
canvas.removeChild(skin);
|
||||
// Очистка скина
|
||||
if (skin.material != null) {
|
||||
skin.material.clear(skin);
|
||||
}
|
||||
// Зачищаем ссылки
|
||||
skin.nextSkin = null;
|
||||
skin.primitive = null;
|
||||
skin.material = null;
|
||||
// Удаляем
|
||||
Skin.destroySkin(skin);
|
||||
// Следующий устанавливаем текущим
|
||||
skin = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Сохраняем камеру
|
||||
_camera = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ширина области вывода в пикселях.
|
||||
*/
|
||||
override public function get width():Number {
|
||||
return _width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
override public function set width(value:Number):void {
|
||||
if (_width != value) {
|
||||
_width = value;
|
||||
canvas.x = _width*0.5;
|
||||
if (_camera != null) {
|
||||
camera.addOperationToScene(camera.calculatePlanesOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Высота области вывода в пикселях.
|
||||
*/
|
||||
override public function get height():Number {
|
||||
return _height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
override public function set height(value:Number):void {
|
||||
if (_height != value) {
|
||||
_height = value;
|
||||
canvas.y = _height*0.5;
|
||||
if (_camera != null) {
|
||||
camera.addOperationToScene(camera.calculatePlanesOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package alternativa.engine3d.errors {
|
||||
|
||||
import alternativa.utils.TextUtils;
|
||||
|
||||
/**
|
||||
* Базовый класс для ошибок 3d-engine.
|
||||
*/
|
||||
public class Engine3DError extends Error {
|
||||
|
||||
/**
|
||||
* Источник ошибки - объект в котором произошла ошибка.
|
||||
*/
|
||||
public var source:Object;
|
||||
|
||||
/**
|
||||
* Создание экземпляра класса.
|
||||
*
|
||||
* @param message описание ошибки
|
||||
* @param source источник ошибки
|
||||
*/
|
||||
public function Engine3DError(message:String = "", source:Object = null) {
|
||||
super(message);
|
||||
this.source = source;
|
||||
this.name = "Engine3DError";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
1053
Alternativa3D5/5.3/alternativa/engine3d/loaders/Loader3DS.as
Normal file
1053
Alternativa3D5/5.3/alternativa/engine3d/loaders/Loader3DS.as
Normal file
File diff suppressed because it is too large
Load Diff
285
Alternativa3D5/5.3/alternativa/engine3d/loaders/LoaderMTL.as
Normal file
285
Alternativa3D5/5.3/alternativa/engine3d/loaders/LoaderMTL.as
Normal file
@@ -0,0 +1,285 @@
|
||||
package alternativa.engine3d.loaders {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.types.Map;
|
||||
import alternativa.utils.ColorUtils;
|
||||
|
||||
import flash.display.Bitmap;
|
||||
import flash.display.BitmapData;
|
||||
import flash.display.Loader;
|
||||
import flash.events.ErrorEvent;
|
||||
import flash.events.Event;
|
||||
import flash.events.EventDispatcher;
|
||||
import flash.events.IOErrorEvent;
|
||||
import flash.events.SecurityErrorEvent;
|
||||
import flash.geom.Point;
|
||||
import flash.net.URLLoader;
|
||||
import flash.net.URLRequest;
|
||||
import flash.system.LoaderContext;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Загрузчик библиотеки материалов из файлов в формате MTL material format (Lightwave, OBJ).
|
||||
* <p>
|
||||
* На данный момент обеспечивается загрузка цвета, прозрачности и диффузной текстуры материала.
|
||||
*/
|
||||
internal class LoaderMTL extends EventDispatcher {
|
||||
|
||||
private static const COMMENT_CHAR:String = "#";
|
||||
private static const CMD_NEW_MATERIAL:String = "newmtl";
|
||||
private static const CMD_DIFFUSE_REFLECTIVITY:String = "Kd";
|
||||
private static const CMD_DISSOLVE:String = "d";
|
||||
private static const CMD_MAP_DIFFUSE:String = "map_Kd";
|
||||
|
||||
private static const REGEXP_TRIM:RegExp = /^\s*(.*)\s*$/;
|
||||
private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/;
|
||||
private static const REGEXP_SPLIT_LINE:RegExp = /\s+/;
|
||||
|
||||
// Загрузчик файла MTL
|
||||
private var fileLoader:URLLoader;
|
||||
// Загрузчик файлов текстур
|
||||
private var bitmapLoader:Loader;
|
||||
// Контекст загрузки для bitmapLoader
|
||||
private var loaderContext:LoaderContext;
|
||||
// Базовый URL файла MTL
|
||||
private var baseUrl:String;
|
||||
|
||||
// Библиотека загруженных материалов
|
||||
private var _library:Map;
|
||||
// Список материалов, имеющих диффузные текстуры
|
||||
private var diffuseMaps:Map;
|
||||
// Имя текущего материала
|
||||
private var materialName:String;
|
||||
// параметры текущего материала
|
||||
private var currentMaterialInfo:MaterialInfo = new MaterialInfo();
|
||||
|
||||
alternativa3d static var stubBitmapData:BitmapData;
|
||||
|
||||
/**
|
||||
* Создаёт новый экземпляр класса.
|
||||
*/
|
||||
public function LoaderMTL() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Прекращение текущей загрузки.
|
||||
*/
|
||||
public function close():void {
|
||||
try {
|
||||
fileLoader.close();
|
||||
} catch (e:Error) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Библиотека материалов. Ключами являются наименования материалов, значениями -- объекты, наследники класса
|
||||
* <code>alternativa.engine3d.loaders.MaterialInfo</code>.
|
||||
* @see alternativa.engine3d.loaders.MaterialInfo
|
||||
*/
|
||||
public function get library():Map {
|
||||
return _library;
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод выполняет загрузку файла материалов, разбор его содержимого, загрузку текстур при необходимости и
|
||||
* формирование библиотеки материалов. После окончания работы метода посылается сообщение
|
||||
* <code>Event.COMPLETE</code> и становится доступна библиотека материалов через свойство <code>library</code>.
|
||||
* <p>
|
||||
* При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения <code>IOErrorEvent.IO_ERROR</code> и
|
||||
* <code>SecurityErrorEvent.SECURITY_ERROR</code> соответственно.
|
||||
* <p>
|
||||
* Если происходит ошибка при загрузке файла текстуры, то соответствующая текстура заменяется на текстуру-заглушку.
|
||||
* <p>
|
||||
* @param url URL MTL-файла
|
||||
* @param loaderContext LoaderContext для загрузки файлов текстур
|
||||
*
|
||||
* @see #library
|
||||
*/
|
||||
public function load(url:String, loaderContext:LoaderContext = null):void {
|
||||
this.loaderContext = loaderContext;
|
||||
baseUrl = url.substring(0, url.lastIndexOf("/") + 1);
|
||||
|
||||
if (fileLoader == null) {
|
||||
fileLoader = new URLLoader();
|
||||
fileLoader.addEventListener(Event.COMPLETE, parseMTLFile);
|
||||
fileLoader.addEventListener(IOErrorEvent.IO_ERROR, onError);
|
||||
fileLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
|
||||
|
||||
bitmapLoader = new Loader();
|
||||
bitmapLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onBitmapLoadComplete);
|
||||
bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onBitmapLoadComplete);
|
||||
bitmapLoader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onBitmapLoadComplete);
|
||||
}
|
||||
|
||||
try {
|
||||
fileLoader.close();
|
||||
bitmapLoader.close();
|
||||
} catch (e:Error) {
|
||||
// Пропуск ошибки при попытке закрытия неактивных загрузчиков
|
||||
}
|
||||
|
||||
fileLoader.load(new URLRequest(url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Разбор содержимого загруженного файла материалов.
|
||||
*/
|
||||
private function parseMTLFile(e:Event = null):void {
|
||||
var lines:Array = fileLoader.data.split(REGEXP_SPLIT_FILE);
|
||||
_library = new Map();
|
||||
diffuseMaps = new Map();
|
||||
for each (var line:String in lines) {
|
||||
parseLine(line);
|
||||
}
|
||||
defineMaterial();
|
||||
|
||||
if (diffuseMaps.isEmpty()) {
|
||||
// Текстур нет, загрузка окончена
|
||||
dispatchEvent(new Event(Event.COMPLETE));
|
||||
} else {
|
||||
// Загрузка файлов текстур
|
||||
loadNextBitmap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Разбор строки файла.
|
||||
*
|
||||
* @param line строка файла
|
||||
*/
|
||||
private function parseLine(line:String):void {
|
||||
line = line.replace(REGEXP_TRIM,"$1")
|
||||
if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) {
|
||||
return;
|
||||
}
|
||||
var parts:Array = line.split(REGEXP_SPLIT_LINE);
|
||||
switch (parts[0]) {
|
||||
case CMD_NEW_MATERIAL:
|
||||
defineMaterial(parts);
|
||||
break;
|
||||
case CMD_DIFFUSE_REFLECTIVITY:
|
||||
readDiffuseReflectivity(parts);
|
||||
break;
|
||||
case CMD_DISSOLVE:
|
||||
readAlpha(parts);
|
||||
break;
|
||||
case CMD_MAP_DIFFUSE:
|
||||
parseDiffuseMapLine(parts);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Определение нового материала.
|
||||
*/
|
||||
private function defineMaterial(parts:Array = null):void {
|
||||
if (materialName != null) {
|
||||
_library[materialName] = currentMaterialInfo;
|
||||
}
|
||||
if (parts != null) {
|
||||
materialName = parts[1];
|
||||
currentMaterialInfo = new MaterialInfo();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Чтение коэффициентов диффузного отражения. Считываются только коэффициенты, заданные в формате r g b. Для текущей
|
||||
* версии движка данные коэффициенты преобразуются в цвет материала.
|
||||
*/
|
||||
private function readDiffuseReflectivity(parts:Array):void {
|
||||
var r:Number = Number(parts[1]);
|
||||
// Проверка, заданы ли коэффициенты в виде r g b
|
||||
if (!isNaN(r)) {
|
||||
var g:Number = Number(parts[2]);
|
||||
var b:Number = Number(parts[3]);
|
||||
currentMaterialInfo.color = ColorUtils.rgb(255 * r, 255 * g, 255 * b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Чтение коэффициента непрозрачности. Считывается только коэффициент, заданный числом
|
||||
* (не поддерживается параметр -halo).
|
||||
*/
|
||||
private function readAlpha(parts:Array):void {
|
||||
var alpha:Number = Number(parts[1]);
|
||||
if (!isNaN(alpha)) {
|
||||
currentMaterialInfo.alpha = alpha;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Разбор строки, задающей текстурную карту для диффузного отражения.
|
||||
*/
|
||||
private function parseDiffuseMapLine(parts:Array):void {
|
||||
var info:MTLTextureMapInfo = MTLTextureMapInfo.parse(parts);
|
||||
diffuseMaps[materialName] = info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузка файла следующей текстуры.
|
||||
*/
|
||||
private function loadNextBitmap():void {
|
||||
// Установка имени текущего текстурного материала, для которого выполняется загрузка текстуры
|
||||
for (materialName in diffuseMaps) {
|
||||
break;
|
||||
}
|
||||
bitmapLoader.load(new URLRequest(baseUrl + diffuseMaps[materialName].fileName), loaderContext);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function createStubBitmap():void {
|
||||
if (stubBitmapData == null) {
|
||||
var size:uint = 10;
|
||||
stubBitmapData = new BitmapData(size, size, false, 0);
|
||||
for (var i:uint = 0; i < size; i++) {
|
||||
for (var j:uint = 0; j < size; j+=2) {
|
||||
stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка результата загрузки файла текстуры.
|
||||
*/
|
||||
private function onBitmapLoadComplete(e:Event):void {
|
||||
var bmd:BitmapData;
|
||||
|
||||
if (e is ErrorEvent) {
|
||||
if (stubBitmapData == null) {
|
||||
createStubBitmap();
|
||||
}
|
||||
bmd = stubBitmapData;
|
||||
} else {
|
||||
bmd = Bitmap(bitmapLoader.content).bitmapData;
|
||||
}
|
||||
|
||||
var mtlInfo:MTLTextureMapInfo = diffuseMaps[materialName];
|
||||
delete diffuseMaps[materialName];
|
||||
var info:MaterialInfo = _library[materialName];
|
||||
|
||||
info.bitmapData = bmd;
|
||||
info.repeat = mtlInfo.repeat;
|
||||
info.mapOffset = new Point(mtlInfo.offsetU, mtlInfo.offsetV);
|
||||
info.mapSize = new Point(mtlInfo.sizeU, mtlInfo.sizeV);
|
||||
info.textureFileName = mtlInfo.fileName;
|
||||
|
||||
if (diffuseMaps.isEmpty()) {
|
||||
dispatchEvent(new Event(Event.COMPLETE));
|
||||
} else {
|
||||
loadNextBitmap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param e
|
||||
*/
|
||||
private function onError(e:IOErrorEvent):void {
|
||||
dispatchEvent(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
506
Alternativa3D5/5.3/alternativa/engine3d/loaders/LoaderOBJ.as
Normal file
506
Alternativa3D5/5.3/alternativa/engine3d/loaders/LoaderOBJ.as
Normal file
@@ -0,0 +1,506 @@
|
||||
package alternativa.engine3d.loaders {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.Face;
|
||||
import alternativa.engine3d.core.Mesh;
|
||||
import alternativa.engine3d.core.Object3D;
|
||||
import alternativa.engine3d.core.Surface;
|
||||
import alternativa.engine3d.core.Vertex;
|
||||
import alternativa.engine3d.materials.FillMaterial;
|
||||
import alternativa.engine3d.materials.TextureMaterial;
|
||||
import alternativa.engine3d.materials.TextureMaterialPrecision;
|
||||
import alternativa.types.Map;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.types.Texture;
|
||||
|
||||
import flash.display.BlendMode;
|
||||
import flash.events.ErrorEvent;
|
||||
import flash.events.Event;
|
||||
import flash.events.EventDispatcher;
|
||||
import flash.events.IOErrorEvent;
|
||||
import flash.events.SecurityErrorEvent;
|
||||
import flash.geom.Point;
|
||||
import flash.net.URLLoader;
|
||||
import flash.net.URLRequest;
|
||||
import flash.system.LoaderContext;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Загрузчик моделей из файла в формате OBJ. Так как OBJ не поддерживает иерархию объектов, все загруженные
|
||||
* модели помещаются в один контейнер <code>Object3D</code>.
|
||||
* <p>
|
||||
* Поддерживаюся следующие команды формата OBJ:
|
||||
* <p>
|
||||
* <table border="1" style="border-collapse: collapse">
|
||||
* <tr>
|
||||
* <th width="30%">Команда</th>
|
||||
* <th>Описание</th>
|
||||
* <th>Действие</th></tr>
|
||||
* <tr>
|
||||
* <td>o object_name</td>
|
||||
* <td>Объявление нового объекта с именем object_name</td>
|
||||
* <td>Если для текущего объекта были определены грани, то команда создаёт новый текущий объект с указанным именем,
|
||||
* иначе у текущего объекта просто меняется имя на указанное.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>v x y z</td>
|
||||
* <td>Объявление вершины с координатами x y z</td>
|
||||
* <td>Вершина помещается в общий список вершин сцены для дальнейшего использования</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>vt u [v]</td>
|
||||
* <td>Объявление текстурной вершины с координатами u v</td>
|
||||
* <td>Вершина помещается в общий список текстурных вершин сцены для дальнейшего использования</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>f v0[/vt0] v1[/vt1] ... vN[/vtN]</td>
|
||||
* <td>Объявление грани, состоящей из указанных вершин и опционально имеющую заданные текстурные координаты для вершин.</td>
|
||||
* <td>Грань добавляется к текущему активному объекту. Если есть активный материал, то грань также добавляется в поверхность
|
||||
* текущего объекта, соответствующую текущему материалу.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>usemtl material_name</td>
|
||||
* <td>Установка текущего материала с именем material_name</td>
|
||||
* <td>С момента установки текущего материала все грани, создаваемые в текущем объекте будут помещаться в поверхность,
|
||||
* соотвествующую этому материалу и имеющую идентификатор, совпадающий с его именем.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>mtllib file1 file2 ...</td>
|
||||
* <td>Объявление файлов, содержащих определения материалов</td>
|
||||
* <td>Выполняется загрузка файлов и формирование библиотеки материалов</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* <p>
|
||||
* Пример использования:
|
||||
* <pre>
|
||||
* var loader:LoaderOBJ = new LoaderOBJ();
|
||||
* loader.addEventListener(Event.COMPLETE, onLoadingComplete);
|
||||
* loader.load("foo.obj");
|
||||
*
|
||||
* function onLoadingComplete(e:Event):void {
|
||||
* scene.root.addChild(e.target.content);
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class LoaderOBJ extends EventDispatcher {
|
||||
|
||||
private static const COMMENT_CHAR:String = "#";
|
||||
|
||||
private static const CMD_OBJECT_NAME:String = "o";
|
||||
private static const CMD_VERTEX:String = "v";
|
||||
private static const CMD_TEXTURE_VERTEX:String = "vt";
|
||||
private static const CMD_FACE:String = "f";
|
||||
private static const CMD_MATERIAL_LIB:String = "mtllib";
|
||||
private static const CMD_USE_MATERIAL:String = "usemtl";
|
||||
|
||||
private static const REGEXP_TRIM:RegExp = /^\s*(.*)\s*$/;
|
||||
private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/;
|
||||
private static const REGEXP_SPLIT_LINE:RegExp = /\s+/;
|
||||
|
||||
private var basePath:String;
|
||||
private var objLoader:URLLoader;
|
||||
private var mtlLoader:LoaderMTL;
|
||||
private var loaderContext:LoaderContext;
|
||||
private var loadMaterials:Boolean;
|
||||
// Объект, содержащий все определённые в obj файле объекты
|
||||
private var _content:Object3D;
|
||||
// Текущий конструируемый объект
|
||||
private var currentObject:Mesh;
|
||||
// Стартовый индекс вершины в глобальном массиве вершин для текущего объекта
|
||||
private var vIndexStart:int = 0;
|
||||
// Стартовый индекс текстурной вершины в глобальном массиве текстурных вершин для текущего объекта
|
||||
private var vtIndexStart:int = 0;
|
||||
// Глобальный массив вершин, определённых во входном файле
|
||||
private var globalVertices:Array;
|
||||
// Глобальный массив текстурных вершин, определённых во входном файле
|
||||
private var globalTextureVertices:Array;
|
||||
// Имя текущего активного материала. Если значение равно null, то активного материала нет.
|
||||
private var currentMaterialName:String;
|
||||
// Массив граней текущего объекта, которым назначен текущий материал
|
||||
private var materialFaces:Array;
|
||||
// Массив имён файлов, содержащих определения материалов
|
||||
private var materialFileNames:Array;
|
||||
private var currentMaterialFileIndex:int;
|
||||
private var materialLibrary:Map;
|
||||
|
||||
/**
|
||||
* Сглаживание текстур при увеличении масштаба.
|
||||
*
|
||||
* @see alternativa.engine3d.materials.TextureMaterial
|
||||
*/
|
||||
public var smooth:Boolean = false;
|
||||
/**
|
||||
* Режим наложения цвета для создаваемых текстурных материалов.
|
||||
*
|
||||
* @see alternativa.engine3d.materials.TextureMaterial
|
||||
*/
|
||||
public var blendMode:String = BlendMode.NORMAL;
|
||||
/**
|
||||
* Точность перспективной коррекции для создаваемых текстурных материалов.
|
||||
*
|
||||
* @see alternativa.engine3d.materials.TextureMaterial
|
||||
*/
|
||||
public var precision:Number = TextureMaterialPrecision.MEDIUM;
|
||||
|
||||
/**
|
||||
* Устанавливаемый уровень мобильности загруженных объектов.
|
||||
*/
|
||||
public var mobility:int = 0;
|
||||
|
||||
/**
|
||||
* При установленном значении <code>true</code> выполняется преобразование координат геометрических вершин посредством
|
||||
* поворота на 90 градусов относительно оси X. Смысл флага в преобразовании системы координат, в которой вверх направлена
|
||||
* ось <code>Y</code>, в систему координат, использующуюся в Alternativa3D (вверх направлена ось <code>Z</code>).
|
||||
*/
|
||||
public var rotateModel:Boolean;
|
||||
|
||||
/**
|
||||
* Создаёт новый экземпляр загрузчика.
|
||||
*/
|
||||
public function LoaderOBJ() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Контейнер, содержащий все загруженные из OBJ-файла модели.
|
||||
*/
|
||||
public function get content():Object3D {
|
||||
return _content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Прекращение текущей загрузки.
|
||||
*/
|
||||
public function close():void {
|
||||
try {
|
||||
objLoader.close();
|
||||
} catch (e:Error) {
|
||||
}
|
||||
mtlLoader.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузка сцены из OBJ-файла по указанному адресу. По окончании загрузки посылается сообщение <code>Event.COMPLETE</code>,
|
||||
* после чего контейнер с загруженными объектами становится доступным через свойство <code>content</code>.
|
||||
* <p>
|
||||
* При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения <code>IOErrorEvent.IO_ERROR</code> и
|
||||
* <code>SecurityErrorEvent.SECURITY_ERROR</code> соответственно.
|
||||
* <p>
|
||||
* @param url URL OBJ-файла
|
||||
* @param loadMaterials флаг загрузки материалов. Если указано значение <code>true</code>, будут обработаны все файлы
|
||||
* материалов, указанные в исходном OBJ-файле.
|
||||
* @param context LoaderContext для загрузки файлов текстур
|
||||
*
|
||||
* @see #content
|
||||
*/
|
||||
public function load(url:String, loadMaterials:Boolean = true, context:LoaderContext = null):void {
|
||||
_content = null;
|
||||
this.loadMaterials = loadMaterials;
|
||||
this.loaderContext = context;
|
||||
basePath = url.substring(0, url.lastIndexOf("/") + 1);
|
||||
if (objLoader == null) {
|
||||
objLoader = new URLLoader();
|
||||
objLoader.addEventListener(Event.COMPLETE, onObjLoadComplete);
|
||||
objLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError);
|
||||
objLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError);
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
objLoader.load(new URLRequest(url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка окончания загрузки obj файла.
|
||||
*
|
||||
* @param e
|
||||
*/
|
||||
private function onObjLoadComplete(e:Event):void {
|
||||
parse(objLoader.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка ошибки при загрузке.
|
||||
*
|
||||
* @param e
|
||||
*/
|
||||
private function onObjLoadError(e:ErrorEvent):void {
|
||||
dispatchEvent(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод выполняет разбор данных, полученных из obj файла.
|
||||
*
|
||||
* @param s содержимое obj файла
|
||||
* @param materialLibrary библиотека материалов
|
||||
* @return объект, содержащий все трёхмерные объекты, определённые в obj файле
|
||||
*/
|
||||
private function parse(data:String):void {
|
||||
_content = new Object3D();
|
||||
currentObject = new Mesh();
|
||||
currentObject.mobility = mobility;
|
||||
_content.addChild(currentObject);
|
||||
|
||||
globalVertices = new Array();
|
||||
globalTextureVertices = new Array();
|
||||
materialFileNames = new Array();
|
||||
|
||||
var lines:Array = data.split(REGEXP_SPLIT_FILE);
|
||||
for each (var line:String in lines) {
|
||||
parseLine(line);
|
||||
}
|
||||
moveFacesToSurface();
|
||||
// Вся геометрия загружена и сформирована. Выполняется загрузка информации о материалах.
|
||||
if (loadMaterials && materialFileNames.length > 0) {
|
||||
loadMaterialsLibrary();
|
||||
} else {
|
||||
dispatchEvent(new Event(Event.COMPLETE));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function parseLine(line:String):void {
|
||||
line = line.replace(REGEXP_TRIM,"$1");
|
||||
if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) {
|
||||
return;
|
||||
}
|
||||
var parts:Array = line.split(REGEXP_SPLIT_LINE);
|
||||
switch (parts[0]) {
|
||||
// Объявление нового объекта
|
||||
case CMD_OBJECT_NAME:
|
||||
defineObject(parts[1]);
|
||||
break;
|
||||
// Объявление вершины
|
||||
case CMD_VERTEX:
|
||||
globalVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3])));
|
||||
break;
|
||||
// Объявление текстурной вершины
|
||||
case CMD_TEXTURE_VERTEX:
|
||||
globalTextureVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3])));
|
||||
break;
|
||||
// Объявление грани
|
||||
case CMD_FACE:
|
||||
createFace(parts);
|
||||
break;
|
||||
case CMD_MATERIAL_LIB:
|
||||
storeMaterialFileNames(parts);
|
||||
break;
|
||||
case CMD_USE_MATERIAL:
|
||||
setNewMaterial(parts);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Объявление нового объекта.
|
||||
*
|
||||
* @param objectName имя объекта
|
||||
*/
|
||||
private function defineObject(objectName:String):void {
|
||||
if (currentObject.faces.length == 0) {
|
||||
// Если у текущего объекта нет граней, то он остаётся текущим, но меняется имя
|
||||
currentObject.name = objectName;
|
||||
} else {
|
||||
// Если у текущего объекта есть грани, то обявление нового имени создаёт новый объект
|
||||
moveFacesToSurface();
|
||||
currentObject = new Mesh(objectName);
|
||||
currentObject.mobility = mobility;
|
||||
_content.addChild(currentObject);
|
||||
}
|
||||
vIndexStart = globalVertices.length;
|
||||
vtIndexStart = globalTextureVertices.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создание грани в текущем объекте.
|
||||
*
|
||||
* @param parts массив, содержащий индексы вершин грани, начиная с элемента с индексом 1
|
||||
*/
|
||||
private function createFace(parts:Array):void {
|
||||
// Стартовый индекс вершины в объекте для добавляемой грани
|
||||
var startVertexIndex:int = currentObject.vertices.length;
|
||||
// Создание вершин в объекте
|
||||
var faceVertexCount:int = parts.length - 1;
|
||||
var vtIndices:Array = new Array(3);
|
||||
// Массив идентификаторов вершин грани
|
||||
var faceVertices:Array = new Array(faceVertexCount);
|
||||
for (var i:int = 0; i < faceVertexCount; i++) {
|
||||
var indices:Array = parts[i + 1].split("/");
|
||||
// Создание вершины
|
||||
var vIdx:int = int(indices[0]);
|
||||
// Если индекс положительный, то его значение уменьшается на единицу, т.к. в obj формате индексация начинается с 1.
|
||||
// Если индекс отрицательный, то выполняется смещение на его значение назад от стартового глобального индекса вершин для текущего объекта.
|
||||
var actualIndex:int = vIdx > 0 ? vIdx - 1 : vIndexStart + vIdx;
|
||||
|
||||
var vertex:Vertex = currentObject.vertices[actualIndex];
|
||||
// Если вершины нет в объекте, она добавляется
|
||||
if (vertex == null) {
|
||||
var p:Point3D = globalVertices[actualIndex];
|
||||
if (rotateModel) {
|
||||
// В формате obj направление "вверх" совпадает с осью Y, поэтому выполняется поворот координат на 90 градусов по оси X
|
||||
vertex = currentObject.createVertex(p.x, -p.z, p.y, actualIndex);
|
||||
} else {
|
||||
vertex = currentObject.createVertex(p.x, p.y, p.z, actualIndex);
|
||||
}
|
||||
}
|
||||
faceVertices[i] = vertex;
|
||||
|
||||
// Запись индекса текстурной вершины
|
||||
if (i < 3) {
|
||||
vtIndices[i] = int(indices[1]);
|
||||
}
|
||||
}
|
||||
// Создание грани
|
||||
var face:Face = currentObject.createFace(faceVertices, currentObject.faces.length);
|
||||
// Установка uv координат
|
||||
if (vtIndices[0] != 0) {
|
||||
p = globalTextureVertices[vtIndices[0] - 1];
|
||||
face.aUV = new Point(p.x, p.y);
|
||||
p = globalTextureVertices[vtIndices[1] - 1];
|
||||
face.bUV = new Point(p.x, p.y);
|
||||
p = globalTextureVertices[vtIndices[2] - 1];
|
||||
face.cUV = new Point(p.x, p.y);
|
||||
}
|
||||
// Если есть активный материал, то грань заносится в массив для последующего формирования поверхности в объекте
|
||||
if (currentMaterialName != null) {
|
||||
materialFaces.push(face);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузка библиотек материалов.
|
||||
*
|
||||
* @param parts массив, содержащий имена файлов материалов, начиная с элемента с индексом 1
|
||||
*/
|
||||
private function storeMaterialFileNames(parts:Array):void {
|
||||
for (var i:int = 1; i < parts.length; i++) {
|
||||
materialFileNames.push(parts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка нового текущего материала.
|
||||
*
|
||||
* @param parts массив, во втором элементе которого содержится имя материала
|
||||
*/
|
||||
private function setNewMaterial(parts:Array):void {
|
||||
// Все сохранённые грани добавляются в соответствующую поверхность текущего объекта
|
||||
moveFacesToSurface();
|
||||
// Установка нового текущего материала
|
||||
currentMaterialName = parts[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление всех граней с текущим материалом в поверхность с идентификатором, совпадающим с именем материала.
|
||||
*/
|
||||
private function moveFacesToSurface():void {
|
||||
if (currentMaterialName != null && materialFaces.length > 0) {
|
||||
if (currentObject.hasSurface(currentMaterialName)) {
|
||||
// При наличии поверхности с таким идентификатором, грани добавляются в неё
|
||||
var surface:Surface = currentObject.getSurfaceById(currentMaterialName);
|
||||
for each (var face:* in materialFaces) {
|
||||
surface.addFace(face);
|
||||
}
|
||||
} else {
|
||||
// При отсутствии поверхности с таким идентификатором, создатся новая поверхность
|
||||
currentObject.createSurface(materialFaces, currentMaterialName);
|
||||
}
|
||||
}
|
||||
materialFaces = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузка материалов.
|
||||
*/
|
||||
private function loadMaterialsLibrary():void {
|
||||
if (mtlLoader == null) {
|
||||
mtlLoader = new LoaderMTL();
|
||||
mtlLoader.addEventListener(Event.COMPLETE, onMaterialFileLoadComplete);
|
||||
mtlLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError);
|
||||
mtlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError);
|
||||
}
|
||||
materialLibrary = new Map();
|
||||
|
||||
currentMaterialFileIndex = -1;
|
||||
loadNextMaterialFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка успешной загрузки библиотеки материалов.
|
||||
*/
|
||||
private function onMaterialFileLoadComplete(e:Event):void {
|
||||
materialLibrary.concat(mtlLoader.library);
|
||||
// Загрузка следующего файла материалов
|
||||
loadNextMaterialFile();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function loadNextMaterialFile():void {
|
||||
currentMaterialFileIndex++;
|
||||
if (currentMaterialFileIndex == materialFileNames.length) {
|
||||
setMaterials();
|
||||
dispatchEvent(new Event(Event.COMPLETE));
|
||||
} else {
|
||||
mtlLoader.load(basePath + materialFileNames[currentMaterialFileIndex], loaderContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка материалов.
|
||||
*/
|
||||
private function setMaterials():void {
|
||||
if (materialLibrary != null) {
|
||||
for (var objectKey:* in _content.children) {
|
||||
var object:Mesh = objectKey;
|
||||
for (var surfaceKey:* in object.surfaces) {
|
||||
var surface:Surface = object.surfaces[surfaceKey];
|
||||
// Поверхности имеют идентификаторы, соответствующие именам материалов
|
||||
var materialInfo:MaterialInfo = materialLibrary[surfaceKey];
|
||||
if (materialInfo != null) {
|
||||
if (materialInfo.bitmapData == null) {
|
||||
surface.material = new FillMaterial(materialInfo.color, materialInfo.alpha, blendMode);
|
||||
} else {
|
||||
surface.material = new TextureMaterial(new Texture(materialInfo.bitmapData, materialInfo.textureFileName), materialInfo.alpha, materialInfo.repeat, (materialInfo.bitmapData != LoaderMTL.stubBitmapData) ? smooth : false, blendMode, -1, 0, precision);
|
||||
transformUVs(surface, materialInfo.mapOffset, materialInfo.mapSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод выполняет преобразование UV-координат текстурированных граней. В связи с тем, что в формате MRL предусмотрено
|
||||
* масштабирование и смещение текстурной карты в UV-пространстве, а в движке такой фунциональности нет, необходимо
|
||||
* эмулировать преобразования текстуры преобразованием UV-координат граней. Преобразования выполняются исходя из предположения,
|
||||
* что текстурное пространство сначала масштабируется относительно центра, а затем сдвигается на указанную величину
|
||||
* смещения.
|
||||
*
|
||||
* @param surface поверхность, грани которой обрабатываюся
|
||||
* @param mapOffset смещение текстурной карты. Значение mapOffset.x указывает смещение по U, значение mapOffset.y
|
||||
* указывает смещение по V.
|
||||
* @param mapSize коэффициенты масштабирования текстурной карты. Значение mapSize.x указывает коэффициент масштабирования
|
||||
* по оси U, значение mapSize.y указывает коэффициент масштабирования по оси V.
|
||||
*/
|
||||
private function transformUVs(surface:Surface, mapOffset:Point, mapSize:Point):void {
|
||||
for (var key:* in surface.faces) {
|
||||
var face:Face = key;
|
||||
var uv:Point = face.aUV;
|
||||
uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
|
||||
uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
|
||||
face.aUV = uv;
|
||||
uv = face.bUV;
|
||||
uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
|
||||
uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
|
||||
face.bUV = uv;
|
||||
uv = face.cUV;
|
||||
uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
|
||||
uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
|
||||
face.cUV = uv;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package alternativa.engine3d.loaders {
|
||||
/**
|
||||
* @private
|
||||
* Класс содержит информацию о текстуре в формате MTL material format (Lightwave, OBJ) и функционал для разбора
|
||||
* описания текстуры.
|
||||
* Описание формата можно посмотреть по адресу: http://local.wasp.uwa.edu.au/~pbourke/dataformats/mtl/
|
||||
*/
|
||||
internal class MTLTextureMapInfo {
|
||||
|
||||
// Ассоциация параметров команды объявления текстуры и методов для их чтения
|
||||
private static const optionReaders:Object = {
|
||||
"-clamp": clampReader,
|
||||
"-o": offsetReader,
|
||||
"-s": sizeReader,
|
||||
|
||||
"-blendu": stubReader,
|
||||
"-blendv": stubReader,
|
||||
"-bm": stubReader,
|
||||
"-boost": stubReader,
|
||||
"-cc": stubReader,
|
||||
"-imfchan": stubReader,
|
||||
"-mm": stubReader,
|
||||
"-t": stubReader,
|
||||
"-texres": stubReader
|
||||
};
|
||||
|
||||
// Смещение в текстурном пространстве
|
||||
public var offsetU:Number = 0;
|
||||
public var offsetV:Number = 0;
|
||||
public var offsetW:Number = 0;
|
||||
|
||||
// Масштабирование текстурного пространства
|
||||
public var sizeU:Number = 1;
|
||||
public var sizeV:Number = 1;
|
||||
public var sizeW:Number = 1;
|
||||
|
||||
// Флаг повторения текстуры
|
||||
public var repeat:Boolean = true;
|
||||
// Имя файла текстуры
|
||||
public var fileName:String;
|
||||
|
||||
/**
|
||||
* Метод выполняет разбор данных о текстуре.
|
||||
*
|
||||
* @param parts Данные о текстуре. Массив должен содержать части разделённой по пробелам входной строки MTL-файла.
|
||||
* @return объект, содержащий данные о текстуре
|
||||
*/
|
||||
public static function parse(parts:Array):MTLTextureMapInfo {
|
||||
var info:MTLTextureMapInfo = new MTLTextureMapInfo();
|
||||
// Начальное значение индекса единица, т.к. первый элемент массива содержит тип текстуры
|
||||
var index:int = 1;
|
||||
var reader:Function;
|
||||
// Чтение параметров текстуры
|
||||
while ((reader = optionReaders[parts[index]]) != null) {
|
||||
index = reader(index, parts, info);
|
||||
}
|
||||
// Если не было ошибок, последний элемент массива должен содержать имя файла текстуры
|
||||
info.fileName = parts[index];
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Читатель-заглушка. Пропускает все неподдерживаемые параметры.
|
||||
*/
|
||||
private static function stubReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
|
||||
index++;
|
||||
var maxIndex:int = parts.length - 1;
|
||||
while ((MTLTextureMapInfo.optionReaders[parts[index]] == null) && (index < maxIndex)) {
|
||||
index++;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод чтения параметров масштабирования текстурного пространства.
|
||||
*/
|
||||
private static function sizeReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
|
||||
info.sizeU = Number(parts[index + 1]);
|
||||
index += 2;
|
||||
var value:Number = Number(parts[index]);
|
||||
if (!isNaN(value)) {
|
||||
info.sizeV = value;
|
||||
index++;
|
||||
value = Number(parts[index]);
|
||||
if (!isNaN(value)) {
|
||||
info.sizeW = value;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод чтения параметров смещения текстуры.
|
||||
*/
|
||||
private static function offsetReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
|
||||
info.offsetU = Number(parts[index + 1]);
|
||||
index += 2;
|
||||
var value:Number = Number(parts[index]);
|
||||
if (!isNaN(value)) {
|
||||
info.offsetV = value;
|
||||
index++;
|
||||
value = Number(parts[index]);
|
||||
if (!isNaN(value)) {
|
||||
info.offsetW = value;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод чтения параметра повторения текстуры.
|
||||
*/
|
||||
private static function clampReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
|
||||
info.repeat = parts[index + 1] == "off";
|
||||
return index + 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package alternativa.engine3d.loaders {
|
||||
import flash.display.BitmapData;
|
||||
import flash.geom.Point;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Класс содержит обобщённую информацию о материале.
|
||||
*/
|
||||
internal class MaterialInfo {
|
||||
public var color:uint;
|
||||
public var alpha:Number;
|
||||
|
||||
public var textureFileName:String;
|
||||
public var bitmapData:BitmapData;
|
||||
public var repeat:Boolean;
|
||||
|
||||
public var mapOffset:Point;
|
||||
public var mapSize:Point;
|
||||
}
|
||||
}
|
||||
198
Alternativa3D5/5.3/alternativa/engine3d/materials/DevMaterial.as
Normal file
198
Alternativa3D5/5.3/alternativa/engine3d/materials/DevMaterial.as
Normal file
@@ -0,0 +1,198 @@
|
||||
package alternativa.engine3d.materials {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.Camera3D;
|
||||
import alternativa.engine3d.display.Skin;
|
||||
|
||||
import flash.display.BlendMode;
|
||||
import flash.display.Graphics;
|
||||
import alternativa.utils.ColorUtils;
|
||||
import alternativa.engine3d.core.PolyPrimitive;
|
||||
import alternativa.engine3d.core.BSPNode;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Материал, заполняющий грань сплошной заливкой цветом в соответствии с уровнем мобильности. Помимо заливки материал может рисовать границу
|
||||
* полигона линией заданной толщины и цвета.
|
||||
*/
|
||||
public class DevMaterial extends SurfaceMaterial {
|
||||
/**
|
||||
* @private
|
||||
* Цвет
|
||||
*/
|
||||
alternativa3d var _color:uint;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Толщина линий обводки
|
||||
*/
|
||||
alternativa3d var _wireThickness:Number;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Цвет линий обводки
|
||||
*/
|
||||
alternativa3d var _wireColor:uint;
|
||||
|
||||
/**
|
||||
* Создание экземпляра класса.
|
||||
*
|
||||
* @param color цвет заливки
|
||||
* @param alpha прозрачность
|
||||
* @param blendMode режим наложения цвета
|
||||
* @param wireThickness толщина линии обводки
|
||||
* @param wireColor цвет линии обводки
|
||||
*/
|
||||
public function DevMaterial(color:uint = 0xFFFFFF, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) {
|
||||
super(alpha, blendMode);
|
||||
_color = color;
|
||||
_wireThickness = wireThickness;
|
||||
_wireColor = wireColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @param camera
|
||||
* @param skin
|
||||
* @param length
|
||||
* @param points
|
||||
*/
|
||||
override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
|
||||
skin.alpha = _alpha;
|
||||
skin.blendMode = _blendMode;
|
||||
|
||||
var i:uint;
|
||||
var point:DrawPoint;
|
||||
var gfx:Graphics = skin.gfx;
|
||||
|
||||
/*
|
||||
//Мобильность
|
||||
var param:int = skin.primitive.mobility*10;
|
||||
*/
|
||||
|
||||
/*
|
||||
// Уровень распиленности
|
||||
var param:int = 0;
|
||||
var prm:PolyPrimitive = skin.primitive;
|
||||
while (prm != null) {
|
||||
prm = prm.parent;
|
||||
param++;
|
||||
}
|
||||
param *= 10;
|
||||
*/
|
||||
|
||||
// Уровень в BSP-дереве
|
||||
var param:int = 0;
|
||||
var node:BSPNode = skin.primitive.node;
|
||||
while (node != null) {
|
||||
node = node.parent;
|
||||
param++;
|
||||
}
|
||||
param *= 5;
|
||||
|
||||
var c:uint = ColorUtils.rgb(param, param, param);
|
||||
|
||||
if (camera._orthographic) {
|
||||
gfx.beginFill(c);
|
||||
if (_wireThickness >= 0) {
|
||||
gfx.lineStyle(_wireThickness, _wireColor);
|
||||
}
|
||||
point = points[0];
|
||||
gfx.moveTo(point.x, point.y);
|
||||
for (i = 1; i < length; i++) {
|
||||
point = points[i];
|
||||
gfx.lineTo(point.x, point.y);
|
||||
}
|
||||
if (_wireThickness >= 0) {
|
||||
point = points[0];
|
||||
gfx.lineTo(point.x, point.y);
|
||||
}
|
||||
} else {
|
||||
gfx.beginFill(c);
|
||||
if (_wireThickness >= 0) {
|
||||
gfx.lineStyle(_wireThickness, _wireColor);
|
||||
}
|
||||
point = points[0];
|
||||
var perspective:Number = camera.focalLength/point.z;
|
||||
gfx.moveTo(point.x*perspective, point.y*perspective);
|
||||
for (i = 1; i < length; i++) {
|
||||
point = points[i];
|
||||
perspective = camera.focalLength/point.z;
|
||||
gfx.lineTo(point.x*perspective, point.y*perspective);
|
||||
}
|
||||
if (_wireThickness >= 0) {
|
||||
point = points[0];
|
||||
perspective = camera.focalLength/point.z;
|
||||
gfx.lineTo(point.x*perspective, point.y*perspective);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Цвет заливки.
|
||||
*/
|
||||
public function get color():uint {
|
||||
return _color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set color(value:uint):void {
|
||||
if (_color != value) {
|
||||
_color = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Толщина линии обводки. Если значение отрицательное, то отрисовка линии не выполняется.
|
||||
*/
|
||||
public function get wireThickness():Number {
|
||||
return _wireThickness;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set wireThickness(value:Number):void {
|
||||
if (_wireThickness != value) {
|
||||
_wireThickness = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Цвет линии обводки.
|
||||
*/
|
||||
public function get wireColor():uint {
|
||||
return _wireColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set wireColor(value:uint):void {
|
||||
if (_wireColor != value) {
|
||||
_wireColor = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override public function clone():Material {
|
||||
var res:DevMaterial = new DevMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package alternativa.engine3d.materials {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public final class DrawPoint {
|
||||
|
||||
public var x:Number;
|
||||
public var y:Number;
|
||||
public var z:Number;
|
||||
public var u:Number;
|
||||
public var v:Number;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
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 прозрачность
|
||||
* @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
|
||||
*
|
||||
* @param camera
|
||||
* @param skin
|
||||
* @param length
|
||||
* @param points
|
||||
*/
|
||||
override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
|
||||
skin.alpha = _alpha;
|
||||
skin.blendMode = _blendMode;
|
||||
|
||||
var i:uint;
|
||||
var point:DrawPoint;
|
||||
var gfx:Graphics = skin.gfx;
|
||||
|
||||
if (camera._orthographic) {
|
||||
gfx.beginFill(_color);
|
||||
if (_wireThickness >= 0) {
|
||||
gfx.lineStyle(_wireThickness, _wireColor);
|
||||
}
|
||||
point = points[0];
|
||||
gfx.moveTo(point.x, point.y);
|
||||
for (i = 1; i < length; i++) {
|
||||
point = points[i];
|
||||
gfx.lineTo(point.x, point.y);
|
||||
}
|
||||
if (_wireThickness >= 0) {
|
||||
point = points[0];
|
||||
gfx.lineTo(point.x, point.y);
|
||||
}
|
||||
} else {
|
||||
gfx.beginFill(_color);
|
||||
if (_wireThickness >= 0) {
|
||||
gfx.lineStyle(_wireThickness, _wireColor);
|
||||
}
|
||||
point = points[0];
|
||||
var perspective:Number = camera.focalLength/point.z;
|
||||
gfx.moveTo(point.x*perspective, point.y*perspective);
|
||||
for (i = 1; i < length; i++) {
|
||||
point = points[i];
|
||||
perspective = camera.focalLength/point.z;
|
||||
gfx.lineTo(point.x*perspective, point.y*perspective);
|
||||
}
|
||||
if (_wireThickness >= 0) {
|
||||
point = points[0];
|
||||
perspective = camera.focalLength/point.z;
|
||||
gfx.lineTo(point.x*perspective, point.y*perspective);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Цвет заливки.
|
||||
*/
|
||||
public function get color():uint {
|
||||
return _color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set color(value:uint):void {
|
||||
if (_color != value) {
|
||||
_color = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Толщина линии обводки. Если значение отрицательное, то отрисовка линии не выполняется.
|
||||
*/
|
||||
public function get wireThickness():Number {
|
||||
return _wireThickness;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set wireThickness(value:Number):void {
|
||||
if (_wireThickness != value) {
|
||||
_wireThickness = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Цвет линии обводки.
|
||||
*/
|
||||
public function get wireColor():uint {
|
||||
return _wireColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set wireColor(value:uint):void {
|
||||
if (_wireColor != value) {
|
||||
_wireColor = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override public function clone():Material {
|
||||
var res:FillMaterial = new FillMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package alternativa.engine3d.materials {
|
||||
import alternativa.engine3d.*;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Базовый класс для материалов.
|
||||
*/
|
||||
public class Material {
|
||||
|
||||
/**
|
||||
* Создание клона материала.
|
||||
*
|
||||
* @return клон материала
|
||||
*/
|
||||
public function clone():Material {
|
||||
return new Material();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
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
|
||||
* Альфа
|
||||
*/
|
||||
alternativa3d var _alpha:Number;
|
||||
/**
|
||||
* @private
|
||||
* Режим наложения
|
||||
*/
|
||||
alternativa3d var _blendMode:String = BlendMode.NORMAL;
|
||||
/**
|
||||
* @private
|
||||
* Материал использует информация об UV-координатах
|
||||
*/
|
||||
alternativa3d var useUV:Boolean = false;
|
||||
|
||||
/**
|
||||
* Создание экземпляра класса.
|
||||
*
|
||||
* @param alpha прозрачность материала
|
||||
* @param blendMode режим наложения цвета
|
||||
*/
|
||||
public function SurfaceMaterial(alpha:Number = 1, blendMode:String = BlendMode.NORMAL) {
|
||||
_alpha = alpha;
|
||||
_blendMode = 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Прозрачность материала.
|
||||
*/
|
||||
public function get alpha():Number {
|
||||
return _alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set alpha(value:Number):void {
|
||||
if (_alpha != value) {
|
||||
_alpha = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Режим наложения цвета.
|
||||
*/
|
||||
public function get blendMode():String {
|
||||
return _blendMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set blendMode(value:String):void {
|
||||
if (_blendMode != value) {
|
||||
_blendMode = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d function clear(skin:Skin):void {
|
||||
skin.gfx.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
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.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 прозрачность материала
|
||||
* @param repeat повтор текстуры при заполнении
|
||||
* @param smooth сглаживание текстуры при увеличении масштаба
|
||||
* @param blendMode режим наложения цвета
|
||||
* @param wireThickness толщина линии обводки
|
||||
* @param wireColor цвет линии обводки
|
||||
* @param precision точность перспективной коррекции. Может быть задана одной из констант класса
|
||||
* <code>TextureMaterialPrecision</code> или числом типа Number. Во втором случае, чем меньшее значение будет
|
||||
* установлено, тем более качественная перспективная коррекция будет выполнена, и тем больше времени будет затрачено
|
||||
* на расчёт кадра.
|
||||
*/
|
||||
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
|
||||
* @param primitive
|
||||
* @return
|
||||
*/
|
||||
override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
|
||||
return _texture != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param camera
|
||||
* @param skin
|
||||
* @param length
|
||||
* @param points
|
||||
*/
|
||||
override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
|
||||
skin.alpha = _alpha;
|
||||
skin.blendMode = _blendMode;
|
||||
|
||||
var i:uint;
|
||||
var point:DrawPoint;
|
||||
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.uvMatrix, _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;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Повтор текстуры при заливке. Более подробную информацию можно найти в описании метода
|
||||
* <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;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Сглаживание текстуры при увеличении масштаба. Более подробную информацию можно найти в описании метода
|
||||
* <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;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Цвет линии обводки полигона.
|
||||
*/
|
||||
public function get wireColor():uint {
|
||||
return _wireColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set wireColor(value:uint):void {
|
||||
if (_wireColor != value) {
|
||||
_wireColor = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Точность перспективной коррекции.
|
||||
*/
|
||||
public function get precision():Number {
|
||||
return _precision;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set precision(value:Number):void {
|
||||
if (_precision != value) {
|
||||
_precision = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override public function clone():Material {
|
||||
var res:TextureMaterial = new TextureMaterial(_texture, _alpha, _repeat, _smooth, _blendMode, _wireThickness, _wireColor, _precision);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package alternativa.engine3d.materials {
|
||||
|
||||
/**
|
||||
* Класс содержит константы точности перспективной коррекции текстурного материала.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package alternativa.engine3d.materials {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.Camera3D;
|
||||
import alternativa.engine3d.display.Skin;
|
||||
|
||||
import flash.display.BlendMode;
|
||||
import flash.display.Graphics;
|
||||
import alternativa.engine3d.core.PolyPrimitive;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Материал для рисования рёбер полигонов.
|
||||
*/
|
||||
public class WireMaterial extends SurfaceMaterial {
|
||||
/**
|
||||
* @private
|
||||
* Цвет
|
||||
*/
|
||||
alternativa3d var _color:uint;
|
||||
/**
|
||||
* @private
|
||||
* Толщина линий
|
||||
*/
|
||||
alternativa3d var _thickness:Number;
|
||||
|
||||
/**
|
||||
* Создание экземпляра класса.
|
||||
*
|
||||
* @param thickness толщина линий
|
||||
* @param color цвет линий
|
||||
* @param alpha прозрачность линий
|
||||
* @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
|
||||
* @param primitive
|
||||
* @return
|
||||
*/
|
||||
override alternativa3d function canDraw(primitive:PolyPrimitive):Boolean {
|
||||
return _thickness >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param camera
|
||||
* @param skin
|
||||
* @param length
|
||||
* @param points
|
||||
*/
|
||||
override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
|
||||
skin.alpha = _alpha;
|
||||
skin.blendMode = _blendMode;
|
||||
|
||||
var i:uint;
|
||||
var point:DrawPoint;
|
||||
var gfx:Graphics = skin.gfx;
|
||||
|
||||
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;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Толщина линий. Если толщина отрицательная, то отрисовка не происходит.
|
||||
*/
|
||||
public function get thickness():Number {
|
||||
return _thickness;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set thickness(value:Number):void {
|
||||
if (_thickness != value) {
|
||||
_thickness = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override public function clone():Material {
|
||||
return new WireMaterial(_thickness, _color, _alpha, _blendMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Alternativa3D5/5.3/alternativa/engine3d/physics/Collision.as
Normal file
17
Alternativa3D5/5.3/alternativa/engine3d/physics/Collision.as
Normal file
@@ -0,0 +1,17 @@
|
||||
package alternativa.engine3d.physics {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.Face;
|
||||
import alternativa.types.Point3D;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public class Collision {
|
||||
public var face:Face;
|
||||
public var normal:Point3D;
|
||||
public var offset:Number;
|
||||
public var point:Point3D;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package alternativa.engine3d.physics {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.BSPNode;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public class CollisionPlane {
|
||||
public var node:BSPNode;
|
||||
public var infront:Boolean;
|
||||
public var sourceOffset:Number;
|
||||
public var destinationOffset:Number;
|
||||
|
||||
// Хранилище неиспользуемых плоскостей
|
||||
static private var collector:Array = new Array();
|
||||
|
||||
// Создать плоскость
|
||||
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;
|
||||
}
|
||||
|
||||
// Удалить плоскость, все ссылки должны быть почищены
|
||||
static alternativa3d function destroyCollisionPlane(plane:CollisionPlane):void {
|
||||
collector.push(plane);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
package alternativa.engine3d.physics {
|
||||
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.BSPNode;
|
||||
import alternativa.engine3d.core.PolyPrimitive;
|
||||
import alternativa.engine3d.core.Scene3D;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.types.Set;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Класс реализует алгоритм определения столкновений сферы с полигонами сцены.
|
||||
*/
|
||||
public class SphereCollider {
|
||||
|
||||
// Максимальное кол-во попыток найти свободное от столкновения со сценой направление
|
||||
private static const maxCollisions:uint = 5000;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Радиус сферы
|
||||
*/
|
||||
public var sphereRadius:Number;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Список объектов, с которыми не будут проверяться столкновения
|
||||
*/
|
||||
public var ignoreSet:Set;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Установка погрешности определения расстояний.
|
||||
*/
|
||||
public var offsetThreshold:Number = 0.01;
|
||||
|
||||
private var scene:Scene3D;
|
||||
|
||||
private var collisionSource:Point3D;
|
||||
private var collisionVector:Point3D;
|
||||
private var collisionVelocity:Point3D = new Point3D();
|
||||
private var collisionDestination:Point3D = new Point3D();
|
||||
private var collisionPlanes:Array = new Array();
|
||||
private var collisionPlanePoint:Point3D = new Point3D();
|
||||
private var collisionPrimitive:PolyPrimitive;
|
||||
private var collisionPrimitivePoint:Point3D = new Point3D();
|
||||
private var collisionPrimitiveNearest:PolyPrimitive;
|
||||
private var collisionPrimitiveNearestLengthSqr:Number;
|
||||
private var collisionPoint:Point3D = new Point3D();
|
||||
private var collisionNormal:Point3D = new Point3D();
|
||||
private var collisionOffset:Number;
|
||||
private var coords:Point3D = new Point3D();
|
||||
private var collision:Collision = new Collision();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Создание экземпляра класса.
|
||||
*
|
||||
* @param scene сцена, в которой определяется столкновение
|
||||
*/
|
||||
public function SphereCollider(scene:Scene3D, radius:Number = 0) {
|
||||
if (scene == null) {
|
||||
throw new Error("SphereCollider: scene is null");
|
||||
}
|
||||
this.scene = scene;
|
||||
sphereRadius = radius;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Считает конечное положения сферы по направлению вектора перемещения, с учетом столкновений
|
||||
* её с объектами сцены, кроме объектов из списка ignoreSet
|
||||
*
|
||||
* @param source начальное положение сферы
|
||||
* @param velocity вектор перемещения сферы
|
||||
* @param destination результат выполнения метода - конечное положение сферы
|
||||
*/
|
||||
public function calculateDestination(source:Point3D, velocity:Point3D, destination:Point3D):void {
|
||||
|
||||
// Сохраняем вектор перемещения во внутренний вектор
|
||||
collisionVelocity.x = velocity.x;
|
||||
collisionVelocity.y = velocity.y;
|
||||
collisionVelocity.z = velocity.z;
|
||||
|
||||
// Расчеты не производятся, если скорость мала
|
||||
if (collisionVelocity.x <= offsetThreshold && collisionVelocity.x >= -offsetThreshold &&
|
||||
collisionVelocity.y <= offsetThreshold && collisionVelocity.y >= -offsetThreshold &&
|
||||
collisionVelocity.z <= offsetThreshold && collisionVelocity.z >= -offsetThreshold) {
|
||||
destination.x = source.x;
|
||||
destination.y = source.y;
|
||||
destination.z = source.z;
|
||||
return;
|
||||
}
|
||||
|
||||
coords.x = source.x;
|
||||
coords.y = source.y;
|
||||
coords.z = source.z;
|
||||
|
||||
destination.x = source.x + collisionVelocity.x;
|
||||
destination.y = source.y + collisionVelocity.y;
|
||||
destination.z = source.z + collisionVelocity.z;
|
||||
var collisions:uint = 0;
|
||||
do {
|
||||
var collision:Collision = calculateCollision(coords, collisionVelocity, this.collision);
|
||||
if (collision != null) {
|
||||
// Вынос точки назначения из-за плоскости столкновения на высоту радиуса сферы над плоскостью по направлению нормали
|
||||
var offset:Number = sphereRadius + offsetThreshold + collision.offset - destination.x*collision.normal.x - destination.y*collision.normal.y - destination.z*collision.normal.z;
|
||||
destination.x += collision.normal.x * offset;
|
||||
destination.y += collision.normal.y * offset;
|
||||
destination.z += collision.normal.z * offset;
|
||||
// Коррекция текущих кординат центра сферы для следующей итерации
|
||||
coords.x = collision.point.x + collision.normal.x * (sphereRadius + offsetThreshold);
|
||||
coords.y = collision.point.y + collision.normal.y * (sphereRadius + offsetThreshold);
|
||||
coords.z = collision.point.z + collision.normal.z * (sphereRadius + offsetThreshold);
|
||||
// Коррекция вектора скорости. Результирующий вектор направлен вдоль плоскости столкновения.
|
||||
collisionVelocity.x = destination.x - coords.x;
|
||||
collisionVelocity.y = destination.y - coords.y;
|
||||
collisionVelocity.z = destination.z - coords.z;
|
||||
|
||||
// Если смещение слишком мало, останавливаемся
|
||||
if (collisionVelocity.x <= offsetThreshold && collisionVelocity.x >= -offsetThreshold &&
|
||||
collisionVelocity.y <= offsetThreshold && collisionVelocity.y >= -offsetThreshold &&
|
||||
collisionVelocity.z <= offsetThreshold && collisionVelocity.z >= -offsetThreshold) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while ((collision != null) && (++collisions < maxCollisions));
|
||||
|
||||
// Если после maxCollisions ходов выход не найден, то остаемся на старом месте
|
||||
if (collisions == maxCollisions) {
|
||||
destination.x = source.x;
|
||||
destination.y = source.y;
|
||||
destination.z = source.z;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Определение столкновения сферы с полигонами сцены.
|
||||
*
|
||||
* @param source исходная точка положения сферы в сцене
|
||||
* @param vector вектор перемещения сферы в сцене
|
||||
* @param collision экземпляр класса для возврата в него результата
|
||||
*
|
||||
* @return объект, содержащий параметры столкновения или <code>null</code> в случае отсутствия столкновений
|
||||
*/
|
||||
public function calculateCollision(source:Point3D, vector:Point3D, collision:Collision = null):Collision {
|
||||
collisionSource = source;
|
||||
collisionVector = vector;
|
||||
collisionDestination.x = collisionSource.x + collisionVector.x;
|
||||
collisionDestination.y = collisionSource.y + collisionVector.y;
|
||||
collisionDestination.z = collisionSource.z + collisionVector.z;
|
||||
|
||||
// Собираем потенциальные плоскости столкновения
|
||||
collectCollisionPlanes(scene.bsp);
|
||||
|
||||
// Перебираем плоскости по мере удалённости
|
||||
collisionPlanes.sortOn("sourceOffset", Array.NUMERIC | Array.DESCENDING);
|
||||
var plane:CollisionPlane;
|
||||
// Пока не найдём столкновение с примитивом или плоскости не кончатся
|
||||
while ((plane = collisionPlanes.pop()) != null) {
|
||||
if (collisionPrimitive == null) {
|
||||
calculateCollisionWithPlane(plane);
|
||||
}
|
||||
plane.node = null;
|
||||
CollisionPlane.destroyCollisionPlane(plane);
|
||||
}
|
||||
|
||||
if (collisionPrimitive != null) {
|
||||
if (collision == null) {
|
||||
collision = new Collision();
|
||||
}
|
||||
collision.face = collisionPrimitive.face;
|
||||
collision.normal = collisionNormal.clone();
|
||||
collision.point = collisionPoint.clone();
|
||||
collision.offset = collisionOffset;
|
||||
} else {
|
||||
collision = null;
|
||||
}
|
||||
|
||||
collisionSource = null;
|
||||
collisionVector = null;
|
||||
collisionPrimitive = null;
|
||||
return collision;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Сбор потенциальных плоскостей столкновения.
|
||||
*
|
||||
* @param node текущий узел BSP-дерева
|
||||
*/
|
||||
private function collectCollisionPlanes(node:BSPNode):void {
|
||||
if (node != null) {
|
||||
var sourceOffset:Number = collisionSource.x * node.normal.x + collisionSource.y * node.normal.y + collisionSource.z * node.normal.z - node.offset;
|
||||
var destinationOffset:Number = collisionDestination.x * node.normal.x + collisionDestination.y * node.normal.y + collisionDestination.z * node.normal.z - node.offset;
|
||||
var plane:CollisionPlane;
|
||||
|
||||
if (sourceOffset >= 0) {
|
||||
// Перед нодой
|
||||
|
||||
// Проверяем передние ноды
|
||||
collectCollisionPlanes(node.front);
|
||||
|
||||
if (destinationOffset < sphereRadius) {
|
||||
|
||||
// Нашли пересечение с плоскостью
|
||||
plane = CollisionPlane.createCollisionPlane(node, true, sourceOffset, destinationOffset);
|
||||
collisionPlanes.push(plane);
|
||||
|
||||
// Проверяем задние ноды
|
||||
collectCollisionPlanes(node.back);
|
||||
}
|
||||
} else {
|
||||
// За нодой
|
||||
|
||||
// Проверяем задние ноды
|
||||
collectCollisionPlanes(node.back);
|
||||
|
||||
if (destinationOffset > -sphereRadius) {
|
||||
|
||||
// Если в ноде есть сзади примитивы
|
||||
if (node.backPrimitives != null) {
|
||||
// Нашли пересечение с плоскостью
|
||||
plane = CollisionPlane.createCollisionPlane(node, false, -sourceOffset, -destinationOffset);
|
||||
collisionPlanes.push(plane);
|
||||
}
|
||||
|
||||
// Проверяем передние ноды
|
||||
collectCollisionPlanes(node.front);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Определение пересечения сферы с примитивами, лежащими в заданной плоскости.
|
||||
*
|
||||
* @param plane плоскость, содержащая примитивы для проверки
|
||||
*/
|
||||
private function calculateCollisionWithPlane(plane:CollisionPlane):void {
|
||||
collisionPlanePoint.copy(collisionSource);
|
||||
|
||||
var normal:Point3D = plane.node.normal;
|
||||
// Если сфера врезана в плоскость
|
||||
if (plane.sourceOffset <= sphereRadius) {
|
||||
if (plane.infront) {
|
||||
collisionPlanePoint.x -= normal.x * plane.sourceOffset;
|
||||
collisionPlanePoint.y -= normal.y * plane.sourceOffset;
|
||||
collisionPlanePoint.z -= normal.z * plane.sourceOffset;
|
||||
} else {
|
||||
collisionPlanePoint.x += normal.x * plane.sourceOffset;
|
||||
collisionPlanePoint.y += normal.y * plane.sourceOffset;
|
||||
collisionPlanePoint.z += normal.z * plane.sourceOffset;
|
||||
}
|
||||
} else {
|
||||
// Находим центр сферы во время столкновения с плоскостью
|
||||
var time:Number = (plane.sourceOffset - sphereRadius) / (plane.sourceOffset - plane.destinationOffset);
|
||||
collisionPlanePoint.x = collisionSource.x + collisionVector.x * time;
|
||||
collisionPlanePoint.y = collisionSource.y + collisionVector.y * time;
|
||||
collisionPlanePoint.z = collisionSource.z + collisionVector.z * time;
|
||||
|
||||
// Устанавливаем точку пересечения cферы с плоскостью
|
||||
if (plane.infront) {
|
||||
collisionPlanePoint.x -= normal.x * sphereRadius;
|
||||
collisionPlanePoint.y -= normal.y * sphereRadius;
|
||||
collisionPlanePoint.z -= normal.z * sphereRadius;
|
||||
} else {
|
||||
collisionPlanePoint.x += normal.x * sphereRadius;
|
||||
collisionPlanePoint.y += normal.y * sphereRadius;
|
||||
collisionPlanePoint.z += normal.z * sphereRadius;
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем примитивы плоскости
|
||||
var primitive:*;
|
||||
collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE;
|
||||
collisionPrimitiveNearest = null;
|
||||
if (plane.infront) {
|
||||
if (plane.node.primitive != null) {
|
||||
if (ignoreSet == null || ignoreSet[plane.node.primitive.face._mesh] == undefined) {
|
||||
calculateCollisionWithPrimitive(plane.node.primitive);
|
||||
}
|
||||
} else {
|
||||
for (primitive in plane.node.frontPrimitives) {
|
||||
if (ignoreSet == null || ignoreSet[primitive.face._mesh] == undefined) {
|
||||
calculateCollisionWithPrimitive(primitive);
|
||||
if (collisionPrimitive != null) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (primitive in plane.node.backPrimitives) {
|
||||
if (ignoreSet == null || ignoreSet[primitive.face._mesh] == undefined) {
|
||||
calculateCollisionWithPrimitive(primitive);
|
||||
if (collisionPrimitive != null) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (collisionPrimitive != null) {
|
||||
// Если точка пересечения попала в примитив
|
||||
|
||||
// Нормаль плоскости при столкновении - нормаль плоскости
|
||||
if (plane.infront) {
|
||||
collisionNormal.x = normal.x;
|
||||
collisionNormal.y = normal.y;
|
||||
collisionNormal.z = normal.z;
|
||||
collisionOffset = plane.node.offset;
|
||||
} else {
|
||||
collisionNormal.x = -normal.x;
|
||||
collisionNormal.y = -normal.y;
|
||||
collisionNormal.z = -normal.z;
|
||||
collisionOffset = -plane.node.offset;
|
||||
}
|
||||
|
||||
// Точка столкновения в точке столкновения с плоскостью
|
||||
collisionPoint.x = collisionPlanePoint.x;
|
||||
collisionPoint.y = collisionPlanePoint.y;
|
||||
collisionPoint.z = collisionPlanePoint.z;
|
||||
|
||||
} else {
|
||||
// Если точка пересечения не попала ни в один примитив, проверяем столкновение с ближайшей
|
||||
|
||||
// Вектор из ближайшей точки в центр сферы
|
||||
var nearestPointToSourceX:Number = collisionSource.x - collisionPrimitivePoint.x;
|
||||
var nearestPointToSourceY:Number = collisionSource.y - collisionPrimitivePoint.y;
|
||||
var nearestPointToSourceZ:Number = collisionSource.z - collisionPrimitivePoint.z;
|
||||
|
||||
// Если движение в сторону точки
|
||||
if (nearestPointToSourceX * collisionVector.x + nearestPointToSourceY * collisionVector.y + nearestPointToSourceZ * collisionVector.z <= 0) {
|
||||
|
||||
// Ищем нормализованный вектор обратного направления
|
||||
var vectorLength:Number = Math.sqrt(collisionVector.x * collisionVector.x + collisionVector.y * collisionVector.y + collisionVector.z * collisionVector.z);
|
||||
var vectorX:Number = -collisionVector.x / vectorLength;
|
||||
var vectorY:Number = -collisionVector.y / vectorLength;
|
||||
var vectorZ:Number = -collisionVector.z / vectorLength;
|
||||
|
||||
// Длина вектора из ближайшей точки в центр сферы
|
||||
var nearestPointToSourceLengthSqr:Number = nearestPointToSourceX * nearestPointToSourceX + nearestPointToSourceY * nearestPointToSourceY + nearestPointToSourceZ * nearestPointToSourceZ;
|
||||
|
||||
// Проекция вектора из ближайшей точки в центр сферы на нормализованный вектор обратного направления
|
||||
var projectionLength:Number = nearestPointToSourceX * vectorX + nearestPointToSourceY * vectorY + nearestPointToSourceZ * vectorZ;
|
||||
|
||||
var projectionInsideSphereLengthSqr:Number = sphereRadius * sphereRadius - nearestPointToSourceLengthSqr + projectionLength * projectionLength;
|
||||
|
||||
if (projectionInsideSphereLengthSqr > 0) {
|
||||
// Находим расстояние из ближайшей точки до сферы
|
||||
var distance:Number = projectionLength - Math.sqrt(projectionInsideSphereLengthSqr);
|
||||
|
||||
if (distance < vectorLength) {
|
||||
// Столкновение сферы с ближайшей точкой произошло
|
||||
|
||||
// Точка столкновения в ближайшей точке
|
||||
collisionPoint.x = collisionPrimitivePoint.x;
|
||||
collisionPoint.y = collisionPrimitivePoint.y;
|
||||
collisionPoint.z = collisionPrimitivePoint.z;
|
||||
|
||||
// Находим нормаль плоскости столкновения
|
||||
var nearestPointToSourceLength:Number = Math.sqrt(nearestPointToSourceLengthSqr);
|
||||
collisionNormal.x = nearestPointToSourceX / nearestPointToSourceLength;
|
||||
collisionNormal.y = nearestPointToSourceY / nearestPointToSourceLength;
|
||||
collisionNormal.z = nearestPointToSourceZ / nearestPointToSourceLength;
|
||||
|
||||
// Смещение плоскости столкновения
|
||||
collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z;
|
||||
collisionPrimitive = collisionPrimitiveNearest;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Определение столкновения с примитивом.
|
||||
*
|
||||
* @param primitive примитив, столкновение с которым проверяется
|
||||
*/
|
||||
private function calculateCollisionWithPrimitive(primitive:PolyPrimitive):void {
|
||||
|
||||
var length:uint = primitive.num;
|
||||
var points:Array = primitive.points;
|
||||
var normal:Point3D = primitive.face.globalNormal;
|
||||
var inside:Boolean = true;
|
||||
|
||||
for (var i:uint = 0; i < length; i++) {
|
||||
|
||||
var p1:Point3D = points[i];
|
||||
var p2:Point3D = points[(i < length - 1) ? (i + 1) : 0];
|
||||
|
||||
var edgeX:Number = p2.x - p1.x;
|
||||
var edgeY:Number = p2.y - p1.y;
|
||||
var edgeZ:Number = p2.z - p1.z;
|
||||
|
||||
var vectorX:Number = collisionPlanePoint.x - p1.x;
|
||||
var vectorY:Number = collisionPlanePoint.y - p1.y;
|
||||
var vectorZ:Number = collisionPlanePoint.z - p1.z;
|
||||
|
||||
var crossX:Number = vectorY * edgeZ - vectorZ * edgeY;
|
||||
var crossY:Number = vectorZ * edgeX - vectorX * edgeZ;
|
||||
var crossZ:Number = vectorX * edgeY - vectorY * edgeX;
|
||||
|
||||
if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) {
|
||||
// Точка за пределами полигона
|
||||
inside = false;
|
||||
|
||||
var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ;
|
||||
var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr;
|
||||
|
||||
// Если расстояние до прямой меньше текущего ближайшего
|
||||
if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) {
|
||||
|
||||
// Ищем нормализованный вектор ребра
|
||||
var edgeLength:Number = Math.sqrt(edgeLengthSqr);
|
||||
var edgeNormX:Number = edgeX / edgeLength;
|
||||
var edgeNormY:Number = edgeY / edgeLength;
|
||||
var edgeNormZ:Number = edgeZ / edgeLength;
|
||||
|
||||
// Находим расстояние до точки перпендикуляра вдоль ребра
|
||||
var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ;
|
||||
|
||||
var vectorLengthSqr:Number;
|
||||
if (t < 0) {
|
||||
// Ближайшая точка - первая
|
||||
vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
|
||||
if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
|
||||
collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
|
||||
collisionPrimitivePoint.x = p1.x;
|
||||
collisionPrimitivePoint.y = p1.y;
|
||||
collisionPrimitivePoint.z = p1.z;
|
||||
collisionPrimitiveNearest = primitive;
|
||||
}
|
||||
} else {
|
||||
if (t > edgeLength) {
|
||||
// Ближайшая точка - вторая
|
||||
vectorX = collisionPlanePoint.x - p2.x;
|
||||
vectorY = collisionPlanePoint.y - p2.y;
|
||||
vectorZ = collisionPlanePoint.z - p2.z;
|
||||
vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
|
||||
if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
|
||||
collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
|
||||
collisionPrimitivePoint.x = p2.x;
|
||||
collisionPrimitivePoint.y = p2.y;
|
||||
collisionPrimitivePoint.z = p2.z;
|
||||
collisionPrimitiveNearest = primitive;
|
||||
}
|
||||
} else {
|
||||
// Ближайшая точка на ребре
|
||||
collisionPrimitiveNearestLengthSqr = edgeDistanceSqr;
|
||||
collisionPrimitivePoint.x = p1.x + edgeNormX * t;
|
||||
collisionPrimitivePoint.y = p1.y + edgeNormY * t;
|
||||
collisionPrimitivePoint.z = p1.z + edgeNormZ * t;
|
||||
collisionPrimitiveNearest = primitive;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Если попали в примитив
|
||||
if (inside) {
|
||||
collisionPrimitive = primitive;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
316
Alternativa3D5/5.3/alternativa/engine3d/primitives/Box.as
Normal file
316
Alternativa3D5/5.3/alternativa/engine3d/primitives/Box.as
Normal file
@@ -0,0 +1,316 @@
|
||||
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 Box extends Mesh {
|
||||
|
||||
/**
|
||||
* Создание нового параллелепипеда.
|
||||
* <p>Параллелепипед после создания будет содержать в себе шесть поверхностей.
|
||||
* <code>"front"</code>, <code>"back"</code>, <code>"left"</code>, <code>"right"</code>, <code>"top"</code>, <code>"bottom"</code>
|
||||
* на каждую из которых может быть установлен свой материал.</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
264
Alternativa3D5/5.3/alternativa/engine3d/primitives/Cone.as
Normal file
264
Alternativa3D5/5.3/alternativa/engine3d/primitives/Cone.as
Normal file
@@ -0,0 +1,264 @@
|
||||
package alternativa.engine3d.primitives {
|
||||
import alternativa.engine3d.*;
|
||||
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 alternativa.engine3d.core.Face;
|
||||
import flash.geom.Point;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Усеченный конус или цилиндр.
|
||||
*/
|
||||
public class Cone extends Mesh {
|
||||
|
||||
/**
|
||||
* Создает примитив усеченный конус или цилиндр.
|
||||
* <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
197
Alternativa3D5/5.3/alternativa/engine3d/primitives/GeoPlane.as
Normal file
197
Alternativa3D5/5.3/alternativa/engine3d/primitives/GeoPlane.as
Normal file
@@ -0,0 +1,197 @@
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Создает геоплоскость.
|
||||
* <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
321
Alternativa3D5/5.3/alternativa/engine3d/primitives/GeoSphere.as
Normal file
321
Alternativa3D5/5.3/alternativa/engine3d/primitives/GeoSphere.as
Normal file
@@ -0,0 +1,321 @@
|
||||
package alternativa.engine3d.primitives {
|
||||
import alternativa.engine3d.*;
|
||||
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 alternativa.engine3d.core.Face;
|
||||
import flash.geom.Point;
|
||||
import alternativa.types.Point3D;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Геосфера.
|
||||
*/
|
||||
public class GeoSphere extends Mesh {
|
||||
|
||||
/**
|
||||
* Создает геосферу.
|
||||
* <p>Геосфера после создания содержит в себе одну поверхность с идентификатором по умолчанию.</p>
|
||||
* <p>Текстурные координаты у геосферы не находятся в промежутке <code>[0, 1]</code>,
|
||||
* поэтому для материала с текстурой необходимо устанавливать флаг repeat.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
117
Alternativa3D5/5.3/alternativa/engine3d/primitives/Plane.as
Normal file
117
Alternativa3D5/5.3/alternativa/engine3d/primitives/Plane.as
Normal file
@@ -0,0 +1,117 @@
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Создает плоскость.
|
||||
* <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
144
Alternativa3D5/5.3/alternativa/engine3d/primitives/Sphere.as
Normal file
144
Alternativa3D5/5.3/alternativa/engine3d/primitives/Sphere.as
Normal file
@@ -0,0 +1,144 @@
|
||||
package alternativa.engine3d.primitives {
|
||||
import alternativa.engine3d.*;
|
||||
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 alternativa.engine3d.core.Face;
|
||||
import flash.geom.Point;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Сфера.
|
||||
*/
|
||||
public class Sphere extends Mesh {
|
||||
|
||||
/**
|
||||
* Создает сферу.
|
||||
* <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
861
Alternativa3D5/5.3/alternativa/utils/MeshUtils.as
Normal file
861
Alternativa3D5/5.3/alternativa/utils/MeshUtils.as
Normal file
@@ -0,0 +1,861 @@
|
||||
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-координат
|
||||
*/
|
||||
static public function autoWeldFaces(mesh:Mesh, angleThreshold:Number = 0, uvThreshold:Number = 0):void {
|
||||
angleThreshold = Math.cos(angleThreshold);
|
||||
|
||||
var face:Face;
|
||||
var sibling:Face;
|
||||
var key:*;
|
||||
var i:uint;
|
||||
|
||||
// Формируем списки граней
|
||||
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()) {
|
||||
var faceNormal:Point3D = face.normal;
|
||||
if (faceNormal.x != 0 || faceNormal.y != 0 || faceNormal.z != 0) {
|
||||
faces1[face] = true;
|
||||
normals[face] = faceNormal;
|
||||
} 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;
|
||||
|
||||
// Перебираем грани текущей точки
|
||||
var vertex: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) {
|
||||
// Если грани сонаправлены
|
||||
var normal:Point3D = 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;
|
||||
|
||||
if (crossX == 0 && crossY == 0 && crossZ == 0) {
|
||||
if (cx*dx + cy*dy + cz*dz > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var dot:Number = crossX*normal.x + crossY*normal.y + crossZ*normal.z;
|
||||
|
||||
// Если в первой точке перегиба нет
|
||||
if (dot >= 0) {
|
||||
|
||||
// Считаем второй перегиб
|
||||
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;
|
||||
|
||||
if (crossX == 0 && crossY == 0 && crossZ == 0) {
|
||||
if (cx*dx + cy*dy + cz*dz > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dot = crossX*normal.x + crossY*normal.y + crossZ*normal.z;
|
||||
|
||||
// Если во второй точке перегиба нет
|
||||
if (dot >= 0) {
|
||||
|
||||
// Флаг наличия 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 v:Vertex;
|
||||
var key:*;
|
||||
var face:Face;
|
||||
var index:uint;
|
||||
var length:uint;
|
||||
for each (var vertex:Vertex in mesh._vertices.clone()) {
|
||||
var unless: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;
|
||||
if (aby*acz - abz*acy == 0 && abz*acx - abx*acz == 0 && abx*acy - aby*acx == 0) {
|
||||
indexes[face] = index;
|
||||
} else {
|
||||
unless = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (unless && !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;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @private
|
||||
// * Убирает лишние точки на ребрах и добавляет точки в месте распилов на соседних гранях
|
||||
// *
|
||||
// * @param object объект
|
||||
// * @param recursive если установлена в <code>true</code>, метод будет вызываться рекурсивно на потомках потомков потомков объекта тоже.
|
||||
// *
|
||||
// * @return количество добавленных точек с вычетом убранных
|
||||
// */
|
||||
// p function fixGaps(object:Object3D, recursive:Boolean = false):int {
|
||||
// var result:int = 0;
|
||||
// if (object is Mesh) {
|
||||
// var mesh:Mesh = Mesh(object);
|
||||
// var faces:Map = mesh._faces;
|
||||
// for each (var face:Face in faces) {
|
||||
// //face.
|
||||
// }
|
||||
// }
|
||||
// if (recursive) {
|
||||
// var children:Set = object._children;
|
||||
// for (var child:* in children) {
|
||||
// result += fixGaps(child, true);
|
||||
// }
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
14
Alternativa3D5/5.4/alternativa/Alternativa3D.as
Normal file
14
Alternativa3D5/5.4/alternativa/Alternativa3D.as
Normal file
@@ -0,0 +1,14 @@
|
||||
package alternativa {
|
||||
|
||||
/**
|
||||
* Класс содержит информацию о версии библиотеки.
|
||||
* Также используется для интеграции библиотеки в среду разработки Adobe Flash.
|
||||
*/
|
||||
public class Alternativa3D {
|
||||
|
||||
/**
|
||||
* Версия библиотеки в формате: поколение.feature-версия.fix-версия
|
||||
*/
|
||||
public static const version:String = "5.4.1";
|
||||
}
|
||||
}
|
||||
3
Alternativa3D5/5.4/alternativa/engine3d/alternativa3d.as
Normal file
3
Alternativa3D5/5.4/alternativa/engine3d/alternativa3d.as
Normal file
@@ -0,0 +1,3 @@
|
||||
package alternativa.engine3d {
|
||||
public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d";
|
||||
}
|
||||
@@ -0,0 +1,889 @@
|
||||
package alternativa.engine3d.controllers {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.Camera3D;
|
||||
import alternativa.engine3d.physics.EllipsoidCollider;
|
||||
import alternativa.types.Map;
|
||||
import alternativa.types.Matrix3D;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.types.Set;
|
||||
import alternativa.utils.KeyboardUtils;
|
||||
import alternativa.utils.MathUtils;
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.events.KeyboardEvent;
|
||||
import flash.events.MouseEvent;
|
||||
import flash.geom.Point;
|
||||
import flash.utils.getTimer;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Контроллер камеры. Контроллер обеспечивает управление движением и поворотами камеры с использованием
|
||||
* клавиатуры и мыши, а также предоставляет простую проверку столкновений камеры с объектами сцены.
|
||||
*/
|
||||
public class CameraController {
|
||||
/**
|
||||
* Имя действия для привязки клавиш движения вперёд.
|
||||
*/
|
||||
public static const ACTION_FORWARD:String = "ACTION_FORWARD";
|
||||
/**
|
||||
* Имя действия для привязки клавиш движения назад.
|
||||
*/
|
||||
public static const ACTION_BACK:String = "ACTION_BACK";
|
||||
/**
|
||||
* Имя действия для привязки клавиш движения влево.
|
||||
*/
|
||||
public static const ACTION_LEFT:String = "ACTION_LEFT";
|
||||
/**
|
||||
* Имя действия для привязки клавиш движения вправо.
|
||||
*/
|
||||
public static const ACTION_RIGHT:String = "ACTION_RIGHT";
|
||||
/**
|
||||
* Имя действия для привязки клавиш движения вверх.
|
||||
*/
|
||||
public static const ACTION_UP:String = "ACTION_UP";
|
||||
/**
|
||||
* Имя действия для привязки клавиш движения вниз.
|
||||
*/
|
||||
public static const ACTION_DOWN:String = "ACTION_DOWN";
|
||||
// public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT";
|
||||
// public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT";
|
||||
/**
|
||||
* Имя действия для привязки клавиш увеличения угла тангажа.
|
||||
*/
|
||||
public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP";
|
||||
/**
|
||||
* Имя действия для привязки клавиш уменьшения угла тангажа.
|
||||
*/
|
||||
public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN";
|
||||
/**
|
||||
* Имя действия для привязки клавиш уменьшения угла рысканья (поворота налево).
|
||||
*/
|
||||
public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT";
|
||||
/**
|
||||
* Имя действия для привязки клавиш увеличения угла рысканья (поворота направо).
|
||||
*/
|
||||
public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT";
|
||||
/**
|
||||
* Имя действия для привязки клавиш увеличения скорости.
|
||||
*/
|
||||
public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE";
|
||||
|
||||
// флаги действий
|
||||
private var _forward:Boolean;
|
||||
private var _back:Boolean;
|
||||
private var _left:Boolean;
|
||||
private var _right:Boolean;
|
||||
private var _up:Boolean;
|
||||
private var _down:Boolean;
|
||||
private var _pitchUp:Boolean;
|
||||
private var _pitchDown:Boolean;
|
||||
private var _yawLeft:Boolean;
|
||||
private var _yawRight:Boolean;
|
||||
private var _accelerate:Boolean;
|
||||
|
||||
private var _moveLocal:Boolean = true;
|
||||
|
||||
// Флаг включения управления камерой
|
||||
private var _controlsEnabled:Boolean = false;
|
||||
|
||||
// Значение таймера в начале прошлого кадра
|
||||
private var lastFrameTime:uint;
|
||||
|
||||
// Чувствительность обзора мышью. Коэффициент умножения базовых коэффициентов поворотов.
|
||||
private var _mouseSensitivity:Number = 1;
|
||||
// Коэффициент поворота камеры по тангажу. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
|
||||
private var _mousePitch:Number = Math.PI / 360;
|
||||
// Результирующий коэффициент поворота камеры мышью по тангажу
|
||||
private var _mousePitchCoeff:Number = _mouseSensitivity * _mousePitch;
|
||||
// Коэффициент поворота камеры по рысканью. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
|
||||
private var _mouseYaw:Number = Math.PI / 360;
|
||||
// Результирующий коэффициент поворота камеры мышью по расканью
|
||||
private var _mouseYawCoeff:Number = _mouseSensitivity * _mouseYaw;
|
||||
|
||||
// Вспомогательные переменные для обзора мышью
|
||||
private var mouseLookActive:Boolean;
|
||||
private var startDragCoords:Point = new Point();
|
||||
private var currentDragCoords:Point = new Point();
|
||||
private var prevDragCoords:Point = new Point();
|
||||
private var startRotX:Number;
|
||||
private var startRotZ:Number;
|
||||
|
||||
// Скорость изменения тангажа в радианах за секунду при управлении с клавиатуры
|
||||
private var _pitchSpeed:Number = 1;
|
||||
// Скорость изменения рысканья в радианах за секунду при управлении с клавиатуры
|
||||
private var _yawSpeed:Number = 1;
|
||||
// Скорость изменения крена в радианах за секунду при управлении с клавиатуры
|
||||
// public var bankSpeed:Number = 2;
|
||||
// private var bankMatrix:Matrix3D = new Matrix3D();
|
||||
|
||||
// Скорость поступательного движения в единицах за секунду
|
||||
private var _speed:Number = 100;
|
||||
// Коэффициент увеличения скорости при соответствующей нажатой клавише
|
||||
private var _speedMultiplier:Number = 2;
|
||||
|
||||
private var velocity:Point3D = new Point3D();
|
||||
private var destination:Point3D = new Point3D();
|
||||
|
||||
private var _fovStep:Number = Math.PI / 180;
|
||||
private var _zoomMultiplier:Number = 0.1;
|
||||
|
||||
// Привязка клавиш к действиям
|
||||
private var keyBindings:Map = new Map();
|
||||
// Привязка действий к обработчикам
|
||||
private var actionBindings:Map = new Map();
|
||||
|
||||
// Источник событий клавиатуры и мыши
|
||||
private var _eventsSource:DisplayObject;
|
||||
// Управляемая камера
|
||||
private var _camera:Camera3D;
|
||||
|
||||
// Класс реализации определния столкновений
|
||||
private var _collider:EllipsoidCollider;
|
||||
// Флаг необходимости проверки столкновений
|
||||
private var _checkCollisions:Boolean;
|
||||
// Радиус сферы для определения столкновений
|
||||
private var _collisionRadius:Number = 0;
|
||||
// Набор исключаемых из проверки столкновений объектов
|
||||
private var _collisionIgnoreSet:Set = new Set(true);
|
||||
// Флаг движения
|
||||
private var _isMoving:Boolean;
|
||||
|
||||
private var _onStartMoving:Function;
|
||||
private var _onStopMoving:Function;
|
||||
|
||||
/**
|
||||
* Создание экземпляра контроллера.
|
||||
*
|
||||
* @param eventsSourceObject объект, используемый для получения событий мыши и клавиатуры
|
||||
*/
|
||||
public function CameraController(eventsSourceObject:DisplayObject) {
|
||||
if (eventsSourceObject == null) {
|
||||
throw new ArgumentError("CameraController: eventsSource is null");
|
||||
}
|
||||
_eventsSource = eventsSourceObject;
|
||||
|
||||
actionBindings[ACTION_FORWARD] = forward;
|
||||
actionBindings[ACTION_BACK] = back;
|
||||
actionBindings[ACTION_LEFT] = left;
|
||||
actionBindings[ACTION_RIGHT] = right;
|
||||
actionBindings[ACTION_UP] = up;
|
||||
actionBindings[ACTION_DOWN] = down;
|
||||
actionBindings[ACTION_PITCH_UP] = pitchUp;
|
||||
actionBindings[ACTION_PITCH_DOWN] = pitchDown;
|
||||
actionBindings[ACTION_YAW_LEFT] = yawLeft;
|
||||
actionBindings[ACTION_YAW_RIGHT] = yawRight;
|
||||
actionBindings[ACTION_ACCELERATE] = accelerate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
|
||||
* <table border="1" style="border-collapse: collapse">
|
||||
* <tr><th>Клавиша</th><th>Действие</th></tr>
|
||||
* <tr><td>W</td><td>ACTION_FORWARD</td></tr>
|
||||
* <tr><td>S</td><td>ACTION_BACK</td></tr>
|
||||
* <tr><td>A</td><td>ACTION_LEFT</td></tr>
|
||||
* <tr><td>D</td><td>ACTION_RIGHT</td></tr>
|
||||
* <tr><td>SPACE</td><td>ACTION_UP</td></tr>
|
||||
* <tr><td>CONTROL</td><td>ACTION_DOWN</td></tr>
|
||||
* <tr><td>SHIFT</td><td>ACTION_ACCELERATE</td></tr>
|
||||
* <tr><td>UP</td><td>ACTION_PITCH_UP</td></tr>
|
||||
* <tr><td>DOWN</td><td>ACTION_PITCH_DOWN</td></tr>
|
||||
* <tr><td>LEFT</td><td>ACTION_YAW_LEFT</td></tr>
|
||||
* <tr><td>RIGHT</td><td>ACTION_YAW_RIGHT</td></tr>
|
||||
* </table>
|
||||
*/
|
||||
public function setDefaultBindings():void {
|
||||
unbindAll();
|
||||
bindKey(KeyboardUtils.W, ACTION_FORWARD);
|
||||
bindKey(KeyboardUtils.S, ACTION_BACK);
|
||||
bindKey(KeyboardUtils.A, ACTION_LEFT);
|
||||
bindKey(KeyboardUtils.D, ACTION_RIGHT);
|
||||
bindKey(KeyboardUtils.SPACE, ACTION_UP);
|
||||
bindKey(KeyboardUtils.CONTROL, ACTION_DOWN);
|
||||
bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
|
||||
bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
|
||||
bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT);
|
||||
bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT);
|
||||
bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Направление камеры на точку.
|
||||
*
|
||||
* @param point координаты точки направления камеры
|
||||
*/
|
||||
public function lookAt(point:Point3D):void {
|
||||
if (_camera == null) {
|
||||
return;
|
||||
}
|
||||
var dx:Number = point.x - _camera.x;
|
||||
var dy:Number = point.y - _camera.y;
|
||||
var dz:Number = point.z - _camera.z;
|
||||
_camera.rotationZ = -Math.atan2(dx, dy);
|
||||
_camera.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - MathUtils.DEG90;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback-функция, вызываемая при начале движения камеры.
|
||||
*/
|
||||
public function get onStartMoving():Function {
|
||||
return _onStartMoving;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set onStartMoving(value:Function):void {
|
||||
_onStartMoving = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback-функция, вызываемая при прекращении движения камеры.
|
||||
*/
|
||||
public function get onStopMoving():Function {
|
||||
return _onStopMoving;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set onStopMoving(value:Function):void {
|
||||
_onStopMoving = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Набор объектов, исключаемых из проверки столкновений.
|
||||
*/
|
||||
public function get collisionIgnoreSet():Set {
|
||||
return _collisionIgnoreSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Источник событий клавиатуры и мыши.
|
||||
*/
|
||||
public function get eventsSource():DisplayObject {
|
||||
return _eventsSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set eventsSource(value:DisplayObject):void {
|
||||
if (_eventsSource != value) {
|
||||
if (value == null) {
|
||||
throw new ArgumentError("CameraController: eventsSource is null");
|
||||
}
|
||||
if (_controlsEnabled) {
|
||||
unregisterEventsListeners();
|
||||
}
|
||||
_eventsSource = value;
|
||||
if (_controlsEnabled) {
|
||||
registerEventListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ассоциированная камера.
|
||||
*/
|
||||
public function get camera():Camera3D {
|
||||
return _camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set camera(value:Camera3D):void {
|
||||
if (_camera != value) {
|
||||
_camera = value;
|
||||
if (value == null) {
|
||||
controlsEnabled = false;
|
||||
} else {
|
||||
createCollider();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Режим движения камеры. Если значение равно <code>true</code>, то перемещения камеры происходят относительно
|
||||
* локальной системы координат, иначе относительно глобальной.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
public function get moveLocal():Boolean {
|
||||
return _moveLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set moveLocal(value:Boolean):void {
|
||||
_moveLocal = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Включение режима проверки столкновений.
|
||||
*/
|
||||
public function get checkCollisions():Boolean {
|
||||
return _checkCollisions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set checkCollisions(value:Boolean):void {
|
||||
_checkCollisions = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Радиус сферы для определения столкновений.
|
||||
*
|
||||
* @default 0
|
||||
*/
|
||||
public function get collisionRadius():Number {
|
||||
return _collisionRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set collisionRadius(value:Number):void {
|
||||
_collisionRadius = value;
|
||||
if (_collider != null) {
|
||||
_collider.radiusX = _collisionRadius;
|
||||
_collider.radiusY = _collisionRadius;
|
||||
_collider.radiusZ = _collisionRadius;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Привязка клавиши к действию.
|
||||
*
|
||||
* @param keyCode код клавиши
|
||||
* @param action наименование действия
|
||||
*/
|
||||
public function bindKey(keyCode:uint, action:String):void {
|
||||
var method:Function = actionBindings[action];
|
||||
if (method != null) {
|
||||
keyBindings[keyCode] = method;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Очистка привязки клавиши.
|
||||
*
|
||||
* @param keyCode код клавиши
|
||||
*/
|
||||
public function unbindKey(keyCode:uint):void {
|
||||
keyBindings.remove(keyCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Очистка привязки всех клавиш.
|
||||
*/
|
||||
public function unbindAll():void {
|
||||
keyBindings.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация движения камеры вперёд.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function forward(value:Boolean):void {
|
||||
_forward = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация движения камеры назад.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function back(value:Boolean):void {
|
||||
_back = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация движения камеры влево.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function left(value:Boolean):void {
|
||||
_left = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация движения камеры вправо.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function right(value:Boolean):void {
|
||||
_right = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация движения камеры вверх.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function up(value:Boolean):void {
|
||||
_up = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация движения камеры вниз.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function down(value:Boolean):void {
|
||||
_down = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация поворота камеры вверх.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function pitchUp(value:Boolean):void {
|
||||
_pitchUp = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация поворота камеры вниз.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function pitchDown(value:Boolean):void {
|
||||
_pitchDown = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация поворота камеры влево.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function yawLeft(value:Boolean):void {
|
||||
_yawLeft = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация поворота камеры вправо.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function yawRight(value:Boolean):void {
|
||||
_yawRight = value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Активация режима увеличенной скорости.
|
||||
*
|
||||
* @param value <code>true</code> для включения ускорения, <code>false</code> для выключения
|
||||
*/
|
||||
public function accelerate(value:Boolean):void {
|
||||
_accelerate = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private function createCollider():void {
|
||||
_collider = new EllipsoidCollider(_camera.scene, _collisionRadius);
|
||||
_collider.offsetThreshold = 0.01;
|
||||
_collider.collisionSet = _collisionIgnoreSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Чувствительность мыши — коэффициент умножения <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
package alternativa.engine3d.controllers {
|
||||
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.Camera3D;
|
||||
import alternativa.types.Matrix3D;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.utils.KeyboardUtils;
|
||||
import alternativa.utils.MathUtils;
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Контроллер, реализующий управление, подобное управлению летательным аппаратом для объекта, находящегося в системе
|
||||
* координат корневого объекта сцены. Повороты выполняются вокруг локальных осей объекта, собственные ускорения
|
||||
* действуют вдоль локальных осей.
|
||||
*
|
||||
* <p>Соответствия локальных осей для объектов, не являющихся камерой:
|
||||
* <table border="1" style="border-collapse: collapse">
|
||||
* <tr>
|
||||
* <th>Ось</th><th>Направление</th><th>Поворот</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>X</td><td>Вправо</td><td>Тангаж</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Y</td><td>Вперёд</td><td>Крен</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Z</td><td>Вверх</td><td>Рысканье</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* <p>Соответствия локальных осей для объектов, являющихся камерой:
|
||||
* <table border="1" style="border-collapse: collapse">
|
||||
* <tr>
|
||||
* <th>Ось</th><th>Направление</th><th>Поворот</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>X</td><td>Вправо</td><td>Тангаж</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Y</td><td>Вниз</td><td>Рысканье</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Z</td><td>Вперёд</td><td>Крен</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* Поворот мышью реализован следующим образом: в момент активации режима поворота (нажата левая кнопка мыши или
|
||||
* соответствующая кнопка на клавиатуре) текущее положение курсора становится точкой, относительно которой определяются
|
||||
* дальнейшие отклонения. Отклонение курсора по вертикали в пикселях, умноженное на коэффициент чувствительности мыши
|
||||
* по вертикали даёт угловую скорость по тангажу. Отклонение курсора по горизонтали в пикселях, умноженное на коэффициент
|
||||
* чувствительности мыши по горизонтали даёт угловую скорость по крену.
|
||||
*/
|
||||
public class FlyController extends ObjectController {
|
||||
/**
|
||||
* Имя действия для привязки клавиш поворота по крену влево.
|
||||
*/
|
||||
public static const ACTION_ROLL_LEFT:String = "ACTION_ROLL_LEFT";
|
||||
/**
|
||||
* Имя действия для привязки клавиш поворота по крену вправо.
|
||||
*/
|
||||
public static const ACTION_ROLL_RIGHT:String = "ACTION_ROLL_RIGHT";
|
||||
|
||||
private var _rollLeft:Boolean;
|
||||
private var _rollRight:Boolean;
|
||||
|
||||
private var _rollSpeed:Number = 1;
|
||||
|
||||
private var rotations:Point3D;
|
||||
private var rollMatrix:Matrix3D = new Matrix3D();
|
||||
private var transformation:Matrix3D = new Matrix3D();
|
||||
private var axis:Point3D = new Point3D();
|
||||
|
||||
private var velocity:Point3D = new Point3D();
|
||||
private var displacement:Point3D = new Point3D();
|
||||
private var destination:Point3D = new Point3D();
|
||||
private var deltaVelocity:Point3D = new Point3D();
|
||||
private var accelerationVector:Point3D = new Point3D();
|
||||
private var currentTransform:Matrix3D = new Matrix3D();
|
||||
|
||||
private var _currentSpeed:Number = 1;
|
||||
/**
|
||||
* Текущие координаты мышиного курсора в режиме mouse look.
|
||||
*/
|
||||
private var currentMouseCoords:Point3D = new Point3D();
|
||||
|
||||
/**
|
||||
* Модуль вектора ускорния, получаемого от команд движения.
|
||||
*/
|
||||
public var acceleration:Number = 1000;
|
||||
/**
|
||||
* Модуль вектора замедляющего ускорения.
|
||||
*/
|
||||
public var deceleration:Number = 50;
|
||||
/**
|
||||
* Погрешность определения скорости. Скорость приравнивается к нулю, если её модуль не превышает заданного значения.
|
||||
*/
|
||||
public var speedThreshold:Number = 1;
|
||||
/**
|
||||
* Переключение инерционного режима. В инерционном режиме отсутствует замедляющее ускорение, в результате чего вектор
|
||||
* скорости объекта остаётся постоянным, если нет управляющих воздействий. При выключенном инерционном режиме к объекту
|
||||
* прикладывается замедляющее ускорение.
|
||||
*/
|
||||
public var inertialMode:Boolean;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function FlyController(eventsSourceObject:DisplayObject) {
|
||||
super(eventsSourceObject);
|
||||
|
||||
actionBindings[ACTION_ROLL_LEFT] = rollLeft;
|
||||
actionBindings[ACTION_ROLL_RIGHT] = rollRight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Текущая скорость движения.
|
||||
*/
|
||||
public function get currentSpeed():Number {
|
||||
return _currentSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация вращения по крену влево.
|
||||
*/
|
||||
public function rollLeft(value:Boolean):void {
|
||||
_rollLeft = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация вращения по крену вправо.
|
||||
*/
|
||||
public function rollRight(value:Boolean):void {
|
||||
_rollRight = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
|
||||
* <table border="1" style="border-collapse: collapse">
|
||||
* <tr><th>Клавиша</th><th>Действие</th></tr>
|
||||
* <tr><td>W</td><td>ACTION_FORWARD</td></tr>
|
||||
* <tr><td>S</td><td>ACTION_BACK</td></tr>
|
||||
* <tr><td>A</td><td>ACTION_LEFT</td></tr>
|
||||
* <tr><td>D</td><td>ACTION_RIGHT</td></tr>
|
||||
* <tr><td>SPACE</td><td>ACTION_UP</td></tr>
|
||||
* <tr><td>CONTROL</td><td>ACTION_DOWN</td></tr>
|
||||
* <tr><td>UP</td><td>ACTION_PITCH_UP</td></tr>
|
||||
* <tr><td>DOWN</td><td>ACTION_PITCH_DOWN</td></tr>
|
||||
* <tr><td>LEFT</td><td>ACTION_ROLL_LEFT</td></tr>
|
||||
* <tr><td>RIGHT</td><td>ACTION_ROLL_RIGHT</td></tr>
|
||||
* <tr><td>Q</td><td>ACTION_YAW_LEFT</td></tr>
|
||||
* <tr><td>E</td><td>ACTION_YAW_RIGHT</td></tr>
|
||||
* <tr><td>M</td><td>ACTION_MOUSE_LOOK</td></tr>
|
||||
* </table>
|
||||
*/
|
||||
override public function setDefaultBindings():void {
|
||||
unbindAll();
|
||||
bindKey(KeyboardUtils.W, ACTION_FORWARD);
|
||||
bindKey(KeyboardUtils.S, ACTION_BACK);
|
||||
bindKey(KeyboardUtils.A, ACTION_LEFT);
|
||||
bindKey(KeyboardUtils.D, ACTION_RIGHT);
|
||||
bindKey(KeyboardUtils.SPACE, ACTION_UP);
|
||||
bindKey(KeyboardUtils.CONTROL, ACTION_DOWN);
|
||||
bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
|
||||
bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
|
||||
bindKey(KeyboardUtils.LEFT, ACTION_ROLL_LEFT);
|
||||
bindKey(KeyboardUtils.RIGHT, ACTION_ROLL_RIGHT);
|
||||
bindKey(KeyboardUtils.Q, ACTION_YAW_LEFT);
|
||||
bindKey(KeyboardUtils.E, ACTION_YAW_RIGHT);
|
||||
bindKey(KeyboardUtils.M, ACTION_MOUSE_LOOK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод выполняет поворот объекта относительно локальных осей в соответствии с имеющимися воздействиями.
|
||||
*
|
||||
* @param frameTime длительность текущего кадра в секундах
|
||||
*/
|
||||
override protected function rotateObject(frameTime:Number):void {
|
||||
var transformation:Matrix3D = _object.transformation;
|
||||
if (_mouseLookActive) {
|
||||
currentMouseCoords.x = _eventsSource.stage.mouseX;
|
||||
currentMouseCoords.y = _eventsSource.stage.mouseY;
|
||||
if (!currentMouseCoords.equals(startMouseCoords)) {
|
||||
var deltaYaw:Number = (currentMouseCoords.x - startMouseCoords.x) * _mouseCoefficientX;
|
||||
if (_object is Camera3D) {
|
||||
axis.x = transformation.c;
|
||||
axis.y = transformation.g;
|
||||
axis.z = transformation.k;
|
||||
} else {
|
||||
axis.x = transformation.b;
|
||||
axis.y = transformation.f;
|
||||
axis.z = transformation.j;
|
||||
}
|
||||
|
||||
rotateObjectAroundAxis(axis, deltaYaw * frameTime);
|
||||
|
||||
currentTransform.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1);
|
||||
var deltaPitch:Number = (startMouseCoords.y - currentMouseCoords.y) * _mouseCoefficientY;
|
||||
axis.x = currentTransform.a;
|
||||
axis.y = currentTransform.e;
|
||||
axis.z = currentTransform.i;
|
||||
|
||||
rotateObjectAroundAxis(axis, deltaPitch * frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Поворот относительно продольной оси (крен, roll)
|
||||
if (_rollLeft) {
|
||||
if (_object is Camera3D) {
|
||||
axis.x = transformation.c;
|
||||
axis.y = transformation.g;
|
||||
axis.z = transformation.k;
|
||||
} else {
|
||||
axis.x = transformation.b;
|
||||
axis.y = transformation.f;
|
||||
axis.z = transformation.j;
|
||||
}
|
||||
rotateObjectAroundAxis(axis, -_rollSpeed * frameTime);
|
||||
} else if (_rollRight) {
|
||||
if (_object is Camera3D) {
|
||||
axis.x = transformation.c;
|
||||
axis.y = transformation.g;
|
||||
axis.z = transformation.k;
|
||||
} else {
|
||||
axis.x = transformation.b;
|
||||
axis.y = transformation.f;
|
||||
axis.z = transformation.j;
|
||||
}
|
||||
rotateObjectAroundAxis(axis, _rollSpeed * frameTime);
|
||||
}
|
||||
|
||||
// Поворот относительно поперечной оси (тангаж, pitch)
|
||||
if (_pitchUp) {
|
||||
axis.x = transformation.a;
|
||||
axis.y = transformation.e;
|
||||
axis.z = transformation.i;
|
||||
rotateObjectAroundAxis(axis, _pitchSpeed * frameTime);
|
||||
} else if (_pitchDown) {
|
||||
axis.x = transformation.a;
|
||||
axis.y = transformation.e;
|
||||
axis.z = transformation.i;
|
||||
rotateObjectAroundAxis(axis, -_pitchSpeed * frameTime);
|
||||
}
|
||||
|
||||
// Поворот относительно вертикальной оси (рысканье, yaw)
|
||||
if (_yawRight) {
|
||||
if (_object is Camera3D) {
|
||||
axis.x = transformation.b;
|
||||
axis.y = transformation.f;
|
||||
axis.z = transformation.j;
|
||||
rotateObjectAroundAxis(axis, _yawSpeed * frameTime);
|
||||
} else {
|
||||
axis.x = transformation.c;
|
||||
axis.y = transformation.g;
|
||||
axis.z = transformation.k;
|
||||
rotateObjectAroundAxis(axis, -_yawSpeed * frameTime);
|
||||
}
|
||||
} else if (_yawLeft) {
|
||||
if (_object is Camera3D) {
|
||||
axis.x = transformation.b;
|
||||
axis.y = transformation.f;
|
||||
axis.z = transformation.j;
|
||||
rotateObjectAroundAxis(axis, -_yawSpeed * frameTime);
|
||||
} else {
|
||||
axis.x = transformation.c;
|
||||
axis.y = transformation.g;
|
||||
axis.z = transformation.k;
|
||||
rotateObjectAroundAxis(axis, _yawSpeed * frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод вычисляет вектор потенциального смещения эллипсоида.
|
||||
*
|
||||
* @param frameTime длительность текущего кадра в секундах
|
||||
* @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта
|
||||
*/
|
||||
override protected function getDisplacement(frameTime:Number, displacement:Point3D):void {
|
||||
// Движение вперед-назад
|
||||
accelerationVector.x = 0;
|
||||
accelerationVector.y = 0;
|
||||
accelerationVector.z = 0;
|
||||
if (_forward) {
|
||||
accelerationVector.y = 1;
|
||||
} else if (_back) {
|
||||
accelerationVector.y = -1;
|
||||
}
|
||||
// Движение влево-вправо
|
||||
if (_right) {
|
||||
accelerationVector.x = 1;
|
||||
} else if (_left) {
|
||||
accelerationVector.x = -1;
|
||||
}
|
||||
// Движение ввверх-вниз
|
||||
if (_up) {
|
||||
accelerationVector.z = 1;
|
||||
} else if (_down) {
|
||||
accelerationVector.z = -1;
|
||||
}
|
||||
|
||||
var speedLoss:Number;
|
||||
var len:Number;
|
||||
|
||||
if (accelerationVector.x != 0 || accelerationVector.y != 0 || accelerationVector.z != 0) {
|
||||
// Управление активно
|
||||
if (_object is Camera3D) {
|
||||
var tmp:Number = accelerationVector.z;
|
||||
accelerationVector.z = accelerationVector.y;
|
||||
accelerationVector.y = -tmp;
|
||||
}
|
||||
accelerationVector.normalize();
|
||||
accelerationVector.x *= acceleration;
|
||||
accelerationVector.y *= acceleration;
|
||||
accelerationVector.z *= acceleration;
|
||||
currentTransform.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1);
|
||||
accelerationVector.transform(currentTransform);
|
||||
deltaVelocity.x = accelerationVector.x;
|
||||
deltaVelocity.y = accelerationVector.y;
|
||||
deltaVelocity.z = accelerationVector.z;
|
||||
deltaVelocity.x *= frameTime;
|
||||
deltaVelocity.y *= frameTime;
|
||||
deltaVelocity.z *= frameTime;
|
||||
|
||||
if (!inertialMode) {
|
||||
speedLoss = deceleration * frameTime;
|
||||
var dot:Number = Point3D.dot(velocity, accelerationVector);
|
||||
if (dot > 0) {
|
||||
len = accelerationVector.length;
|
||||
var x:Number = accelerationVector.x / len;
|
||||
var y:Number = accelerationVector.y / len;
|
||||
var z:Number = accelerationVector.z / len;
|
||||
len = dot / len;
|
||||
x = velocity.x - len * x;
|
||||
y = velocity.y - len * y;
|
||||
z = velocity.z - len * z;
|
||||
len = Math.sqrt(x*x + y*y + z*z);
|
||||
if (len > speedLoss) {
|
||||
x *= speedLoss / len;
|
||||
y *= speedLoss / len;
|
||||
z *= speedLoss / len;
|
||||
}
|
||||
velocity.x -= x;
|
||||
velocity.y -= y;
|
||||
velocity.z -= z;
|
||||
} else {
|
||||
len = velocity.length;
|
||||
velocity.length = (len > speedLoss) ? (len - speedLoss) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
velocity.x += deltaVelocity.x;
|
||||
velocity.y += deltaVelocity.y;
|
||||
velocity.z += deltaVelocity.z;
|
||||
|
||||
if (velocity.length > _speed) {
|
||||
velocity.length = _speed;
|
||||
}
|
||||
} else {
|
||||
// Управление неактивно
|
||||
if (!inertialMode) {
|
||||
speedLoss = deceleration * frameTime;
|
||||
len = velocity.length;
|
||||
velocity.length = (len > speedLoss) ? (len - speedLoss) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
displacement.x = velocity.x * frameTime;
|
||||
displacement.y = velocity.y * frameTime;
|
||||
displacement.z = velocity.z * frameTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод применяет потенциальный вектор смещения к эллипсоиду с учётом столкновений с геометрией сцены, если включён
|
||||
* соотвествующий режим.
|
||||
*
|
||||
* @param frameTime время кадра в секундах
|
||||
* @param displacement векотр потенциального смещения эллипсоида
|
||||
*/
|
||||
override protected function applyDisplacement(frameTime:Number, displacement:Point3D):void {
|
||||
if (checkCollisions) {
|
||||
_collider.calculateDestination(_coords, displacement, destination);
|
||||
|
||||
displacement.x = destination.x - _coords.x;
|
||||
displacement.y = destination.y - _coords.y;
|
||||
displacement.z = destination.z - _coords.z;
|
||||
} else {
|
||||
destination.x = _coords.x + displacement.x;
|
||||
destination.y = _coords.y + displacement.y;
|
||||
destination.z = _coords.z + displacement.z;
|
||||
}
|
||||
|
||||
velocity.x = displacement.x / frameTime;
|
||||
velocity.y = displacement.y / frameTime;
|
||||
velocity.z = displacement.z / frameTime;
|
||||
|
||||
_coords.x = destination.x;
|
||||
_coords.y = destination.y;
|
||||
_coords.z = destination.z;
|
||||
setObjectCoords();
|
||||
|
||||
var len:Number = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
|
||||
if (len < speedThreshold) {
|
||||
velocity.x = 0;
|
||||
velocity.y = 0;
|
||||
velocity.z = 0;
|
||||
_currentSpeed = 0;
|
||||
} else {
|
||||
_currentSpeed = len;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Поворот объекта вокруг заданной оси.
|
||||
*
|
||||
* @param axis
|
||||
* @param angle
|
||||
*/
|
||||
private function rotateObjectAroundAxis(axis:Point3D, angle:Number):void {
|
||||
transformation.toTransform(0, 0, 0, _object.rotationX, _object.rotationY, _object.rotationZ, 1, 1, 1);
|
||||
rollMatrix.fromAxisAngle(axis, angle);
|
||||
rollMatrix.inverseCombine(transformation);
|
||||
rotations = rollMatrix.getRotations(rotations);
|
||||
_object.rotationX = rotations.x;
|
||||
_object.rotationY = rotations.y;
|
||||
_object.rotationZ = rotations.z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Направление объекта на точку. В результате работы метода локальная ось объекта, соответствующая направлению "вперёд"
|
||||
* будет направлена на указанную точку, а угол поворота вокруг этой оси будет равен нулю.
|
||||
*
|
||||
* @param point координаты точки, на которую должен быть направлен объект
|
||||
*/
|
||||
public function lookAt(point:Point3D):void {
|
||||
if (_object == null) {
|
||||
return;
|
||||
}
|
||||
var dx:Number = point.x - _object.x;
|
||||
var dy:Number = point.y - _object.y;
|
||||
var dz:Number = point.z - _object.z;
|
||||
_object.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - (_object is Camera3D ? MathUtils.DEG90 : 0);
|
||||
_object.rotationY = 0;
|
||||
_object.rotationZ = -Math.atan2(dx, dy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,839 @@
|
||||
package alternativa.engine3d.controllers {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.Camera3D;
|
||||
import alternativa.engine3d.core.Object3D;
|
||||
import alternativa.engine3d.physics.EllipsoidCollider;
|
||||
import alternativa.types.Map;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.utils.MathUtils;
|
||||
import alternativa.utils.ObjectUtils;
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.events.KeyboardEvent;
|
||||
import flash.events.MouseEvent;
|
||||
import flash.utils.getTimer;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Базовый контроллер для изменения ориентации и положения объекта в сцене с помощью клавиатуры и мыши. В классе
|
||||
* реализована поддержка назначения обработчиков клавиатурных команд, а также обработчики для основных команд
|
||||
* перемещения.
|
||||
*/
|
||||
public class ObjectController {
|
||||
/**
|
||||
* Имя действия для привязки клавиш движения вперёд.
|
||||
*/
|
||||
public static const ACTION_FORWARD:String = "ACTION_FORWARD";
|
||||
/**
|
||||
* Имя действия для привязки клавиш движения назад.
|
||||
*/
|
||||
public static const ACTION_BACK:String = "ACTION_BACK";
|
||||
/**
|
||||
* Имя действия для привязки клавиш движения влево.
|
||||
*/
|
||||
public static const ACTION_LEFT:String = "ACTION_LEFT";
|
||||
/**
|
||||
* Имя действия для привязки клавиш движения вправо.
|
||||
*/
|
||||
public static const ACTION_RIGHT:String = "ACTION_RIGHT";
|
||||
/**
|
||||
* Имя действия для привязки клавиш движения вверх.
|
||||
*/
|
||||
public static const ACTION_UP:String = "ACTION_UP";
|
||||
/**
|
||||
* Имя действия для привязки клавиш движения вниз.
|
||||
*/
|
||||
public static const ACTION_DOWN:String = "ACTION_DOWN";
|
||||
/**
|
||||
* Имя действия для привязки клавиш поворота вверх.
|
||||
*/
|
||||
public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP";
|
||||
/**
|
||||
* Имя действия для привязки клавиш поворота вниз.
|
||||
*/
|
||||
public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN";
|
||||
/**
|
||||
* Имя действия для привязки клавиш поворота налево.
|
||||
*/
|
||||
public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT";
|
||||
/**
|
||||
* Имя действия для привязки клавиш поворота направо.
|
||||
*/
|
||||
public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT";
|
||||
/**
|
||||
* Имя действия для привязки клавиш увеличения скорости.
|
||||
*/
|
||||
public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE";
|
||||
/**
|
||||
* Имя действия для привязки клавиш активации обзора мышью.
|
||||
*/
|
||||
public static const ACTION_MOUSE_LOOK:String = "ACTION_MOUSE_LOOK";
|
||||
|
||||
/**
|
||||
* Флаг движения вперёд.
|
||||
*/
|
||||
protected var _forward:Boolean;
|
||||
/**
|
||||
* Флаг движения назад.
|
||||
*/
|
||||
protected var _back:Boolean;
|
||||
/**
|
||||
* Флаг движения влево.
|
||||
*/
|
||||
protected var _left:Boolean;
|
||||
/**
|
||||
* Флаг движения вправо.
|
||||
*/
|
||||
protected var _right:Boolean;
|
||||
/**
|
||||
* Флаг движения вверх.
|
||||
*/
|
||||
protected var _up:Boolean;
|
||||
/**
|
||||
* Флаг движения вниз.
|
||||
*/
|
||||
protected var _down:Boolean;
|
||||
/**
|
||||
* Флаг поворота относительно оси X в положительном направлении (взгляд вверх).
|
||||
*/
|
||||
protected var _pitchUp:Boolean;
|
||||
/**
|
||||
* Флаг поворота относительно оси X в отрицательном направлении (взгляд вниз).
|
||||
*/
|
||||
protected var _pitchDown:Boolean;
|
||||
/**
|
||||
* Флаг поворота относительно оси Z в положительном направлении (взгляд налево).
|
||||
*/
|
||||
protected var _yawLeft:Boolean;
|
||||
/**
|
||||
* Флаг активности поворота относительно оси Z в отрицательном направлении (взгляд направо).
|
||||
*/
|
||||
protected var _yawRight:Boolean;
|
||||
/**
|
||||
* Флаг активности режима ускорения.
|
||||
*/
|
||||
protected var _accelerate:Boolean;
|
||||
/**
|
||||
* Флаг активности режима поворотов мышью.
|
||||
*/
|
||||
protected var _mouseLookActive:Boolean;
|
||||
/**
|
||||
* Начальные координаты мышиного курсора в режиме mouse look.
|
||||
*/
|
||||
protected var startMouseCoords:Point3D = new Point3D();
|
||||
/**
|
||||
* Флаг активности контроллера.
|
||||
*/
|
||||
protected var _enabled:Boolean = true;
|
||||
/**
|
||||
* Источник событий клавиатуры и мыши
|
||||
*/
|
||||
protected var _eventsSource:DisplayObject;
|
||||
/**
|
||||
* Ассоциативный массив, связывающий коды клавиатурных клавиш с именами команд.
|
||||
*/
|
||||
protected var keyBindings:Map = new Map();
|
||||
/**
|
||||
* Ассоциативный массив, связывающий имена команд с реализующими их функциями. Функции должны иметь вид
|
||||
* function(value:Boolean):void. Значение параметра <code>value</code> указывает, нажата или отпущена соответсвующая команде
|
||||
* клавиша.
|
||||
*/
|
||||
protected var actionBindings:Map = new Map();
|
||||
/**
|
||||
* Флаг активности клавиатуры.
|
||||
*/
|
||||
protected var _keyboardEnabled:Boolean;
|
||||
/**
|
||||
* Флаг активности мыши.
|
||||
*/
|
||||
protected var _mouseEnabled:Boolean;
|
||||
/**
|
||||
* Общая чувствительность мыши. Коэффициент умножения чувствительности по вертикали и горизонтали.
|
||||
*/
|
||||
protected var _mouseSensitivity:Number = 1;
|
||||
/**
|
||||
* Коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
|
||||
*/
|
||||
protected var _mouseSensitivityY:Number = Math.PI / 360;
|
||||
/**
|
||||
* Коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
|
||||
*/
|
||||
protected var _mouseSensitivityX:Number = Math.PI / 360;
|
||||
/**
|
||||
* Результирующий коэффициент чувствительности мыши по вертикали. Значение угла в радианах на один пиксель перемещения мыши по вертикали.
|
||||
*/
|
||||
protected var _mouseCoefficientY:Number = _mouseSensitivity * _mouseSensitivityY;
|
||||
/**
|
||||
* Результирующий коэффициент чувствительности мыши по горизонтали. Значение угла в радианах на один пиксель перемещения мыши по горизонтали.
|
||||
*/
|
||||
protected var _mouseCoefficientX:Number = _mouseSensitivity * _mouseSensitivityX;
|
||||
/**
|
||||
* Угловая скорость поворота вокруг поперечной оси в радианах за секунду.
|
||||
*/
|
||||
protected var _pitchSpeed:Number = 1;
|
||||
/**
|
||||
* Угловая скорость поворота вокруг вертикальной оси в радианах за секунду.
|
||||
*/
|
||||
protected var _yawSpeed:Number = 1;
|
||||
/**
|
||||
* Скорость поступательного движения в единицах за секунду.
|
||||
*/
|
||||
protected var _speed:Number = 100;
|
||||
/**
|
||||
* Коэффициент увеличения скорости при соответствующей активной команде.
|
||||
*/
|
||||
protected var _speedMultiplier:Number = 2;
|
||||
/**
|
||||
* Управляемый объект.
|
||||
*/
|
||||
protected var _object:Object3D;
|
||||
/**
|
||||
* Время в секундах, прошедшее с последнего вызова метода processInput (обычно с последнего кадра).
|
||||
*/
|
||||
protected var lastFrameTime:uint;
|
||||
/**
|
||||
* Текущие координаты контроллера.
|
||||
*/
|
||||
protected var _coords:Point3D = new Point3D();
|
||||
/**
|
||||
* Индикатор движения объекта (перемещения или поворота) в текущем кадре.
|
||||
*/
|
||||
protected var _isMoving:Boolean;
|
||||
/**
|
||||
* Объект для определения столкновений.
|
||||
*/
|
||||
protected var _collider:EllipsoidCollider = new EllipsoidCollider();
|
||||
|
||||
/**
|
||||
* Включение и выключение режима проверки столкновений.
|
||||
*/
|
||||
public var checkCollisions:Boolean;
|
||||
/**
|
||||
* Функция вида <code>function():void</code>, вызываемая при начале движения объекта. Под движением
|
||||
* понимается изменение координат или ориентации.
|
||||
*/
|
||||
public var onStartMoving:Function;
|
||||
/**
|
||||
* Функция вида <code>function():void</code>, вызываемая при прекращении движения объекта. Под движением
|
||||
* понимается изменение координат или ориентации.
|
||||
*/
|
||||
public var onStopMoving:Function;
|
||||
|
||||
// Вектор смещения
|
||||
private var _displacement:Point3D = new Point3D();
|
||||
|
||||
/**
|
||||
* Создаёт новый экземпляр контролллера.
|
||||
*
|
||||
* @param eventsSourceObject источник событий клавиатуры и мыши
|
||||
*/
|
||||
public function ObjectController(eventsSourceObject:DisplayObject) {
|
||||
if (eventsSourceObject == null) {
|
||||
throw new ArgumentError(ObjectUtils.getClassName(this) + ": eventsSourceObject is null");
|
||||
}
|
||||
_eventsSource = eventsSourceObject;
|
||||
|
||||
actionBindings[ACTION_FORWARD] = moveForward;
|
||||
actionBindings[ACTION_BACK] = moveBack;
|
||||
actionBindings[ACTION_LEFT] = moveLeft;
|
||||
actionBindings[ACTION_RIGHT] = moveRight;
|
||||
actionBindings[ACTION_UP] = moveUp;
|
||||
actionBindings[ACTION_DOWN] = moveDown;
|
||||
actionBindings[ACTION_PITCH_UP] = pitchUp;
|
||||
actionBindings[ACTION_PITCH_DOWN] = pitchDown;
|
||||
actionBindings[ACTION_YAW_LEFT] = yawLeft;
|
||||
actionBindings[ACTION_YAW_RIGHT] = yawRight;
|
||||
actionBindings[ACTION_ACCELERATE] = accelerate;
|
||||
actionBindings[ACTION_MOUSE_LOOK] = setMouseLook;
|
||||
|
||||
keyboardEnabled = true;
|
||||
mouseEnabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Включение и выключение контроллера. Выключенный контроллер пропускает выполнение метода <code>processInput()</code>.
|
||||
*
|
||||
* @default true
|
||||
*
|
||||
* @see #processInput()
|
||||
*/
|
||||
public function get enabled():Boolean {
|
||||
return _enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set enabled(value:Boolean):void {
|
||||
_enabled = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Координаты контроллера. Координаты совпадают с координатами центра эллипсоида, используемого для определения
|
||||
* столкновений. Координаты управляемого объекта могут не совпадать с координатами контроллера.
|
||||
*
|
||||
* @see #setObjectCoords()
|
||||
*/
|
||||
public function get coords():Point3D {
|
||||
return _coords.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set coords(value:Point3D):void {
|
||||
_coords.copy(value);
|
||||
setObjectCoords();
|
||||
}
|
||||
|
||||
/**
|
||||
* Чтение координат контроллера в заданную переменную.
|
||||
*
|
||||
* @param point переменная, в которую записываются координаты контроллера
|
||||
*/
|
||||
public function readCoords(point:Point3D):void {
|
||||
point.copy(_coords);
|
||||
}
|
||||
|
||||
/**
|
||||
* Управляемый объект.
|
||||
*/
|
||||
public function get object():Object3D {
|
||||
return _object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* При установке объекта устанавливается сцена для коллайдера.
|
||||
*/
|
||||
public function set object(value:Object3D):void {
|
||||
_object = value;
|
||||
_collider.scene = _object == null ? null : _object.scene;
|
||||
}
|
||||
|
||||
/**
|
||||
* Объект, реализующий проверку столкновений для эллипсоида.
|
||||
*/
|
||||
public function get collider():EllipsoidCollider {
|
||||
return _collider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация движения вперёд.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function moveForward(value:Boolean):void {
|
||||
_forward = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация движения назад.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function moveBack(value:Boolean):void {
|
||||
_back = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация движения влево.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function moveLeft(value:Boolean):void {
|
||||
_left = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация движения вправо.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function moveRight(value:Boolean):void {
|
||||
_right = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация движения вверх.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function moveUp(value:Boolean):void {
|
||||
_up = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация движения вниз.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function moveDown(value:Boolean):void {
|
||||
_down = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация поворота вверх.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function pitchUp(value:Boolean):void {
|
||||
_pitchUp = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация поворота вниз.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function pitchDown(value:Boolean):void {
|
||||
_pitchDown = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация поворота влево.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function yawLeft(value:Boolean):void {
|
||||
_yawLeft = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация поворота вправо.
|
||||
*
|
||||
* @param value <code>true</code> для начала движения, <code>false</code> для окончания
|
||||
*/
|
||||
public function yawRight(value:Boolean):void {
|
||||
_yawRight = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация режима увеличенной скорости.
|
||||
*
|
||||
* @param value <code>true</code> для включения ускорения, <code>false</code> для выключения
|
||||
*/
|
||||
public function accelerate(value:Boolean):void {
|
||||
_accelerate = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Угловая скорость поворота вокруг поперечной оси (радианы в секунду).
|
||||
*
|
||||
* @default 1
|
||||
*/
|
||||
public function get pitchSpeed():Number {
|
||||
return _pitchSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set pitchSpeed(spd:Number):void {
|
||||
_pitchSpeed = spd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Угловая скорость поворота вокруг вертикальной оси (радианы в секунду).
|
||||
*
|
||||
* @default 1
|
||||
*/
|
||||
public function get yawSpeed():Number {
|
||||
return _yawSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set yawSpeed(spd:Number):void {
|
||||
_yawSpeed = spd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Скорость движения в единицах за секунду. При установке отрицательного значения берётся модуль.
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
public function get speed():Number {
|
||||
return _speed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set speed(value:Number):void {
|
||||
_speed = value < 0 ? -value : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Коэффициент увеличения скорости при активном действии <code>ACTION_ACCELERATE</code>.
|
||||
*
|
||||
* @default 2
|
||||
*/
|
||||
public function get speedMultiplier():Number {
|
||||
return _speedMultiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set speedMultiplier(value:Number):void {
|
||||
_speedMultiplier = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Чувствительность мыши — коэффициент умножения <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>,
|
||||
* при выключении — <code>stoptMouseLook()</code>.
|
||||
*
|
||||
* @see #startMouseLook()
|
||||
* @see #stopMouseLook()
|
||||
*/
|
||||
public function setMouseLook(value:Boolean):void {
|
||||
if (_mouseLookActive != value) {
|
||||
_mouseLookActive = value;
|
||||
if (_mouseLookActive) {
|
||||
startMouseLook();
|
||||
} else {
|
||||
stopMouseLook();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод выполняет необходимые действия при включении режима вращения объекта мышью.
|
||||
* Реализация по умолчанию записывает начальные глобальные координаты курсора мыши в переменную <code>startMouseCoords</code>.
|
||||
*
|
||||
* @see #startMouseCoords
|
||||
* @see #setMouseLook()
|
||||
* @see #stopMouseLook()
|
||||
*/
|
||||
protected function startMouseLook():void {
|
||||
startMouseCoords.x = _eventsSource.stage.mouseX;
|
||||
startMouseCoords.y = _eventsSource.stage.mouseY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод выполняет необходимые действия при выключении вращения объекта мышью. Реализация по умолчанию не делает
|
||||
* ничего.
|
||||
*
|
||||
* @see #setMouseLook()
|
||||
* @see #startMouseLook()
|
||||
*/
|
||||
protected function stopMouseLook():void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод выполняет обработку всех воздействий на объект. Если объект не установлен или свойство <code>enabled</code>
|
||||
* равно <code>false</code>, метод не выполняется.
|
||||
* <p>
|
||||
* Алгоритм работы метода следующий:
|
||||
* <ul>
|
||||
* <li> Вычисляется время в секундах, прошедшее с последнего вызова метода (с последнего кадра). Это время считается
|
||||
* длительностью текущего кадра;
|
||||
* <li> Вызывается метод rotateObject(), который изменяет ориентацию объекта в соответствии с воздействиями;
|
||||
* <li> Вызывается метод getDisplacement(), который вычисляет потенциальное перемещение объекта;
|
||||
* <li> Вызывается метод applyDisplacement(), которому передаётся вектор перемещения, полученный на предыдущем шаге.
|
||||
* Задачей метода является применение заданного вектора перемещения;
|
||||
* <li> При необходимости вызываются обработчики начала и прекращения движения управляемого объекта;
|
||||
* </ul>
|
||||
*/
|
||||
public function processInput():void {
|
||||
if (!_enabled || _object == null) {
|
||||
return;
|
||||
}
|
||||
var frameTime:Number = getTimer() - lastFrameTime;
|
||||
lastFrameTime += frameTime;
|
||||
if (frameTime > 100) {
|
||||
frameTime = 100;
|
||||
}
|
||||
frameTime /= 1000;
|
||||
|
||||
rotateObject(frameTime);
|
||||
getDisplacement(frameTime, _displacement);
|
||||
applyDisplacement(frameTime, _displacement);
|
||||
|
||||
// Обработка начала/окончания движения
|
||||
if (_object.changeRotationOrScaleOperation.queued || _object.changeCoordsOperation.queued) {
|
||||
if (!_isMoving) {
|
||||
_isMoving = true;
|
||||
if (onStartMoving != null) {
|
||||
onStartMoving.call(this);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_isMoving) {
|
||||
_isMoving = false;
|
||||
if (onStopMoving != null) {
|
||||
onStopMoving.call(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Реализация по умолчанию не делает ничего.
|
||||
*
|
||||
* @param frameTime длительность текущего кадра в секундах
|
||||
*/
|
||||
protected function rotateObject(frameTime:Number):void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод вычисляет потенциальное смещение объекта за кадр. Реализация по умолчанию не делает ничего.
|
||||
*
|
||||
* @param frameTime длительность текущего кадра в секундах
|
||||
* @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта
|
||||
*/
|
||||
protected function getDisplacement(frameTime:Number, displacement:Point3D):void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод применяет потенциальное смещение объекта. Реализация по умолчанию не делает ничего.
|
||||
*
|
||||
* @param frameTime длительность текущего кадра в секундах
|
||||
* @param displacement смещение объекта, которое нужно обработать
|
||||
*/
|
||||
protected function applyDisplacement(frameTime:Number, displacement:Point3D):void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод выполняет привязку клавиши к действию. Одной клавише может быть назначено только одно действие.
|
||||
*
|
||||
* @param keyCode код клавиши
|
||||
* @param action наименование действия
|
||||
*
|
||||
* @see #unbindKey()
|
||||
* @see #unbindAll()
|
||||
*/
|
||||
public function bindKey(keyCode:uint, action:String):void {
|
||||
var method:Function = actionBindings[action];
|
||||
if (method != null) {
|
||||
keyBindings[keyCode] = method;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Очистка привязки клавиши.
|
||||
*
|
||||
* @param keyCode код клавиши
|
||||
*
|
||||
* @see #bindKey()
|
||||
* @see #unbindAll()
|
||||
*/
|
||||
public function unbindKey(keyCode:uint):void {
|
||||
keyBindings.remove(keyCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Очистка привязки всех клавиш.
|
||||
*
|
||||
* @see #bindKey()
|
||||
* @see #unbindKey()
|
||||
*/
|
||||
public function unbindAll():void {
|
||||
keyBindings.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод устанавливает привязки клавиш по умолчанию. Реализация по умолчанию не делает ничего.
|
||||
*
|
||||
* @see #bindKey()
|
||||
* @see #unbindKey()
|
||||
* @see #unbindAll()
|
||||
*/
|
||||
public function setDefaultBindings():void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Включение и выключение обработки клавиатурных событий. При включении выполняется метод <code>registerKeyboardListeners</code>,
|
||||
* при выключении — <code>unregisterKeyboardListeners</code>.
|
||||
*
|
||||
* @see #registerKeyboardListeners()
|
||||
* @see #unregisterKeyboardListeners()
|
||||
*/
|
||||
public function get keyboardEnabled():Boolean {
|
||||
return _keyboardEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set keyboardEnabled(value:Boolean):void {
|
||||
if (_keyboardEnabled != value) {
|
||||
_keyboardEnabled = value;
|
||||
if (_keyboardEnabled) {
|
||||
registerKeyboardListeners();
|
||||
} else {
|
||||
unregisterKeyboardListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Запуск обработчиков клавиатурных команд.
|
||||
*/
|
||||
private function onKeyboardEvent(e:KeyboardEvent):void {
|
||||
var method:Function = keyBindings[e.keyCode];
|
||||
if (method != null) {
|
||||
method.call(this, e.type == KeyboardEvent.KEY_DOWN);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Регистрация необходимых обработчиков при включении обработки клавиатурных событий.
|
||||
*
|
||||
* @see #unregisterKeyboardListeners()
|
||||
*/
|
||||
protected function registerKeyboardListeners():void {
|
||||
_eventsSource.addEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent);
|
||||
_eventsSource.addEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление обработчиков при выключении обработки клавиатурных событий.
|
||||
*
|
||||
* @see #registerKeyboardListeners()
|
||||
*/
|
||||
protected function unregisterKeyboardListeners():void {
|
||||
_eventsSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent);
|
||||
_eventsSource.removeEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Включение и выключение обработки мышиных событий. При включении выполняется метод <code>registerMouseListeners</code>,
|
||||
* при выключении — <code>unregisterMouseListeners</code>.
|
||||
*
|
||||
* @see #registerMouseListeners()
|
||||
* @see #unregisterMouseListeners()
|
||||
*/
|
||||
public function get mouseEnabled():Boolean {
|
||||
return _mouseEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set mouseEnabled(value:Boolean):void {
|
||||
if (_mouseEnabled != value) {
|
||||
_mouseEnabled = value;
|
||||
if (_mouseEnabled) {
|
||||
registerMouseListeners();
|
||||
} else {
|
||||
unregisterMouseListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Регистрация необходимых обработчиков при включении обработки мышиных событий.
|
||||
*
|
||||
* @see #unregisterMouseListeners()
|
||||
*/
|
||||
protected function registerMouseListeners():void {
|
||||
_eventsSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление используемых обработчиков при выключении обработки мышиных событий.
|
||||
*
|
||||
* @see #registerMouseListeners()
|
||||
*/
|
||||
protected function unregisterMouseListeners():void {
|
||||
_eventsSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
|
||||
_eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация mouselook
|
||||
*/
|
||||
private function onMouseDown(e:MouseEvent):void {
|
||||
setMouseLook(true);
|
||||
_eventsSource.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Отключение mouselook
|
||||
*/
|
||||
private function onMouseUp(e:MouseEvent):void {
|
||||
setMouseLook(false);
|
||||
_eventsSource.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка координат управляемого объекта в зависимости от текущих координат контроллера.
|
||||
*/
|
||||
protected function setObjectCoords():void {
|
||||
_object.coords = _coords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Индикатор режима увеличенной скорости.
|
||||
*/
|
||||
public function get accelerated():Boolean {
|
||||
return _accelerate;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
package alternativa.engine3d.controllers {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.Camera3D;
|
||||
import alternativa.engine3d.core.Mesh;
|
||||
import alternativa.engine3d.core.Object3D;
|
||||
import alternativa.engine3d.physics.Collision;
|
||||
import alternativa.types.Matrix3D;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.utils.KeyboardUtils;
|
||||
import alternativa.utils.MathUtils;
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Контроллер, реализующий управление движением объекта, находящегося в системе координат корневого объекта сцены.
|
||||
*
|
||||
* <p>Контроллер предоставляет два режима движения: режим ходьбы с учётом силы тяжести и режим полёта, в котором сила
|
||||
* тяжести не учитывается. В обоих режимах может быть включена проверка столкновений с объектами сцены. Если проверка
|
||||
* столкновений отключена, то в режиме ходьбы сила тяжести также игнорируется и дополнительно появляется возможность
|
||||
* движения по вертикали.
|
||||
*
|
||||
* <p>Для всех объектов, за исключением <code>Camera3D</code>, направлением "вперёд" считается направление его оси
|
||||
* <code>Y</code>, направлением "вверх" — направление оси <code>Z</code>. Для объектов класса
|
||||
* <code>Camera3D</code> направление "вперёд" совпадает с направлением локальной оси <code>Z</code>, а направление
|
||||
* "вверх" противоположно направлению локальной оси <code>Y</code>.
|
||||
*
|
||||
* <p>Вне зависимости от того, включена проверка столкновений или нет, координаты при перемещении расчитываются для
|
||||
* эллипсоида, параметры которого устанавливаются через свойство <code>collider</code>. Координаты управляемого
|
||||
* объекта вычисляются исходя из положения центра эллипсоида и положения объекта на вертикальной оси эллипсоида,
|
||||
* задаваемого параметром <code>objectZPosition</code>.
|
||||
*
|
||||
* <p>Команда <code>ACTION_UP</code> в режиме ходьбы при ненулевой гравитации вызывает прыжок, в остальных случаях
|
||||
* происходит движение вверх.
|
||||
*/
|
||||
public class WalkController extends ObjectController {
|
||||
/**
|
||||
* Величина ускорения свободного падения. При положительном значении сила тяжести направлена против оси Z,
|
||||
* при отрицательном — по оси Z.
|
||||
*/
|
||||
public var gravity:Number = 0;
|
||||
/**
|
||||
* Вертикальная скорость прыжка.
|
||||
*/
|
||||
public var jumpSpeed:Number = 0;
|
||||
/**
|
||||
* Объект, на котором стоит эллипсоид при ненулевой гравитации.
|
||||
*/
|
||||
private var _groundMesh:Mesh;
|
||||
/**
|
||||
* Погрешность определения скорости. В режиме полёта или в режиме ходьбы при нахождении на поверхности
|
||||
* скорость приравнивается к нулю, если её модуль не превышает заданного значения.
|
||||
*/
|
||||
public var speedThreshold:Number = 1;
|
||||
|
||||
// Коэффициент эффективности управления перемещением при нахождении в воздухе в режиме ходьбы и нулевой гравитации.
|
||||
private var _airControlCoefficient:Number = 1;
|
||||
|
||||
private var _currentSpeed:Number = 0;
|
||||
|
||||
private var minGroundCos:Number = Math.cos(MathUtils.toRadian(70));
|
||||
|
||||
private var destination:Point3D = new Point3D();
|
||||
private var collision:Collision = new Collision();
|
||||
|
||||
private var _objectZPosition:Number = 0.5;
|
||||
private var _flyMode:Boolean;
|
||||
private var _onGround:Boolean;
|
||||
|
||||
private var velocity:Point3D = new Point3D();
|
||||
private var tmpVelocity:Point3D = new Point3D();
|
||||
|
||||
private var controlsActive:Boolean;
|
||||
|
||||
private var inJump:Boolean;
|
||||
private var startRotX:Number;
|
||||
private var startRotZ:Number;
|
||||
|
||||
// Координаты мышиного курсора в режиме mouse look в предыдущем кадре.
|
||||
private var prevMouseCoords:Point3D = new Point3D();
|
||||
// Текущие координаты мышиного курсора в режиме mouse look.
|
||||
private var currentMouseCoords:Point3D = new Point3D();
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function WalkController(eventSourceObject:DisplayObject) {
|
||||
super(eventSourceObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Объект, на котором стоит эллипсоид при ненулевой гравитации.
|
||||
*/
|
||||
public function get groundMesh():Mesh {
|
||||
return _groundMesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Направление объекта на точку. В результате работы метода локальная ось объекта, соответствующая направлению "вперёд"
|
||||
* будет направлена на указанную точку, а угол поворота вокруг этой оси будет равен нулю.
|
||||
*
|
||||
* @param point координаты точки, на которую должен быть направлен объект
|
||||
*/
|
||||
public function lookAt(point:Point3D):void {
|
||||
if (_object == null) {
|
||||
return;
|
||||
}
|
||||
var dx:Number = point.x - _object.x;
|
||||
var dy:Number = point.y - _object.y;
|
||||
var dz:Number = point.z - _object.z;
|
||||
_object.rotationX = Math.atan2(dz, Math.sqrt(dx * dx + dy * dy)) - (_object is Camera3D ? MathUtils.DEG90 : 0);
|
||||
_object.rotationY = 0;
|
||||
_object.rotationZ = -Math.atan2(dx, dy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Коэффициент эффективности управления перемещением в режиме ходьбы при нахождении в воздухе и ненулевой гравитации.
|
||||
* Значение 0 обозначает полное отсутствие контроля, значение 1 указывает, что управление так же эффективно, как при
|
||||
* нахождении на поверхности.
|
||||
*
|
||||
* @default 1
|
||||
*/
|
||||
public function get airControlCoefficient():Number {
|
||||
return _airControlCoefficient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set airControlCoefficient(value:Number):void {
|
||||
_airControlCoefficient = value > 0 ? value : -value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Максимальный угол наклона поверхности в радианах, на которой возможен прыжок и на которой объект стоит на месте
|
||||
* в отсутствие управляющих воздействий. Если угол наклона поверхности превышает заданное значение, свойство
|
||||
* <code>onGround</code> будет иметь значение <code>false</code>.
|
||||
*
|
||||
* @see #onGround
|
||||
*/
|
||||
public function get maxGroundAngle():Number {
|
||||
return Math.acos(minGroundCos);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set maxGroundAngle(value:Number):void {
|
||||
minGroundCos = Math.cos(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Положение управляемого объекта на оси Z эллипсоида. Значение 0 указывает положение в нижней точке эллипсоида,
|
||||
* значение 1 -- положение в верхней точке эллипсоида.
|
||||
*/
|
||||
public function get objectZPosition():Number {
|
||||
return _objectZPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set objectZPosition(value:Number):void {
|
||||
_objectZPosition = value;
|
||||
setObjectCoords();
|
||||
}
|
||||
|
||||
/**
|
||||
* Включене и выключение режима полёта.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
public function get flyMode():Boolean {
|
||||
return _flyMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set flyMode(value:Boolean):void {
|
||||
_flyMode = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Индикатор положения эллипсоида на поверхности в режиме ходьбы. Считается, что эллипсоид находится на поверхности,
|
||||
* если угол наклона поверхности под ним не превышает заданного свойством <code>maxGroundAngle</code> значения.
|
||||
*
|
||||
* @see #maxGroundAngle
|
||||
*/
|
||||
public function get onGround():Boolean {
|
||||
return _onGround;
|
||||
}
|
||||
|
||||
/**
|
||||
* Модуль текущей скорости.
|
||||
*/
|
||||
public function get currentSpeed():Number {
|
||||
return _currentSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
|
||||
* <table border="1" style="border-collapse: collapse">
|
||||
* <tr><th>Клавиша</th><th>Действие</th></tr>
|
||||
* <tr><td>W</td><td>ACTION_FORWARD</td></tr>
|
||||
* <tr><td>S</td><td>ACTION_BACK</td></tr>
|
||||
* <tr><td>A</td><td>ACTION_LEFT</td></tr>
|
||||
* <tr><td>D</td><td>ACTION_RIGHT</td></tr>
|
||||
* <tr><td>SPACE</td><td>ACTION_UP</td></tr>
|
||||
* <tr><td>CONTROL</td><td>ACTION_DOWN</td></tr>
|
||||
* <tr><td>SHIFT</td><td>ACTION_ACCELERATE</td></tr>
|
||||
* <tr><td>UP</td><td>ACTION_PITCH_UP</td></tr>
|
||||
* <tr><td>DOWN</td><td>ACTION_PITCH_DOWN</td></tr>
|
||||
* <tr><td>LEFT</td><td>ACTION_YAW_LEFT</td></tr>
|
||||
* <tr><td>RIGHT</td><td>ACTION_YAW_RIGHT</td></tr>
|
||||
* <tr><td>M</td><td>ACTION_MOUSE_LOOK</td></tr>
|
||||
* </table>
|
||||
*/
|
||||
override public function setDefaultBindings():void {
|
||||
unbindAll();
|
||||
bindKey(KeyboardUtils.W, ACTION_FORWARD);
|
||||
bindKey(KeyboardUtils.S, ACTION_BACK);
|
||||
bindKey(KeyboardUtils.A, ACTION_LEFT);
|
||||
bindKey(KeyboardUtils.D, ACTION_RIGHT);
|
||||
bindKey(KeyboardUtils.SPACE, ACTION_UP);
|
||||
bindKey(KeyboardUtils.CONTROL, ACTION_DOWN);
|
||||
bindKey(KeyboardUtils.UP, ACTION_PITCH_UP);
|
||||
bindKey(KeyboardUtils.DOWN, ACTION_PITCH_DOWN);
|
||||
bindKey(KeyboardUtils.LEFT, ACTION_YAW_LEFT);
|
||||
bindKey(KeyboardUtils.RIGHT, ACTION_YAW_RIGHT);
|
||||
bindKey(KeyboardUtils.SHIFT, ACTION_ACCELERATE);
|
||||
bindKey(KeyboardUtils.M, ACTION_MOUSE_LOOK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод выполняет поворот объекта в соответствии с имеющимися воздействиями. Взгляд вверх и вниз ограничен
|
||||
* отклонением в 90 градусов от горизонтали.
|
||||
*
|
||||
* @param frameTime длительность текущего кадра в секундах
|
||||
*/
|
||||
override protected function rotateObject(frameTime:Number):void {
|
||||
// Mouse look
|
||||
var rotX:Number;
|
||||
if (_mouseLookActive) {
|
||||
prevMouseCoords.copy(currentMouseCoords);
|
||||
currentMouseCoords.x = _eventsSource.stage.mouseX;
|
||||
currentMouseCoords.y = _eventsSource.stage.mouseY;
|
||||
if (!prevMouseCoords.equals(currentMouseCoords)) {
|
||||
_object.rotationZ = startRotZ + (startMouseCoords.x - currentMouseCoords.x) * _mouseCoefficientX;
|
||||
rotX = startRotX + (startMouseCoords.y - currentMouseCoords.y) * _mouseCoefficientY;
|
||||
if (_object is Camera3D) {
|
||||
// Коррекция поворота для камеры
|
||||
_object.rotationX = (rotX > MathUtils.DEG90) ? 0 : (rotX < -MathUtils.DEG90) ? -Math.PI : rotX - MathUtils.DEG90;
|
||||
} else {
|
||||
_object.rotationX = (rotX > MathUtils.DEG90) ? MathUtils.DEG90 : (rotX < -MathUtils.DEG90) ? -MathUtils.DEG90 : rotX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Повороты влево-вправо
|
||||
if (_yawLeft) {
|
||||
_object.rotationZ += _yawSpeed * frameTime;
|
||||
} else if (_yawRight) {
|
||||
_object.rotationZ -= _yawSpeed * frameTime;
|
||||
}
|
||||
// Взгляд вверх-вниз
|
||||
rotX = NaN;
|
||||
if (_pitchUp) {
|
||||
rotX = _object.rotationX + _pitchSpeed * frameTime;
|
||||
} else if (_pitchDown) {
|
||||
rotX = _object.rotationX - _pitchSpeed * frameTime;
|
||||
}
|
||||
if (!isNaN(rotX)) {
|
||||
if (_object is Camera3D) {
|
||||
// Коррекция поворота для камеры
|
||||
_object.rotationX = (rotX > 0) ? 0 : (rotX < -Math.PI) ? -Math.PI : rotX;
|
||||
} else {
|
||||
_object.rotationX = (rotX > MathUtils.DEG90) ? MathUtils.DEG90 : (rotX < -MathUtils.DEG90) ? -MathUtils.DEG90 : rotX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод вычисляет вектор потенциального смещения эллипсоида, учитывая режим перемещения, команды перемещения и силу тяжести.
|
||||
*
|
||||
* @param frameTime длительность текущего кадра в секундах
|
||||
* @param displacement в эту переменную записывается вычисленное потенциальное смещение объекта
|
||||
*/
|
||||
override protected function getDisplacement(frameTime:Number, displacement:Point3D):void {
|
||||
var cos:Number = 0;
|
||||
if (checkCollisions && !_flyMode) {
|
||||
// Проверка наличия под ногами поверхности
|
||||
displacement.x = 0;
|
||||
displacement.y = 0;
|
||||
displacement.z = - 0.5 * gravity * frameTime * frameTime;
|
||||
if (_collider.getCollision(_coords, displacement, collision)) {
|
||||
cos = collision.normal.z;
|
||||
_groundMesh = collision.face._mesh;
|
||||
} else {
|
||||
_groundMesh = null;
|
||||
}
|
||||
}
|
||||
_onGround = cos > minGroundCos;
|
||||
|
||||
if (_onGround && inJump) {
|
||||
inJump = false;
|
||||
}
|
||||
|
||||
var len:Number;
|
||||
var x:Number;
|
||||
var y:Number;
|
||||
var z:Number;
|
||||
|
||||
// При наличии управляющих воздействий расчитывается приращение скорости
|
||||
controlsActive = _forward || _back || _right || _left || _up || _down;
|
||||
if (controlsActive) {
|
||||
if (_flyMode) {
|
||||
tmpVelocity.x = 0;
|
||||
tmpVelocity.y = 0;
|
||||
tmpVelocity.z = 0;
|
||||
// Режим полёта, ускорения направлены вдоль локальных осей
|
||||
// Ускорение вперёд-назад
|
||||
if (_forward) {
|
||||
tmpVelocity.y = 1;
|
||||
} else if (_back) {
|
||||
tmpVelocity.y = -1;
|
||||
}
|
||||
// Ускорение влево-вправо
|
||||
if (_right) {
|
||||
tmpVelocity.x = 1;
|
||||
} else if (_left) {
|
||||
tmpVelocity.x = -1;
|
||||
}
|
||||
// Ускорение вверх-вниз
|
||||
if (_up) {
|
||||
tmpVelocity.z = 1;
|
||||
} else if (_down) {
|
||||
tmpVelocity.z = -1;
|
||||
}
|
||||
var matrix:Matrix3D = _object.transformation;
|
||||
x = tmpVelocity.x;
|
||||
if (_object is Camera3D) {
|
||||
y = -tmpVelocity.z;
|
||||
z = tmpVelocity.y;
|
||||
} else {
|
||||
y = tmpVelocity.y;
|
||||
z = tmpVelocity.z;
|
||||
}
|
||||
// Поворот вектора из локальной системы координат объекта в глобальную
|
||||
velocity.x += (x * matrix.a + y * matrix.b + z * matrix.c) * _speed;
|
||||
velocity.y += (x * matrix.e + y * matrix.f + z * matrix.g) * _speed;
|
||||
velocity.z += (x * matrix.i + y * matrix.j + z * matrix.k) * _speed;
|
||||
} else {
|
||||
|
||||
// Режим хождения, ускорения вперёд-назад-влево-вправо лежат в глобальной плоскости XY, вверх-вниз направлены вдоль глобальной оси Z
|
||||
var heading:Number = _object.rotationZ;
|
||||
var headingCos:Number = Math.cos(heading);
|
||||
var headingSin:Number = Math.sin(heading);
|
||||
|
||||
var spd:Number = _speed;
|
||||
if (gravity != 0 && !_onGround) {
|
||||
spd *= _airControlCoefficient;
|
||||
}
|
||||
|
||||
// Вперёд-назад
|
||||
if (_forward) {
|
||||
velocity.x -= spd * headingSin;
|
||||
velocity.y += spd * headingCos;
|
||||
} else if (_back) {
|
||||
velocity.x += spd * headingSin;
|
||||
velocity.y -= spd * headingCos;
|
||||
}
|
||||
// Влево-вправо
|
||||
if (_right) {
|
||||
velocity.x += spd * headingCos;
|
||||
velocity.y += spd * headingSin;
|
||||
} else if (_left) {
|
||||
velocity.x -= spd * headingCos;
|
||||
velocity.y -= spd * headingSin;
|
||||
}
|
||||
if (gravity == 0) {
|
||||
// Ускорение вверх-вниз
|
||||
if (_up) {
|
||||
velocity.z += _speed;
|
||||
} else if (_down) {
|
||||
velocity.z -= _speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Управление неактивно, замедление движения
|
||||
len = 1 / Math.pow(3, frameTime * 10);
|
||||
if (_flyMode || gravity == 0) {
|
||||
velocity.x *= len;
|
||||
velocity.y *= len;
|
||||
velocity.z *= len;
|
||||
} else {
|
||||
if (_onGround) {
|
||||
velocity.x *= len;
|
||||
velocity.y *= len;
|
||||
if (velocity.z < 0) {
|
||||
velocity.z *= len;
|
||||
}
|
||||
} else {
|
||||
if (cos > 0 && velocity.z > 0) {
|
||||
velocity.z = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Прыжок
|
||||
if (_onGround && _up && !inJump) {
|
||||
velocity.z = jumpSpeed;
|
||||
inJump = true;
|
||||
_onGround = false;
|
||||
cos = 0;
|
||||
}
|
||||
// В режиме ходьбы добавляется ускорение свободного падения, если находимся не на ровной поверхности
|
||||
if (!(_flyMode || _onGround)) {
|
||||
velocity.z -= gravity * frameTime;
|
||||
}
|
||||
|
||||
// Ограничение скорости
|
||||
var max:Number = _accelerate ? _speed * _speedMultiplier : _speed;
|
||||
if (_flyMode || gravity == 0) {
|
||||
len = velocity.length;
|
||||
if (len > max) {
|
||||
velocity.length = max;
|
||||
}
|
||||
} else {
|
||||
len = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y);
|
||||
if (len > max) {
|
||||
velocity.x *= max / len;
|
||||
velocity.y *= max / len;
|
||||
}
|
||||
if (cos > 0 && velocity.z > 0) {
|
||||
velocity.z = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Cмещение за кадр
|
||||
displacement.x = velocity.x * frameTime;
|
||||
displacement.y = velocity.y * frameTime;
|
||||
displacement.z = velocity.z * frameTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод применяет потенциальный вектор смещения к эллипсоиду с учётом столкновений с геометрией сцены, если включён
|
||||
* соотвествующий режим.
|
||||
*
|
||||
* @param frameTime время кадра в секундах
|
||||
* @param displacement векотр потенциального смещения эллипсоида
|
||||
*/
|
||||
override protected function applyDisplacement(frameTime:Number, displacement:Point3D):void {
|
||||
if (checkCollisions) {
|
||||
_collider.calculateDestination(_coords, displacement, destination);
|
||||
|
||||
displacement.x = destination.x - _coords.x;
|
||||
displacement.y = destination.y - _coords.y;
|
||||
displacement.z = destination.z - _coords.z;
|
||||
} else {
|
||||
destination.x = _coords.x + displacement.x;
|
||||
destination.y = _coords.y + displacement.y;
|
||||
destination.z = _coords.z + displacement.z;
|
||||
}
|
||||
|
||||
velocity.x = displacement.x / frameTime;
|
||||
velocity.y = displacement.y / frameTime;
|
||||
velocity.z = displacement.z / frameTime;
|
||||
|
||||
_coords.x = destination.x;
|
||||
_coords.y = destination.y;
|
||||
_coords.z = destination.z;
|
||||
setObjectCoords();
|
||||
|
||||
var len:Number = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
|
||||
if (len < speedThreshold) {
|
||||
velocity.x = 0;
|
||||
velocity.y = 0;
|
||||
velocity.z = 0;
|
||||
_currentSpeed = 0;
|
||||
} else {
|
||||
_currentSpeed = len;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод устанавливает координаты управляемого объекта в зависимости от параметра <code>objectZPosition</code>.
|
||||
*
|
||||
* @see #objectZPosition
|
||||
*/
|
||||
override protected function setObjectCoords():void {
|
||||
_object.x = _coords.x;
|
||||
_object.y = _coords.y;
|
||||
_object.z = _coords.z + (2 * _objectZPosition - 1) * _collider.radiusZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод выполняет необходимые действия при включении вращения объекта мышью.
|
||||
*/
|
||||
override protected function startMouseLook():void {
|
||||
super.startMouseLook();
|
||||
startRotX = _object is Camera3D ? _object.rotationX + MathUtils.DEG90 : _object.rotationX;
|
||||
startRotZ = _object.rotationZ;
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Alternativa3D5/5.4/alternativa/engine3d/core/BSPNode.as
Normal file
65
Alternativa3D5/5.4/alternativa/engine3d/core/BSPNode.as
Normal file
@@ -0,0 +1,65 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.types.Set;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public final class BSPNode {
|
||||
|
||||
// Родительская нода
|
||||
alternativa3d var parent:BSPNode;
|
||||
|
||||
// Дочерние ветки
|
||||
alternativa3d var front:BSPNode;
|
||||
alternativa3d var back:BSPNode;
|
||||
|
||||
// Нормаль плоскости ноды
|
||||
alternativa3d var normal:Point3D = new Point3D();
|
||||
|
||||
// Смещение плоскости примитива
|
||||
alternativa3d var offset:Number;
|
||||
|
||||
// Минимальная мобильность ноды
|
||||
alternativa3d var mobility:int = int.MAX_VALUE;
|
||||
|
||||
// Набор примитивов в ноде
|
||||
alternativa3d var primitive:PolyPrimitive;
|
||||
alternativa3d var backPrimitives:Set;
|
||||
alternativa3d var frontPrimitives:Set;
|
||||
|
||||
// Хранилище неиспользуемых нод
|
||||
static private var collector:Array = new Array();
|
||||
|
||||
// Создать ноду на основе примитива
|
||||
static alternativa3d function createBSPNode(primitive:PolyPrimitive):BSPNode {
|
||||
// Достаём ноду из коллектора
|
||||
var node:BSPNode = collector.pop();
|
||||
// Если коллектор пуст, создаём новую ноду
|
||||
if (node == null) {
|
||||
node = new BSPNode();
|
||||
}
|
||||
|
||||
// Добавляем примитив в ноду
|
||||
node.primitive = primitive;
|
||||
// Сохраняем ноду
|
||||
primitive.node = node;
|
||||
// Сохраняем плоскость
|
||||
node.normal.copy(primitive.face.globalNormal);
|
||||
node.offset = primitive.face.globalOffset;
|
||||
// Сохраняем мобильность
|
||||
node.mobility = primitive.mobility;
|
||||
return node;
|
||||
}
|
||||
|
||||
// Удалить ноду, все ссылки должны быть почищены
|
||||
static alternativa3d function destroyBSPNode(node:BSPNode):void {
|
||||
//trace(node.back, node.front, node.parent, node.primitive, node.backPrimitives, node.frontPrimitives);
|
||||
collector.push(node);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
902
Alternativa3D5/5.4/alternativa/engine3d/core/Camera3D.as
Normal file
902
Alternativa3D5/5.4/alternativa/engine3d/core/Camera3D.as
Normal file
@@ -0,0 +1,902 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.display.Skin;
|
||||
import alternativa.engine3d.display.View;
|
||||
import alternativa.engine3d.materials.DrawPoint;
|
||||
import alternativa.engine3d.materials.SurfaceMaterial;
|
||||
import alternativa.types.Matrix3D;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.types.Set;
|
||||
|
||||
import flash.geom.Matrix;
|
||||
import flash.geom.Point;
|
||||
import alternativa.utils.MathUtils;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Камера для отображения 3D-сцены на экране.
|
||||
*
|
||||
* <p> Направление камеры совпадает с её локальной осью Z, поэтому только что созданная камера смотрит вверх в системе
|
||||
* координат родителя.
|
||||
*
|
||||
* <p> Для отображения видимой через камеру части сцены на экран, к камере должна быть подключёна область вывода —
|
||||
* экземпляр класса <code>alternativa.engine3d.display.View</code>.
|
||||
*
|
||||
* @see alternativa.engine3d.display.View
|
||||
*/
|
||||
public class Camera3D extends Object3D {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт матрицы пространства камеры
|
||||
*/
|
||||
alternativa3d var calculateMatrixOperation:Operation = new Operation("calculateMatrix", this, calculateMatrix, Operation.CAMERA_CALCULATE_MATRIX);
|
||||
/**
|
||||
* @private
|
||||
* Расчёт плоскостей отсечения
|
||||
*/
|
||||
alternativa3d var calculatePlanesOperation:Operation = new Operation("calculatePlanes", this, calculatePlanes, Operation.CAMERA_CALCULATE_PLANES);
|
||||
/**
|
||||
* @private
|
||||
* Отрисовка
|
||||
*/
|
||||
alternativa3d var renderOperation:Operation = new Operation("render", this, render, Operation.CAMERA_RENDER);
|
||||
|
||||
// Инкремент количества объектов
|
||||
private static var counter:uint = 0;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Поле зрения
|
||||
*/
|
||||
alternativa3d var _fov:Number = Math.PI/2;
|
||||
/**
|
||||
* @private
|
||||
* Фокусное расстояние
|
||||
*/
|
||||
alternativa3d var focalLength:Number;
|
||||
/**
|
||||
* @private
|
||||
* Перспективное искажение
|
||||
*/
|
||||
alternativa3d var focalDistortion:Number;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Флаги рассчитанности UV-матриц
|
||||
*/
|
||||
alternativa3d var uvMatricesCalculated:Set = new Set(true);
|
||||
|
||||
// Всмомогательные точки для расчёта UV-матриц
|
||||
private var textureA:Point3D = new Point3D();
|
||||
private var textureB:Point3D = new Point3D();
|
||||
private var textureC:Point3D = new Point3D();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Вид из камеры
|
||||
*/
|
||||
alternativa3d var _view:View;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Режим отрисовки
|
||||
*/
|
||||
alternativa3d var _orthographic:Boolean = false;
|
||||
private var fullDraw:Boolean;
|
||||
|
||||
// Масштаб
|
||||
private var _zoom:Number = 1;
|
||||
|
||||
// Синус половинчатого угла обзора камеры
|
||||
private var viewAngle:Number;
|
||||
|
||||
// Направление камеры
|
||||
private var direction:Point3D = new Point3D(0, 0, 1);
|
||||
|
||||
// Обратная трансформация камеры
|
||||
private var cameraMatrix:Matrix3D = new Matrix3D();
|
||||
|
||||
// Скины
|
||||
private var firstSkin:Skin;
|
||||
private var prevSkin:Skin;
|
||||
private var currentSkin:Skin;
|
||||
|
||||
// Плоскости отсечения
|
||||
private var leftPlane:Point3D = new Point3D();
|
||||
private var rightPlane:Point3D = new Point3D();
|
||||
private var topPlane:Point3D = new Point3D();
|
||||
private var bottomPlane:Point3D = new Point3D();
|
||||
private var leftOffset:Number;
|
||||
private var rightOffset:Number;
|
||||
private var topOffset:Number;
|
||||
private var bottomOffset:Number;
|
||||
|
||||
// Вспомогательные массивы точек для отрисовки
|
||||
private var points1:Array = new Array();
|
||||
private var points2:Array = new Array();
|
||||
|
||||
/**
|
||||
* Создание нового экземпляра камеры.
|
||||
*
|
||||
* @param name имя экземпляра
|
||||
*/
|
||||
public function Camera3D(name:String = null) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private function calculateMatrix():void {
|
||||
// Расчёт матрицы пространства камеры
|
||||
cameraMatrix.copy(transformation);
|
||||
cameraMatrix.invert();
|
||||
if (_orthographic) {
|
||||
cameraMatrix.scale(_zoom, _zoom, _zoom);
|
||||
}
|
||||
// Направление камеры
|
||||
direction.x = transformation.c;
|
||||
direction.y = transformation.g;
|
||||
direction.z = transformation.k;
|
||||
direction.normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт плоскостей отсечения
|
||||
*/
|
||||
private function calculatePlanes():void {
|
||||
var halfWidth:Number = _view._width*0.5;
|
||||
var halfHeight:Number = _view._height*0.5;
|
||||
|
||||
var aw:Number = transformation.a*halfWidth;
|
||||
var ew:Number = transformation.e*halfWidth;
|
||||
var iw:Number = transformation.i*halfWidth;
|
||||
var bh:Number = transformation.b*halfHeight;
|
||||
var fh:Number = transformation.f*halfHeight;
|
||||
var jh:Number = transformation.j*halfHeight;
|
||||
if (_orthographic) {
|
||||
// Расчёт плоскостей отсечения в изометрии
|
||||
aw /= _zoom;
|
||||
ew /= _zoom;
|
||||
iw /= _zoom;
|
||||
bh /= _zoom;
|
||||
fh /= _zoom;
|
||||
jh /= _zoom;
|
||||
|
||||
// Левая плоскость
|
||||
leftPlane.x = transformation.f*transformation.k - transformation.j*transformation.g;
|
||||
leftPlane.y = transformation.j*transformation.c - transformation.b*transformation.k;
|
||||
leftPlane.z = transformation.b*transformation.g - transformation.f*transformation.c;
|
||||
leftOffset = (transformation.d - aw)*leftPlane.x + (transformation.h - ew)*leftPlane.y + (transformation.l - iw)*leftPlane.z;
|
||||
|
||||
// Правая плоскость
|
||||
rightPlane.x = -leftPlane.x;
|
||||
rightPlane.y = -leftPlane.y;
|
||||
rightPlane.z = -leftPlane.z;
|
||||
rightOffset = (transformation.d + aw)*rightPlane.x + (transformation.h + ew)*rightPlane.y + (transformation.l + iw)*rightPlane.z;
|
||||
|
||||
// Верхняя плоскость
|
||||
topPlane.x = transformation.g*transformation.i - transformation.k*transformation.e;
|
||||
topPlane.y = transformation.k*transformation.a - transformation.c*transformation.i;
|
||||
topPlane.z = transformation.c*transformation.e - transformation.g*transformation.a;
|
||||
topOffset = (transformation.d - bh)*topPlane.x + (transformation.h - fh)*topPlane.y + (transformation.l - jh)*topPlane.z;
|
||||
|
||||
// Нижняя плоскость
|
||||
bottomPlane.x = -topPlane.x;
|
||||
bottomPlane.y = -topPlane.y;
|
||||
bottomPlane.z = -topPlane.z;
|
||||
bottomOffset = (transformation.d + bh)*bottomPlane.x + (transformation.h + fh)*bottomPlane.y + (transformation.l + jh)*bottomPlane.z;
|
||||
} else {
|
||||
// Вычисляем расстояние фокуса
|
||||
focalLength = Math.sqrt(_view._width*_view._width + _view._height*_view._height)*0.5/Math.tan(0.5*_fov);
|
||||
// Вычисляем минимальное (однопиксельное) искажение перспективной коррекции
|
||||
focalDistortion = 1/(focalLength*focalLength);
|
||||
|
||||
// Расчёт плоскостей отсечения в перспективе
|
||||
var cl:Number = transformation.c*focalLength;
|
||||
var gl:Number = transformation.g*focalLength;
|
||||
var kl:Number = transformation.k*focalLength;
|
||||
|
||||
// Угловые вектора пирамиды видимости
|
||||
var leftTopX:Number = -aw - bh + cl;
|
||||
var leftTopY:Number = -ew - fh + gl;
|
||||
var leftTopZ:Number = -iw - jh + kl;
|
||||
var rightTopX:Number = aw - bh + cl;
|
||||
var rightTopY:Number = ew - fh + gl;
|
||||
var rightTopZ:Number = iw - jh + kl;
|
||||
var leftBottomX:Number = -aw + bh + cl;
|
||||
var leftBottomY:Number = -ew + fh + gl;
|
||||
var leftBottomZ:Number = -iw + jh + kl;
|
||||
var rightBottomX:Number = aw + bh + cl;
|
||||
var rightBottomY:Number = ew + fh + gl;
|
||||
var rightBottomZ:Number = iw + jh + kl;
|
||||
|
||||
// Левая плоскость
|
||||
leftPlane.x = leftBottomY*leftTopZ - leftBottomZ*leftTopY;
|
||||
leftPlane.y = leftBottomZ*leftTopX - leftBottomX*leftTopZ;
|
||||
leftPlane.z = leftBottomX*leftTopY - leftBottomY*leftTopX;
|
||||
leftOffset = transformation.d*leftPlane.x + transformation.h*leftPlane.y + transformation.l*leftPlane.z;
|
||||
|
||||
// Правая плоскость
|
||||
rightPlane.x = rightTopY*rightBottomZ - rightTopZ*rightBottomY;
|
||||
rightPlane.y = rightTopZ*rightBottomX - rightTopX*rightBottomZ;
|
||||
rightPlane.z = rightTopX*rightBottomY - rightTopY*rightBottomX;
|
||||
rightOffset = transformation.d*rightPlane.x + transformation.h*rightPlane.y + transformation.l*rightPlane.z;
|
||||
|
||||
// Верхняя плоскость
|
||||
topPlane.x = leftTopY*rightTopZ - leftTopZ*rightTopY;
|
||||
topPlane.y = leftTopZ*rightTopX - leftTopX*rightTopZ;
|
||||
topPlane.z = leftTopX*rightTopY - leftTopY*rightTopX;
|
||||
topOffset = transformation.d*topPlane.x + transformation.h*topPlane.y + transformation.l*topPlane.z;
|
||||
|
||||
// Нижняя плоскость
|
||||
bottomPlane.x = rightBottomY*leftBottomZ - rightBottomZ*leftBottomY;
|
||||
bottomPlane.y = rightBottomZ*leftBottomX - rightBottomX*leftBottomZ;
|
||||
bottomPlane.z = rightBottomX*leftBottomY - rightBottomY*leftBottomX;
|
||||
bottomOffset = transformation.d*bottomPlane.x + transformation.h*bottomPlane.y + transformation.l*bottomPlane.z;
|
||||
|
||||
|
||||
// Расчёт угла конуса
|
||||
var length:Number = Math.sqrt(leftTopX*leftTopX + leftTopY*leftTopY + leftTopZ*leftTopZ);
|
||||
leftTopX /= length;
|
||||
leftTopY /= length;
|
||||
leftTopZ /= length;
|
||||
length = Math.sqrt(rightTopX*rightTopX + rightTopY*rightTopY + rightTopZ*rightTopZ);
|
||||
rightTopX /= length;
|
||||
rightTopY /= length;
|
||||
rightTopZ /= length;
|
||||
length = Math.sqrt(leftBottomX*leftBottomX + leftBottomY*leftBottomY + leftBottomZ*leftBottomZ);
|
||||
leftBottomX /= length;
|
||||
leftBottomY /= length;
|
||||
leftBottomZ /= length;
|
||||
length = Math.sqrt(rightBottomX*rightBottomX + rightBottomY*rightBottomY + rightBottomZ*rightBottomZ);
|
||||
rightBottomX /= length;
|
||||
rightBottomY /= length;
|
||||
rightBottomZ /= length;
|
||||
|
||||
viewAngle = leftTopX*direction.x + leftTopY*direction.y + leftTopZ*direction.z;
|
||||
var dot:Number = rightTopX*direction.x + rightTopY*direction.y + rightTopZ*direction.z;
|
||||
viewAngle = (dot < viewAngle) ? dot : viewAngle;
|
||||
dot = leftBottomX*direction.x + leftBottomY*direction.y + leftBottomZ*direction.z;
|
||||
viewAngle = (dot < viewAngle) ? dot : viewAngle;
|
||||
dot = rightBottomX*direction.x + rightBottomY*direction.y + rightBottomZ*direction.z;
|
||||
viewAngle = (dot < viewAngle) ? dot : viewAngle;
|
||||
|
||||
viewAngle = Math.sin(Math.acos(viewAngle));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private function render():void {
|
||||
// Режим отрисовки
|
||||
fullDraw = (calculateMatrixOperation.queued || calculatePlanesOperation.queued);
|
||||
|
||||
// Очистка рассчитанных текстурных матриц
|
||||
uvMatricesCalculated.clear();
|
||||
|
||||
// Отрисовка
|
||||
prevSkin = null;
|
||||
currentSkin = firstSkin;
|
||||
renderBSPNode(_scene.bsp);
|
||||
|
||||
// Удаление ненужных скинов
|
||||
while (currentSkin != null) {
|
||||
removeCurrentSkin();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private function renderBSPNode(node:BSPNode):void {
|
||||
if (node != null) {
|
||||
var primitive:*;
|
||||
var normal:Point3D = node.normal;
|
||||
var cameraAngle:Number = direction.x*normal.x + direction.y*normal.y + direction.z*normal.z;
|
||||
var cameraOffset:Number;
|
||||
if (!_orthographic) {
|
||||
cameraOffset = globalCoords.x*normal.x + globalCoords.y*normal.y + globalCoords.z*normal.z - node.offset;
|
||||
}
|
||||
if (node.primitive != null) {
|
||||
// В ноде только базовый примитив
|
||||
if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) {
|
||||
// Камера спереди ноды
|
||||
if (_orthographic || cameraAngle < viewAngle) {
|
||||
renderBSPNode(node.back);
|
||||
drawSkin(node.primitive);
|
||||
}
|
||||
renderBSPNode(node.front);
|
||||
} else {
|
||||
// Камера сзади ноды
|
||||
if (_orthographic || cameraAngle > -viewAngle) {
|
||||
renderBSPNode(node.front);
|
||||
}
|
||||
renderBSPNode(node.back);
|
||||
}
|
||||
} else {
|
||||
// В ноде несколько примитивов
|
||||
if (_orthographic ? (cameraAngle < 0) : (cameraOffset > 0)) {
|
||||
// Камера спереди ноды
|
||||
if (_orthographic || cameraAngle < viewAngle) {
|
||||
renderBSPNode(node.back);
|
||||
for (primitive in node.frontPrimitives) {
|
||||
drawSkin(primitive);
|
||||
}
|
||||
}
|
||||
renderBSPNode(node.front);
|
||||
} else {
|
||||
// Камера сзади ноды
|
||||
if (_orthographic || cameraAngle > -viewAngle) {
|
||||
renderBSPNode(node.front);
|
||||
for (primitive in node.backPrimitives) {
|
||||
drawSkin(primitive);
|
||||
}
|
||||
}
|
||||
renderBSPNode(node.back);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Отрисовка скина примитива
|
||||
*/
|
||||
private function drawSkin(primitive:PolyPrimitive):void {
|
||||
if (!fullDraw && currentSkin != null && currentSkin.primitive == primitive && !_scene.changedPrimitives[primitive]) {
|
||||
// Пропуск скина
|
||||
prevSkin = currentSkin;
|
||||
currentSkin = currentSkin.nextSkin;
|
||||
} else {
|
||||
// Проверка поверхности
|
||||
var surface:Surface = primitive.face._surface;
|
||||
if (surface == null) {
|
||||
return;
|
||||
}
|
||||
// Проверка материала
|
||||
var material:SurfaceMaterial = surface._material;
|
||||
if (material == null || !material.canDraw(primitive)) {
|
||||
return;
|
||||
}
|
||||
// Отсечение выходящих за окно просмотра частей
|
||||
var i:uint;
|
||||
var length:uint = primitive.num;
|
||||
var primitivePoint:Point3D;
|
||||
var primitiveUV:Point;
|
||||
var point:DrawPoint;
|
||||
var useUV:Boolean = !_orthographic && material.useUV && primitive.face.uvMatrixBase;
|
||||
if (useUV) {
|
||||
// Формируем список точек и UV-координат полигона
|
||||
for (i = 0; i < length; i++) {
|
||||
primitivePoint = primitive.points[i];
|
||||
primitiveUV = primitive.uvs[i];
|
||||
point = points1[i];
|
||||
if (point == null) {
|
||||
points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z, primitiveUV.x, primitiveUV.y);
|
||||
} else {
|
||||
point.x = primitivePoint.x;
|
||||
point.y = primitivePoint.y;
|
||||
point.z = primitivePoint.z;
|
||||
point.u = primitiveUV.x;
|
||||
point.v = primitiveUV.y;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Формируем список точек полигона
|
||||
for (i = 0; i < length; i++) {
|
||||
primitivePoint = primitive.points[i];
|
||||
point = points1[i];
|
||||
if (point == null) {
|
||||
points1[i] = new DrawPoint(primitivePoint.x, primitivePoint.y, primitivePoint.z);
|
||||
} else {
|
||||
point.x = primitivePoint.x;
|
||||
point.y = primitivePoint.y;
|
||||
point.z = primitivePoint.z;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Отсечение по левой стороне
|
||||
length = clip(length, points1, points2, leftPlane, leftOffset, useUV);
|
||||
if (length < 3) {
|
||||
return;
|
||||
}
|
||||
// Отсечение по правой стороне
|
||||
length = clip(length, points2, points1, rightPlane, rightOffset, useUV);
|
||||
if (length < 3) {
|
||||
return;
|
||||
}
|
||||
// Отсечение по верхней стороне
|
||||
length = clip(length, points1, points2, topPlane, topOffset, useUV);
|
||||
if (length < 3) {
|
||||
return;
|
||||
}
|
||||
// Отсечение по нижней стороне
|
||||
length = clip(length, points2, points1, bottomPlane, bottomOffset, useUV);
|
||||
if (length < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fullDraw || _scene.changedPrimitives[primitive]) {
|
||||
|
||||
// Если конец списка скинов
|
||||
if (currentSkin == null) {
|
||||
// Добавляем скин в конец
|
||||
addCurrentSkin();
|
||||
} else {
|
||||
if (fullDraw || _scene.changedPrimitives[currentSkin.primitive]) {
|
||||
// Очистка скина
|
||||
currentSkin.material.clear(currentSkin);
|
||||
} else {
|
||||
// Вставка скина перед текущим
|
||||
insertCurrentSkin();
|
||||
}
|
||||
}
|
||||
|
||||
// Переводим координаты в систему камеры
|
||||
var x:Number;
|
||||
var y:Number;
|
||||
var z:Number;
|
||||
for (i = 0; i < length; i++) {
|
||||
point = points1[i];
|
||||
x = point.x;
|
||||
y = point.y;
|
||||
z = point.z;
|
||||
point.x = cameraMatrix.a*x + cameraMatrix.b*y + cameraMatrix.c*z + cameraMatrix.d;
|
||||
point.y = cameraMatrix.e*x + cameraMatrix.f*y + cameraMatrix.g*z + cameraMatrix.h;
|
||||
point.z = cameraMatrix.i*x + cameraMatrix.j*y + cameraMatrix.k*z + cameraMatrix.l;
|
||||
}
|
||||
|
||||
// Назначаем скину примитив и материал
|
||||
currentSkin.primitive = primitive;
|
||||
currentSkin.material = material;
|
||||
material.draw(this, currentSkin, length, points1);
|
||||
|
||||
// Переключаемся на следующий скин
|
||||
prevSkin = currentSkin;
|
||||
currentSkin = currentSkin.nextSkin;
|
||||
|
||||
} else {
|
||||
|
||||
// Удаление ненужных скинов
|
||||
while (currentSkin != null && _scene.changedPrimitives[currentSkin.primitive]) {
|
||||
removeCurrentSkin();
|
||||
}
|
||||
|
||||
// Переключение на следующий скин
|
||||
if (currentSkin != null) {
|
||||
prevSkin = currentSkin;
|
||||
currentSkin = currentSkin.nextSkin;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Отсечение полигона плоскостью.
|
||||
*/
|
||||
private function clip(length:uint, points1:Array, points2:Array, plane:Point3D, offset:Number, calculateUV:Boolean):uint {
|
||||
var i:uint;
|
||||
var k:Number;
|
||||
var index:uint = 0;
|
||||
var point:DrawPoint;
|
||||
var point1:DrawPoint;
|
||||
var point2:DrawPoint;
|
||||
var offset1:Number;
|
||||
var offset2:Number;
|
||||
|
||||
point1 = points1[length - 1];
|
||||
offset1 = plane.x*point1.x + plane.y*point1.y + plane.z*point1.z - offset;
|
||||
|
||||
if (calculateUV) {
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
|
||||
point2 = points1[i];
|
||||
offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset;
|
||||
|
||||
if (offset2 > 0) {
|
||||
if (offset1 <= 0) {
|
||||
k = offset2/(offset2 - offset1);
|
||||
point = points2[index];
|
||||
if (point == null) {
|
||||
point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k);
|
||||
points2[index] = point;
|
||||
} else {
|
||||
point.x = point2.x - (point2.x - point1.x)*k;
|
||||
point.y = point2.y - (point2.y - point1.y)*k;
|
||||
point.z = point2.z - (point2.z - point1.z)*k;
|
||||
point.u = point2.u - (point2.u - point1.u)*k;
|
||||
point.v = point2.v - (point2.v - point1.v)*k;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
point = points2[index];
|
||||
if (point == null) {
|
||||
point = new DrawPoint(point2.x, point2.y, point2.z, point2.u, point2.v);
|
||||
points2[index] = point;
|
||||
} else {
|
||||
point.x = point2.x;
|
||||
point.y = point2.y;
|
||||
point.z = point2.z;
|
||||
point.u = point2.u;
|
||||
point.v = point2.v;
|
||||
}
|
||||
index++;
|
||||
} else {
|
||||
if (offset1 > 0) {
|
||||
k = offset2/(offset2 - offset1);
|
||||
point = points2[index];
|
||||
if (point == null) {
|
||||
point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k, point2.u - (point2.u - point1.u)*k, point2.v - (point2.v - point1.v)*k);
|
||||
points2[index] = point;
|
||||
} else {
|
||||
point.x = point2.x - (point2.x - point1.x)*k;
|
||||
point.y = point2.y - (point2.y - point1.y)*k;
|
||||
point.z = point2.z - (point2.z - point1.z)*k;
|
||||
point.u = point2.u - (point2.u - point1.u)*k;
|
||||
point.v = point2.v - (point2.v - point1.v)*k;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
offset1 = offset2;
|
||||
point1 = point2;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
|
||||
point2 = points1[i];
|
||||
offset2 = plane.x*point2.x + plane.y*point2.y + plane.z*point2.z - offset;
|
||||
|
||||
if (offset2 > 0) {
|
||||
if (offset1 <= 0) {
|
||||
k = offset2/(offset2 - offset1);
|
||||
point = points2[index];
|
||||
if (point == null) {
|
||||
point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k);
|
||||
points2[index] = point;
|
||||
} else {
|
||||
point.x = point2.x - (point2.x - point1.x)*k;
|
||||
point.y = point2.y - (point2.y - point1.y)*k;
|
||||
point.z = point2.z - (point2.z - point1.z)*k;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
point = points2[index];
|
||||
if (point == null) {
|
||||
point = new DrawPoint(point2.x, point2.y, point2.z);
|
||||
points2[index] = point;
|
||||
} else {
|
||||
point.x = point2.x;
|
||||
point.y = point2.y;
|
||||
point.z = point2.z;
|
||||
}
|
||||
index++;
|
||||
} else {
|
||||
if (offset1 > 0) {
|
||||
k = offset2/(offset2 - offset1);
|
||||
point = points2[index];
|
||||
if (point == null) {
|
||||
point = new DrawPoint(point2.x - (point2.x - point1.x)*k, point2.y - (point2.y - point1.y)*k, point2.z - (point2.z - point1.z)*k);
|
||||
points2[index] = point;
|
||||
} else {
|
||||
point.x = point2.x - (point2.x - point1.x)*k;
|
||||
point.y = point2.y - (point2.y - point1.y)*k;
|
||||
point.z = point2.z - (point2.z - point1.z)*k;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
offset1 = offset2;
|
||||
point1 = point2;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Добавление текущего скина.
|
||||
*/
|
||||
private function addCurrentSkin():void {
|
||||
currentSkin = Skin.createSkin();
|
||||
_view.canvas.addChild(currentSkin);
|
||||
if (prevSkin == null) {
|
||||
firstSkin = currentSkin;
|
||||
} else {
|
||||
prevSkin.nextSkin = currentSkin;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Вставляем под текущий скин.
|
||||
*/
|
||||
private function insertCurrentSkin():void {
|
||||
var skin:Skin = Skin.createSkin();
|
||||
_view.canvas.addChildAt(skin, _view.canvas.getChildIndex(currentSkin));
|
||||
skin.nextSkin = currentSkin;
|
||||
if (prevSkin == null) {
|
||||
firstSkin = skin;
|
||||
} else {
|
||||
prevSkin.nextSkin = skin;
|
||||
}
|
||||
currentSkin = skin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаляет текущий скин.
|
||||
*/
|
||||
private function removeCurrentSkin():void {
|
||||
// Сохраняем следующий
|
||||
var next:Skin = currentSkin.nextSkin;
|
||||
// Удаляем из канваса
|
||||
_view.canvas.removeChild(currentSkin);
|
||||
// Очистка скина
|
||||
if (currentSkin.material != null) {
|
||||
currentSkin.material.clear(currentSkin);
|
||||
}
|
||||
// Зачищаем ссылки
|
||||
currentSkin.nextSkin = null;
|
||||
currentSkin.primitive = null;
|
||||
currentSkin.material = null;
|
||||
// Удаляем
|
||||
Skin.destroySkin(currentSkin);
|
||||
// Следующий устанавливаем текущим
|
||||
currentSkin = next;
|
||||
// Устанавливаем связь от предыдущего скина
|
||||
if (prevSkin == null) {
|
||||
firstSkin = currentSkin;
|
||||
} else {
|
||||
prevSkin.nextSkin = currentSkin;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d function calculateUVMatrix(face:Face, width:uint, height:uint):void {
|
||||
|
||||
// Расчёт точек базового примитива в координатах камеры
|
||||
var point:Point3D = face.primitive.points[0];
|
||||
textureA.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
|
||||
textureA.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
|
||||
point = face.primitive.points[1];
|
||||
textureB.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
|
||||
textureB.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
|
||||
point = face.primitive.points[2];
|
||||
textureC.x = cameraMatrix.a*point.x + cameraMatrix.b*point.y + cameraMatrix.c*point.z;
|
||||
textureC.y = cameraMatrix.e*point.x + cameraMatrix.f*point.y + cameraMatrix.g*point.z;
|
||||
|
||||
// Находим AB и AC
|
||||
var abx:Number = textureB.x - textureA.x;
|
||||
var aby:Number = textureB.y - textureA.y;
|
||||
var acx:Number = textureC.x - textureA.x;
|
||||
var acy:Number = textureC.y - textureA.y;
|
||||
|
||||
// Расчёт текстурной матрицы
|
||||
var uvMatrixBase:Matrix = face.uvMatrixBase;
|
||||
var uvMatrix:Matrix = face.uvMatrix;
|
||||
uvMatrix.a = (uvMatrixBase.a*abx + uvMatrixBase.b*acx)/width;
|
||||
uvMatrix.b = (uvMatrixBase.a*aby + uvMatrixBase.b*acy)/width;
|
||||
uvMatrix.c = -(uvMatrixBase.c*abx + uvMatrixBase.d*acx)/height;
|
||||
uvMatrix.d = -(uvMatrixBase.c*aby + uvMatrixBase.d*acy)/height;
|
||||
uvMatrix.tx = (uvMatrixBase.tx + uvMatrixBase.c)*abx + (uvMatrixBase.ty + uvMatrixBase.d)*acx + textureA.x + cameraMatrix.d;
|
||||
uvMatrix.ty = (uvMatrixBase.tx + uvMatrixBase.c)*aby + (uvMatrixBase.ty + uvMatrixBase.d)*acy + textureA.y + cameraMatrix.h;
|
||||
|
||||
// Помечаем, как рассчитанную
|
||||
uvMatricesCalculated[face] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Поле вывода, в котором происходит отрисовка камеры.
|
||||
*/
|
||||
public function get view():View {
|
||||
return _view;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set view(value:View):void {
|
||||
if (value != _view) {
|
||||
if (_view != null) {
|
||||
_view.camera = null;
|
||||
}
|
||||
if (value != null) {
|
||||
value.camera = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Включение режима аксонометрической проекции.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
public function get orthographic():Boolean {
|
||||
return _orthographic;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set orthographic(value:Boolean):void {
|
||||
if (_orthographic != value) {
|
||||
// Отправляем сигнал об изменении типа камеры
|
||||
addOperationToScene(calculateMatrixOperation);
|
||||
// Сохраняем новое значение
|
||||
_orthographic = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Угол поля зрения в радианах в режиме перспективной проекции. При изменении FOV изменяется фокусное расстояние
|
||||
* камеры по формуле <code>f = d/tan(fov/2)</code>, где <code>d</code> является половиной диагонали поля вывода.
|
||||
* Угол зрения ограничен диапазоном 0-180 градусов.
|
||||
*/
|
||||
public function get fov():Number {
|
||||
return _fov;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set fov(value:Number):void {
|
||||
value = (value < 0) ? 0 : ((value > (Math.PI - 0.0001)) ? (Math.PI - 0.0001) : value);
|
||||
if (_fov != value) {
|
||||
// Если перспектива
|
||||
if (!_orthographic) {
|
||||
// Отправляем сигнал об изменении плоскостей отсечения
|
||||
addOperationToScene(calculatePlanesOperation);
|
||||
}
|
||||
// Сохраняем новое значение
|
||||
_fov = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Коэффициент увеличения изображения в режиме аксонометрической проекции.
|
||||
*/
|
||||
public function get zoom():Number {
|
||||
return _zoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set zoom(value:Number):void {
|
||||
value = (value < 0) ? 0 : value;
|
||||
if (_zoom != value) {
|
||||
// Если изометрия
|
||||
if (_orthographic) {
|
||||
// Отправляем сигнал об изменении zoom
|
||||
addOperationToScene(calculateMatrixOperation);
|
||||
}
|
||||
// Сохраняем новое значение
|
||||
_zoom = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override protected function addToScene(scene:Scene3D):void {
|
||||
super.addToScene(scene);
|
||||
if (_view != null) {
|
||||
// Отправляем операцию расчёта плоскостей отсечения
|
||||
scene.addOperation(calculatePlanesOperation);
|
||||
// Подписываемся на сигналы сцены
|
||||
scene.changePrimitivesOperation.addSequel(renderOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override protected function removeFromScene(scene:Scene3D):void {
|
||||
super.removeFromScene(scene);
|
||||
|
||||
// Удаляем все операции из очереди
|
||||
scene.removeOperation(calculateMatrixOperation);
|
||||
scene.removeOperation(calculatePlanesOperation);
|
||||
scene.removeOperation(renderOperation);
|
||||
|
||||
if (_view != null) {
|
||||
// Отписываемся от сигналов сцены
|
||||
scene.changePrimitivesOperation.removeSequel(renderOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d function addToView(view:View):void {
|
||||
// Сохраняем первый скин
|
||||
firstSkin = (view.canvas.numChildren > 0) ? Skin(view.canvas.getChildAt(0)) : null;
|
||||
|
||||
// Подписка на свои операции
|
||||
|
||||
// При изменении камеры пересчёт матрицы
|
||||
calculateTransformationOperation.addSequel(calculateMatrixOperation);
|
||||
// При изменении матрицы или FOV пересчёт плоскостей отсечения
|
||||
calculateMatrixOperation.addSequel(calculatePlanesOperation);
|
||||
// При изменении плоскостей перерисовка
|
||||
calculatePlanesOperation.addSequel(renderOperation);
|
||||
|
||||
if (_scene != null) {
|
||||
// Отправляем сигнал перерисовки
|
||||
_scene.addOperation(calculateMatrixOperation);
|
||||
// Подписываемся на сигналы сцены
|
||||
_scene.changePrimitivesOperation.addSequel(renderOperation);
|
||||
}
|
||||
|
||||
// Сохраняем вид
|
||||
_view = view;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d function removeFromView(view:View):void {
|
||||
// Сброс ссылки на первый скин
|
||||
firstSkin = null;
|
||||
|
||||
// Отписка от своих операций
|
||||
|
||||
// При изменении камеры пересчёт матрицы
|
||||
calculateTransformationOperation.removeSequel(calculateMatrixOperation);
|
||||
// При изменении матрицы или FOV пересчёт плоскостей отсечения
|
||||
calculateMatrixOperation.removeSequel(calculatePlanesOperation);
|
||||
// При изменении плоскостей перерисовка
|
||||
calculatePlanesOperation.removeSequel(renderOperation);
|
||||
|
||||
if (_scene != null) {
|
||||
// Удаляем все операции из очереди
|
||||
_scene.removeOperation(calculateMatrixOperation);
|
||||
_scene.removeOperation(calculatePlanesOperation);
|
||||
_scene.removeOperation(renderOperation);
|
||||
// Отписываемся от сигналов сцены
|
||||
_scene.changePrimitivesOperation.removeSequel(renderOperation);
|
||||
}
|
||||
// Удаляем ссылку на вид
|
||||
_view = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override protected function defaultName():String {
|
||||
return "camera" + ++counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected override function createEmptyObject():Object3D {
|
||||
return new Camera3D();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected override function clonePropertiesFrom(source:Object3D):void {
|
||||
super.clonePropertiesFrom(source);
|
||||
|
||||
var src:Camera3D = Camera3D(source);
|
||||
orthographic = src._orthographic;
|
||||
zoom = src._zoom;
|
||||
fov = src._fov;
|
||||
}
|
||||
}
|
||||
}
|
||||
921
Alternativa3D5/5.4/alternativa/engine3d/core/Face.as
Normal file
921
Alternativa3D5/5.4/alternativa/engine3d/core/Face.as
Normal file
@@ -0,0 +1,921 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.types.Set;
|
||||
|
||||
import flash.geom.Matrix;
|
||||
import flash.geom.Point;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Грань, образованная тремя или более вершинами. Грани являются составными частями полигональных объектов. Каждая грань
|
||||
* содержит информацию об объекте и поверхности, которым она принадлежит. Для обеспечения возможности наложения
|
||||
* текстуры на грань, первым трём её вершинам могут быть заданы UV-координаты, на основании которых расчитывается
|
||||
* матрица трансформации текстуры.
|
||||
*/
|
||||
final public class Face {
|
||||
// Операции
|
||||
/**
|
||||
* @private
|
||||
* Расчёт глобальной нормали плоскости грани.
|
||||
*/
|
||||
alternativa3d var calculateNormalOperation:Operation = new Operation("calculateNormal", this, calculateNormal, Operation.FACE_CALCULATE_NORMAL);
|
||||
/**
|
||||
* @private
|
||||
* Расчёт UV-координат (выполняется до трансформации, чтобы UV корректно разбились при построении BSP).
|
||||
*/
|
||||
alternativa3d var calculateUVOperation:Operation = new Operation("calculateUV", this, calculateUV, Operation.FACE_CALCULATE_UV);
|
||||
/**
|
||||
* @private
|
||||
* Обновление примитива в сцене.
|
||||
*/
|
||||
alternativa3d var updatePrimitiveOperation:Operation = new Operation("updatePrimitive", this, updatePrimitive, Operation.FACE_UPDATE_PRIMITIVE);
|
||||
/**
|
||||
* @private
|
||||
* Обновление материала.
|
||||
*/
|
||||
alternativa3d var updateMaterialOperation:Operation = new Operation("updateMaterial", this, updateMaterial, Operation.FACE_UPDATE_MATERIAL);
|
||||
/**
|
||||
* @private
|
||||
* Расчёт UV для фрагментов (выполняется после трансформации, если её не было).
|
||||
*/
|
||||
alternativa3d var calculateFragmentsUVOperation:Operation = new Operation("calculateFragmentsUV", this, calculateFragmentsUV, Operation.FACE_CALCULATE_FRAGMENTS_UV);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Меш
|
||||
*/
|
||||
alternativa3d var _mesh:Mesh;
|
||||
/**
|
||||
* @private
|
||||
* Поверхность
|
||||
*/
|
||||
alternativa3d var _surface:Surface;
|
||||
/**
|
||||
* @private
|
||||
* Вершины грани
|
||||
*/
|
||||
alternativa3d var _vertices:Array;
|
||||
/**
|
||||
* @private
|
||||
* Количество вершин
|
||||
*/
|
||||
alternativa3d var _verticesCount:uint;
|
||||
/**
|
||||
* @private
|
||||
* Примитив
|
||||
*/
|
||||
alternativa3d var primitive:PolyPrimitive;
|
||||
|
||||
// UV-координаты
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d var _aUV:Point;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d var _bUV:Point;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d var _cUV:Point;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Коэффициенты базовой UV-матрицы
|
||||
*/
|
||||
alternativa3d var uvMatrixBase:Matrix;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* UV Матрица перевода текстурных координат в изометрическую камеру.
|
||||
*/
|
||||
alternativa3d var uvMatrix:Matrix;
|
||||
/**
|
||||
* @private
|
||||
* Нормаль плоскости
|
||||
*/
|
||||
alternativa3d var globalNormal:Point3D = new Point3D();
|
||||
/**
|
||||
* @private
|
||||
* Смещение плоскости
|
||||
*/
|
||||
alternativa3d var globalOffset:Number;
|
||||
|
||||
/**
|
||||
* Создание экземпляра грани.
|
||||
*
|
||||
* @param vertices массив объектов типа <code>alternativa.engine3d.core.Vertex</code>, задающий вершины грани в
|
||||
* порядке обхода лицевой стороны грани против часовой стрелки.
|
||||
*
|
||||
* @see Vertex
|
||||
*/
|
||||
public function Face(vertices:Array) {
|
||||
// Сохраняем вершины
|
||||
_vertices = vertices;
|
||||
_verticesCount = vertices.length;
|
||||
|
||||
// Создаём оригинальный примитив
|
||||
primitive = PolyPrimitive.createPolyPrimitive();
|
||||
primitive.face = this;
|
||||
primitive.num = _verticesCount;
|
||||
|
||||
// Обрабатываем вершины
|
||||
for (var i:uint = 0; i < _verticesCount; i++) {
|
||||
var vertex:Vertex = vertices[i];
|
||||
// Добавляем координаты вершины в примитив
|
||||
primitive.points.push(vertex.globalCoords);
|
||||
// Добавляем пустые UV-координаты в примитив
|
||||
primitive.uvs.push(null);
|
||||
// Добавляем вершину в грань
|
||||
vertex.addToFace(this);
|
||||
}
|
||||
|
||||
// Расчёт нормали
|
||||
calculateNormalOperation.addSequel(updatePrimitiveOperation);
|
||||
|
||||
// Расчёт UV грани инициирует расчёт UV фрагментов и перерисовку
|
||||
calculateUVOperation.addSequel(calculateFragmentsUVOperation);
|
||||
calculateUVOperation.addSequel(updateMaterialOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт нормали в глобальных координатах
|
||||
*/
|
||||
private function calculateNormal():void {
|
||||
// Вектор AB
|
||||
var vertex:Vertex = _vertices[0];
|
||||
var av:Point3D = vertex.globalCoords;
|
||||
vertex = _vertices[1];
|
||||
var bv:Point3D = vertex.globalCoords;
|
||||
var abx:Number = bv.x - av.x;
|
||||
var aby:Number = bv.y - av.y;
|
||||
var abz:Number = bv.z - av.z;
|
||||
// Вектор AC
|
||||
vertex = _vertices[2];
|
||||
var cv:Point3D = vertex.globalCoords;
|
||||
var acx:Number = cv.x - av.x;
|
||||
var acy:Number = cv.y - av.y;
|
||||
var acz:Number = cv.z - av.z;
|
||||
// Перпендикуляр к плоскости
|
||||
globalNormal.x = acz*aby - acy*abz;
|
||||
globalNormal.y = acx*abz - acz*abx;
|
||||
globalNormal.z = acy*abx - acx*aby;
|
||||
// Нормализация перпендикуляра
|
||||
globalNormal.normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчитывает глобальное смещение плоскости грани.
|
||||
* Помечает конечные примитивы на удаление, а базовый на добавление в сцене.
|
||||
*/
|
||||
private function updatePrimitive():void {
|
||||
// Расчёт смещения
|
||||
var vertex:Vertex = _vertices[0];
|
||||
globalOffset = vertex.globalCoords.x*globalNormal.x + vertex.globalCoords.y*globalNormal.y + vertex.globalCoords.z*globalNormal.z;
|
||||
|
||||
removePrimitive(primitive);
|
||||
primitive.mobility = _mesh.inheritedMobility;
|
||||
_mesh._scene.addPrimitives.push(primitive);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на удаление из сцены
|
||||
*/
|
||||
private function removePrimitive(primitive:PolyPrimitive):void {
|
||||
if (primitive.backFragment != null) {
|
||||
removePrimitive(primitive.backFragment);
|
||||
removePrimitive(primitive.frontFragment);
|
||||
primitive.backFragment = null;
|
||||
primitive.frontFragment = null;
|
||||
if (primitive != this.primitive) {
|
||||
primitive.parent = null;
|
||||
primitive.sibling = null;
|
||||
PolyPrimitive.destroyPolyPrimitive(primitive);
|
||||
}
|
||||
} else {
|
||||
// Если примитив в BSP-дереве
|
||||
if (primitive.node != null) {
|
||||
// Удаление примитива
|
||||
_mesh._scene.removeBSPPrimitive(primitive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Пометка на перерисовку фрагментов грани.
|
||||
*/
|
||||
private function updateMaterial():void {
|
||||
if (!updatePrimitiveOperation.queued) {
|
||||
changePrimitive(primitive);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Рекурсивно проходит по фрагментам примитива и отправляет конечные фрагменты на перерисовку
|
||||
*/
|
||||
private function changePrimitive(primitive:PolyPrimitive):void {
|
||||
if (primitive.backFragment != null) {
|
||||
changePrimitive(primitive.backFragment);
|
||||
changePrimitive(primitive.frontFragment);
|
||||
} else {
|
||||
_mesh._scene.changedPrimitives[primitive] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт UV-матрицы на основании первых трёх UV-координат.
|
||||
* Расчёт UV-координат для оставшихся точек.
|
||||
*/
|
||||
private function calculateUV():void {
|
||||
var i:uint;
|
||||
// Расчёт UV-матрицы
|
||||
if (_aUV != null && _bUV != null && _cUV != null) {
|
||||
var abu:Number = _bUV.x - _aUV.x;
|
||||
var abv:Number = _bUV.y - _aUV.y;
|
||||
var acu:Number = _cUV.x - _aUV.x;
|
||||
var acv:Number = _cUV.y - _aUV.y;
|
||||
var det:Number = abu*acv - abv*acu;
|
||||
if (det != 0) {
|
||||
if (uvMatrixBase == null) {
|
||||
uvMatrixBase = new Matrix();
|
||||
uvMatrix = new Matrix();
|
||||
}
|
||||
uvMatrixBase.a = acv/det;
|
||||
uvMatrixBase.b = -abv/det;
|
||||
uvMatrixBase.c = -acu/det;
|
||||
uvMatrixBase.d = abu/det;
|
||||
uvMatrixBase.tx = -(uvMatrixBase.a*_aUV.x + uvMatrixBase.c*_aUV.y);
|
||||
uvMatrixBase.ty = -(uvMatrixBase.b*_aUV.x + uvMatrixBase.d*_aUV.y);
|
||||
|
||||
// Заполняем UV в базовом примитиве
|
||||
primitive.uvs[0] = _aUV;
|
||||
primitive.uvs[1] = _bUV;
|
||||
primitive.uvs[2] = _cUV;
|
||||
|
||||
// Расчёт недостающих UV
|
||||
if (_verticesCount > 3) {
|
||||
var a:Point3D = primitive.points[0];
|
||||
var b:Point3D = primitive.points[1];
|
||||
var c:Point3D = primitive.points[2];
|
||||
|
||||
var ab1:Number;
|
||||
var ab2:Number;
|
||||
var ac1:Number;
|
||||
var ac2:Number;
|
||||
var ad1:Number;
|
||||
var ad2:Number;
|
||||
var abk:Number;
|
||||
var ack:Number;
|
||||
|
||||
var uv:Point;
|
||||
var point:Point3D;
|
||||
|
||||
// Выбор наиболее подходящих осей для расчёта
|
||||
if (((globalNormal.x < 0) ? -globalNormal.x : globalNormal.x) > ((globalNormal.y < 0) ? -globalNormal.y : globalNormal.y)) {
|
||||
if (((globalNormal.x < 0) ? -globalNormal.x : globalNormal.x) > ((globalNormal.z < 0) ? -globalNormal.z : globalNormal.z)) {
|
||||
// Ось X
|
||||
ab1 = b.y - a.y;
|
||||
ab2 = b.z - a.z;
|
||||
ac1 = c.y - a.y;
|
||||
ac2 = c.z - a.z;
|
||||
det = ab1*ac2 - ac1*ab2;
|
||||
for (i = 3; i < _verticesCount; i++) {
|
||||
point = primitive.points[i];
|
||||
ad1 = point.y - a.y;
|
||||
ad2 = point.z - a.z;
|
||||
abk = (ad1*ac2 - ac1*ad2)/det;
|
||||
ack = (ab1*ad2 - ad1*ab2)/det;
|
||||
uv = primitive.uvs[i];
|
||||
if (uv == null) {
|
||||
uv = new Point();
|
||||
primitive.uvs[i] = uv;
|
||||
}
|
||||
uv.x = _aUV.x + abu*abk + acu*ack;
|
||||
uv.y = _aUV.y + abv*abk + acv*ack;
|
||||
}
|
||||
} else {
|
||||
// Ось Z
|
||||
ab1 = b.x - a.x;
|
||||
ab2 = b.y - a.y;
|
||||
ac1 = c.x - a.x;
|
||||
ac2 = c.y - a.y;
|
||||
det = ab1*ac2 - ac1*ab2;
|
||||
for (i = 3; i < _verticesCount; i++) {
|
||||
point = primitive.points[i];
|
||||
ad1 = point.x - a.x;
|
||||
ad2 = point.y - a.y;
|
||||
abk = (ad1*ac2 - ac1*ad2)/det;
|
||||
ack = (ab1*ad2 - ad1*ab2)/det;
|
||||
uv = primitive.uvs[i];
|
||||
if (uv == null) {
|
||||
uv = new Point();
|
||||
primitive.uvs[i] = uv;
|
||||
}
|
||||
uv.x = _aUV.x + abu*abk + acu*ack;
|
||||
uv.y = _aUV.y + abv*abk + acv*ack;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (((globalNormal.y < 0) ? -globalNormal.y : globalNormal.y) > ((globalNormal.z < 0) ? -globalNormal.z : globalNormal.z)) {
|
||||
// Ось Y
|
||||
ab1 = b.x - a.x;
|
||||
ab2 = b.z - a.z;
|
||||
ac1 = c.x - a.x;
|
||||
ac2 = c.z - a.z;
|
||||
det = ab1*ac2 - ac1*ab2;
|
||||
for (i = 3; i < _verticesCount; i++) {
|
||||
point = primitive.points[i];
|
||||
ad1 = point.x - a.x;
|
||||
ad2 = point.z - a.z;
|
||||
abk = (ad1*ac2 - ac1*ad2)/det;
|
||||
ack = (ab1*ad2 - ad1*ab2)/det;
|
||||
uv = primitive.uvs[i];
|
||||
if (uv == null) {
|
||||
uv = new Point();
|
||||
primitive.uvs[i] = uv;
|
||||
}
|
||||
uv.x = _aUV.x + abu*abk + acu*ack;
|
||||
uv.y = _aUV.y + abv*abk + acv*ack;
|
||||
}
|
||||
} else {
|
||||
// Ось Z
|
||||
ab1 = b.x - a.x;
|
||||
ab2 = b.y - a.y;
|
||||
ac1 = c.x - a.x;
|
||||
ac2 = c.y - a.y;
|
||||
det = ab1*ac2 - ac1*ab2;
|
||||
for (i = 3; i < _verticesCount; i++) {
|
||||
point = primitive.points[i];
|
||||
ad1 = point.x - a.x;
|
||||
ad2 = point.y - a.y;
|
||||
abk = (ad1*ac2 - ac1*ad2)/det;
|
||||
ack = (ab1*ad2 - ad1*ab2)/det;
|
||||
uv = primitive.uvs[i];
|
||||
if (uv == null) {
|
||||
uv = new Point();
|
||||
primitive.uvs[i] = uv;
|
||||
}
|
||||
uv.x = _aUV.x + abu*abk + acu*ack;
|
||||
uv.y = _aUV.y + abv*abk + acv*ack;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Удаляем UV-матрицу
|
||||
uvMatrixBase = null;
|
||||
uvMatrix = null;
|
||||
// Удаляем UV-координаты из базового примитива
|
||||
for (i = 0; i < _verticesCount; i++) {
|
||||
primitive.uvs[i] = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Удаляем UV-матрицу
|
||||
uvMatrixBase = null;
|
||||
uvMatrix = null;
|
||||
// Удаляем UV-координаты из базового примитива
|
||||
for (i = 0; i < _verticesCount; i++) {
|
||||
primitive.uvs[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт UV-координат для фрагментов примитива, если не было трансформации
|
||||
*/
|
||||
private function calculateFragmentsUV():void {
|
||||
// Если в этом цикле не было трансформации
|
||||
if (!updatePrimitiveOperation.queued) {
|
||||
if (uvMatrixBase != null) {
|
||||
// Рассчитываем UV в примитиве
|
||||
calculatePrimitiveUV(primitive);
|
||||
} else {
|
||||
// Удаляем UV в примитиве
|
||||
removePrimitiveUV(primitive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт UV для точек базового примитива.
|
||||
*
|
||||
* @param primitive
|
||||
*/
|
||||
private function calculatePrimitiveUV(primitive:PolyPrimitive):void {
|
||||
if (primitive.backFragment != null) {
|
||||
var points:Array = primitive.points;
|
||||
var backPoints:Array = primitive.backFragment.points;
|
||||
var frontPoints:Array = primitive.frontFragment.points;
|
||||
var uvs:Array = primitive.uvs;
|
||||
var backUVs:Array = primitive.backFragment.uvs;
|
||||
var frontUVs:Array = primitive.frontFragment.uvs;
|
||||
var index1:uint = 0;
|
||||
var index2:uint = 0;
|
||||
var point:Point3D;
|
||||
var uv:Point;
|
||||
var uv1:Point;
|
||||
var uv2:Point;
|
||||
var t:Number;
|
||||
var firstSplit:Boolean = true;
|
||||
for (var i:uint = 0; i < primitive.num; i++) {
|
||||
var split:Boolean = true;
|
||||
point = points[i];
|
||||
if (point == frontPoints[index2]) {
|
||||
if (frontUVs[index2] == null) {
|
||||
frontUVs[index2] = uvs[i];
|
||||
}
|
||||
split = false;
|
||||
index2++;
|
||||
}
|
||||
if (point == backPoints[index1]) {
|
||||
if (backUVs[index1] == null) {
|
||||
backUVs[index1] = uvs[i];
|
||||
}
|
||||
split = false;
|
||||
index1++;
|
||||
}
|
||||
|
||||
if (split) {
|
||||
uv1 = uvs[(i == 0) ? (primitive.num - 1) : (i - 1)];
|
||||
uv2 = uvs[i];
|
||||
t = (firstSplit) ? primitive.splitTime1 : primitive.splitTime2;
|
||||
uv = frontUVs[index2];
|
||||
if (uv == null) {
|
||||
uv = new Point(uv1.x + (uv2.x - uv1.x)*t, uv1.y + (uv2.y - uv1.y)*t);
|
||||
frontUVs[index2] = uv;
|
||||
backUVs[index1] = uv;
|
||||
} else {
|
||||
uv.x = uv1.x + (uv2.x - uv1.x)*t;
|
||||
uv.y = uv1.y + (uv2.y - uv1.y)*t;
|
||||
}
|
||||
firstSplit = false;
|
||||
index2++;
|
||||
index1++;
|
||||
if (point == frontPoints[index2]) {
|
||||
if (frontUVs[index2] == null) {
|
||||
frontUVs[index2] = uvs[i];
|
||||
}
|
||||
index2++;
|
||||
}
|
||||
if (point == backPoints[index1]) {
|
||||
if (backUVs[index1] == null) {
|
||||
backUVs[index1] = uvs[i];
|
||||
}
|
||||
index1++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Проверяем рассечение последнего ребра
|
||||
if (index2 < primitive.frontFragment.num) {
|
||||
uv1 = uvs[primitive.num - 1];
|
||||
uv2 = uvs[0];
|
||||
t = (firstSplit) ? primitive.splitTime1 : primitive.splitTime2;
|
||||
uv = frontUVs[index2];
|
||||
if (uv == null) {
|
||||
uv = new Point(uv1.x + (uv2.x - uv1.x)*t, uv1.y + (uv2.y - uv1.y)*t);
|
||||
frontUVs[index2] = uv;
|
||||
backUVs[index1] = uv;
|
||||
} else {
|
||||
uv.x = uv1.x + (uv2.x - uv1.x)*t;
|
||||
uv.y = uv1.y + (uv2.y - uv1.y)*t;
|
||||
}
|
||||
}
|
||||
|
||||
calculatePrimitiveUV(primitive.backFragment);
|
||||
calculatePrimitiveUV(primitive.frontFragment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление UV в примитиве и его фрагментах
|
||||
* @param primitive
|
||||
*/
|
||||
private function removePrimitiveUV(primitive:PolyPrimitive):void {
|
||||
// Очищаем список UV
|
||||
for (var i:uint = 0; i < primitive.num; i++) {
|
||||
primitive.uvs[i] = null;
|
||||
}
|
||||
// Если есть фрагменты, удаляем UV в них
|
||||
if (primitive.backFragment != null) {
|
||||
removePrimitiveUV(primitive.backFragment);
|
||||
removePrimitiveUV(primitive.frontFragment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Массив вершин грани, представленных объектами класса <code>alternativa.engine3d.core.Vertex</code>.
|
||||
*
|
||||
* @see Vertex
|
||||
*/
|
||||
public function get vertices():Array {
|
||||
return new Array().concat(_vertices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Количество вершин грани.
|
||||
*/
|
||||
public function get verticesCount():uint {
|
||||
return _verticesCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Полигональный объект, которому принадлежит грань.
|
||||
*/
|
||||
public function get mesh():Mesh {
|
||||
return _mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Поверхность, которой принадлежит грань.
|
||||
*/
|
||||
public function get surface():Surface {
|
||||
return _surface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Идентификатор грани в полигональном объекте. В случае, если грань не принадлежит ни одному объекту, идентификатор
|
||||
* имеет значение <code>null</code>.
|
||||
*/
|
||||
public function get id():Object {
|
||||
return (_mesh != null) ? _mesh.getFaceId(this) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* UV-координаты, соответствующие первой вершине грани.
|
||||
*/
|
||||
public function get aUV():Point {
|
||||
return (_aUV != null) ? _aUV.clone() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* UV-координаты, соответствующие второй вершине грани.
|
||||
*/
|
||||
public function get bUV():Point {
|
||||
return (_bUV != null) ? _bUV.clone() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* UV-координаты, соответствующие третьей вершине грани.
|
||||
*/
|
||||
public function get cUV():Point {
|
||||
return (_cUV != null) ? _cUV.clone() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set aUV(value:Point):void {
|
||||
if (_aUV != null) {
|
||||
if (value != null) {
|
||||
if (!_aUV.equals(value)) {
|
||||
_aUV.x = value.x;
|
||||
_aUV.y = value.y;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_aUV = null;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (value != null) {
|
||||
_aUV = value.clone();
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set bUV(value:Point):void {
|
||||
if (_bUV != null) {
|
||||
if (value != null) {
|
||||
if (!_bUV.equals(value)) {
|
||||
_bUV.x = value.x;
|
||||
_bUV.y = value.y;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_bUV = null;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (value != null) {
|
||||
_bUV = value.clone();
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set cUV(value:Point):void {
|
||||
if (_cUV != null) {
|
||||
if (value != null) {
|
||||
if (!_cUV.equals(value)) {
|
||||
_cUV.x = value.x;
|
||||
_cUV.y = value.y;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_cUV = null;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (value != null) {
|
||||
_cUV = value.clone();
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(calculateUVOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Нормаль в локальной системе координат.
|
||||
*/
|
||||
public function get normal():Point3D {
|
||||
var res:Point3D = new Point3D();
|
||||
var vertex:Vertex = _vertices[0];
|
||||
var av:Point3D = vertex.coords;
|
||||
vertex = _vertices[1];
|
||||
var bv:Point3D = vertex.coords;
|
||||
var abx:Number = bv.x - av.x;
|
||||
var aby:Number = bv.y - av.y;
|
||||
var abz:Number = bv.z - av.z;
|
||||
vertex = _vertices[2];
|
||||
var cv:Point3D = vertex.coords;
|
||||
var acx:Number = cv.x - av.x;
|
||||
var acy:Number = cv.y - av.y;
|
||||
var acz:Number = cv.z - av.z;
|
||||
res.x = acz*aby - acy*abz;
|
||||
res.y = acx*abz - acz*abx;
|
||||
res.z = acy*abx - acx*aby;
|
||||
if (res.x != 0 || res.y != 0 || res.z != 0) {
|
||||
var k:Number = Math.sqrt(res.x*res.x + res.y*res.y + res.z*res.z);
|
||||
res.x /= k;
|
||||
res.y /= k;
|
||||
res.z /= k;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Расчёт UV-координат для произвольной точки в системе координат объекта, которому принадлежит грань.
|
||||
*
|
||||
* @param point точка в плоскости грани, для которой производится расчёт UV-координат
|
||||
* @return UV-координаты заданной точки
|
||||
*/
|
||||
public function getUV(point:Point3D):Point {
|
||||
return getUVFast(point, normal);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт UV-координат для произвольной точки в локальной системе координат без расчёта
|
||||
* локальной нормали грани. Используется для оптимизации.
|
||||
*
|
||||
* @param point точка в плоскости грани, для которой производится расчёт UV-координат
|
||||
* @param normal нормаль плоскости грани в локальной системе координат
|
||||
* @return UV-координаты заданной точки
|
||||
*/
|
||||
alternativa3d function getUVFast(point:Point3D, normal:Point3D):Point {
|
||||
if (_aUV == null || _bUV == null || _cUV == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Выбор наиболее длинной оси нормали
|
||||
var dir:uint;
|
||||
if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.y < 0) ? -normal.y : normal.y)) {
|
||||
if (((normal.x < 0) ? -normal.x : normal.x) > ((normal.z < 0) ? -normal.z : normal.z)) {
|
||||
dir = 0;
|
||||
} else {
|
||||
dir = 2;
|
||||
}
|
||||
} else {
|
||||
if (((normal.y < 0) ? -normal.y : normal.y) > ((normal.z < 0) ? -normal.z : normal.z)) {
|
||||
dir = 1;
|
||||
} else {
|
||||
dir = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Расчёт соотношения по векторам AB и AC
|
||||
var v:Vertex = _vertices[0];
|
||||
var a:Point3D = v._coords;
|
||||
v = _vertices[1];
|
||||
var b:Point3D = v._coords;
|
||||
v = _vertices[2];
|
||||
var c:Point3D = v._coords;
|
||||
|
||||
var ab1:Number = (dir == 0) ? (b.y - a.y) : (b.x - a.x);
|
||||
var ab2:Number = (dir == 2) ? (b.y - a.y) : (b.z - a.z);
|
||||
var ac1:Number = (dir == 0) ? (c.y - a.y) : (c.x - a.x);
|
||||
var ac2:Number = (dir == 2) ? (c.y - a.y) : (c.z - a.z);
|
||||
var det:Number = ab1*ac2 - ac1*ab2;
|
||||
|
||||
var ad1:Number = (dir == 0) ? (point.y - a.y) : (point.x - a.x);
|
||||
var ad2:Number = (dir == 2) ? (point.y - a.y) : (point.z - a.z);
|
||||
var abk:Number = (ad1*ac2 - ac1*ad2)/det;
|
||||
var ack:Number = (ab1*ad2 - ad1*ab2)/det;
|
||||
|
||||
// Интерполяция по UV первых точек
|
||||
var abu:Number = _bUV.x - _aUV.x;
|
||||
var abv:Number = _bUV.y - _aUV.y;
|
||||
var acu:Number = _cUV.x - _aUV.x;
|
||||
var acv:Number = _cUV.y - _aUV.y;
|
||||
|
||||
return new Point(_aUV.x + abu*abk + acu*ack, _aUV.y + abv*abk + acv*ack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Множество граней, имеющих общие рёбра с текущей гранью.
|
||||
*/
|
||||
public function get edgeJoinedFaces():Set {
|
||||
var res:Set = new Set(true);
|
||||
// Перебираем точки грани
|
||||
for (var i:uint = 0; i < _verticesCount; i++) {
|
||||
var a:Vertex = _vertices[i];
|
||||
var b:Vertex = _vertices[(i < _verticesCount - 1) ? (i + 1) : 0];
|
||||
|
||||
// Перебираем грани текущей точки
|
||||
for (var key:* in a._faces) {
|
||||
var face:Face = key;
|
||||
// Если это другая грань и у неё также есть следующая точка
|
||||
if (face != this && face._vertices.indexOf(b) >= 0) {
|
||||
// Значит у граней общее ребро
|
||||
res[face] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление всех вершин из грани.
|
||||
* Очистка базового примитива.
|
||||
*/
|
||||
alternativa3d function removeVertices():void {
|
||||
// Удалить вершины
|
||||
for (var i:uint = 0; i < _verticesCount; i++) {
|
||||
// Удаляем из списка
|
||||
var vertex:Vertex = _vertices.pop();
|
||||
// Удаляем координаты вершины из примитива
|
||||
primitive.points.pop();
|
||||
// Удаляем вершину из грани
|
||||
vertex.removeFromFace(this);
|
||||
}
|
||||
// Обнуляем количество вершин
|
||||
_verticesCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Добавление грани на сцену
|
||||
* @param scene
|
||||
*/
|
||||
alternativa3d function addToScene(scene:Scene3D):void {
|
||||
// При добавлении на сцену рассчитываем плоскость и UV
|
||||
scene.addOperation(calculateNormalOperation);
|
||||
scene.addOperation(calculateUVOperation);
|
||||
|
||||
// Подписываем сцену на операции
|
||||
updatePrimitiveOperation.addSequel(scene.calculateBSPOperation);
|
||||
updateMaterialOperation.addSequel(scene.changePrimitivesOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление грани из сцены
|
||||
* @param scene
|
||||
*/
|
||||
alternativa3d function removeFromScene(scene:Scene3D):void {
|
||||
// Удаляем все операции из очереди
|
||||
scene.removeOperation(calculateUVOperation);
|
||||
scene.removeOperation(calculateFragmentsUVOperation);
|
||||
scene.removeOperation(calculateNormalOperation);
|
||||
scene.removeOperation(updatePrimitiveOperation);
|
||||
scene.removeOperation(updateMaterialOperation);
|
||||
|
||||
// Удаляем примитивы из сцены
|
||||
removePrimitive(primitive);
|
||||
|
||||
// Посылаем операцию сцены на расчёт BSP
|
||||
scene.addOperation(scene.calculateBSPOperation);
|
||||
|
||||
// Отписываем сцену от операций
|
||||
updatePrimitiveOperation.removeSequel(scene.calculateBSPOperation);
|
||||
updateMaterialOperation.removeSequel(scene.changePrimitivesOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Добавление грани в меш
|
||||
* @param mesh
|
||||
*/
|
||||
alternativa3d function addToMesh(mesh:Mesh):void {
|
||||
// Подписка на операции меша
|
||||
mesh.changeCoordsOperation.addSequel(updatePrimitiveOperation);
|
||||
mesh.changeRotationOrScaleOperation.addSequel(calculateNormalOperation);
|
||||
mesh.calculateMobilityOperation.addSequel(updatePrimitiveOperation);
|
||||
// Сохранить меш
|
||||
_mesh = mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление грани из меша
|
||||
* @param mesh
|
||||
*/
|
||||
alternativa3d function removeFromMesh(mesh:Mesh):void {
|
||||
// Отписка от операций меша
|
||||
mesh.changeCoordsOperation.removeSequel(updatePrimitiveOperation);
|
||||
mesh.changeRotationOrScaleOperation.removeSequel(calculateNormalOperation);
|
||||
mesh.calculateMobilityOperation.removeSequel(updatePrimitiveOperation);
|
||||
// Удалить ссылку на меш
|
||||
_mesh = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Добавление к поверхности
|
||||
*
|
||||
* @param surface
|
||||
*/
|
||||
alternativa3d function addToSurface(surface:Surface):void {
|
||||
// Подписка поверхности на операции
|
||||
surface.changeMaterialOperation.addSequel(updateMaterialOperation);
|
||||
// Если при смене поверхности изменился материал
|
||||
if (_mesh != null && (_surface != null && _surface._material != surface._material || _surface == null && surface._material != null)) {
|
||||
// Отправляем сигнал смены материала
|
||||
_mesh.addOperationToScene(updateMaterialOperation);
|
||||
}
|
||||
// Сохранить поверхность
|
||||
_surface = surface;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление из поверхности
|
||||
*
|
||||
* @param surface
|
||||
*/
|
||||
alternativa3d function removeFromSurface(surface:Surface):void {
|
||||
// Отписка поверхности от операций
|
||||
surface.changeMaterialOperation.removeSequel(updateMaterialOperation);
|
||||
// Если был материал
|
||||
if (surface._material != null) {
|
||||
// Отправляем сигнал смены материала
|
||||
_mesh.addOperationToScene(updateMaterialOperation);
|
||||
}
|
||||
// Удалить ссылку на поверхность
|
||||
_surface = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Строковое представление объекта.
|
||||
*
|
||||
* @return строковое представление объекта
|
||||
*/
|
||||
public function toString():String {
|
||||
var res:String = "[Face ID:" + id + ((_verticesCount > 0) ? " vertices:" : "");
|
||||
for (var i:uint = 0; i < _verticesCount; i++) {
|
||||
var vertex:Vertex = _vertices[i];
|
||||
res += vertex.id + ((i < _verticesCount - 1) ? ", " : "");
|
||||
}
|
||||
res += "]";
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
985
Alternativa3D5/5.4/alternativa/engine3d/core/Mesh.as
Normal file
985
Alternativa3D5/5.4/alternativa/engine3d/core/Mesh.as
Normal file
@@ -0,0 +1,985 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.errors.FaceExistsError;
|
||||
import alternativa.engine3d.errors.FaceNeedMoreVerticesError;
|
||||
import alternativa.engine3d.errors.FaceNotFoundError;
|
||||
import alternativa.engine3d.errors.InvalidIDError;
|
||||
import alternativa.engine3d.errors.SurfaceExistsError;
|
||||
import alternativa.engine3d.errors.SurfaceNotFoundError;
|
||||
import alternativa.engine3d.errors.VertexExistsError;
|
||||
import alternativa.engine3d.errors.VertexNotFoundError;
|
||||
import alternativa.engine3d.materials.SurfaceMaterial;
|
||||
import alternativa.types.Map;
|
||||
import alternativa.utils.ObjectUtils;
|
||||
|
||||
import flash.geom.Point;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Полигональный объект — базовый класс для трёхмерных объектов, состоящих из граней-полигонов. Объект
|
||||
* содержит в себе наборы вершин, граней и поверхностей.
|
||||
*/
|
||||
public class Mesh extends Object3D {
|
||||
|
||||
// Инкремент количества объектов
|
||||
private static var counter:uint = 0;
|
||||
|
||||
// Инкременты для идентификаторов вершин, граней и поверхностей
|
||||
private var vertexIDCounter:uint = 0;
|
||||
private var faceIDCounter:uint = 0;
|
||||
private var surfaceIDCounter:uint = 0;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Список вершин
|
||||
*/
|
||||
alternativa3d var _vertices:Map = new Map();
|
||||
/**
|
||||
* @private
|
||||
* Список граней
|
||||
*/
|
||||
alternativa3d var _faces:Map = new Map();
|
||||
/**
|
||||
* @private
|
||||
* Список поверхностей
|
||||
*/
|
||||
alternativa3d var _surfaces:Map = new Map();
|
||||
|
||||
/**
|
||||
* Создание экземпляра полигонального объекта.
|
||||
*
|
||||
* @param name имя экземпляра
|
||||
*/
|
||||
public function Mesh(name:String = null) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление новой вершины к объекту.
|
||||
*
|
||||
* @param x координата X в локальной системе координат объекта
|
||||
* @param y координата Y в локальной системе координат объекта
|
||||
* @param z координата Z в локальной системе координат объекта
|
||||
* @param id идентификатор вершины. Если указано значение <code>null</code>, идентификатор будет
|
||||
* сформирован автоматически.
|
||||
*
|
||||
* @return экземпляр добавленной вершины
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.VertexExistsError объект уже содержит вершину с указанным идентификатором
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function createVertex(x:Number = 0, y:Number = 0, z:Number = 0, id:Object = null):Vertex {
|
||||
// Проверяем ID
|
||||
if (id != null) {
|
||||
// Если уже есть вершина с таким ID
|
||||
if (_vertices[id] != undefined) {
|
||||
if (_vertices[id] is Vertex) {
|
||||
throw new VertexExistsError(id, this);
|
||||
} else {
|
||||
throw new InvalidIDError(id, this);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ищем первый свободный
|
||||
while (_vertices[vertexIDCounter] != undefined) {
|
||||
vertexIDCounter++;
|
||||
}
|
||||
id = vertexIDCounter;
|
||||
}
|
||||
|
||||
// Создаём вершину
|
||||
var v:Vertex = new Vertex(x, y, z);
|
||||
|
||||
// Добавляем вершину на сцену
|
||||
if (_scene != null) {
|
||||
v.addToScene(_scene);
|
||||
}
|
||||
|
||||
// Добавляем вершину в меш
|
||||
v.addToMesh(this);
|
||||
_vertices[id] = v;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление вершины из объекта. При удалении вершины из объекта также удаляются все грани, которым принадлежит данная вершина.
|
||||
*
|
||||
* @param vertex экземпляр класса <code>alternativa.engine3d.core.Vertex</code> или идентификатор удаляемой вершины
|
||||
*
|
||||
* @return экземпляр удалённой вершины
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function removeVertex(vertex:Object):Vertex {
|
||||
var byLink:Boolean = vertex is Vertex;
|
||||
|
||||
// Проверяем на null
|
||||
if (vertex == null) {
|
||||
throw new VertexNotFoundError(null, this);
|
||||
}
|
||||
|
||||
// Проверяем наличие вершины в меше
|
||||
if (byLink) {
|
||||
// Если удаляем по ссылке
|
||||
if (Vertex(vertex)._mesh != this) {
|
||||
// Если вершина не в меше
|
||||
throw new VertexNotFoundError(vertex, this);
|
||||
}
|
||||
} else {
|
||||
// Если удаляем по ID
|
||||
if (_vertices[vertex] == undefined) {
|
||||
// Если нет вершины с таким ID
|
||||
throw new VertexNotFoundError(vertex, this);
|
||||
} else if (!(_vertices[vertex] is Vertex)) {
|
||||
// По этому id не вершина
|
||||
throw new InvalidIDError(vertex, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Находим вершину и её ID
|
||||
var v:Vertex = byLink ? Vertex(vertex) : _vertices[vertex];
|
||||
var id:Object = byLink ? getVertexId(Vertex(vertex)) : vertex;
|
||||
|
||||
// Удаляем вершину из сцены
|
||||
if (_scene != null) {
|
||||
v.removeFromScene(_scene);
|
||||
}
|
||||
|
||||
// Удаляем вершину из меша
|
||||
v.removeFromMesh(this);
|
||||
delete _vertices[id];
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление грани к объекту. В результате выполнения метода в объекте появляется новая грань, не привязанная
|
||||
* ни к одной поверхности.
|
||||
*
|
||||
* @param vertices массив вершин грани, указанных в порядке обхода лицевой стороны грани против часовой
|
||||
* стрелки. Каждый элемент массива может быть либо экземпляром класса <code>alternativa.engine3d.core.Vertex</code>,
|
||||
* либо идентификатором в наборе вершин объекта. В обоих случаях объект должен содержать указанную вершину.
|
||||
* @param id идентификатор грани. Если указано значение <code>null</code>, идентификатор будет
|
||||
* сформирован автоматически.
|
||||
*
|
||||
* @return экземпляр добавленной грани
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.FaceNeedMoreVerticesError в качестве массива вершин был передан
|
||||
* <code>null</code>, либо количество вершин в массиве меньше трёх
|
||||
* @throws alternativa.engine3d.errors.FaceExistsError объект уже содержит грань с заданным идентификатором
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
* @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит какую-либо вершину из входного массива
|
||||
*
|
||||
* @see Vertex
|
||||
*/
|
||||
public function createFace(vertices:Array, id:Object = null):Face {
|
||||
|
||||
// Проверяем на null
|
||||
if (vertices == null) {
|
||||
throw new FaceNeedMoreVerticesError(this);
|
||||
}
|
||||
|
||||
// Проверяем ID
|
||||
if (id != null) {
|
||||
// Если уже есть грань с таким ID
|
||||
if (_faces[id] != undefined) {
|
||||
if (_faces[id] is Face) {
|
||||
throw new FaceExistsError(id, this);
|
||||
} else {
|
||||
throw new InvalidIDError(id, this);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ищем первый свободный ID
|
||||
while (_faces[faceIDCounter] != undefined) {
|
||||
faceIDCounter++;
|
||||
}
|
||||
id = faceIDCounter;
|
||||
}
|
||||
|
||||
// Проверяем количество точек
|
||||
var length:uint = vertices.length;
|
||||
if (length < 3) {
|
||||
throw new FaceNeedMoreVerticesError(this, length);
|
||||
}
|
||||
|
||||
// Проверяем и формируем список вершин
|
||||
var v:Array = new Array();
|
||||
var vertex:Vertex;
|
||||
for (var i:uint = 0; i < length; i++) {
|
||||
if (vertices[i] is Vertex) {
|
||||
// Если работаем со ссылками
|
||||
vertex = vertices[i];
|
||||
if (vertex._mesh != this) {
|
||||
// Если вершина не в меше
|
||||
throw new VertexNotFoundError(vertices[i], this);
|
||||
}
|
||||
} else {
|
||||
// Если работаем с ID
|
||||
if (_vertices[vertices[i]] == null) {
|
||||
// Если нет вершины с таким ID
|
||||
throw new VertexNotFoundError(vertices[i], this);
|
||||
} else if (!(_vertices[vertices[i]] is Vertex)) {
|
||||
// Если id зарезервировано
|
||||
throw new InvalidIDError(vertices[i],this);
|
||||
}
|
||||
vertex = _vertices[vertices[i]];
|
||||
}
|
||||
v.push(vertex);
|
||||
}
|
||||
|
||||
// Создаём грань
|
||||
var f:Face = new Face(v);
|
||||
|
||||
// Добавляем грань на сцену
|
||||
if (_scene != null) {
|
||||
f.addToScene(_scene);
|
||||
}
|
||||
|
||||
// Добавляем грань в меш
|
||||
f.addToMesh(this);
|
||||
_faces[id] = f;
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление грани из объекта. Грань также удаляется из поверхности объекта, которой она принадлежит.
|
||||
*
|
||||
* @param экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор удаляемой грани
|
||||
*
|
||||
* @return экземпляр удалённой грани
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function removeFace(face:Object):Face {
|
||||
var byLink:Boolean = face is Face;
|
||||
|
||||
// Проверяем на null
|
||||
if (face == null) {
|
||||
throw new FaceNotFoundError(null, this);
|
||||
}
|
||||
|
||||
// Проверяем наличие грани в меше
|
||||
if (byLink) {
|
||||
// Если удаляем по ссылке
|
||||
if (Face(face)._mesh != this) {
|
||||
// Если грань не в меше
|
||||
throw new FaceNotFoundError(face, this);
|
||||
}
|
||||
} else {
|
||||
// Если удаляем по ID
|
||||
if (_faces[face] == undefined) {
|
||||
// Если нет грани с таким ID
|
||||
throw new FaceNotFoundError(face, this);
|
||||
} else if (!(_faces[face] is Face)) {
|
||||
throw new InvalidIDError(face, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Находим грань и её ID
|
||||
var f:Face = byLink ? Face(face) : _faces[face] ;
|
||||
var id:Object = byLink ? getFaceId(Face(face)) : face;
|
||||
|
||||
// Удаляем грань из сцены
|
||||
if (_scene != null) {
|
||||
f.removeFromScene(_scene);
|
||||
}
|
||||
|
||||
// Удаляем вершины из грани
|
||||
f.removeVertices();
|
||||
|
||||
// Удаляем грань из поверхности
|
||||
if (f._surface != null) {
|
||||
f._surface._faces.remove(f);
|
||||
f.removeFromSurface(f._surface);
|
||||
}
|
||||
|
||||
// Удаляем грань из меша
|
||||
f.removeFromMesh(this);
|
||||
delete _faces[id];
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление новой поверхности к объекту.
|
||||
*
|
||||
* @param faces набор граней, составляющих поверхность. Каждый элемент массива должен быть либо экземпляром класса
|
||||
* <code>alternativa.engine3d.core.Face</code>, либо идентификатором грани. В обоих случаях объект должен содержать
|
||||
* указанную грань. Если значение параметра равно <code>null</code>, то будет создана пустая поверхность. Если
|
||||
* какая-либо грань содержится в другой поверхности, она будет перенесена в новую поверхность.
|
||||
* @param id идентификатор новой поверхности. Если указано значение <code>null</code>, идентификатор будет
|
||||
* сформирован автоматически.
|
||||
*
|
||||
* @return экземпляр добавленной поверхности
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.SurfaceExistsError объект уже содержит поверхность с заданным идентификатором
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*
|
||||
* @see Face
|
||||
*/
|
||||
public function createSurface(faces:Array = null, id:Object = null):Surface {
|
||||
// Проверяем ID
|
||||
if (id != null) {
|
||||
// Если уже есть поверхность с таким ID
|
||||
if (_surfaces[id] != undefined) {
|
||||
if (_surfaces[id] is Surface) {
|
||||
throw new SurfaceExistsError(id, this);
|
||||
} else {
|
||||
throw new InvalidIDError(id, this);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ищем первый свободный ID
|
||||
while (_surfaces[surfaceIDCounter] != undefined) {
|
||||
surfaceIDCounter++;
|
||||
}
|
||||
id = surfaceIDCounter;
|
||||
}
|
||||
|
||||
// Создаём поверхность
|
||||
var s:Surface = new Surface();
|
||||
|
||||
// Добавляем поверхность на сцену
|
||||
if (_scene != null) {
|
||||
s.addToScene(_scene);
|
||||
}
|
||||
|
||||
// Добавляем поверхность в меш
|
||||
s.addToMesh(this);
|
||||
_surfaces[id] = s;
|
||||
|
||||
// Добавляем грани, если есть
|
||||
if (faces != null) {
|
||||
var length:uint = faces.length;
|
||||
for (var i:uint = 0; i < length; i++) {
|
||||
s.addFace(faces[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление поверхности объекта. Из удаляемой поверхности также удаляются все содержащиеся в ней грани.
|
||||
*
|
||||
* @param surface экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор удаляемой поверхности
|
||||
*
|
||||
* @return экземпляр удалённой поверхности
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function removeSurface(surface:Object):Surface {
|
||||
var byLink:Boolean = surface is Surface;
|
||||
|
||||
// Проверяем на null
|
||||
if (surface == null) {
|
||||
throw new SurfaceNotFoundError(null, this);
|
||||
}
|
||||
|
||||
// Проверяем наличие поверхности в меше
|
||||
if (byLink) {
|
||||
// Если удаляем по ссылке
|
||||
if (Surface(surface)._mesh != this) {
|
||||
// Если поверхность не в меше
|
||||
throw new SurfaceNotFoundError(surface, this);
|
||||
}
|
||||
} else {
|
||||
// Если удаляем по ID
|
||||
if (_surfaces[surface] == undefined) {
|
||||
// Если нет поверхности с таким ID
|
||||
throw new SurfaceNotFoundError(surface, this);
|
||||
} else if (!(_surfaces[surface] is Surface)) {
|
||||
throw new InvalidIDError(surface, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Находим поверхность и её ID
|
||||
var s:Surface = byLink ? Surface(surface) : _surfaces[surface];
|
||||
var id:Object = byLink ? getSurfaceId(Surface(surface)) : surface;
|
||||
|
||||
// Удаляем поверхность из сцены
|
||||
if (_scene != null) {
|
||||
s.removeFromScene(_scene);
|
||||
}
|
||||
|
||||
// Удаляем грани из поверхности
|
||||
s.removeFaces();
|
||||
|
||||
// Удаляем поверхность из меша
|
||||
s.removeFromMesh(this);
|
||||
delete _surfaces[id];
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление всех граней объекта в указанную поверхность.
|
||||
*
|
||||
* @param surface экземпляр класса <code>alternativa.engine3d.core.Surface</code> или идентификатор поверхности, в
|
||||
* которую добавляются грани. Если задан идентификатор, и объект не содержит поверхность с таким идентификатором,
|
||||
* будет создана новая поверхность.
|
||||
*
|
||||
* @param removeSurfaces удалять или нет пустые поверхности после переноса граней
|
||||
*
|
||||
* @return экземпляр поверхности, в которую перенесены грани
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанный экземпляр поверхности
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function moveAllFacesToSurface(surface:Object = null, removeSurfaces:Boolean = false):Surface {
|
||||
var returnSurface:Surface;
|
||||
var returnSurfaceId:Object;
|
||||
if (surface is Surface) {
|
||||
// Работаем с экземпляром Surface
|
||||
if (surface._mesh == this) {
|
||||
returnSurface = Surface(surface);
|
||||
} else {
|
||||
throw new SurfaceNotFoundError(surface, this);
|
||||
}
|
||||
} else {
|
||||
// Работаем с идентификатором
|
||||
if (_surfaces[surface] == undefined) {
|
||||
// Поверхности еще нет
|
||||
returnSurface = createSurface(null, surface);
|
||||
returnSurfaceId = surface;
|
||||
} else {
|
||||
if (_surfaces[surface] is Surface) {
|
||||
returnSurface = _surfaces[surface];
|
||||
} else {
|
||||
// _surfaces[surface] по идентификатору возвращает не Surface
|
||||
throw new InvalidIDError(surface, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Перемещаем все грани
|
||||
for each (var face:Face in _faces) {
|
||||
if (face._surface != returnSurface) {
|
||||
returnSurface.addFace(face);
|
||||
}
|
||||
}
|
||||
if (removeSurfaces) {
|
||||
// Удаляем старые, теперь вручную - меньше проверок, но рискованно
|
||||
if (returnSurfaceId == null) {
|
||||
returnSurfaceId = getSurfaceId(returnSurface);
|
||||
}
|
||||
var newSurfaces:Map = new Map();
|
||||
newSurfaces[returnSurfaceId] = returnSurface;
|
||||
delete _surfaces[returnSurfaceId];
|
||||
// Удаляем оставшиеся
|
||||
for (var currentSurfaceId:* in _surfaces) {
|
||||
// Удаляем поверхность из сцены
|
||||
var currentSurface:Surface = _surfaces[currentSurfaceId];
|
||||
if (_scene != null) {
|
||||
currentSurface.removeFromScene(_scene);
|
||||
}
|
||||
// Удаляем поверхность из меша
|
||||
currentSurface.removeFromMesh(this);
|
||||
delete _surfaces[currentSurfaceId];
|
||||
}
|
||||
// Новый список граней
|
||||
_surfaces = newSurfaces;
|
||||
}
|
||||
return returnSurface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка материала для указанной поверхности.
|
||||
*
|
||||
* @param material материал, назначаемый поверхности. Один экземпляр SurfaceMaterial можно назначить только одной поверхности.
|
||||
* @param surface экземпляр класса <code>alternativa.engine3d.core.Surface</code> или идентификатор поверхности
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*
|
||||
* @see Surface
|
||||
*/
|
||||
public function setMaterialToSurface(material:SurfaceMaterial, surface:Object):void {
|
||||
var byLink:Boolean = surface is Surface;
|
||||
|
||||
// Проверяем на null
|
||||
if (surface == null) {
|
||||
throw new SurfaceNotFoundError(null, this);
|
||||
}
|
||||
|
||||
// Проверяем наличие поверхности в меше
|
||||
if (byLink) {
|
||||
// Если назначаем по ссылке
|
||||
if (Surface(surface)._mesh != this) {
|
||||
// Если поверхность не в меше
|
||||
throw new SurfaceNotFoundError(surface, this);
|
||||
}
|
||||
} else {
|
||||
// Если назначаем по ID
|
||||
if (_surfaces[surface] == undefined) {
|
||||
// Если нет поверхности с таким ID
|
||||
throw new SurfaceNotFoundError(surface, this);
|
||||
} else if (!(_surfaces[surface] is Surface)) {
|
||||
throw new InvalidIDError(surface, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Находим поверхность
|
||||
var s:Surface = byLink ? Surface(surface) : _surfaces[surface];
|
||||
|
||||
// Назначаем материал
|
||||
s.material = material;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка материала для всех поверхностей объекта. Для каждой поверхности устанавливается копия материала.
|
||||
* При передаче <code>null</code> в качестве параметра происходит сброс материалов у всех поверхностей.
|
||||
*
|
||||
* @param material устанавливаемый материал
|
||||
*/
|
||||
public function cloneMaterialToAllSurfaces(material:SurfaceMaterial):void {
|
||||
for each (var surface:Surface in _surfaces) {
|
||||
surface.material = (material != null) ? SurfaceMaterial(material.clone()) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка UV-координат для указанной грани объекта. Матрица преобразования UV-координат расчитывается по
|
||||
* UV-координатам первых трёх вершин грани, поэтому для корректного текстурирования эти вершины должны образовывать
|
||||
* невырожденный треугольник в UV-пространстве.
|
||||
*
|
||||
* @param aUV UV-координаты, соответствующие первой вершине грани
|
||||
* @param bUV UV-координаты, соответствующие второй вершине грани
|
||||
* @param cUV UV-координаты, соответствующие третьей вершине грани
|
||||
* @param face экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор грани
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*
|
||||
* @see Face
|
||||
*/
|
||||
public function setUVsToFace(aUV:Point, bUV:Point, cUV:Point, face:Object):void {
|
||||
var byLink:Boolean = face is Face;
|
||||
|
||||
// Проверяем на null
|
||||
if (face == null) {
|
||||
throw new FaceNotFoundError(null, this);
|
||||
}
|
||||
|
||||
// Проверяем наличие грани в меше
|
||||
if (byLink) {
|
||||
// Если назначаем по ссылке
|
||||
if (Face(face)._mesh != this) {
|
||||
// Если грань не в меше
|
||||
throw new FaceNotFoundError(face, this);
|
||||
}
|
||||
} else {
|
||||
// Если назначаем по ID
|
||||
if (_faces[face] == undefined) {
|
||||
// Если нет грани с таким ID
|
||||
throw new FaceNotFoundError(face, this);
|
||||
} else if (!(_faces[face] is Face)) {
|
||||
throw new InvalidIDError(face, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Находим грань
|
||||
var f:Face = byLink ? Face(face) : _faces[face];
|
||||
|
||||
// Назначаем UV-координаты
|
||||
f.aUV = aUV;
|
||||
f.bUV = bUV;
|
||||
f.cUV = cUV;
|
||||
}
|
||||
|
||||
/**
|
||||
* Набор вершин объекта. Ключами ассоциативного массива являются идентификаторы вершин, значениями - экземпляры вершин.
|
||||
*/
|
||||
public function get vertices():Map {
|
||||
return _vertices.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Набор граней объекта. Ключами ассоциативного массива являются идентификаторы граней, значениями - экземпляры граней.
|
||||
*/
|
||||
public function get faces():Map {
|
||||
return _faces.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Набор поверхностей объекта. Ключами ассоциативного массива являются идентификаторы поверхностей, значениями - экземпляры поверхностей.
|
||||
*/
|
||||
public function get surfaces():Map {
|
||||
return _surfaces.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение вершины объекта по её идентификатору.
|
||||
*
|
||||
* @param id идентификатор вершины
|
||||
*
|
||||
* @return экземпляр вершины с указанным идентификатором
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит вершину с указанным идентификатором
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function getVertexById(id:Object):Vertex {
|
||||
if (id == null) {
|
||||
throw new VertexNotFoundError(null, this);
|
||||
}
|
||||
if (_vertices[id] == undefined) {
|
||||
// Если нет вершины с таким ID
|
||||
throw new VertexNotFoundError(id, this);
|
||||
} else {
|
||||
if (_vertices[id] is Vertex) {
|
||||
return _vertices[id];
|
||||
} else {
|
||||
throw new InvalidIDError(id, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение идентификатора вершины объекта.
|
||||
*
|
||||
* @param экземпляр вершины
|
||||
*
|
||||
* @return идентификатор указанной вершины
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.VertexNotFoundError объект не содержит указанную вершину
|
||||
*/
|
||||
public function getVertexId(vertex:Vertex):Object {
|
||||
if (vertex == null) {
|
||||
throw new VertexNotFoundError(null, this);
|
||||
}
|
||||
if (vertex._mesh != this) {
|
||||
// Если вершина не в меше
|
||||
throw new VertexNotFoundError(vertex, this);
|
||||
}
|
||||
for (var i:Object in _vertices) {
|
||||
if (_vertices[i] == vertex) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new VertexNotFoundError(vertex, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка наличия вершины в объекте.
|
||||
*
|
||||
* @param vertex экземпляр класса <code>alternativa.engine3d.core.Vertex</code> или идентификатор вершины
|
||||
*
|
||||
* @return <code>true</code>, если объект содержит указанную вершину, иначе <code>false</code>
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.VertexNotFoundError в качестве vertex был передан null
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*
|
||||
* @see Vertex
|
||||
*/
|
||||
public function hasVertex(vertex:Object):Boolean {
|
||||
if (vertex == null) {
|
||||
throw new VertexNotFoundError(null, this);
|
||||
}
|
||||
if (vertex is Vertex) {
|
||||
// Проверка вершины
|
||||
return vertex._mesh == this;
|
||||
} else {
|
||||
// Проверка ID вершины
|
||||
if (_vertices[vertex] != undefined) {
|
||||
// По этому ID есть объект
|
||||
if (_vertices[vertex] is Vertex) {
|
||||
// Объект является вершиной
|
||||
return true;
|
||||
} else {
|
||||
// ID некорректный
|
||||
throw new InvalidIDError(vertex, this);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение грани объекта по ее идентификатору.
|
||||
*
|
||||
* @param id идентификатор грани
|
||||
*
|
||||
* @return экземпляр грани с указанным идентификатором
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит грань с указанным идентификатором
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function getFaceById(id:Object):Face {
|
||||
if (id == null) {
|
||||
throw new FaceNotFoundError(null, this);
|
||||
}
|
||||
if (_faces[id] == undefined) {
|
||||
// Если нет грани с таким ID
|
||||
throw new FaceNotFoundError(id, this);
|
||||
} else {
|
||||
if (_faces[id] is Face) {
|
||||
return _faces[id];
|
||||
} else {
|
||||
throw new InvalidIDError(id, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение идентификатора грани объекта.
|
||||
*
|
||||
* @param face экземпляр грани
|
||||
*
|
||||
* @return идентификатор указанной грани
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.FaceNotFoundError объект не содержит указанную грань
|
||||
*/
|
||||
public function getFaceId(face:Face):Object {
|
||||
if (face == null) {
|
||||
throw new FaceNotFoundError(null, this);
|
||||
}
|
||||
if (face._mesh != this) {
|
||||
// Если грань не в меше
|
||||
throw new FaceNotFoundError(face, this);
|
||||
}
|
||||
for (var i:Object in _faces) {
|
||||
if (_faces[i] == face) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new FaceNotFoundError(face, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка наличия грани в объекте.
|
||||
*
|
||||
* @param face экземпляр класса <code>Face</code> или идентификатор грани
|
||||
*
|
||||
* @return <code>true</code>, если объект содержит указанную грань, иначе <code>false</code>
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.FaceNotFoundError в качестве face был указан null
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function hasFace(face:Object):Boolean {
|
||||
if (face == null) {
|
||||
throw new FaceNotFoundError(null, this);
|
||||
}
|
||||
if (face is Face) {
|
||||
// Проверка грани
|
||||
return face._mesh == this;
|
||||
} else {
|
||||
// Проверка ID грани
|
||||
if (_faces[face] != undefined) {
|
||||
// По этому ID есть объект
|
||||
if (_faces[face] is Face) {
|
||||
// Объект является гранью
|
||||
return true;
|
||||
} else {
|
||||
// ID некорректный
|
||||
throw new InvalidIDError(face, this);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение поверхности объекта по ее идентификатору
|
||||
*
|
||||
* @param id идентификатор поверхности
|
||||
*
|
||||
* @return экземпляр поверхности с указанным идентификатором
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит поверхность с указанным идентификатором
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function getSurfaceById(id:Object):Surface {
|
||||
if (id == null) {
|
||||
throw new SurfaceNotFoundError(null, this);
|
||||
}
|
||||
if (_surfaces[id] == undefined) {
|
||||
// Если нет поверхности с таким ID
|
||||
throw new SurfaceNotFoundError(id, this);
|
||||
} else {
|
||||
if (_surfaces[id] is Surface) {
|
||||
return _surfaces[id];
|
||||
} else {
|
||||
throw new InvalidIDError(id, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение идентификатора поверхности объекта.
|
||||
*
|
||||
* @param surface экземпляр поверхности
|
||||
*
|
||||
* @return идентификатор указанной поверхности
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.SurfaceNotFoundError объект не содержит указанную поверхность
|
||||
*/
|
||||
public function getSurfaceId(surface:Surface):Object {
|
||||
if (surface == null) {
|
||||
throw new SurfaceNotFoundError(null, this);
|
||||
}
|
||||
if (surface._mesh != this) {
|
||||
// Если поверхность не в меше
|
||||
throw new SurfaceNotFoundError(surface, this);
|
||||
}
|
||||
for (var i:Object in _surfaces) {
|
||||
if (_surfaces[i] == surface) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка наличия поверхности в объекте.
|
||||
*
|
||||
* @param surface экземпляр класса <code>Surface</code> или идентификатор поверхности
|
||||
*
|
||||
* @return <code>true</true>, если объект содержит указанную поверхность, иначе <code>false</code>
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.SurfaceNotFoundError в качестве surface был передан null
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*/
|
||||
public function hasSurface(surface:Object):Boolean {
|
||||
if (surface == null) {
|
||||
throw new SurfaceNotFoundError(null, this);
|
||||
}
|
||||
if (surface is Surface) {
|
||||
// Проверка поверхности
|
||||
return surface._mesh == this;
|
||||
} else {
|
||||
// Проверка ID поверхности
|
||||
if (_surfaces[surface] != undefined) {
|
||||
// По этому ID есть объект
|
||||
if (_surfaces[surface] is Surface) {
|
||||
// Объект является поверхностью
|
||||
return true;
|
||||
} else {
|
||||
// ID некорректный
|
||||
throw new InvalidIDError(surface, this);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @inheritDoc
|
||||
*/
|
||||
override alternativa3d function setScene(value:Scene3D):void {
|
||||
if (_scene != value) {
|
||||
var vertex:Vertex;
|
||||
var face:Face;
|
||||
var surface:Surface;
|
||||
if (value != null) {
|
||||
// Добавить вершины на сцену
|
||||
for each (vertex in _vertices) {
|
||||
vertex.addToScene(value);
|
||||
}
|
||||
// Добавить грани на сцену
|
||||
for each (face in _faces) {
|
||||
face.addToScene(value);
|
||||
}
|
||||
// Добавить поверхности на сцену
|
||||
for each (surface in _surfaces) {
|
||||
surface.addToScene(value);
|
||||
}
|
||||
} else {
|
||||
// Удалить вершины из сцены
|
||||
for each (vertex in _vertices) {
|
||||
vertex.removeFromScene(_scene);
|
||||
}
|
||||
// Удалить грани из сцены
|
||||
for each (face in _faces) {
|
||||
face.removeFromScene(_scene);
|
||||
}
|
||||
// Удалить поверхности из сцены
|
||||
for each (surface in _surfaces) {
|
||||
surface.removeFromScene(_scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
super.setScene(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override protected function defaultName():String {
|
||||
return "mesh" + ++counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override public function toString():String {
|
||||
return "[" + ObjectUtils.getClassName(this) + " " + _name + " vertices: " + _vertices.length + " faces: " + _faces.length + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected override function createEmptyObject():Object3D {
|
||||
return new Mesh();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected override function clonePropertiesFrom(source:Object3D):void {
|
||||
super.clonePropertiesFrom(source);
|
||||
|
||||
var src:Mesh = Mesh(source);
|
||||
|
||||
var id:*;
|
||||
var len:int;
|
||||
var i:int;
|
||||
// Копирование вершин
|
||||
var vertexMap:Map = new Map(true);
|
||||
for (id in src._vertices) {
|
||||
var sourceVertex:Vertex = src._vertices[id];
|
||||
vertexMap[sourceVertex] = createVertex(sourceVertex.x, sourceVertex.y, sourceVertex.z, id);
|
||||
}
|
||||
|
||||
// Копирование граней
|
||||
var faceMap:Map = new Map(true);
|
||||
for (id in src._faces) {
|
||||
var sourceFace:Face = src._faces[id];
|
||||
len = sourceFace._vertices.length;
|
||||
var faceVertices:Array = new Array(len);
|
||||
for (i = 0; i < len; i++) {
|
||||
faceVertices[i] = vertexMap[sourceFace._vertices[i]];
|
||||
}
|
||||
var newFace:Face = createFace(faceVertices, id);
|
||||
newFace.aUV = sourceFace._aUV;
|
||||
newFace.bUV = sourceFace._bUV;
|
||||
newFace.cUV = sourceFace._cUV;
|
||||
faceMap[sourceFace] = newFace;
|
||||
}
|
||||
|
||||
// Копирование поверхностей
|
||||
for (id in src._surfaces) {
|
||||
var sourceSurface:Surface = src._surfaces[id];
|
||||
var surfaceFaces:Array = sourceSurface._faces.toArray();
|
||||
len = surfaceFaces.length;
|
||||
for (i = 0; i < len; i++) {
|
||||
surfaceFaces[i] = faceMap[surfaceFaces[i]];
|
||||
}
|
||||
var surface:Surface = createSurface(surfaceFaces, id);
|
||||
var sourceMaterial:SurfaceMaterial = sourceSurface.material;
|
||||
if (sourceMaterial != null) {
|
||||
surface.material = SurfaceMaterial(sourceMaterial.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
708
Alternativa3D5/5.4/alternativa/engine3d/core/Object3D.as
Normal file
708
Alternativa3D5/5.4/alternativa/engine3d/core/Object3D.as
Normal file
@@ -0,0 +1,708 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.errors.Object3DHierarchyError;
|
||||
import alternativa.engine3d.errors.Object3DNotFoundError;
|
||||
import alternativa.types.Matrix3D;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.types.Set;
|
||||
import alternativa.utils.ObjectUtils;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Базовый класс для объектов, находящихся в сцене. Класс реализует иерархию объектов сцены, а также содержит сведения
|
||||
* о трансформации объекта как единого целого.
|
||||
*
|
||||
* <p> Масштабирование, ориентация и положение объекта задаются в родительской системе координат. Результирующая
|
||||
* локальная трансформация является композицией операций масштабирования, поворотов объекта относительно осей
|
||||
* <code>X</code>, <code>Y</code>, <code>Z</code> и параллельного переноса центра объекта из начала координат.
|
||||
* Операции применяются в порядке их перечисления.
|
||||
*
|
||||
* <p> Глобальная трансформация (в системе координат корневого объекта сцены) является композицией трансформаций
|
||||
* самого объекта и всех его предков по иерархии объектов сцены.
|
||||
*/
|
||||
public class Object3D {
|
||||
// Операции
|
||||
/**
|
||||
* @private
|
||||
* Поворот или масштабирование
|
||||
*/
|
||||
alternativa3d var changeRotationOrScaleOperation:Operation = new Operation("changeRotationOrScale", this);
|
||||
/**
|
||||
* @private
|
||||
* Перемещение
|
||||
*/
|
||||
alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this);
|
||||
/**
|
||||
* @private
|
||||
* Расчёт матрицы трансформации
|
||||
*/
|
||||
alternativa3d var calculateTransformationOperation:Operation = new Operation("calculateTransformation", this, calculateTransformation, Operation.OBJECT_CALCULATE_TRANSFORMATION);
|
||||
/**
|
||||
* @private
|
||||
* Изменение уровеня мобильности
|
||||
*/
|
||||
alternativa3d var calculateMobilityOperation:Operation = new Operation("calculateMobility", this, calculateMobility, Operation.OBJECT_CALCULATE_MOBILITY);
|
||||
|
||||
// Инкремент количества объектов
|
||||
private static var counter:uint = 0;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Наименование
|
||||
*/
|
||||
alternativa3d var _name:String;
|
||||
/**
|
||||
* @private
|
||||
* Сцена
|
||||
*/
|
||||
alternativa3d var _scene:Scene3D;
|
||||
/**
|
||||
* @private
|
||||
* Родительский объект
|
||||
*/
|
||||
alternativa3d var _parent:Object3D;
|
||||
/**
|
||||
* @private
|
||||
* Дочерние объекты
|
||||
*/
|
||||
alternativa3d var _children:Set = new Set();
|
||||
/**
|
||||
* @private
|
||||
* Уровень мобильности
|
||||
*/
|
||||
alternativa3d var _mobility:int = 0;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d var inheritedMobility:int;
|
||||
/**
|
||||
* @private
|
||||
* Координаты объекта относительно родителя
|
||||
*/
|
||||
alternativa3d var _coords:Point3D = new Point3D();
|
||||
/**
|
||||
* @private
|
||||
* Поворот объекта по оси X относительно родителя. Угол измеряется в радианах.
|
||||
*/
|
||||
alternativa3d var _rotationX:Number = 0;
|
||||
/**
|
||||
* @private
|
||||
* Поворот объекта по оси Y относительно родителя. Угол измеряется в радианах.
|
||||
*/
|
||||
alternativa3d var _rotationY:Number = 0;
|
||||
/**
|
||||
* @private
|
||||
* Поворот объекта по оси Z относительно родителя. Угол измеряется в радианах.
|
||||
*/
|
||||
alternativa3d var _rotationZ:Number = 0;
|
||||
/**
|
||||
* @private
|
||||
* Мастшаб объекта по оси X относительно родителя
|
||||
*/
|
||||
alternativa3d var _scaleX:Number = 1;
|
||||
/**
|
||||
* @private
|
||||
* Мастшаб объекта по оси Y относительно родителя
|
||||
*/
|
||||
alternativa3d var _scaleY:Number = 1;
|
||||
/**
|
||||
* @private
|
||||
* Мастшаб объекта по оси Z относительно родителя
|
||||
*/
|
||||
alternativa3d var _scaleZ:Number = 1;
|
||||
/**
|
||||
* @private
|
||||
* Полная матрица трансформации, переводящая координаты из локальной системы координат объекта в систему координат сцены
|
||||
*/
|
||||
alternativa3d var transformation:Matrix3D = new Matrix3D();
|
||||
/**
|
||||
* @private
|
||||
* Координаты в сцене
|
||||
*/
|
||||
alternativa3d var globalCoords:Point3D = new Point3D();
|
||||
|
||||
/**
|
||||
* Создание экземпляра класса.
|
||||
*
|
||||
* @param name имя экземпляра
|
||||
*/
|
||||
public function Object3D(name:String = null) {
|
||||
// Имя по-умолчанию
|
||||
_name = (name != null) ? name : defaultName();
|
||||
|
||||
// Последствия операций
|
||||
changeRotationOrScaleOperation.addSequel(calculateTransformationOperation);
|
||||
changeCoordsOperation.addSequel(calculateTransformationOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт трансформации
|
||||
*/
|
||||
private function calculateTransformation():void {
|
||||
if (changeRotationOrScaleOperation.queued) {
|
||||
// Если полная трансформация
|
||||
transformation.toTransform(_coords.x, _coords.y, _coords.z, _rotationX, _rotationY, _rotationZ, _scaleX, _scaleY, _scaleZ);
|
||||
if (_parent != null) {
|
||||
transformation.combine(_parent.transformation);
|
||||
}
|
||||
// Сохраняем глобальные координаты объекта
|
||||
globalCoords.x = transformation.d;
|
||||
globalCoords.y = transformation.h;
|
||||
globalCoords.z = transformation.l;
|
||||
} else {
|
||||
// Если только перемещение
|
||||
globalCoords.copy(_coords);
|
||||
if (_parent != null) {
|
||||
globalCoords.transform(_parent.transformation);
|
||||
}
|
||||
transformation.offset(globalCoords.x, globalCoords.y, globalCoords.z);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Расчёт общей мобильности
|
||||
*/
|
||||
private function calculateMobility():void {
|
||||
inheritedMobility = ((_parent != null) ? _parent.inheritedMobility : 0) + _mobility;
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление дочернего объекта. Добавляемый объект удаляется из списка детей предыдущего родителя.
|
||||
* Новой сценой дочернего объекта становится сцена родителя.
|
||||
*
|
||||
* @param child добавляемый объект
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.Object3DHierarchyError нарушение иерархии объектов сцены
|
||||
*/
|
||||
public function addChild(child:Object3D):void {
|
||||
|
||||
// Проверка на null
|
||||
if (child == null) {
|
||||
throw new Object3DHierarchyError(null, this);
|
||||
}
|
||||
|
||||
// Проверка на наличие
|
||||
if (child._parent == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка на добавление к самому себе
|
||||
if (child == this) {
|
||||
throw new Object3DHierarchyError(this, this);
|
||||
}
|
||||
|
||||
// Проверка на добавление родительского объекта
|
||||
if (child._scene == _scene) {
|
||||
// Если объект был в той же сцене, либо оба не были в сцене
|
||||
var parentObject:Object3D = _parent;
|
||||
while (parentObject != null) {
|
||||
if (child == parentObject) {
|
||||
throw new Object3DHierarchyError(child, this);
|
||||
return;
|
||||
}
|
||||
parentObject = parentObject._parent;
|
||||
}
|
||||
}
|
||||
|
||||
// Если объект был в другом объекте
|
||||
if (child._parent != null) {
|
||||
// Удалить его оттуда
|
||||
child._parent._children.remove(child);
|
||||
} else {
|
||||
// Если объект был корневым в сцене
|
||||
if (child._scene != null && child._scene._root == child) {
|
||||
child._scene.root = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем в список
|
||||
_children.add(child);
|
||||
// Указываем себя как родителя
|
||||
child.setParent(this);
|
||||
// Устанавливаем уровни
|
||||
child.setLevel((calculateTransformationOperation.priority & 0xFFFFFF) + 1);
|
||||
// Указываем сцену
|
||||
child.setScene(_scene);
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление дочернего объекта.
|
||||
*
|
||||
* @param child удаляемый дочерний объект
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.Object3DNotFoundError указанный объект не содержится в списке детей текущего объекта
|
||||
*/
|
||||
public function removeChild(child:Object3D):void {
|
||||
// Проверка на null
|
||||
if (child == null) {
|
||||
throw new Object3DNotFoundError(null, this);
|
||||
}
|
||||
// Проверка на наличие
|
||||
if (child._parent != this) {
|
||||
throw new Object3DNotFoundError(child, this);
|
||||
}
|
||||
// Убираем из списка
|
||||
_children.remove(child);
|
||||
// Удаляем ссылку на родителя
|
||||
child.setParent(null);
|
||||
// Удаляем ссылку на сцену
|
||||
child.setScene(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Установка родительского объекта.
|
||||
*
|
||||
* @param value родительский объект
|
||||
*/
|
||||
alternativa3d function setParent(value:Object3D):void {
|
||||
// Отписываемся от сигналов старого родителя
|
||||
if (_parent != null) {
|
||||
removeParentSequels();
|
||||
}
|
||||
// Сохранить родителя
|
||||
_parent = value;
|
||||
// Если устанавливаем родителя
|
||||
if (value != null) {
|
||||
// Подписка на сигналы родителя
|
||||
addParentSequels();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Установка новой сцены для объекта.
|
||||
*
|
||||
* @param value сцена
|
||||
*/
|
||||
alternativa3d function setScene(value:Scene3D):void {
|
||||
if (_scene != value) {
|
||||
// Если была сцена
|
||||
if (_scene != null) {
|
||||
// Удалиться из сцены
|
||||
removeFromScene(_scene);
|
||||
}
|
||||
// Если новая сцена
|
||||
if (value != null) {
|
||||
// Добавиться на сцену
|
||||
addToScene(value);
|
||||
}
|
||||
// Сохранить сцену
|
||||
_scene = value;
|
||||
} else {
|
||||
// Посылаем операцию трансформации
|
||||
addOperationToScene(changeRotationOrScaleOperation);
|
||||
// Посылаем операцию пересчёта мобильности
|
||||
addOperationToScene(calculateMobilityOperation);
|
||||
}
|
||||
// Установить эту сцену у дочерних объектов
|
||||
for (var key:* in _children) {
|
||||
var object:Object3D = key;
|
||||
object.setScene(_scene);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Установка уровня операции трансформации.
|
||||
*
|
||||
* @param value уровень операции трансформации
|
||||
*/
|
||||
alternativa3d function setLevel(value:uint):void {
|
||||
// Установить уровень операции трансформации и расчёта мобильности
|
||||
calculateTransformationOperation.priority = (calculateTransformationOperation.priority & 0xFF000000) | value;
|
||||
calculateMobilityOperation.priority = (calculateMobilityOperation.priority & 0xFF000000) | value;
|
||||
// Установить уровни у дочерних объектов
|
||||
for (var key:* in _children) {
|
||||
var object:Object3D = key;
|
||||
object.setLevel(value + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Подписка на сигналы родителя.
|
||||
*/
|
||||
private function addParentSequels():void {
|
||||
_parent.changeCoordsOperation.addSequel(changeCoordsOperation);
|
||||
_parent.changeRotationOrScaleOperation.addSequel(changeRotationOrScaleOperation);
|
||||
_parent.calculateMobilityOperation.addSequel(calculateMobilityOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление подписки на сигналы родителя.
|
||||
*/
|
||||
private function removeParentSequels():void {
|
||||
_parent.changeCoordsOperation.removeSequel(changeCoordsOperation);
|
||||
_parent.changeRotationOrScaleOperation.removeSequel(changeRotationOrScaleOperation);
|
||||
_parent.calculateMobilityOperation.removeSequel(calculateMobilityOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод вызывается при добавлении объекта на сцену. Наследники могут переопределять метод для выполнения
|
||||
* специфических действий.
|
||||
*
|
||||
* @param scene сцена, в которую добавляется объект
|
||||
*/
|
||||
protected function addToScene(scene:Scene3D):void {
|
||||
// При добавлении на сцену полная трансформация и расчёт мобильности
|
||||
scene.addOperation(changeRotationOrScaleOperation);
|
||||
scene.addOperation(calculateMobilityOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод вызывается при удалении объекта со сцены. Наследники могут переопределять метод для выполнения
|
||||
* специфических действий.
|
||||
*
|
||||
* @param scene сцена, из которой удаляется объект
|
||||
*/
|
||||
protected function removeFromScene(scene:Scene3D):void {
|
||||
// Удаляем все операции из очереди
|
||||
scene.removeOperation(changeRotationOrScaleOperation);
|
||||
scene.removeOperation(changeCoordsOperation);
|
||||
scene.removeOperation(calculateMobilityOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Имя объекта.
|
||||
*/
|
||||
public function get name():String {
|
||||
return _name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set name(value:String):void {
|
||||
_name = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сцена, которой принадлежит объект.
|
||||
*/
|
||||
public function get scene():Scene3D {
|
||||
return _scene;
|
||||
}
|
||||
|
||||
/**
|
||||
* Родительский объект.
|
||||
*/
|
||||
public function get parent():Object3D {
|
||||
return _parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Набор дочерних объектов.
|
||||
*/
|
||||
public function get children():Set {
|
||||
return _children.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Уровень мобильности. Результирующая мобильность объекта является суммой мобильностей объекта и всех его предков
|
||||
* по иерархии объектов в сцене. Результирующая мобильность влияет на положение объекта в BSP-дереве. Менее мобильные
|
||||
* объекты находятся ближе к корню дерева, чем более мобильные.
|
||||
*/
|
||||
public function get mobility():int {
|
||||
return _mobility;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set mobility(value:int):void {
|
||||
if (_mobility != value) {
|
||||
_mobility = value;
|
||||
addOperationToScene(calculateMobilityOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Координата X.
|
||||
*/
|
||||
public function get x():Number {
|
||||
return _coords.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Координата Y.
|
||||
*/
|
||||
public function get y():Number {
|
||||
return _coords.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Координата Z.
|
||||
*/
|
||||
public function get z():Number {
|
||||
return _coords.z;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set x(value:Number):void {
|
||||
if (_coords.x != value) {
|
||||
_coords.x = value;
|
||||
addOperationToScene(changeCoordsOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set y(value:Number):void {
|
||||
if (_coords.y != value) {
|
||||
_coords.y = value;
|
||||
addOperationToScene(changeCoordsOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set z(value:Number):void {
|
||||
if (_coords.z != value) {
|
||||
_coords.z = value;
|
||||
addOperationToScene(changeCoordsOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Координаты объекта.
|
||||
*/
|
||||
public function get coords():Point3D {
|
||||
return _coords.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set coords(value:Point3D):void {
|
||||
if (!_coords.equals(value)) {
|
||||
_coords.copy(value);
|
||||
addOperationToScene(changeCoordsOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Угол поворота вокруг оси X, заданный в радианах.
|
||||
*/
|
||||
public function get rotationX():Number {
|
||||
return _rotationX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Угол поворота вокруг оси Y, заданный в радианах.
|
||||
*/
|
||||
public function get rotationY():Number {
|
||||
return _rotationY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Угол поворота вокруг оси Z, заданный в радианах.
|
||||
*/
|
||||
public function get rotationZ():Number {
|
||||
return _rotationZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set rotationX(value:Number):void {
|
||||
if (_rotationX != value) {
|
||||
_rotationX = value;
|
||||
addOperationToScene(changeRotationOrScaleOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set rotationY(value:Number):void {
|
||||
if (_rotationY != value) {
|
||||
_rotationY = value;
|
||||
addOperationToScene(changeRotationOrScaleOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set rotationZ(value:Number):void {
|
||||
if (_rotationZ != value) {
|
||||
_rotationZ = value;
|
||||
addOperationToScene(changeRotationOrScaleOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Коэффициент масштабирования вдоль оси X.
|
||||
*/
|
||||
public function get scaleX():Number {
|
||||
return _scaleX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Коэффициент масштабирования вдоль оси Y.
|
||||
*/
|
||||
public function get scaleY():Number {
|
||||
return _scaleY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Коэффициент масштабирования вдоль оси Z.
|
||||
*/
|
||||
public function get scaleZ():Number {
|
||||
return _scaleZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set scaleX(value:Number):void {
|
||||
if (_scaleX != value) {
|
||||
_scaleX = value;
|
||||
addOperationToScene(changeRotationOrScaleOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set scaleY(value:Number):void {
|
||||
if (_scaleY != value) {
|
||||
_scaleY = value;
|
||||
addOperationToScene(changeRotationOrScaleOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set scaleZ(value:Number):void {
|
||||
if (_scaleZ != value) {
|
||||
_scaleZ = value;
|
||||
addOperationToScene(changeRotationOrScaleOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Строковое представление объекта.
|
||||
*
|
||||
* @return строковое представление объекта
|
||||
*/
|
||||
public function toString():String {
|
||||
return "[" + ObjectUtils.getClassName(this) + " " + _name + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Имя объекта по умолчанию.
|
||||
*
|
||||
* @return имя объекта по умолчанию
|
||||
*/
|
||||
protected function defaultName():String {
|
||||
return "object" + ++counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Добавление операции в очередь.
|
||||
*
|
||||
* @param operation добавляемая операция
|
||||
*/
|
||||
alternativa3d function addOperationToScene(operation:Operation):void {
|
||||
if (_scene != null) {
|
||||
_scene.addOperation(operation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление операции из очереди.
|
||||
*
|
||||
* @param operation удаляемая операция
|
||||
*/
|
||||
alternativa3d function removeOperationFromScene(operation:Operation):void {
|
||||
if (_scene != null) {
|
||||
_scene.removeOperation(operation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Создание пустого объекта без какой-либо внутренней структуры. Например, если некоторый геометрический примитив при
|
||||
* своём создании формирует набор вершин, граней и поверхностей, то этот метод не должен создавать вершины, грани и
|
||||
* поверхности. Данный метод используется в методе clone() и должен быть переопределён в потомках для получения
|
||||
* правильного объекта.
|
||||
*
|
||||
* @return новый пустой объект
|
||||
*/
|
||||
protected function createEmptyObject():Object3D {
|
||||
return new Object3D();
|
||||
}
|
||||
|
||||
/**
|
||||
* Копирование свойств объекта-источника. Данный метод используется в методе clone() и должен быть переопределён в
|
||||
* потомках для получения правильного объекта. Каждый потомок должен в переопределённом методе копировать только те
|
||||
* свойства, которые добавлены к базовому классу именно в нём. Копирование унаследованных свойств выполняется
|
||||
* вызовом super.clonePropertiesFrom(source).
|
||||
*
|
||||
* @param source объект, свойства которого копируются
|
||||
*/
|
||||
protected function clonePropertiesFrom(source:Object3D):void {
|
||||
_name = source._name;
|
||||
_mobility = source._mobility;
|
||||
_coords.x = source._coords.x;
|
||||
_coords.y = source._coords.y;
|
||||
_coords.z = source._coords.z;
|
||||
_rotationX = source._rotationX;
|
||||
_rotationY = source._rotationY;
|
||||
_rotationZ = source._rotationZ;
|
||||
_scaleX = source._scaleX;
|
||||
_scaleY = source._scaleY;
|
||||
_scaleZ = source._scaleZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Клонирование объекта. Для реализации собственного клонирования наследники должны переопределять методы
|
||||
* <code>createEmptyObject()</code> и <code>clonePropertiesFrom()</code>.
|
||||
*
|
||||
* @return клонированный экземпляр объекта
|
||||
*
|
||||
* @see #createEmptyObject()
|
||||
* @see #clonePropertiesFrom()
|
||||
*/
|
||||
public function clone():Object3D {
|
||||
var copy:Object3D = createEmptyObject();
|
||||
copy.clonePropertiesFrom(this);
|
||||
|
||||
// Клонирование детей
|
||||
for (var key:* in _children) {
|
||||
var child:Object3D = key;
|
||||
copy.addChild(child.clone());
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение дочернего объекта с заданным именем.
|
||||
*
|
||||
* @param name имя дочернего объекта
|
||||
* @return любой дочерний объект с заданным именем или <code>null</code> в случае отсутствия таких объектов
|
||||
*/
|
||||
public function getChildByName(name:String):Object3D {
|
||||
for (var key:* in _children) {
|
||||
var child:Object3D = key;
|
||||
if (child._name == name) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
125
Alternativa3D5/5.4/alternativa/engine3d/core/Operation.as
Normal file
125
Alternativa3D5/5.4/alternativa/engine3d/core/Operation.as
Normal file
@@ -0,0 +1,125 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.types.Set;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public class Operation {
|
||||
|
||||
alternativa3d static const OBJECT_CALCULATE_TRANSFORMATION:uint = 0x01000000;
|
||||
alternativa3d static const OBJECT_CALCULATE_MOBILITY:uint = 0x02000000;
|
||||
alternativa3d static const VERTEX_CALCULATE_COORDS:uint = 0x03000000;
|
||||
alternativa3d static const FACE_CALCULATE_NORMAL:uint = 0x04000000;
|
||||
alternativa3d static const FACE_CALCULATE_UV:uint = 0x05000000;
|
||||
alternativa3d static const FACE_UPDATE_PRIMITIVE:uint = 0x06000000;
|
||||
alternativa3d static const SCENE_CALCULATE_BSP:uint = 0x07000000;
|
||||
alternativa3d static const FACE_UPDATE_MATERIAL:uint = 0x08000000;
|
||||
alternativa3d static const FACE_CALCULATE_FRAGMENTS_UV:uint = 0x09000000;
|
||||
alternativa3d static const CAMERA_CALCULATE_MATRIX:uint = 0x0A000000;
|
||||
alternativa3d static const CAMERA_CALCULATE_PLANES:uint = 0x0B000000;
|
||||
alternativa3d static const CAMERA_RENDER:uint = 0x0C000000;
|
||||
alternativa3d static const SCENE_CLEAR_PRIMITIVES:uint = 0x0D000000;
|
||||
|
||||
// Объект
|
||||
alternativa3d var object:Object;
|
||||
|
||||
// Метод
|
||||
alternativa3d var method:Function;
|
||||
|
||||
// Название метода
|
||||
alternativa3d var name:String;
|
||||
|
||||
// Последствия
|
||||
private var sequel:Operation;
|
||||
private var sequels:Set;
|
||||
|
||||
// Приоритет операции
|
||||
alternativa3d var priority:uint;
|
||||
|
||||
// Находится ли операция в очереди
|
||||
alternativa3d var queued:Boolean = false;
|
||||
|
||||
public function Operation(name:String, object:Object = null, method:Function = null, priority:uint = 0) {
|
||||
this.object = object;
|
||||
this.method = method;
|
||||
this.name = name;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
// Добавить последствие
|
||||
alternativa3d function addSequel(operation:Operation):void {
|
||||
if (sequel == null) {
|
||||
if (sequels == null) {
|
||||
sequel = operation;
|
||||
} else {
|
||||
sequels[operation] = true;
|
||||
}
|
||||
} else {
|
||||
if (sequel != operation) {
|
||||
sequels = new Set(true);
|
||||
sequels[sequel] = true;
|
||||
sequels[operation] = true;
|
||||
sequel = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Удалить последствие
|
||||
alternativa3d function removeSequel(operation:Operation):void {
|
||||
if (sequel == null) {
|
||||
if (sequels != null) {
|
||||
delete sequels[operation];
|
||||
var key:*;
|
||||
var single:Boolean = false;
|
||||
for (key in sequels) {
|
||||
if (single) {
|
||||
single = false;
|
||||
break;
|
||||
}
|
||||
single = true;
|
||||
}
|
||||
if (single) {
|
||||
sequel = key;
|
||||
sequels = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (sequel == operation) {
|
||||
sequel = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alternativa3d function collectSequels(collector:Array):void {
|
||||
if (sequel == null) {
|
||||
// Проверяем последствия
|
||||
for (var key:* in sequels) {
|
||||
var operation:Operation = key;
|
||||
// Если операция ещё не в очереди
|
||||
if (!operation.queued) {
|
||||
// Добавляем её в очередь
|
||||
collector.push(operation);
|
||||
// Устанавливаем флаг очереди
|
||||
operation.queued = true;
|
||||
// Вызываем добавление в очередь её последствий
|
||||
operation.collectSequels(collector);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!sequel.queued) {
|
||||
collector.push(sequel);
|
||||
sequel.queued = true;
|
||||
sequel.collectSequels(collector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return "[Operation " + (priority >>> 24) + "/" + (priority & 0xFFFFFF) + " " + object + "." + name + "]";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
130
Alternativa3D5/5.4/alternativa/engine3d/core/PolyPrimitive.as
Normal file
130
Alternativa3D5/5.4/alternativa/engine3d/core/PolyPrimitive.as
Normal file
@@ -0,0 +1,130 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Примитивный полигон (примитив), хранящийся в узле BSP-дерева.
|
||||
*/
|
||||
public class PolyPrimitive {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Количество точек
|
||||
*/
|
||||
alternativa3d var num:uint;
|
||||
/**
|
||||
* @private
|
||||
* Точки
|
||||
*/
|
||||
alternativa3d var points:Array = new Array();
|
||||
/**
|
||||
* @private
|
||||
* UV-координаты
|
||||
*/
|
||||
alternativa3d var uvs:Array = new Array();
|
||||
/**
|
||||
* @private
|
||||
* Грань
|
||||
*/
|
||||
alternativa3d var face:Face;
|
||||
/**
|
||||
* @private
|
||||
* Родительский примитив
|
||||
*/
|
||||
alternativa3d var parent:PolyPrimitive;
|
||||
/**
|
||||
* @private
|
||||
* Соседний примитив (при наличии родительского)
|
||||
*/
|
||||
alternativa3d var sibling:PolyPrimitive;
|
||||
/**
|
||||
* @private
|
||||
* Фрагменты
|
||||
*/
|
||||
alternativa3d var backFragment:PolyPrimitive;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d var frontFragment:PolyPrimitive;
|
||||
/**
|
||||
* @private
|
||||
* Рассечения
|
||||
*/
|
||||
alternativa3d var splitTime1:Number;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d var splitTime2:Number;
|
||||
/**
|
||||
* @private
|
||||
* BSP-нода, в которой находится примитив
|
||||
*/
|
||||
alternativa3d var node:BSPNode;
|
||||
/**
|
||||
* @private
|
||||
* Значения для расчёта качества сплиттера
|
||||
*/
|
||||
alternativa3d var splits:uint;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
alternativa3d var disbalance:int;
|
||||
/**
|
||||
* @private
|
||||
* Качество примитива как сплиттера (меньше - лучше)
|
||||
*/
|
||||
public var splitQuality:Number;
|
||||
/**
|
||||
* @private
|
||||
* Приоритет в BSP-дереве. Чем ниже мобильность, тем примитив выше в дереве.
|
||||
*/
|
||||
public var mobility:int;
|
||||
|
||||
// Хранилище неиспользуемых примитивов
|
||||
static private var collector:Array = new Array();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Создать примитив
|
||||
*/
|
||||
static alternativa3d function createPolyPrimitive():PolyPrimitive {
|
||||
// Достаём примитив из коллектора
|
||||
var primitive:PolyPrimitive = collector.pop();
|
||||
// Если коллектор пуст, создаём новый примитив
|
||||
if (primitive == null) {
|
||||
primitive = new PolyPrimitive();
|
||||
}
|
||||
//trace(primitive.num, primitive.points.length, primitive.face, primitive.parent, primitive.sibling, primitive.fragment1, primitive.fragment2, primitive.node);
|
||||
return primitive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Кладёт примитив в коллектор для последующего реиспользования.
|
||||
* Ссылка на грань и массивы точек зачищаются в этом методе.
|
||||
* Ссылки на фрагменты (parent, sibling, back, front) должны быть зачищены перед запуском метода.
|
||||
*
|
||||
* Исключение:
|
||||
* при сборке примитивов в сцене ссылки на back и front зачищаются после запуска метода.
|
||||
*
|
||||
* @param primitive примитив на реиспользование
|
||||
*/
|
||||
static alternativa3d function destroyPolyPrimitive(primitive:PolyPrimitive):void {
|
||||
primitive.face = null;
|
||||
for (var i:uint = 0; i < primitive.num; i++) {
|
||||
primitive.points.pop();
|
||||
primitive.uvs.pop();
|
||||
}
|
||||
collector.push(primitive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Строковое представление объекта.
|
||||
*/
|
||||
public function toString():String {
|
||||
return "[Primitive " + face._mesh._name + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
1256
Alternativa3D5/5.4/alternativa/engine3d/core/Scene3D.as
Normal file
1256
Alternativa3D5/5.4/alternativa/engine3d/core/Scene3D.as
Normal file
File diff suppressed because it is too large
Load Diff
350
Alternativa3D5/5.4/alternativa/engine3d/core/Surface.as
Normal file
350
Alternativa3D5/5.4/alternativa/engine3d/core/Surface.as
Normal file
@@ -0,0 +1,350 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.errors.FaceExistsError;
|
||||
import alternativa.engine3d.errors.FaceNotFoundError;
|
||||
import alternativa.engine3d.errors.InvalidIDError;
|
||||
import alternativa.engine3d.materials.SurfaceMaterial;
|
||||
import alternativa.types.Set;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Поверхность — набор граней, объединённых в группу. Поверхности используются для установки материалов,
|
||||
* визуализирующих грани объекта.
|
||||
*/
|
||||
public class Surface {
|
||||
// Операции
|
||||
/**
|
||||
* @private
|
||||
* Изменение набора граней
|
||||
*/
|
||||
alternativa3d var changeFacesOperation:Operation = new Operation("changeFaces", this);
|
||||
/**
|
||||
* @private
|
||||
* Изменение материала
|
||||
*/
|
||||
alternativa3d var changeMaterialOperation:Operation = new Operation("changeMaterial", this);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Меш
|
||||
*/
|
||||
alternativa3d var _mesh:Mesh;
|
||||
/**
|
||||
* @private
|
||||
* Материал
|
||||
*/
|
||||
alternativa3d var _material:SurfaceMaterial;
|
||||
/**
|
||||
* @private
|
||||
* Грани
|
||||
*/
|
||||
alternativa3d var _faces:Set = new Set();
|
||||
|
||||
/**
|
||||
* Создание экземпляра поверхности.
|
||||
*/
|
||||
public function Surface() {}
|
||||
|
||||
/**
|
||||
* Добавление грани в поверхность.
|
||||
*
|
||||
* @param face экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор грани полигонального объекта
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.FaceNotFoundError грань не найдена в полигональном объекте содержащем поверхность
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
* @throws alternativa.engine3d.errors.FaceExistsError поверхность уже содержит указанную грань
|
||||
*
|
||||
* @see Face
|
||||
*/
|
||||
public function addFace(face:Object):void {
|
||||
var byLink:Boolean = face is Face;
|
||||
|
||||
// Проверяем на нахождение поверхности в меше
|
||||
if (_mesh == null) {
|
||||
throw new FaceNotFoundError(face, this);
|
||||
}
|
||||
|
||||
// Проверяем на null
|
||||
if (face == null) {
|
||||
throw new FaceNotFoundError(null, this);
|
||||
}
|
||||
|
||||
// Проверяем наличие грани в меше
|
||||
if (byLink) {
|
||||
// Если удаляем по ссылке
|
||||
if (Face(face)._mesh != _mesh) {
|
||||
// Если грань не в меше
|
||||
throw new FaceNotFoundError(face, this);
|
||||
}
|
||||
} else {
|
||||
// Если удаляем по ID
|
||||
if (_mesh._faces[face] == undefined) {
|
||||
// Если нет грани с таким ID
|
||||
throw new FaceNotFoundError(face, this);
|
||||
} else {
|
||||
if (!(_mesh._faces[face] is Face)) {
|
||||
throw new InvalidIDError(face, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Находим грань
|
||||
var f:Face = byLink ? Face(face) : _mesh._faces[face];
|
||||
|
||||
// Проверяем наличие грани в поверхности
|
||||
if (_faces.has(f)) {
|
||||
// Если грань уже в поверхности
|
||||
throw new FaceExistsError(f, this);
|
||||
}
|
||||
|
||||
// Проверяем грань на нахождение в другой поверхности
|
||||
if (f._surface != null) {
|
||||
// Удаляем её из той поверхности
|
||||
f._surface._faces.remove(f);
|
||||
f.removeFromSurface(f._surface);
|
||||
}
|
||||
|
||||
// Добавляем грань в поверхность
|
||||
_faces.add(f);
|
||||
f.addToSurface(this);
|
||||
|
||||
// Отправляем операцию изменения набора граней
|
||||
_mesh.addOperationToScene(changeFacesOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление грани из поверхности.
|
||||
*
|
||||
* @param face экземпляр класса <code>alternativa.engine3d.core.Face</code> или идентификатор грани полигонального объекта
|
||||
*
|
||||
* @throws alternativa.engine3d.errors.FaceNotFoundError поверхность не содержит указанную грань
|
||||
* @throws alternativa.engine3d.errors.InvalidIDError указано недопустимое значение идентификатора
|
||||
*
|
||||
* @see Face
|
||||
*/
|
||||
public function removeFace(face:Object):void {
|
||||
var byLink:Boolean = face is Face;
|
||||
|
||||
// Проверяем на нахождение поверхности в меше
|
||||
if (_mesh == null) {
|
||||
throw new FaceNotFoundError(face, this);
|
||||
}
|
||||
|
||||
// Проверяем на null
|
||||
if (face == null) {
|
||||
throw new FaceNotFoundError(null, this);
|
||||
}
|
||||
|
||||
// Проверяем наличие грани в меше
|
||||
if (byLink) {
|
||||
// Если удаляем по ссылке
|
||||
if (Face(face)._mesh != _mesh) {
|
||||
// Если грань не в меше
|
||||
throw new FaceNotFoundError(face, this);
|
||||
}
|
||||
} else {
|
||||
// Если удаляем по ID
|
||||
if (_mesh._faces[face] == undefined) {
|
||||
// Если нет грани с таким ID
|
||||
throw new FaceNotFoundError(face, this);
|
||||
} else {
|
||||
if (!(_mesh._faces[face] is Face)) {
|
||||
throw new InvalidIDError(face, this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Находим грань
|
||||
var f:Face = byLink ? Face(face) : _mesh._faces[face];
|
||||
|
||||
// Проверяем наличие грани в поверхности
|
||||
if (!_faces.has(f)) {
|
||||
// Если грань не в поверхности
|
||||
throw new FaceNotFoundError(f, this);
|
||||
}
|
||||
|
||||
// Удаляем грань из поверхности
|
||||
_faces.remove(f);
|
||||
f.removeFromSurface(this);
|
||||
|
||||
// Отправляем операцию изменения набора граней
|
||||
_mesh.addOperationToScene(changeFacesOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Материал поверхности. При установке нового значения, устанавливаемый материал будет удалён из старой поверхности.
|
||||
*/
|
||||
public function get material():SurfaceMaterial {
|
||||
return _material;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set material(value:SurfaceMaterial):void {
|
||||
if (_material != value) {
|
||||
// Если был материал
|
||||
if (_material != null) {
|
||||
// Удалить материал из поверхности
|
||||
_material.removeFromSurface(this);
|
||||
// Удалить материал из меша
|
||||
if (_mesh != null) {
|
||||
_material.removeFromMesh(_mesh);
|
||||
// Удалить материал из сцены
|
||||
if (_mesh._scene != null) {
|
||||
_material.removeFromScene(_mesh._scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Если новый материал
|
||||
if (value != null) {
|
||||
// Если материал был в другой поверхности
|
||||
if (value._surface != null) {
|
||||
// Удалить его оттуда
|
||||
value._surface.material = null;
|
||||
}
|
||||
// Добавить материал в поверхность
|
||||
value.addToSurface(this);
|
||||
// Добавить материал в меш
|
||||
if (_mesh != null) {
|
||||
value.addToMesh(_mesh);
|
||||
// Добавить материал в сцену
|
||||
if (_mesh._scene != null) {
|
||||
value.addToScene(_mesh._scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Сохраняем материал
|
||||
_material = value;
|
||||
// Отправляем операцию изменения материала
|
||||
addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Набор граней поверхности.
|
||||
*/
|
||||
public function get faces():Set {
|
||||
return _faces.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Полигональный объект, которому принадлежит поверхность.
|
||||
*/
|
||||
public function get mesh():Mesh {
|
||||
return _mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Идентификатор поверхности в полигональном объекте. Если поверхность не принадлежит ни одному объекту,
|
||||
* значение идентификатора равно <code>null</code>.
|
||||
*/
|
||||
public function get id():Object {
|
||||
return (_mesh != null) ? _mesh.getSurfaceId(this) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Добавление в сцену.
|
||||
*
|
||||
* @param scene
|
||||
*/
|
||||
alternativa3d function addToScene(scene:Scene3D):void {
|
||||
// Добавляем на сцену материал
|
||||
if (_material != null) {
|
||||
_material.addToScene(scene);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление из сцены.
|
||||
*
|
||||
* @param scene
|
||||
*/
|
||||
alternativa3d function removeFromScene(scene:Scene3D):void {
|
||||
// Удаляем все операции из очереди
|
||||
scene.removeOperation(changeFacesOperation);
|
||||
scene.removeOperation(changeMaterialOperation);
|
||||
// Удаляем из сцены материал
|
||||
if (_material != null) {
|
||||
_material.removeFromScene(scene);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Добавление к мешу
|
||||
* @param mesh
|
||||
*/
|
||||
alternativa3d function addToMesh(mesh:Mesh):void {
|
||||
// Подписка на операции меша
|
||||
|
||||
// Добавляем в меш материал
|
||||
if (_material != null) {
|
||||
_material.addToMesh(mesh);
|
||||
}
|
||||
// Сохранить меш
|
||||
_mesh = mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление из меша
|
||||
*
|
||||
* @param mesh
|
||||
*/
|
||||
alternativa3d function removeFromMesh(mesh:Mesh):void {
|
||||
// Отписка от операций меша
|
||||
|
||||
// Удаляем из меша материал
|
||||
if (_material != null) {
|
||||
_material.removeFromMesh(mesh);
|
||||
}
|
||||
// Удалить ссылку на меш
|
||||
_mesh = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление граней
|
||||
*/
|
||||
alternativa3d function removeFaces():void {
|
||||
for (var key:* in _faces) {
|
||||
var face:Face = key;
|
||||
_faces.remove(face);
|
||||
face.removeFromSurface(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Изменение материала
|
||||
*/
|
||||
alternativa3d function addMaterialChangedOperationToScene():void {
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(changeMaterialOperation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Строковое представление объекта.
|
||||
*
|
||||
* @return строковое представление объекта
|
||||
*/
|
||||
public function toString():String {
|
||||
var length:uint = _faces.length;
|
||||
var res:String = "[Surface ID:" + id + ((length > 0) ? " faces:" : "");
|
||||
var i:uint = 0;
|
||||
for (var key:* in _faces) {
|
||||
var face:Face = key;
|
||||
res += face.id + ((i < length - 1) ? ", " : "");
|
||||
i++;
|
||||
}
|
||||
res += "]";
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
250
Alternativa3D5/5.4/alternativa/engine3d/core/Vertex.as
Normal file
250
Alternativa3D5/5.4/alternativa/engine3d/core/Vertex.as
Normal file
@@ -0,0 +1,250 @@
|
||||
package alternativa.engine3d.core {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.types.Matrix3D;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.types.Set;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Вершина полигона в трёхмерном пространстве. Вершина хранит свои координаты, а также ссылки на
|
||||
* полигональный объект и грани этого объекта, которым она принадлежит.
|
||||
*/
|
||||
final public class Vertex {
|
||||
// Операции
|
||||
/**
|
||||
* @private
|
||||
* Изменение локальных координат
|
||||
*/
|
||||
alternativa3d var changeCoordsOperation:Operation = new Operation("changeCoords", this);
|
||||
/**
|
||||
* @private
|
||||
* Расчёт глобальных координат
|
||||
*/
|
||||
alternativa3d var calculateCoordsOperation:Operation = new Operation("calculateCoords", this, calculateCoords, Operation.VERTEX_CALCULATE_COORDS);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Меш
|
||||
*/
|
||||
alternativa3d var _mesh:Mesh;
|
||||
/**
|
||||
* @private
|
||||
* Координаты точки
|
||||
*/
|
||||
alternativa3d var _coords:Point3D;
|
||||
/**
|
||||
* @private
|
||||
* Грани
|
||||
*/
|
||||
alternativa3d var _faces:Set = new Set();
|
||||
/**
|
||||
* @private
|
||||
* Координаты в сцене
|
||||
*/
|
||||
alternativa3d var globalCoords:Point3D = new Point3D();
|
||||
|
||||
/**
|
||||
* Создание экземпляра вершины.
|
||||
*
|
||||
* @param x координата вершины по оси X
|
||||
* @param y координата вершины по оси Y
|
||||
* @param z координата вершины по оси Z
|
||||
*/
|
||||
public function Vertex(x:Number = 0, y:Number = 0, z:Number = 0) {
|
||||
_coords = new Point3D(x, y, z);
|
||||
|
||||
// Изменение координат инициирует пересчёт глобальных координат
|
||||
changeCoordsOperation.addSequel(calculateCoordsOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Вызывается из операции calculateCoordsOperation для расчета глобальных координат вершины
|
||||
*/
|
||||
private function calculateCoords():void {
|
||||
globalCoords.copy(_coords);
|
||||
globalCoords.transform(_mesh.transformation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set x(value:Number):void {
|
||||
if (_coords.x != value) {
|
||||
_coords.x = value;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(changeCoordsOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set y(value:Number):void {
|
||||
if (_coords.y != value) {
|
||||
_coords.y = value;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(changeCoordsOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set z(value:Number):void {
|
||||
if (_coords.z != value) {
|
||||
_coords.z = value;
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(changeCoordsOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set coords(value:Point3D):void {
|
||||
if (!_coords.equals(value)) {
|
||||
_coords.copy(value);
|
||||
if (_mesh != null) {
|
||||
_mesh.addOperationToScene(changeCoordsOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* координата вершины по оси X.
|
||||
*/
|
||||
public function get x():Number {
|
||||
return _coords.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* координата вершины по оси Y.
|
||||
*/
|
||||
public function get y():Number {
|
||||
return _coords.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* координата вершины по оси Z.
|
||||
*/
|
||||
public function get z():Number {
|
||||
return _coords.z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Координаты вершины.
|
||||
*/
|
||||
public function get coords():Point3D {
|
||||
return _coords.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Полигональный объект, которому принадлежит вершина.
|
||||
*/
|
||||
public function get mesh():Mesh {
|
||||
return _mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Множество граней, которым принадлежит вершина. Каждый элемент множества является объектом класса
|
||||
* <code>altertnativa.engine3d.core.Face</code>.
|
||||
*
|
||||
* @see Face
|
||||
*/
|
||||
public function get faces():Set {
|
||||
return _faces.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Идентификатор вершины в полигональном объекте. Если вершина не принадлежит полигональному объекту, возвращается <code>null</code>.
|
||||
*/
|
||||
public function get id():Object {
|
||||
return (_mesh != null) ? _mesh.getVertexId(this) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param scene
|
||||
*/
|
||||
alternativa3d function addToScene(scene:Scene3D):void {
|
||||
// При добавлении на сцену расчитать глобальные координаты
|
||||
scene.addOperation(calculateCoordsOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param scene
|
||||
*/
|
||||
alternativa3d function removeFromScene(scene:Scene3D):void {
|
||||
// Удаляем все операции из очереди
|
||||
scene.removeOperation(calculateCoordsOperation);
|
||||
scene.removeOperation(changeCoordsOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param mesh
|
||||
*/
|
||||
alternativa3d function addToMesh(mesh:Mesh):void {
|
||||
// Подписка на операции меша
|
||||
mesh.changeCoordsOperation.addSequel(calculateCoordsOperation);
|
||||
mesh.changeRotationOrScaleOperation.addSequel(calculateCoordsOperation);
|
||||
// Сохранить меш
|
||||
_mesh = mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param mesh
|
||||
*/
|
||||
alternativa3d function removeFromMesh(mesh:Mesh):void {
|
||||
// Отписка от операций меша
|
||||
mesh.changeCoordsOperation.removeSequel(calculateCoordsOperation);
|
||||
mesh.changeRotationOrScaleOperation.removeSequel(calculateCoordsOperation);
|
||||
// Удалить зависимые грани
|
||||
for (var key:* in _faces) {
|
||||
var face:Face = key;
|
||||
mesh.removeFace(face);
|
||||
}
|
||||
// Удалить ссылку на меш
|
||||
_mesh = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param face
|
||||
*/
|
||||
alternativa3d function addToFace(face:Face):void {
|
||||
// Подписка грани на операции
|
||||
changeCoordsOperation.addSequel(face.calculateUVOperation);
|
||||
changeCoordsOperation.addSequel(face.calculateNormalOperation);
|
||||
// Добавить грань в список
|
||||
_faces.add(face);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param face
|
||||
*/
|
||||
alternativa3d function removeFromFace(face:Face):void {
|
||||
// Отписка грани от операций
|
||||
changeCoordsOperation.removeSequel(face.calculateUVOperation);
|
||||
changeCoordsOperation.removeSequel(face.calculateNormalOperation);
|
||||
// Удалить грань из списка
|
||||
_faces.remove(face);
|
||||
}
|
||||
|
||||
/**
|
||||
* Строковое представление объекта.
|
||||
*
|
||||
* @return строковое представление объекта
|
||||
*/
|
||||
public function toString():String {
|
||||
return "[Vertex ID:" + id + " " + _coords.x.toFixed(2) + ", " + _coords.y.toFixed(2) + ", " + _coords.z.toFixed(2) + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
66
Alternativa3D5/5.4/alternativa/engine3d/display/Skin.as
Normal file
66
Alternativa3D5/5.4/alternativa/engine3d/display/Skin.as
Normal file
@@ -0,0 +1,66 @@
|
||||
package alternativa.engine3d.display {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.PolyPrimitive;
|
||||
import alternativa.engine3d.materials.SurfaceMaterial;
|
||||
|
||||
import flash.display.Graphics;
|
||||
import flash.display.Sprite;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Контейнер, исползуемый материалами для отрисовки примитивов. Каждый примитив BSP-дерева рисуется в своём контейнере.
|
||||
*/
|
||||
public class Skin extends Sprite {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Графика скина (для быстрого доступа)
|
||||
*/
|
||||
alternativa3d var gfx:Graphics = graphics;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Ссылка на следующий скин
|
||||
*/
|
||||
alternativa3d var nextSkin:Skin;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Примитив
|
||||
*/
|
||||
alternativa3d var primitive:PolyPrimitive;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Материал, связанный со скином.
|
||||
*/
|
||||
alternativa3d var material:SurfaceMaterial;
|
||||
|
||||
// Хранилище неиспользуемых скинов
|
||||
static private var collector:Array = new Array();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Создание скина.
|
||||
*/
|
||||
static alternativa3d function createSkin():Skin {
|
||||
// Достаём скин из коллектора
|
||||
var skin:Skin = collector.pop();
|
||||
// Если коллектор пуст, создаём новый скин
|
||||
if (skin == null) {
|
||||
skin = new Skin();
|
||||
}
|
||||
return skin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Удаление скина, все ссылки должны быть почищены.
|
||||
*/
|
||||
static alternativa3d function destroySkin(skin:Skin):void {
|
||||
collector.push(skin);
|
||||
}
|
||||
}
|
||||
}
|
||||
186
Alternativa3D5/5.4/alternativa/engine3d/display/View.as
Normal file
186
Alternativa3D5/5.4/alternativa/engine3d/display/View.as
Normal file
@@ -0,0 +1,186 @@
|
||||
package alternativa.engine3d.display {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.Camera3D;
|
||||
import alternativa.engine3d.core.Face;
|
||||
|
||||
import flash.display.Sprite;
|
||||
import flash.geom.Point;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Область для вывода изображения с камеры.
|
||||
*/
|
||||
public class View extends Sprite {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Область отрисовки спрайтов
|
||||
*/
|
||||
alternativa3d var canvas:Sprite;
|
||||
|
||||
private var _camera:Camera3D;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Ширина области вывода
|
||||
*/
|
||||
alternativa3d var _width:Number;
|
||||
/**
|
||||
* @private
|
||||
* Высота области вывода
|
||||
*/
|
||||
alternativa3d var _height:Number;
|
||||
|
||||
/**
|
||||
* Создание экземпляра области вывода.
|
||||
*
|
||||
* @param camera камера, изображение с которой должно выводиться
|
||||
* @param width ширина области вывода
|
||||
* @param height высота области вывода
|
||||
*/
|
||||
public function View(camera:Camera3D = null, width:Number = 0, height:Number = 0) {
|
||||
canvas = new Sprite();
|
||||
canvas.mouseEnabled = false;
|
||||
canvas.mouseChildren = false;
|
||||
canvas.tabEnabled = false;
|
||||
canvas.tabChildren = false;
|
||||
addChild(canvas);
|
||||
|
||||
this.camera = camera;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Камера с которой ведётся отображение.
|
||||
*/
|
||||
public function get camera():Camera3D {
|
||||
return _camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set camera(value:Camera3D):void {
|
||||
if (_camera != value) {
|
||||
// Если была камера
|
||||
if (_camera != null) {
|
||||
// Удалить камеру
|
||||
_camera.removeFromView(this);
|
||||
}
|
||||
// Если новая камера
|
||||
if (value != null) {
|
||||
// Если камера была в другом вьюпорте
|
||||
if (value._view != null) {
|
||||
// Удалить её оттуда
|
||||
value._view.camera = null;
|
||||
}
|
||||
// Добавить камеру
|
||||
value.addToView(this);
|
||||
} else {
|
||||
// Зачистка скинов
|
||||
if (canvas.numChildren > 0) {
|
||||
var skin:Skin = Skin(canvas.getChildAt(0));
|
||||
while (skin != null) {
|
||||
// Сохраняем следующий
|
||||
var next:Skin = skin.nextSkin;
|
||||
// Удаляем из канваса
|
||||
canvas.removeChild(skin);
|
||||
// Очистка скина
|
||||
if (skin.material != null) {
|
||||
skin.material.clear(skin);
|
||||
}
|
||||
// Зачищаем ссылки
|
||||
skin.nextSkin = null;
|
||||
skin.primitive = null;
|
||||
skin.material = null;
|
||||
// Удаляем
|
||||
Skin.destroySkin(skin);
|
||||
// Следующий устанавливаем текущим
|
||||
skin = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Сохраняем камеру
|
||||
_camera = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ширина области вывода в пикселях.
|
||||
*/
|
||||
override public function get width():Number {
|
||||
return _width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
override public function set width(value:Number):void {
|
||||
if (_width != value) {
|
||||
_width = value;
|
||||
canvas.x = _width*0.5;
|
||||
if (_camera != null) {
|
||||
camera.addOperationToScene(camera.calculatePlanesOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Высота области вывода в пикселях.
|
||||
*/
|
||||
override public function get height():Number {
|
||||
return _height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
override public function set height(value:Number):void {
|
||||
if (_height != value) {
|
||||
_height = value;
|
||||
canvas.y = _height*0.5;
|
||||
if (_camera != null) {
|
||||
camera.addOperationToScene(camera.calculatePlanesOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод возвращает грань, находящуюся под указанной точкой в области вывода.
|
||||
*
|
||||
* @param viewPoint координаты точки относительно области вывода
|
||||
*
|
||||
* @return ближайшая к камере грань под заданной точкой области вывода
|
||||
*/
|
||||
public function getFaceUnderPoint(viewPoint:Point):Face {
|
||||
var p:Point = localToGlobal(viewPoint);
|
||||
var objects:Array = canvas.getObjectsUnderPoint(p);
|
||||
var skin:Skin = objects.pop() as Skin;
|
||||
if (skin != null) {
|
||||
return skin.primitive.face;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод возвращает грани, находящиеся под указанной точкой в области вывода.
|
||||
*
|
||||
* @param viewPoint координаты точки относительно области вывода
|
||||
*
|
||||
* @return массив граней, расположенных под заданной точкой области вывода. Первым элементом массива является самая дальняя грань.
|
||||
*/
|
||||
public function getFacesUnderPoint(viewPoint:Point):Array {
|
||||
var p:Point = localToGlobal(viewPoint);
|
||||
var objects:Array = canvas.getObjectsUnderPoint(p);
|
||||
var res:Array = new Array();
|
||||
var length:uint = objects.length;
|
||||
for (var i:uint = 0; i < length; i++) {
|
||||
var skin:Skin = objects[i];
|
||||
res.push(skin.primitive.face);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package alternativa.engine3d.errors {
|
||||
|
||||
import alternativa.utils.TextUtils;
|
||||
|
||||
/**
|
||||
* Базовый класс для ошибок 3d-engine.
|
||||
*/
|
||||
public class Engine3DError extends Error {
|
||||
|
||||
/**
|
||||
* Источник ошибки - объект в котором произошла ошибка.
|
||||
*/
|
||||
public var source:Object;
|
||||
|
||||
/**
|
||||
* Создание экземпляра класса.
|
||||
*
|
||||
* @param message описание ошибки
|
||||
* @param source источник ошибки
|
||||
*/
|
||||
public function Engine3DError(message:String = "", source:Object = null) {
|
||||
super(message);
|
||||
this.source = source;
|
||||
this.name = "Engine3DError";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
1055
Alternativa3D5/5.4/alternativa/engine3d/loaders/Loader3DS.as
Normal file
1055
Alternativa3D5/5.4/alternativa/engine3d/loaders/Loader3DS.as
Normal file
File diff suppressed because it is too large
Load Diff
285
Alternativa3D5/5.4/alternativa/engine3d/loaders/LoaderMTL.as
Normal file
285
Alternativa3D5/5.4/alternativa/engine3d/loaders/LoaderMTL.as
Normal file
@@ -0,0 +1,285 @@
|
||||
package alternativa.engine3d.loaders {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.types.Map;
|
||||
import alternativa.utils.ColorUtils;
|
||||
|
||||
import flash.display.Bitmap;
|
||||
import flash.display.BitmapData;
|
||||
import flash.display.Loader;
|
||||
import flash.events.ErrorEvent;
|
||||
import flash.events.Event;
|
||||
import flash.events.EventDispatcher;
|
||||
import flash.events.IOErrorEvent;
|
||||
import flash.events.SecurityErrorEvent;
|
||||
import flash.geom.Point;
|
||||
import flash.net.URLLoader;
|
||||
import flash.net.URLRequest;
|
||||
import flash.system.LoaderContext;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Загрузчик библиотеки материалов из файлов в формате MTL material format (Lightwave, OBJ).
|
||||
* <p>
|
||||
* На данный момент обеспечивается загрузка цвета, прозрачности и диффузной текстуры материала.
|
||||
*/
|
||||
internal class LoaderMTL extends EventDispatcher {
|
||||
|
||||
private static const COMMENT_CHAR:String = "#";
|
||||
private static const CMD_NEW_MATERIAL:String = "newmtl";
|
||||
private static const CMD_DIFFUSE_REFLECTIVITY:String = "Kd";
|
||||
private static const CMD_DISSOLVE:String = "d";
|
||||
private static const CMD_MAP_DIFFUSE:String = "map_Kd";
|
||||
|
||||
private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/;
|
||||
private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/;
|
||||
private static const REGEXP_SPLIT_LINE:RegExp = /\s+/;
|
||||
|
||||
// Загрузчик файла MTL
|
||||
private var fileLoader:URLLoader;
|
||||
// Загрузчик файлов текстур
|
||||
private var bitmapLoader:Loader;
|
||||
// Контекст загрузки для bitmapLoader
|
||||
private var loaderContext:LoaderContext;
|
||||
// Базовый URL файла MTL
|
||||
private var baseUrl:String;
|
||||
|
||||
// Библиотека загруженных материалов
|
||||
private var _library:Map;
|
||||
// Список материалов, имеющих диффузные текстуры
|
||||
private var diffuseMaps:Map;
|
||||
// Имя текущего материала
|
||||
private var materialName:String;
|
||||
// параметры текущего материала
|
||||
private var currentMaterialInfo:MaterialInfo = new MaterialInfo();
|
||||
|
||||
alternativa3d static var stubBitmapData:BitmapData;
|
||||
|
||||
/**
|
||||
* Создаёт новый экземпляр класса.
|
||||
*/
|
||||
public function LoaderMTL() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Прекращение текущей загрузки.
|
||||
*/
|
||||
public function close():void {
|
||||
try {
|
||||
fileLoader.close();
|
||||
} catch (e:Error) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Библиотека материалов. Ключами являются наименования материалов, значениями -- объекты, наследники класса
|
||||
* <code>alternativa.engine3d.loaders.MaterialInfo</code>.
|
||||
* @see alternativa.engine3d.loaders.MaterialInfo
|
||||
*/
|
||||
public function get library():Map {
|
||||
return _library;
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод выполняет загрузку файла материалов, разбор его содержимого, загрузку текстур при необходимости и
|
||||
* формирование библиотеки материалов. После окончания работы метода посылается сообщение
|
||||
* <code>Event.COMPLETE</code> и становится доступна библиотека материалов через свойство <code>library</code>.
|
||||
* <p>
|
||||
* При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения <code>IOErrorEvent.IO_ERROR</code> и
|
||||
* <code>SecurityErrorEvent.SECURITY_ERROR</code> соответственно.
|
||||
* <p>
|
||||
* Если происходит ошибка при загрузке файла текстуры, то соответствующая текстура заменяется на текстуру-заглушку.
|
||||
* <p>
|
||||
* @param url URL MTL-файла
|
||||
* @param loaderContext LoaderContext для загрузки файлов текстур
|
||||
*
|
||||
* @see #library
|
||||
*/
|
||||
public function load(url:String, loaderContext:LoaderContext = null):void {
|
||||
this.loaderContext = loaderContext;
|
||||
baseUrl = url.substring(0, url.lastIndexOf("/") + 1);
|
||||
|
||||
if (fileLoader == null) {
|
||||
fileLoader = new URLLoader();
|
||||
fileLoader.addEventListener(Event.COMPLETE, parseMTLFile);
|
||||
fileLoader.addEventListener(IOErrorEvent.IO_ERROR, onError);
|
||||
fileLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
|
||||
|
||||
bitmapLoader = new Loader();
|
||||
bitmapLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onBitmapLoadComplete);
|
||||
bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onBitmapLoadComplete);
|
||||
bitmapLoader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onBitmapLoadComplete);
|
||||
}
|
||||
|
||||
try {
|
||||
fileLoader.close();
|
||||
bitmapLoader.close();
|
||||
} catch (e:Error) {
|
||||
// Пропуск ошибки при попытке закрытия неактивных загрузчиков
|
||||
}
|
||||
|
||||
fileLoader.load(new URLRequest(url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Разбор содержимого загруженного файла материалов.
|
||||
*/
|
||||
private function parseMTLFile(e:Event = null):void {
|
||||
var lines:Array = fileLoader.data.split(REGEXP_SPLIT_FILE);
|
||||
_library = new Map();
|
||||
diffuseMaps = new Map();
|
||||
for each (var line:String in lines) {
|
||||
parseLine(line);
|
||||
}
|
||||
defineMaterial();
|
||||
|
||||
if (diffuseMaps.isEmpty()) {
|
||||
// Текстур нет, загрузка окончена
|
||||
dispatchEvent(new Event(Event.COMPLETE));
|
||||
} else {
|
||||
// Загрузка файлов текстур
|
||||
loadNextBitmap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Разбор строки файла.
|
||||
*
|
||||
* @param line строка файла
|
||||
*/
|
||||
private function parseLine(line:String):void {
|
||||
line = line.replace(REGEXP_TRIM,"$1")
|
||||
if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) {
|
||||
return;
|
||||
}
|
||||
var parts:Array = line.split(REGEXP_SPLIT_LINE);
|
||||
switch (parts[0]) {
|
||||
case CMD_NEW_MATERIAL:
|
||||
defineMaterial(parts);
|
||||
break;
|
||||
case CMD_DIFFUSE_REFLECTIVITY:
|
||||
readDiffuseReflectivity(parts);
|
||||
break;
|
||||
case CMD_DISSOLVE:
|
||||
readAlpha(parts);
|
||||
break;
|
||||
case CMD_MAP_DIFFUSE:
|
||||
parseDiffuseMapLine(parts);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Определение нового материала.
|
||||
*/
|
||||
private function defineMaterial(parts:Array = null):void {
|
||||
if (materialName != null) {
|
||||
_library[materialName] = currentMaterialInfo;
|
||||
}
|
||||
if (parts != null) {
|
||||
materialName = parts[1];
|
||||
currentMaterialInfo = new MaterialInfo();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Чтение коэффициентов диффузного отражения. Считываются только коэффициенты, заданные в формате r g b. Для текущей
|
||||
* версии движка данные коэффициенты преобразуются в цвет материала.
|
||||
*/
|
||||
private function readDiffuseReflectivity(parts:Array):void {
|
||||
var r:Number = Number(parts[1]);
|
||||
// Проверка, заданы ли коэффициенты в виде r g b
|
||||
if (!isNaN(r)) {
|
||||
var g:Number = Number(parts[2]);
|
||||
var b:Number = Number(parts[3]);
|
||||
currentMaterialInfo.color = ColorUtils.rgb(255 * r, 255 * g, 255 * b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Чтение коэффициента непрозрачности. Считывается только коэффициент, заданный числом
|
||||
* (не поддерживается параметр -halo).
|
||||
*/
|
||||
private function readAlpha(parts:Array):void {
|
||||
var alpha:Number = Number(parts[1]);
|
||||
if (!isNaN(alpha)) {
|
||||
currentMaterialInfo.alpha = alpha;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Разбор строки, задающей текстурную карту для диффузного отражения.
|
||||
*/
|
||||
private function parseDiffuseMapLine(parts:Array):void {
|
||||
var info:MTLTextureMapInfo = MTLTextureMapInfo.parse(parts);
|
||||
diffuseMaps[materialName] = info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузка файла следующей текстуры.
|
||||
*/
|
||||
private function loadNextBitmap():void {
|
||||
// Установка имени текущего текстурного материала, для которого выполняется загрузка текстуры
|
||||
for (materialName in diffuseMaps) {
|
||||
break;
|
||||
}
|
||||
bitmapLoader.load(new URLRequest(baseUrl + diffuseMaps[materialName].fileName), loaderContext);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function createStubBitmap():void {
|
||||
if (stubBitmapData == null) {
|
||||
var size:uint = 10;
|
||||
stubBitmapData = new BitmapData(size, size, false, 0);
|
||||
for (var i:uint = 0; i < size; i++) {
|
||||
for (var j:uint = 0; j < size; j+=2) {
|
||||
stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка результата загрузки файла текстуры.
|
||||
*/
|
||||
private function onBitmapLoadComplete(e:Event):void {
|
||||
var bmd:BitmapData;
|
||||
|
||||
if (e is ErrorEvent) {
|
||||
if (stubBitmapData == null) {
|
||||
createStubBitmap();
|
||||
}
|
||||
bmd = stubBitmapData;
|
||||
} else {
|
||||
bmd = Bitmap(bitmapLoader.content).bitmapData;
|
||||
}
|
||||
|
||||
var mtlInfo:MTLTextureMapInfo = diffuseMaps[materialName];
|
||||
delete diffuseMaps[materialName];
|
||||
var info:MaterialInfo = _library[materialName];
|
||||
|
||||
info.bitmapData = bmd;
|
||||
info.repeat = mtlInfo.repeat;
|
||||
info.mapOffset = new Point(mtlInfo.offsetU, mtlInfo.offsetV);
|
||||
info.mapSize = new Point(mtlInfo.sizeU, mtlInfo.sizeV);
|
||||
info.textureFileName = mtlInfo.fileName;
|
||||
|
||||
if (diffuseMaps.isEmpty()) {
|
||||
dispatchEvent(new Event(Event.COMPLETE));
|
||||
} else {
|
||||
loadNextBitmap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param e
|
||||
*/
|
||||
private function onError(e:IOErrorEvent):void {
|
||||
dispatchEvent(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
509
Alternativa3D5/5.4/alternativa/engine3d/loaders/LoaderOBJ.as
Normal file
509
Alternativa3D5/5.4/alternativa/engine3d/loaders/LoaderOBJ.as
Normal file
@@ -0,0 +1,509 @@
|
||||
package alternativa.engine3d.loaders {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.Face;
|
||||
import alternativa.engine3d.core.Mesh;
|
||||
import alternativa.engine3d.core.Object3D;
|
||||
import alternativa.engine3d.core.Surface;
|
||||
import alternativa.engine3d.core.Vertex;
|
||||
import alternativa.engine3d.materials.FillMaterial;
|
||||
import alternativa.engine3d.materials.TextureMaterial;
|
||||
import alternativa.engine3d.materials.TextureMaterialPrecision;
|
||||
import alternativa.types.Map;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.types.Texture;
|
||||
|
||||
import flash.display.BlendMode;
|
||||
import flash.events.ErrorEvent;
|
||||
import flash.events.Event;
|
||||
import flash.events.EventDispatcher;
|
||||
import flash.events.IOErrorEvent;
|
||||
import flash.events.SecurityErrorEvent;
|
||||
import flash.geom.Point;
|
||||
import flash.net.URLLoader;
|
||||
import flash.net.URLRequest;
|
||||
import flash.system.LoaderContext;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Загрузчик моделей из файла в формате OBJ. Так как OBJ не поддерживает иерархию объектов, все загруженные
|
||||
* модели помещаются в один контейнер <code>Object3D</code>.
|
||||
* <p>
|
||||
* Поддерживаюся следующие команды формата OBJ:
|
||||
* <p>
|
||||
* <table border="1" style="border-collapse: collapse">
|
||||
* <tr>
|
||||
* <th width="30%">Команда</th>
|
||||
* <th>Описание</th>
|
||||
* <th>Действие</th></tr>
|
||||
* <tr>
|
||||
* <td>o object_name</td>
|
||||
* <td>Объявление нового объекта с именем object_name</td>
|
||||
* <td>Если для текущего объекта были определены грани, то команда создаёт новый текущий объект с указанным именем,
|
||||
* иначе у текущего объекта просто меняется имя на указанное.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>v x y z</td>
|
||||
* <td>Объявление вершины с координатами x y z</td>
|
||||
* <td>Вершина помещается в общий список вершин сцены для дальнейшего использования</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>vt u [v]</td>
|
||||
* <td>Объявление текстурной вершины с координатами u v</td>
|
||||
* <td>Вершина помещается в общий список текстурных вершин сцены для дальнейшего использования</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>f v0[/vt0] v1[/vt1] ... vN[/vtN]</td>
|
||||
* <td>Объявление грани, состоящей из указанных вершин и опционально имеющую заданные текстурные координаты для вершин.</td>
|
||||
* <td>Грань добавляется к текущему активному объекту. Если есть активный материал, то грань также добавляется в поверхность
|
||||
* текущего объекта, соответствующую текущему материалу.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>usemtl material_name</td>
|
||||
* <td>Установка текущего материала с именем material_name</td>
|
||||
* <td>С момента установки текущего материала все грани, создаваемые в текущем объекте будут помещаться в поверхность,
|
||||
* соотвествующую этому материалу и имеющую идентификатор, совпадающий с его именем.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>mtllib file1 file2 ...</td>
|
||||
* <td>Объявление файлов, содержащих определения материалов</td>
|
||||
* <td>Выполняется загрузка файлов и формирование библиотеки материалов</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* <p>
|
||||
* Пример использования:
|
||||
* <pre>
|
||||
* var loader:LoaderOBJ = new LoaderOBJ();
|
||||
* loader.addEventListener(Event.COMPLETE, onLoadingComplete);
|
||||
* loader.load("foo.obj");
|
||||
*
|
||||
* function onLoadingComplete(e:Event):void {
|
||||
* scene.root.addChild(e.target.content);
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class LoaderOBJ extends EventDispatcher {
|
||||
|
||||
private static const COMMENT_CHAR:String = "#";
|
||||
|
||||
private static const CMD_OBJECT_NAME:String = "o";
|
||||
private static const CMD_GROUP_NAME:String = "g";
|
||||
private static const CMD_VERTEX:String = "v";
|
||||
private static const CMD_TEXTURE_VERTEX:String = "vt";
|
||||
private static const CMD_FACE:String = "f";
|
||||
private static const CMD_MATERIAL_LIB:String = "mtllib";
|
||||
private static const CMD_USE_MATERIAL:String = "usemtl";
|
||||
|
||||
private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/;
|
||||
private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/;
|
||||
private static const REGEXP_SPLIT_LINE:RegExp = /\s+/;
|
||||
|
||||
private var basePath:String;
|
||||
private var objLoader:URLLoader;
|
||||
private var mtlLoader:LoaderMTL;
|
||||
private var loaderContext:LoaderContext;
|
||||
private var loadMaterials:Boolean;
|
||||
// Объект, содержащий все определённые в obj файле объекты
|
||||
private var _content:Object3D;
|
||||
// Текущий конструируемый объект
|
||||
private var currentObject:Mesh;
|
||||
// Стартовый индекс вершины в глобальном массиве вершин для текущего объекта
|
||||
private var vIndexStart:int = 0;
|
||||
// Стартовый индекс текстурной вершины в глобальном массиве текстурных вершин для текущего объекта
|
||||
private var vtIndexStart:int = 0;
|
||||
// Глобальный массив вершин, определённых во входном файле
|
||||
private var globalVertices:Array;
|
||||
// Глобальный массив текстурных вершин, определённых во входном файле
|
||||
private var globalTextureVertices:Array;
|
||||
// Имя текущего активного материала. Если значение равно null, то активного материала нет.
|
||||
private var currentMaterialName:String;
|
||||
// Массив граней текущего объекта, которым назначен текущий материал
|
||||
private var materialFaces:Array;
|
||||
// Массив имён файлов, содержащих определения материалов
|
||||
private var materialFileNames:Array;
|
||||
private var currentMaterialFileIndex:int;
|
||||
private var materialLibrary:Map;
|
||||
|
||||
/**
|
||||
* Сглаживание текстур при увеличении масштаба.
|
||||
*
|
||||
* @see alternativa.engine3d.materials.TextureMaterial
|
||||
*/
|
||||
public var smooth:Boolean = false;
|
||||
/**
|
||||
* Режим наложения цвета для создаваемых текстурных материалов.
|
||||
*
|
||||
* @see alternativa.engine3d.materials.TextureMaterial
|
||||
*/
|
||||
public var blendMode:String = BlendMode.NORMAL;
|
||||
/**
|
||||
* Точность перспективной коррекции для создаваемых текстурных материалов.
|
||||
*
|
||||
* @see alternativa.engine3d.materials.TextureMaterial
|
||||
*/
|
||||
public var precision:Number = TextureMaterialPrecision.MEDIUM;
|
||||
|
||||
/**
|
||||
* Устанавливаемый уровень мобильности загруженных объектов.
|
||||
*/
|
||||
public var mobility:int = 0;
|
||||
|
||||
/**
|
||||
* При установленном значении <code>true</code> выполняется преобразование координат геометрических вершин посредством
|
||||
* поворота на 90 градусов относительно оси X. Смысл флага в преобразовании системы координат, в которой вверх направлена
|
||||
* ось <code>Y</code>, в систему координат, использующуюся в Alternativa3D (вверх направлена ось <code>Z</code>).
|
||||
*/
|
||||
public var rotateModel:Boolean;
|
||||
|
||||
/**
|
||||
* Создаёт новый экземпляр загрузчика.
|
||||
*/
|
||||
public function LoaderOBJ() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Контейнер, содержащий все загруженные из OBJ-файла модели.
|
||||
*/
|
||||
public function get content():Object3D {
|
||||
return _content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Прекращение текущей загрузки.
|
||||
*/
|
||||
public function close():void {
|
||||
try {
|
||||
objLoader.close();
|
||||
} catch (e:Error) {
|
||||
}
|
||||
mtlLoader.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузка сцены из OBJ-файла по указанному адресу. По окончании загрузки посылается сообщение <code>Event.COMPLETE</code>,
|
||||
* после чего контейнер с загруженными объектами становится доступным через свойство <code>content</code>.
|
||||
* <p>
|
||||
* При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения <code>IOErrorEvent.IO_ERROR</code> и
|
||||
* <code>SecurityErrorEvent.SECURITY_ERROR</code> соответственно.
|
||||
* <p>
|
||||
* @param url URL OBJ-файла
|
||||
* @param loadMaterials флаг загрузки материалов. Если указано значение <code>true</code>, будут обработаны все файлы
|
||||
* материалов, указанные в исходном OBJ-файле.
|
||||
* @param context LoaderContext для загрузки файлов текстур
|
||||
*
|
||||
* @see #content
|
||||
*/
|
||||
public function load(url:String, loadMaterials:Boolean = true, context:LoaderContext = null):void {
|
||||
_content = null;
|
||||
this.loadMaterials = loadMaterials;
|
||||
this.loaderContext = context;
|
||||
basePath = url.substring(0, url.lastIndexOf("/") + 1);
|
||||
if (objLoader == null) {
|
||||
objLoader = new URLLoader();
|
||||
objLoader.addEventListener(Event.COMPLETE, onObjLoadComplete);
|
||||
objLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError);
|
||||
objLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError);
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
objLoader.load(new URLRequest(url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка окончания загрузки obj файла.
|
||||
*
|
||||
* @param e
|
||||
*/
|
||||
private function onObjLoadComplete(e:Event):void {
|
||||
parse(objLoader.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка ошибки при загрузке.
|
||||
*
|
||||
* @param e
|
||||
*/
|
||||
private function onObjLoadError(e:ErrorEvent):void {
|
||||
dispatchEvent(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод выполняет разбор данных, полученных из obj файла.
|
||||
*
|
||||
* @param s содержимое obj файла
|
||||
* @param materialLibrary библиотека материалов
|
||||
* @return объект, содержащий все трёхмерные объекты, определённые в obj файле
|
||||
*/
|
||||
private function parse(data:String):void {
|
||||
_content = new Object3D();
|
||||
currentObject = new Mesh();
|
||||
currentObject.mobility = mobility;
|
||||
_content.addChild(currentObject);
|
||||
|
||||
globalVertices = new Array();
|
||||
globalTextureVertices = new Array();
|
||||
materialFileNames = new Array();
|
||||
|
||||
var lines:Array = data.split(REGEXP_SPLIT_FILE);
|
||||
for each (var line:String in lines) {
|
||||
parseLine(line);
|
||||
}
|
||||
moveFacesToSurface();
|
||||
// Вся геометрия загружена и сформирована. Выполняется загрузка информации о материалах.
|
||||
if (loadMaterials && materialFileNames.length > 0) {
|
||||
loadMaterialsLibrary();
|
||||
} else {
|
||||
dispatchEvent(new Event(Event.COMPLETE));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function parseLine(line:String):void {
|
||||
line = line.replace(REGEXP_TRIM,"$1");
|
||||
if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) {
|
||||
return;
|
||||
}
|
||||
var parts:Array = line.split(REGEXP_SPLIT_LINE);
|
||||
switch (parts[0]) {
|
||||
// Объявление нового объекта
|
||||
case CMD_OBJECT_NAME:
|
||||
defineObject(parts[1]);
|
||||
break;
|
||||
// Объявление вершины
|
||||
case CMD_VERTEX:
|
||||
globalVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3])));
|
||||
break;
|
||||
// Объявление текстурной вершины
|
||||
case CMD_TEXTURE_VERTEX:
|
||||
globalTextureVertices.push(new Point3D(Number(parts[1]), Number(parts[2]), Number(parts[3])));
|
||||
break;
|
||||
// Объявление грани
|
||||
case CMD_FACE:
|
||||
createFace(parts);
|
||||
break;
|
||||
case CMD_MATERIAL_LIB:
|
||||
storeMaterialFileNames(parts);
|
||||
break;
|
||||
case CMD_USE_MATERIAL:
|
||||
setNewMaterial(parts);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Объявление нового объекта.
|
||||
*
|
||||
* @param objectName имя объекта
|
||||
*/
|
||||
private function defineObject(objectName:String):void {
|
||||
if (currentObject.faces.length == 0) {
|
||||
// Если у текущего объекта нет граней, то он остаётся текущим, но меняется имя
|
||||
currentObject.name = objectName;
|
||||
} else {
|
||||
// Если у текущего объекта есть грани, то обявление нового имени создаёт новый объект
|
||||
moveFacesToSurface();
|
||||
currentObject = new Mesh(objectName);
|
||||
currentObject.mobility = mobility;
|
||||
_content.addChild(currentObject);
|
||||
}
|
||||
vIndexStart = globalVertices.length;
|
||||
vtIndexStart = globalTextureVertices.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создание грани в текущем объекте.
|
||||
*
|
||||
* @param parts массив, содержащий индексы вершин грани, начиная с элемента с индексом 1
|
||||
*/
|
||||
private function createFace(parts:Array):void {
|
||||
// Стартовый индекс вершины в объекте для добавляемой грани
|
||||
var startVertexIndex:int = currentObject.vertices.length;
|
||||
// Создание вершин в объекте
|
||||
var faceVertexCount:int = parts.length - 1;
|
||||
var vtIndices:Array = new Array(3);
|
||||
// Массив идентификаторов вершин грани
|
||||
var faceVertices:Array = new Array(faceVertexCount);
|
||||
for (var i:int = 0; i < faceVertexCount; i++) {
|
||||
var indices:Array = parts[i + 1].split("/");
|
||||
// Создание вершины
|
||||
var vIdx:int = int(indices[0]);
|
||||
// Если индекс положительный, то его значение уменьшается на единицу, т.к. в obj формате индексация начинается с 1.
|
||||
// Если индекс отрицательный, то выполняется смещение на его значение назад от стартового глобального индекса вершин для текущего объекта.
|
||||
var actualIndex:int = vIdx > 0 ? vIdx - 1 : vIndexStart + vIdx;
|
||||
|
||||
var vertex:Vertex = currentObject.vertices[actualIndex];
|
||||
// Если вершины нет в объекте, она добавляется
|
||||
if (vertex == null) {
|
||||
var p:Point3D = globalVertices[actualIndex];
|
||||
if (rotateModel) {
|
||||
// В формате obj направление "вверх" совпадает с осью Y, поэтому выполняется поворот координат на 90 градусов по оси X
|
||||
vertex = currentObject.createVertex(p.x, -p.z, p.y, actualIndex);
|
||||
} else {
|
||||
vertex = currentObject.createVertex(p.x, p.y, p.z, actualIndex);
|
||||
}
|
||||
}
|
||||
faceVertices[i] = vertex;
|
||||
|
||||
// Запись индекса текстурной вершины
|
||||
if (i < 3) {
|
||||
vtIndices[i] = int(indices[1]);
|
||||
}
|
||||
}
|
||||
// Создание грани
|
||||
var face:Face = currentObject.createFace(faceVertices, currentObject.faces.length);
|
||||
// Установка uv координат
|
||||
if (vtIndices[0] != 0) {
|
||||
p = globalTextureVertices[vtIndices[0] - 1];
|
||||
face.aUV = new Point(p.x, p.y);
|
||||
p = globalTextureVertices[vtIndices[1] - 1];
|
||||
face.bUV = new Point(p.x, p.y);
|
||||
p = globalTextureVertices[vtIndices[2] - 1];
|
||||
face.cUV = new Point(p.x, p.y);
|
||||
}
|
||||
// Если есть активный материал, то грань заносится в массив для последующего формирования поверхности в объекте
|
||||
if (currentMaterialName != null) {
|
||||
materialFaces.push(face);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузка библиотек материалов.
|
||||
*
|
||||
* @param parts массив, содержащий имена файлов материалов, начиная с элемента с индексом 1
|
||||
*/
|
||||
private function storeMaterialFileNames(parts:Array):void {
|
||||
for (var i:int = 1; i < parts.length; i++) {
|
||||
materialFileNames.push(parts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка нового текущего материала.
|
||||
*
|
||||
* @param parts массив, во втором элементе которого содержится имя материала
|
||||
*/
|
||||
private function setNewMaterial(parts:Array):void {
|
||||
// Все сохранённые грани добавляются в соответствующую поверхность текущего объекта
|
||||
moveFacesToSurface();
|
||||
// Установка нового текущего материала
|
||||
currentMaterialName = parts[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление всех граней с текущим материалом в поверхность с идентификатором, совпадающим с именем материала.
|
||||
*/
|
||||
private function moveFacesToSurface():void {
|
||||
if (currentMaterialName != null && materialFaces.length > 0) {
|
||||
if (currentObject.hasSurface(currentMaterialName)) {
|
||||
// При наличии поверхности с таким идентификатором, грани добавляются в неё
|
||||
var surface:Surface = currentObject.getSurfaceById(currentMaterialName);
|
||||
for each (var face:* in materialFaces) {
|
||||
surface.addFace(face);
|
||||
}
|
||||
} else {
|
||||
// При отсутствии поверхности с таким идентификатором, создатся новая поверхность
|
||||
currentObject.createSurface(materialFaces, currentMaterialName);
|
||||
}
|
||||
}
|
||||
materialFaces = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузка материалов.
|
||||
*/
|
||||
private function loadMaterialsLibrary():void {
|
||||
if (mtlLoader == null) {
|
||||
mtlLoader = new LoaderMTL();
|
||||
mtlLoader.addEventListener(Event.COMPLETE, onMaterialFileLoadComplete);
|
||||
mtlLoader.addEventListener(IOErrorEvent.IO_ERROR, onObjLoadError);
|
||||
mtlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onObjLoadError);
|
||||
}
|
||||
materialLibrary = new Map();
|
||||
|
||||
currentMaterialFileIndex = -1;
|
||||
loadNextMaterialFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка успешной загрузки библиотеки материалов.
|
||||
*/
|
||||
private function onMaterialFileLoadComplete(e:Event):void {
|
||||
materialLibrary.concat(mtlLoader.library);
|
||||
// Загрузка следующего файла материалов
|
||||
loadNextMaterialFile();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function loadNextMaterialFile():void {
|
||||
currentMaterialFileIndex++;
|
||||
if (currentMaterialFileIndex == materialFileNames.length) {
|
||||
setMaterials();
|
||||
dispatchEvent(new Event(Event.COMPLETE));
|
||||
} else {
|
||||
mtlLoader.load(basePath + materialFileNames[currentMaterialFileIndex], loaderContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка материалов.
|
||||
*/
|
||||
private function setMaterials():void {
|
||||
if (materialLibrary != null) {
|
||||
for (var objectKey:* in _content.children) {
|
||||
var object:Mesh = objectKey;
|
||||
for (var surfaceKey:* in object.surfaces) {
|
||||
var surface:Surface = object.surfaces[surfaceKey];
|
||||
// Поверхности имеют идентификаторы, соответствующие именам материалов
|
||||
var materialInfo:MaterialInfo = materialLibrary[surfaceKey];
|
||||
if (materialInfo != null) {
|
||||
if (materialInfo.bitmapData == null) {
|
||||
surface.material = new FillMaterial(materialInfo.color, materialInfo.alpha, blendMode);
|
||||
} else {
|
||||
surface.material = new TextureMaterial(new Texture(materialInfo.bitmapData, materialInfo.textureFileName), materialInfo.alpha, materialInfo.repeat, (materialInfo.bitmapData != LoaderMTL.stubBitmapData) ? smooth : false, blendMode, -1, 0, precision);
|
||||
transformUVs(surface, materialInfo.mapOffset, materialInfo.mapSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод выполняет преобразование UV-координат текстурированных граней. В связи с тем, что в формате MRL предусмотрено
|
||||
* масштабирование и смещение текстурной карты в UV-пространстве, а в движке такой фунциональности нет, необходимо
|
||||
* эмулировать преобразования текстуры преобразованием UV-координат граней. Преобразования выполняются исходя из предположения,
|
||||
* что текстурное пространство сначала масштабируется относительно центра, а затем сдвигается на указанную величину
|
||||
* смещения.
|
||||
*
|
||||
* @param surface поверхность, грани которой обрабатываюся
|
||||
* @param mapOffset смещение текстурной карты. Значение mapOffset.x указывает смещение по U, значение mapOffset.y
|
||||
* указывает смещение по V.
|
||||
* @param mapSize коэффициенты масштабирования текстурной карты. Значение mapSize.x указывает коэффициент масштабирования
|
||||
* по оси U, значение mapSize.y указывает коэффициент масштабирования по оси V.
|
||||
*/
|
||||
private function transformUVs(surface:Surface, mapOffset:Point, mapSize:Point):void {
|
||||
for (var key:* in surface.faces) {
|
||||
var face:Face = key;
|
||||
var uv:Point = face.aUV;
|
||||
if (uv != null) {
|
||||
uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
|
||||
uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
|
||||
face.aUV = uv;
|
||||
uv = face.bUV;
|
||||
uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
|
||||
uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
|
||||
face.bUV = uv;
|
||||
uv = face.cUV;
|
||||
uv.x = 0.5 + (uv.x - 0.5 - mapOffset.x) * mapSize.x;
|
||||
uv.y = 0.5 + (uv.y - 0.5 - mapOffset.y) * mapSize.y;
|
||||
face.cUV = uv;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package alternativa.engine3d.loaders {
|
||||
/**
|
||||
* @private
|
||||
* Класс содержит информацию о текстуре в формате MTL material format (Lightwave, OBJ) и функционал для разбора
|
||||
* описания текстуры.
|
||||
* Описание формата можно посмотреть по адресу: http://local.wasp.uwa.edu.au/~pbourke/dataformats/mtl/
|
||||
*/
|
||||
internal class MTLTextureMapInfo {
|
||||
|
||||
// Ассоциация параметров команды объявления текстуры и методов для их чтения
|
||||
private static const optionReaders:Object = {
|
||||
"-clamp": clampReader,
|
||||
"-o": offsetReader,
|
||||
"-s": sizeReader,
|
||||
|
||||
"-blendu": stubReader,
|
||||
"-blendv": stubReader,
|
||||
"-bm": stubReader,
|
||||
"-boost": stubReader,
|
||||
"-cc": stubReader,
|
||||
"-imfchan": stubReader,
|
||||
"-mm": stubReader,
|
||||
"-t": stubReader,
|
||||
"-texres": stubReader
|
||||
};
|
||||
|
||||
// Смещение в текстурном пространстве
|
||||
public var offsetU:Number = 0;
|
||||
public var offsetV:Number = 0;
|
||||
public var offsetW:Number = 0;
|
||||
|
||||
// Масштабирование текстурного пространства
|
||||
public var sizeU:Number = 1;
|
||||
public var sizeV:Number = 1;
|
||||
public var sizeW:Number = 1;
|
||||
|
||||
// Флаг повторения текстуры
|
||||
public var repeat:Boolean = true;
|
||||
// Имя файла текстуры
|
||||
public var fileName:String;
|
||||
|
||||
/**
|
||||
* Метод выполняет разбор данных о текстуре.
|
||||
*
|
||||
* @param parts Данные о текстуре. Массив должен содержать части разделённой по пробелам входной строки MTL-файла.
|
||||
* @return объект, содержащий данные о текстуре
|
||||
*/
|
||||
public static function parse(parts:Array):MTLTextureMapInfo {
|
||||
var info:MTLTextureMapInfo = new MTLTextureMapInfo();
|
||||
// Начальное значение индекса единица, т.к. первый элемент массива содержит тип текстуры
|
||||
var index:int = 1;
|
||||
var reader:Function;
|
||||
// Чтение параметров текстуры
|
||||
while ((reader = optionReaders[parts[index]]) != null) {
|
||||
index = reader(index, parts, info);
|
||||
}
|
||||
// Если не было ошибок, последний элемент массива должен содержать имя файла текстуры
|
||||
info.fileName = parts[index];
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Читатель-заглушка. Пропускает все неподдерживаемые параметры.
|
||||
*/
|
||||
private static function stubReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
|
||||
index++;
|
||||
var maxIndex:int = parts.length - 1;
|
||||
while ((MTLTextureMapInfo.optionReaders[parts[index]] == null) && (index < maxIndex)) {
|
||||
index++;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод чтения параметров масштабирования текстурного пространства.
|
||||
*/
|
||||
private static function sizeReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
|
||||
info.sizeU = Number(parts[index + 1]);
|
||||
index += 2;
|
||||
var value:Number = Number(parts[index]);
|
||||
if (!isNaN(value)) {
|
||||
info.sizeV = value;
|
||||
index++;
|
||||
value = Number(parts[index]);
|
||||
if (!isNaN(value)) {
|
||||
info.sizeW = value;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод чтения параметров смещения текстуры.
|
||||
*/
|
||||
private static function offsetReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
|
||||
info.offsetU = Number(parts[index + 1]);
|
||||
index += 2;
|
||||
var value:Number = Number(parts[index]);
|
||||
if (!isNaN(value)) {
|
||||
info.offsetV = value;
|
||||
index++;
|
||||
value = Number(parts[index]);
|
||||
if (!isNaN(value)) {
|
||||
info.offsetW = value;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод чтения параметра повторения текстуры.
|
||||
*/
|
||||
private static function clampReader(index:int, parts:Array, info:MTLTextureMapInfo):int {
|
||||
info.repeat = parts[index + 1] == "off";
|
||||
return index + 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package alternativa.engine3d.loaders {
|
||||
import flash.display.BitmapData;
|
||||
import flash.geom.Point;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Класс содержит обобщённую информацию о материале.
|
||||
*/
|
||||
internal class MaterialInfo {
|
||||
public var color:uint;
|
||||
public var alpha:Number;
|
||||
|
||||
public var textureFileName:String;
|
||||
public var bitmapData:BitmapData;
|
||||
public var repeat:Boolean;
|
||||
|
||||
public var mapOffset:Point;
|
||||
public var mapSize:Point;
|
||||
}
|
||||
}
|
||||
198
Alternativa3D5/5.4/alternativa/engine3d/materials/DevMaterial.as
Normal file
198
Alternativa3D5/5.4/alternativa/engine3d/materials/DevMaterial.as
Normal file
@@ -0,0 +1,198 @@
|
||||
package alternativa.engine3d.materials {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.Camera3D;
|
||||
import alternativa.engine3d.display.Skin;
|
||||
|
||||
import flash.display.BlendMode;
|
||||
import flash.display.Graphics;
|
||||
import alternativa.utils.ColorUtils;
|
||||
import alternativa.engine3d.core.PolyPrimitive;
|
||||
import alternativa.engine3d.core.BSPNode;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Материал, заполняющий грань сплошной заливкой цветом в соответствии с уровнем мобильности. Помимо заливки материал может рисовать границу
|
||||
* полигона линией заданной толщины и цвета.
|
||||
*/
|
||||
public class DevMaterial extends SurfaceMaterial {
|
||||
/**
|
||||
* @private
|
||||
* Цвет
|
||||
*/
|
||||
alternativa3d var _color:uint;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Толщина линий обводки
|
||||
*/
|
||||
alternativa3d var _wireThickness:Number;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Цвет линий обводки
|
||||
*/
|
||||
alternativa3d var _wireColor:uint;
|
||||
|
||||
/**
|
||||
* Создание экземпляра класса.
|
||||
*
|
||||
* @param color цвет заливки
|
||||
* @param alpha прозрачность
|
||||
* @param blendMode режим наложения цвета
|
||||
* @param wireThickness толщина линии обводки
|
||||
* @param wireColor цвет линии обводки
|
||||
*/
|
||||
public function DevMaterial(color:uint = 0xFFFFFF, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) {
|
||||
super(alpha, blendMode);
|
||||
_color = color;
|
||||
_wireThickness = wireThickness;
|
||||
_wireColor = wireColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @param camera
|
||||
* @param skin
|
||||
* @param length
|
||||
* @param points
|
||||
*/
|
||||
override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
|
||||
skin.alpha = _alpha;
|
||||
skin.blendMode = _blendMode;
|
||||
|
||||
var i:uint;
|
||||
var point:DrawPoint;
|
||||
var gfx:Graphics = skin.gfx;
|
||||
|
||||
/*
|
||||
//Мобильность
|
||||
var param:int = skin.primitive.mobility*10;
|
||||
*/
|
||||
|
||||
/*
|
||||
// Уровень распиленности
|
||||
var param:int = 0;
|
||||
var prm:PolyPrimitive = skin.primitive;
|
||||
while (prm != null) {
|
||||
prm = prm.parent;
|
||||
param++;
|
||||
}
|
||||
param *= 10;
|
||||
*/
|
||||
|
||||
// Уровень в BSP-дереве
|
||||
var param:int = 0;
|
||||
var node:BSPNode = skin.primitive.node;
|
||||
while (node != null) {
|
||||
node = node.parent;
|
||||
param++;
|
||||
}
|
||||
param *= 5;
|
||||
|
||||
var c:uint = ColorUtils.rgb(param, param, param);
|
||||
|
||||
if (camera._orthographic) {
|
||||
gfx.beginFill(c);
|
||||
if (_wireThickness >= 0) {
|
||||
gfx.lineStyle(_wireThickness, _wireColor);
|
||||
}
|
||||
point = points[0];
|
||||
gfx.moveTo(point.x, point.y);
|
||||
for (i = 1; i < length; i++) {
|
||||
point = points[i];
|
||||
gfx.lineTo(point.x, point.y);
|
||||
}
|
||||
if (_wireThickness >= 0) {
|
||||
point = points[0];
|
||||
gfx.lineTo(point.x, point.y);
|
||||
}
|
||||
} else {
|
||||
gfx.beginFill(c);
|
||||
if (_wireThickness >= 0) {
|
||||
gfx.lineStyle(_wireThickness, _wireColor);
|
||||
}
|
||||
point = points[0];
|
||||
var perspective:Number = camera.focalLength/point.z;
|
||||
gfx.moveTo(point.x*perspective, point.y*perspective);
|
||||
for (i = 1; i < length; i++) {
|
||||
point = points[i];
|
||||
perspective = camera.focalLength/point.z;
|
||||
gfx.lineTo(point.x*perspective, point.y*perspective);
|
||||
}
|
||||
if (_wireThickness >= 0) {
|
||||
point = points[0];
|
||||
perspective = camera.focalLength/point.z;
|
||||
gfx.lineTo(point.x*perspective, point.y*perspective);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Цвет заливки.
|
||||
*/
|
||||
public function get color():uint {
|
||||
return _color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set color(value:uint):void {
|
||||
if (_color != value) {
|
||||
_color = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Толщина линии обводки. Если значение отрицательное, то отрисовка линии не выполняется.
|
||||
*/
|
||||
public function get wireThickness():Number {
|
||||
return _wireThickness;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set wireThickness(value:Number):void {
|
||||
if (_wireThickness != value) {
|
||||
_wireThickness = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Цвет линии обводки.
|
||||
*/
|
||||
public function get wireColor():uint {
|
||||
return _wireColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set wireColor(value:uint):void {
|
||||
if (_wireColor != value) {
|
||||
_wireColor = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override public function clone():Material {
|
||||
var res:DevMaterial = new DevMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package alternativa.engine3d.materials {
|
||||
/**
|
||||
* @private
|
||||
* Точка, подготовленная к отрисовке.
|
||||
*/
|
||||
public final class DrawPoint {
|
||||
/**
|
||||
* Координата X в системе координат камеры.
|
||||
*/
|
||||
public var x:Number;
|
||||
/**
|
||||
* Координата Y в системе координат камеры.
|
||||
*/
|
||||
public var y:Number;
|
||||
/**
|
||||
* Координата Z в системе координат камеры.
|
||||
*/
|
||||
public var z:Number;
|
||||
/**
|
||||
* Координата U в текстурном пространстве.
|
||||
*/
|
||||
public var u:Number;
|
||||
/**
|
||||
* Координата V в текстурном пространстве.
|
||||
*/
|
||||
public var v:Number;
|
||||
|
||||
/**
|
||||
* Создаёт новый экземпляр класса.
|
||||
*
|
||||
* @param x координата X в системе координат камеры
|
||||
* @param y координата Y в системе координат камеры
|
||||
* @param z координата Z в системе координат камеры
|
||||
* @param u координата U в текстурном пространстве
|
||||
* @param v координата V в текстурном пространстве
|
||||
*/
|
||||
public function DrawPoint(x:Number, y:Number, z:Number, u:Number = 0, v:Number = 0) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.u = u;
|
||||
this.v = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package alternativa.engine3d.materials {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.Camera3D;
|
||||
import alternativa.engine3d.display.Skin;
|
||||
|
||||
import flash.display.BlendMode;
|
||||
import flash.display.Graphics;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Материал, заполняющий грань сплошной одноцветной заливкой. Помимо заливки цветом, материал может рисовать границу
|
||||
* грани линией заданной толщины и цвета.
|
||||
*/
|
||||
public class FillMaterial extends SurfaceMaterial {
|
||||
/**
|
||||
* @private
|
||||
* Цвет
|
||||
*/
|
||||
alternativa3d var _color:uint;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Толщина линий обводки
|
||||
*/
|
||||
alternativa3d var _wireThickness:Number;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Цвет линий обводки
|
||||
*/
|
||||
alternativa3d var _wireColor:uint;
|
||||
|
||||
/**
|
||||
* Создание экземпляра класса.
|
||||
*
|
||||
* @param color цвет заливки
|
||||
* @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
|
||||
* @param blendMode режим наложения цвета
|
||||
* @param wireThickness толщина линии обводки
|
||||
* @param wireColor цвет линии обводки
|
||||
*/
|
||||
public function FillMaterial(color:uint, alpha:Number = 1, blendMode:String = BlendMode.NORMAL, wireThickness:Number = -1, wireColor:uint = 0) {
|
||||
super(alpha, blendMode);
|
||||
_color = color;
|
||||
_wireThickness = wireThickness;
|
||||
_wireColor = wireColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @inheritDoc
|
||||
*/
|
||||
override alternativa3d function draw(camera:Camera3D, skin:Skin, length:uint, points:Array):void {
|
||||
skin.alpha = _alpha;
|
||||
skin.blendMode = _blendMode;
|
||||
|
||||
var i:uint;
|
||||
var point:DrawPoint;
|
||||
var gfx:Graphics = skin.gfx;
|
||||
|
||||
if (camera._orthographic) {
|
||||
gfx.beginFill(_color);
|
||||
if (_wireThickness >= 0) {
|
||||
gfx.lineStyle(_wireThickness, _wireColor);
|
||||
}
|
||||
point = points[0];
|
||||
gfx.moveTo(point.x, point.y);
|
||||
for (i = 1; i < length; i++) {
|
||||
point = points[i];
|
||||
gfx.lineTo(point.x, point.y);
|
||||
}
|
||||
if (_wireThickness >= 0) {
|
||||
point = points[0];
|
||||
gfx.lineTo(point.x, point.y);
|
||||
}
|
||||
} else {
|
||||
gfx.beginFill(_color);
|
||||
if (_wireThickness >= 0) {
|
||||
gfx.lineStyle(_wireThickness, _wireColor);
|
||||
}
|
||||
point = points[0];
|
||||
var perspective:Number = camera.focalLength/point.z;
|
||||
gfx.moveTo(point.x*perspective, point.y*perspective);
|
||||
for (i = 1; i < length; i++) {
|
||||
point = points[i];
|
||||
perspective = camera.focalLength/point.z;
|
||||
gfx.lineTo(point.x*perspective, point.y*perspective);
|
||||
}
|
||||
if (_wireThickness >= 0) {
|
||||
point = points[0];
|
||||
perspective = camera.focalLength/point.z;
|
||||
gfx.lineTo(point.x*perspective, point.y*perspective);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Цвет заливки.
|
||||
*/
|
||||
public function get color():uint {
|
||||
return _color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set color(value:uint):void {
|
||||
if (_color != value) {
|
||||
_color = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Толщина линии обводки. Если значение отрицательное, то обводка не рисуется.
|
||||
*/
|
||||
public function get wireThickness():Number {
|
||||
return _wireThickness;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set wireThickness(value:Number):void {
|
||||
if (_wireThickness != value) {
|
||||
_wireThickness = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Цвет линии обводки.
|
||||
*/
|
||||
public function get wireColor():uint {
|
||||
return _wireColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set wireColor(value:uint):void {
|
||||
if (_wireColor != value) {
|
||||
_wireColor = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override public function clone():Material {
|
||||
var res:FillMaterial = new FillMaterial(_color, _alpha, _blendMode, _wireThickness, _wireColor);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package alternativa.engine3d.materials {
|
||||
import alternativa.engine3d.*;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Базовый класс для материалов.
|
||||
*/
|
||||
public class Material {
|
||||
|
||||
/**
|
||||
* Создание клона материала.
|
||||
*
|
||||
* @return клон материала
|
||||
*/
|
||||
public function clone():Material {
|
||||
return new Material();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
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
|
||||
* Альфа
|
||||
*/
|
||||
alternativa3d var _alpha:Number;
|
||||
/**
|
||||
* @private
|
||||
* Режим наложения цвета
|
||||
*/
|
||||
alternativa3d var _blendMode:String = BlendMode.NORMAL;
|
||||
/**
|
||||
* @private
|
||||
* Материал использует информация об UV-координатах
|
||||
*/
|
||||
alternativa3d var useUV:Boolean = false;
|
||||
|
||||
/**
|
||||
* Создание экземпляра класса.
|
||||
*
|
||||
* @param alpha коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
|
||||
* @param blendMode режим наложения цвета
|
||||
*/
|
||||
public function SurfaceMaterial(alpha:Number = 1, blendMode:String = BlendMode.NORMAL) {
|
||||
_alpha = alpha;
|
||||
_blendMode = 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Коэффициент непрозрачности материала. Значение 1 соответствует полной непрозрачности, значение 0 соответствует полной прозрачности.
|
||||
*/
|
||||
public function get alpha():Number {
|
||||
return _alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set alpha(value:Number):void {
|
||||
if (_alpha != value) {
|
||||
_alpha = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Режим наложения цвета.
|
||||
*/
|
||||
public function get blendMode():String {
|
||||
return _blendMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set blendMode(value:String):void {
|
||||
if (_blendMode != value) {
|
||||
_blendMode = value;
|
||||
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 skin скин для очистки
|
||||
*/
|
||||
alternativa3d function clear(skin:Skin):void {
|
||||
skin.gfx.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
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.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
|
||||
* Метод определяет, может ли материал нарисовать указанный примитив. Метод используется в системе отрисовки сцены и должен использоваться
|
||||
* наследниками для указания видимости связанной с материалом поверхности или отдельного примитива.
|
||||
*
|
||||
* @param primitive примитив для проверки
|
||||
*
|
||||
* @return <code>true</code>, если материал может отрисовать указанный примитив, иначе <code>false</code>
|
||||
*/
|
||||
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.uvMatrix, _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;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Повтор текстуры при заливке. Более подробную информацию можно найти в описании метода
|
||||
* <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;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Сглаживание текстуры при увеличении масштаба. Более подробную информацию можно найти в описании метода
|
||||
* <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;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Цвет линии обводки полигона.
|
||||
*/
|
||||
public function get wireColor():uint {
|
||||
return _wireColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set wireColor(value:uint):void {
|
||||
if (_wireColor != value) {
|
||||
_wireColor = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Точность перспективной коррекции.
|
||||
*/
|
||||
public function get precision():Number {
|
||||
return _precision;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set precision(value:Number):void {
|
||||
if (_precision != value) {
|
||||
_precision = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override public function clone():Material {
|
||||
var res:TextureMaterial = new TextureMaterial(_texture, _alpha, _repeat, _smooth, _blendMode, _wireThickness, _wireColor, _precision);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package alternativa.engine3d.materials {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.Camera3D;
|
||||
import alternativa.engine3d.display.Skin;
|
||||
|
||||
import flash.display.BlendMode;
|
||||
import flash.display.Graphics;
|
||||
import alternativa.engine3d.core.PolyPrimitive;
|
||||
|
||||
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;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Толщина линий. Если толщина отрицательная, то отрисовка не выполняется.
|
||||
*/
|
||||
public function get thickness():Number {
|
||||
return _thickness;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set thickness(value:Number):void {
|
||||
if (_thickness != value) {
|
||||
_thickness = value;
|
||||
if (_surface != null) {
|
||||
_surface.addMaterialChangedOperationToScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override public function clone():Material {
|
||||
return new WireMaterial(_thickness, _color, _alpha, _blendMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Alternativa3D5/5.4/alternativa/engine3d/physics/Collision.as
Normal file
30
Alternativa3D5/5.4/alternativa/engine3d/physics/Collision.as
Normal file
@@ -0,0 +1,30 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,993 @@
|
||||
package alternativa.engine3d.physics {
|
||||
import alternativa.engine3d.*;
|
||||
import alternativa.engine3d.core.BSPNode;
|
||||
import alternativa.engine3d.core.PolyPrimitive;
|
||||
import alternativa.engine3d.core.Scene3D;
|
||||
import alternativa.types.Point3D;
|
||||
import alternativa.types.Set;
|
||||
import alternativa.utils.ObjectUtils;
|
||||
|
||||
use namespace alternativa3d;
|
||||
|
||||
/**
|
||||
* Класс реализует алгоритм непрерывного определения столкновений эллипсоида с плоскими выпуклыми многоугольниками.
|
||||
*/
|
||||
public class EllipsoidCollider {
|
||||
// Максимальное количество попыток найти свободное от столкновения со сценой направление
|
||||
private static const MAX_COLLISIONS:uint = 50;
|
||||
// Радиус наибольшей сферы
|
||||
private var _radius:Number = 100;
|
||||
private var _radius2:Number = _radius * _radius;
|
||||
private var _radiusX:Number = _radius;
|
||||
private var _radiusY:Number = _radius;
|
||||
private var _radiusZ:Number = _radius;
|
||||
private var _radiusX2:Number = _radiusX * _radiusX;
|
||||
private var _radiusY2:Number = _radiusY * _radiusY;
|
||||
private var _radiusZ2:Number = _radiusZ * _radiusZ;
|
||||
// Коэффициенты масштабирования осей
|
||||
private var _scaleX:Number = 1;
|
||||
private var _scaleY:Number = 1;
|
||||
private var _scaleZ:Number = 1;
|
||||
// Квадраты коэффициентов масштабирования осей
|
||||
private var _scaleX2:Number = 1;
|
||||
private var _scaleY2:Number = 1;
|
||||
private var _scaleZ2:Number = 1;
|
||||
|
||||
private var collisionSource:Point3D;
|
||||
private var currentDisplacement:Point3D = new Point3D();
|
||||
private var collisionDestination:Point3D = new Point3D();
|
||||
|
||||
private var collisionPlanes:Array = new Array();
|
||||
private var collisionPrimitive:PolyPrimitive;
|
||||
private var collisionPrimitiveNearest:PolyPrimitive;
|
||||
private var collisionPlanePoint:Point3D = new Point3D();
|
||||
private var collisionPrimitiveNearestLengthSqr:Number;
|
||||
private var collisionPrimitivePoint:Point3D = new Point3D();
|
||||
|
||||
private var collisionNormal:Point3D = new Point3D();
|
||||
private var collisionPoint:Point3D = new Point3D();
|
||||
private var collisionOffset:Number;
|
||||
|
||||
private var currentCoords:Point3D = new Point3D();
|
||||
private var collision:Collision = new Collision();
|
||||
private var collisionRadius:Number;
|
||||
private var radiusVector:Point3D = new Point3D();
|
||||
private var p1:Point3D = new Point3D();
|
||||
private var p2:Point3D = new Point3D();;
|
||||
private var localCollisionPlanePoint:Point3D = new Point3D();
|
||||
|
||||
// Флаг использования упорщённого алгоритма. Включается когда эллипсоид представляет собой сферу.
|
||||
private var useSimpleAlgorithm:Boolean = true;
|
||||
|
||||
/**
|
||||
* Сцена, в которой определяются столкновения.
|
||||
*/
|
||||
public var scene:Scene3D;
|
||||
/**
|
||||
* Погрешность определения расстояний и координат. Две точки совпадают, если модуль разности любых соответствующих
|
||||
* координат меньше указанной погрешности.
|
||||
*/
|
||||
public var offsetThreshold:Number = 0.01;
|
||||
/**
|
||||
* Множество объектов, учитываемых в процессе определения столкновений. В качестве объектов могут выступать экземпляры
|
||||
* классов <code>Mesh</code> и <code>Surface</code>. Каким образом учитываются перечисленные в множестве объекты зависит
|
||||
* от значения поля <code>collisionSetMode</code>. Значение <code>null</code> эквивалентно заданию пустого множества.
|
||||
*
|
||||
* @see #collisionSetMode
|
||||
* @see alternativa.engine3d.core.Mesh
|
||||
* @see alternativa.engine3d.core.Surface
|
||||
*/
|
||||
public var collisionSet:Set;
|
||||
/**
|
||||
* Параметр определяет, каким образом учитываются объекты, перечисленные в множестве <code>collisionSet</code>. Если
|
||||
* значение параметра равно <code>true</code>, то грани объектов из множества игнорируются при определении столкновений.
|
||||
* При значении параметра <code>false</code> учитываются только столкновения с гранями, принадлежащим перечисленным
|
||||
* в множестве объектам.
|
||||
*
|
||||
* @default true
|
||||
* @see #collisionSet
|
||||
*/
|
||||
private var _collisionSetMode:int = CollisionSetMode.EXCLUDE;
|
||||
|
||||
/**
|
||||
* Создаёт новый экземпляр класса.
|
||||
*
|
||||
* @param scene сцена, в которой определяются столкновения
|
||||
* @param scaleX радиус эллипсоида по оси X
|
||||
* @param scaleY радиус эллипсоида по оси Y
|
||||
* @param scaleZ радиус эллипсоида по оси Z
|
||||
*/
|
||||
public function EllipsoidCollider(scene:Scene3D = null, radiusX:Number = 100, radiusY:Number = 100, radiusZ:Number = 100) {
|
||||
this.scene = scene;
|
||||
this.radiusX = radiusX;
|
||||
this.radiusY = radiusY;
|
||||
this.radiusZ = radiusZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function get collisionSetMode():int {
|
||||
return _collisionSetMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Параметр определяет, каким образом учитываются объекты, перечисленные в множестве <code>collisionSet</code>.
|
||||
*
|
||||
* @default CollisionSetMode.EXCLUDE
|
||||
* @see #collisionSet
|
||||
* @see CollisionSetMode
|
||||
*/
|
||||
public function set collisionSetMode(value:int):void {
|
||||
if (value != CollisionSetMode.EXCLUDE && value != CollisionSetMode.INCLUDE) {
|
||||
throw ArgumentError(ObjectUtils.getClassName(this) + ".collisionSetMode invalid value");
|
||||
}
|
||||
_collisionSetMode = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Величина радиуса (полуоси) эллипсоида по оси X. При установке отрицательного значения берётся модуль.
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
public function get radiusX():Number {
|
||||
return _radiusX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set radiusX(value:Number):void {
|
||||
_radiusX = value >= 0 ? value : -value;
|
||||
_radiusX2 = _radiusX * _radiusX;
|
||||
calculateScales();
|
||||
}
|
||||
|
||||
/**
|
||||
* Величина радиуса (полуоси) эллипсоида по оси Y. При установке отрицательного значения берётся модуль.
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
public function get radiusY():Number {
|
||||
return _radiusY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set radiusY(value:Number):void {
|
||||
_radiusY = value >= 0 ? value : -value;
|
||||
_radiusY2 = _radiusY * _radiusY;
|
||||
calculateScales();
|
||||
}
|
||||
|
||||
/**
|
||||
* Величина радиуса (полуоси) эллипсоида по оси Z. При установке отрицательного значения берётся модуль.
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
public function get radiusZ():Number {
|
||||
return _radiusZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set radiusZ(value:Number):void {
|
||||
_radiusZ = value >= 0 ? value : -value;
|
||||
_radiusZ2 = _radiusZ * _radiusZ;
|
||||
calculateScales();
|
||||
}
|
||||
|
||||
/**
|
||||
* Расчёт коэффициентов масштабирования осей.
|
||||
*/
|
||||
private function calculateScales():void {
|
||||
_radius = _radiusX;
|
||||
if (_radiusY > _radius) {
|
||||
_radius = _radiusY;
|
||||
}
|
||||
if (_radiusZ > _radius) {
|
||||
_radius = _radiusZ;
|
||||
}
|
||||
_radius2 = _radius * _radius;
|
||||
_scaleX = _radiusX / _radius;
|
||||
_scaleY = _radiusY / _radius;
|
||||
_scaleZ = _radiusZ / _radius;
|
||||
_scaleX2 = _scaleX * _scaleX;
|
||||
_scaleY2 = _scaleY * _scaleY;
|
||||
_scaleZ2 = _scaleZ * _scaleZ;
|
||||
|
||||
useSimpleAlgorithm = (_radiusX == _radiusY) && (_radiusX == _radiusZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Расчёт конечного положения эллипсоида по заданному начальному положению и вектору смещения. Если задано значение
|
||||
* поля <code>scene</code>, то при вычислении конечного положения учитываются столкновения с объектами сцены,
|
||||
* принимая во внимание множество <code>collisionSet</code> и режим работы <code>collisionSetMode</code>. Если
|
||||
* значение поля <code>scene</code> равно <code>null</code>, то результат работы метода будет простой суммой двух
|
||||
* входных векторов.
|
||||
*
|
||||
* @param sourcePoint начальное положение центра эллипсоида в системе координат корневого объекта сцены
|
||||
* @param displacementVector вектор перемещения эллипсоида в системе координат корневого объекта сцены. Если модуль
|
||||
* каждого компонента вектора не превышает значения <code>offsetThreshold</code>, эллипсоид остаётся в начальной точке.
|
||||
* @param destinationPoint в эту переменную записывается расчётное положение центра эллипсоида в системе координат
|
||||
* корневого объекта сцены
|
||||
*
|
||||
* @see #scene
|
||||
* @see #collisionSet
|
||||
* @see #collisionSetMode
|
||||
* @see #offsetThreshold
|
||||
*/
|
||||
public function calculateDestination(sourcePoint:Point3D, displacementVector:Point3D, destinationPoint:Point3D):void {
|
||||
// Расчеты не производятся, если перемещение мало
|
||||
if (displacementVector.x < offsetThreshold && displacementVector.x > -offsetThreshold &&
|
||||
displacementVector.y < offsetThreshold && displacementVector.y > -offsetThreshold &&
|
||||
displacementVector.z < offsetThreshold && displacementVector.z > -offsetThreshold) {
|
||||
destinationPoint.x = sourcePoint.x;
|
||||
destinationPoint.y = sourcePoint.y;
|
||||
destinationPoint.z = sourcePoint.z;
|
||||
return;
|
||||
}
|
||||
|
||||
// Начальные координаты
|
||||
currentCoords.x = sourcePoint.x;
|
||||
currentCoords.y = sourcePoint.y;
|
||||
currentCoords.z = sourcePoint.z;
|
||||
// Начальный вектор перемещения
|
||||
currentDisplacement.x = displacementVector.x;
|
||||
currentDisplacement.y = displacementVector.y;
|
||||
currentDisplacement.z = displacementVector.z;
|
||||
// Начальная точка назначения
|
||||
destinationPoint.x = sourcePoint.x + currentDisplacement.x;
|
||||
destinationPoint.y = sourcePoint.y + currentDisplacement.y;
|
||||
destinationPoint.z = sourcePoint.z + currentDisplacement.z;
|
||||
|
||||
if (useSimpleAlgorithm) {
|
||||
calculateDestinationS(sourcePoint, destinationPoint);
|
||||
} else {
|
||||
calculateDestinationE(sourcePoint, destinationPoint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Вычисление точки назначения для сферы.
|
||||
* @param sourcePoint
|
||||
* @param destinationPoint
|
||||
*/
|
||||
private function calculateDestinationS(sourcePoint:Point3D, destinationPoint:Point3D):void {
|
||||
var collisionCount:uint = 0;
|
||||
var hasCollision:Boolean;
|
||||
do {
|
||||
hasCollision = getCollision(currentCoords, currentDisplacement, collision);
|
||||
if (hasCollision ) {
|
||||
// Вынос точки назначения из-за плоскости столкновения на высоту радиуса сферы над плоскостью по направлению нормали
|
||||
var offset:Number = _radius + offsetThreshold + collision.offset - destinationPoint.x*collision.normal.x - destinationPoint.y*collision.normal.y - destinationPoint.z*collision.normal.z;
|
||||
destinationPoint.x += collision.normal.x * offset;
|
||||
destinationPoint.y += collision.normal.y * offset;
|
||||
destinationPoint.z += collision.normal.z * offset;
|
||||
// Коррекция текущих кординат центра сферы для следующей итерации
|
||||
currentCoords.x = collision.point.x + collision.normal.x * (_radius + offsetThreshold);
|
||||
currentCoords.y = collision.point.y + collision.normal.y * (_radius + offsetThreshold);
|
||||
currentCoords.z = collision.point.z + collision.normal.z * (_radius + offsetThreshold);
|
||||
// Коррекция вектора скорости. Результирующий вектор направлен вдоль плоскости столкновения.
|
||||
currentDisplacement.x = destinationPoint.x - currentCoords.x;
|
||||
currentDisplacement.y = destinationPoint.y - currentCoords.y;
|
||||
currentDisplacement.z = destinationPoint.z - currentCoords.z;
|
||||
|
||||
// Если смещение слишком мало, останавливаемся
|
||||
if (currentDisplacement.x < offsetThreshold && currentDisplacement.x > -offsetThreshold &&
|
||||
currentDisplacement.y < offsetThreshold && currentDisplacement.y > -offsetThreshold &&
|
||||
currentDisplacement.z < offsetThreshold && currentDisplacement.z > -offsetThreshold) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (hasCollision && (++collisionCount < MAX_COLLISIONS));
|
||||
// Если количество итераций достигло максимально возможного значения, то остаемся на старом месте
|
||||
if (collisionCount == MAX_COLLISIONS) {
|
||||
destinationPoint.x = sourcePoint.x;
|
||||
destinationPoint.y = sourcePoint.y;
|
||||
destinationPoint.z = sourcePoint.z;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Вычисление точки назначения для эллипсоида.
|
||||
* @param destinationPoint
|
||||
* @return
|
||||
*/
|
||||
private function calculateDestinationE(sourcePoint:Point3D, destinationPoint:Point3D):void {
|
||||
var collisionCount:uint = 0;
|
||||
var hasCollision:Boolean;
|
||||
// Цикл выполняется до тех пор, пока не будет найдено ни одного столкновения на очередной итерации или пока не
|
||||
// будет достигнуто максимально допустимое количество столкновений, что означает зацикливание алгоритма и
|
||||
// необходимость принудительного выхода.
|
||||
do {
|
||||
hasCollision = getCollision(currentCoords, currentDisplacement, collision);
|
||||
if (hasCollision) {
|
||||
// Вынос точки назначения из-за плоскости столкновения на высоту эффективного радиуса эллипсоида над плоскостью по направлению нормали
|
||||
var offset:Number = collisionRadius + offsetThreshold + collision.offset - destinationPoint.x * collision.normal.x - destinationPoint.y * collision.normal.y - destinationPoint.z * collision.normal.z;
|
||||
destinationPoint.x += collision.normal.x * offset;
|
||||
destinationPoint.y += collision.normal.y * offset;
|
||||
destinationPoint.z += collision.normal.z * offset;
|
||||
// Коррекция текущих кординат центра эллипсоида для следующей итерации
|
||||
collisionRadius = (collisionRadius + offsetThreshold) / collisionRadius;
|
||||
currentCoords.x = collision.point.x - collisionRadius * radiusVector.x;
|
||||
currentCoords.y = collision.point.y - collisionRadius * radiusVector.y;
|
||||
currentCoords.z = collision.point.z - collisionRadius * radiusVector.z;
|
||||
// Коррекция вектора смещения. Результирующий вектор направлен параллельно плоскости столкновения.
|
||||
currentDisplacement.x = destinationPoint.x - currentCoords.x;
|
||||
currentDisplacement.y = destinationPoint.y - currentCoords.y;
|
||||
currentDisplacement.z = destinationPoint.z - currentCoords.z;
|
||||
// Если смещение слишком мало, останавливаемся
|
||||
if (currentDisplacement.x < offsetThreshold && currentDisplacement.x > -offsetThreshold &&
|
||||
currentDisplacement.y < offsetThreshold && currentDisplacement.y > -offsetThreshold &&
|
||||
currentDisplacement.z < offsetThreshold && currentDisplacement.z > -offsetThreshold) {
|
||||
destinationPoint.x = currentCoords.x;
|
||||
destinationPoint.y = currentCoords.y;
|
||||
destinationPoint.z = currentCoords.z;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (hasCollision && (++collisionCount < MAX_COLLISIONS));
|
||||
// Если количество итераций достигло максимально возможного значения, то остаемся на старом месте
|
||||
if (collisionCount == MAX_COLLISIONS) {
|
||||
destinationPoint.x = sourcePoint.x;
|
||||
destinationPoint.y = sourcePoint.y;
|
||||
destinationPoint.z = sourcePoint.z;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод определяет наличие столкновения при смещении эллипсоида из заданной точки на величину указанного вектора
|
||||
* перемещения, принимая во внимание множество <code>collisionSet</code> и режим работы <code>collisionSetMode</code>.
|
||||
*
|
||||
* @param sourcePoint начальное положение центра эллипсоида в системе координат корневого объекта сцены
|
||||
* @param displacementVector вектор перемещения эллипсоида в системе координат корневого объекта сцены
|
||||
* @param collision в эту переменную будут записаны данные о плоскости и точке столкновения в системе координат
|
||||
* корневого объекта сцены
|
||||
*
|
||||
* @return <code>true</code>, если эллипсоид при заданном перемещении столкнётся с каким-либо полигоном сцены,
|
||||
* <code>false</code> если столкновений нет или не задано значение поля <code>scene</code>.
|
||||
*
|
||||
* @see #scene
|
||||
* @see #collisionSet
|
||||
* @see #collisionSetMode
|
||||
*/
|
||||
public function getCollision(sourcePoint:Point3D, displacementVector:Point3D, collision:Collision):Boolean {
|
||||
if (scene == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
collisionSource = sourcePoint;
|
||||
|
||||
currentDisplacement.x = displacementVector.x;
|
||||
currentDisplacement.y = displacementVector.y;
|
||||
currentDisplacement.z = displacementVector.z;
|
||||
|
||||
collisionDestination.x = collisionSource.x + currentDisplacement.x;
|
||||
collisionDestination.y = collisionSource.y + currentDisplacement.y;
|
||||
collisionDestination.z = collisionSource.z + currentDisplacement.z;
|
||||
|
||||
collectPotentialCollisionPlanes(scene.bsp);
|
||||
collisionPlanes.sortOn("sourceOffset", Array.NUMERIC | Array.DESCENDING);
|
||||
|
||||
var plane:CollisionPlane;
|
||||
// Пока не найдём столкновение с примитивом или плоскости не кончатся
|
||||
if (useSimpleAlgorithm) {
|
||||
while ((plane = collisionPlanes.pop()) != null) {
|
||||
if (collisionPrimitive == null) {
|
||||
calculateCollisionWithPlaneS(plane);
|
||||
}
|
||||
CollisionPlane.destroyCollisionPlane(plane);
|
||||
}
|
||||
} else {
|
||||
while ((plane = collisionPlanes.pop()) != null) {
|
||||
if (collisionPrimitive == null) {
|
||||
calculateCollisionWithPlaneE(plane);
|
||||
}
|
||||
CollisionPlane.destroyCollisionPlane(plane);
|
||||
}
|
||||
}
|
||||
|
||||
var collisionFound:Boolean = collisionPrimitive != null;
|
||||
if (collisionFound) {
|
||||
collision.face = collisionPrimitive.face;
|
||||
collision.normal = collisionNormal;
|
||||
collision.offset = collisionOffset;
|
||||
collision.point = collisionPoint;
|
||||
}
|
||||
|
||||
collisionPrimitive = null;
|
||||
collisionSource = null;
|
||||
|
||||
return collisionFound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сбор потенциальных плоскостей столкновения.
|
||||
*
|
||||
* @param node текущий узел BSP-дерева
|
||||
*/
|
||||
private function collectPotentialCollisionPlanes(node:BSPNode):void {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceOffset:Number = collisionSource.x * node.normal.x + collisionSource.y * node.normal.y + collisionSource.z * node.normal.z - node.offset;
|
||||
var destinationOffset:Number = collisionDestination.x * node.normal.x + collisionDestination.y * node.normal.y + collisionDestination.z * node.normal.z - node.offset;
|
||||
var plane:CollisionPlane;
|
||||
|
||||
if (sourceOffset >= 0) {
|
||||
// Исходное положение центра перед плоскостью ноды
|
||||
// Проверяем передние ноды
|
||||
collectPotentialCollisionPlanes(node.front);
|
||||
// Грубая оценка пересечения с плоскостью по радиусу ограничивающей сферы эллипсоида
|
||||
if (destinationOffset < _radius) {
|
||||
// Нашли потенциальное пересечение с плоскостью
|
||||
plane = CollisionPlane.createCollisionPlane(node, true, sourceOffset, destinationOffset);
|
||||
collisionPlanes.push(plane);
|
||||
// Проверяем задние ноды
|
||||
collectPotentialCollisionPlanes(node.back);
|
||||
}
|
||||
} else {
|
||||
// Исходное положение центра за плоскостью ноды
|
||||
// Проверяем задние ноды
|
||||
collectPotentialCollisionPlanes(node.back);
|
||||
// Грубая оценка пересечения с плоскостью по радиусу ограничивающей сферы эллипсоида
|
||||
if (destinationOffset > -_radius) {
|
||||
// Столкновение возможно только в случае если в ноде есть примитивы, направленные назад
|
||||
if (node.backPrimitives != null) {
|
||||
// Нашли потенциальное пересечение с плоскостью
|
||||
plane = CollisionPlane.createCollisionPlane(node, false, -sourceOffset, -destinationOffset);
|
||||
collisionPlanes.push(plane);
|
||||
}
|
||||
// Проверяем передние ноды
|
||||
collectPotentialCollisionPlanes(node.front);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Определение пересечения сферы с примитивами, лежащими в заданной плоскости.
|
||||
*
|
||||
* @param plane плоскость, содержащая примитивы для проверки
|
||||
*/
|
||||
private function calculateCollisionWithPlaneS(plane:CollisionPlane):void {
|
||||
collisionPlanePoint.copy(collisionSource);
|
||||
|
||||
var normal:Point3D = plane.node.normal;
|
||||
// Если сфера врезана в плоскость
|
||||
if (plane.sourceOffset <= _radius) {
|
||||
if (plane.infront) {
|
||||
collisionPlanePoint.x -= normal.x * plane.sourceOffset;
|
||||
collisionPlanePoint.y -= normal.y * plane.sourceOffset;
|
||||
collisionPlanePoint.z -= normal.z * plane.sourceOffset;
|
||||
} else {
|
||||
collisionPlanePoint.x += normal.x * plane.sourceOffset;
|
||||
collisionPlanePoint.y += normal.y * plane.sourceOffset;
|
||||
collisionPlanePoint.z += normal.z * plane.sourceOffset;
|
||||
}
|
||||
} else {
|
||||
// Находим центр сферы во время столкновения с плоскостью
|
||||
var time:Number = (plane.sourceOffset - _radius) / (plane.sourceOffset - plane.destinationOffset);
|
||||
collisionPlanePoint.x = collisionSource.x + currentDisplacement.x * time;
|
||||
collisionPlanePoint.y = collisionSource.y + currentDisplacement.y * time;
|
||||
collisionPlanePoint.z = collisionSource.z + currentDisplacement.z * time;
|
||||
|
||||
// Устанавливаем точку пересечения cферы с плоскостью
|
||||
if (plane.infront) {
|
||||
collisionPlanePoint.x -= normal.x * _radius;
|
||||
collisionPlanePoint.y -= normal.y * _radius;
|
||||
collisionPlanePoint.z -= normal.z * _radius;
|
||||
} else {
|
||||
collisionPlanePoint.x += normal.x * _radius;
|
||||
collisionPlanePoint.y += normal.y * _radius;
|
||||
collisionPlanePoint.z += normal.z * _radius;
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем примитивы плоскости
|
||||
var primitive:*;
|
||||
collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE;
|
||||
collisionPrimitiveNearest = null;
|
||||
if (plane.infront) {
|
||||
if ((primitive = plane.node.primitive) != null) {
|
||||
if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
|
||||
((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
|
||||
calculateCollisionWithPrimitiveS(plane.node.primitive);
|
||||
}
|
||||
} else {
|
||||
for (primitive in plane.node.frontPrimitives) {
|
||||
if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
|
||||
((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
|
||||
calculateCollisionWithPrimitiveS(primitive);
|
||||
if (collisionPrimitive != null) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (primitive in plane.node.backPrimitives) {
|
||||
if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
|
||||
((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
|
||||
calculateCollisionWithPrimitiveS(primitive);
|
||||
if (collisionPrimitive != null) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (collisionPrimitive != null) {
|
||||
// Если точка пересечения попала в примитив
|
||||
|
||||
// Нормаль плоскости при столкновении - нормаль плоскости
|
||||
if (plane.infront) {
|
||||
collisionNormal.x = normal.x;
|
||||
collisionNormal.y = normal.y;
|
||||
collisionNormal.z = normal.z;
|
||||
collisionOffset = plane.node.offset;
|
||||
} else {
|
||||
collisionNormal.x = -normal.x;
|
||||
collisionNormal.y = -normal.y;
|
||||
collisionNormal.z = -normal.z;
|
||||
collisionOffset = -plane.node.offset;
|
||||
}
|
||||
|
||||
// Точка столкновения в точке столкновения с плоскостью
|
||||
collisionPoint.x = collisionPlanePoint.x;
|
||||
collisionPoint.y = collisionPlanePoint.y;
|
||||
collisionPoint.z = collisionPlanePoint.z;
|
||||
|
||||
} else {
|
||||
// Если точка пересечения не попала ни в один примитив, проверяем столкновение с ближайшей
|
||||
|
||||
// Вектор из ближайшей точки в центр сферы
|
||||
var nearestPointToSourceX:Number = collisionSource.x - collisionPrimitivePoint.x;
|
||||
var nearestPointToSourceY:Number = collisionSource.y - collisionPrimitivePoint.y;
|
||||
var nearestPointToSourceZ:Number = collisionSource.z - collisionPrimitivePoint.z;
|
||||
|
||||
// Если движение в сторону точки
|
||||
if (nearestPointToSourceX * currentDisplacement.x + nearestPointToSourceY * currentDisplacement.y + nearestPointToSourceZ * currentDisplacement.z <= 0) {
|
||||
|
||||
// Ищем нормализованный вектор обратного направления
|
||||
var vectorLength:Number = Math.sqrt(currentDisplacement.x * currentDisplacement.x + currentDisplacement.y * currentDisplacement.y + currentDisplacement.z * currentDisplacement.z);
|
||||
var vectorX:Number = -currentDisplacement.x / vectorLength;
|
||||
var vectorY:Number = -currentDisplacement.y / vectorLength;
|
||||
var vectorZ:Number = -currentDisplacement.z / vectorLength;
|
||||
|
||||
// Длина вектора из ближайшей точки в центр сферы
|
||||
var nearestPointToSourceLengthSqr:Number = nearestPointToSourceX * nearestPointToSourceX + nearestPointToSourceY * nearestPointToSourceY + nearestPointToSourceZ * nearestPointToSourceZ;
|
||||
|
||||
// Проекция вектора из ближайшей точки в центр сферы на нормализованный вектор обратного направления
|
||||
var projectionLength:Number = nearestPointToSourceX * vectorX + nearestPointToSourceY * vectorY + nearestPointToSourceZ * vectorZ;
|
||||
|
||||
var projectionInsideSphereLengthSqr:Number = _radius2 - nearestPointToSourceLengthSqr + projectionLength * projectionLength;
|
||||
|
||||
if (projectionInsideSphereLengthSqr > 0) {
|
||||
// Находим расстояние из ближайшей точки до сферы
|
||||
var distance:Number = projectionLength - Math.sqrt(projectionInsideSphereLengthSqr);
|
||||
|
||||
if (distance < vectorLength) {
|
||||
// Столкновение сферы с ближайшей точкой произошло
|
||||
|
||||
// Точка столкновения в ближайшей точке
|
||||
collisionPoint.x = collisionPrimitivePoint.x;
|
||||
collisionPoint.y = collisionPrimitivePoint.y;
|
||||
collisionPoint.z = collisionPrimitivePoint.z;
|
||||
|
||||
// Находим нормаль плоскости столкновения
|
||||
var nearestPointToSourceLength:Number = Math.sqrt(nearestPointToSourceLengthSqr);
|
||||
collisionNormal.x = nearestPointToSourceX / nearestPointToSourceLength;
|
||||
collisionNormal.y = nearestPointToSourceY / nearestPointToSourceLength;
|
||||
collisionNormal.z = nearestPointToSourceZ / nearestPointToSourceLength;
|
||||
|
||||
// Смещение плоскости столкновения
|
||||
collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z;
|
||||
collisionPrimitive = collisionPrimitiveNearest;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Определение столкновения сферы с примитивом.
|
||||
*
|
||||
* @param primitive примитив, столкновение с которым проверяется
|
||||
*/
|
||||
private function calculateCollisionWithPrimitiveS(primitive:PolyPrimitive):void {
|
||||
|
||||
var length:uint = primitive.num;
|
||||
var points:Array = primitive.points;
|
||||
var normal:Point3D = primitive.face.globalNormal;
|
||||
var inside:Boolean = true;
|
||||
|
||||
for (var i:uint = 0; i < length; i++) {
|
||||
|
||||
var p1:Point3D = points[i];
|
||||
var p2:Point3D = points[(i < length - 1) ? (i + 1) : 0];
|
||||
|
||||
var edgeX:Number = p2.x - p1.x;
|
||||
var edgeY:Number = p2.y - p1.y;
|
||||
var edgeZ:Number = p2.z - p1.z;
|
||||
|
||||
var vectorX:Number = collisionPlanePoint.x - p1.x;
|
||||
var vectorY:Number = collisionPlanePoint.y - p1.y;
|
||||
var vectorZ:Number = collisionPlanePoint.z - p1.z;
|
||||
|
||||
var crossX:Number = vectorY * edgeZ - vectorZ * edgeY;
|
||||
var crossY:Number = vectorZ * edgeX - vectorX * edgeZ;
|
||||
var crossZ:Number = vectorX * edgeY - vectorY * edgeX;
|
||||
|
||||
if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) {
|
||||
// Точка за пределами полигона
|
||||
inside = false;
|
||||
|
||||
var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ;
|
||||
var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr;
|
||||
|
||||
// Если расстояние до прямой меньше текущего ближайшего
|
||||
if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) {
|
||||
|
||||
// Ищем нормализованный вектор ребра
|
||||
var edgeLength:Number = Math.sqrt(edgeLengthSqr);
|
||||
var edgeNormX:Number = edgeX / edgeLength;
|
||||
var edgeNormY:Number = edgeY / edgeLength;
|
||||
var edgeNormZ:Number = edgeZ / edgeLength;
|
||||
|
||||
// Находим расстояние до точки перпендикуляра вдоль ребра
|
||||
var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ;
|
||||
|
||||
var vectorLengthSqr:Number;
|
||||
if (t < 0) {
|
||||
// Ближайшая точка - первая
|
||||
vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
|
||||
if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
|
||||
collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
|
||||
collisionPrimitivePoint.x = p1.x;
|
||||
collisionPrimitivePoint.y = p1.y;
|
||||
collisionPrimitivePoint.z = p1.z;
|
||||
collisionPrimitiveNearest = primitive;
|
||||
}
|
||||
} else {
|
||||
if (t > edgeLength) {
|
||||
// Ближайшая точка - вторая
|
||||
vectorX = collisionPlanePoint.x - p2.x;
|
||||
vectorY = collisionPlanePoint.y - p2.y;
|
||||
vectorZ = collisionPlanePoint.z - p2.z;
|
||||
vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
|
||||
if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
|
||||
collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
|
||||
collisionPrimitivePoint.x = p2.x;
|
||||
collisionPrimitivePoint.y = p2.y;
|
||||
collisionPrimitivePoint.z = p2.z;
|
||||
collisionPrimitiveNearest = primitive;
|
||||
}
|
||||
} else {
|
||||
// Ближайшая точка на ребре
|
||||
collisionPrimitiveNearestLengthSqr = edgeDistanceSqr;
|
||||
collisionPrimitivePoint.x = p1.x + edgeNormX * t;
|
||||
collisionPrimitivePoint.y = p1.y + edgeNormY * t;
|
||||
collisionPrimitivePoint.z = p1.z + edgeNormZ * t;
|
||||
collisionPrimitiveNearest = primitive;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Если попали в примитив
|
||||
if (inside) {
|
||||
collisionPrimitive = primitive;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка на действительное столкновение эллипсоида с плоскостью.
|
||||
*/
|
||||
private function calculateCollisionWithPlaneE(plane:CollisionPlane):void {
|
||||
var normalX:Number = plane.node.normal.x;
|
||||
var normalY:Number = plane.node.normal.y;
|
||||
var normalZ:Number = plane.node.normal.z;
|
||||
// Смещение по направлению к плоскости вдоль нормали. Положительное смещение означает приближение к плоскости, отрицательное -- удаление
|
||||
// от плоскости, в этом случае столкновения не происходит.
|
||||
var displacementAlongNormal:Number = currentDisplacement.x * normalX + currentDisplacement.y * normalY + currentDisplacement.z * normalZ;
|
||||
if (plane.infront) {
|
||||
displacementAlongNormal = -displacementAlongNormal;
|
||||
}
|
||||
// Выходим из функции в случае удаления от плоскости
|
||||
if (displacementAlongNormal < 0) {
|
||||
return;
|
||||
}
|
||||
// Определение ближайшей к плоскости точки эллипсоида
|
||||
var k:Number = _radius / Math.sqrt(normalX * normalX * _scaleX2 + normalY * normalY * _scaleY2 + normalZ * normalZ * _scaleZ2);
|
||||
// Положение точки в локальной системе координат эллипсоида
|
||||
var localClosestX:Number = k * normalX * _scaleX2;
|
||||
var localClosestY:Number = k * normalY * _scaleY2;
|
||||
var localClosestZ:Number = k * normalZ * _scaleZ2;
|
||||
// Глобальные координаты точки
|
||||
var px:Number = collisionSource.x + localClosestX;
|
||||
var py:Number = collisionSource.y + localClosestY;
|
||||
var pz:Number = collisionSource.z + localClosestZ;
|
||||
// Растояние от найденной точки эллипсоида до плоскости
|
||||
var closestPointDistance:Number = px * normalX + py * normalY + pz * normalZ - plane.node.offset;
|
||||
if (!plane.infront) {
|
||||
closestPointDistance = -closestPointDistance;
|
||||
}
|
||||
if (closestPointDistance > plane.sourceOffset) {
|
||||
// Найдена наиболее удалённая точка, расчитываем вторую
|
||||
px = collisionSource.x - localClosestX;
|
||||
py = collisionSource.y - localClosestY;
|
||||
pz = collisionSource.z - localClosestZ;
|
||||
closestPointDistance = px * normalX + py * normalY + pz * normalZ - plane.node.offset;
|
||||
if (!plane.infront) {
|
||||
closestPointDistance = -closestPointDistance;
|
||||
}
|
||||
}
|
||||
// Если расстояние от ближайшей точки эллипсоида до плоскости больше, чем смещение эллипсоида вдоль нормали плоскости,
|
||||
// то столкновения не произошло и нужно завершить выполнение функции
|
||||
if (closestPointDistance > displacementAlongNormal) {
|
||||
return;
|
||||
}
|
||||
// Если добрались до этого места, значит произошло столкновение с плоскостью. Требуется определить точку столкновения
|
||||
// с ближайшим полигоном, лежащим в этой плоскости
|
||||
if (closestPointDistance <= 0 ) {
|
||||
// Эллипсоид пересекается с плоскостью, ищем проекцию ближайшей точки эллипсоида на плоскость
|
||||
if (plane.infront) {
|
||||
collisionPlanePoint.x = px - normalX * closestPointDistance;
|
||||
collisionPlanePoint.y = py - normalY * closestPointDistance;
|
||||
collisionPlanePoint.z = pz - normalZ * closestPointDistance;
|
||||
} else {
|
||||
collisionPlanePoint.x = px + normalX * closestPointDistance;
|
||||
collisionPlanePoint.y = py + normalY * closestPointDistance;
|
||||
collisionPlanePoint.z = pz + normalZ * closestPointDistance;
|
||||
}
|
||||
} else {
|
||||
// Эллипсоид не пересекается с плоскостью, ищем точку контакта
|
||||
var t:Number = closestPointDistance / displacementAlongNormal;
|
||||
collisionPlanePoint.x = px + currentDisplacement.x * t;
|
||||
collisionPlanePoint.y = py + currentDisplacement.y * t;
|
||||
collisionPlanePoint.z = pz + currentDisplacement.z * t;
|
||||
}
|
||||
// Проверяем примитивы плоскости
|
||||
var primitive:*;
|
||||
collisionPrimitiveNearestLengthSqr = Number.MAX_VALUE;
|
||||
collisionPrimitiveNearest = null;
|
||||
if (plane.infront) {
|
||||
if ((primitive = plane.node.primitive) != null) {
|
||||
if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
|
||||
((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
|
||||
calculateCollisionWithPrimitiveE(primitive);
|
||||
}
|
||||
} else {
|
||||
for (primitive in plane.node.frontPrimitives) {
|
||||
if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
|
||||
((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
|
||||
calculateCollisionWithPrimitiveE(primitive);
|
||||
if (collisionPrimitive != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (primitive in plane.node.backPrimitives) {
|
||||
if (((_collisionSetMode == CollisionSetMode.EXCLUDE) && (collisionSet == null || !(collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) ||
|
||||
((_collisionSetMode == CollisionSetMode.INCLUDE) && (collisionSet != null) && (collisionSet[primitive.face._mesh] || collisionSet[primitive.face._surface]))) {
|
||||
calculateCollisionWithPrimitiveE(primitive);
|
||||
if (collisionPrimitive != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (collisionPrimitive != null) {
|
||||
// Если точка пересечения попала в примитив
|
||||
// Нормаль плоскости при столкновении - нормаль плоскости примитива
|
||||
if (plane.infront) {
|
||||
collisionNormal.x = normalX;
|
||||
collisionNormal.y = normalY;
|
||||
collisionNormal.z = normalZ;
|
||||
collisionOffset = plane.node.offset;
|
||||
} else {
|
||||
collisionNormal.x = -normalX;
|
||||
collisionNormal.y = -normalY;
|
||||
collisionNormal.z = -normalZ;
|
||||
collisionOffset = -plane.node.offset;
|
||||
}
|
||||
// Радиус эллипсоида в точке столкновения
|
||||
collisionRadius = localClosestX * collisionNormal.x + localClosestY * collisionNormal.y + localClosestZ * collisionNormal.z;
|
||||
if (collisionRadius < 0) {
|
||||
collisionRadius = -collisionRadius;
|
||||
}
|
||||
radiusVector.x = px - collisionSource.x;
|
||||
radiusVector.y = py - collisionSource.y;
|
||||
radiusVector.z = pz - collisionSource.z;
|
||||
// Точка столкновения совпадает с точкой столкновения с плоскостью примитива
|
||||
collisionPoint.x = collisionPlanePoint.x;
|
||||
collisionPoint.y = collisionPlanePoint.y;
|
||||
collisionPoint.z = collisionPlanePoint.z;
|
||||
} else {
|
||||
// Если точка пересечения не попала внутрь примитива, находим пересечение с ближайшей точкой ближайшего примитива
|
||||
// Трансформированная в пространство эллипсоида ближайшая точка на примитиве
|
||||
px = collisionPrimitivePoint.x;
|
||||
py = collisionPrimitivePoint.y;
|
||||
pz = collisionPrimitivePoint.z;
|
||||
|
||||
var collisionExists:Boolean;
|
||||
// Квадрат расстояния из центра эллипсоида до точки примитива
|
||||
var r2:Number = px*px + py*py + pz*pz;
|
||||
if (r2 < _radius2) {
|
||||
// Точка оказалась внутри эллипсоида, находим точку на поверхности эллипсоида, лежащую на том же радиусе
|
||||
k = _radius / Math.sqrt(r2);
|
||||
px *= k * _scaleX;
|
||||
py *= k * _scaleY;
|
||||
pz *= k * _scaleZ;
|
||||
|
||||
collisionExists = true;
|
||||
} else {
|
||||
// Точка вне эллипсоида, находим пересечение луча, направленного противоположно скорости эллипсоида из точки
|
||||
// примитива, с поверхностью эллипсоида
|
||||
// Трансформированный в пространство эллипсоида противоположный вектор скорости
|
||||
var vx:Number = - currentDisplacement.x / _scaleX;
|
||||
var vy:Number = - currentDisplacement.y / _scaleY;
|
||||
var vz:Number = - currentDisplacement.z / _scaleZ;
|
||||
// Нахождение точки пересечения сферы и луча, направленного вдоль вектора скорости
|
||||
var a:Number = vx*vx + vy*vy + vz*vz;
|
||||
var b:Number = 2 * (px*vx + py*vy + pz*vz);
|
||||
var c:Number = r2 - _radius2;
|
||||
var d:Number = b*b - 4*a*c;
|
||||
// Решение есть только при действительном дискриминанте квадратного уравнения
|
||||
if (d >=0) {
|
||||
// Выбирается минимальное время, т.к. нужна первая точка пересечения
|
||||
t = -0.5 * (b + Math.sqrt(d)) / a;
|
||||
// Точка лежит на луче только если время положительное
|
||||
if (t >= 0 && t <= 1) {
|
||||
// Координаты точки пересечения луча с эллипсоидом, переведённые обратно в нормальное пространство
|
||||
px = (px + t * vx) * _scaleX;
|
||||
py = (py + t * vy) * _scaleY;
|
||||
pz = (pz + t * vz) * _scaleZ;
|
||||
|
||||
collisionExists = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (collisionExists) {
|
||||
// Противоположная нормаль к эллипсоиду в точке пересечения
|
||||
collisionNormal.x = - px / _scaleX2;
|
||||
collisionNormal.y = - py / _scaleY2;
|
||||
collisionNormal.z = - pz / _scaleZ2;
|
||||
collisionNormal.normalize();
|
||||
// Радиус эллипсоида в точке столкновения
|
||||
collisionRadius = px * collisionNormal.x + py * collisionNormal.y + pz * collisionNormal.z;
|
||||
if (collisionRadius < 0) {
|
||||
collisionRadius = -collisionRadius;
|
||||
}
|
||||
radiusVector.x = px;
|
||||
radiusVector.y = py;
|
||||
radiusVector.z = pz;
|
||||
// Точка столкновения в ближайшей точке
|
||||
collisionPoint.x = collisionPrimitivePoint.x * _scaleX + currentCoords.x;
|
||||
collisionPoint.y = collisionPrimitivePoint.y * _scaleY + currentCoords.y;
|
||||
collisionPoint.z = collisionPrimitivePoint.z * _scaleZ + currentCoords.z;
|
||||
// Смещение плоскости столкновения
|
||||
collisionOffset = collisionPoint.x * collisionNormal.x + collisionPoint.y * collisionNormal.y + collisionPoint.z * collisionNormal.z;
|
||||
collisionPrimitive = collisionPrimitiveNearest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Определение наличия столкновения эллипсоида с примитивом. Все расчёты выполняются в пространстве эллипсоида, где он выглядит
|
||||
* как сфера. По окончании работы может быть установлена переменная collisionPrimitive в случае попадания точки
|
||||
* столкновения внутрь примитива или collisionPrimitiveNearest в случае столкновения с ребром примитива через
|
||||
* минимальное время.
|
||||
*
|
||||
* @param primitive примитив, столкновение с которым проверяется
|
||||
*/
|
||||
private function calculateCollisionWithPrimitiveE(primitive:PolyPrimitive):void {
|
||||
var length:uint = primitive.num;
|
||||
var points:Array = primitive.points;
|
||||
var normal:Point3D = primitive.face.globalNormal;
|
||||
var inside:Boolean = true;
|
||||
|
||||
var point1:Point3D;
|
||||
var point2:Point3D = points[length - 1];
|
||||
p2.x = (point2.x - currentCoords.x) / _scaleX;
|
||||
p2.y = (point2.y - currentCoords.y) / _scaleY;
|
||||
p2.z = (point2.z - currentCoords.z) / _scaleZ;
|
||||
|
||||
localCollisionPlanePoint.x = (collisionPlanePoint.x - currentCoords.x) / _scaleX;
|
||||
localCollisionPlanePoint.y = (collisionPlanePoint.y - currentCoords.y) / _scaleY;
|
||||
localCollisionPlanePoint.z = (collisionPlanePoint.z - currentCoords.z) / _scaleZ;
|
||||
// Обход всех рёбер примитива
|
||||
for (var i:uint = 0; i < length; i++) {
|
||||
point1 = point2;
|
||||
point2 = points[i];
|
||||
|
||||
p1.x = p2.x;
|
||||
p1.y = p2.y;
|
||||
p1.z = p2.z;
|
||||
|
||||
p2.x = (point2.x - currentCoords.x) / _scaleX;
|
||||
p2.y = (point2.y - currentCoords.y) / _scaleY;
|
||||
p2.z = (point2.z - currentCoords.z) / _scaleZ;
|
||||
|
||||
// Расчёт векторного произведения вектора ребра на радиус-вектор точки столкновения относительно начала ребра
|
||||
// с целью определения положения точки столкновения относительно полигона
|
||||
var edgeX:Number = p2.x - p1.x;
|
||||
var edgeY:Number = p2.y - p1.y;
|
||||
var edgeZ:Number = p2.z - p1.z;
|
||||
|
||||
var vectorX:Number = localCollisionPlanePoint.x - p1.x;
|
||||
var vectorY:Number = localCollisionPlanePoint.y - p1.y;
|
||||
var vectorZ:Number = localCollisionPlanePoint.z - p1.z;
|
||||
|
||||
var crossX:Number = vectorY * edgeZ - vectorZ * edgeY;
|
||||
var crossY:Number = vectorZ * edgeX - vectorX * edgeZ;
|
||||
var crossZ:Number = vectorX * edgeY - vectorY * edgeX;
|
||||
|
||||
if (crossX * normal.x + crossY * normal.y + crossZ * normal.z > 0) {
|
||||
// Точка за пределами полигона
|
||||
inside = false;
|
||||
|
||||
var edgeLengthSqr:Number = edgeX * edgeX + edgeY * edgeY + edgeZ * edgeZ;
|
||||
var edgeDistanceSqr:Number = (crossX * crossX + crossY * crossY + crossZ * crossZ) / edgeLengthSqr;
|
||||
|
||||
// Если расстояние до прямой меньше текущего ближайшего
|
||||
if (edgeDistanceSqr < collisionPrimitiveNearestLengthSqr) {
|
||||
// Ищем нормализованный вектор ребра
|
||||
var edgeLength:Number = Math.sqrt(edgeLengthSqr);
|
||||
var edgeNormX:Number = edgeX / edgeLength;
|
||||
var edgeNormY:Number = edgeY / edgeLength;
|
||||
var edgeNormZ:Number = edgeZ / edgeLength;
|
||||
// Находим расстояние до точки перпендикуляра вдоль ребра
|
||||
var t:Number = edgeNormX * vectorX + edgeNormY * vectorY + edgeNormZ * vectorZ;
|
||||
var vectorLengthSqr:Number;
|
||||
if (t < 0) {
|
||||
// Ближайшая точка - первая
|
||||
vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
|
||||
if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
|
||||
collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
|
||||
collisionPrimitivePoint.x = p1.x;
|
||||
collisionPrimitivePoint.y = p1.y;
|
||||
collisionPrimitivePoint.z = p1.z;
|
||||
collisionPrimitiveNearest = primitive;
|
||||
}
|
||||
} else {
|
||||
if (t > edgeLength) {
|
||||
// Ближайшая точка - вторая
|
||||
vectorX = localCollisionPlanePoint.x - p2.x;
|
||||
vectorY = localCollisionPlanePoint.y - p2.y;
|
||||
vectorZ = localCollisionPlanePoint.z - p2.z;
|
||||
vectorLengthSqr = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
|
||||
if (vectorLengthSqr < collisionPrimitiveNearestLengthSqr) {
|
||||
collisionPrimitiveNearestLengthSqr = vectorLengthSqr;
|
||||
collisionPrimitivePoint.x = p2.x;
|
||||
collisionPrimitivePoint.y = p2.y;
|
||||
collisionPrimitivePoint.z = p2.z;
|
||||
collisionPrimitiveNearest = primitive;
|
||||
}
|
||||
} else {
|
||||
// Ближайшая точка на ребре
|
||||
collisionPrimitiveNearestLengthSqr = edgeDistanceSqr;
|
||||
collisionPrimitivePoint.x = p1.x + edgeNormX * t;
|
||||
collisionPrimitivePoint.y = p1.y + edgeNormY * t;
|
||||
collisionPrimitivePoint.z = p1.z + edgeNormZ * t;
|
||||
collisionPrimitiveNearest = primitive;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Если попали в примитив
|
||||
if (inside) {
|
||||
collisionPrimitive = primitive;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
316
Alternativa3D5/5.4/alternativa/engine3d/primitives/Box.as
Normal file
316
Alternativa3D5/5.4/alternativa/engine3d/primitives/Box.as
Normal file
@@ -0,0 +1,316 @@
|
||||
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 Box extends Mesh {
|
||||
|
||||
/**
|
||||
* Создание нового параллелепипеда.
|
||||
* <p>Параллелепипед после создания будет содержать в себе шесть поверхностей.
|
||||
* <code>"front"</code>, <code>"back"</code>, <code>"left"</code>, <code>"right"</code>, <code>"top"</code>, <code>"bottom"</code>
|
||||
* на каждую из которых может быть установлен свой материал.</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user