Files
alternativa3d-archive/Alternativa3D5/5.0.0/alternativa/engine3d/controllers/.svn/text-base/CameraController.as.svn-base
2024-10-05 12:11:16 +01:00

752 lines
24 KiB
Plaintext
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.physics.SphereCollider;
import alternativa.types.Map;
import alternativa.types.Matrix3D;
import alternativa.types.Point3D;
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";
// флаги действий
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 _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 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;
/**
* Создание экземпляра класса.
*
* @param eventsSourceObject объект, используемый для получения событий мыши и клавиатуры
*/
public function CameraController(eventsSourceObject:DisplayObject) {
if (eventsSourceObject == null) {
throw new ArgumentError("CameraController: eventsSource is null");
}
_eventsSource = eventsSourceObject;
actionBindings[ACTION_FORWARD] = setForwardFlag;
actionBindings[ACTION_BACK] = setBackFlag;
actionBindings[ACTION_LEFT] = setLeftFlag;
actionBindings[ACTION_RIGHT] = setRightFlag;
actionBindings[ACTION_UP] = setUpFlag;
actionBindings[ACTION_DOWN] = setDownFlag;
actionBindings[ACTION_PITCH_UP] = setPitchUpFlag;
actionBindings[ACTION_PITCH_DOWN] = setPitchDownFlag;
actionBindings[ACTION_YAW_LEFT] = setYawLeftFlag;
actionBindings[ACTION_YAW_RIGHT] = setYawRightFlag;
}
/**
* Установка привязки клавиш по умолчанию.
*/
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.SHIFT, 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);
}
/**
* Направление камеры на точку.
*
* @param point координаты точки направления камеры
*/
public function lookAt(point:Point3D):void {
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;
}
/**
* Источник событий клавиатуры и мыши.
*/
public function get eventsSource():DisplayObject {
return _eventsSource;
}
/**
* @private
*/
public function set eventsSource(value:DisplayObject):void {
if (value == null) {
throw new ArgumentError("CameraController: eventsSource is null");
}
if (_eventsSource != value) {
if (_controlsEnabled) {
unregisterEventsListeners();
}
_eventsSource = value;
if (_controlsEnabled) {
registerEventListeners();
}
}
}
/**
* Текущая управляемая камера.
*/
public function get camera():Camera3D {
return _camera;
}
/**
* @private
*/
public function set camera(value:Camera3D):void {
if (value == null) {
controlsEnabled = false;
}
_camera = value;
}
/**
* Режим движения камеры. Если значение равно <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 {
if (_camera.scene == null) {
return;
}
if (_checkCollisions != value) {
_checkCollisions = value;
if (_checkCollisions && _collider == null) {
_collider = new SphereCollider(_camera.scene, _collisionRadius);
_collider.offsetThreshold = 0.01;
}
}
}
/**
* Радиус сферы для определения столкновений.
*
* @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();
}
/**
* @private
* @param value
*/
private function setForwardFlag(value:Boolean):void {
_forward = value;
}
/**
* @private
* @param value
*/
private function setBackFlag(value:Boolean):void {
_back = value;
}
/**
* @private
* @param value
*/
private function setLeftFlag(value:Boolean):void {
_left = value;
}
/**
* @private
* @param value
*/
private function setRightFlag(value:Boolean):void {
_right = value;
}
/**
* @private
* @param value
*/
private function setUpFlag(value:Boolean):void {
_up = value;
}
/**
* @private
* @param value
*/
private function setDownFlag(value:Boolean):void {
_down = value;
}
/**
* @private
* @param value
*/
private function setPitchUpFlag(value:Boolean):void {
_pitchUp = value;
}
/**
* @private
* @param value
*/
private function setPitchDownFlag(value:Boolean):void {
_pitchDown = value;
}
/**
* @private
* @param value
*/
private function setYawLeftFlag(value:Boolean):void {
_yawLeft = value;
}
/**
* @private
* @param value
*/
private function setYawRightFlag(value:Boolean):void {
_yawRight = value;
}
/**
* Чувствительность мыши.
*
* @default 1
*/
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;
}
/**
* Активность управления камеры.
*
* @default false
*/
public function get controlsEnabled():Boolean {
return _controlsEnabled;
}
/**
* @private
*/
public function set controlsEnabled(state:Boolean):void {
if (_camera == null || _controlsEnabled == state) return;
if (state) {
lastFrameTime = getTimer();
registerEventListeners();
}
else {
unregisterEventsListeners();
}
_controlsEnabled = state;
}
/**
* Базовый шаг изменения угла зрения в радианах. Реальный шаг получаеся умножением этого значения на величину
* <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;
}
/**
* Коэффициент изменения коэффициента увеличения.
*
* @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) {
_camera.rotationX += _pitchSpeed * frameTime;
} else if (_pitchDown) {
_camera.rotationX -= _pitchSpeed * frameTime;
}
// TODO: Поворот относительно продольной оси (крен, roll)
var frameDistance:Number = _speed * frameTime;
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;
}
}
}
}