From 0ff1865c2e565ab84328fc26cf5607a276ef0e40 Mon Sep 17 00:00:00 2001
From: Moses Turner <mosesturner@protonmail.com>
Date: Mon, 7 Sep 2020 10:14:17 +0000
Subject: [PATCH] d/ns: Add version 2 config support

---
 .../distortion/deformation_northstar.cpp      |   6 +-
 .../distortion/deformation_northstar.h        |   2 +-
 src/xrt/drivers/north_star/ns_hmd.c           | 346 +++++++++++++++---
 src/xrt/drivers/north_star/ns_hmd.h           |  25 +-
 .../drivers/north_star/v1_example_config.json | 153 ++++++++
 .../drivers/north_star/v2_example_config.json |  17 +
 6 files changed, 495 insertions(+), 54 deletions(-)
 create mode 100644 src/xrt/drivers/north_star/v1_example_config.json
 create mode 100644 src/xrt/drivers/north_star/v2_example_config.json

diff --git a/src/xrt/drivers/north_star/distortion/deformation_northstar.cpp b/src/xrt/drivers/north_star/distortion/deformation_northstar.cpp
index ef5d7d30d..87d336e3d 100644
--- a/src/xrt/drivers/north_star/distortion/deformation_northstar.cpp
+++ b/src/xrt/drivers/north_star/distortion/deformation_northstar.cpp
@@ -20,7 +20,7 @@ OpticalSystem::OpticalSystem(const OpticalSystem &_in)
 }
 
 void
-OpticalSystem::LoadOpticalData(struct ns_eye *eye)
+OpticalSystem::LoadOpticalData(struct ns_v1_eye *eye)
 {
 
 	ellipseMinorAxis = eye->ellipse_minor_axis;
@@ -294,7 +294,7 @@ OpticalSystem::DisplayUVToRenderUVPreviousSeed(Vector2 inputUV)
 
 
 extern "C" struct ns_optical_system *
-ns_create_optical_system(struct ns_eye *eye)
+ns_create_optical_system(struct ns_v1_eye *eye)
 {
 	OpticalSystem *opticalSystem = new OpticalSystem();
 	opticalSystem->LoadOpticalData(eye);
@@ -306,7 +306,7 @@ ns_create_optical_system(struct ns_eye *eye)
 extern "C" void
 ns_display_uv_to_render_uv(struct ns_uv in,
                            struct ns_uv *out,
-                           struct ns_eye *eye)
+                           struct ns_v1_eye *eye)
 {
 	OpticalSystem *opticalSystem = (OpticalSystem *)eye->optical_system;
 	Vector2 inUV = Vector2(in.u, 1.f - in.v);
diff --git a/src/xrt/drivers/north_star/distortion/deformation_northstar.h b/src/xrt/drivers/north_star/distortion/deformation_northstar.h
index 87049e7bb..a6b0070d0 100644
--- a/src/xrt/drivers/north_star/distortion/deformation_northstar.h
+++ b/src/xrt/drivers/north_star/distortion/deformation_northstar.h
@@ -17,7 +17,7 @@ public:
 	OpticalSystem(const OpticalSystem &_in);
 
 	void
-	LoadOpticalData(struct ns_eye *eye);
+	LoadOpticalData(struct ns_v1_eye *eye);
 
 	Vector3
 	GetEyePosition()
diff --git a/src/xrt/drivers/north_star/ns_hmd.c b/src/xrt/drivers/north_star/ns_hmd.c
index 3e47a76f7..bb1ee8325 100644
--- a/src/xrt/drivers/north_star/ns_hmd.c
+++ b/src/xrt/drivers/north_star/ns_hmd.c
@@ -1,11 +1,13 @@
 // Copyright 2020, Collabora, Ltd.
 // Copyright 2020, Nova King.
+// Copyright 2020, Moses Turner.
 // SPDX-License-Identifier: BSL-1.0
 /*!
  * @file
  * @brief  North Star HMD code.
  * @author Nova King <technobaboo@gmail.com>
  * @author Jakob Bornecrantz <jakob@collabora.com>
+ * @author Moses Turner <mosesturner@protonmail.com>
  * @ingroup drv_ns
  */
 
@@ -35,7 +37,7 @@
 
 /*
  *
- * Functions
+ * Common functions
  *
  */
 
@@ -60,6 +62,12 @@ ns_hmd_update_inputs(struct xrt_device *xdev)
 	}
 }
 
