Files
alternativa3d-archive/Alternativa3D5/5.4/alternativa/engine3d/controllers/WalkController.as
Pyogenics c58621fb99 Add A3D5
2024-09-28 17:29:26 +01:00

508 lines
20 KiB
ActionScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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