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; /** * Контроллер, реализующий управление движением объекта, находящегося в системе координат корневого объекта сцены. * *
Контроллер предоставляет два режима движения: режим ходьбы с учётом силы тяжести и режим полёта, в котором сила * тяжести не учитывается. В обоих режимах может быть включена проверка столкновений с объектами сцены. Если проверка * столкновений отключена, то в режиме ходьбы сила тяжести также игнорируется и дополнительно появляется возможность * движения по вертикали. * *
Для всех объектов, за исключением Camera3D, направлением "вперёд" считается направление его оси
	 * Y, направлением "вверх" — направление оси Z. Для объектов класса
	 * Camera3D направление "вперёд" совпадает с направлением локальной оси Z, а направление
	 * "вверх" противоположно направлению локальной оси Y.
	 * 
	 * 
Вне зависимости от того, включена проверка столкновений или нет, координаты при перемещении расчитываются для
	 * эллипсоида, параметры которого устанавливаются через свойство collider. Координаты управляемого
	 * объекта вычисляются исходя из положения центра эллипсоида и положения объекта на вертикальной оси эллипсоида,
	 * задаваемого параметром objectZPosition.
	 * 
	 * 
Команда ACTION_UP в режиме ходьбы при ненулевой гравитации вызывает прыжок, в остальных случаях
	 * происходит движение вверх.
	 */	
	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;
		}
		
		/**
		 * Максимальный угол наклона поверхности в радианах, на которой возможен прыжок и на которой объект стоит на месте
		 * в отсутствие управляющих воздействий. Если угол наклона поверхности превышает заданное значение, свойство
		 * onGround будет иметь значение false.
		 * 
		 * @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;
		}
		
		/**
		 * Индикатор положения эллипсоида на поверхности в режиме ходьбы. Считается, что эллипсоид находится на поверхности,
		 * если угол наклона поверхности под ним не превышает заданного свойством maxGroundAngle значения.
		 * 
		 * @see #maxGroundAngle 
		 */
		public function get onGround():Boolean {
			return _onGround;
		}
		/**
		 * Модуль текущей скорости.
		 */
		public function get currentSpeed():Number {
			return _currentSpeed;
		}
		
		/**
		 * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие:
		 * 
| Клавиша | Действие | 
|---|---|
| W | ACTION_FORWARD | 
| S | ACTION_BACK | 
| A | ACTION_LEFT | 
| D | ACTION_RIGHT | 
| SPACE | ACTION_UP | 
| CONTROL | ACTION_DOWN | 
| SHIFT | ACTION_ACCELERATE | 
| UP | ACTION_PITCH_UP | 
| DOWN | ACTION_PITCH_DOWN | 
| LEFT | ACTION_YAW_LEFT | 
| RIGHT | ACTION_YAW_RIGHT | 
| M | ACTION_MOUSE_LOOK | 
objectZPosition.
		 * 
		 * @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;
		}
	}
}