+/*
+ *
+ * V1 functions
+ *
+ */
+
 static void
 ns_hmd_get_tracked_pose(struct xrt_device *xdev,
                         enum xrt_input_name name,
@@ -99,13 +107,13 @@ ns_hmd_get_view_pose(struct xrt_device *xdev,
                      struct xrt_pose *out_pose)
 {
 	struct ns_hmd *ns = ns_hmd(xdev);
-	*out_pose = ns->eye_configs[view_index].eye_pose;
+	*out_pose = ns->eye_configs_v1[view_index].eye_pose;
 }
 
 
 /*
  *
- * Mesh functions.
+ * V1 Mesh functions.
  *
  */
 
@@ -121,7 +129,7 @@ ns_mesh_calc(struct u_uv_generator *gen,
 	struct ns_uv uv = {u, v};
 	struct ns_uv warped_uv = {0.0f, 0.0f};
 	ns_display_uv_to_render_uv(uv, &warped_uv,
-	                           &mesh->ns->eye_configs[view]);
+	                           &mesh->ns->eye_configs_v1[view]);
 
 	result->r.x = warped_uv.u;
 	result->r.y = warped_uv.v;
@@ -138,6 +146,12 @@ ns_mesh_destroy(struct u_uv_generator *gen)
 	(void)mesh; // Noop
 }
 
+/*
+ *
+ * Parse function.
+ *
+ */
+
 static void
 ns_leap_parse(struct ns_leap *leap, struct cJSON *leap_data)
 {
@@ -164,7 +178,7 @@ ns_leap_parse(struct ns_leap *leap, struct cJSON *leap_data)
 }
 
 static void
