|
| 1 | +extends Node |
| 2 | + |
| 3 | +# Below are a number of helper functions that show how you can use the raw sensor data to determine the orientation |
| 4 | +# of your phone/device. The cheapest phones only have an accelerometer only the most expensive phones have all three. |
| 5 | +# Note that none of this logic filters data. Filters introduce lag but also provide stability. There are plenty |
| 6 | +# of examples on the internet on how to implement these. I wanted to keep this straight forward. |
| 7 | + |
| 8 | +# We draw a few arrow objects to visualize the vectors and two cubes to show two implementation for orientating |
| 9 | +# these cubes to our phones orientation. |
| 10 | +# This is a 3D example however reading the phones orientation is also invaluable for 2D |
| 11 | + |
| 12 | +# This function calculates a rotation matrix based on a direction vector. As our arrows are cylindrical we don't |
| 13 | +# care about the rotation around this axis. |
| 14 | +func get_basis_for_arrow(p_vector): |
| 15 | + var rotate = Basis() |
| 16 | + |
| 17 | + # as our arrow points up, Y = our direction vector |
| 18 | + rotate.y = p_vector.normalized() |
| 19 | + |
| 20 | + # get an arbitrary vector we can use to calculate our other two vectors |
| 21 | + var v = Vector3(1.0, 0.0, 0.0) |
| 22 | + if (abs(v.dot(rotate.y)) > 0.9): |
| 23 | + v = Vector3(0.0, 1.0, 0.0) |
| 24 | + |
| 25 | + # use our vector to get a vector perpendicular to our two vectors |
| 26 | + rotate.x = rotate.y.cross(v).normalized() |
| 27 | + |
| 28 | + # and the cross product again gives us our final vector perpendicular to our previous two vectors |
| 29 | + rotate.z = rotate.x.cross(rotate.y).normalized() |
| 30 | + |
| 31 | + return rotate |
| 32 | + |
| 33 | +# This function combines the magnetometer reading with the gravity vector to get a vector that points due north |
| 34 | +func calc_north(p_grav, p_mag): |
| 35 | + # Always use normalized vectors! |
| 36 | + p_grav = p_grav.normalized() |
| 37 | + |
| 38 | + # Calculate east (or is it west) by getting our cross product. |
| 39 | + # The cross product of two normalized vectors returns a vector that |
| 40 | + # is perpendicular to our two vectors |
| 41 | + var east = p_grav.cross(p_mag.normalized()).normalized() |
| 42 | + |
| 43 | + # Cross again to get our horizon aligned north |
| 44 | + return east.cross(p_grav).normalized() |
| 45 | + |
| 46 | +# This function creates an orientation matrix using the magnetometer and gravity vector as inputs. |
| 47 | +func orientate_by_mag_and_grav(p_mag, p_grav): |
| 48 | + var rotate = Basis() |
| 49 | + |
| 50 | + # as always, normalize! |
| 51 | + p_mag = p_mag.normalized() |
| 52 | + |
| 53 | + # gravity points down, so - gravity points up! |
| 54 | + rotate.y = -p_grav.normalized() |
| 55 | + |
| 56 | + # Cross products with our magnetic north gives an aligned east (or west, I always forget) |
| 57 | + rotate.x = rotate.y.cross(p_mag) |
| 58 | + |
| 59 | + # And cross product again and we get our aligned north completing our matrix |
| 60 | + rotate.z = rotate.x.cross(rotate.y) |
| 61 | + |
| 62 | + return rotate |
| 63 | + |
| 64 | +# This function takes our gyro input and update an orientation matrix accordingly |
| 65 | +# The gyro is special as this vector does not contain a direction but rather a |
| 66 | +# rotational velocity. This is why we multiply our values with delta. |
| 67 | +func rotate_by_gyro(p_gyro, p_basis, p_delta): |
| 68 | + var rotate = Basis() |
| 69 | + |
| 70 | + rotate = rotate.rotated(p_basis.x, -p_gyro.x * p_delta) |
| 71 | + rotate = rotate.rotated(p_basis.y, -p_gyro.y * p_delta) |
| 72 | + rotate = rotate.rotated(p_basis.z, -p_gyro.z * p_delta) |
| 73 | + |
| 74 | + return rotate * p_basis |
| 75 | + |
| 76 | +# This function corrects the drift in our matrix by our gravity vector |
| 77 | +func drift_correction(p_basis, p_grav): |
| 78 | + # as always, make sure our vector is normalized but also invert as our gravity points down |
| 79 | + var real_up = -p_grav.normalized() |
| 80 | + |
| 81 | + # start by calculating the dot product, this gives us the cosine angle between our two vectors |
| 82 | + var dot = p_basis.y.dot(real_up) |
| 83 | + |
| 84 | + # if our dot is 1.0 we're good |
| 85 | + if (dot < 1.0): |
| 86 | + # the cross between our two vectors gives us a vector perpendicular to our two vectors |
| 87 | + var axis = p_basis.y.cross(real_up).normalized() |
| 88 | + var correction = Basis(axis, acos(dot)) |
| 89 | + p_basis = correction * p_basis |
| 90 | + |
| 91 | + return p_basis |
| 92 | + |
| 93 | +func _process(delta): |
| 94 | + # Get our data |
| 95 | + var acc = Input.get_accelerometer() |
| 96 | + var grav = Input.get_gravity() |
| 97 | + var mag = Input.get_magnetometer() |
| 98 | + var gyro = Input.get_gyroscope() |
| 99 | + |
| 100 | + # Show our base values |
| 101 | + get_node("Control/Accelerometer").text = 'Accelerometer: ' + str(acc) + ', gravity: ' + str(grav) |
| 102 | + get_node("Control/Magnetometer").text = 'Magnetometer: ' + str(mag) |
| 103 | + get_node("Control/Gyroscope").text = 'Gyroscope: ' + str(gyro) |
| 104 | + |
| 105 | + # Check if we have all needed data |
| 106 | + if grav.length() < 0.1: |
| 107 | + if acc.length() < 0.1: |
| 108 | + # we don't have either... |
| 109 | + grav = Vector3(0.0, -1.0, 0.0) |
| 110 | + else: |
| 111 | + # The gravity vector is calculated by the OS by combining the other sensor inputs. |
| 112 | + # If we don't have a gravity vector, from now on, use accelerometer... |
| 113 | + grav = acc |
| 114 | + |
| 115 | + if mag.length() < 0.1: |
| 116 | + mag = Vector3(1.0, 0.0, 0.0) |
| 117 | + |
| 118 | + # Update our arrow showing gravity |
| 119 | + get_node("Arrows/AccelerometerArrow").transform.basis = get_basis_for_arrow(grav) |
| 120 | + |
| 121 | + # Update our arrow showing our magnetometer |
| 122 | + # Note that in absense of other strong magnetic forces this will point to magnetic north, which is not horizontal thanks to the earth being, uhm, round |
| 123 | + get_node("Arrows/MagnetoArrow").transform.basis = get_basis_for_arrow(mag) |
| 124 | + |
| 125 | + # Calculate our north vector and show that |
| 126 | + var north = calc_north(grav,mag) |
| 127 | + get_node("Arrows/NorthArrow").transform.basis = get_basis_for_arrow(north) |
| 128 | + |
| 129 | + # Combine our magnetometer and gravity vector to position our box. This will be fairly accurate |
| 130 | + # but our magnetometer can be easily influenced by magnets. Cheaper phones often don't have gyros |
| 131 | + # so it is a good backup. |
| 132 | + var mag_and_grav = get_node("Boxes/MagAndGrav") |
| 133 | + mag_and_grav.transform.basis = orientate_by_mag_and_grav(mag, grav).orthonormalized() |
| 134 | + |
| 135 | + # Using our gyro and do a drift correction using our gravity vector gives the best result |
| 136 | + var gyro_and_grav = get_node("Boxes/GyroAndGrav") |
| 137 | + var new_basis = rotate_by_gyro(gyro, gyro_and_grav.transform.basis, delta).orthonormalized() |
| 138 | + gyro_and_grav.transform.basis = drift_correction(new_basis, grav) |
| 139 | + |
| 140 | + |
| 141 | + |
0 commit comments