From dabf3f300cbd047059d6da32f6e2d9d060079196 Mon Sep 17 00:00:00 2001
From: mfranz <mfranz>
Date: Fri, 3 Nov 2006 17:38:32 +0000
Subject: [PATCH] - aircraft.nas: add variable-interval EWMA lowpass filter
 class - dynamic_view.nas: use aircraft.lowpass() class

This makes the dynamic view independent of the frame rate. Currently,
each filter instance reads out dt on its own, which is a bit inefficient.
I'll change that to just one read for all instances later (when Nasal
implemented predictable module loading order. :-)
---
 Nasal/aircraft.nas     | 40 +++++++++++++++++++++++++++++++++++++
 Nasal/dynamic_view.nas | 45 ++++++++----------------------------------
 2 files changed, 48 insertions(+), 37 deletions(-)

diff --git a/Nasal/aircraft.nas b/Nasal/aircraft.nas
index ecb65e1a0..429ace407 100644
--- a/Nasal/aircraft.nas
+++ b/Nasal/aircraft.nas
@@ -216,6 +216,46 @@ light = {
 
 
 
+# lowpass
+# ==============================================================================
+# class that implements a variable-interval EWMA (Exponentially Weighted
+# Moving Average) lowpass filter with characteristics independent of the
+# frame rate.
+#
+# SYNOPSIS:
+#	lowpass.new(<coefficient>);
+#
+# EXAMPLE:
+#	var lp = lowpass(0.5);
+#	print(lp.filter(10));
+#	print(lp.filter(0));
+#
+lowpass = {
+	new : func(coeff) {
+		var m = { parents : [lowpass] };
+		m.dtN = props.globals.getNode("/sim/time/delta-realtime-sec", 1);
+		m.value = nil;
+		m.coeff = abs(coeff);
+		return m;
+	},
+	# filter(raw_value)    -> push new value, returns filtered value
+	filter : func(v) {
+		me.filter = me._filter_;
+		me.value = v;
+	},
+	# get()                -> returns filtered value
+	get : func {
+		me.value;
+	},
+	_filter_ : func(v) {
+		var dt = me.dtN.getValue();
+		var c = dt / (me.coeff + dt);
+		me.value = v * c + me.value * (1 - c);
+	},
+};
+
+
+
 # HUD control class to handle both HUD implementations.
 #
 HUDControl = {
diff --git a/Nasal/dynamic_view.nas b/Nasal/dynamic_view.nas
index 551c55f83..89967d628 100644
--- a/Nasal/dynamic_view.nas
+++ b/Nasal/dynamic_view.nas
@@ -3,7 +3,6 @@
 # acceleration.
 
 
-
 sin = func(a) { math.sin(a * math.pi / 180.0) }
 cos = func(a) { math.cos(a * math.pi / 180.0) }
 sigmoid = func(x) { 1 / (1 + math.exp(-x)) }
@@ -25,34 +24,6 @@ normdeg = func(a) {
 
 
 
-# Class that implements EWMA (Exponentially Weighted Moving Average)
-# lowpass filter. Initialize with coefficient, set new value when
-# you fetch one. filter() pushes new value and returns filtered value,
-# get() only returns filtered value. A filter coefficient of 0 means
-# no filtering, coeff = 1 generates a factor 0.1, coeff = 2 a facdtor
-# 0.01, coeff = 3 a factor 0.001, etc.
-#
-LowPass = {
-	new : func(coeff) {
-		var m = { parents : [LowPass] };
-		m.value = nil;
-		m.coeff = 1.0 / pow(10, abs(coeff));
-		return m;
-	},
-	filter : func(v) {
-		me.filter = me._filter_;
-		me.value = v;
-	},
-	_filter_ : func(v) {
-		me.value = v * me.coeff + me.value * (1 - me.coeff);
-	},
-	get : func {
-		me.value;
-	},
-};
-
-
-
 # Class that reads a property value, applies factor & offset, clamps to min & max,
 # and optionally lowpass filters.
 #
@@ -64,7 +35,7 @@ Input = {
 		m.offset = offset;
 		m.min = min;
 		m.max = max;
-		m.lowpass = filter ? LowPass.new(filter) : nil;
+		m.lowpass = filter ? aircraft.lowpass.new(filter) : nil;
 		return m;
 	},
 	get : func {
@@ -131,19 +102,19 @@ ViewManager = {
 		m.roll_axis = ViewAxis.new("/sim/current-view/goal-roll-offset-deg");
 
 		# accerations are converted to G (one G is subtraced from z-accel)
-		m.ax = Input.new("/accelerations/pilot/x-accel-fps_sec", 0.03108095, 0, 1.1, 0);
-		m.ay = Input.new("/accelerations/pilot/y-accel-fps_sec", 0.03108095, 0, 1.3);
-		m.az = Input.new("/accelerations/pilot/z-accel-fps_sec", -0.03108095, -1, 1.0073);
+		m.ax = Input.new("/accelerations/pilot/x-accel-fps_sec", 0.03108095, 0, 0.58, 0);
+		m.ay = Input.new("/accelerations/pilot/y-accel-fps_sec", 0.03108095, 0, 0.95);
+		m.az = Input.new("/accelerations/pilot/z-accel-fps_sec", -0.03108095, -1, 0.46);
 
 		# velocities are converted to knots
-		m.vx = Input.new("/velocities/uBody-fps", 0.5924838, 0, 1);
+		m.vx = Input.new("/velocities/uBody-fps", 0.5924838, 0, 0.45);
 		m.vy = Input.new("/velocities/vBody-fps", 0.5924838, 0);
 		m.vz = Input.new("/velocities/wBody-fps", 0.5924838, 0);
 
 		# turn WoW bool into smooth values ranging from 0 to 1
-		m.wow = Input.new("/gear/gear/wow", 1, 0, 1.2);
-		m.hdg_change = LowPass.new(1.3);
-		m.ubody = LowPass.new(1.3);
+		m.wow = Input.new("/gear/gear/wow", 1, 0, 0.74);
+		m.hdg_change = aircraft.lowpass.new(0.95);
+		m.ubody = aircraft.lowpass.new(0.95);
 		m.last_heading = m.headingN.getValue();
 		m.size_factor = -getprop("/sim/chase-distance-m") / 25;
 		m.reset();