-ns_eye_parse(struct ns_eye *eye, struct cJSON *eye_data)
+ns_eye_parse(struct ns_v1_eye *eye, struct cJSON *eye_data)
 {
 	u_json_get_float(
 	    cJSON_GetObjectItemCaseSensitive(eye_data, "ellipseMinorAxis"),
@@ -209,15 +223,11 @@ ns_eye_parse(struct ns_eye *eye, struct cJSON *eye_data)
 }
 
 
-/*
- *
- * Parse function.
- *
- */
-
 static void
 ns_fov_calculate(struct xrt_fov *fov, struct xrt_quat projection)
-{
+{ // All of these are wrong - gets "hidden" by the simple_fov making it look
+  // okay. Needs to be fixed.
+
 	fov->angle_up = projection.x; // atanf(fabsf(projection.x) /
 	                              // near_plane);
 	fov->angle_down =
@@ -228,6 +238,116 @@ ns_fov_calculate(struct xrt_fov *fov, struct xrt_quat projection)
 	    projection.w; // atanf(fabsf(projection.w) / near_plane);
 }
 
+/*
+ *
+ * V2 optics.
+ *
+ */
+
+
+static void
+ns_v2_hmd_get_view_pose(struct xrt_device *xdev,
+                        struct xrt_vec3 *eye_relation,
+                        uint32_t view_index,
+                        struct xrt_pose *out_pose)
+{
+	// Copied from dummy driver
+	struct xrt_pose pose = {{0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}};
+	bool adjust = view_index == 0;
+
+	pose.position.x = eye_relation->x / 2.0f;
+	pose.position.y = eye_relation->y / 2.0f;
+	pose.position.z = eye_relation->z / 2.0f;
+
+	// Adjust for left/right while also making sure there aren't any -0.f.
+	if (pose.position.x > 0.0f && adjust) {
+		pose.position.x = -pose.position.x;
+	}
+	if (pose.position.y > 0.0f && adjust) {
+		pose.position.y = -pose.position.y;
+	}
+	if (pose.position.z > 0.0f && adjust) {
+		pose.position.z = -pose.position.z;
+	}
+
+	*out_pose = pose;
+}
+
+static float
+ns_v2_polyval2d(float X, float Y, float C[16])
+{
+	float X2 = X * X;
+	float X3 = X2 * X;
+	float Y2 = Y * Y;
+	float Y3 = Y2 * Y;
+	return (
+	    ((C[0]) + (C[1] * Y) + (C[2] * Y2) + (C[3] * Y3)) +
+	    ((C[4] * X) + (C[5] * X * Y) + (C[6] * X * Y2) + (C[7] * X * Y3)) +
+	    ((C[8] * X2) + (C[9] * X2 * Y) + (C[10] * X2 * Y2) +
+	     (C[11] * X2 * Y3)) +
+	    ((C[12] * X3) + (C[13] * X3 * Y) + (C[14] * X3 * Y2) +
+	     (C[15] * X3 * Y3)));
+}
+
+
+
+static void
+ns_v2_fov_calculate(struct ns_hmd *ns, int eye_index)
+{
+	ns->base.hmd->views[eye_index].fov.angle_down =
+	    ns->eye_configs_v2[eye_index].fov.angle_down;
+	ns->base.hmd->views[eye_index].fov.angle_up =
+	    ns->eye_configs_v2[eye_index].fov.angle_up;
+	ns->base.hmd->views[eye_index].fov.angle_left =
+	    ns->eye_configs_v2[eye_index].fov.angle_left;
+	ns->base.hmd->views[eye_index].fov.angle_right =
+	    ns->eye_configs_v2[eye_index].fov.angle_right;
+}
+
+
+
+static void
+ns_v2_mesh_calc(struct u_uv_generator *gen,
+                int view,
+                float u,
+                float v,
+                struct u_uv_triplet *result)
+{
+
+
+
+	float x = 0.0f;
+	float y = 0.0f;
+
+	u = 1.0 - u;
+	v = 1.0 - v;
+
+	struct ns_mesh *mesh = ns_mesh(gen);
+	float L = mesh->ns->eye_configs_v2[view].fov.angle_left;
+	float R = mesh->ns->eye_configs_v2[view].fov.angle_right;
+	float T = mesh->ns->eye_configs_v2[view].fov.angle_up;
+	float B = mesh->ns->eye_configs_v2[view].fov.angle_down;
+
+	float x_ray = ns_v2_polyval2d(
+	    u, v, mesh->ns->eye_configs_v2[view].x_coefficients);
+	float y_ray = ns_v2_polyval2d(
+	    u, v, mesh->ns->eye_configs_v2[view].y_coefficients);
+	x_ray = x_ray;
+	y_ray = y_ray;
+
+	x = (x_ray + L) / (L - R);
+	y = (y_ray + T) / (T - B);
+
+
+	result->r.x = x;
+	result->r.y = y;
+	result->g.x = x;
+	result->g.y = y;
+	result->b.x = x;
+	result->b.y = y;
+}
+
+
 static bool
 ns_config_load(struct ns_hmd *ns)
 {
@@ -272,13 +392,111 @@ ns_config_load(struct ns_hmd *ns)
 		}
 		return false;
 	}
+	if (cJSON_GetObjectItemCaseSensitive(config_json, "leftEye") == NULL &&
+	    cJSON_GetObjectItemCaseSensitive(config_json,
+	                                     "left_uv_to_rect_x") != NULL) {
+		// Bad hack to tell that we're v2. Error checking is not good
+		// enough for public consumption - many cases where a malformed
+		// config json results in cryptic errors. Should get fixed
+		// whenever we have more than 5 people using this.
+
+		u_json_get_float_array(cJSON_GetObjectItemCaseSensitive(
+		                           config_json, "left_uv_to_rect_x"),
+		                       ns->eye_configs_v2[0].y_coefficients,
+		                       16);
+		u_json_get_float_array(cJSON_GetObjectItemCaseSensitive(
+		                           config_json, "left_uv_to_rect_y"),
+		                       ns->eye_configs_v2[0].x_coefficients,
+		                       16);
+		u_json_get_float_array(cJSON_GetObjectItemCaseSensitive(
+		                           config_json, "right_uv_to_rect_x"),
+		                       ns->eye_configs_v2[1].y_coefficients,
+		                       16);
+		u_json_get_float_array(cJSON_GetObjectItemCaseSensitive(
+		                           config_json, "right_uv_to_rect_y"),
+		                       ns->eye_configs_v2[1].x_coefficients,
+		                       16);
+
+		if (!u_json_get_float(
+		        cJSON_GetObjectItemCaseSensitive(config_json,
+		                                         "leftEyeAngleLeft"),
+		        &ns->eye_configs_v2[0]
+		             .fov
+		             .angle_left)) { // not putting this directly in
+			                     // (&ns->base.hmd->views[eye_index].fov
+			                     // because i smell a rat there -
+			                     // that value seems to unexpectedly
+			                     // change during init process.
+			printf(
+			    "Just so you know, you can add tunable FoV "
+			    "parameters to your v2 json file. There's an "
+			    "example in "
+			    "src/xrt/drivers/north_star/"
+			    "v2_example_config.json.\n");
+			ns->eye_configs_v2[0].fov.angle_left = -0.6;
+			ns->eye_configs_v2[0].fov.angle_right = 0.6;
+			ns->eye_configs_v2[0].fov.angle_up = 0.6;
+			ns->eye_configs_v2[0].fov.angle_down = -0.6;
+
+			ns->eye_configs_v2[0].fov.angle_left = -0.6;
+			ns->eye_configs_v2[0].fov.angle_right = 0.6;
+			ns->eye_configs_v2[0].fov.angle_up = 0.6;
+			ns->eye_configs_v2[0].fov.angle_down = -0.6;
+
+
+
+		} else {
+			u_json_get_float(
+			    cJSON_GetObjectItemCaseSensitive(
+			        config_json, "leftEyeAngleRight"),
+			    &ns->eye_configs_v2[0].fov.angle_right);
+			u_json_get_float(cJSON_GetObjectItemCaseSensitive(
+			                     config_json, "leftEyeAngleUp"),
+			                 &ns->eye_configs_v2[0].fov.angle_up);
+			u_json_get_float(cJSON_GetObjectItemCaseSensitive(
+			                     config_json, "leftEyeAngleDown"),
+			                 &ns->eye_configs_v2[0].fov.angle_down);
+
+			u_json_get_float(cJSON_GetObjectItemCaseSensitive(
+			                     config_json, "rightEyeAngleLeft"),
+			                 &ns->eye_configs_v2[1].fov.angle_left);
+			u_json_get_float(
+			    cJSON_GetObjectItemCaseSensitive(
+			        config_json, "rightEyeAngleRight"),
+			    &ns->eye_configs_v2[1].fov.angle_right);
+			u_json_get_float(cJSON_GetObjectItemCaseSensitive(
+			                     config_json, "rightEyeAngleUp"),
+			                 &ns->eye_configs_v2[1].fov.angle_up);
+			u_json_get_float(cJSON_GetObjectItemCaseSensitive(
+			                     config_json, "rightEyeAngleDown"),
+			                 &ns->eye_configs_v2[1].fov.angle_down);
+		}
+
+		ns->is_v2 = true;
+
+
+	} else if (cJSON_GetObjectItemCaseSensitive(config_json, "leftEye") !=
+	               NULL &&
+	           cJSON_GetObjectItemCaseSensitive(
+	               config_json, "left_uv_to_rect_x") == NULL) {
+		ns_eye_parse(
+		    &ns->eye_configs_v1[0],
+		    cJSON_GetObjectItemCaseSensitive(config_json, "leftEye"));
+
+		ns_eye_parse(
+		    &ns->eye_configs_v1[1],
+		    cJSON_GetObjectItemCaseSensitive(config_json, "rightEye"));
+		ns_leap_parse(&ns->leap_config,
+		              cJSON_GetObjectItemCaseSensitive(config_json,
+		                                               "leapTracker"));
+		ns->is_v2 = false;
+	} else {
+		printf(
+		    "Bad config file. There are examples of v1 and v2 files in "
+		    "src/xrt/drivers/north_star - if those don't work, "
+		    "something's really wrong.\n");
+	}
 
-	ns_eye_parse(&ns->eye_configs[0],
-	             cJSON_GetObjectItemCaseSensitive(config_json, "leftEye"));
-	ns_eye_parse(&ns->eye_configs[1],
-	             cJSON_GetObjectItemCaseSensitive(config_json, "rightEye"));
-	ns_leap_parse(&ns->leap_config, cJSON_GetObjectItemCaseSensitive(
-	                                    config_json, "leapTracker"));
 	cJSON_Delete(config_json);
 	return true;
 }
