package alternativa.engine3d.controllers { import alternativa.engine3d.core.Camera3D; import alternativa.engine3d.core.Object3D; import flash.display.InteractiveObject; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.geom.Point; import flash.geom.Vector3D; import flash.ui.Keyboard; import flash.utils.getTimer; /** * */ public class SimpleObjectController { /** * Имя действия для привязки клавиш движения вперёд. */ 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_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"; /** * Имя действия для привязки клавиш увеличения скорости. */ public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE"; /** * Имя действия для привязки клавиш активации обзора мышью. */ public static const ACTION_MOUSE_LOOK:String = "ACTION_MOUSE_LOOK"; public var speed:Number; public var speedMultiplier:Number; public var mouseSensitivity:Number; public var maxPitch:Number = Number.MAX_VALUE; public var minPitch:Number = -Number.MAX_VALUE; private var eventSource:InteractiveObject; private var _object:Object3D; private var _up:Boolean; private var _down:Boolean; private var _forward:Boolean; private var _back:Boolean; private var _left:Boolean; private var _right:Boolean; private var _accelerate:Boolean; private var displacement:Vector3D = new Vector3D(); private var mousePoint:Point = new Point(); private var mouseLook:Boolean; private var objectTransform:Vector.; private var time:int; /** * Ассоциативный массив, связывающий имена команд с реализующими их функциями. Функции должны иметь вид * function(value:Boolean):void. Значение параметра value указывает, нажата или отпущена соответсвующая команде * клавиша. */ private var actionBindings:Object = {}; /** * Ассоциативный массив, связывающий коды клавиатурных клавиш с именами команд. */ protected var keyBindings:Object = {}; /** * * @param eventSource источник событий для контроллера * @param speed скорость поступательного перемещения объекта * @param mouseSensitivity чувствительность мыши - количество градусов поворота на один пиксель перемещения мыши */ public function SimpleObjectController(eventSource:InteractiveObject, object:Object3D, speed:Number, speedMultiplier:Number = 3, mouseSensitivity:Number = 1) { this.eventSource = eventSource; this.object = object; this.speed = speed; this.speedMultiplier = speedMultiplier; this.mouseSensitivity = mouseSensitivity; actionBindings[ACTION_FORWARD] = moveForward; actionBindings[ACTION_BACK] = moveBack; actionBindings[ACTION_LEFT] = moveLeft; actionBindings[ACTION_RIGHT] = moveRight; actionBindings[ACTION_UP] = moveUp; actionBindings[ACTION_DOWN] = moveDown; actionBindings[ACTION_ACCELERATE] = accelerate; setDefaultBindings(); enable(); } /** * Активирует контроллер. */ public function enable():void { eventSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey); eventSource.addEventListener(KeyboardEvent.KEY_UP, onKey); eventSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); eventSource.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); } /** * Дективирует контроллер. */ public function disable():void { eventSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey); eventSource.removeEventListener(KeyboardEvent.KEY_UP, onKey); eventSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); eventSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); stopMouseLook(); } /** * */ private function onMouseDown(e:MouseEvent):void { startMouseLook(); } /** * */ private function onMouseUp(e:MouseEvent):void { stopMouseLook(); } /** * Включает режим взгляда мышью. */ public function startMouseLook():void { mousePoint.x = eventSource.mouseX; mousePoint.y = eventSource.mouseY; mouseLook = true; } /** * Отключает режим взгляда мышью. */ public function stopMouseLook():void { mouseLook = false; } /** * */ private function onKey(e:KeyboardEvent):void { var method:Function = keyBindings[e.keyCode]; if (method != null) method.call(this, e.type == KeyboardEvent.KEY_DOWN); } /** * Управляемый объект. */ public function set object(value:Object3D):void { _object = value; updateObjectTransform(); } /** * */ public function get object():Object3D { return _object; } /** * Обновляет инофрмацию о трансформации объекта. Метод следует вызывать после изменения матрицы объекта вне контролллера. */ public function updateObjectTransform():void { if (_object != null) objectTransform = _object.matrix.decompose(); } /** * Вычисляет новое положение объекта, используя внутренний счётчик времени. */ public function update():void { if (_object == null) return; var frameTime:Number = time; time = getTimer(); frameTime = 0.001*(time - frameTime); if (frameTime > 0.1) frameTime = 0.1; var moved:Boolean = false; if (mouseLook) { var dx:Number = eventSource.mouseX - mousePoint.x; var dy:Number = eventSource.mouseY - mousePoint.y; mousePoint.x = eventSource.mouseX; mousePoint.y = eventSource.mouseY; var v:Vector3D = objectTransform[1]; v.x -= dy*Math.PI/180*mouseSensitivity; if (v.x > maxPitch) v.x = maxPitch; if (v.x < minPitch) v.x = minPitch; v.z -= dx*Math.PI/180*mouseSensitivity; moved = true; } displacement.x = _right ? 1 : (_left ? -1 : 0); displacement.y = _forward ? 1 : (_back ? -1 : 0); displacement.z = _up ? 1 : (_down ? -1 : 0); if (displacement.lengthSquared > 0) { if (_object is Camera3D) { var tmp:Number = displacement.z; displacement.z = displacement.y; displacement.y = -tmp; } deltaTransformVector(displacement); if (_accelerate) displacement.scaleBy(speedMultiplier*speed*frameTime/displacement.length); else displacement.scaleBy(speed*frameTime/displacement.length); (objectTransform[0] as Vector3D).incrementBy(displacement); moved = true; } if (moved) _object.matrix.recompose(objectTransform); } /** * * @param pos */ public function setObjectPos(pos:Vector3D):void { if (_object != null) { var v:Vector3D = objectTransform[0]; v.x = pos.x; v.y = pos.y; v.z = pos.z; } } /** * * @param pos */ public function setObjectPosXYZ(x:Number, y:Number, z:Number):void { if (_object != null) { var v:Vector3D = objectTransform[0]; v.x = x; v.y = y; v.z = z; } } /** * * @param point */ public function lookAt(point:Vector3D):void { lookAtXYZ(point.x, point.y, point.z); } /** * * @param x * @param y * @param z */ public function lookAtXYZ(x:Number, y:Number, z:Number):void { if (_object == null) return; var v:Vector3D = objectTransform[0]; var dx:Number = x - v.x; var dy:Number = y - v.y; var dz:Number = z - v.z; v = objectTransform[1]; v.x = Math.atan2(dz, Math.sqrt(dx*dx + dy*dy)); if (_object is Camera3D) v.x -= 0.5*Math.PI; v.y = 0; v.z = -Math.atan2(dx, dy); _object.matrix.recompose(objectTransform); } private var _vin:Vector. = new Vector.(3); private var _vout:Vector. = new Vector.(3); private function deltaTransformVector(v:Vector3D):void { _vin[0] = v.x; _vin[1] = v.y; _vin[2] = v.z; _object.matrix.transformVectors(_vin, _vout); var c:Vector3D = objectTransform[0]; v.x = _vout[0] - c.x; v.y = _vout[1] - c.y; v.z = _vout[2] - c.z; } /** * Активация движения вперёд. * * @param value true для начала движения, false для окончания */ public function moveForward(value:Boolean):void { _forward = value; } /** * Активация движения назад. * * @param value true для начала движения, false для окончания */ public function moveBack(value:Boolean):void { _back = value; } /** * Активация движения влево. * * @param value true для начала движения, false для окончания */ public function moveLeft(value:Boolean):void { _left = value; } /** * Активация движения вправо. * * @param value true для начала движения, false для окончания */ public function moveRight(value:Boolean):void { _right = value; } /** * Активация движения вверх. * * @param value true для начала движения, false для окончания */ public function moveUp(value:Boolean):void { _up = value; } /** * Активация движения вниз. * * @param value true для начала движения, false для окончания */ public function moveDown(value:Boolean):void { _down = value; } /** * Активация режима увеличенной скорости. * * @param value true для включения ускорения, false для выключения */ public function accelerate(value:Boolean):void { _accelerate = value; } /** * Метод выполняет привязку клавиши к действию. Одной клавише может быть назначено только одно действие. * * @param keyCode код клавиши * @param action наименование действия * * @see #unbindKey() * @see #unbindAll() */ public function bindKey(keyCode:uint, action:String):void { var method:Function = actionBindings[action]; if (method != null) keyBindings[keyCode] = method; } /** * */ public function bindKeys(bindings:Array):void { for (var i:int = 0; i < bindings.length; i += 2) bindKey(bindings[i], bindings[i + 1]); } /** * Очистка привязки клавиши. * * @param keyCode код клавиши * * @see #bindKey() * @see #unbindAll() */ public function unbindKey(keyCode:uint):void { delete keyBindings[keyCode]; } /** * Очистка привязки всех клавиш. * * @see #bindKey() * @see #unbindKey() */ public function unbindAll():void { for (var key:* in keyBindings) delete keyBindings[key]; } /** * Метод устанавливает привязки клавиш по умолчанию. Реализация по умолчанию не делает ничего. * * @see #bindKey() * @see #unbindKey() * @see #unbindAll() */ public function setDefaultBindings():void { bindKey(87, ACTION_FORWARD); bindKey(83, ACTION_BACK); bindKey(65, ACTION_LEFT); bindKey(68, ACTION_RIGHT); bindKey(69, ACTION_UP); bindKey(67, ACTION_DOWN); bindKey(Keyboard.SHIFT, ACTION_ACCELERATE); bindKey(Keyboard.UP, ACTION_FORWARD); bindKey(Keyboard.DOWN, ACTION_BACK); bindKey(Keyboard.LEFT, ACTION_LEFT); bindKey(Keyboard.RIGHT, ACTION_RIGHT); } } }