show axis directions on the screen

Fri, 06 Mar 2020 21:53:30 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Fri, 06 Mar 2020 21:53:30 +0200
changeset 71
198d25fe4e21
parent 70
f21b800b02a4
child 72
7c27cda03747

show axis directions on the screen

src/geometry.cpp file | annotate | diff | comparison | revisions
src/geometry.h file | annotate | diff | comparison | revisions
src/gl/partrenderer.cpp file | annotate | diff | comparison | revisions
src/gl/partrenderer.h file | annotate | diff | comparison | revisions
src/ui/canvas.cpp file | annotate | diff | comparison | revisions
--- a/src/geometry.cpp	Fri Mar 06 20:13:10 2020 +0200
+++ b/src/geometry.cpp	Fri Mar 06 21:53:30 2020 +0200
@@ -2,23 +2,15 @@
 #include "geometry.h"
 
 /**
- * @brief Computes a line from two points
- * @param point_1
- * @param point_2
- * @return line
- */
-geom::Line geom::lineFromPoints(const glm::vec3& point_1, const glm::vec3 point_2)
-{
-	return {point_2 - point_1, point_1};
-}
-
-/**
  * @brief Computes line-plane intersection
  * @param line
  * @param plane
  * @return point of intersection. Does not return a value if the line is in parallel to the plane.
  */
-std::optional<glm::vec3> geom::linePlaneIntersection(const geom::Line& line, const geom::Plane& plane, const float epsilon)
+std::optional<glm::vec3> geom::linePlaneIntersection(
+	const geom::Line<3>& line,
+	const geom::Plane& plane,
+	const float epsilon)
 {
 	const float denominator = glm::dot(line.direction, plane.normal);
 	if (std::abs(denominator) < epsilon)
@@ -82,3 +74,123 @@
 	};
 	return glm::vec3{component(matrix, 0), component(matrix, 1), component(matrix, 2)};
 }
+
+std::optional<glm::vec2> geom::lineLineIntersection(const Line<2>& line_1, const Line<2>& line_2)
+{
+	const float denominator = (line_1.direction.x * line_2.direction.y) - (line_1.direction.y * line_2.direction.x);
+	constexpr float epsilon = 1e-6f;
+	if (std::abs(denominator) < epsilon)
+	{
+		return {};
+	}
+	else
+	{
+		const glm::vec2 p1 = line_1.anchor + line_1.direction;
+		const glm::vec2& p2 = line_1.anchor;
+		const glm::vec2 p3 = line_2.anchor + line_2.direction;
+		const glm::vec2& p4 = line_2.anchor;
+		const float a = glm::determinant(glm::mat2{{p1.x, p2.x}, {p1.y, p2.y}});
+		const float b = glm::determinant(glm::mat2{{p3.x, p4.x}, {p3.y, p4.y}});
+		const float c_x = glm::determinant(glm::mat2{{p1.x, p2.x}, {1, 1}});
+		const float c_y = glm::determinant(glm::mat2{{p1.y, p2.y}, {1, 1}});
+		const float d_x = glm::determinant(glm::mat2{{p3.x, p4.x}, {1, 1}});
+		const float d_y = glm::determinant(glm::mat2{{p3.y, p4.y}, {1, 1}});
+		const float x = glm::determinant(glm::mat2{{a, b}, {c_x, d_x}});
+		const float y = glm::determinant(glm::mat2{{a, b}, {c_y, d_y}});
+		return glm::vec2{x / denominator, y / denominator};
+	}
+}
+
+std::optional<glm::vec2> geom::rayLineSegmentIntersection(const Ray<2>& ray, const LineSegment2D& line)
+{
+	std::optional<glm::vec2> result = lineLineIntersection(rayToLine(ray), lineFromPoints(line.points[0], line.points[1]));
+	if (result.has_value())
+	{
+		const float d1 = glm::dot(*result - ray.anchor, ray.direction);
+		if (d1 < 0)
+		{
+			result.reset();
+		}
+		else
+		{
+			const float d2 = glm::dot(*result - line.points[0], *result - line.points[1]);
+			if (d2 > 0)
+			{
+				result.reset();
+			}
+		}
+	}
+	return result;
+}
+
+std::optional<geom::PointOnRectagle> geom::rayRectangleIntersection(const Ray<2>& ray, const QRectF& rectangle)
+{
+	std::optional<glm::vec2> position;
+	std::optional<PointOnRectagle> result;
+	// Try top
+	position = rayLineSegmentIntersection(ray, top(rectangle));
+	if (position.has_value())
+	{
+		result = {*position, RectangleSide::Top};
+	}
+	else
+	{
+		// Try bottom
+		position = rayLineSegmentIntersection(ray, bottom(rectangle));
+		if (position.has_value())
+		{
+			result = {*position, RectangleSide::Bottom};
+		}
+		else
+		{
+			// Try left
+			position = rayLineSegmentIntersection(ray, left(rectangle));
+			if (position.has_value())
+			{
+				result = {*position, RectangleSide::Left};
+			}
+			else
+			{
+				// Try right
+				position = rayLineSegmentIntersection(ray, right(rectangle));
+				if (position.has_value())
+				{
+					result = {*position, RectangleSide::Right};
+				}
+			}
+		}
+	}
+	return result;
+}
+
+geom::LineSegment2D geom::top(const QRectF& rectangle)
+{
+	return {
+		glm::vec2{rectangle.left(), rectangle.top()},
+		glm::vec2{rectangle.right(), rectangle.top()}
+	};
+}
+
+geom::LineSegment2D geom::bottom(const QRectF& rectangle)
+{
+	return {
+		glm::vec2{rectangle.left(), rectangle.bottom()},
+		glm::vec2{rectangle.right(), rectangle.bottom()}
+	};
+}
+
+geom::LineSegment2D geom::left(const QRectF& rectangle)
+{
+	return {
+		glm::vec2{rectangle.left(), rectangle.top()},
+		glm::vec2{rectangle.left(), rectangle.bottom()}
+	};
+}
+
+geom::LineSegment2D geom::right(const QRectF& rectangle)
+{
+	return {
+		glm::vec2{rectangle.right(), rectangle.top()},
+		glm::vec2{rectangle.right(), rectangle.bottom()}
+	};
+}
--- a/src/geometry.h	Fri Mar 06 20:13:10 2020 +0200
+++ b/src/geometry.h	Fri Mar 06 21:53:30 2020 +0200
@@ -9,10 +9,18 @@
 		glm::vec3 anchor;
 	};
 
