diff options
-rw-r--r-- | application/main.cpp | 16 | ||||
-rw-r--r-- | game/network/link.cpp | 8 | ||||
-rw-r--r-- | game/network/rail.cpp | 77 | ||||
-rw-r--r-- | game/terrain.cpp | 59 | ||||
-rw-r--r-- | game/vehicles/railVehicle.cpp | 2 | ||||
-rw-r--r-- | game/vehicles/railVehicleClass.cpp | 8 | ||||
-rw-r--r-- | gfx/followCameraController.cpp | 2 | ||||
-rw-r--r-- | gfx/gl/camera.cpp | 42 | ||||
-rw-r--r-- | gfx/gl/camera.h | 6 | ||||
-rw-r--r-- | gfx/gl/shaders/landmassShader.vs | 2 | ||||
-rw-r--r-- | gfx/gl/shaders/waterShader.vs | 6 | ||||
-rw-r--r-- | gfx/manualCameraController.cpp | 2 | ||||
-rw-r--r-- | gfx/models/obj.h | 12 | ||||
-rw-r--r-- | gfx/models/obj.impl.cpp | 13 | ||||
-rw-r--r-- | lib/maths.cpp | 18 | ||||
-rw-r--r-- | lib/maths.h | 18 | ||||
-rw-r--r-- | test/test-helpers.hpp | 18 | ||||
-rw-r--r-- | test/test-maths.cpp | 215 | ||||
-rw-r--r-- | test/test-network.cpp | 72 | ||||
-rw-r--r-- | test/test-obj.cpp | 2 |
20 files changed, 385 insertions, 213 deletions
diff --git a/application/main.cpp b/application/main.cpp index 61206eb..88cec82 100644 --- a/application/main.cpp +++ b/application/main.cpp @@ -88,10 +88,10 @@ public: { auto rl = world.create<RailLinks>(); - const glm::vec3 j {-1100, 15, -1100}, k {-1100, 15, -1000}, l {-1000, 20, -800}, m {-900, 30, -600}, - n {-600, 32, -500}, o {-500, 30, -800}, p {-600, 25, -900}, q {-1025, 10, -1175}, - r {-925, 10, -1075}; - const glm::vec3 s {-1100, 15, -500}, t {-1100, 15, -450}, u {-1000, 15, -400}; + const glm::vec3 j {-1120, -1100, 3}, k {-1100, -1000, 15}, l {-1000, -800, 20}, m {-900, -600, 30}, + n {-600, -500, 32}, o {-500, -800, 30}, p {-600, -900, 25}, q {-1025, -1175, 10}, + r {-925, -1075, 10}; + const glm::vec3 s {-1100, -500, 15}, t {-1100, -450, 15}, u {-1000, -400, 15}; auto l3 = rl->addLinksBetween(j, k); rl->addLinksBetween(k, l); rl->addLinksBetween(l, m); @@ -115,16 +115,14 @@ public: train->create<RailVehicle>(b47); } train->orders.removeAll(); - train->orders.create<GoTo>(&train->orders, l3->ends[1], l3->length, rl->findNodeAt({-1100, 15, -450})); + train->orders.create<GoTo>(&train->orders, l3->ends[1], l3->length, rl->findNodeAt({-1100, -450, 15})); train->currentActivity = train->orders.current()->createActivity(); } Shader shader; - Camera camera({-1250.0F, 35.0F, -1250.0F}, 70.0F, (float)DISPLAY_WIDTH / (float)DISPLAY_HEIGHT, 0.1F, 10000.0F); - camera.Pitch(0.24); - camera.RotateY(0.7854); + Camera camera({-1250.0F, -1250.0F, 35.0F}, 70.0F, (float)DISPLAY_WIDTH / (float)DISPLAY_HEIGHT, 0.1F, 10000.0F); shader.setView(camera.GetViewProjection()); - shader.setUniform("lightDirection", glm::normalize(glm::vec3 {1, -1, 0})); + shader.setUniform("lightDirection", glm::normalize(glm::vec3 {1, 0, -1})); shader.setUniform("lightColor", {.6, .6, .6}); shader.setUniform("ambientColor", {0.5, 0.5, 0.5}); diff --git a/game/network/link.cpp b/game/network/link.cpp index e45126f..f3a79a6 100644 --- a/game/network/link.cpp +++ b/game/network/link.cpp @@ -12,7 +12,7 @@ bool operator<(const glm::vec3 & a, const glm::vec3 & b) { // NOLINTNEXTLINE(hicpp-use-nullptr,modernize-use-nullptr) - return std::tie(a.x, a.z, a.y) < std::tie(b.x, b.z, b.y); + return std::tie(a.x, a.y, a.z) < std::tie(b.x, b.y, b.z); } bool @@ -27,7 +27,7 @@ LinkStraight::positionAt(float dist, unsigned char start) const const auto es {std::make_pair(ends[start].node.get(), ends[1 - start].node.get())}; const auto diff {es.second->pos - es.first->pos}; const auto dir {glm::normalize(diff)}; - return Location {es.first->pos + vehiclePositionOffset() + dir * dist, {-vector_pitch(dir), vector_yaw(dir), 0}}; + return Location {es.first->pos + vehiclePositionOffset() + dir * dist, {vector_pitch(dir), vector_yaw(dir), 0}}; } Location @@ -40,7 +40,7 @@ LinkCurve::positionAt(float dist, unsigned char start) const const auto ang {as.first + ((as.second - as.first) * frac)}; const auto relPos {!sincosf(ang) * radius}; const auto relClimb {vehiclePositionOffset() - + glm::vec3 {0, -centreBase.y + es.first->pos.y + ((es.second->pos.y - es.first->pos.y) * frac), 0}}; - const auto pitch {vector_pitch({0, (es.first->pos.y - es.second->pos.y) / length, 0})}; + + glm::vec3 {0, 0, es.first->pos.z - centreBase.z + ((es.second->pos.z - es.first->pos.z) * frac)}}; + const auto pitch {vector_pitch({0, 0, (es.second->pos.z - es.first->pos.z) / length})}; return Location {relPos + relClimb + centreBase, {pitch, normalize(ang + dirOffset[start]), 0}}; } diff --git a/game/network/rail.cpp b/game/network/rail.cpp index 1f432cb..5127e34 100644 --- a/game/network/rail.cpp +++ b/game/network/rail.cpp @@ -15,10 +15,13 @@ template class NetworkOf<RailLink>; constexpr auto RAIL_CROSSSECTION_VERTICES {5U}; -constexpr glm::vec3 RAIL_HEIGHT {0, .25F, 0}; +constexpr glm::vec3 RAIL_HEIGHT {0, 0, .25F}; RailLinks::RailLinks() : NetworkOf<RailLink> {"rails.jpg"} { } -void RailLinks::tick(TickDuration) { } +void +RailLinks::tick(TickDuration) +{ +} std::shared_ptr<RailLink> RailLinks::addLinksBetween(glm::vec3 start, glm::vec3 end) @@ -46,11 +49,14 @@ RailLinks::addLinksBetween(glm::vec3 start, glm::vec3 end) throw std::runtime_error("Node exists but couldn't find it"); }; float dir = pi + findDir(node1ins.first); + if (dir == vector_yaw(end - start)) { + return addLink<RailLinkStraight>(start, end); + } const glm::vec2 flatStart {!start}, flatEnd {!end}; if (!node2ins.second) { auto midheight = [&](auto mid) { const auto sm = glm::distance(flatStart, mid), em = glm::distance(flatEnd, mid); - return start.y + ((end.y - start.y) * (sm / (sm + em))); + return start.z + ((end.z - start.z) * (sm / (sm + em))); }; float dir2 = pi + findDir(node2ins.first); if (const auto radii = find_arcs_radius(flatStart, dir, flatEnd, dir2); radii.first < radii.second) { @@ -106,9 +112,9 @@ constexpr const std::array<std::pair<glm::vec3, float>, RAIL_CROSSSECTION_VERTIC // _/ \_ // left to right {{-1.9F, 0.F, 0.F}, 0.F}, - {{-.608F, RAIL_HEIGHT.y, 0.F}, 0.34F}, - {{0, RAIL_HEIGHT.y * .7F, 0.F}, 0.5F}, - {{.608F, RAIL_HEIGHT.y, 0.F}, 0.66F}, + {{-.608F, 0.F, RAIL_HEIGHT.z}, 0.34F}, + {{0, 0.F, RAIL_HEIGHT.z * .7F}, 0.5F}, + {{.608F, 0.F, RAIL_HEIGHT.z}, 0.66F}, {{1.9F, 0.F, 0.F}, 1.F}, }}; constexpr auto sleepers {5.F}; // There are 5 repetitions of sleepers in the texture @@ -124,22 +130,24 @@ RailLinkStraight::RailLinkStraight(const NodePtr & a, const NodePtr & b) : RailL RailLinkStraight::RailLinkStraight(NodePtr a, NodePtr b, const glm::vec3 & diff) : Link({std::move(a), vector_yaw(diff)}, {std::move(b), vector_yaw(-diff)}, glm::length(diff)) { - std::vector<Vertex> vertices; - vertices.reserve(2 * railCrossSection.size()); - const auto len = round_sleepers(length / 2.F); - const auto e {flat_orientation(diff)}; - for (int ei = 0; ei < 2; ei++) { - const auto trans {glm::translate(ends[ei].node->pos) * e}; - for (const auto & rcs : railCrossSection) { - const glm::vec3 m {(trans * glm::vec4 {rcs.first, 1})}; - vertices.emplace_back(m, glm::vec2 {rcs.second, ei ? len : 0.F}, up); + if (glGenVertexArrays) { + std::vector<Vertex> vertices; + vertices.reserve(2 * railCrossSection.size()); + const auto len = round_sleepers(length / 2.F); + const auto e {flat_orientation(diff)}; + for (int ei : {1, 0}) { + const auto trans {glm::translate(ends[ei].node->pos) * e}; + for (const auto & rcs : railCrossSection) { + const glm::vec3 m {(trans * glm::vec4 {rcs.first, 1})}; + vertices.emplace_back(m, glm::vec2 {rcs.second, len * static_cast<float>(ei)}, up); + } } + mesh = defaultMesh(vertices); } - mesh = defaultMesh(vertices); } RailLinkCurve::RailLinkCurve(const NodePtr & a, const NodePtr & b, glm::vec2 c) : - RailLinkCurve(a, b, {c.x, a->pos.y, c.y}, {!c, a->pos, b->pos}) + RailLinkCurve(a, b, c ^ a->pos.z, {!c, a->pos, b->pos}) { } @@ -148,24 +156,27 @@ RailLinkCurve::RailLinkCurve(const NodePtr & a, const NodePtr & b, glm::vec3 c, (glm::length(a->pos - c)) * arc_length(arc)), LinkCurve {c, glm::length(ends[0].node->pos - c), arc} { - const auto & e0p {ends[0].node->pos}; - const auto & e1p {ends[1].node->pos}; - const auto slength = round_sleepers(length / 2.F); - const auto segs = std::round(5.F * slength / std::pow(radius, 0.7F)); - const auto step {glm::vec3 {-arc_length(arc), e0p.y - e1p.y, slength} / segs}; - const auto trans {glm::translate(centreBase)}; - - auto segCount = std::lround(segs); - std::vector<Vertex> vertices; - vertices.reserve((segCount + 1) * railCrossSection.size()); - for (glm::vec3 swing = {arc.second, e1p.y - centreBase.y, 0.F}; segCount >= 0; swing += step, --segCount) { - const auto t {trans * glm::rotate(swing.x - half_pi, up) * glm::translate(glm::vec3 {radius, swing.y, 0.F})}; - for (const auto & rcs : railCrossSection) { - const glm::vec3 m {(t * glm::vec4 {rcs.first, 1})}; - vertices.emplace_back(m, glm::vec2 {rcs.second, swing.z}, up); + if (glGenVertexArrays) { + const auto & e0p {ends[0].node->pos}; + const auto & e1p {ends[1].node->pos}; + const auto slength = round_sleepers(length / 2.F); + const auto segs = std::round(15.F * slength / std::pow(radius, 0.7F)); + const auto step {glm::vec3 {arc_length(arc), e1p.z - e0p.z, slength} / segs}; + const auto trans {glm::translate(centreBase)}; + + auto segCount = std::lround(segs); + std::vector<Vertex> vertices; + vertices.reserve((segCount + 1) * railCrossSection.size()); + for (glm::vec3 swing = {arc.first, centreBase.z - e0p.z, 0.F}; segCount >= 0; swing += step, --segCount) { + const auto t { + trans * glm::rotate(half_pi - swing.x, up) * glm::translate(glm::vec3 {radius, 0.F, swing.y})}; + for (const auto & rcs : railCrossSection) { + const glm::vec3 m {(t * glm::vec4 {rcs.first, 1})}; + vertices.emplace_back(m, glm::vec2 {rcs.second, swing.z}, up); + } } + mesh = defaultMesh(vertices); } - mesh = defaultMesh(vertices); } glm::vec3 diff --git a/game/terrain.cpp b/game/terrain.cpp index ddfa31b..ed8bca5 100644 --- a/game/terrain.cpp +++ b/game/terrain.cpp @@ -23,12 +23,12 @@ Terrain::Terrain() : grass {Texture::cachedTexture.get("grass.png")}, water {Tex vertices.resize(verticesCount, {{}, {}, {}}); // Initial coordinates - for (auto z = 0; z < size; z += 1) { + for (auto y = 0; y < size; y += 1) { for (auto x = 0; x < size; x += 1) { - auto & vertex = vertices[x + (z * size)]; - vertex.pos = {resolution * (x - offset), -1.5, resolution * (z - offset)}; - vertex.normal = {0, 1, 0}; - vertex.texCoord = {x, z}; + auto & vertex = vertices[x + (y * size)]; + vertex.pos = {resolution * (x - offset), resolution * (y - offset), -1.5}; + vertex.normal = up; + vertex.texCoord = {x, y}; } } // Add hills @@ -43,15 +43,15 @@ Terrain::Terrain() : grass {Texture::cachedTexture.get("grass.png")}, water {Tex if (const auto lim2 = hpos + hsize; lim2.x < size && lim2.y < size) { const auto height = (float)rheight(gen); const glm::ivec2 hsizesqrd {hsize.x * hsize.x, hsize.y * hsize.y}; - for (auto z = lim1.y; z < lim2.y; z += 1) { + for (auto y = lim1.y; y < lim2.y; y += 1) { for (auto x = lim1.x; x < lim2.x; x += 1) { - const auto dist {hpos - glm::ivec2 {x, z}}; + const auto dist {hpos - glm::ivec2 {x, y}}; const glm::ivec2 distsqrd {dist.x * dist.x, dist.y * dist.y}; - const auto out {rdiv(sq(x - hpos.x), sq(hsize.x)) + rdiv(sq(z - hpos.y), sq(hsize.y))}; + const auto out {rdiv(sq(x - hpos.x), sq(hsize.x)) + rdiv(sq(y - hpos.y), sq(hsize.y))}; if (out <= 1.0) { - auto & vertex = vertices[x + (z * size)]; + auto & vertex = vertices[x + (y * size)]; const auto m {1.F / (7.F * out - 8.F) + 1.F}; - vertex.pos.y += height * m; + vertex.pos.z += height * m; } } } @@ -72,12 +72,11 @@ Terrain::Terrain(const std::string & fileName) : std::vector<Vertex> vertices; vertices.reserve((map.width * map.height) + 4); - for (auto z = 0; z < map.height; z += 1) { + for (auto y = 0; y < map.height; y += 1) { for (auto x = 0; x < map.width; x += 1) { - vertices.emplace_back( - glm::vec3 {resolution * (x - (map.width / 2)), ((float)map.data[x + (z * map.width)] * 0.1F) - 1.5F, - resolution * (z - (map.height / 2))}, - glm::vec2 {(x % 2) / 2.01, (z % 2) / 2.01}, glm::vec3 {0, 1, 0}); + vertices.emplace_back(glm::vec3 {resolution * (x - (map.width / 2)), resolution * (y - (map.height / 2)), + ((float)map.data[x + (y * map.width)] * 0.1F) - 1.5F}, + glm::vec2 {(x % 2) / 2.01, (y % 2) / 2.01}, up); } } @@ -93,28 +92,28 @@ Terrain::finish(unsigned int width, unsigned int height, std::vector<Vertex> & v std::vector<unsigned int> indices; indices.reserve(indicesCount + 6); // Indices - for (auto z = 0U; z < height - 1; z += 1) { + for (auto y = 0U; y < height - 1; y += 1) { for (auto x = 0U; x < width - 1; x += 1) { - indices.push_back(x + (z * width)); - indices.push_back((x + 1) + ((z + 1) * width)); - indices.push_back((x + 1) + (z * width)); - indices.push_back(x + (z * width)); - indices.push_back(x + ((z + 1) * width)); - indices.push_back((x + 1) + ((z + 1) * width)); + indices.push_back(x + (y * width)); + indices.push_back((x + 1) + (y * width)); + indices.push_back((x + 1) + ((y + 1) * width)); + indices.push_back(x + (y * width)); + indices.push_back((x + 1) + ((y + 1) * width)); + indices.push_back(x + ((y + 1) * width)); } } // Normals - auto v = [&vertices](unsigned int width, unsigned int x, unsigned int z) -> Vertex & { - return vertices[x + (z * width)]; + auto v = [&vertices](unsigned int width, unsigned int x, unsigned int y) -> Vertex & { + return vertices[x + (y * width)]; }; - for (auto z = 1U; z < height - 1; z += 1) { + for (auto y = 1U; y < height - 1; y += 1) { for (auto x = 1U; x < width - 1; x += 1) { - const auto a = v(width, x - 1, z).pos; - const auto b = v(width, x, z - 1).pos; - const auto c = v(width, x + 1, z).pos; - const auto d = v(width, x, z + 1).pos; - v(width, x, z).normal = -glm::normalize(glm::cross(c - a, d - b)); + const auto a = v(width, x - 1, y).pos; + const auto b = v(width, x, y - 1).pos; + const auto c = v(width, x + 1, y).pos; + const auto d = v(width, x, y + 1).pos; + v(width, x, y).normal = -glm::normalize(glm::cross(b - d, a - c)); } } meshes.create<Mesh>(vertices, indices); diff --git a/game/vehicles/railVehicle.cpp b/game/vehicles/railVehicle.cpp index a2373e7..aea7f8c 100644 --- a/game/vehicles/railVehicle.cpp +++ b/game/vehicles/railVehicle.cpp @@ -21,6 +21,6 @@ RailVehicle::move(const Train * t, float & trailBy) const auto & b2Pos = bogies[1] = t->getBogiePosition(t->linkDist, trailBy += rvClass->wheelBase); const auto diff = glm::normalize(b2Pos.pos - b1Pos.pos); location.pos = (b1Pos.pos + b2Pos.pos) / 2.F; - location.rot = {-vector_pitch(diff), vector_yaw(diff), 0}; + location.rot = {vector_pitch(diff), vector_yaw(diff), 0}; trailBy += 0.6F + overhang; } diff --git a/game/vehicles/railVehicleClass.cpp b/game/vehicles/railVehicleClass.cpp index 83eb9a0..52b7dbe 100644 --- a/game/vehicles/railVehicleClass.cpp +++ b/game/vehicles/railVehicleClass.cpp @@ -60,12 +60,12 @@ RailVehicleClass::bogieOffset(ObjParser & o) std::set<std::pair<float, int>> vertexIds; for (const auto & face : object.second) { for (const auto & faceElement : face) { - vertexIds.emplace(o.vertices[faceElement.x - 1].z, faceElement.x - 1); + vertexIds.emplace(o.vertices[faceElement.x - 1].y, faceElement.x - 1); } } const auto offset = (vertexIds.begin()->first + vertexIds.rbegin()->first) / 2; for (const auto & v : vertexIds) { - o.vertices[v.second].z -= offset; + o.vertices[v.second].y -= offset; } wheelBase += std::abs(offset); } @@ -76,7 +76,7 @@ float RailVehicleClass::objectLength(ObjParser & o) { const auto mme = std::minmax_element(o.vertices.begin(), o.vertices.end(), [](const auto & v1, const auto & v2) { - return v1.z < v2.z; + return v1.y < v2.y; }); - return mme.second->z - mme.first->z; + return mme.second->y - mme.first->y; } diff --git a/gfx/followCameraController.cpp b/gfx/followCameraController.cpp index 9e32e21..42d1666 100644 --- a/gfx/followCameraController.cpp +++ b/gfx/followCameraController.cpp @@ -32,7 +32,7 @@ FollowCameraController::updateCamera(Camera * camera) const case Mode::ISO: camera->pos = pos + ((up + north + east) * 40.F); - camera->forward = -glm::normalize(up + north + east); + camera->forward = glm::normalize(down + south + west); camera->up = glm::normalize(up - north - east); break; } diff --git a/gfx/gl/camera.cpp b/gfx/gl/camera.cpp index c98ff70..c957b2c 100644 --- a/gfx/gl/camera.cpp +++ b/gfx/gl/camera.cpp @@ -1,9 +1,9 @@ #include "camera.h" #include <glm/gtx/transform.hpp> +#include <maths.h> Camera::Camera(glm::vec3 pos, float fov, float aspect, float zNear, float zFar) : - pos {pos}, forward {0.0F, 0.0F, 1.0F}, up {0.0F, 1.0F, 0.0F}, projection { - glm::perspective(fov, aspect, zNear, zFar)} + pos {pos}, forward {::north}, up {::up}, projection {glm::perspective(fov, aspect, zNear, zFar)} { } @@ -12,41 +12,3 @@ Camera::GetViewProjection() const { return projection * glm::lookAt(pos, pos + forward, up); } - -void -Camera::MoveForward(float amt) -{ - pos += forward * amt; -} - -void -Camera::SlideForward(float amt) -{ - pos += forward * amt * glm::vec3 {1, 0, 1}; -} - -void -Camera::MoveRight(float amt) -{ - pos += glm::cross(up, forward) * amt; -} - -void -Camera::Pitch(float angle) -{ - const auto right = glm::normalize(glm::cross(up, forward)); - - forward = glm::vec3(glm::normalize(glm::rotate(angle, right) * glm::vec4(forward, 0.0))); - up = glm::normalize(glm::cross(forward, right)); -} - -void -Camera::RotateY(float angle) -{ - static constexpr glm::vec3 UP {0.0F, 1.0F, 0.0F}; - - const auto rotation = glm::rotate(angle, UP); - - forward = glm::vec3(glm::normalize(rotation * glm::vec4(forward, 0.0))); - up = glm::vec3(glm::normalize(rotation * glm::vec4(up, 0.0))); -} diff --git a/gfx/gl/camera.h b/gfx/gl/camera.h index 3d8ece4..72f699d 100644 --- a/gfx/gl/camera.h +++ b/gfx/gl/camera.h @@ -9,12 +9,6 @@ public: [[nodiscard]] glm::mat4 GetViewProjection() const;
- void MoveForward(float amt);
- void SlideForward(float amt);
- void MoveRight(float amt);
- void Pitch(float angle);
- void RotateY(float angle);
-
glm::vec3 pos;
glm::vec3 forward;
glm::vec3 up;
diff --git a/gfx/gl/shaders/landmassShader.vs b/gfx/gl/shaders/landmassShader.vs index 44eed39..94d819d 100644 --- a/gfx/gl/shaders/landmassShader.vs +++ b/gfx/gl/shaders/landmassShader.vs @@ -15,5 +15,5 @@ void main() gl_Position = viewProjection * vec4(position, 1.0); texCoord0 = texCoord; normal0 = normal; - height = position.y; + height = position.z; } diff --git a/gfx/gl/shaders/waterShader.vs b/gfx/gl/shaders/waterShader.vs index 7a641b0..2bcedb4 100644 --- a/gfx/gl/shaders/waterShader.vs +++ b/gfx/gl/shaders/waterShader.vs @@ -14,9 +14,9 @@ void main() { vec3 wpos = vec3( position.x + cos(waves.x), - cos(waves.x + position.x + (position.z / 8)) * .3, - position.z + cos(waves.x * waves.z / 2)); + position.y + cos(waves.x * waves.y / 2), + cos(waves.x + position.x + (position.y / 8)) * .3); gl_Position = viewProjection * vec4(wpos, 1.0); texCoord0 = texCoord; - depth = position.y; + depth = position.z; } diff --git a/gfx/manualCameraController.cpp b/gfx/manualCameraController.cpp index 268920a..022fb72 100644 --- a/gfx/manualCameraController.cpp +++ b/gfx/manualCameraController.cpp @@ -47,7 +47,7 @@ ManualCameraController::handleInput(SDL_Event & e) pitch = std::clamp(pitch - 0.01F * (float)e.motion.yrel, 0.1F, half_pi); } else { - focus += rotate_flat(-direction) * glm::vec2 {e.motion.xrel, e.motion.yrel}; + focus += rotate_flat(-direction) * glm::vec2 {-e.motion.xrel, e.motion.yrel}; } } return true; diff --git a/gfx/models/obj.h b/gfx/models/obj.h index a2d874f..9a2a30e 100644 --- a/gfx/models/obj.h +++ b/gfx/models/obj.h @@ -17,16 +17,8 @@ class Vertex; class ObjParser : yyFlexLexer { public: - explicit ObjParser(const std::filesystem::path & fileName) : ObjParser {std::make_unique<std::ifstream>(fileName)} - { - } - - explicit ObjParser(std::unique_ptr<std::istream> in) : yyFlexLexer(in.get()) - { - assert(in); - ObjParser::yylex(); - assert(in->good()); - } + explicit ObjParser(const std::filesystem::path & fileName); + explicit ObjParser(std::unique_ptr<std::istream> in); int yylex() override; diff --git a/gfx/models/obj.impl.cpp b/gfx/models/obj.impl.cpp index 330e851..0701d13 100644 --- a/gfx/models/obj.impl.cpp +++ b/gfx/models/obj.impl.cpp @@ -9,6 +9,19 @@ #include <utility> #include <vector> +ObjParser::ObjParser(const std::filesystem::path & fileName) : ObjParser {std::make_unique<std::ifstream>(fileName)} { } + +ObjParser::ObjParser(std::unique_ptr<std::istream> in) : yyFlexLexer(in.get()) +{ + assert(in); + ObjParser::yylex(); + assert(in->good()); + std::for_each(vertices.begin(), vertices.end(), [](auto & v) { + std::swap(v.y, v.z); + v.x = -v.x; + }); +} + ObjParser::NamedMeshes ObjParser::createMeshes() const { diff --git a/lib/maths.cpp b/lib/maths.cpp index 0298363..e894d02 100644 --- a/lib/maths.cpp +++ b/lib/maths.cpp @@ -9,7 +9,7 @@ glm::mat4 flat_orientation(const glm::vec3 & diff) { static const auto oneeighty {glm::rotate(pi, up)}; - const auto flatdiff {glm::normalize(glm::vec3 {diff.x, 0, diff.z})}; + const auto flatdiff {glm::normalize(!!diff)}; auto e {glm::orientation(flatdiff, north)}; // Handle if diff is exactly opposite to north return (std::isnan(e[0][0])) ? oneeighty : e; @@ -42,16 +42,16 @@ rotate_flat(float a) return rotation<glm::mat2>(a, {0, 0}, {0, 1}, {1, 1}, {1, 0}); } -// Create a roll transformation matrix +// Create a yaw transformation matrix glm::mat4 -rotate_roll(float a) +rotate_yaw(float a) { - return rotation<glm::mat4>(a, {0, 0}, {0, 1}, {1, 1}, {1, 0}); + return rotation<glm::mat4>(a, {0, 0}, {1, 0}, {1, 1}, {0, 1}); } -// Create a yaw transformation matrix +// Create a roll transformation matrix glm::mat4 -rotate_yaw(float a) +rotate_roll(float a) { return rotation<glm::mat4>(a, {0, 0}, {2, 0}, {2, 2}, {0, 2}); } @@ -63,7 +63,7 @@ rotate_pitch(float a) return rotation<glm::mat4>(a, {1, 1}, {1, 2}, {2, 2}, {2, 1}); } -// Create a bomcined yaw, pitch, roll transformation matrix +// Create a combined yaw, pitch, roll transformation matrix glm::mat4 rotate_ypr(glm::vec3 a) { @@ -73,13 +73,13 @@ rotate_ypr(glm::vec3 a) float vector_yaw(const glm::vec3 & diff) { - return std::atan2(diff.x, diff.z); + return std::atan2(diff.x, diff.y); } float vector_pitch(const glm::vec3 & diff) { - return std::atan(diff.y); + return std::atan(diff.z); } float diff --git a/lib/maths.h b/lib/maths.h index c02123a..285c69a 100644 --- a/lib/maths.h +++ b/lib/maths.h @@ -18,11 +18,13 @@ struct Arc : public std::pair<float, float> { } }; -constexpr const glm::vec3 up {0, 1, 0}; -constexpr const glm::vec3 north {0, 0, 1}; -constexpr const glm::vec3 south {0, 0, -1}; -constexpr const glm::vec3 east {-1, 0, 0}; -constexpr const glm::vec3 west {1, 0, 0}; +constexpr const glm::vec3 origin {0, 0, 0}; +constexpr const glm::vec3 up {0, 0, 1}; +constexpr const glm::vec3 down {0, 0, -1}; +constexpr const glm::vec3 north {0, 1, 0}; +constexpr const glm::vec3 south {0, -1, 0}; +constexpr const glm::vec3 east {1, 0, 0}; +constexpr const glm::vec3 west {-1, 0, 0}; constexpr auto half_pi {glm::half_pi<float>()}; constexpr auto quarter_pi {half_pi / 2}; constexpr auto pi {glm::pi<float>()}; @@ -73,13 +75,13 @@ rdiv(Ta a, Tb b) constexpr inline glm::vec2 operator!(const glm::vec3 & v) { - return {v.x, v.z}; + return {v.x, v.y}; } constexpr inline glm::vec3 -operator^(const glm::vec2 & v, float y) +operator^(const glm::vec2 & v, float z) { - return {v.x, y, v.y}; + return {v.x, v.y, z}; } constexpr inline glm::vec3 diff --git a/test/test-helpers.hpp b/test/test-helpers.hpp new file mode 100644 index 0000000..294b6ff --- /dev/null +++ b/test/test-helpers.hpp @@ -0,0 +1,18 @@ +#ifndef TEST_HELPERS_H +#define TEST_HELPERS_H + +#include <boost/test/tools/context.hpp> +#include <boost/test/tools/interface.hpp> + +#define BOOST_CHECK_CLOSE_VEC(a, b) \ + BOOST_TEST_CONTEXT("BOOST_CHECK_CLOSE_VEC(" << a << ", " << b << ")") { \ + BOOST_CHECK_LT(glm::length(a - b), 0.1F); \ + } + +#define BOOST_CHECK_BETWEEN(a, b, c) \ + BOOST_TEST_CONTEXT("BOOST_CHECK_BETWEEN(" << a << ", " << b << ", " << c << ")") { \ + BOOST_CHECK_LE(b, a); \ + BOOST_CHECK_GE(c, a); \ + } + +#endif diff --git a/test/test-maths.cpp b/test/test-maths.cpp index 420b69e..02d3708 100644 --- a/test/test-maths.cpp +++ b/test/test-maths.cpp @@ -1,5 +1,6 @@ #define BOOST_TEST_MODULE test_maths +#include "test-helpers.hpp" #include <boost/test/data/test_case.hpp> #include <boost/test/unit_test.hpp> #include <glm/gtx/transform.hpp> @@ -7,22 +8,73 @@ #include <string_view> #include <type_traits> +#include <game/network/link.h> #include <glm/glm.hpp> #include <maths.h> #include <tuple> -using vecter_to_angle = std::tuple<glm::vec3, float>; +using vecter_and_angle = std::tuple<glm::vec3, float>; +using angle_pair = std::tuple<float, float>; +// +// STANDARD DEFINITIONS +// +// (x, y) in the 2D plane of geographic coordinates. +// (x, y, z) in the 3D plane, where (x, y) are geographic and z is veritcal. +// +// (x, y, 0) is sea level +// (x, y, +ve) is "up" +static_assert(up.z > 0); +static_assert(down == -up); +// (x, +ve, z) is "north" +static_assert(north.y > 0); +static_assert(south == -north); +// (x, -ve, z) is "south" +static_assert(south.y < 0); +// (+ve, y, z) is "east" +static_assert(east.x > 0); +static_assert(west == -east); +// (-ve, y, z) is "west" +static_assert(west.x < 0); +// +// Therefore, the geographic world exists west -ve to east +ve and from south -ve to north +ve. Forward shall be +// considered +ve motion; the "front" of a vehicle shall have a +ve value in y axis. +// +// An unrotated vehicle shall be facing north, thus forward motion of the vehicle shall increase it's position in the y +// axis. +// +// Positive rotation on the XY plane (y member, yaw, around the down axis, as would be expected for vehicle or building +// on flat land) shall be clockwise, in radians. Cycles shall be considered equal; 0 == 2pi, pi == -pi, 1/4pi == -3/4pi. + BOOST_DATA_TEST_CASE(test_vector_yaw, - boost::unit_test::data::make<vecter_to_angle>( - {{north, 0}, {south, pi}, {west, half_pi}, {east, -half_pi}, {north + east, -quarter_pi}, - {south + east, quarter_pi * -3}, {north + west, quarter_pi}, {south + west, quarter_pi * 3}}), + boost::unit_test::data::make<vecter_and_angle>( + {{up, 0}, {north, 0}, {south, pi}, {west, -half_pi}, {east, half_pi}, {north + east, quarter_pi}, + {south + east, quarter_pi * 3}, {north + west, -quarter_pi}, {south + west, -quarter_pi * 3}}), v, a) { BOOST_CHECK_CLOSE(vector_yaw(v), a, 1.F); } +BOOST_DATA_TEST_CASE(test_angle_normalize, + boost::unit_test::data::make<angle_pair>({ + {0, 0}, + {two_pi, 0}, + {-two_pi, 0}, + {half_pi, half_pi}, + {-half_pi, -half_pi}, + {half_pi * 3, -half_pi}, + {-half_pi * 3, half_pi}, + }), + in, exp) +{ + BOOST_CHECK_CLOSE(normalize(in), exp, 1); +} + +// Positive rotation on the YZ plane (x member, pitch, around the east axis relative to its yaw, as would be expected +// for a vehicle travelling forward uphill), in radians. Cycles can be considered non-sense as even in the worst/best +// cases pitch beyond +/- 1/2pi would be crashing. + BOOST_DATA_TEST_CASE(test_vector_pitch, - boost::unit_test::data::make<vecter_to_angle>({ + boost::unit_test::data::make<vecter_and_angle>({ {north, 0}, {east, 0}, {south, 0}, @@ -43,31 +95,48 @@ BOOST_DATA_TEST_CASE(test_vector_pitch, BOOST_CHECK_CLOSE(vector_pitch(v), a, 1.F); } -using normalize_angle = std::tuple<float, float>; -BOOST_DATA_TEST_CASE(test_angle_normalize, - boost::unit_test::data::make<normalize_angle>({ - {0, 0}, - {two_pi, 0}, - {-two_pi, 0}, - {half_pi, half_pi}, - {-half_pi, -half_pi}, - {half_pi * 3, -half_pi}, - {-half_pi * 3, half_pi}, - }), - in, exp) +// Positive rotation on the ZX plane (z member, roll, around Y axis relative to its yaw and pitch, as would be expected +// for an aircraft banking/turning right), in radians. Cycles can be considered non-sense as even in the worst/best +// cases pitch beyond +/- 1/2pi would be crashing. + +// The ILT functions rotate_yaw, rotate_pitch and rotate_roll provide a simple equivelent to glm::rotate around the +// stated axis. +const auto angs = boost::unit_test::data::make({pi, half_pi, two_pi, quarter_pi, -pi, -half_pi, -quarter_pi, 0.F}) + * boost::unit_test::data::make(0); +const auto random_angs = boost::unit_test::data::random(-two_pi, two_pi) ^ boost::unit_test::data::xrange(1000); +const auto rots = boost::unit_test::data::make<std::tuple<glm::vec3, glm::mat4 (*)(float), std::string_view>>({ + {down, rotate_yaw, "yaw"}, + {east, rotate_pitch, "pitch"}, + {north, rotate_roll, "roll"}, +}); +BOOST_DATA_TEST_CASE(test_rotations, (angs + random_angs) * rots, angle, ai, axis, ilt_func, name) { - BOOST_CHECK_CLOSE(normalize(in), exp, 1); + (void)ai; + BOOST_TEST_CONTEXT(name) { + const auto g {glm::rotate(angle, axis)}, ilt {ilt_func(angle)}; + for (glm::length_t c = 0; c < 4; c++) { + BOOST_TEST_CONTEXT(c) { + for (glm::length_t r = 0; r < 4; r++) { + BOOST_TEST_CONTEXT(r) { + BOOST_CHECK_CLOSE(g[c][r], ilt[c][r], 0.0001); + } + } + } + } + } } +// An arc shall be defined as a centre point, start point and end point. The arc shall progress positively from start to +// end in a clockwise manner. Arc start shall be the yaw from centre to start, arc end shall be greater than arc start. using pos3_to_arc = std::tuple<glm::vec3, glm::vec3, glm::vec3, Arc>; BOOST_DATA_TEST_CASE(test_create_arc, boost::unit_test::data::make<pos3_to_arc>({ - {{0, 0, 0}, north, east, {0, half_pi * 3}}, - {{0, 0, 0}, west, east, {half_pi, half_pi * 3}}, - {{0, 0, 0}, south, east, {pi, half_pi * 3}}, - {{0, 0, 0}, east, north, {-half_pi, 0}}, + {{0, 0, 0}, north, east, {0, half_pi}}, + {{0, 0, 0}, west, east, {-half_pi, half_pi}}, + {{0, 0, 0}, south, east, {pi, half_pi * 5}}, + {{0, 0, 0}, east, north, {half_pi, two_pi}}, {{0, 0, 0}, south, north, {pi, two_pi}}, - {{0, 0, 0}, east, south, {-half_pi, pi}}, + {{0, 0, 0}, east, south, {half_pi, pi}}, }), c, s, e, a) { @@ -99,33 +168,85 @@ BOOST_AUTO_TEST_CASE(test_find_arcs_radius) BOOST_CHECK_CLOSE(find_arcs_radius({10.32, 26.71}, {0.4, .92}, {2.92, 22.41}, {-0.89, -0.45}), 2.29, 1); } -static void -compare_rotations(float a, const glm::vec3 & axis, glm::mat4 (*rotate_func)(float), std::string_view n) +struct TestLinkStraight : public LinkStraight { + TestLinkStraight(glm::vec3 v) : + Link {{std::make_shared<Node>(origin), vector_yaw(v)}, {std::make_shared<Node>(v), vector_yaw(-v)}, + glm::length(v)} + { + } +}; + +using StraightsData = std::tuple<glm::vec3, float /*angFor*/, float /* angBack*/>; +BOOST_DATA_TEST_CASE(straight1, + boost::unit_test::data::make<StraightsData>({ + {north, 0, pi}, + {south, pi, 0}, + {east, half_pi, -half_pi}, + {west, -half_pi, half_pi}, + }), + v, angFor, angBack) { - BOOST_TEST_CONTEXT(n) { - const auto g {glm::rotate(a, axis)}, ilt {rotate_func(a)}; - for (int c = 0; c < 4; c++) { - BOOST_TEST_CONTEXT(c) { - for (int r = 0; r < 4; r++) { - BOOST_TEST_CONTEXT(r) { - BOOST_CHECK_CLOSE(g[c][r], ilt[c][r], 0.0001); - } - } - } - } + TestLinkStraight l(v); + { + const auto p = l.positionAt(0, 0); + BOOST_CHECK_EQUAL(p.pos, origin); + BOOST_CHECK_EQUAL(p.rot, glm::vec3(0, angFor, 0)); + } + { + const auto p = l.positionAt(0, 1); + BOOST_CHECK_EQUAL(p.pos, v); + BOOST_CHECK_EQUAL(p.rot, glm::vec3(0, angBack, 0)); } } -const auto angs = boost::unit_test::data::make({pi, half_pi, two_pi, quarter_pi, -pi, -half_pi, -quarter_pi, 0.F}) - * boost::unit_test::data::make(0); -const auto random_angs = boost::unit_test::data::random(-two_pi, two_pi) ^ boost::unit_test::data::xrange(1000); -const auto rots = boost::unit_test::data::make<std::tuple<glm::vec3, glm::mat4 (*)(float), std::string_view>>({ - {up, rotate_yaw, "yaw"}, - {west, rotate_pitch, "pitch"}, - {north, rotate_roll, "roll"}, -}); -BOOST_DATA_TEST_CASE(test_rotations, (angs + random_angs) * rots, a, ai, ax, func, n) +struct TestLinkCurve : public LinkCurve { + TestLinkCurve(glm::vec3 e0, glm::vec3 e1, glm::vec3 ctr) : + Link {{std::make_shared<Node>(e0), normalize(vector_yaw(ctr - e0) - half_pi)}, + {std::make_shared<Node>(e1), normalize(vector_yaw(ctr - e1) - half_pi)}, glm::length(e1 - e0)}, + LinkCurve(ctr, glm::length(e0 - ctr), {ctr, e0, e1}) + { + } +}; + +using CurvesData = std::tuple<glm::vec3 /*e1*/, glm::vec3 /*ctr*/, float /*angFor*/, float /* angBack*/>; +BOOST_DATA_TEST_CASE(curve1, + boost::unit_test::data::make<CurvesData>({ + {north + east, east, 0, -half_pi}, + {east * 2.F, east, 0, 0}, + {south + east, east, 0, half_pi}, + {south + west, west, pi, half_pi}, + }), + e1, ctr, angFor, angBack) { - (void)ai; - compare_rotations(a, ax, func, n); + { // One-way... + TestLinkCurve l(origin, e1, ctr); + BOOST_CHECK_EQUAL(l.radius, 1.F); + { + const auto p = l.positionAt(0, 0); + BOOST_CHECK_CLOSE_VEC(p.pos, origin); + BOOST_CHECK_CLOSE_VEC(p.rot, glm::vec3(0, angFor, 0)); + } + { + const auto p = l.positionAt(0, 1); + BOOST_CHECK_CLOSE_VEC(p.pos, e1); + BOOST_CHECK_CLOSE_VEC(p.rot, glm::vec3(0, angBack, 0)); + } + } + + { // The other way... + TestLinkCurve l(e1, origin, ctr); + BOOST_CHECK_EQUAL(l.radius, 1.F); + { + const auto p = l.positionAt(0, 0); + const auto angForReversed = normalize(vector_yaw(origin - e1) * 2 - angFor); + BOOST_CHECK_CLOSE_VEC(p.pos, e1); + BOOST_CHECK_CLOSE_VEC(p.rot, glm::vec3(0, angForReversed, 0)); + } + { + const auto p = l.positionAt(0, 1); + const auto angBackReversed = normalize(vector_yaw(e1 - origin) * 2 - angBack); + BOOST_CHECK_CLOSE_VEC(p.pos, origin); + BOOST_CHECK_CLOSE_VEC(p.rot, glm::vec3(0, angBackReversed, 0)); + } + } } diff --git a/test/test-network.cpp b/test/test-network.cpp index 7727434..70f2428 100644 --- a/test/test-network.cpp +++ b/test/test-network.cpp @@ -1,5 +1,6 @@ #define BOOST_TEST_MODULE network +#include "test-helpers.hpp" #include <boost/test/data/test_case.hpp> #include <boost/test/unit_test.hpp> @@ -8,6 +9,7 @@ #include <game/network/link.h> #include <game/network/network.h> #include <game/network/network.impl.h> // IWYU pragma: keep +#include <game/network/rail.h> #include <glm/glm.hpp> #include <maths.h> #include <memory> @@ -21,7 +23,7 @@ struct TestLink : public LinkStraight { }; constexpr glm::vec3 p000 {0, 0, 0}, p100 {1, 0, 0}, p200 {2, 0, 0}, p300 {3, 0, 0}; -constexpr glm::vec3 p101 {1, 0, 1}; +constexpr glm::vec3 p110 {1, 1, 0}; struct TestNetwork : public NetworkOf<TestLink> { TestNetwork() : NetworkOf<TestLink> {RESDIR "rails.jpg"} @@ -31,13 +33,13 @@ struct TestNetwork : public NetworkOf<TestLink> { // \ | / // \ 5 / // 3 | 4 - // \-> p101 <-/ + // \-> p110 <-/ addLink<TestLink>(p000, p100, 1.F); addLink<TestLink>(p100, p200, 1.F); addLink<TestLink>(p200, p300, 1.F); - addLink<TestLink>(p000, p101, 2.F); - addLink<TestLink>(p200, p101, 2.F); - addLink<TestLink>(p100, p101, 1.F); + addLink<TestLink>(p000, p110, 2.F); + addLink<TestLink>(p200, p110, 2.F); + addLink<TestLink>(p100, p110, 1.F); } }; @@ -173,3 +175,63 @@ BOOST_AUTO_TEST_CASE(routeTo_downStream_3to300) } BOOST_AUTO_TEST_SUITE_END() + +BOOST_FIXTURE_TEST_CASE(test_rail_network, RailLinks) +{ + // 0 1 2 + // --p000 <-> p100 <-> p200 <-> p300 \ x + // / ?----- \ x + // / / \ | + // | / 4 / + // 3 p110 \ / + // \ | \ / + // \ / ------/ + // -------- + auto l0 = addLinksBetween(p000, p100); + BOOST_CHECK(dynamic_cast<RailLinkStraight *>(l0.get())); + BOOST_CHECK_EQUAL(l0->length, 1.F); + BOOST_CHECK_CLOSE(l0->ends[0].dir, half_pi, 0.1F); + BOOST_CHECK_CLOSE(l0->ends[1].dir, -half_pi, 0.1F); + BOOST_CHECK(l0->ends[0].nexts.empty()); + BOOST_CHECK(l0->ends[1].nexts.empty()); + + auto l1 = addLinksBetween(p200, p100); + BOOST_CHECK(dynamic_cast<RailLinkStraight *>(l1.get())); + BOOST_CHECK_EQUAL(l1->length, 1.F); + BOOST_CHECK_CLOSE(l1->ends[0].dir, half_pi, 0.1F); + BOOST_CHECK_CLOSE(l1->ends[1].dir, -half_pi, 0.1F); + BOOST_CHECK(l0->ends[0].nexts.empty()); + BOOST_CHECK_EQUAL(l0->ends[1].nexts.at(0).first.lock(), l1); + BOOST_CHECK_EQUAL(l0->ends[1].nexts.at(0).second, 0); + BOOST_CHECK_EQUAL(l1->ends[0].nexts.at(0).first.lock(), l0); + BOOST_CHECK_EQUAL(l1->ends[0].nexts.at(0).second, 1); + BOOST_CHECK(l1->ends[1].nexts.empty()); + + auto l2 = addLinksBetween(p200, p300); + BOOST_CHECK(dynamic_cast<RailLinkStraight *>(l2.get())); + BOOST_CHECK_EQUAL(l2->length, 1.F); + BOOST_CHECK_CLOSE(l2->ends[0].dir, half_pi, 0.1F); + BOOST_CHECK_CLOSE(l2->ends[1].dir, -half_pi, 0.1F); + BOOST_CHECK(l0->ends[0].nexts.empty()); + BOOST_CHECK_EQUAL(l1->ends[1].nexts.at(0).first.lock(), l2); + BOOST_CHECK_EQUAL(l1->ends[1].nexts.at(0).second, 0); + BOOST_CHECK_EQUAL(l2->ends[0].nexts.at(0).first.lock(), l1); + BOOST_CHECK_EQUAL(l2->ends[0].nexts.at(0).second, 1); + BOOST_CHECK(l2->ends[1].nexts.empty()); + + auto l3 = addLinksBetween(p000, p110); + BOOST_CHECK(dynamic_cast<RailLinkCurve *>(l3.get())); + BOOST_CHECK_CLOSE(l3->length, pi + half_pi, 0.1F); + BOOST_CHECK_CLOSE(l3->ends[0].dir, -half_pi, 0.1F); + BOOST_CHECK_CLOSE(l3->ends[1].dir, 0, 0.1F); + BOOST_CHECK_EQUAL(l0->ends[0].nexts.at(0).first.lock(), l3); + BOOST_CHECK_EQUAL(l0->ends[0].nexts.at(0).second, 0); + BOOST_CHECK_EQUAL(l3->ends[0].nexts.at(0).first.lock(), l0); + BOOST_CHECK_EQUAL(l3->ends[0].nexts.at(0).second, 0); + BOOST_CHECK(l3->ends[1].nexts.empty()); + + auto l4 = addLinksBetween(p110, p300); + BOOST_CHECK_CLOSE(l4->length, 3.04F, 0.1F); + BOOST_CHECK_BETWEEN(l4->ends[0].dir, .23F, .24F); + BOOST_CHECK_CLOSE(l4->ends[1].dir, half_pi, 0.1F); +} diff --git a/test/test-obj.cpp b/test/test-obj.cpp index 813a69c..c7bd6ce 100644 --- a/test/test-obj.cpp +++ b/test/test-obj.cpp @@ -34,6 +34,6 @@ BOOST_AUTO_TEST_CASE(create_meshes) const auto & o = ms.at("Body"); BOOST_REQUIRE_EQUAL(88, o.first.size()); const auto & v = o.first.front(); - BOOST_REQUIRE_CLOSE(1.345, v.pos.x, 1); + BOOST_REQUIRE_CLOSE(-1.345, v.pos.x, 1); BOOST_REQUIRE_EQUAL(138, o.second.size()); } |