diff --git a/src/AxisModel.qml b/src/AxisModel.qml index c4a1209..13dbdbb 100644 --- a/src/AxisModel.qml +++ b/src/AxisModel.qml @@ -31,7 +31,8 @@ Node { model: axis.tickLabels delegate: Node { - position: axis.direction ? Qt.vector3d(-root.scl.x + tickX * root.scl.x, -root.scl.y - 210, 0) : Qt.vector3d(-root.scl.x - 30, -root.scl.y - 180 + tickY * root.scl.y, 0) + eulerRotation: axis.direction == Axis.Z ? Qt.vector3d(0, 90.0, 0) : Qt.vector3d(0, 0, 0) + position: axis.direction == Axis.X ? Qt.vector3d(-root.scl.x + tickX * root.scl.x, -root.scl.y - 210, 0) : Qt.vector3d(-root.scl.x - 30, -root.scl.y - 180 + tickY * root.scl.y, tickZ * root.scl.x) Item { anchors.centerIn: parent diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c0f188e..8b20521 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,6 +18,8 @@ qt_add_qml_module( plotGeometry.cpp scatterGeometry.cpp scatterGeometry.h + solid.h + solid.cpp triangle.cpp QML_FILES AxisModel.qml diff --git a/src/axis.cpp b/src/axis.cpp index 5942347..4088921 100644 --- a/src/axis.cpp +++ b/src/axis.cpp @@ -4,10 +4,12 @@ #include "axis.h" #include +#include "solid.h" +#include "vector3.h" #include #include -Axis::Axis() : minimum_(-1), maximum_(1), direction_(false), thickness_(0.001), tickLabels_(*this) +Axis::Axis() : minimum_(-1), maximum_(1), direction_(Axis::Direction::Y), thickness_(0.001), tickLabels_(*this) { updateData(); connect(this, &Axis::dataChanged, this, &Axis::updateData); @@ -15,7 +17,7 @@ Axis::Axis() : minimum_(-1), maximum_(1), direction_(false), thickness_(0.001), AxisTickLabels *Axis::tickLabels() { return &tickLabels_; } -bool Axis::direction() const { return direction_; } +Axis::Direction Axis::direction() const { return direction_; } double Axis::minimum() const { return minimum_; } void Axis::setMinimum(const double value) { minimum_ = value; } double Axis::maximum() const { return maximum_; } @@ -59,52 +61,23 @@ void Axis::updateData() int stride = 3 * sizeof(float); - QByteArray vertexData(6 * stride, Qt::Initialization::Uninitialized); + QByteArray vertexData(6 * 6 * stride, Qt::Initialization::Uninitialized); float *p = reinterpret_cast(vertexData.data()); - if (direction_) + switch (direction_) { - *p++ = -1.0; - *p++ = -1.0 - thickness_; - *p++ = 0; - *p++ = 1.0; - *p++ = -1.0 - thickness_; - *p++ = 0; - *p++ = 1.0; - *p++ = -1.0 + thickness_; - *p++ = 0; - - *p++ = 1.0; - *p++ = -1.0 + thickness_; - *p++ = 0; - *p++ = -1.0; - *p++ = -1.0 + thickness_; - *p++ = 0; - *p++ = -1.0; - *p++ = -1.0 - thickness_; - *p++ = 0; - } - else - { - *p++ = -1.0 - thickness_; - *p++ = -1.0; - *p++ = 0; - *p++ = -1.0 + thickness_; - *p++ = 1.0; - *p++ = 0; - *p++ = -1.0 - thickness_; - *p++ = 1.0; - *p++ = 0; - - *p++ = -1.0 - thickness_; - *p++ = -1.0; - *p++ = 0; - *p++ = -1.0 + thickness_; - *p++ = -1.0; - *p++ = 0; - *p++ = -1.0 + thickness_; - *p++ = 1.0; - *p++ = 0; + case Axis::Direction::X: + draw_tube(p, thickness_, Vec3{-1.0f - (float)thickness_, -1.0, 0}, + Vec3{1.0f + (float)thickness_, -1.0, 0}, yhat, zhat); + break; + case Axis::Direction::Y: + draw_tube(p, thickness_, Vec3{-1.0, -1.0f - (float)thickness_, 0}, + Vec3{-1.0, 1.0f + (float)thickness_, 0}, zhat, xhat); + break; + case Axis::Direction::Z: + draw_tube(p, thickness_, Vec3{-1.0, -1.0, 0.0f - (float)thickness_}, + Vec3{-1.0, -1.0, 2.0f + (float)thickness_}, xhat, yhat); + break; } setVertexData(vertexData); diff --git a/src/axis.h b/src/axis.h index 7e3e1c7..e76eba7 100644 --- a/src/axis.h +++ b/src/axis.h @@ -15,13 +15,21 @@ class Axis : public QQuick3DGeometry Q_PROPERTY(double thickness MEMBER thickness_ NOTIFY dataChanged) Q_PROPERTY(double minimum READ minimum WRITE setMinimum NOTIFY dataChanged) Q_PROPERTY(double maximum READ maximum WRITE setMaximum NOTIFY dataChanged) - Q_PROPERTY(bool direction MEMBER direction_ NOTIFY dataChanged) + Q_PROPERTY(Direction direction MEMBER direction_ NOTIFY dataChanged) Q_PROPERTY(AxisTickLabels *tickLabels READ tickLabels NOTIFY dataChanged) Q_PROPERTY(int tickCount READ tickCount NOTIFY dataChanged) public: Axis(); + enum Direction + { + Z, + Y, + X + }; + Q_ENUMS(Direction) + /** Translate data space values into plot space values. Subclasses will overload this method to enable different scaling methods (e.g. log scaling). */ @@ -32,7 +40,7 @@ class Axis : public QQuick3DGeometry /** The direction in which the axis is pointing. \todo This needs to be a proper enum to handle three dimensions */ - bool direction() const; + Direction direction() const; /** Get the minimum edge of the axis */ double minimum() const; /** Set the minimum edge of the axis */ @@ -65,7 +73,7 @@ class Axis : public QQuick3DGeometry virtual void updateTicks_(); /** Update the axis display in response to changes in the data.*/ void updateData(); - bool direction_; + Direction direction_; /** The line thickness of the axis */ double thickness_; AxisTickLabels tickLabels_; diff --git a/src/axisTickLabels.cpp b/src/axisTickLabels.cpp index e2956d0..71a626e 100644 --- a/src/axisTickLabels.cpp +++ b/src/axisTickLabels.cpp @@ -16,9 +16,11 @@ QVariant AxisTickLabels::data(const QModelIndex &index, int role) const case Qt::UserRole: return QString("%1").arg(parent_.tick(index.row())); case (Qt::UserRole + 1): - return parent_.direction() ? parent_.tickCoord(index.row()) : 0; + return (parent_.direction() == Axis::Direction::X) ? parent_.tickCoord(index.row()) : 0; case (Qt::UserRole + 2): - return parent_.direction() ? 0 : parent_.tickCoord(index.row()); + return (parent_.direction() == Axis::Direction::Y) ? parent_.tickCoord(index.row()) : 0; + case (Qt::UserRole + 3): + return (parent_.direction() == Axis::Direction::Z) ? parent_.tickCoord(index.row()) : 0; default: return index.row(); } @@ -30,6 +32,7 @@ QHash AxisTickLabels::roleNames() const roles[Qt::UserRole] = "tickLabel"; roles[Qt::UserRole + 1] = "tickX"; roles[Qt::UserRole + 2] = "tickY"; + roles[Qt::UserRole + 3] = "tickZ"; return roles; } diff --git a/src/lineGeometry.cpp b/src/lineGeometry.cpp index 248ecc5..3eebb5a 100644 --- a/src/lineGeometry.cpp +++ b/src/lineGeometry.cpp @@ -7,7 +7,7 @@ LineGeometry::LineGeometry() : PlotGeometry() {} -std::vector LineGeometry::faces_(std::vector ps) const +std::vector LineGeometry::faces_(std::vector> ps) const { const int N = ps.size(); diff --git a/src/lineGeometry.h b/src/lineGeometry.h index d6a92bf..1d7145e 100644 --- a/src/lineGeometry.h +++ b/src/lineGeometry.h @@ -18,5 +18,5 @@ class LineGeometry : public PlotGeometry LineGeometry(); private: - std::vector faces_(std::vector points) const override; + std::vector faces_(std::vector> points) const override; }; diff --git a/src/main.qml b/src/main.qml index 9ca6291..0ef2490 100644 --- a/src/main.qml +++ b/src/main.qml @@ -13,7 +13,7 @@ import "../ProjectDissolve" ApplicationWindow { id: root - property vector3d scale: Qt.vector3d(Math.min(graphView.width / 2.5, graphView.height / 2.5), Math.min(graphView.width / 2.5, graphView.height / 2.5), 200) + property vector3d scale: Qt.vector3d(Math.min(graphView.width / 2.5, graphView.height / 2.5), Math.min(graphView.width / 2.5, graphView.height / 2.5), Math.min(graphView.width / 2.5, graphView.height / 2.5)) height: 600 title: "Mildred QML Test" @@ -53,7 +53,7 @@ ApplicationWindow { axis: Axis { id: xAxis - direction: true + direction: Axis.X thickness: 0.01 } } @@ -64,12 +64,23 @@ ApplicationWindow { axis: LogAxis { id: yAxis - direction: false + direction: Axis.Y maximum: 1.0 minimum: 0.01 thickness: 0.01 } } + AxisModel { + color: "black" + scl: root.scale + + axis: Axis { + id: zAxis + + direction: Axis.Z + thickness: 0.01 + } + } } View3D { id: graphView @@ -94,6 +105,11 @@ ApplicationWindow { yAxis.nudge(-0.01 * event.pixelDelta.y); } } + OrbitCameraController { + anchors.fill: parent + camera: cameraOrthographicLeft + origin: standAloneScene + } } Pane { id: settingsPane diff --git a/src/mainpage.md b/src/mainpage.md index eb3f142..93d7f20 100644 --- a/src/mainpage.md +++ b/src/mainpage.md @@ -34,16 +34,16 @@ at least two axes to plot. The axis is declared via the AxisModel. ```qml axis: Axis { id: xAxis - direction: true + direction: Axis.X thickness: 0.01 } ``` -The axis requires a direction (which will eventually be an Enum, but -it currently just a boolean) and a thickness. To plot the axis in the -chart, wrap it within an AxisModel. It will need a `color` (probably -black) and a `scl`, which represents the size of the canvas. The -scale ensures that the graph can be safely resized. +The axis requires a direction (which can be X, Y, or Z) and a +thickness. To plot the axis in the chart, wrap it within an AxisModel. +It will need a `color` (probably black) and a `scl`, which represents +the size of the canvas. The scale ensures that the graph can be +safely resized. ```qml AxisModel { diff --git a/src/plotGeometry.cpp b/src/plotGeometry.cpp index f198590..57ba317 100644 --- a/src/plotGeometry.cpp +++ b/src/plotGeometry.cpp @@ -28,10 +28,10 @@ void PlotGeometry::updateData() auto xs = xAxis_->convert(xs_); auto ys = yAxis_->convert(ys_); - std::vector ps(N); + std::vector> ps(N); for (int i = 0; i < N; i++) { - ps[i] = Point(xs[i], ys[i], 0.0); + ps[i] = Vec3(xs[i], ys[i], 0.0); } auto ts = clip(faces_(ps)); @@ -59,9 +59,9 @@ void PlotGeometry::updateData() update(); } -std::vector PlotGeometry::faces_([[maybe_unused]] std::vector ps) const { return {}; } +std::vector PlotGeometry::faces_([[maybe_unused]] std::vector> ps) const { return {}; } -bool outOfBounds(const Point &p) { return p.x < -1 || p.x > 1 || p.y < -1 || p.y > 1 || p.z < -1 || p.z > 1; } +bool outOfBounds(const Vec3 &p) { return p.x < -1 || p.x > 1 || p.y < -1 || p.y > 1 || p.z < -1 || p.z > 1; } std::optional clipEdge(const Edge &e) { @@ -180,7 +180,7 @@ std::vector PlotGeometry::clip(const std::vector &ts) const for (const auto &t : ts) { std::vector edges = {{t.a, t.b}, {t.b, t.c}, {t.c, t.a}}; - std::vector vertices; + std::vector> vertices; for (const auto &e : edges) { const auto clipped = clipEdge(e); diff --git a/src/plotGeometry.h b/src/plotGeometry.h index 24fb685..f8c069f 100644 --- a/src/plotGeometry.h +++ b/src/plotGeometry.h @@ -33,7 +33,7 @@ class PlotGeometry : public QQuick3DGeometry /** An array of polygon faces that would plot the given data points. The points should be in chart space (not data space).*/ - virtual std::vector faces_(std::vector points) const; + virtual std::vector faces_(std::vector> points) const; /** Take a list of polygon faces and crop them so that nothing extends outside of the charting space. Triangles completely outside the region are dropped while triangles partially diff --git a/src/scatterGeometry.cpp b/src/scatterGeometry.cpp index c0f15e0..3b32ece 100644 --- a/src/scatterGeometry.cpp +++ b/src/scatterGeometry.cpp @@ -7,30 +7,87 @@ ScatterGeometry::ScatterGeometry() : PlotGeometry() {} -std::vector ScatterGeometry::faces_(std::vector ps) const +std::vector ScatterGeometry::faces_(std::vector> ps) const { const int N = ps.size(); - std::vector ts(2 * N); + const int SIDES = 6; + + std::vector ts(SIDES * 2 * N); for (int i = 0; i < N; i++) { - Triangle t(ps[i], ps[i], ps[i]); - t.a.x -= thickness_; - t.a.y -= thickness_; - t.b.x += thickness_; - t.b.y -= thickness_; - t.c.x += thickness_; - t.c.y += thickness_; - ts[2 * i] = t; - - t = Triangle(ps[i], ps[i], ps[i]); - t.a.x -= thickness_; - t.a.y -= thickness_; - t.b.x += thickness_; - t.b.y += thickness_; - t.c.x -= thickness_; - t.c.y += thickness_; - ts[2 * i + 1] = t; + Quad t; + t = Quad(ps[i], ps[i], ps[i], ps[i]); + t = t - zhat * thickness_; + t.a -= (xhat + yhat) * thickness_; + t.b += (xhat - yhat) * thickness_; + t.c += (xhat + yhat) * thickness_; + t.d -= (xhat - yhat) * thickness_; + { + auto [first, second] = t.asTriangles(); + ts[2 * SIDES * i] = first; + ts[2 * SIDES * i + 1] = second; + } + + t = Quad(ps[i], ps[i], ps[i], ps[i]); + t = t + zhat * thickness_; + t.a -= (xhat + yhat) * thickness_; + t.b += (xhat - yhat) * thickness_; + t.c += (xhat + yhat) * thickness_; + t.d -= (xhat - yhat) * thickness_; + { + auto [first, second] = t.flip().asTriangles(); + ts[2 * SIDES * i + 2] = first; + ts[2 * SIDES * i + 3] = second; + } + + t = Quad(ps[i], ps[i], ps[i], ps[i]); + t = t + xhat * thickness_; + t.a -= (zhat + yhat) * thickness_; + t.b += (zhat - yhat) * thickness_; + t.c += (zhat + yhat) * thickness_; + t.d -= (zhat - yhat) * thickness_; + { + auto [first, second] = t.asTriangles(); + ts[2 * SIDES * i + 4] = first; + ts[2 * SIDES * i + 5] = second; + } + + t = Quad(ps[i], ps[i], ps[i], ps[i]); + t = t - xhat * thickness_; + t.a -= (zhat + yhat) * thickness_; + t.b += (zhat - yhat) * thickness_; + t.c += (zhat + yhat) * thickness_; + t.d -= (zhat - yhat) * thickness_; + { + auto [first, second] = t.flip().asTriangles(); + ts[2 * SIDES * i + 6] = first; + ts[2 * SIDES * i + 7] = second; + } + + t = Quad(ps[i], ps[i], ps[i], ps[i]); + t = t + yhat * thickness_; + t.a -= (xhat + zhat) * thickness_; + t.b += (xhat - zhat) * thickness_; + t.c += (xhat + zhat) * thickness_; + t.d -= (xhat - zhat) * thickness_; + { + auto [first, second] = t.asTriangles(); + ts[2 * SIDES * i + 8] = first; + ts[2 * SIDES * i + 9] = second; + } + + t = Quad(ps[i], ps[i], ps[i], ps[i]); + t = t - yhat * thickness_; + t.a -= (xhat + zhat) * thickness_; + t.b += (xhat - zhat) * thickness_; + t.c += (xhat + zhat) * thickness_; + t.d -= (xhat - zhat) * thickness_; + { + auto [first, second] = t.flip().asTriangles(); + ts[2 * SIDES * i + 10] = first; + ts[2 * SIDES * i + 11] = second; + } } return ts; diff --git a/src/scatterGeometry.h b/src/scatterGeometry.h index a7a0866..0f143db 100644 --- a/src/scatterGeometry.h +++ b/src/scatterGeometry.h @@ -17,5 +17,5 @@ class ScatterGeometry : public PlotGeometry ScatterGeometry(); private: - std::vector faces_(std::vector points) const override; + std::vector faces_(std::vector> points) const override; }; diff --git a/src/solid.cpp b/src/solid.cpp new file mode 100644 index 0000000..d889960 --- /dev/null +++ b/src/solid.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2024 Team Dissolve and contributors + +#include "solid.h" +#include "triangle.h" + +float *draw_tube(float *p, float thickness, Vec3 v1, Vec3 v2, Vec3 n1, Vec3 n2) +{ + + Quad q; + q = Quad(v1 - (n1 + n2) * thickness, v1 - (n1 - n2) * thickness, v2 - (n1 - n2) * thickness, v2 - (n1 + n2) * thickness); + p = q.writeByteArray(p); + + q = Quad(v1 + (n1 - n2) * thickness, v1 + (n1 + n2) * thickness, v2 + (n1 + n2) * thickness, v2 + (n1 - n2) * thickness); + p = q.flip().writeByteArray(p); + + q = Quad(v1 - (n2 + n1) * thickness, v1 - (n2 - n1) * thickness, v2 - (n2 - n1) * thickness, v2 - (n2 + n1) * thickness); + p = q.flip().writeByteArray(p); + + q = Quad(v1 + (n2 - n1) * thickness, v1 + (n2 + n1) * thickness, v2 + (n2 + n1) * thickness, v2 + (n2 - n1) * thickness); + p = q.writeByteArray(p); + + // end caps + + q = Quad(v1 + (n1 - n2) * thickness, v1 + (n1 + n2) * thickness, v1 + (n2 - n1) * thickness, v1 - (n1 + n2) * thickness); + p = q.writeByteArray(p); + + q = Quad(v2 + (n1 - n2) * thickness, v2 + (n1 + n2) * thickness, v2 + (n2 - n1) * thickness, v2 - (n1 + n2) * thickness); + p = q.flip().writeByteArray(p); + + return p; +} diff --git a/src/solid.h b/src/solid.h new file mode 100644 index 0000000..cb9f1b1 --- /dev/null +++ b/src/solid.h @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2024 Team Dissolve and contributors + +#pragma once + +#include "vector3.h" + +float *draw_tube(float *p, float thickness, Vec3 v1, Vec3 v2, Vec3 n1, Vec3 n2); diff --git a/src/triangle.cpp b/src/triangle.cpp index 5397290..966ef59 100644 --- a/src/triangle.cpp +++ b/src/triangle.cpp @@ -4,11 +4,6 @@ #include "triangle.h" #include -Point::Point(float a, float b, float c) : x(a), y(b), z(c) {} - -bool Point::operator==(const Point &other) { return x == other.x && y == other.y && z == other.z; } -bool Point::operator!=(const Point &other) { return x != other.x || y != other.y || z != other.z; } - Edge Edge::combine(const Edge &other) const { Edge result; @@ -21,7 +16,7 @@ Edge Edge::combine(const Edge &other) const return result; } -Triangle::Triangle(Point i, Point j, Point k) : a(i), b(j), c(k) {} +Triangle::Triangle(Vec3 i, Vec3 j, Vec3 k) : a(i), b(j), c(k) {} Edge Triangle::bounds() const { @@ -39,17 +34,85 @@ Edge Triangle::bounds() const float *Triangle::writeByteArray(float *p) { - *p++ = a.x; - *p++ = a.y; - *p++ = a.z; + p = a.write(p); + p = b.write(p); + p = c.write(p); + return p; +} + +Triangle Triangle::operator+(const Vec3 &offset) const +{ + Triangle result; + result.a = a + offset; + result.b = b + offset; + result.c = c + offset; + return result; +} + +Triangle Triangle::operator-(const Vec3 &offset) const +{ + Triangle result; + result.a = a - offset; + result.b = b - offset; + result.c = c - offset; + return result; +} + +Triangle Triangle::flip() const { return Triangle(a, c, b); } + +Quad::Quad(Vec3 i, Vec3 j, Vec3 k, Vec3 l) : a(i), b(j), c(k), d(l) {} + +Edge Quad::bounds() const +{ + Edge result; + result.start.x = std::min({a.x, b.x, c.x, d.x}); + result.start.y = std::min({a.y, b.y, c.y, d.y}); + result.start.z = std::min({a.z, b.z, c.z, d.z}); - *p++ = b.x; - *p++ = b.y; - *p++ = b.z; + result.end.x = std::max({a.x, b.x, c.x, d.x}); + result.end.y = std::max({a.y, b.y, c.y, d.y}); + result.end.z = std::max({a.z, b.z, c.z, d.z}); - *p++ = c.x; - *p++ = c.y; - *p++ = c.z; + return result; +} + +float *Quad::writeByteArray(float *p) +{ + p = a.write(p); + p = b.write(p); + p = c.write(p); + + p = c.write(p); + p = d.write(p); + p = a.write(p); return p; } + +Quad Quad::operator+(const Vec3 &offset) const +{ + Quad result; + result.a = a + offset; + result.b = b + offset; + result.c = c + offset; + result.d = d + offset; + return result; +} + +Quad Quad::operator-(const Vec3 &offset) const +{ + Quad result; + result.a = a - offset; + result.b = b - offset; + result.c = c - offset; + result.d = d - offset; + return result; +} + +Quad Quad::flip() const { return Quad(a, d, c, b); } + +std::pair Quad::asTriangles() const +{ + Triangle first(a, b, c), second(c, d, a); + return {first, second}; +} diff --git a/src/triangle.h b/src/triangle.h index be6994a..b8172c9 100644 --- a/src/triangle.h +++ b/src/triangle.h @@ -3,29 +3,16 @@ #pragma once -/** An individual point in 3D space. */ -class Point -{ - public: - /** A constructor that accepts the individual coordinates) */ - Point(float a = 0.0, float b = 0.0, float c = 0.0); - /** @name Coordinates - The location of the point */ - /**@{ The coordinate position */ - float x, y, z; - /**@} */ - bool operator==(const Point &other); - bool operator!=(const Point &other); -}; +#include "vector3.h" /** A line segment between two vertices of a polygon */ class Edge { public: /** The beginning point of the edge */ - Point start; + Vec3 start; /** The stopping point of the edge */ - Point end; + Vec3 end; /** Treat two edges as the corners of a bounding box and return the bouning box of their union. */ Edge combine(const Edge &other) const; @@ -35,11 +22,11 @@ class Edge class Triangle { public: - Triangle(Point i = 0.0, Point j = 0.0, Point k = 0.0); + Triangle(Vec3 i = 0.0f, Vec3 j = 0.0f, Vec3 k = 0.0f); /** @name Vertices The vertices of the triangle */ /**@{ A vertex of the triangle */ - Point a, b, c; + Vec3 a, b, c; /**@} */ /** Write the triangle into a vertex buffer @@ -49,4 +36,31 @@ class Triangle float *writeByteArray(float *p); /** find the bounding box of the triangle and return as the diagonal from min to max */ Edge bounds() const; + Triangle flip() const; + Triangle operator+(const Vec3 &offset) const; + Triangle operator-(const Vec3 &offset) const; +}; + +/** An individual quad face in the mesh. */ +class Quad +{ + public: + Quad(Vec3 i = 0.0f, Vec3 j = 0.0f, Vec3 k = 0.0f, Vec3 l = 0.0f); + /** @name Vertices + The vertices of the quad */ + /**@{ A vertex of the quad */ + Vec3 a, b, c, d; + /**@} */ + /** Write the quad into a vertex buffer + + @param p A pointer into the vertex buffer. The buffer must + have enough space to write six 3-vectors of floats. + */ + float *writeByteArray(float *p); + /** find the bounding box of the triangle and return as the diagonal from min to max */ + Edge bounds() const; + Quad flip() const; + Quad operator+(const Vec3 &offset) const; + Quad operator-(const Vec3 &offset) const; + std::pair asTriangles() const; }; diff --git a/src/vector3.h b/src/vector3.h new file mode 100644 index 0000000..6bd25d6 --- /dev/null +++ b/src/vector3.h @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2024 Team Dissolve and contributors + +#pragma once + +#include +#include + +// 3D vector +template class Vec3 +{ + public: + Vec3() : x(T()), y(T()), z(T()){}; + Vec3(T value) : x(value), y(value), z(value){}; + Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz){}; + // Components of vector + T x, y, z; + + /* + * Set / adjust / retrieve + */ + public: + // Set the vector to 0,0,0 + void zero() + { + x = 0; + y = 0; + z = 0; + } + // Set the specific element to value + void set(int index, T value) + { + if (index == 0) + x = value; + else if (index == 1) + y = value; + else if (index == 2) + z = value; + } + // Set all three values simultaneously + void set(T newX, T newY, T newZ) + { + x = newX; + y = newY; + z = newZ; + } + // Add value to single component + void add(int index, T delta) + { + if (index == 0) + x += delta; + else if (index == 1) + y += delta; + else if (index == 2) + z += delta; + } + // Add values to all three values simultaneously + void add(T dx, T dy, T dz) + { + x += dx; + y += dy; + z += dz; + } + + /* + * Operators + */ + public: + bool operator==(const Vec3 &value) const { return x == value.x && y == value.y && z == value.z; } + bool operator!=(const Vec3 &value) const { return !(*this == value); } + bool operator==(const T &value) const { return x == value && y == value && z == value; } + bool operator!=(const T &value) const { return !(*this == value); } + void operator=(const T value) + { + x = value; + y = value; + z = value; + } + // Operators + and += + void operator+=(const T value) + { + x += value; + y += value; + z += value; + } + void operator+=(const Vec3 &v) + { + x += v.x; + y += v.y; + z += v.z; + } + Vec3 operator+(const T value) const + { + Vec3 result; + result.x = x + value; + result.y = y + value; + result.z = z + value; + return result; + } + Vec3 operator+(const Vec3 &v) const + { + Vec3 result; + result.x = x + v.x; + result.y = y + v.y; + result.z = z + v.z; + return result; + } + // Operators - and -= + inline Vec3 operator-() const { return Vec3(-x, -y, -z); } + void operator-=(const T value) + { + x -= value; + y -= value; + z -= value; + } + void operator-=(const Vec3 &v) + { + x -= v.x; + y -= v.y; + z -= v.z; + } + inline Vec3 operator-(const T value) const { return Vec3(x - value, y - value, z - value); } + inline Vec3 operator-(const Vec3 &v) const { return Vec3(x - v.x, y - v.y, z - v.z); } + // Operators / and /= + void operator/=(const T value) + { + x /= value; + y /= value; + z /= value; + } + void operator/=(const Vec3 &v) + { + x /= v.x; + y /= v.y; + z /= v.z; + } + Vec3 operator/(const T value) const { return Vec3(x / value, y / value, z / value); } + Vec3 operator/(const Vec3 &v) const { return Vec3(x / v.x, y / v.y, z / v.z); } + // Operators * and *= + Vec3 operator*(const T value) const { return Vec3(x * value, y * value, z * value); } + void operator*=(const T value) + { + x *= value; + y *= value; + z *= value; + } + Vec3 operator*(const Vec3 &v) const + { + // Cross Product + return Vec3(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x); + } + + /* + * Methods + */ + public: + // Return vector of absolute elements + Vec3 abs() const { return Vec3(fabs(x), fabs(y), fabs(z)); } + // Returns the largest absolute value of the vector + T absMax() const + { + T a = (fabs(x) < fabs(y)) ? fabs(y) : fabs(x); + return (a < fabs(z)) ? fabs(z) : a; + } + // Returns the index of the maximum absolute-valued element in the vector + int absMaxElement() const + { + if ((fabs(x) >= fabs(y)) && (fabs(x) >= fabs(z))) + return 0; + if ((fabs(y) >= fabs(x)) && (fabs(y) >= fabs(z))) + return 1; + return 2; + } + // Returns the smallest absolute value of the vector + T absMin() const + { + T a = (fabs(x) > fabs(y)) ? fabs(y) : fabs(x); + return (a > fabs(z)) ? fabs(z) : a; + } + // Returns the index of the minimum absolute-valued element in the vector + int absMinElement() const + { + if ((fabs(x) <= fabs(y)) && (fabs(x) <= fabs(z))) + return 0; + if ((fabs(y) <= fabs(x)) && (fabs(y) <= fabs(z))) + return 1; + return 2; + } + // Dot product between this and supplied vector + double dp(const Vec3 &v) const { return (x * v.x + y * v.y + z * v.z); } + // Normalise and return original magnitude + double magAndNormalise() + { + double mag = sqrt(x * x + y * y + z * z); + if (mag < 1.0E-8) + zero(); + else + { + x /= mag; + y /= mag; + z /= mag; + } + return mag; + } + // Normalise and return original magnitude squared + double magSqAndNormalise() + { + double magSq = x * x + y * y + z * z; + double mag = sqrt(magSq); + if (mag < 1.0E-8) + zero(); + else + { + x /= mag; + y /= mag; + z /= mag; + } + return magSq; + } + // Calculate vector magnitude + inline double magnitude() const { return sqrt(x * x + y * y + z * z); } + // Calculate square of vector magnitude + inline double magnitudeSq() const { return x * x + y * y + z * z; } + // Returns the largest value of the vector + T max() const + { + T a = (x < y) ? y : x; + return (a < z) ? z : a; + } + // Returns the maximum valued element in the vector + int maxElement() const + { + if ((x >= y) && (x >= z)) + return 0; + if ((y >= x) && (y >= z)) + return 1; + return 2; + } + // Returns the smallest value of the vector + T min() const + { + T a = (x > y) ? y : x; + return (a > z) ? z : a; + } + // Returns the minimum valued element in the vector + int minElement() const + { + if ((x <= y) && (x <= z)) + return 0; + if ((y <= x) && (y <= z)) + return 1; + return 2; + } + // Return vector with specified element adjusted + Vec3 adjusted(int element, double delta) const + { + auto newValue = *this; + newValue[element] += delta; + return newValue; + } + // Multiply elements of this vector with factors supplied + void multiply(double facx, double facy, double facz) + { + x *= facx; + y *= facy; + z *= facz; + } + // Multiply elements of this vector by those of supplied vector + void multiply(Vec3 v) + { + x *= v.x; + y *= v.y; + z *= v.z; + } + // Normalise the vector to unity + void normalise() + { + double mag = sqrt(x * x + y * y + z * z); + if (mag < 1.0E-8) + zero(); + else + { + x /= mag; + y /= mag; + z /= mag; + } + } + // Returns an orthogonal, normalised unit vector + Vec3 orthogonal() const + { + Vec3 result; + const int maxComponent = absMaxElement(); + if (maxComponent == 0) + { + // X component is largest so return XP with (0,1,0) + result.x = -z; + result.y = 0.0; + result.z = x; + } + else if (maxComponent == 1) + { + // Y component is largest, so return XP with (0,0,1) + result.x = y; + result.y = -x; + result.z = 0.0; + } + else + { + // Z component is largest, so return XP with (1,0,0) + result.x = 0.0; + result.y = z; + result.z = -y; + } + result.normalise(); + return result; + } + // Orthogonalise (Gram-Schmidt) w.r.t. supplied vector + void orthogonalise(const Vec3 &reference) + { + double sourcemag = reference.magnitude(); + double dpovermagsq = dp(reference) / (sourcemag * sourcemag); + x = x - dpovermagsq * reference.x; + y = y - dpovermagsq * reference.y; + z = z - dpovermagsq * reference.z; + } + // Orthogonalise (two vectors) + void orthogonalise(const Vec3 &reference1, const Vec3 &reference2) + { + // This routine actually generates the orthogonal vector via the cross-product + // We also calculate the scalar resolute (dp) to ensure the new vector points in the same direction + Vec3 newvec = reference1 * reference2; + newvec.normalise(); + double dp = newvec.dp(*this); + if (dp < 0.0) + newvec *= -1.0; + *this = newvec; + } + // Return a unit vector along the specified direction + static Vec3 unit(int index) + { + if (index == 0) + return Vec3(1, 0, 0); + else if (index == 1) + return Vec3(0, 1, 0); + else if (index == 2) + return Vec3(0, 0, 1); + + throw(std::runtime_error("Vec3 - unit() generation failed - index " + std::string(index) + " is out of bounds.")); + return Vec3(); + } + + // Write a vector to a buffer + T *write(T *buffer) + { + *buffer++ = x; + *buffer++ = y; + *buffer++ = z; + return buffer; + } +}; + +const Vec3 xhat = Vec3{1, 0, 0}; +const Vec3 yhat = Vec3{0, 1, 0}; +const Vec3 zhat = Vec3{0, 0, 1};