| Scripts/reversecontrol.js | this.name        = "reversecontrol";
this.author      = "Norby, cag";
this.copyright   = "2014 Norbert Nagy";
this.licence     = "CC BY-NC-SA 3.0";
this.description = "Reverse up-down control in the aft view.";
(function(){
	"use strict";
	
//customizable properties
this.$ReverseControlOn = true;	//can turn off without uninstall
this.$InertiaOn = true; 		// "
//internal properties, should not touch
this.$AxesWritable = oolite.compareVersion("1.87") <= 0;
this.$ReverseControlFCB = null; //FrameCallBack
this.$revCtrlCfg = {	Name:		this.name, 
						Display: 	"Reverse Control", 
						Alive:	  	"$revCtrlCfg",
						Notify:		"$versionCheck",
						Bool: {  	B0: { 	Name: 	"$ReverseControlOn", 
											Def:	true, 
											Desc:	"Toggle reverse ctrls"
										}, 
									B1: { 	Name: 	"$InertiaOn", 
											Def:	true, 
											Desc:	"Toggle inertia"
										}, 
									Info:	"Inertia for keyboard only (sticks already have it). Settings are independent; you can set either, both or none. NB: Inertia requires a minimum Oolite version of 1.87"  
							}
					};
					
this.$versionCheck = function versionCheck() {
	var that = versionCheck;
	var wr = (that.wr = that.wr || worldScripts.reversecontrol);
	var writable = oolite.compareVersion("1.87") <= 0;
	if( wr.$InertiaOn && !writable ) {
		wr.$ReverseControl_FCB.inertiaOn = wr.$InertiaOn = false;
		log(this.name, 'this version of Oolite (' + oolite.version + ') cannot support Inertia (need at least 1.87).  Inertia has been disabled.');
	}
}
//world script events
this.startUpComplete = function () {
	var wLC = worldScripts.Lib_Config;
	if( wLC ) {
		let error = wLC._registerSet( this.$revCtrlCfg );
		if( error !== 0 )
			log(this.name, 'startUpComplete, _registerSet returned error = ' + error );
	}
	this.$versionCheck();
}
this.shipWillLaunchFromStation = function shipWillLaunchFromStation() {
	var that = shipWillLaunchFromStation;
	var wr = (that.wr = that.wr || worldScripts.reversecontrol);
	if( wr.$InertiaOn ) { // fcb always on to catch axis reversal
		if( !isValidFrameCallback( wr.$ReverseControlFCB ) ) {
			wr.$ReverseControlFCB = addFrameCallback( wr.$ReverseControl_FCB.bind(wr) );
		}
	}
}
this.step1Up = function() {
}
this.shipWillDockWithStation = function(/*station*/) {
	this.$Stop( true );
}
this.$nearZero = 1e-6;			// some floating point numbers never === 0
this.viewDirectionChanged = function viewDirectionChanged( view ) {
	var that = viewDirectionChanged;
	var wr = (that.wr = that.wr || worldScripts.reversecontrol);
	var transferFn = (that.transferFn = that.transferFn || wr._transferInertia);// fn ref
	var inertiaFn = (that.inertiaFn = that.inertiaFn || wr._inInertialMotion);	// fn ref
	var nearZero = (that.nearZero = that.nearZero || wr.$nearZero);				// static var
	var revCtrlOn = wr.$ReverseControlOn;
	var inertiaOn = wr.$InertiaOn;
	if( inertiaOn ) {
		var ps = player.ship, pitch = ps.pitch, roll = ps.roll;
		var fcbFn = wr.$ReverseControl_FCB;
		var abs_pitch = pitch < 0 ? -pitch : pitch;
		var abs_roll = roll < 0 ? -roll : roll;
		if( abs_pitch > nearZero || abs_roll > nearZero ) { // have inertia to bleed off (doesn't apply to yaw as same in all views)
			let inertiaType = 0, PITCH = 1, ROLL = 2; 		// bitflags for axes			
			if( revCtrlOn ) 								// set pitch/roll in inertiaFn w/ cross axis values
				transferFn( view, ps, fcbFn.prevPitch, fcbFn.prevRoll ); // use fcb's prev values so 1st call to inertiaFn can calc diffs
			if( abs_pitch > nearZero ){						// only save significant pitch
				inertiaType |= PITCH;
				if( !revCtrlOn )							// transfer prevPitch value
					inertiaFn.pitch = fcbFn.prevPitch;
			}
			if( abs_roll > nearZero ){						// only save significant roll
				inertiaType |= ROLL;
				if( !revCtrlOn )							// transfer prevRoll value
					inertiaFn.roll = fcbFn.prevRoll;
			}
			fcbFn.isInertial = inertiaType;					// bleed off inertia before overriding input
			inertiaFn.keyboardInput = false;
		} else {
			transferFn.lastView = view;
			fcbFn.isInertial = 0;							// no inertia
		}
	}
	switch( view ) {
		case "VIEW_AFT":
		case "VIEW_PORT":
		case "VIEW_STARBOARD":
		case "VIEW_FORWARD":								// added for inertia
			if( view === 'VIEW_FORWARD' && !inertiaOn ) {
				wr.$Stop( true );
			} else if( (revCtrlOn || inertiaOn) && 
						!isValidFrameCallback( wr.$ReverseControlFCB ) ) {
				wr.$ReverseControlFCB = addFrameCallback( wr.$ReverseControl_FCB.bind(wr) );
			}
			break;
		default:
			wr.$Stop( !inertiaOn );
	}
}
// moved code in from core so this oxp no longer generates any garbage to be collected (speed is comparable)
this._rotate_about_axis = function _rotate_about_axis( quat, vector, angle, result ) {
	var that = _rotate_about_axis;
	var cos = (that.cos = that.cos || Math.cos);
	var sin = (that.sin = that.sin || Math.sin);
	var rotn = (that.rotn = that.rotn || [0, 0, 0, 0]);
	var a = angle / 2;
	var c = cos(a);
	var s = sin(a);
	// rotation quaternion
	rotn[0] = c;
	rotn[1] = vector[0] * s;
	rotn[2] = vector[1] * s;
	rotn[3] = vector[2] * s;
	// multiply quaternions
	result[0] = quat[0] * rotn[0] - quat[1] * rotn[1] - quat[2] * rotn[2] - quat[3] * rotn[3];
	result[1] = quat[0] * rotn[1] + quat[1] * rotn[0] + quat[2] * rotn[3] - quat[3] * rotn[2];
	result[2] = quat[0] * rotn[2] + quat[2] * rotn[0] + quat[3] * rotn[1] - quat[1] * rotn[3];
	result[3] = quat[0] * rotn[3] + quat[3] * rotn[0] + quat[1] * rotn[2] - quat[2] * rotn[1];
}
this._transferInertia = function _transferInertia( view, ps, pitch, roll ) {
	var that = _transferInertia;
	var wr = (that.wr = that.wr || worldScripts.reversecontrol);
	var inertiaFn = (that.inertiaFn = that.inertiaFn || wr._inInertialMotion);	// fn ref
	var nearZero = (that.nearZero = that.nearZero || wr.$nearZero);				// static var
	var lastView = (that.lastView = that.lastView || 'VIEW_FORWARD');	// store viewDirection to know where changed from
	var rpRatio = wr.$ReverseControl_FCB.rpRatio;
	var abs_pitch = pitch < 0 ? -pitch : pitch;
	var abs_roll = roll < 0 ? -roll : roll;
	var newPitch, newRoll;
	switch( lastView ) {
		case "VIEW_FORWARD":
			switch( view ) {
				case "VIEW_AFT":
					newPitch = abs_pitch > nearZero ? -pitch : 0;
					newRoll =  abs_roll > nearZero  ? -roll  : 0;
					break;
				case "VIEW_PORT":
					newPitch = abs_roll > nearZero ? roll / rpRatio   : 0;
					newRoll = abs_pitch > nearZero ? -pitch * rpRatio : 0;
					break;
				case "VIEW_STARBOARD":
					newPitch = abs_roll > nearZero ? -roll / rpRatio : 0;
					newRoll = abs_pitch > nearZero ? pitch * rpRatio : 0;
					break;
				default:	// needed in case hit F5 <d'Oh>
					return;
			}
			break;
		case "VIEW_AFT":
			switch( view ) {
				case "VIEW_FORWARD":
					newPitch = abs_pitch > nearZero ? -pitch : 0;
					newRoll =  abs_roll > nearZero  ? -roll  : 0;
					break;
				case "VIEW_PORT":
					newPitch = abs_roll > nearZero ? -roll / rpRatio : 0;
					newRoll = abs_pitch > nearZero ? pitch * rpRatio : 0;
					break;
				case "VIEW_STARBOARD":
					newPitch = abs_roll > nearZero ? roll / rpRatio   : 0;
					newRoll = abs_pitch > nearZero ? -pitch * rpRatio : 0;
					break;
				default:
					return;
			}
			break;
		case "VIEW_PORT":
			switch( view ) {
				case "VIEW_FORWARD":
					newPitch = abs_roll > nearZero ? -roll /rpRatio  : 0;
					newRoll = abs_pitch > nearZero ? pitch * rpRatio : 0;
					break;
				case "VIEW_AFT":
					newPitch = abs_roll > nearZero ? roll / rpRatio   : 0;
					newRoll = abs_pitch > nearZero ? -pitch * rpRatio : 0;
					break;
				case "VIEW_STARBOARD":
					newPitch = abs_pitch > nearZero ? -pitch : 0;
					newRoll =  abs_roll > nearZero  ? -roll  : 0;
					break;
				default:
					return;
			}
			break;
		case "VIEW_STARBOARD":
			switch( view ) {
				case "VIEW_FORWARD":
					newPitch = abs_roll > nearZero ? roll / rpRatio   : 0;
					newRoll = abs_pitch > nearZero ? -pitch * rpRatio : 0;
					break;
				case "VIEW_AFT":
					newPitch = abs_roll > nearZero ? -roll / rpRatio : 0;
					newRoll = abs_pitch > nearZero ? pitch * rpRatio : 0;
					break;
				case "VIEW_PORT":
					newPitch = abs_pitch > nearZero ? -pitch : 0;
					newRoll =  abs_roll > nearZero  ? -roll  : 0;
					break;
				default:
					return;
			}
			break;
	}
	inertiaFn.pitch = ps.pitch = newPitch;	// save inertia values
	inertiaFn.roll = ps.roll = newRoll;
	that.lastView = view;
}
this._inInertialMotion = function _inInertialMotion( delta, ps, view, isInertial, pitch, roll, yaw ) {
	/* core game differs in how it handles input from joystick/gamepad vs keyboard
	  - when you reverse an axis, say pitch down from an up pitch, the stick handlers reverse from the
		current value through zero and beyond to the opposite maxPitch.  When using the keyboard, a reverse
		immediately sets the rate to zero and proceeds to the maxPitch.  This has the unfortunate consequence
		of cancelling any inertia.  This function attempts to detect when a keyboard is being used and
		simulate the inertial effects that one sees when using a stick.  Stick users should not be
		impacted in any way (hopefully) :)
	*/
	var that = _inInertialMotion;
	var wr = (that.wr = that.wr || worldScripts.reversecontrol);
	var nearZero = (that.nearZero = that.nearZero || wr.$nearZero);
	var maxPitch = that.maxPitch = that.maxPitch === undefined ? player.ship.maxPitch : that.maxPitch;	// ship values that may chang
	var maxRoll = that.maxRoll = that.maxRoll === undefined ? player.ship.maxRoll : that.maxRoll;		//  - updated 1/sec in fcb
	var maxYaw = that.maxYaw = that.maxYaw === undefined ? player.ship.maxYaw : that.maxYaw;
	var keyboardInput = that.keyboardInput = that.keyboardInput === undefined ? 0 : that.keyboardInput;
	
/*	for keyboard: 									(src: PlayerEntityControls.m)
	  [self increase_flight_roll:isCtrlDown ? flightArrowKeyPrecisionFactor*roll_dampner*roll_delta : delta_t*roll_delta];
	  ...
	  if (!rolling) ... [self decrease_flight_roll:roll_dampner];
	where #define ROLL_DAMPING_FACTOR		1.0f 	(src: PlayerEntity.h)
		  roll_delta = 2.0f * max_flight_roll; 		(src: PlayerEntity.m)
		  roll_dampner = ROLL_DAMPING_FACTOR * delta_t;
	- same goes for pitch & yaw
	- so change in axis is scaled to full +- range (thus 0 to full motion is thus 0.5 sec for all axes)
	- thus, max change frame to frame, maxDelta, is:  delta * 2 * max<axis>
*/
	function adjustKeyInput( axis, prev, curr, abs_curr, max, flag ) {		
		if( prev !== 0 ) {									// prev is set == 0 when it becomes insignificant
			var finished = false;
			let abs_prev = prev < 0 ? -prev : prev;
			if( abs_curr > nearZero ) {
				let diff = abs_prev - abs_curr;
				let abs_diff = diff < 0 ? -diff : diff;
				let maxDelta = delta * 2 * max + nearZero;
				if( curr * prev < 0							// change direction 
						||	(abs_diff > maxDelta &&			//   or keyboard input (core setting to zero!)
							 maxDelta + nearZero >= abs_curr) ) {
					keyboardInput = that.keyboardInput |= flag;
					let newValue = abs_curr > maxDelta ? maxDelta : abs_curr; // use core's value except on change of view
					curr = that[axis] = ps[axis] = prev + newValue * (prev > 0 ? -1 : 1);
					finished = curr * prev < 0;				// exit when inertia has been bled off, ie. cross zero
				} else {									// input agrees w/ inertial direction
					finished = true;
				}
			} else if( curr === 0 ) {						// zero => no input or both axis keys pressed, so dampen
				keyboardInput = that.keyboardInput |= flag;
				curr = that[axis] = ps[axis] = prev + delta * (prev > 0 ? -1 : 1);
				finished = abs_prev < delta;
			}
			if( finished )	{					
				prev = that[axis] = 0;
				keyboardInput = that.keyboardInput &= ~flag;
			}
		}
	}
	var PITCH = 1, ROLL = 2, YAW = 4, 						// bitflags for axes
		prevPitch, abs_pitch, prevRoll, abs_roll, prevYaw, abs_yaw;
	abs_pitch = pitch < 0 ? -pitch : pitch;
	abs_roll = roll < 0 ? -roll : roll;
	abs_yaw = yaw < 0 ? -yaw : yaw;
	prevPitch = that.pitch === undefined ? 0 : that.pitch;	// load saved values from previous frame
	prevRoll = that.roll === undefined ? 0 : that.roll;
	prevYaw = that.yaw === undefined ? 0 : that.yaw;
	that.pitch = abs_pitch > nearZero ? pitch : 0;			// only save significant pitch
	that.roll = abs_roll > nearZero ? roll : 0;
	that.yaw = abs_yaw > nearZero ? yaw : 0;
	adjustKeyInput( 'pitch', prevPitch, pitch, abs_pitch, maxPitch, PITCH );
	adjustKeyInput( 'roll', prevRoll, roll, abs_roll, maxRoll, ROLL );
	adjustKeyInput( 'yaw', prevYaw, yaw, abs_yaw, maxYaw, YAW );
	if( !keyboardInput || (prevPitch === 0 && prevRoll === 0 && prevYaw === 0) ) {
		keyboardInput = that.keyboardInput = 0;
		that.pitch = that.roll = that.yaw = 0;
		return false;
	}
	return keyboardInput;
}
/*		(function() {	// IIFE for neither
var wr = worldScripts.reversecontrol;
wr.$Stop( true );
wr.$ReverseControl_FCB.revCtrlOn = wr.$ReverseControlOn = false;
wr.$ReverseControl_FCB.inertiaOn = wr.$InertiaOn = false;
console.clearConsole();
log('ReverseControlOn = ' + wr.$ReverseControlOn + ', InertiaOn = ' + wr.$InertiaOn + 
	', FCB ' + (isValidFrameCallback(wr.$ReverseControlFCB) ? 'is running' : 'not registered') );
})() //*/
/*		(function() {	// IIFE for ReverseControl only
var wr = worldScripts.reversecontrol;
wr.$Stop( true );
wr.$ReverseControl_FCB.revCtrlOn = wr.$ReverseControlOn = true;
wr.$ReverseControl_FCB.inertiaOn = wr.$InertiaOn = false;
if( player.ship.viewDirection !== 'VIEW_FORWARD' )
	wr.$ReverseControlFCB = addFrameCallback( wr.$ReverseControl_FCB.bind(wr) );
console.clearConsole();
log('ReverseControlOn = ' + wr.$ReverseControlOn + ', InertiaOn = ' + wr.$InertiaOn +
	', FCB ' + (isValidFrameCallback(wr.$ReverseControlFCB) ? 'is running' : 'not registered') );
})() //*/
/*		(function() {	// IIFE for Inertia only
var wr = worldScripts.reversecontrol;
wr.$Stop( true );
wr.$ReverseControl_FCB.revCtrlOn = wr.$ReverseControlOn = false;
wr.$ReverseControl_FCB.inertiaOn = wr.$InertiaOn = true;
wr.$versionCheck();
if( wr.$InertiaOn )
	wr.$ReverseControlFCB = addFrameCallback( wr.$ReverseControl_FCB.bind(wr) );
console.clearConsole();
log('ReverseControlOn = ' + wr.$ReverseControlOn + ', InertiaOn = ' + wr.$InertiaOn +
	', FCB ' + (isValidFrameCallback(wr.$ReverseControlFCB) ? 'is running' : 'not registered') );
})() //*/
/*		(function() {	// IIFE for both
var wr = worldScripts.reversecontrol;
wr.$Stop( true );
wr.$ReverseControl_FCB.revCtrlOn = wr.$ReverseControlOn = true;
wr.$ReverseControl_FCB.inertiaOn = wr.$InertiaOn = true;
wr.$versionCheck();
if( wr.$InertiaOn )
	wr.$ReverseControlFCB = addFrameCallback( wr.$ReverseControl_FCB.bind(wr) );
console.clearConsole();
log('ReverseControlOn = ' + wr.$ReverseControlOn + ', InertiaOn = ' + wr.$InertiaOn +
	', FCB ' + (isValidFrameCallback(wr.$ReverseControlFCB) ? 'is running' : 'not registered') );
})() //*/
// 		console.script.wr = worldScripts.reversecontrol;
// 		console.script.ps = player.ship;
//		wr.$ReverseControlOn = true;
//		wr.$ReverseControlOn = false;
//		wr.$InertiaOn = true;
//		wr.$InertiaOn = false;
//		wr.$ReverseControlFCB
//		:time wr.$ReverseControl_FCB( 0.02 )
//		log('ReverseControlOn = ' + wr.$ReverseControlOn + ', InertiaOn = ' + wr.$InertiaOn );
/*		(function() {	// IIFE for debug console
var wr = worldScripts.reversecontrol;
wr.$Stop( true );
if( wr.$InertiaOn || player.ship.viewDirection !== 'VIEW_FORWARD' )
	wr.$ReverseControlFCB = addFrameCallback( wr.$ReverseControl_FCB.bind(wr) );
console.clearConsole();
log('ReverseControlOn = ' + wr.$ReverseControlOn + ', InertiaOn = ' + wr.$InertiaOn +
	', FCB ' + (isValidFrameCallback(wr.$ReverseControlFCB) ? 'is running' : 'not registered') );
})() //*/
//ReverseControl methods
this.$ReverseControl_FCB = function ReverseControl_FCB( delta ) { //FrameCallBack
	var that = ReverseControl_FCB;
	var wr = (that.wr = that.wr || worldScripts.reversecontrol);
	var _rotate_about_axis = (that._rotate_about_axis = that._rotate_about_axis || wr._rotate_about_axis);	// fn ref
	var _inInertialMotion = (that._inInertialMotion = that._inInertialMotion || wr._inInertialMotion);		//  "
	var nearZero = (that.nearZero = that.nearZero || wr.$nearZero);				// static var
	var revCtrlOn = (that.revCtrlOn = that.revCtrlOn || wr.$ReverseControlOn);	// static var's (can change in stn but new fcb created)
	var inertiaOn = (that.inertiaOn = that.inertiaOn || wr.$InertiaOn);			//  "
	var orient = (that.orient = that.orient || []);	// working quaternion
	var step1 = (that.step1 = that.step1 || []);	// working vector
	var step2 = (that.step2 = that.step2 || []);	//	"
	var prevVR = (that.prevVR = that.prevVR || []);	// vectorRight from previous frame
	var prevVF = (that.prevVF = that.prevVF || []);	// vectorForward "
	var prevVU = (that.prevVU = that.prevVU || []);	// vectorUp      "
	var prevPitch = that.prevPitch = that.prevPitch === undefined ? 0 : that.prevPitch;	// pitch from previous frame
	var prevRoll = that.prevRoll = that.prevRoll === undefined ? 0 : that.prevRoll;		// roll "
	var prevYaw = that.prevYaw = that.prevYaw === undefined ? 0 : that.prevYaw;			// yaw  "
	var isInertial = that.isInertial = that.isInertial === undefined ? 0 : that.isInertial;	// flag for input override
	var sumDelta = that.sumDelta = that.sumDelta === undefined ? null : that.sumDelta;			// counter for per second updates (null as used as flag for 1st time)
	var lastView = that.lastView = that.lastView === lastView ? 'VIEW_FORWARD' : that.lastView;// to separate view chg's from reversals
	var axesWritable = that.axesWritable = that.axesWritable === undefined ? wr.$AxesWritable : that.axesWritable;	
	// - flag for compatible version (axis writability only supported starting in 1.87)
	var rpRatio = (that.rpRatio = that.rpRatio || 2);	// relative difference in angular speed of roll vs pitch
	// - makes motion consistent (closed loophole where you could pitch faster than maxPitch by switching view to port/starboard and rolling)
	function copy_vector( a, b ) { 							// a -> b
		b[0] = a[0];
		b[1] = a[1];
		b[2] = a[2];
	}
	function copy_quaternion( a, b ) {						// a -> b
		b[0] = a[0];
		b[1] = a[1];
		b[2] = a[2];
		b[3] = a[3];
	}
	function checkReversal( axis, prev, curr, abs_curr, flag ) {
		let abs_prev = prev < 0 ? -prev : prev;
		if( abs_prev < nearZero ) return;
		if( prev * curr < 0 || curr === 0 ) { 				// different signs or zero => no input or both axis keys pressed
			_inInertialMotion[axis] = prev;					// save inertia values
			_inInertialMotion.keyboardInput &= ~flag;		// reset flag (determined in _inInertialMotion)
			isInertial = that.isInertial |= flag;			// set flag for axis
		}
	}
    var PITCH = 1, ROLL = 2, YAW = 4, 		// bitflags for axes
		angle, ps = player.ship;
    if( ps && ps.isValid && !ps.docked && (revCtrlOn || inertiaOn) && ps.AI != "dockingAI.plist" ) {
		var origPitch, pitch, abs_pitch, origRoll, roll, abs_roll, yaw, abs_yaw,
			view = ps.viewDirection;
		origPitch = pitch = ps.pitch;
		origRoll = roll = ps.roll;
		yaw = ps.yaw;
		if( sumDelta === null ) { 			// 1st time thru, ie. 1st frame in this view
			that.sumDelta = 1;				// force update of max values
			copy_vector( ps.vectorForward, prevVF )
			copy_vector( ps.vectorRight, prevVR )
			copy_vector( ps.vectorUp, prevVU )
			prevPitch = pitch;
			prevRoll = roll;
			prevYaw = yaw;
		}
		if( sumDelta >= 1 ) {				// limit refreshing values to 1/second
			that.sumDelta = 0;				//  - are periodically updated to detect change from equipment, battle damage or oxp's
			let maxRoll = ps.maxRoll;
			let maxPitch = ps.maxPitch;
			rpRatio = that.rpRatio = maxRoll / maxPitch;
			_inInertialMotion.maxRoll = maxRoll;
			_inInertialMotion.maxPitch = maxPitch;
			_inInertialMotion.maxYaw = ps.maxYaw;
		}
		that.sumDelta += delta;
		abs_pitch = pitch < 0 ? -pitch : pitch;
		abs_roll = roll < 0 ? -roll : roll;
		abs_yaw = yaw < 0 ? -yaw : yaw;
		if( inertiaOn && axesWritable ) {
			if( view === that.lastView ) {	// must separate view chgs & reversals else they could cancel each other
				checkReversal( 'pitch', prevPitch, pitch, abs_pitch, PITCH );
				checkReversal( 'roll', prevRoll, roll, abs_roll, ROLL );
				checkReversal( 'yaw', prevYaw, yaw, abs_yaw, YAW );
			}
			that.lastView = view;
		}
		if( isInertial !== 0 && axesWritable ) {  	// delay correcting until inertia bleeds off
			let result = _inInertialMotion( delta, ps, view, isInertial, pitch, roll, yaw );
			if( result === false ) {
				isInertial = that.isInertial = 0;	// ending inertial phase
			} else {
				if( result & PITCH ) {
					pitch = _inInertialMotion.pitch;
					abs_pitch = pitch < 0 ? -pitch : pitch;
				}
				if( result & ROLL ) {
					roll = _inInertialMotion.roll;
					abs_roll = roll < 0 ? -roll : roll;
				}
				if( result & YAW ) {
					yaw = _inInertialMotion.yaw;
					abs_yaw = yaw < 0 ? -yaw : yaw;
				}
			}
		}
		if( abs_pitch > nearZero || abs_roll > nearZero || (isInertial & YAW && abs_yaw > nearZero) ) {
			// yaw treated only on reversal, as it doesn't change from one view to another
			copy_quaternion( ps.orientation, orient );
			let setOrientation = false, 
				lastQuat = orient;		// each view alternates step1, step2, so _rotate_about_axis always gets diff quats
			switch( view ) {
				case "VIEW_FORWARD": 	// aligns w/ input, so only concerned w/ inertia				
					if( abs_pitch > nearZero && isInertial & PITCH ) {
						angle = -pitch * delta;
						_rotate_about_axis(lastQuat, prevVR, angle, lastQuat = step1);
						setOrientation = true;
					}
					if( abs_roll > nearZero && isInertial & ROLL) {
						angle = -roll * delta;
						_rotate_about_axis(lastQuat, prevVF, angle, lastQuat = step2);
						setOrientation = true;
					}
					break;
				case "VIEW_AFT":
					if( abs_pitch > nearZero && (revCtrlOn || isInertial & PITCH) ) {
						angle = pitch * delta * (revCtrlOn ? 1 : -1);
						angle *= revCtrlOn ? 2 : 1;		// 2 * incl's correcting for cmd that got us here
						_rotate_about_axis(lastQuat, prevVR, angle, lastQuat = step1);
						setOrientation = true;
					}
					if( abs_roll > nearZero && (revCtrlOn || isInertial & ROLL) ) {
						angle = roll * delta * (revCtrlOn ? 1 : -1);
						angle *= revCtrlOn ? 2 : 1;		// 2 * incl's correcting for cmd that got us here
						_rotate_about_axis(lastQuat, prevVF, angle, lastQuat = step2);
						setOrientation = true;
					}
					break;
				case "VIEW_PORT":
					// important to alternate prevVR, prevVF,...; doing prevVR, prevVF, prevVF, prevVR as before
					// will cause center to be off vertically; but this way, only set ps.orientation once, not 4 times
					if( abs_pitch > nearZero && (revCtrlOn || isInertial & PITCH) ) {		//pitch -> -roll
						angle = (revCtrlOn ? origPitch : -pitch) * delta;
						_rotate_about_axis(lastQuat, prevVR, angle, lastQuat = step1);		// - undo this frame's pitch
						if( revCtrlOn ) {
							angle = -pitch * delta * rpRatio;
							_rotate_about_axis(lastQuat, prevVF, angle, lastQuat = step2);	// - apply pitch cmd as roll
						}
						setOrientation = true;
/*	for dampening (no input)  						(src: PlayerEntityControls.m)
		self decrease_flight_roll:roll_dampner]
		roll_dampner = ROLL_DAMPING_FACTOR * delta_t; 
		#define ROLL_DAMPING_FACTOR		1.0f  		(src: PlayerEntity.h)
	- so time to decay to zero varies with each axis
	- as we're swapping axes, an adjustment is needed to correct the decay time
*/
						let abs_prevP = prevPitch < 0 ? -prevPitch : prevPitch;
						if( abs_prevP > abs_pitch && axesWritable ) {	// dampening
							let adjustment = delta - delta / rpRatio; 	// +delta (undo pitch time) - delta/rpRatio (add roll time)
							pitch = ps.pitch = ps.pitch + adjustment * (pitch < 0 ? -1 : 1); // rolling ship, lengthen dampening time
						}
					}
					if( abs_roll > nearZero && (revCtrlOn || isInertial & ROLL) ) {			//roll -> pitch
						if( revCtrlOn ) {
							angle = roll * delta / rpRatio;
							_rotate_about_axis(lastQuat, prevVR, angle, lastQuat = step1);	// - apply roll cmd as pitch
						}
						angle = (revCtrlOn ? origRoll : -roll) * delta;
						_rotate_about_axis(lastQuat, prevVF, angle, lastQuat = step2);		// - undo this frame's roll
						setOrientation = true;
						let abs_prevR = prevRoll < 0 ? -prevRoll : prevRoll;
						if( abs_prevR > abs_roll && axesWritable ) { 	// dampening
							let adjustment = delta - delta * rpRatio; 	// +delta (undo roll time) - delta * rpRatio (add pitch time)
							roll = ps.roll = ps.roll + adjustment * (roll < 0 ? -1 : 1); 	// rolling ship, shorten dampening time
						}
					}
					break;
				case "VIEW_STARBOARD":
					// important to alternate prevVR, prevVF,...; doing prevVR, prevVF, prevVF, prevVR as before
					// will cause center to be off vertically; but this way, only set ps.orientation once, not 4 times
					if( abs_pitch > nearZero && (revCtrlOn || isInertial & PITCH) ) {		//pitch -> -roll
						angle = (revCtrlOn ? origPitch : -pitch) * delta;
						_rotate_about_axis(lastQuat, prevVR, angle, lastQuat = step1); 		// - undo this frame's pitch
						if( revCtrlOn ) {
							angle = pitch * delta * rpRatio;
							_rotate_about_axis(lastQuat, prevVF, angle, lastQuat = step2);	// - apply pitch cmd as roll
						}
						setOrientation = true;
						let abs_prevP = prevPitch < 0 ? -prevPitch : prevPitch;
						if( abs_prevP > abs_pitch && axesWritable ) { 	// dampening
							let adjustment = delta - delta / rpRatio; 	// +delta (undo pitch time) - delta/rpRatio (add roll time)
							pitch = ps.pitch = ps.pitch + adjustment * (pitch < 0 ? -1 : 1); // rolling ship, lengthen dampening time
						}
					}
					if( abs_roll > nearZero && (revCtrlOn || isInertial & ROLL) ) {			//roll -> pitch
						if( revCtrlOn ) {
							angle = -roll * delta / rpRatio;
							_rotate_about_axis(lastQuat, prevVR, angle, lastQuat = step1);	// - apply roll cmd as pitch
						}
						angle = (revCtrlOn ? origRoll : -roll) * delta;
						_rotate_about_axis(lastQuat, prevVF, angle, lastQuat = step2);		// - undo this frame's roll
						setOrientation = true;
						let abs_prevR = prevRoll < 0 ? -prevRoll : prevRoll;
						if( abs_prevR > abs_roll && axesWritable ) { 	// dampening
							let adjustment = delta - delta * rpRatio; 	// +delta (undo roll time) - delta * rpRatio (add pitch time)
							roll = ps.roll = ps.roll + adjustment * (roll < 0 ? -1 : 1); 	// pitching ship, shorten dampening time
						}
					}
					break;
				default:
					wr.$Stop( !wr.$InertiaOn );
			}
			if( isInertial & YAW && abs_yaw > nearZero ) {	// put result in orient to keep it simple
				angle = -yaw * delta;
				_rotate_about_axis(lastQuat, prevVU, angle, lastQuat = orient);
				setOrientation = true;
			}
			if( setOrientation ) ps.orientation = lastQuat;
		}
		copy_vector( ps.vectorForward, prevVF )
		copy_vector( ps.vectorRight, prevVR )
		copy_vector( ps.vectorUp, prevVU )
		that.prevPitch = pitch;
		that.prevRoll = roll;
		that.prevYaw = yaw;
    } else {
		wr.$Stop( true );
    }
}
this.$Stop = function Stop( force ) {
	var that = Stop;
	var wr = (that.wr = that.wr || worldScripts.reversecontrol);
	if( force || !wr.$InertiaOn ) {
		var rcFCB = wr.$ReverseControlFCB;
		if( rcFCB && isValidFrameCallback( rcFCB ) ) {
			removeFrameCallback( rcFCB );
			wr.$ReverseControlFCB = null;
		}
		wr.$ReverseControl_FCB.sumDelta = null; // internal flag to reload values
	}
}
/*
this.$ReverseControl_FCB = function( delta ) { //FrameCallBack, improved by cag
    var ps = player.ship;
    var a, c = null;
    if( ps && ps.isValid && !ps.docked && this.$ReverseControlOn && ps.AI != "dockingAI.plist" ) {
	var prevori = this.$ReverseControlPrevOri;
	if( prevori ) switch ( ps.viewDirection ) {
	    case "VIEW_AFT":
		//reverse up-down
		a = ps.pitch * delta;
		c = prevori.vectorRight();
		ps.orientation = ps.orientation.rotate( c, 2 * a ); // 2 * incl's correcting for pitch cmd that got us here
		//reverse roll
		a = ps.roll * delta;
		c = prevori.vectorForward();
		ps.orientation = ps.orientation.rotate( c, 2 * a ); // 2 * incl's correcting for roll cmd that got us here
		break;
	    case "VIEW_PORT":
		//pitch -> roll
		a = ps.pitch * delta;
		c = prevori.vectorRight();
		ps.orientation = ps.orientation.rotate( c, 1 * a ); // - 1st undo this frame's pitch
		c = prevori.vectorForward();
		ps.orientation = ps.orientation.rotate( c, -1 * a ); // - now apply pitch cmd as roll
		//roll -> -pitch
		a = ps.roll * delta;
		c = prevori.vectorForward();
		ps.orientation = ps.orientation.rotate( c, 1 * a ); // - 1st undo this frame's roll
		c = prevori.vectorRight();
		ps.orientation = ps.orientation.rotate( c, 1 * a ); // - now apply roll cmd as pitch
		break;
	    case "VIEW_STARBOARD":
		//pitch -> roll
		a = ps.pitch * delta;
		c = prevori.vectorRight();
		ps.orientation = ps.orientation.rotate( c, 1 * a ); // - 1st undo this frame's pitch
		c = prevori.vectorForward();
		ps.orientation = ps.orientation.rotate( c, 1 * a ); // - now apply pitch cmd as roll
		//roll -> -pitch
		a = ps.roll * delta;
		c = prevori.vectorForward();
		ps.orientation = ps.orientation.rotate( c, 1 * a ); // - 1st undo this frame's roll
		c = prevori.vectorRight();
		ps.orientation = ps.orientation.rotate( c, -1 * a ); // - now apply roll cmd as pitch
		break;
	    default:
		this.$Stop();
	}
	this.$ReverseControlPrevOri = ps.orientation;
    } else {
	this.$ReverseControlPrevOri = null;
    }
}
this.$ReverseControl_FCB_old = function( delta ) { //FrameCallBack, old version
	var ps = player.ship;
	if( ps && ps.isValid && !ps.docked && this.$ReverseControlOn && ps.AI != "dockingAI.plist" ) {
		var prevori = this.$ReverseControlPrevOri;
		if( prevori ) switch ( ps.viewDirection ) {
			case "VIEW_AFT":
				//reverse up-down
				//var a = ps.heading.angleTo( prevori.vectorUp() ) - Math.PI / 2; //buggy with sniperlock
				var a = ps.pitch * delta;
				var c = prevori.vectorRight();
				ps.orientation = ps.orientation.rotate( c, 2 * a );
				//reverse roll
				//var a = ps.orientation.vectorRight().angleTo( prevori.vectorUp() ) - Math.PI / 2; //buggy with sniperlock
				var a = ps.roll * delta;
				var c = prevori.vectorForward();
				ps.orientation = ps.orientation.rotate( c, 2 * a );
				break;
			case "VIEW_PORT":
				//pitch -> roll
				var a = ps.pitch * delta;
				var c = prevori.vectorForward();
				ps.orientation = ps.orientation.rotate( c, -2 * a );
				//roll -> -pitch
				var a = ps.roll * delta;
				var c = prevori.vectorRight();
				ps.orientation = ps.orientation.rotate( c, 2 * a );
				break;
			case "VIEW_STARBOARD":
				//pitch -> -roll
				var a = ps.pitch * delta;
				var c = prevori.vectorForward();
				ps.orientation = ps.orientation.rotate( c, 2 * a );
				//roll -> pitch
				var a = ps.roll * delta;
				var c = prevori.vectorRight();
				ps.orientation = ps.orientation.rotate( c, -2 * a );
				break;
			default:
				this.$Stop();
		}
		this.$ReverseControlPrevOri = ps.orientation;
	} else {
		this.$ReverseControlPrevOri = null;
	}
}
*/
}).call(this);
 |