+	template<int N, typename T = float, glm::qualifier Q = glm::defaultp>
 	struct Line
 	{
-		glm::vec3 direction;
-		glm::vec3 anchor;
+		glm::vec<N, T, Q> direction;
+		glm::vec<N, T, Q> anchor;
+	};
+
+	template<int N, typename T = float, glm::qualifier Q = glm::defaultp>
+	struct Ray
+	{
+		glm::vec<N, T, Q> direction;
+		glm::vec<N, T, Q> anchor;
 	};
 
 	template<int N>
@@ -21,17 +29,68 @@
 		glm::vec3 points[N];
 	};
 
+	template<int N>
+	struct Polygon2D
+	{
+		glm::vec2 points[N];
+	};
+
 	inline const glm::vec3 origin = {0, 0, 0};
 	inline const Plane XY = {{0, 0, 1}, origin};
 	inline const Plane XZ = {{0, 1, 0}, origin};
 	inline const Plane YZ = {{1, 0, 0}, origin};
 	using Triangle = Polygon<3>;
+	using LineSegment2D = Polygon2D<2>;
 
-	Line lineFromPoints(const glm::vec3& point_1, const glm::vec3 point_2);
+	/**
+	 * @brief Computes a line from two points
+	 * @param point_1
+	 * @param point_2
+	 * @return line
+	 */
+	template<int N, typename T, glm::qualifier Q>
+	Line<N, T, Q> lineFromPoints(const glm::vec<N, T, Q>& point_1, const glm::vec<N, T, Q>& point_2)
+	{
+		return {point_2 - point_1, point_1};
+	}
+
+	template<int N, typename T, glm::qualifier Q>
+	Ray<N, T, Q> rayFromPoints(const glm::vec<N, T, Q>& point_1, const glm::vec<N, T, Q>& point_2)
+	{
+		return {point_2 - point_1, point_1};
+	}
+
+	template<int N, typename T, glm::qualifier Q>
+	Line<N, T, Q> rayToLine(const Ray<N, T, Q>& ray)
+	{
+		return {ray.direction, ray.anchor};
+	}
+
+	enum class RectangleSide
+	{
+		Top,
+		Left,
+		Bottom,
+		Right
+	};
+
+	struct PointOnRectagle
+	{
+		glm::vec2 position;
+		RectangleSide side;
+	};
+
+	std::optional<glm::vec2> lineLineIntersection(const Line<2>& line_1, const Line<2>& line_2);
+	std::optional<glm::vec2> rayLineSegmentIntersection(const Ray<2>& ray, const LineSegment2D& line);
+	std::optional<PointOnRectagle> rayRectangleIntersection(const Ray<2>& ray, const QRectF& rectangle);
 	Plane planeFromTriangle(const Triangle& triangle);
 	glm::vec3 normalVector(const Triangle& triangle);
