From 7ad46a0e239c7e68c4436e632a77a0d8a14b6333 Mon Sep 17 00:00:00 2001
From: Dan Goodliffe <dan@randomdan.homeip.net>
Date: Fri, 5 Jan 2024 20:08:26 +0000
Subject: Inline functions of GeometricPlane

---
 lib/geometricPlane.cpp | 28 ----------------------------
 lib/geometricPlane.h   | 30 +++++++++++++++++++++++++-----
 2 files changed, 25 insertions(+), 33 deletions(-)
 delete mode 100644 lib/geometricPlane.cpp

(limited to 'lib')

diff --git a/lib/geometricPlane.cpp b/lib/geometricPlane.cpp
deleted file mode 100644
index 567f98a..0000000
--- a/lib/geometricPlane.cpp
+++ /dev/null
@@ -1,28 +0,0 @@
-#include "geometricPlane.h"
-#include "ray.h"
-#include <glm/geometric.hpp>
-#include <glm/gtx/intersect.hpp>
-
-GeometricPlane::PlaneRelation
-GeometricPlane::getRelation(Position3D p) const
-{
-	const auto d = glm::dot(normal, p - origin);
-	return d < 0.F ? PlaneRelation::Below : d > 0.F ? PlaneRelation::Above : PlaneRelation::On;
-}
-
-bool
-GeometricPlane::isIntersect(PlaneRelation a, PlaneRelation b)
-{
-	return ((a == PlaneRelation::Above && b == PlaneRelation::Below)
-			|| (a == PlaneRelation::Below && b == PlaneRelation::Above));
-}
-
-std::optional<GeometricPlane::DistAndPosition>
-GeometricPlane::getRayIntersectPosition(const Ray & ray) const
-{
-	float dist {};
-	if (!glm::intersectRayPlane(ray.start, ray.direction, origin, normal, dist)) {
-		return {};
-	}
-	return DistAndPosition {dist, ray.start + (ray.direction * dist)};
-}
diff --git a/lib/geometricPlane.h b/lib/geometricPlane.h
index c74beff..5be76ec 100644
--- a/lib/geometricPlane.h
+++ b/lib/geometricPlane.h
@@ -1,11 +1,12 @@
 #pragma once
 
 #include "config/types.h"
+#include "ray.h"
+#include <glm/geometric.hpp>
+#include <glm/gtx/intersect.hpp>
 #include <glm/vec3.hpp>
 #include <optional>
 
