summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--application/main.cpp16
-rw-r--r--game/network/link.cpp8
-rw-r--r--game/network/rail.cpp77
-rw-r--r--game/terrain.cpp59
-rw-r--r--game/vehicles/railVehicle.cpp2
-rw-r--r--game/vehicles/railVehicleClass.cpp8
-rw-r--r--gfx/followCameraController.cpp2
-rw-r--r--gfx/gl/camera.cpp42
-rw-r--r--gfx/gl/camera.h6
-rw-r--r--gfx/gl/shaders/landmassShader.vs2
-rw-r--r--gfx/gl/shaders/waterShader.vs6
-rw-r--r--gfx/manualCameraController.cpp2
-rw-r--r--gfx/models/obj.h12
-rw-r--r--gfx/models/obj.impl.cpp13
-rw-r--r--lib/maths.cpp18
-rw-r--r--lib/maths.h18
-rw-r--r--test/test-helpers.hpp18
-rw-r--r--test/test-maths.cpp215
-rw-r--r--test/test-network.cpp72
-rw-r--r--test/test-obj.cpp2
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());
}