-	std::optional<glm::vec3> linePlaneIntersection(const Line& line, const Plane& plane, const float epsilon = 1e-6f);
+	std::optional<glm::vec3> linePlaneIntersection(const Line<3>& line, const Plane& plane, const float epsilon = 1e-6f);
 	glm::vec3 scalingVector(const glm::mat4 matrix);
+	LineSegment2D top(const QRectF& rectangle);
+	LineSegment2D bottom(const QRectF& rectangle);
+	LineSegment2D left(const QRectF& rectangle);
+	LineSegment2D right(const QRectF& rectangle);
 	struct ScalingExtract
 	{
 		glm::vec3 scaling;
--- a/src/gl/partrenderer.cpp	Fri Mar 06 20:13:10 2020 +0200
+++ b/src/gl/partrenderer.cpp	Fri Mar 06 21:53:30 2020 +0200
@@ -309,7 +309,7 @@
 	return toQPointF(glm::vec2{projected.x, this->height() - projected.y});
 }
 
-geom::Line PartRenderer::cameraLine(const QPoint& point) const
+geom::Line<3> PartRenderer::cameraLine(const QPoint& point) const
 {
 	const glm::vec3 p1 = this->unproject({point.x(), point.y(), 0});
 	const glm::vec3 p2 = this->unproject({point.x(), point.y(), 1});
--- a/src/gl/partrenderer.h	Fri Mar 06 20:13:10 2020 +0200
+++ b/src/gl/partrenderer.h	Fri Mar 06 21:53:30 2020 +0200
@@ -36,7 +36,7 @@
 	ldraw::Id highlighted = ldraw::NULL_ID;
 	std::optional<glm::vec3> screenToModelCoordinates(const QPoint& point, const geom::Plane& plane) const;
 	QPointF modelToScreenCoordinates(const glm::vec3& point) const;
-	geom::Line cameraLine(const QPoint& point) const;
+	geom::Line<3> cameraLine(const QPoint& point) const;
 	glm::vec3 unproject(const glm::vec3& win) const;
 	glm::mat4 projectionMatrix;
 	glm::mat4 viewMatrix;
--- a/src/ui/canvas.cpp	Fri Mar 06 20:13:10 2020 +0200
+++ b/src/ui/canvas.cpp	Fri Mar 06 21:53:30 2020 +0200
@@ -180,6 +180,61 @@
 		painter.setPen(Qt::white);
 		painter.drawText(pos + QPointF{5, 5}, vectorToString(*this->worldPosition));
 	}
+	{
+		QPainter painter{this};
+		QFont font;
+		//font.setStyle(QFont::StyleItalic);
+		painter.setFont(font);
+		QFontMetrics fontMetrics{font};
+		const auto renderText = [&](const QString& text, const geom::PointOnRectagle& intersection)
+		{
+			QPointF position = toQPointF(intersection.position);
+			const geom::RectangleSide side = intersection.side;
+			switch (side)
+			{
+			case geom::RectangleSide::Top:
+				position += QPointF{0, static_cast<qreal>(fontMetrics.ascent())};
+				break;
+			case geom::RectangleSide::Left:
+				break;
+			case geom::RectangleSide::Bottom:
+				position += QPointF{0, static_cast<qreal>(-fontMetrics.descent())};
+				break;
+			case geom::RectangleSide::Right:
+				position += QPointF{static_cast<qreal>(-fontMetrics.width(text)), 0};
+				break;
+			}
+			painter.drawText(position, text);
+		};
+		const QRectF box {
+			QPointF{0, 0},
+			QPointF{static_cast<qreal>(this->width()), static_cast<qreal>(this->height())}
+		};
+		const QPointF p1 = this->modelToScreenCoordinates(glm::vec3{0, 0, 0});
+
+		static const struct
+		{
+			QString text;
+			glm::vec3 direction;
+		} directions[] =
+		{
+			{"+𝑥", {1, 0, 0}},
+			{"-𝑥", {-1, 0, 0}},
+			{"+𝑦", {0, 1, 0}},
+			{"-𝑦", {0, -1, 0}},
+			{"+𝑧", {0, 0, 1}},
+			{"-𝑧", {0, 0, -1}},
+		};
+		for (const auto& axis : directions)
+		{
+			const QPointF x_p = this->modelToScreenCoordinates(axis.direction);
+			const auto intersection = geom::rayRectangleIntersection(geom::rayFromPoints(toVec2(p1), toVec2(x_p)), box);
+			if (intersection.has_value())
+			{
+				renderText(axis.text, *intersection);
+			}
+		}
+	}
 }
 
 void Canvas::updateGridMatrix()

mercurial