@@ -295,10 +513,12 @@ ns_hmd_create(const char *config_path, bool print_spew, bool print_debug)
 {
 	enum u_device_alloc_flags flags = (enum u_device_alloc_flags)(
 	    U_DEVICE_ALLOC_HMD | U_DEVICE_ALLOC_TRACKING_NONE);
+
+
 	struct ns_hmd *ns = U_DEVICE_ALLOCATE(struct ns_hmd, flags, 1, 0);
 	ns->base.update_inputs = ns_hmd_update_inputs;
 	ns->base.get_tracked_pose = ns_hmd_get_tracked_pose;
-	ns->base.get_view_pose = ns_hmd_get_view_pose;
+	// NOT HERE ns->base.get_view_pose = ns_hmd_get_view_pose;
 	ns->base.destroy = ns_hmd_destroy;
 	ns->base.name = XRT_DEVICE_GENERIC_HMD;
 	ns->pose.orientation.w = 1.0f; // All other values set to zero.
@@ -308,7 +528,6 @@ ns_hmd_create(const char *config_path, bool print_spew, bool print_debug)
 
 	// Print name.
 	snprintf(ns->base.str, XRT_DEVICE_NAME_LEN, "North Star");
-
 	// Setup input.
 	ns->base.inputs[0].name = XRT_INPUT_GENERIC_HEAD_POSE;
 
@@ -323,45 +542,82 @@ ns_hmd_create(const char *config_path, bool print_spew, bool print_debug)
 	info.views[0].fov = 70.0f * (M_PI / 180.0f);
 	info.views[1].fov = 70.0f * (M_PI / 180.0f);
 
+
 	if (!ns_config_load(ns))
 		goto cleanup;
 
-	ns_fov_calculate(&ns->base.hmd->views[0].fov,
-	                 ns->eye_configs[0].camera_projection);
-	ns_fov_calculate(&ns->base.hmd->views[1].fov,
-	                 ns->eye_configs[1].camera_projection);
+	if (ns->is_v2) { // ns_config_load() sets ns->is_v2
+		ns->base.get_view_pose = ns_v2_hmd_get_view_pose;
+		ns_v2_fov_calculate(ns, 0);
+		ns_v2_fov_calculate(ns, 1);
+		// Setup the north star basic info
+		if (!u_device_setup_split_side_by_side(&ns->base, &info)) {
+			NS_ERROR(ns, "Failed to setup basic device info");
+			goto cleanup;
+		}
+		ns_v2_fov_calculate(ns, 0);
+		ns_v2_fov_calculate(ns, 1);
 
-	// Create the optical systems
-	ns->eye_configs[0].optical_system =
-	    ns_create_optical_system(&ns->eye_configs[0]);
-	ns->eye_configs[1].optical_system =
-	    ns_create_optical_system(&ns->eye_configs[1]);
 
-	// Setup the north star basic info
-	if (!u_device_setup_split_side_by_side(&ns->base, &info)) {
-		NS_ERROR(ns, "Failed to setup basic device info");
-		goto cleanup;
+
+		// Setup the distortion mesh.
+		struct ns_mesh mesh;
+		U_ZERO(&mesh);
+		mesh.ns = ns;
+		mesh.base.calc = ns_v2_mesh_calc;
+		mesh.base.destroy = ns_mesh_destroy;
+
+		// Do the mesh generation.
+		u_distortion_mesh_from_gen(&mesh.base, 2, ns->base.hmd);
+		// u_distortion_mesh_none(ns->base.hmd);
+
+
+
+	} else {
+		// V1
+		ns->base.get_view_pose = ns_hmd_get_view_pose;
+		ns_fov_calculate(&ns->base.hmd->views[0].fov,
+		                 ns->eye_configs_v1[0].camera_projection);
+		ns_fov_calculate(&ns->base.hmd->views[1].fov,
+		                 ns->eye_configs_v1[1].camera_projection);
+
+		// Create the optical systems
+		ns->eye_configs_v1[0].optical_system =
+		    ns_create_optical_system(&ns->eye_configs_v1[0]);
+		ns->eye_configs_v1[1].optical_system =
+		    ns_create_optical_system(&ns->eye_configs_v1[1]);
+
+		// Setup the north star basic info
+		if (!u_device_setup_split_side_by_side(&ns->base, &info)) {
+			NS_ERROR(ns, "Failed to setup basic device info");
+			goto cleanup;
+		}
+
+		// If built, try to load the realsense tracker.
+#ifdef XRT_BUILD_DRIVER_RS
+		ns->tracker = rs_6dof_create();
+#endif
+
+
+
+		// Setup the distortion mesh.
+		struct ns_mesh mesh;
+		U_ZERO(&mesh);
+		mesh.ns = ns;
+		mesh.base.calc = ns_mesh_calc;
+		mesh.base.destroy = ns_mesh_destroy;
+
+		// Do the mesh generation.
+		u_distortion_mesh_from_gen(&mesh.base, 2, ns->base.hmd);
 	}
 
 	// If built, try to load the realsense tracker.
 #ifdef XRT_BUILD_DRIVER_RS
 	ns->tracker = rs_6dof_create();
 #endif
-
 	// Setup variable tracker.
 	u_var_add_root(ns, "North Star", true);
 	u_var_add_pose(ns, &ns->pose, "pose");
-
-	// Setup the distortion mesh.
-	struct ns_mesh mesh;
-	U_ZERO(&mesh);
-	mesh.ns = ns;
-	mesh.base.calc = ns_mesh_calc;
-	mesh.base.destroy = ns_mesh_destroy;
-
-	// Do the mesh generation.
-	u_distortion_mesh_from_gen(&mesh.base, 2, ns->base.hmd);
-
 	ns->base.orientation_tracking_supported = true;
 	ns->base.position_tracking_supported = ns->tracker != NULL;
 	if (ns->tracker) {
diff --git a/src/xrt/drivers/north_star/ns_hmd.h b/src/xrt/drivers/north_star/ns_hmd.h
index 7f3ec9983..213165a85 100644
--- a/src/xrt/drivers/north_star/ns_hmd.h
+++ b/src/xrt/drivers/north_star/ns_hmd.h
@@ -96,7 +96,7 @@ struct ns_leap
  *
  * @ingroup drv_ns
  */
-struct ns_eye
+struct ns_v1_eye
 {
 	float ellipse_minor_axis;
 	float ellipse_major_axis;
@@ -114,28 +114,43 @@ struct ns_eye
 	struct ns_optical_system *optical_system;
 };
 
+struct ns_v2_eye
+{
+	float x_coefficients[16];
+	float y_coefficients[16];
+	struct xrt_pose eye_pose;
+	struct xrt_fov fov;
+};
+
 /*!
  * Information about the whole North Star headset.
  *
  * @ingroup drv_ns
  * @implements xrt_device
  */
+
 struct ns_hmd
 {
+
 	struct xrt_device base;
 	struct xrt_pose pose;
 
 	const char *config_path;
 
-	struct ns_eye eye_configs[2];
-	struct ns_leap leap_config;
+	struct ns_v1_eye eye_configs_v1[2]; // will be NULL if is_v2.
+	struct ns_v2_eye eye_configs_v2[2]; // will be NULL if !is_v2
+
+	struct ns_leap leap_config; // will be NULL if is_v2
 
 	struct xrt_device *tracker;
 
 	bool print_spew;
 	bool print_debug;
+	bool is_v2; // True if V2, false if V1. If we ever get a v3 this should
+	            // be an enum or something
 };
 
+
 /*!
  * The mesh generator for the North Star distortion.
  *
@@ -185,10 +200,10 @@ ns_mesh(struct u_uv_generator *gen)
 void
 ns_display_uv_to_render_uv(struct ns_uv in,
                            struct ns_uv *out,
-                           struct ns_eye *eye);
+                           struct ns_v1_eye *eye);
 
 struct ns_optical_system *
-ns_create_optical_system(struct ns_eye *eye);
+ns_create_optical_system(struct ns_v1_eye *eye);
 
 
 #ifdef __cplusplus
diff --git a/src/xrt/drivers/north_star/v1_example_config.json b/src/xrt/drivers/north_star/v1_example_config.json
new file mode 100644
index 000000000..4578b3489
--- /dev/null
+++ b/src/xrt/drivers/north_star/v1_example_config.json
@@ -0,0 +1,153 @@
+{
+  "leftEye": {
+    "ellipseMinorAxis": 0.24494899809360505,
+    "ellipseMajorAxis": 0.3099985122680664,
+    "screenForward": {
+      "x": 0.2824554741382599,
+      "y": -0.20611988008022309,
+      "z": 0.9368743300437927
+    },
+    "screenPosition": {
+      "x": -0.07859157025814057,
+      "y": -0.005049339961260557,
+      "z": 0.012514465488493443
+    },
+    "eyePosition": {
+      "x": -0.03200000151991844,
+      "y": 0.001083331648260355,
+      "z": -0.012499995529651642
+    },
+    "eyeRotation": {
+      "x": 7.395533370627759e-32,
+      "y": 1.7763568394002506e-15,
+      "z": 1.1241009818395605e-14,
+      "w": 1.0
+    },
+    "cameraProjection": {
+      "x": -0.699999988079071,
+      "y": 0.699999988079071,
+      "z": 0.30000001192092898,
+      "w": -1.399999976158142
+    },
+    "sphereToWorldSpace": {
+      "e00": 0.05101080238819122,
+      "e01": 0.06071346998214722,
+      "e02": 0.29330453276634219,
+      "e03": -0.11532726138830185,
+      "e10": 0.0,
+      "e11": 0.23695310950279237,
+      "e12": -0.07855913043022156,
+      "e13": 0.026422245427966119,
+      "e20": -0.23957861959934236,
+      "e21": 0.012927045114338398,
+      "e22": 0.062450066208839419,
+      "e23": -0.05475877597928047,
+      "e30": 0.0,
+      "e31": 0.0,
+      "e32": 0.0,
+      "e33": 1.0
+    },
+    "worldToScreenSpace": {
+      "e00": 1.3777992725372315,
+      "e01": 14.81815242767334,
+      "e02": 2.8447227478027345,
+      "e03": 0.14750510454177857,
+      "e10": -16.076780319213868,
+      "e11": 0.5414643883705139,
+      "e12": 4.966066360473633,
+      "e13": -1.3229130506515504,
+      "e20": 0.2824554145336151,
+      "e21": -0.2061198651790619,
+      "e22": 0.9368743300437927,
+      "e23": 0.009433363564312458,
+      "e30": 0.0,
+      "e31": 0.0,
+      "e32": 0.0,
+      "e33": 1.0
+    }
+  },
+  "rightEye": {
+    "ellipseMinorAxis": 0.24494899809360505,
+    "ellipseMajorAxis": 0.3099985122680664,
+    "screenForward": {
+      "x": -0.2919599413871765,
+      "y": -0.20477625727653504,
+      "z": 0.934251606464386
+    },
+    "screenPosition": {
+      "x": 0.07982077449560166,
+      "y": -0.005731542594730854,
+      "z": 0.01401037909090519
+    },
+    "eyePosition": {
+      "x": 0.03200000151991844,
+      "y": 0.001083331648260355,
+      "z": -0.012499995529651642
+    },
+    "eyeRotation": {
+      "x": 7.395533370627759e-32,
+      "y": 1.7763568394002506e-15,
+      "z": 1.1241009818395605e-14,
+      "w": 1.0
+    },
+    "cameraProjection": {
+      "x": -0.699999988079071,
+      "y": 0.699999988079071,
+      "z": 0.30000001192092898,
+      "w": -1.399999976158142
+    },
+    "sphereToWorldSpace": {
+      "e00": 0.05101081356406212,
+      "e01": -0.060713451355695727,
+      "e02": -0.29330453276634219,
+      "e03": 0.11689796298742295,
+      "e10": 0.0,
+      "e11": 0.23695312440395356,
+      "e12": -0.07855910062789917,
+      "e13": 0.026351751759648324,
+      "e20": 0.23957861959934236,
+      "e21": 0.0129270413890481,
+      "e22": 0.062450066208839419,
+      "e23": -0.052946362644433978,
+      "e30": 0.0,
+      "e31": 0.0,
+      "e32": 0.0,
+      "e33": 1.0
+    },
+    "worldToScreenSpace": {
+      "e00": -1.4188950061798096,
+      "e01": 14.821781158447266,
+      "e02": 2.805335283279419,
+      "e03": 0.15890516340732575,
+      "e10": -16.02415657043457,
+      "e11": -0.5628440976142883,
+      "e12": -5.131026744842529,
+      "e13": 1.3477222919464112,
+      "e20": -0.2919600307941437,
+      "e21": -0.2047763168811798,
+      "e22": 0.9342517256736755,
+      "e23": 0.00904157105833292,
+      "e30": 0.0,
+      "e31": 0.0,
+      "e32": 0.0,
+      "e33": 1.0
+    }
+  },
+  "leapTracker": {
+    "name": "Leap Odometry Origin",
+    "serial": "",
+    "localPose": {
+      "position": {
+        "x": 0.0,
+        "y": 0.039777886122465137,
+        "z": 0.0804821103811264
+      },
+      "rotation": {
+        "x": 0.3123600482940674,
+        "y": 0.0,
+        "z": 0.0,
+        "w": 0.9499638080596924
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/xrt/drivers/north_star/v2_example_config.json b/src/xrt/drivers/north_star/v2_example_config.json
new file mode 100644
index 000000000..a7cba35d5
--- /dev/null
+++ b/src/xrt/drivers/north_star/v2_example_config.json
@@ -0,0 +1,17 @@
+{"baseline": 64.0, 
+    "left_uv_to_rect_x": [-0.6496146862315304, 1.522223227078294, -0.4813404618897057, 0.35702657717980785, -0.07956037872995925, 1.1454020997970913, -3.2505199559593336, 1.6279076622052455, 0.38831554051443307, -3.5783949261594095, 7.668716710704405, -4.166750433790205, -0.2855168004377634, 2.304654520623994, -4.773857525576798, 2.6771905826607756], 
+    "left_uv_to_rect_y": [0.5735680823264238, -0.07201032485360503, -0.3760147865913098, 0.12237851205003134, -1.1789607449622186, -0.3639051399896993, 1.9433544351592553, -1.428768695251512, 0.43448180786102064, 1.4198893425729027, -4.061494083054925, 2.5833812097228908, -0.20318807476847794, -0.8824999019236596, 2.2256952824902, -1.3778198332457088], 
+    "right_uv_to_rect_x": [-0.6265143917747795, 1.2911622275040877, -0.5517955125123675, 0.34277683878971815, -0.008660007647723567, 0.5057868616188462, -0.7608772693869136, 0.441561492954449, 0.01102824175518119, -0.32306477445426557, 0.7580684800480195, -0.4808991141180218, -0.0517219007106306, 0.2799238117904546, -0.4494394376437649, 0.3706889336770174], 
+    "right_uv_to_rect_y": [0.5259106471825626, -0.1663727700922601, 0.28233333974385694, 0.1745576324081204, -1.0358003574621981, 0.13565673296823005, 0.03669780270514961, -0.28419296928833887, 0.4258466087687195, -0.03876248767088333, -0.37766287840406676, 0.43848943099165466, -0.34808893357030296, 0.22156250909608152, 0.13817451999521518, -0.2468046426038312],
+
+    "leftEyeAngleLeft": -0.6,
+    "leftEyeAngleRight": 0.6,
+    "leftEyeAngleUp": 0.6,
+    "leftEyeAngleDown": -0.6,
+
+    "rightEyeAngleLeft": -0.6,
+    "rightEyeAngleRight": 0.6,
+    "rightEyeAngleUp": 0.6,
+    "rightEyeAngleDown": -0.6
+
+}
\ No newline at end of file