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; /** * Контроллер, реализующий управление, подобное управлению летательным аппаратом для объекта, находящегося в системе * координат корневого объекта сцены. Повороты выполняются вокруг локальных осей объекта, собственные ускорения * действуют вдоль локальных осей. * *

Соответствия локальных осей для объектов, не являющихся камерой: * * * * * * * * * * * * * *
ОсьНаправлениеПоворот
XВправоТангаж
YВперёдКрен
ZВверхРысканье
* *

Соответствия локальных осей для объектов, являющихся камерой: * * * * * * * * * * * * * *
ОсьНаправлениеПоворот
XВправоТангаж
YВнизРысканье
ZВперёдКрен
* * Поворот мышью реализован следующим образом: в момент активации режима поворота (нажата левая кнопка мыши или * соответствующая кнопка на клавиатуре) текущее положение курсора становится точкой, относительно которой определяются * дальнейшие отклонения. Отклонение курсора по вертикали в пикселях, умноженное на коэффициент чувствительности мыши * по вертикали даёт угловую скорость по тангажу. Отклонение курсора по горизонтали в пикселях, умноженное на коэффициент * чувствительности мыши по горизонтали даёт угловую скорость по крену. */ 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; } /** * Установка привязки клавиш по умолчанию. Данный метод очищает все существующие привязки клавиш и устанавливает следующие: * * * * * * * * * * * * * * * *
КлавишаДействие
WACTION_FORWARD
SACTION_BACK
AACTION_LEFT
DACTION_RIGHT
SPACEACTION_UP
CONTROLACTION_DOWN
UPACTION_PITCH_UP
DOWNACTION_PITCH_DOWN
LEFTACTION_ROLL_LEFT
RIGHTACTION_ROLL_RIGHT
QACTION_YAW_LEFT
EACTION_YAW_RIGHT
MACTION_MOUSE_LOOK
*/ 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); } } }