-class Ray;
-
 class GeometricPlane {
 public:
 	struct DistAndPosition {
@@ -17,8 +18,27 @@ public:
 	Position3D origin;
 	Normal3D normal;
 
-	[[nodiscard]] PlaneRelation getRelation(Position3D point) const;
-	[[nodiscard]] std::optional<DistAndPosition> getRayIntersectPosition(const Ray &) const;
+	[[nodiscard]] inline PlaneRelation
+	getRelation(Position3D point) const
+	{
+		const auto d = glm::dot(normal, point - origin);
+		return d < 0.F ? PlaneRelation::Below : d > 0.F ? PlaneRelation::Above : PlaneRelation::On;
+	}
+
+	[[nodiscard]] inline std::optional<DistAndPosition>
+	getRayIntersectPosition(const Ray & ray) const
+	{
+		float dist {};
+		if (!glm::intersectRayPlane(ray.start, ray.direction, origin, normal, dist)) {
+			return {};
+		}
+		return DistAndPosition {dist, ray.start + (ray.direction * dist)};
+	}
 
-	static bool isIntersect(PlaneRelation a, PlaneRelation b);
+	inline static bool
+	isIntersect(PlaneRelation a, PlaneRelation b)
+	{
+		return ((a == PlaneRelation::Above && b == PlaneRelation::Below)
+				|| (a == PlaneRelation::Below && b == PlaneRelation::Above));
+	}
 };
-- 
cgit v1.2.3


From 335e9c6b4bbc5bbf1a861ac16abf612f5519dd5e Mon Sep 17 00:00:00 2001
From: Dan Goodliffe <dan@randomdan.homeip.net>
Date: Fri, 5 Jan 2024 20:20:14 +0000
Subject: Template GeometicPlane on Position type

---
 assetFactory/faceController.h |  2 +-
 lib/geometricPlane.cpp        |  8 ++++++++
 lib/geometricPlane.h          | 23 +++++++++++------------
 3 files changed, 20 insertions(+), 13 deletions(-)
 create mode 100644 lib/geometricPlane.cpp

(limited to 'lib')

diff --git a/assetFactory/faceController.h b/assetFactory/faceController.h
index fb89d25..e940640 100644
--- a/assetFactory/faceController.h
+++ b/assetFactory/faceController.h
@@ -11,7 +11,7 @@
 
 class FaceController : public Mutation, public Style, public Persistence::Persistable {
 public:
-	class Split : public Persistable, public GeometricPlane {
+	class Split : public Persistable, public GeometricPlaneT<RelativePosition3D> {
 	public:
 		std::string id;
 
diff --git a/lib/geometricPlane.cpp b/lib/geometricPlane.cpp
new file mode 100644
index 0000000..7aadf6a
--- /dev/null
+++ b/lib/geometricPlane.cpp
@@ -0,0 +1,8 @@
+#include "geometricPlane.h"
+
+bool
+GeometricPlane::isIntersect(PlaneRelation a, PlaneRelation b)
+{
+	return ((a == PlaneRelation::Above && b == PlaneRelation::Below)
+			|| (a == PlaneRelation::Below && b == PlaneRelation::Above));
+}
diff --git a/lib/geometricPlane.h b/lib/geometricPlane.h
index 5be76ec..fd3a1d7 100644
--- a/lib/geometricPlane.h
+++ b/lib/geometricPlane.h
@@ -8,18 +8,24 @@
 #include <optional>
 
 class GeometricPlane {
+public:
+	enum class PlaneRelation { Above, Below, On };
+
+	static bool isIntersect(PlaneRelation a, PlaneRelation b);
+};
+
+template<typename PositionType> class GeometricPlaneT : public GeometricPlane {
 public:
 	struct DistAndPosition {
-		float dist;
-		Position3D position;
+		PositionType::value_type dist;
+		PositionType position;
 	};
-	enum class PlaneRelation { Above, Below, On };
 
-	Position3D origin;
+	PositionType origin;
 	Normal3D normal;
 
 	[[nodiscard]] inline PlaneRelation
-	getRelation(Position3D point) const
+	getRelation(PositionType point) const
 	{
 		const auto d = glm::dot(normal, point - origin);
 		return d < 0.F ? PlaneRelation::Below : d > 0.F ? PlaneRelation::Above : PlaneRelation::On;
@@ -34,11 +40,4 @@ public:
 		}
 		return DistAndPosition {dist, ray.start + (ray.direction * dist)};
 	}
-
-	inline static bool
-	isIntersect(PlaneRelation a, PlaneRelation b)
-	{
-		return ((a == PlaneRelation::Above && b == PlaneRelation::Below)
-				|| (a == PlaneRelation::Below && b == PlaneRelation::Above));
-	}
 };
-- 
cgit v1.2.3


From dcef947ea986f4ad4633189652abccebdd7aa3f6 Mon Sep 17 00:00:00 2001
From: Dan Goodliffe <dan@randomdan.homeip.net>
Date: Sat, 6 Jan 2024 13:03:42 +0000
Subject: Helper to create a cubiod from dimensions

---
 game/vehicles/railVehicle.cpp | 16 +++++-----------
 lib/basicShapes.h             | 19 +++++++++++++++++++
 2 files changed, 24 insertions(+), 11 deletions(-)
 create mode 100644 lib/basicShapes.h

(limited to 'lib')

diff --git a/game/vehicles/railVehicle.cpp b/game/vehicles/railVehicle.cpp
index 94f72e4..c720eb6 100644
--- a/game/vehicles/railVehicle.cpp
+++ b/game/vehicles/railVehicle.cpp
@@ -3,12 +3,12 @@
 #include "train.h"
 #include <algorithm>
 #include <array>
+#include <basicShapes.h>
 #include <glm/glm.hpp>
 #include <glm/gtx/intersect.hpp>
 #include <glm/gtx/transform.hpp>
 #include <location.h>
 #include <maths.h>
-#include <memory>
 #include <ray.h>
 
 RailVehicle::RailVehicle(RailVehicleClassPtr rvc) :
@@ -50,16 +50,10 @@ RailVehicle::intersectRay(const Ray & ray, BaryPosition & baryPos, RelativeDista
 	const auto Y = this->rvClass->length / 2.F;
 	constexpr const auto Z = 3900.F;
 	const glm::mat3 moveBy = location.getRotationTransform();
-	const std::array<Position3D, 8> cornerVertices {{
-			location.position() + GlobalPosition3D(moveBy * glm::vec3 {-X, Y, 0}), //  LFB
-			location.position() + GlobalPosition3D(moveBy * glm::vec3 {X, Y, 0}), //   RFB
-			location.position() + GlobalPosition3D(moveBy * glm::vec3 {-X, Y, Z}), //  LFT
-			location.position() + GlobalPosition3D(moveBy * glm::vec3 {X, Y, Z}), //   RFT
-			location.position() + GlobalPosition3D(moveBy * glm::vec3 {-X, -Y, 0}), // LBB
-			location.position() + GlobalPosition3D(moveBy * glm::vec3 {X, -Y, 0}), //  RBB
-			location.position() + GlobalPosition3D(moveBy * glm::vec3 {-X, -Y, Z}), // LBT
-			location.position() + GlobalPosition3D(moveBy * glm::vec3 {X, -Y, Z}), //  RBT
-	}};
+	const auto cornerVertices
+			= cuboidCorners(-X, X, -Y, Y, 0.F, Z) * [&moveBy, this](const auto & corner) -> Position3D {
+		return location.position() + GlobalPosition3D(moveBy * corner);
+	};
 	static constexpr const std::array<glm::vec<3, uint8_t>, 10> triangles {{
 			// Front
 			{0, 1, 2},
diff --git a/lib/basicShapes.h b/lib/basicShapes.h
new file mode 100644
index 0000000..183863e
--- /dev/null
+++ b/lib/basicShapes.h
@@ -0,0 +1,19 @@
+#pragma once
+#include <array>
+#include <glm/vec3.hpp>
+
+template<typename T, glm::qualifier Q = glm::defaultp>
+constexpr std::array<glm::vec<3, T, Q>, 8>
+cuboidCorners(T lx, T ux, T ly, T uy, T lz, T uz)
+{
+	return {{
+			{lx, uy, lz}, // LFB
+			{ux, uy, lz}, // RFB
+			{lx, uy, uz}, // LFT
+			{ux, uy, uz}, // RFT
+			{lx, ly, lz}, // LBB
+			{ux, ly, lz}, // RBB
+			{lx, ly, uz}, // LBT
+			{ux, ly, uz}, // RBT
+	}};
+}
-- 
cgit v1.2.3


From 95eed94c9294f719724283e672cc7c83b2abd282 Mon Sep 17 00:00:00 2001
From: Dan Goodliffe <dan@randomdan.homeip.net>
Date: Sun, 7 Jan 2024 01:10:26 +0000
Subject: Unified crossProduct

---
 game/geoData.cpp  |  2 +-
 gfx/gl/camera.cpp |  4 ++--
 lib/maths.h       | 20 +++++++++++++++++---
 lib/ray.cpp       |  5 +++--
 4 files changed, 23 insertions(+), 8 deletions(-)

(limited to 'lib')

diff --git a/game/geoData.cpp b/game/geoData.cpp
index 97f4a26..816733f 100644
--- a/game/geoData.cpp
+++ b/game/geoData.cpp
@@ -161,7 +161,7 @@ namespace {
 	positionOnTriangle(const GlobalPosition2D point, const GeoData::Triangle<3> & t)
 	{
 		const CalcPosition3D a = t[1] - t[0], b = t[2] - t[0];
-		const auto n = crossInt(a, b);
+		const auto n = crossProduct(a, b);
 		return {point, ((n.x * t[0].x) + (n.y * t[0].y) + (n.z * t[0].z) - (n.x * point.x) - (n.y * point.y)) / n.z};
 	}
 
diff --git a/gfx/gl/camera.cpp b/gfx/gl/camera.cpp
index d362b94..0bc911a 100644
--- a/gfx/gl/camera.cpp
+++ b/gfx/gl/camera.cpp
@@ -31,8 +31,8 @@ Camera::updateView()
 Direction3D
 Camera::upFromForward(const Direction3D & forward)
 {
-	const auto right = glm::cross(forward, ::down);
-	return glm::cross(forward, right);
+	const auto right = crossProduct(forward, ::down);
+	return crossProduct(forward, right);
 }
 
 std::array<GlobalPosition4D, 4>
diff --git a/lib/maths.h b/lib/maths.h
index c1bf61a..b5af9ca 100644
--- a/lib/maths.h
+++ b/lib/maths.h
@@ -68,9 +68,9 @@ sq(T v)
 	return v * v;
 }
 
-template<std::integral T, glm::qualifier Q>
-inline constexpr glm::vec<3, T, Q>
-crossInt(const glm::vec<3, T, Q> a, const glm::vec<3, T, Q> b)
+template<glm::qualifier Q>
+inline constexpr glm::vec<3, int64_t, Q>
+crossProduct(const glm::vec<3, int64_t, Q> a, const glm::vec<3, int64_t, Q> b)
 {
 	return {
 			(a.y * b.z) - (a.z * b.y),
@@ -79,6 +79,20 @@ crossInt(const glm::vec<3, T, Q> a, const glm::vec<3, T, Q> b)
 	};
 }
 
+template<std::integral T, glm::qualifier Q>
+inline constexpr glm::vec<3, T, Q>
+crossProduct(const glm::vec<3, T, Q> a, const glm::vec<3, T, Q> b)
+{
+	return crossProduct<int64_t, Q>(a, b);
+}
+
+template<std::floating_point T, glm::qualifier Q>
+inline constexpr glm::vec<3, T, Q>
+crossProduct(const glm::vec<3, T, Q> a, const glm::vec<3, T, Q> b)
+{
+	return glm::cross(a, b);
+}
+
 template<typename R = float, typename Ta, typename Tb>
 inline constexpr auto
 ratio(Ta a, Tb b)
diff --git a/lib/ray.cpp b/lib/ray.cpp
index 9fb3648..254ad14 100644
--- a/lib/ray.cpp
+++ b/lib/ray.cpp
@@ -1,4 +1,5 @@
 #include "ray.h"
+#include "maths.h"
 #include <algorithm>
 
 Ray
@@ -14,8 +15,8 @@ Ray::distanceToLine(const Position3D & p1, const Position3D & e1) const
 	const auto diff = p1 - e1;
 	const auto d1 = glm::normalize(diff);
 	const auto &p2 = start, &d2 = direction;
-	const auto n = glm::cross(d1, d2);
-	const auto n2 = glm::cross(d2, n);
+	const auto n = crossProduct(d1, d2);
+	const auto n2 = crossProduct(d2, n);
 	const auto c1 = p1 + (glm::dot((p2 - p1), n2) / glm::dot(d1, n2)) * d1;
 	const auto difflength = glm::length(diff);
 	if (glm::length(c1 - p1) > difflength || glm::length(c1 - e1) > difflength) {
-- 
cgit v1.2.3


From 6a1df3dfbae98a05e74c646cc216fbc19ffdb6d6 Mon Sep 17 00:00:00 2001
From: Dan Goodliffe <dan@randomdan.homeip.net>
Date: Sun, 7 Jan 2024 13:04:31 +0000
Subject: Template Ray on position type

---
 assetFactory/faceController.cpp |  2 +-
 game/geoData.cpp                | 12 +++----
 game/geoData.h                  |  4 +--
 game/network/link.cpp           |  9 ++---
 game/network/link.h             |  8 ++---
 game/network/network.cpp        |  7 ++--
 game/network/network.h          |  8 ++---
 game/network/network.impl.h     |  2 +-
 game/selectable.h               |  5 +--
 game/vehicles/railVehicle.cpp   |  6 ++--
 game/vehicles/railVehicle.h     |  4 +--
 game/vehicles/train.cpp         |  4 +--
 game/vehicles/train.h           |  4 +--
 gfx/gl/camera.cpp               |  2 +-
 gfx/gl/camera.h                 |  2 +-
 lib/geometricPlane.h            |  2 +-
 lib/ray.cpp                     | 36 -------------------
 lib/ray.h                       | 78 +++++++++++++++++++++++++++++++++++++----
 test/test-geoData.cpp           |  4 +--
 test/test-maths.cpp             |  2 +-
 ui/builders/freeExtend.cpp      |  6 ++--
 ui/builders/freeExtend.h        |  6 ++--
 ui/builders/join.cpp            |  5 +--
 ui/builders/join.h              |  6 ++--
 ui/builders/straight.cpp        |  6 ++--
 ui/builders/straight.h          |  6 ++--
 ui/editNetwork.cpp              |  4 +--
 ui/editNetwork.h                | 10 +++---
 ui/gameMainSelector.cpp         |  6 ++--
 ui/gameMainSelector.h           |  8 ++---
 30 files changed, 153 insertions(+), 111 deletions(-)
 delete mode 100644 lib/ray.cpp

(limited to 'lib')

diff --git a/assetFactory/faceController.cpp b/assetFactory/faceController.cpp
index d045b3c..10a0c1e 100644
--- a/assetFactory/faceController.cpp
+++ b/assetFactory/faceController.cpp
@@ -90,7 +90,7 @@ FaceController::split(
 		const size_t nextIdx = (curIdx + 1) % vertexRelations.size();
 		const auto &current = vertexRelations[curIdx], next = vertexRelations[nextIdx];
 		if (GeometricPlane::isIntersect(current.second, next.second)) {
-			const auto ray = Ray::fromPoints(mesh.point(current.first), mesh.point(next.first));
+			const auto ray = RayFactory::fromPoints(mesh.point(current.first), mesh.point(next.first));
 			const auto intersect = split.getRayIntersectPosition(ray);
 			assert(intersect);
 			const auto newv = mesh.add_vertex(intersect->position);
diff --git a/game/geoData.cpp b/game/geoData.cpp
index 816733f..b30a35b 100644
--- a/game/geoData.cpp
+++ b/game/geoData.cpp
@@ -194,23 +194,23 @@ GeoData::positionAt(const PointFace & p) const
 }
 
 [[nodiscard]] std::optional<GlobalPosition3D>
-GeoData::intersectRay(const Ray & ray) const
+GeoData::intersectRay(const Ray<GlobalPosition3D> & ray) const
 {
 	return intersectRay(ray, findPoint(ray.start));
 }
 
 [[nodiscard]] std::optional<GlobalPosition3D>
-GeoData::intersectRay(const Ray & ray, FaceHandle face) const
+GeoData::intersectRay(const Ray<GlobalPosition3D> & ray, FaceHandle face) const
 {
 	std::optional<GlobalPosition3D> out;
 	walkUntil(PointFace {ray.start, face},
-			ray.start.xy() + (ray.direction.xy() * RelativePosition2D(upperExtent.xy() - lowerExtent.xy())),
+			ray.start.xy()
+					+ GlobalPosition2D(ray.direction.xy() * RelativePosition2D(upperExtent.xy() - lowerExtent.xy())),
 			[&out, &ray, this](FaceHandle face) {
 				BaryPosition bari {};
-				float dist {};
+				RelativeDistance dist {};
 				const auto t = triangle<3>(face);
-				if (glm::intersectRayTriangle<RelativePosition3D::value_type, glm::defaultp>(
-							ray.start, ray.direction, t[0], t[1], t[2], bari, dist)) {
+				if (ray.intersectTriangle(t.x, t.y, t.z, bari, dist)) {
 					out = t * bari;
 					return true;
 				}
diff --git a/game/geoData.h b/game/geoData.h
index d4d0fb3..e234bfe 100644
--- a/game/geoData.h
+++ b/game/geoData.h
@@ -74,8 +74,8 @@ public:
 	[[nodiscard]] FaceHandle findPoint(GlobalPosition2D, FaceHandle start) const;
 
 	[[nodiscard]] GlobalPosition3D positionAt(const PointFace &) const;
-	[[nodiscard]] std::optional<GlobalPosition3D> intersectRay(const Ray &) const;
-	[[nodiscard]] std::optional<GlobalPosition3D> intersectRay(const Ray &, FaceHandle start) const;
+	[[nodiscard]] std::optional<GlobalPosition3D> intersectRay(const Ray<GlobalPosition3D> &) const;
+	[[nodiscard]] std::optional<GlobalPosition3D> intersectRay(const Ray<GlobalPosition3D> &, FaceHandle start) const;
 
 	void walk(const PointFace & from, const GlobalPosition2D to, const std::function<void(FaceHandle)> & op) const;
 	void walkUntil(const PointFace & from, const GlobalPosition2D to, const std::function<bool(FaceHandle)> & op) const;
diff --git a/game/network/link.cpp b/game/network/link.cpp
index 703a1ca..e8eaea2 100644
--- a/game/network/link.cpp
+++ b/game/network/link.cpp
@@ -33,9 +33,10 @@ LinkStraight::positionAt(float dist, unsigned char start) const
 }
 
 bool
-LinkStraight::intersectRay(const Ray & ray) const
+LinkStraight::intersectRay(const Ray<GlobalPosition3D> & ray) const
 {
-	return ray.passesCloseToEdges(std::array {ends.front().node->pos, ends.back().node->pos}, 1.F);
+	return ray.passesCloseToEdges(
+			std::array {GlobalPosition3D {ends.front().node->pos}, GlobalPosition3D {ends.back().node->pos}}, 1000);
 }
 
 Location
@@ -54,7 +55,7 @@ LinkCurve::positionAt(float dist, unsigned char start) const
 }
 
 bool
-LinkCurve::intersectRay(const Ray & ray) const
+LinkCurve::intersectRay(const Ray<GlobalPosition3D> & ray) const
 {
 	const auto & e0p {ends[0].node->pos};
 	const auto & e1p {ends[1].node->pos};
@@ -64,7 +65,7 @@ LinkCurve::intersectRay(const Ray & ray) const
 	const auto trans {glm::translate(centreBase)};
 
 	auto segCount = static_cast<std::size_t>(std::lround(segs)) + 1;
-	std::vector<Position3D> points;
+	std::vector<GlobalPosition3D> points;
 	points.reserve(segCount);
 	for (Position3D swing = {arc.first, centreBase.z - e0p.z, 0.F}; segCount; swing += step, --segCount) {
 		const auto t {trans * glm::rotate(half_pi - swing.x, up) * glm::translate(Position3D {radius, 0.F, swing.y})};
diff --git a/game/network/link.h b/game/network/link.h
index 78d3e91..95c141e 100644
--- a/game/network/link.h
+++ b/game/network/link.h
@@ -10,7 +10,7 @@
 #include <utility>
 #include <vector>
 
-class Ray;
+template<typename> class Ray;
 
 // Generic network node
 // something that can be travelled to
@@ -45,7 +45,7 @@ public:
 	NO_MOVE(Link);
 
 	[[nodiscard]] virtual Location positionAt(float dist, unsigned char start) const = 0;
-	[[nodiscard]] virtual bool intersectRay(const Ray &) const = 0;
+	[[nodiscard]] virtual bool intersectRay(const Ray<GlobalPosition3D> &) const = 0;
 
 	std::array<End, 2> ends;
 	float length;
@@ -69,7 +69,7 @@ public:
 	NO_MOVE(LinkStraight);
 
 	[[nodiscard]] Location positionAt(float dist, unsigned char start) const override;
-	[[nodiscard]] bool intersectRay(const Ray &) const override;
+	[[nodiscard]] bool intersectRay(const Ray<GlobalPosition3D> &) const override;
 };
 
 LinkStraight::~LinkStraight() = default;
@@ -82,7 +82,7 @@ public:
 	NO_MOVE(LinkCurve);
 
 	[[nodiscard]] Location positionAt(float dist, unsigned char start) const override;
-	[[nodiscard]] bool intersectRay(const Ray &) const override;
+	[[nodiscard]] bool intersectRay(const Ray<GlobalPosition3D> &) const override;
 
 	Position3D centreBase;
 	float radius;
diff --git a/game/network/network.cpp b/game/network/network.cpp
index 1ff5b26..d52e804 100644
--- a/game/network/network.cpp
+++ b/game/network/network.cpp
@@ -49,13 +49,14 @@ Network::candidateNodeAt(Position3D pos) const
 }
 
 Node::Ptr
-Network::intersectRayNodes(const Ray & ray) const
+Network::intersectRayNodes(const Ray<GlobalPosition3D> & ray) const
 {
 	// Click within 2m of a node
 	if (const auto node = std::find_if(nodes.begin(), nodes.end(),
 				[&ray](const Node::Ptr & node) {
-					Position3D ipos, inorm;
-					return glm::intersectRaySphere(ray.start, ray.direction, node->pos, 2.F, ipos, inorm);
+					GlobalPosition3D ipos;
+					Normal3D inorm;
+					return ray.intersectSphere(node->pos, 2000, ipos, inorm);
 				});
 			node != nodes.end()) {
 		return *node;
diff --git a/game/network/network.h b/game/network/network.h
index 8af06a9..9ce6e81 100644
--- a/game/network/network.h
+++ b/game/network/network.h
@@ -14,7 +14,7 @@
 
 class Texture;
 class SceneShader;
-class Ray;
+template<typename> class Ray;
 
 template<size_t... n> using GenDef = std::tuple<glm::vec<n, Distance>...>;
 using GenCurveDef = GenDef<3, 3, 2>;
@@ -32,8 +32,8 @@ public:
 	using NodeInsertion = std::pair<Node::Ptr, NodeIs>;
 	[[nodiscard]] NodeInsertion newNodeAt(Position3D);
 	[[nodiscard]] NodeInsertion candidateNodeAt(Position3D) const;
-	[[nodiscard]] virtual Link::Ptr intersectRayLinks(const Ray &) const = 0;
-	[[nodiscard]] virtual Node::Ptr intersectRayNodes(const Ray &) const;
+	[[nodiscard]] virtual Link::Ptr intersectRayLinks(const Ray<GlobalPosition3D> &) const = 0;
+	[[nodiscard]] virtual Node::Ptr intersectRayNodes(const Ray<GlobalPosition3D> &) const;
 
 	[[nodiscard]] Link::Nexts routeFromTo(const Link::End &, Position3D) const;
 	[[nodiscard]] Link::Nexts routeFromTo(const Link::End &, const Node::Ptr &) const;
@@ -66,7 +66,7 @@ protected:
 	void joinLinks(const Link::Ptr &) const;
 
 protected:
-	[[nodiscard]] Link::Ptr intersectRayLinks(const Ray &) const override;
+	[[nodiscard]] Link::Ptr intersectRayLinks(const Ray<GlobalPosition3D> &) const override;
 
 public:
 	template<typename L, typename... Params>
diff --git a/game/network/network.impl.h b/game/network/network.impl.h
index 8e9e85c..4acbb6d 100644
--- a/game/network/network.impl.h
+++ b/game/network/network.impl.h
@@ -24,7 +24,7 @@ NetworkOf<T>::joinLinks(const Link::Ptr & l) const
 
 template<typename T>
 Link::Ptr
-NetworkOf<T>::intersectRayLinks(const Ray & ray) const
+NetworkOf<T>::intersectRayLinks(const Ray<GlobalPosition3D> & ray) const
 {
 	// Click link
 	if (const auto link = std::find_if(links.objects.begin(), links.objects.end(),
diff --git a/game/selectable.h b/game/selectable.h
index c794461..fc6af4e 100644
--- a/game/selectable.h
+++ b/game/selectable.h
@@ -4,7 +4,7 @@
 #include <glm/glm.hpp>
 #include <special_members.h>
 
-class Ray;
+template<typename> class Ray;
 
 class Selectable {
 public:
@@ -12,5 +12,6 @@ public:
 	virtual ~Selectable() = default;
 	DEFAULT_MOVE_COPY(Selectable);
 
-	[[nodiscard]] virtual bool intersectRay(const Ray &, BaryPosition &, RelativeDistance &) const = 0;
+	[[nodiscard]] virtual bool intersectRay(const Ray<GlobalPosition3D> &, BaryPosition &, RelativeDistance &) const
+			= 0;
 };
diff --git a/game/vehicles/railVehicle.cpp b/game/vehicles/railVehicle.cpp
index c720eb6..985e9f2 100644
--- a/game/vehicles/railVehicle.cpp
+++ b/game/vehicles/railVehicle.cpp
@@ -44,7 +44,7 @@ RailVehicle::move(const Train * t, float & trailBy)
 }
 
 bool
-RailVehicle::intersectRay(const Ray & ray, BaryPosition & baryPos, RelativeDistance & distance) const
+RailVehicle::intersectRay(const Ray<GlobalPosition3D> & ray, BaryPosition & baryPos, RelativeDistance & distance) const
 {
 	constexpr const auto X = 1350.F;
 	const auto Y = this->rvClass->length / 2.F;
@@ -73,7 +73,7 @@ RailVehicle::intersectRay(const Ray & ray, BaryPosition & baryPos, RelativeDista
 	}};
 	return std::any_of(
 			triangles.begin(), triangles.end(), [&cornerVertices, &ray, &baryPos, &distance](const auto & idx) {
-				return glm::intersectRayTriangle(ray.start, ray.direction, cornerVertices[idx[0]],
-						cornerVertices[idx[1]], cornerVertices[idx[2]], baryPos, distance);
+				return ray.intersectTriangle(
+						cornerVertices[idx[0]], cornerVertices[idx[1]], cornerVertices[idx[2]], baryPos, distance);
 			});
 }
diff --git a/game/vehicles/railVehicle.h b/game/vehicles/railVehicle.h
index f6e4764..bf1e782 100644
--- a/game/vehicles/railVehicle.h
+++ b/game/vehicles/railVehicle.h
@@ -7,7 +7,7 @@
 #include <glm/glm.hpp>
 #include <memory>
 
-class Ray;
+template<typename> class Ray;
 class Train;
 
 class RailVehicle : Selectable, RailVehicleClass::Instance {
@@ -16,7 +16,7 @@ public:
 
 	void move(const Train *, float & trailBy);
 
-	[[nodiscard]] bool intersectRay(const Ray &, BaryPosition &, RelativeDistance &) const override;
+	[[nodiscard]] bool intersectRay(const Ray<GlobalPosition3D> &, BaryPosition &, RelativeDistance &) const override;
 
 	RailVehicleClassPtr rvClass;
 	using LV = RailVehicleClass::LocationVertex;
diff --git a/game/vehicles/train.cpp b/game/vehicles/train.cpp
index 05b2d8a..5bddd61 100644
--- a/game/vehicles/train.cpp
+++ b/game/vehicles/train.cpp
@@ -9,7 +9,7 @@
 #include <optional>
 #include <utility>
 
-class Ray;
+template<typename> class Ray;
 
 Location
 Train::getBogiePosition(float linkDist, float dist) const
@@ -20,7 +20,7 @@ Train::getBogiePosition(float linkDist, float dist) const
 }
 
 bool
-Train::intersectRay(const Ray & ray, BaryPosition & baryPos, RelativeDistance & distance) const
+Train::intersectRay(const Ray<GlobalPosition3D> & ray, BaryPosition & baryPos, RelativeDistance & distance) const
 {
 	return applyOne(&RailVehicle::intersectRay, ray, baryPos, distance) != end();
 }
diff --git a/game/vehicles/train.h b/game/vehicles/train.h
index bb668ed..4320103 100644
--- a/game/vehicles/train.h
+++ b/game/vehicles/train.h
@@ -13,7 +13,7 @@
 
 class SceneShader;
 class ShadowMapper;
-class Ray;
+template<typename> class Ray;
 
 class Train : public Vehicle, public Collection<RailVehicle, false>, public Can<Go>, public Can<Idle> {
 public:
@@ -25,7 +25,7 @@ public:
 		return objects.front()->location;
 	}
 
-	[[nodiscard]] bool intersectRay(const Ray &, BaryPosition &, RelativeDistance &) const override;
+	[[nodiscard]] bool intersectRay(const Ray<GlobalPosition3D> &, BaryPosition &, RelativeDistance &) const override;
 
 	void tick(TickDuration elapsed) override;
 	void doActivity(Go *, TickDuration) override;
diff --git a/gfx/gl/camera.cpp b/gfx/gl/camera.cpp
index 0bc911a..fb711dd 100644
--- a/gfx/gl/camera.cpp
+++ b/gfx/gl/camera.cpp
@@ -13,7 +13,7 @@ Camera::Camera(GlobalPosition3D pos, Angle fov, Angle aspect, GlobalDistance zNe
 	updateView();
 }
 
-Ray
+Ray<GlobalPosition3D>
 Camera::unProject(const ScreenRelCoord & mouse) const
 {
 	static constexpr const glm::vec4 screen {0, 0, 1, 1};
diff --git a/gfx/gl/camera.h b/gfx/gl/camera.h
index eca7b8f..8d53261 100644
--- a/gfx/gl/camera.h
+++ b/gfx/gl/camera.h
@@ -15,7 +15,7 @@ public:
 		return viewProjection;
 	}
 
-	[[nodiscard]] Ray unProject(const ScreenRelCoord &) const;
+	[[nodiscard]] Ray<GlobalPosition3D> unProject(const ScreenRelCoord &) const;
 
 	void
 	setPosition(const GlobalPosition3D & p)
diff --git a/lib/geometricPlane.h b/lib/geometricPlane.h
index fd3a1d7..3f95d3c 100644
--- a/lib/geometricPlane.h
+++ b/lib/geometricPlane.h
@@ -32,7 +32,7 @@ public:
 	}
 
 	[[nodiscard]] inline std::optional<DistAndPosition>
-	getRayIntersectPosition(const Ray & ray) const
+	getRayIntersectPosition(const Ray<PositionType> & ray) const
 	{
 		float dist {};
 		if (!glm::intersectRayPlane(ray.start, ray.direction, origin, normal, dist)) {
diff --git a/lib/ray.cpp b/lib/ray.cpp
deleted file mode 100644
index 254ad14..0000000
--- a/lib/ray.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-#include "ray.h"
-#include "maths.h"
-#include <algorithm>
-
-Ray
-Ray::fromPoints(Position3D start, Position3D p)
-{
-	return {start, glm::normalize(p - start)};
-}
-
-float
-Ray::distanceToLine(const Position3D & p1, const Position3D & e1) const
-{
-	// https://en.wikipedia.org/wiki/Skew_lines
-	const auto diff = p1 - e1;
-	const auto d1 = glm::normalize(diff);
-	const auto &p2 = start, &d2 = direction;
-	const auto n = crossProduct(d1, d2);
-	const auto n2 = crossProduct(d2, n);
-	const auto c1 = p1 + (glm::dot((p2 - p1), n2) / glm::dot(d1, n2)) * d1;
-	const auto difflength = glm::length(diff);
-	if (glm::length(c1 - p1) > difflength || glm::length(c1 - e1) > difflength) {
-		return std::numeric_limits<float>::infinity();
-	}
-	return glm::abs(glm::dot(n, p1 - p2));
-}
-
-bool
-Ray::passesCloseToEdges(const std::span<const Position3D> positions, float distance) const
-{
-	return std::adjacent_find(positions.begin(), positions.end(),
-				   [this, distance](const Position3D & a, const Position3D & b) {
-					   return distanceToLine(a, b) <= distance;
-				   })
-			!= positions.end();
-}
diff --git a/lib/ray.h b/lib/ray.h
index bc70c74..e1f43c3 100644
--- a/lib/ray.h
+++ b/lib/ray.h
@@ -1,20 +1,84 @@
 #pragma once
 
 #include "config/types.h"
+#include "maths.h"
+
+#include <algorithm>
 #include <glm/glm.hpp>
+#include <glm/gtx/intersect.hpp>
 #include <span>
 
-class Ray {
+template<typename PositionType> class Ray {
 public:
 #ifndef __cpp_aggregate_paren_init
-	Ray(Position3D start, Direction3D direction) : start {start}, direction {direction} { }
+	Ray(PositionType start, Direction3D direction) : start {start}, direction {direction} { }
 #endif
 
-	static Ray fromPoints(Position3D, Position3D);
-
-	Position3D start;
+	PositionType start;
 	Direction3D direction;
 
-	[[nodiscard]] float distanceToLine(const Position3D & a, const Position3D & b) const;
-	[[nodiscard]] bool passesCloseToEdges(const std::span<const Position3D> positions, float distance) const;
+	[[nodiscard]] PositionType::value_type
+	distanceToLine(const PositionType & p1, const PositionType & e1) const
+	{
+		// https://en.wikipedia.org/wiki/Skew_lines
+		const RelativePosition3D diff = p1 - e1;
+		const auto d1 = glm::normalize(diff);
+		const auto n = crossProduct(d1, direction);
+		const auto n2 = crossProduct(direction, n);
+		const auto c1 = p1 + PositionType((glm::dot(RelativePosition3D(start - p1), n2) / glm::dot(d1, n2)) * d1);
+		const auto difflength = glm::length(diff);
+		if (glm::length(RelativePosition3D(c1 - p1)) > difflength
+				|| glm::length(RelativePosition3D(c1 - e1)) > difflength) {
+			return std::numeric_limits<typename PositionType::value_type>::infinity();
+		}
+		return static_cast<PositionType::value_type>(glm::abs(glm::dot(n, RelativePosition3D(p1 - start))));
+	}
+
+	[[nodiscard]] bool
+	passesCloseToEdges(const std::span<const PositionType> positions, const PositionType::value_type distance) const
+	{
+		return std::adjacent_find(positions.begin(), positions.end(), [this, distance](const auto & a, const auto & b) {
+			return distanceToLine(a, b) <= distance;
+		}) != positions.end();
+	}
+
+	bool
+	intersectTriangle(const PositionType t0, const PositionType t1, const PositionType t2, BaryPosition & bary,
+			RelativeDistance & distance) const
+	{
+		if constexpr (std::is_floating_point_v<typename PositionType::value_type>) {
+			return glm::intersectRayTriangle(start, direction, t0, t1, t2, bary, distance);
+		}
+		else {
+			const RelativePosition3D t0r = t0 - start, t1r = t1 - start, t2r = t2 - start;
+			return glm::intersectRayTriangle({}, direction, t0r, t1r, t2r, bary, distance);
+		}
+	}
+
+	bool
+	intersectSphere(const PositionType centre, const PositionType::value_type size, PositionType & position,
+			Normal3D & normal) const
+	{
+		if constexpr (std::is_floating_point_v<typename PositionType::value_type>) {
+			return glm::intersectRaySphere(start, direction, centre, size, position, normal);
+		}
+		else {
+			const RelativePosition3D cr = centre - start;
+			RelativePosition3D positionF {};
+			const auto r = glm::intersectRaySphere(
+					{}, direction, cr, static_cast<RelativeDistance>(size), positionF, normal);
+			position = GlobalPosition3D(positionF) + start;
+			return r;
+		}
+	}
+};
+
+class RayFactory {
+public:
+	template<typename PositionType>
+	static Ray<PositionType>
+	fromPoints(PositionType start, PositionType p)
+	{
+		return {start, glm::normalize(p - start)};
+	}
 };
diff --git a/test/test-geoData.cpp b/test/test-geoData.cpp
index d1c6b8b..efe845c 100644
--- a/test/test-geoData.cpp
+++ b/test/test-geoData.cpp
@@ -109,8 +109,8 @@ using FindRayIntersectData = std::tuple<GlobalPosition3D, Direction3D, GlobalPos
 
 BOOST_DATA_TEST_CASE(findRayIntersect,
 		boost::unit_test::data::make<FindRayIntersectData>({
-				{{310000000, 490000000, 50000}, {1, 1, -2}, {310008585, 490008585, 32834}},
-				{{310000000, 490000000, 50000}, {1, 1, -1}, {310017136, 490017136, 32868}},
+				{{310000000, 490000000, 50000}, {1, 1, -2}, {310008582, 490008582, 32834}},
+				{{310000000, 490000000, 50000}, {1, 1, -1}, {310017131, 490017131, 32868}},
 		}),
 		p, d, i)
 {
diff --git a/test/test-maths.cpp b/test/test-maths.cpp
index b363c17..8680409 100644
--- a/test/test-maths.cpp
+++ b/test/test-maths.cpp
@@ -265,7 +265,7 @@ BOOST_AUTO_TEST_CASE(camera_clicks)
 	Camera camera {{}, ::half_pi, 1.25F, 1000, 10000000};
 	constexpr float centre {0.5F}, right {0.9F}, left {0.1F}, top {1.F}, bottom {0.F};
 	camera.setForward(::north);
-	BOOST_CHECK_EQUAL(camera.unProject({centre, centre}).start, RelativePosition3D {});
+	BOOST_CHECK_EQUAL(camera.unProject({centre, centre}).start, GlobalPosition3D {});
 	BOOST_CHECK_CLOSE_VEC(camera.unProject({centre, centre}).direction, ::north);
 	BOOST_CHECK_CLOSE_VEC(camera.unProject({left, centre}).direction, glm::normalize(::north + ::west));
 	BOOST_CHECK_CLOSE_VEC(camera.unProject({right, centre}).direction, glm::normalize(::north + ::east));
diff --git a/ui/builders/freeExtend.cpp b/ui/builders/freeExtend.cpp
index 1520421..47356c3 100644
--- a/ui/builders/freeExtend.cpp
+++ b/ui/builders/freeExtend.cpp
@@ -11,7 +11,8 @@ BuilderFreeExtend::hint() const
 }
 
 void
-BuilderFreeExtend::move(Network * network, const GeoData * geoData, const SDL_MouseMotionEvent &, const Ray & ray)
+BuilderFreeExtend::move(
+		Network * network, const GeoData * geoData, const SDL_MouseMotionEvent &, const Ray<GlobalPosition3D> & ray)
 {
 	if (p1) {
 		if (const auto p = network->intersectRayNodes(ray)) {
@@ -30,7 +31,8 @@ BuilderFreeExtend::move(Network * network, const GeoData * geoData, const SDL_Mo
 }
 
 void
-BuilderFreeExtend::click(Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e, const Ray & ray)
+BuilderFreeExtend::click(
+		Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e, const Ray<GlobalPosition3D> & ray)
 {
 	switch (e.button) {
 		case SDL_BUTTON_LEFT:
diff --git a/ui/builders/freeExtend.h b/ui/builders/freeExtend.h
index b276426..127fdc6 100644
--- a/ui/builders/freeExtend.h
+++ b/ui/builders/freeExtend.h
@@ -6,8 +6,10 @@ class GeoData;
 
 class BuilderFreeExtend : public EditNetwork::Builder {
 	std::string hint() const override;
-	void click(Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e, const Ray & ray) override;
-	void move(Network * network, const GeoData * geoData, const SDL_MouseMotionEvent & e, const Ray & ray) override;
+	void click(Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e,
+			const Ray<GlobalPosition3D> & ray) override;
+	void move(Network * network, const GeoData * geoData, const SDL_MouseMotionEvent & e,
+			const Ray<GlobalPosition3D> & ray) override;
 
 	std::optional<Position3D> p1;
 };
diff --git a/ui/builders/join.cpp b/ui/builders/join.cpp
index caa635f..7474c5b 100644
--- a/ui/builders/join.cpp
+++ b/ui/builders/join.cpp
@@ -11,7 +11,7 @@ BuilderJoin::hint() const
 }
 
 void
-BuilderJoin::move(Network * network, const GeoData *, const SDL_MouseMotionEvent &, const Ray & ray)
+BuilderJoin::move(Network * network, const GeoData *, const SDL_MouseMotionEvent &, const Ray<GlobalPosition3D> & ray)
 {
 	if (p1) {
 		if (const auto p = network->intersectRayNodes(ray)) {
@@ -24,7 +24,8 @@ BuilderJoin::move(Network * network, const GeoData *, const SDL_MouseMotionEvent
 }
 
 void
-BuilderJoin::click(Network * network, const GeoData *, const SDL_MouseButtonEvent & e, const Ray & ray)
+BuilderJoin::click(
+		Network * network, const GeoData *, const SDL_MouseButtonEvent & e, const Ray<GlobalPosition3D> & ray)
 {
 	switch (e.button) {
 		case SDL_BUTTON_LEFT:
diff --git a/ui/builders/join.h b/ui/builders/join.h
index bb0bd4c..dd57895 100644
--- a/ui/builders/join.h
+++ b/ui/builders/join.h
@@ -6,8 +6,10 @@ class GeoData;
 
 class BuilderJoin : public EditNetwork::Builder {
 	std::string hint() const override;
-	void click(Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e, const Ray & ray) override;
-	void move(Network * network, const GeoData * geoData, const SDL_MouseMotionEvent & e, const Ray & ray) override;
+	void click(Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e,
+			const Ray<GlobalPosition3D> & ray) override;
+	void move(Network * network, const GeoData * geoData, const SDL_MouseMotionEvent & e,
+			const Ray<GlobalPosition3D> & ray) override;
 
 	void create(Network * network, const Node::Ptr & p1, const Node::Ptr & p2) const;
 
diff --git a/ui/builders/straight.cpp b/ui/builders/straight.cpp
index 9b262bb..866705a 100644
--- a/ui/builders/straight.cpp
+++ b/ui/builders/straight.cpp
@@ -11,7 +11,8 @@ BuilderStraight::hint() const
 }
 
 void
-BuilderStraight::move(Network * network, const GeoData * geoData, const SDL_MouseMotionEvent &, const Ray & ray)
+BuilderStraight::move(
+		Network * network, const GeoData * geoData, const SDL_MouseMotionEvent &, const Ray<GlobalPosition3D> & ray)
 {
 	if (p1) {
 		if (const auto p = geoData->intersectRay(ray)) {
@@ -24,7 +25,8 @@ BuilderStraight::move(Network * network, const GeoData * geoData, const SDL_Mous
 }
 
 void
-BuilderStraight::click(Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e, const Ray & ray)
+BuilderStraight::click(
+		Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e, const Ray<GlobalPosition3D> & ray)
 {
 	switch (e.button) {
 		case SDL_BUTTON_LEFT:
diff --git a/ui/builders/straight.h b/ui/builders/straight.h
index cf99a1d..63f9a40 100644
--- a/ui/builders/straight.h
+++ b/ui/builders/straight.h
@@ -6,8 +6,10 @@ class GeoData;
 
 class BuilderStraight : public EditNetwork::Builder {
 	std::string hint() const override;
-	void click(Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e, const Ray & ray) override;
-	void move(Network * network, const GeoData * geoData, const SDL_MouseMotionEvent & e, const Ray & ray) override;
+	void click(Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e,
+			const Ray<GlobalPosition3D> & ray) override;
+	void move(Network * network, const GeoData * geoData, const SDL_MouseMotionEvent & e,
+			const Ray<GlobalPosition3D> & ray) override;
 
 	void create(Network * network, Position3D p1, Position3D p2) const;
 
diff --git a/ui/editNetwork.cpp b/ui/editNetwork.cpp
index 754053b..7fbde32 100644
--- a/ui/editNetwork.cpp
+++ b/ui/editNetwork.cpp
@@ -22,7 +22,7 @@ EditNetwork::EditNetwork(Network * n) :
 }
 
 bool
-EditNetwork::click(const SDL_MouseButtonEvent & e, const Ray & ray)
+EditNetwork::click(const SDL_MouseButtonEvent & e, const Ray<GlobalPosition3D> & ray)
 {
 	if (builder && (e.button == SDL_BUTTON_LEFT || e.button == SDL_BUTTON_MIDDLE)) {
 		builder->click(network, gameState->geoData.get(), e, ray);
@@ -32,7 +32,7 @@ EditNetwork::click(const SDL_MouseButtonEvent & e, const Ray & ray)
 }
 
 bool
-EditNetwork::move(const SDL_MouseMotionEvent & e, const Ray & ray)
+EditNetwork::move(const SDL_MouseMotionEvent & e, const Ray<GlobalPosition3D> & ray)
 {
 	if (builder) {
 		builder->move(network, gameState->geoData.get(), e, ray);
diff --git a/ui/editNetwork.h b/ui/editNetwork.h
index e1aaa61..c8a2f13 100644
--- a/ui/editNetwork.h
+++ b/ui/editNetwork.h
@@ -9,14 +9,14 @@
 #include <gfx/models/texture.h>
 #include <optional>
 
-class Ray;
+template<typename> class Ray;
 
 class EditNetwork : public GameMainSelector::Component, public WorldOverlay {
 public:
 	explicit EditNetwork(Network *);
 
-	bool click(const SDL_MouseButtonEvent & e, const Ray &) override;
-	bool move(const SDL_MouseMotionEvent & e, const Ray &) override;
+	bool click(const SDL_MouseButtonEvent & e, const Ray<GlobalPosition3D> &) override;
+	bool move(const SDL_MouseMotionEvent & e, const Ray<GlobalPosition3D> &) override;
 	bool handleInput(const SDL_Event & e, const UIComponent::Position &) override;
 	void render(const SceneShader &) const override;
 	void render(const UIShader & shader, const UIComponent::Position & pos) const override;
@@ -28,8 +28,8 @@ public:
 		virtual ~Builder() = default;
 		virtual void render(const SceneShader & shader) const;
 		virtual std::string hint() const = 0;
-		virtual void click(Network *, const GeoData *, const SDL_MouseButtonEvent &, const Ray &) = 0;
-		virtual void move(Network *, const GeoData *, const SDL_MouseMotionEvent &, const Ray &) = 0;
+		virtual void click(Network *, const GeoData *, const SDL_MouseButtonEvent &, const Ray<GlobalPosition3D> &) = 0;
+		virtual void move(Network *, const GeoData *, const SDL_MouseMotionEvent &, const Ray<GlobalPosition3D> &) = 0;
 
 		using Ptr = std::unique_ptr<Builder>;
 
diff --git a/ui/gameMainSelector.cpp b/ui/gameMainSelector.cpp
index 703cfab..a577838 100644
--- a/ui/gameMainSelector.cpp
+++ b/ui/gameMainSelector.cpp
@@ -71,7 +71,7 @@ GameMainSelector::handleInput(const SDL_Event & e, const Position & parentPos)
 }
 
 void
-GameMainSelector::defaultClick(const Ray & ray)
+GameMainSelector::defaultClick(const Ray<GlobalPosition3D> & ray)
 {
 	BaryPosition baryPos {};
 	RelativeDistance distance {};
@@ -90,13 +90,13 @@ GameMainSelector::defaultClick(const Ray & ray)
 }
 
 bool
-GameMainSelector::Component::click(const SDL_MouseButtonEvent &, const Ray &)
+GameMainSelector::Component::click(const SDL_MouseButtonEvent &, const Ray<GlobalPosition3D> &)
 {
 	return false;
 }
 
 bool
-GameMainSelector::Component::move(const SDL_MouseMotionEvent &, const Ray &)
+GameMainSelector::Component::move(const SDL_MouseMotionEvent &, const Ray<GlobalPosition3D> &)
 {
 	return false;
 }
diff --git a/ui/gameMainSelector.h b/ui/gameMainSelector.h
index 88db34b..cc30707 100644
--- a/ui/gameMainSelector.h
+++ b/ui/gameMainSelector.h
@@ -10,7 +10,7 @@
 #include <string>
 
 class SceneShader;
-class Ray;
+template<typename> class Ray;
 class UIShader;
 class Camera;
 
@@ -20,8 +20,8 @@ public:
 	public:
 		virtual ~Component() = default;
 
-		virtual bool click(const SDL_MouseButtonEvent &, const Ray &);
-		virtual bool move(const SDL_MouseMotionEvent &, const Ray &);
+		virtual bool click(const SDL_MouseButtonEvent &, const Ray<GlobalPosition3D> &);
+		virtual bool move(const SDL_MouseMotionEvent &, const Ray<GlobalPosition3D> &);
 		virtual bool handleInput(const SDL_Event &, const Position & pos);
 		virtual void render(const UIShader & shader, const Position & pos) const;
 		virtual void render(const SceneShader &) const;
@@ -34,7 +34,7 @@ public:
 
 	bool handleInput(const SDL_Event & e, const Position &) override;
 
-	void defaultClick(const Ray & ray);
+	void defaultClick(const Ray<GlobalPosition3D> & ray);
 
 	std::unique_ptr<Component> target;
 
-- 
cgit v1.2.3