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 roadAdded hills for the roadAdded 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:
Fake 3D effect in SFML - follow up 2
c++;c++11;sfml