mirror of
https://github.com/MapMakersAndProgrammers/alternativa3d-archive.git
synced 2025-10-26 09:49:07 -07:00
508 lines
20 KiB
ActionScript
508 lines
20 KiB
ActionScript
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;
|
||
}
|
||
}
|
||
} |