5
\$\begingroup\$

Based on my previous question, I have implemented all the recommendations received. In addition, i have implemented new features and completed road geometries.

Here is a summary of the improvements:

  • Rewritten the Colors class again instead of old class which is used hierarchy design.
  • Added curve for the road
  • Added hills for the road
  • Added fake fog effect to the scene (no Opengl used)

I would like to know how can I improve it further.

#define _USE_MATH_DEFINES
#include <SFML/Graphics.hpp>
#include <vector>
#include <array>
#include <memory>
#include <cmath>
#include <random>
#include <stdexcept>
#include <iostream>
#ifndef M_PI
#define M_PI 3.141592653589793238462643383
#endif 
#ifndef M_E
#define M_E 2.71828182845904523536
#endif 
namespace
{
 float increase(float start, float increment, float max)
 {
 auto result = start + increment;
 while (result >= max)
 result -= max;
 while (result < 0)
 result += max;
 return result;
 }
 float limit(float value, float min, float max)
 {
 return std::max(min, std::min(value, max));
 }
 float rumbleWidth(float projectedRoadWidth, std::size_t lanes)
 {
 return projectedRoadWidth / std::max(6u, 2 * lanes);
 }
 float laneMarkerWidth(float projectedRoadWidth, std::size_t lanes)
 {
 return projectedRoadWidth / std::max(32u, 8 * lanes);
 }
 float exponentialFog(float distance, float density)
 {
 return static_cast<float>(1 / std::pow(M_E, (distance * distance * density)));
 }
 float easeIn(float a, float b, float percent)
 {
 return a + (b - a) * std::pow(percent, 2.f);
 }
 float easeOut(float a, float b, float percent)
 {
 return a + (b - a) * (1 - std::pow(1.f - percent, 2.f));
 }
 float easeInOut(float a, float b, float percent)
 {
 return a + (b - a) * (static_cast<float>(-std::cos(percent * M_PI) / 2.f) + 0.5f);
 }
 float interpolate(float a, float b, float percent)
 {
 return a + (b - a)*percent;
 }
 float percentRemaining(float n, float total)
 {
 return (static_cast<int>(n) % static_cast<int>(total)) / total;
 }
}
class Polygon final : public sf::Drawable, public sf::Transformable, sf::NonCopyable
{
public:
 Polygon() : mVertices(sf::Quads, 4u) {}
 void setVertices(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, sf::Color color)
 {
 mVertices[0].position = sf::Vector2f(x1, y1);
 mVertices[1].position = sf::Vector2f(x2, y2);
 mVertices[2].position = sf::Vector2f(x3, y3);
 mVertices[3].position = sf::Vector2f(x4, y4);
 mVertices[0].color = mVertices[1].color = mVertices[2].color = mVertices[3].color = color;
 }
private:
 void draw(sf::RenderTarget& target, sf::RenderStates states) const override
 {
 states.transform *= getTransform();
 target.draw(mVertices, states);
 }
private:
 sf::VertexArray mVertices;
};
struct Point
{
 struct Screen
 {
 float x{};
 float y{};
 float w{};
 }screen{};
 sf::Vector3f world{};
 sf::Vector3f camera{};
 void project(float cameraX, float cameraY, float cameraZ, float cameraDepth, float width, float height, float roadWidth)
 {
 camera.x = world.x - cameraX;
 camera.y = world.y - cameraY;
 camera.z = world.z - cameraZ;
 auto scale = cameraDepth / camera.z;
 screen.x = width / 2.f + scale * camera.x * width / 2.f;
 screen.y = height / 2.f - scale * camera.y * height / 2.f;
 screen.w = scale * roadWidth * width / 2.f;
 }
};
class Colors
{
 struct ColorsData
 {
 sf::Color road{};
 sf::Color grass{};
 sf::Color rumble{};
 sf::Color lanes{};
 };
 using ColorsContainer = std::vector<ColorsData>;
public:
 enum Type
 {
 Light,
 Dark,
 Start,
 Finish,
 TypeCount
 };
public:
 Colors(Type type = Colors::Light)
 : mType(type)
 , mData(Colors::TypeCount)
 {
 mData[Colors::Light].road = { 100, 100, 100 };
 mData[Colors::Light].grass = { 16, 170, 16 };
 mData[Colors::Light].rumble = { 85, 85 , 85 };
 mData[Colors::Light].lanes = sf::Color::White;
 mData[Colors::Dark].road = { 100, 100, 100 };
 mData[Colors::Dark].grass = { 0, 154, 0 };
 mData[Colors::Dark].rumble = { 187,187, 187 };
 mData[Colors::Dark].lanes = { 100, 100, 100 };
 mData[Colors::Start].road = sf::Color::White;
 mData[Colors::Start].grass = { 16, 170, 16 };
 mData[Colors::Start].rumble = sf::Color::White;
 mData[Colors::Start].lanes = sf::Color::White;
 mData[Colors::Finish].road = {};
 mData[Colors::Finish].grass = { 16, 170, 16 };
 mData[Colors::Finish].rumble = {};
 mData[Colors::Finish].lanes = {};
 }
 void setType(Type type) { mType = type;}
 sf::Color getRoad() const { return mData[mType].road;}
 sf::Color getGrass() const { return mData[mType].grass;}
 sf::Color getRumble() const { return mData[mType].rumble;}
 sf::Color getLane() const { return mData[mType].lanes;}
private:
 Type mType;
 ColorsContainer mData;
};
class Segment final : public sf::Drawable, public sf::Transformable, sf::NonCopyable
{
public:
 using Ptr = std::unique_ptr<Segment>;
public:
 void setCurve(float i) { mCurve = i; }
 float getCurve() const { return mCurve; }
 Point& getPoint1() { return mPoint1; }
 Point& getPoint2() { return mPoint2; }
 const Point& getPoint1() const { return mPoint1; }
 const Point& getPoint2() const { return mPoint2; }
 void setSegmentColors(Colors::Type c) { mColors.setType(c); }
 void setIndex(std::size_t i) { mIndex = i; }
 std::size_t getIndex() const { return mIndex; }
 void setGrounds(float width, float fog)
 {
 auto lanes = 3u;
 // Landscape
 mLandscape.setSize({ width, mPoint1.screen.y - mPoint2.screen.y });
 mLandscape.setPosition(0, mPoint2.screen.y);
 mLandscape.setFillColor(mColors.getGrass());
 // Rumble sides
 auto rumbleWidth1 = rumbleWidth(mPoint1.screen.w, lanes);
 auto rumbleWidth2 = rumbleWidth(mPoint2.screen.w, lanes);
 mRumbleSide1.setVertices(mPoint1.screen.x - mPoint1.screen.w - rumbleWidth1, mPoint1.screen.y,
 mPoint1.screen.x - mPoint1.screen.w, mPoint1.screen.y,
 mPoint2.screen.x - mPoint2.screen.w, mPoint2.screen.y,
 mPoint2.screen.x - mPoint2.screen.w - rumbleWidth2, mPoint2.screen.y, mColors.getRumble());
 mRumbleSide2.setVertices(mPoint1.screen.x + mPoint1.screen.w + rumbleWidth1, mPoint1.screen.y,
 mPoint1.screen.x + mPoint1.screen.w, mPoint1.screen.y,
 mPoint2.screen.x + mPoint2.screen.w, mPoint2.screen.y,
 mPoint2.screen.x + mPoint2.screen.w + rumbleWidth2, mPoint2.screen.y, mColors.getRumble());
 // Main Road
 mMainRoad.setVertices(mPoint1.screen.x - mPoint1.screen.w, mPoint1.screen.y,
 mPoint1.screen.x + mPoint1.screen.w, mPoint1.screen.y,
 mPoint2.screen.x + mPoint2.screen.w, mPoint2.screen.y,
 mPoint2.screen.x - mPoint2.screen.w, mPoint2.screen.y, mColors.getRoad());
 // Lanes
 auto laneMarkerWidth1 = laneMarkerWidth(mPoint1.screen.w, lanes);
 auto laneMarkerWidth2 = laneMarkerWidth(mPoint2.screen.w, lanes);
 auto lanew1 = mPoint1.screen.w * 2 / lanes;
 auto lanew2 = mPoint2.screen.w * 2 / lanes;
 auto lanex1 = mPoint1.screen.x - mPoint1.screen.w + lanew1;
 auto lanex2 = mPoint2.screen.x - mPoint2.screen.w + lanew2;
 for (auto lane = 1u; lane < lanes; lanex1 += lanew1 + 1, lanex2 += lanew2 + 1, lane++)
 {
 if (lane == 1)
 mLanes1.setVertices(lanex1 - laneMarkerWidth1 / 2, mPoint1.screen.y,
 lanex1 + laneMarkerWidth1 / 2, mPoint1.screen.y,
 lanex2 + laneMarkerWidth2 / 2, mPoint2.screen.y,
 lanex2 - laneMarkerWidth2 / 2, mPoint2.screen.y, mColors.getLane());
 else
 mLanes2.setVertices(lanex1 - laneMarkerWidth1 / 2, mPoint1.screen.y,
 lanex1 + laneMarkerWidth1 / 2, mPoint1.screen.y,
 lanex2 + laneMarkerWidth2 / 2, mPoint2.screen.y,
 lanex2 - laneMarkerWidth2 / 2, mPoint2.screen.y, mColors.getLane());
 }
 // Fog effect
 mFog.setSize({ width, mPoint1.screen.y - mPoint2.screen.y });
 mFog.setPosition(0, mPoint1.screen.y);
 mFog.setFillColor(sf::Color(255, 255, 255, 255 - static_cast<unsigned char>(fog * 255)));
 }
private:
 void draw(sf::RenderTarget& target, sf::RenderStates states) const override
 {
 states.transform *= getTransform();
 target.draw(mLandscape, states);
 target.draw(mRumbleSide1, states);
 target.draw(mRumbleSide2, states);
 target.draw(mMainRoad, states);
 target.draw(mLanes1, states);
 target.draw(mLanes2, states);
 target.draw(mFog, states);
 }
private:
 Point mPoint1{};
 Point mPoint2{};
 Polygon mRumbleSide1{};
 Polygon mRumbleSide2{};
 Polygon mLanes1{};
 Polygon mLanes2{};
 Polygon mMainRoad{};
 sf::RectangleShape mLandscape{};
 sf::RectangleShape mFog{};
 Colors mColors{};
 std::size_t mIndex{};
 float mCurve{};
};
struct Road
{
 struct Length
 {
 const float shorty = 25.f;
 const float medium = 50.f;
 const float longy = 100.f;
 } length;
 struct Curve
 {
 const float easy = 2.f;
 const float medium = 4.f;
 const float hard = 6.f;
 } curve;
 struct Hill
 {
 const float none = 0.f;
 const float low = 20.f;
 const float medium = 40.f;
 const float high = 60.f;
 }hill;
}road;
class Game
{
 using SegmentContainer = std::vector<Segment::Ptr>;
public:
 Game()
 : mWindow(sf::VideoMode(640, 480), "test")
 , mSegments()
 , mSegmentLength(200.f)
 , mPlayerX(0.f)
 , mCameraDepth(1 / std::atan((100.f / 2.f)))
 , mCameraHeight(1000.f)
 , mPlayerZ((mCameraHeight * mCameraDepth))
 , mPosition(0.f)
 , mRumbleLength(3u)
 , mTrackLength(0.f)
 , mSpeed(0.f)
 {
 // build road
 addStraight(road.length.shorty / 2);
 addHill(road.length.shorty, road.hill.low);
 addLowRollingHills();
 addCurve(road.length.medium, road.curve.medium, road.hill.low);
 addLowRollingHills();
 addCurve(road.length.longy, road.curve.medium, road.hill.medium);
 addStraight();
 addCurve(road.length.longy, -road.curve.medium, road.hill.medium);
 addHill(road.length.longy, road.hill.high);
 addCurve(road.length.longy, road.curve.medium, -road.hill.low);
 addHill(road.length.longy, -road.curve.medium);
 addStraight();
 addDownhillToEnd();
 // setup start and finish of road
 mSegments[mSegments[static_cast<std::size_t>(std::floor(mPlayerZ / mSegmentLength)) % mSegments.size()]->getIndex() + 2]->setSegmentColors(Colors::Start);
 mSegments[mSegments[static_cast<std::size_t>(std::floor(mPlayerZ / mSegmentLength)) % mSegments.size()]->getIndex() + 3]->setSegmentColors(Colors::Start);
 for (auto n = 0u; n < mRumbleLength; n++)
 {
 mSegments[mSegments.size() - 1 - n]->setSegmentColors(Colors::Finish);
 }
 mTrackLength = mSegments.size() * mSegmentLength;
 }
 void run()
 {
 sf::Clock clock;
 auto timeSinceLastUpdate = sf::Time::Zero;
 while (mWindow.isOpen())
 {
 auto elapsedTime = clock.restart();
 timeSinceLastUpdate += elapsedTime;
 while (timeSinceLastUpdate > TimePerFrame)
 {
 timeSinceLastUpdate -= TimePerFrame;
 processEvents();
 update(TimePerFrame);
 }
 render();
 }
 }
private:
 void processEvents()
 {
 sf::Event event;
 while (mWindow.pollEvent(event))
 {
 if (event.type == sf::Event::Closed)
 mWindow.close();
 }
 }
 void update(sf::Time TimePerFrame)
 {
 auto dt = TimePerFrame.asSeconds();
 auto step = 1 / 60.f;
 auto maxSpeed = mSegmentLength / step;
 auto accel = maxSpeed / 5.f;
 auto breaking = -maxSpeed;
 auto decel = -maxSpeed / 5.f;
 auto offRoadDecel = -maxSpeed / 2.f;
 auto offRoadLimit = maxSpeed / 4.f;
 auto centrifugal = 0.3f;
 const auto& playerSegment = *mSegments[static_cast<std::size_t>(std::floor((mPosition + mPlayerZ) / mSegmentLength)) % mSegments.size()];
 auto speedPercent = mSpeed / maxSpeed;
 auto dx = dt * speedPercent;
 if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
 mPlayerX -= dx;
 if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
 mPlayerX += dx;
 mPlayerX -= (dx * speedPercent * playerSegment.getCurve() * centrifugal);
 if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
 mSpeed += accel * dt;
 else
 mSpeed += decel * dt;
 if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
 mSpeed += breaking * dt;
 if (((mPlayerX < -1.f) || (mPlayerX > 1.f)) && (mSpeed > offRoadLimit))
 mSpeed += offRoadDecel * dt;
 mPlayerX = limit(mPlayerX, -2.f, 2.f);
 mSpeed = limit(mSpeed, 0, maxSpeed);
 mPosition = increase(mPosition, dt * mSpeed, mTrackLength);
 }
 void render()
 {
 auto width = 640.f;
 auto height = 480.f;
 auto roadWidth = 2000.f;
 auto drawDistance = 500u;
 auto fogDensity = 5.f;
 const auto& baseSegment = *mSegments[static_cast<std::size_t>(std::floor(mPosition / mSegmentLength)) % mSegments.size()];
 auto basePercent = percentRemaining(mPosition, mSegmentLength);
 const auto& playerSegment = *mSegments[static_cast<std::size_t>(std::floor((mPosition + mPlayerZ) / mSegmentLength)) % mSegments.size()];
 auto playerPercent = percentRemaining(mPosition + mPlayerZ, mSegmentLength);
 auto playerY = interpolate(playerSegment.getPoint1().world.y, playerSegment.getPoint2().world.y, playerPercent);
 auto x = 0.f;
 auto dx = -(baseSegment.getCurve() * basePercent);
 auto maxy = height;
 mWindow.clear();
 for (auto n = 0u; n < drawDistance; ++n)
 {
 auto& segment = *mSegments[(baseSegment.getIndex() + n) % mSegments.size()];
 bool looped = segment.getIndex() < baseSegment.getIndex();
 auto fog = exponentialFog(n / (drawDistance * 1.f), fogDensity);
 auto camX = mPlayerX * roadWidth;
 auto camY = playerY + mCameraHeight;
 auto camZ = mPosition - (looped ? mTrackLength : 0.f);
 auto& point1 = segment.getPoint1();
 auto& point2 = segment.getPoint2();
 point1.project(camX - x, camY, camZ, mCameraDepth, width, height, roadWidth);
 point2.project(camX - x - dx, camY, camZ, mCameraDepth, width, height, roadWidth);
 x += dx;
 dx += segment.getCurve();
 if ((point1.camera.z <= mCameraDepth) || (point2.screen.y >= maxy || point2.screen.y >= point1.screen.y))
 continue;
 segment.setGrounds(width, fog);
 mWindow.draw(segment);
 maxy = point2.screen.y;
 }
 mWindow.display();
 }
 void addSegment(float curve, float y)
 {
 auto n = mSegments.size();
 auto segment = std::make_unique<Segment>();
 segment->setIndex(n);
 segment->setCurve(curve);
 segment->getPoint1().world.y = lastY();
 segment->getPoint2().world.y = y;
 segment->getPoint1().world.z = n * mSegmentLength;
 segment->getPoint2().world.z = (n + 1) * mSegmentLength;
 if (static_cast<std::size_t>(std::floor(n / mRumbleLength)) % 2)
 segment->setSegmentColors(Colors::Light);
 else
 segment->setSegmentColors(Colors::Dark);
 mSegments.push_back(std::move(segment));
 }
 void addRoad(float enter, float hold, float leave, float curve, float y)
 {
 auto startY = lastY();
 auto endY = startY + (y * mSegmentLength);
 auto total = enter + hold + leave;
 for (auto n = 0.f; n < enter; ++n)
 addSegment(easeIn(0, curve, n / enter), easeInOut(startY, endY, n / total));
 for (auto n = 0.f; n < hold; ++n)
 addSegment(curve, easeInOut(startY, endY, (enter + n) / total));
 for (auto n = 0.f; n < leave; ++n)
 addSegment(easeInOut(curve, 0, n / leave), easeInOut(startY, endY, (enter + hold + n) / total));
 }
 void addStraight(float n = 0)
 {
 auto num = (n == 0) ? road.length.medium : n;
 addRoad(num, num, num, 0, 0);
 }
 void addHill(float n = 0, float h = 0)
 {
 auto num = (n == 0) ? road.length.medium : n;
 auto height = (h == 0) ? road.hill.medium : h;
 addRoad(num, num, num, 0, height);
 }
 void addCurve(float n = 0, float c = 0, float h = 0)
 {
 auto num = (n == 0) ? road.length.medium : n;
 auto curve = (c == 0) ? road.curve.medium : c;
 auto height = (h == 0) ? road.hill.none : h;
 addRoad(num, num, num, curve, height);
 }
 void addDownhillToEnd(float n = 0)
 {
 auto num = (n == 0) ? 200 : n;
 addRoad(num, num, num, -road.curve.easy, -lastY() / mSegmentLength);
 }
 void addSCurves()
 {
 addRoad(road.length.medium, road.length.medium, road.length.medium, -road.curve.easy, road.hill.none);
 addRoad(road.length.medium, road.length.medium, road.length.medium, road.curve.medium, road.hill.medium);
 addRoad(road.length.medium, road.length.medium, road.length.medium, road.curve.easy, -road.hill.low);
 addRoad(road.length.medium, road.length.medium, road.length.medium, -road.curve.easy, road.hill.medium);
 addRoad(road.length.medium, road.length.medium, road.length.medium, -road.curve.medium, -road.hill.medium);
 }
 void addLowRollingHills(float n = 0, float h = 0)
 {
 auto num = (n == 0) ? road.length.shorty : n;
 auto height = (h == 0) ? road.hill.low : h;
 addRoad(num, num, num, 0, height / 2);
 addRoad(num, num, num, 0, -height);
 addRoad(num, num, num, 0, height);
 addRoad(num, num, num, 0, 0);
 addRoad(num, num, num, 0, height / 2);
 addRoad(num, num, num, 0, 0);
 }
 float lastY()
 {
 return (mSegments.size() == 0) ? 0 : mSegments[mSegments.size() - 1]->getPoint2().world.y;
 }
private:
 sf::RenderWindow mWindow;
 const static sf::Time TimePerFrame;
 SegmentContainer mSegments;
 std::size_t mRumbleLength;
 float mSegmentLength;
 float mPlayerX;
 float mCameraDepth;
 float mCameraHeight;
 float mPlayerZ;
 float mPosition;
 float mTrackLength;
 float mSpeed;
};
const sf::Time Game::TimePerFrame = sf::seconds(1 / 60.f);
int main()
{
 try
 {
 Game game;
 game.run();
 }
 catch (std::exception& e)
 {
 std::cout << "\nEXCEPTION: " << e.what() << std::endl;
 }
}

Output:

enter image description here

asked Nov 28, 2015 at 23:25
\$\endgroup\$

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.