RandomWalkers3D Example Code



  • Hallo,
    hier ein Beispiel für die colorierten und gezeichneten Wege von mehreren Random Walkers in einem drehenden 3D-Würfel.
    Ich denke, man kann den Code verstehen, ohne zu wissen, was die zp::DotEngine macht. Es wird zwar von ihr geerbt, aber für deren Funktionen braucht man nicht das Verständnis für.

    Ich teile den Code in drei Teile auf. Ein Post je Teil:

    • diese Einleitung und den 'Description Header'. In diesem werden notwendigen Variablen erstellt und Werte zugewiesen. In der Entwicklung kann man auch an diesem Ort editieren.
    • die .h-Datei. Mit einigen constexpr Methoden. Einige, die Update-Methoden sind hier definiert. Ich denke, das ist die beste Methode für constexpr-Deklaration. Dann trennt sich auch die Streu vom Weizen, was nicht constexpr ist, läuft dann auch nicht.
    • die .cpp-Datei. Hier habe ich einige constexpr Methoden definiert, hauptsächlich um nicht so viel Code auf einer Seite zu haben, außerdem sind es nur Setup-Methoden, die nur einmal aufgerufen werden.

    So weit, so gut. Dann schauen wir mal...
    .

    #pragma once
    #include <zp/Core/Common.h>
    
    namespace zp
    {
        // node start position
        enum
        {
            CENTER = 0,
            LEFT,
            RIGHT,
            TOP,
            BOTTOM,
            FRONT,
            BACK,
            RANDOM
        };
    
        // how to render node
        enum
        {
            NONE = 0,
            WHITE_POINT,
            COLORED_POINT,
            WHITE_NODE,
            COLORED_NODE
        };
    
        // definition of node & walker
        struct Node3D
        {
            Vector3D<double> Pos, Vel;
            double Radius = 0;
            double Heading = 0;
            RGBColor Color;
    
            bool IsDone = false;
        };
    
        struct Walker3D
        {
            Node3D Active, Start, End;
            std::vector<Node3D> Trace;
        };
    
        // description & setup
        struct RandomWalkersDesc
        {
            int FOV = 30; // field of view
            int CubeScaled = 100;
            Coord CubePosition = { 10, 0 }; // from center
            int CubeDistance = 330;
    
            static constexpr int NUM_OF_WALKERS = 50;
           
            double NodeRadius = 2.0;
            double NodeHeading = 0.2;
            double NodeScaler = 0.002; // ratio between cube side and node radius
    
            int NodeStartPosID = CENTER;
            int NodeMode = WHITE_NODE;
            int TraceMode = COLORED_NODE;
       
    
            bool ShowGenerate = false;
            bool StartWalking = true;
            bool AutoRotate = true;
    
            bool GenerateLoop = true;
            int LoopDelay = 75;
    
            int PaletteSize = 1000;
            std::vector<RGBColor> Palette;
    
        };
    
        using RandomWalkers3D = std::array<Walker3D, RandomWalkersDesc::NUM_OF_WALKERS>;
    }
    
    


  • #pragma once
    #include <zp/Engine/DotEngine.h>
    #include <zp/Models/Cube.h>
    #include "Headers/ModelView3D.h"
    #include "Headers/TransformObject.h"
    #include "Headers/CoordinateAxis3D.h"
    
    #include "Headers/RandomWalkers3DDesc.h"
    
    namespace zp
    {
        class RandomWalker3DX : public DotEngine
        {
        public:
            RandomWalker3DX();
            RandomWalker3DX(const int windowWidth, const int windowHeight);
            RandomWalker3DX(const int windowWidth, const int windowHeight, const int dotWidth, const int dotHeight);
            RandomWalker3DX(const ScreenResDesc& desc);
            ~RandomWalker3DX();
    
        private:
            // side of Cube is { -0.5, 0.5 }, side half is 0.5
            static constexpr double BORDER = Cube3D<double>::SIDE_HALF(); 
    
            ModelView3D<double> View;
            TransformObject<double> Global;
            CoordinateAxis3D<double> Axes3D;
    
            RandomWalkers3D Walkers;
            RandomWalkersDesc Desc;
    
            std::size_t TotalNodes = 0;
            std::size_t WalkersDone = 0;
            bool Generating = false;
            float GenTime = 0;
    
        private:
            void setupCanvas();
            virtual bool onCreate() override;
            virtual bool onUpdate() override;
            virtual bool onInput() override;
    
        public:
            constexpr void setupWalkers(RandomWalkers3D& walkers);
    
            constexpr void createModelView(const int fovDegree);
            constexpr void createGlobal();
            constexpr void createBorderCube();
            constexpr void createAxes3D();
            constexpr void createColorPalette(const int size);
    
            constexpr void createWalkers(RandomWalkers3D& walkers);
            constexpr void resetWalkers(RandomWalkers3D& walkers);
    
            void createWalker(Walker3D& walker);
            void resetWalker(Walker3D& walker);
    
            constexpr void printInfo(const RandomWalkers3D& walkers);
    
        private:
            void setRandomStartPosition(Node3D& node) const;
            Vector3D<double> getRandomVelocityInt(const double scale) const;
            Vector3D<double> getRandomVelocity(const double heading, const double scale) const;
            Vector3D<double> getRandomAngle(const double heading) const;
    
        private:
            
            // Generation ////////////////////////////////////////////////////////////////////////////////
          
            constexpr void generateWalker(Walker3D& walker, const int renderID)
            {
                if (walker.Active.IsDone)
                    return;
    
                if (isPointInsideBorders(walker.Active.Pos))
                {
                    walker.Active.Vel *= Mat4x4<double>().makeRotation(getRandomAngle(walker.Active.Heading));
                    walker.Active.Pos += walker.Active.Vel;
    
                    walker.Active.Color = getNodeColor(walker.Active.Pos);
                    drawNode(walker.Active, renderID);
    
                    walker.Trace.emplace_back(walker.Active);
                    walker.End.Pos = walker.Active.Pos;
    
                    TotalNodes++;
                }
                else
                {
                    walker.Active.Vel.clear();
                    walker.Active.IsDone = true;
    
                    WalkersDone++;
                    if (WalkersDone >= std::size(Walkers))
                        Generating = false;
                }
            }
    
            constexpr void generateWalkers(RandomWalkers3D& walkers)
            {
                for (auto& walker : walkers)
                    generateWalker(walker, Desc.NodeMode);
            }
    
            constexpr void updateWalkers(RandomWalkers3D& walkers)
            {
                if (Generating)
                {
                    generateWalkers(walkers);
                }
    
                if (!Generating || Desc.ShowGenerate)
                {
                    drawWalkers(walkers, Desc.TraceMode);
    
                    if (Desc.GenerateLoop)
                    {
                        if (delayThis(Desc.LoopDelay))
                            resetWalkers(walkers);
                    }
                }
            }
    
            // Projection //////////////////////////////////////////////////////////////////////////////////////////
    
            constexpr Vector3D<double> projectedPoint(const Vector3D<double>& p) const
            {
                Vector3D<double> point = p;
    
                point.transform(Global.matrix());
                point = View.projectedToScreen(point);
    
                return point;
            }
    
            constexpr double projectedLength(const double length) const
            {
                TransformObject<double> local = Global;
                local.rotate().clear();
    
                Vector3D<double> pointA;
                pointA.transform(local.matrix());
                pointA = View.projectedToScreen(pointA);
    
                Vector3D<double> pointB = { length, 0.0, 0.0 };
                pointB.transform(local.matrix());
                pointB = View.projectedToScreen(pointB);
    
                return pointB.x - pointA.x;
            }
    
            constexpr Node3D projectedNode(const Node3D& n) const
            {
                Node3D node = n;
    
                node.Pos = projectedPoint(node.Pos);
                node.Radius = projectedLength(node.Radius);
    
                return node;
            }
    
            constexpr std::vector<Node3D> projectedWalkers(const RandomWalkers3D& walkers) const
            {
                std::vector<Node3D> allNodes;
    
                for (const auto& walker : walkers)
                {
                    std::vector<Node3D> nodes = walker.Trace;
    
                    for (auto& node : nodes)
                        node = projectedNode(node);
    
                    if (Desc.ShowGenerate)
                    {
                        nodes.insert(nodes.begin(), projectedNode(walker.Start));
                        nodes.emplace_back(projectedNode(walker.End));
                    }
    
                    allNodes = utils::add(allNodes, nodes);
                }
    
                std::sort(allNodes.begin(), allNodes.end(), [](const auto& p1, const auto& p2)
                    {
                        return p1.Pos.z > p2.Pos.z;
                    });
    
                return allNodes;
            }
    
            constexpr std::vector<Box<double>> projectedBoxes() const
            {
                std::vector<Box<double>> boxes(std::size(Cube3D<double>::BOX_MESH()));
    
                auto n = 0;
                for (const auto& b : Cube3D<double>::BOX_MESH())
                {
                    Box<double> box = b;
                    box.transform(Global.matrix());
                    box.color() = DARK_GREY;
    
                    if (View.isVisibleToCamera(box))
                        box.color() = GREY;
    
                    box = View.projectedToScreen(box);
                    boxes[n++] = box;
                }
    
                std::sort(boxes.begin(), boxes.end(), [](const auto& c1, const auto& c2)
                    {
                        return c1.center().z > c2.center().z;
                    });
    
                return boxes;
            }
    
            ////////////////////////////////////////////////////////////////////////////////////////////////////////
    
            constexpr RGBColor getNodeColor(const Vector3D<double>& pos) const
            {
                std::size_t index = 0;
                double radius = 0;
    
                switch (Desc.NodeStartPosID)
                {
                case CENTER:
                    radius = math::toSpherical(pos).Radius;
                    index = static_cast<std::size_t>(utils::map(radius, 0.0, BORDER * 1.3, 0.0, std::size(Desc.Palette) - 1.0));
                    return Desc.Palette[index];
                case LEFT:
                    index = static_cast<std::size_t>(utils::map(pos.x, -BORDER, BORDER, 0.0, std::size(Desc.Palette) - 1.0));
                    return Desc.Palette[index];
                case RIGHT:
                    index = static_cast<std::size_t>(utils::map(pos.x, BORDER, -BORDER, 0.0, std::size(Desc.Palette) - 1.0));
                    return Desc.Palette[index];
                case TOP:
                    index = static_cast<std::size_t>(utils::map(pos.y, -BORDER, BORDER, 0.0, std::size(Desc.Palette) - 1.0));
                    return Desc.Palette[index];
                case BOTTOM:
                    index = static_cast<std::size_t>(utils::map(pos.y, BORDER, -BORDER, 0.0, std::size(Desc.Palette) - 1.0));
                    return Desc.Palette[index];
                case FRONT:
                    index = static_cast<std::size_t>(utils::map(pos.z, -BORDER, BORDER, 0.0, std::size(Desc.Palette) - 1.0));
                    return Desc.Palette[index];
                case BACK:
                    index = static_cast<std::size_t>(utils::map(pos.z, BORDER, -BORDER, 0.0, std::size(Desc.Palette) - 1.0));
                    return Desc.Palette[index];
                case RANDOM:
                    radius = math::toSpherical(pos).Radius;
                    index = static_cast<std::size_t>(utils::map(radius, BORDER * 1.3, 0.0, 0.0, std::size(Desc.Palette) - 1.0));
                    return Desc.Palette[index];
                default:
                    return GREY;
                }
            }
    
            constexpr void drawNode(const Node3D& node, const int renderID) const
            {
                switch (renderID)
                {
                case NONE:
                    break;
                case WHITE_POINT:
                    drawPoint(projectedPoint(node.Pos), WHITE);
                    break;
                case COLORED_POINT:
                    drawPoint(projectedPoint(node.Pos), node.Color);
                    break;
                case WHITE_NODE:
                    drawFillCircle(projectedPoint(node.Pos), projectedLength(node.Radius), WHITE);
                    break;
                case COLORED_NODE:
                    drawFillCircle(projectedPoint(node.Pos), projectedLength(node.Radius), node.Color);
                    break;
                default:
                    break;
                }
            }
    
            constexpr void drawWalkers(const RandomWalkers3D& walkers, const int renderID) const
            {
                switch (renderID)
                {
                case NONE:
                    break;
                case WHITE_POINT:
                    for (const auto& node : projectedWalkers(walkers))
                        drawPoint(node.Pos, WHITE);
                    break;
                case COLORED_POINT:
                    for (const auto& node : projectedWalkers(walkers))
                        drawPoint(node.Pos, node.Color);
                    break;
                case WHITE_NODE:
                    for (const auto& node : projectedWalkers(walkers))
                         drawFillCircle(node.Pos, node.Radius, WHITE);
                    break;
                case COLORED_NODE:
                    for (const auto& node : projectedWalkers(walkers))
                        drawFillCircle(node.Pos, node.Radius, node.Color);
                    break;
                default:
                    break;
                }
            }
    
            constexpr void updateBorderCube()
            {
                if (Global.translate().z < Desc.CubeDistance + 15.0)
                    drawCoordinateAxes();
    
                drawBorderCube();
            }
    
            constexpr void drawBorderCube() const
            {
                for (const auto& box : projectedBoxes())
                    drawBox(box, box.color());
            }
    
            constexpr void drawCoordinateAxes()
            {
                Axes3D.transform(Global.matrix());
    
                const int tmp = lineWeight();
                lineWeight(2);
                Axes3D.draw(*this, { 0, 0 }, 1); // to do
                lineWeight(tmp);
            }
    
            constexpr bool isPointInsideBorders(const Vector3D<double>& point, const double delta = 0.0) const
            {
                const double min = -BORDER + delta;
                const double max = BORDER - delta;
    
                return (point.x >= min && point.x <= max &&
                    point.y >= min && point.y <= max &&
                    point.z >= min && point.z <= max);
            }
    
            constexpr bool isPointOutsideBorders(const Vector3D<double>& point, const double delta = 0.0) const
            {
                return !isPointInsideBorders(point, delta);
            }
        };
    }
    
    


  • #include "RandomWalkers3DX.h"
    #include <zp/Math/Random.h>
    
    zp::RandomWalker3DX::RandomWalker3DX()
    {
    }
    
    zp::RandomWalker3DX::RandomWalker3DX(const int windowWidth, const int windowHeight) :
    	DotEngine(windowHeight, windowHeight)
    {
    }
    
    zp::RandomWalker3DX::RandomWalker3DX(const int windowWidth, const int windowHeight, const int dotWidth, const int dotHeight) :
    	DotEngine(windowWidth, windowHeight, dotWidth, dotHeight)
    {
    }
    
    zp::RandomWalker3DX::RandomWalker3DX(const ScreenResDesc& desc) :
    	DotEngine(desc)
    {
    }
    
    zp::RandomWalker3DX::~RandomWalker3DX()
    {
    }
    
    void zp::RandomWalker3DX::setupCanvas()
    {
    	screenColor() = VERY_DARK_GREY;
    	screenFont().setAppearance({ 1, 2 }, 1);
    	screenFont().color() = BLACK;
    
    	showFPS();
    }
    
    bool zp::RandomWalker3DX::onCreate()
    {
    	setupCanvas();
    	setupWalkers(Walkers);
    
    	return true;
    }
    
    bool zp::RandomWalker3DX::onUpdate()
    {
    	updateWalkers(Walkers);
    	updateBorderCube();
    	printInfo(Walkers);
    
    	return true;
    }
    
    bool zp::RandomWalker3DX::onInput()
    {
    	// exit
    	if (getKey(VK_ESCAPE).Pressed)
    		return false;
    
    	// reset walkers
    	if (getKey(VK_BACK).Pressed)
    	{
    		resetWalkers(Walkers);
    	}
    
    	// reset transformation
    	if (getKey(VK_RETURN).Pressed)
    	{
    		createGlobal();
    	}
    
    	// auto rotate
    	if (getKey(VK_SPACE).Released)
    	{
    		Desc.AutoRotate = !Desc.AutoRotate;
    	}
    
    	if (Desc.AutoRotate)
    	{
    		Global.rotate().x += 0.4 * elapsedTime();
    		Global.rotate().y += 0.5 * elapsedTime();
    		Global.rotate().z += 0.3 * elapsedTime();
    	}
    
    	// free hand transform 
    	Global.translateXY(*this, M_LEFT, 3.0);
    	Global.translateZ(*this, 'A', 'Y', 2.0);
    
    	Global.rotateXY(*this, M_RIGHT, 2.0);
    	Global.rotateZ(*this, 'Q', 'W', 1.0);
    
    	return true;
    }
    
    constexpr void zp::RandomWalker3DX::setupWalkers(RandomWalkers3D& walkers)
    {
    	createModelView(Desc.FOV);
    	createGlobal();
    	createBorderCube();
    	createColorPalette(Desc.PaletteSize);
    	createWalkers(walkers);
    }
    
    constexpr void zp::RandomWalker3DX::createModelView(const int fovDegree)
    {
    	View = { screenRect(), static_cast<float>(fovDegree) };
    }
    
    constexpr void zp::RandomWalker3DX::createGlobal()
    {
    	const Vector3D<double> scale(Desc.CubeScaled, Desc.CubeScaled, Desc.CubeScaled);
    	const Vector3D<double> rotate(0, 0, 0);
    	const Vector3D<double> translate(Desc.CubePosition.X, Desc.CubePosition.Y, Desc.CubeDistance);
    
    	Global.setup(scale, rotate, translate);
    }
    
    constexpr void zp::RandomWalker3DX::createBorderCube()
    {
    	Cube3D<double>::SETUP();
    	createAxes3D();
    }
    
    constexpr void zp::RandomWalker3DX::createAxes3D()
    {
    	Axes3D.setup(View);
    	Axes3D.createLength(0.1);
    	Axes3D.createColors(BLACK, BLACK, BLACK);
    }
    
    constexpr void zp::RandomWalker3DX::createColorPalette(const int size)
    {
    	Desc.Palette = utils::reMap(color::RGBPalette(), size);
    }
    
    constexpr void zp::RandomWalker3DX::createWalkers(RandomWalkers3D& walkers)
    {
    	for (auto& walker : walkers)
    		createWalker(walker);
    
    	Generating = true;
    }
    
    constexpr void zp::RandomWalker3DX::resetWalkers(RandomWalkers3D& walkers)
    {
    	for (auto& walker : walkers)
    		resetWalker(walker);
    
    	TotalNodes = 0;
    	WalkersDone = 0;
    	Generating = true;
    }
    
    void zp::RandomWalker3DX::createWalker(Walker3D& walker)
    {
    	walker.Active.Radius = Desc.NodeRadius * Desc.NodeScaler;
    	walker.Active.Heading = Desc.NodeHeading;
    	walker.Active.Color = GREY;
    
    	resetWalker(walker);
    }
    
    void zp::RandomWalker3DX::resetWalker(Walker3D& walker)
    {
    	walker.Trace.clear();
    	setRandomStartPosition(walker.Active);
    
    	walker.Active.IsDone = false;
    	walker.Start = walker.Active;
    	walker.Start.Radius *= 1.5;
    	walker.Start.Color = BLACK;
    	walker.End = walker.Start;
    	walker.End.Color = WHITE;
    }
    
    constexpr void zp::RandomWalker3DX::printInfo(const RandomWalkers3D& walkers)
    {
    	Walker3D walker = walkers[0];
    	Vector2D<int> pos = { 1, screenFont().getHeight() / 2 };
    
    	drawString(pos, "Node Radius   " + utils::toString(projectedLength(walker.Active.Radius), 2, 2));
    	pos.y += screenFont().getHeight();
    	drawString(pos, "Node Heading  " + utils::toString(walker.Active.Heading, 1, 2));
    	pos.y += screenFont().getHeight();
    	drawString(pos, "        Palette    " + std::to_string(std::size(Desc.Palette)));
    	pos.y += screenFont().getHeight();
    	drawString(pos, "       Walkers    " + std::to_string(std::size(walkers)));
    	pos.y += screenFont().getHeight() * 2;
    
    	drawString(pos, "Walkers Done     " + std::to_string(WalkersDone));
    	pos.y += screenFont().getHeight();
    	drawString(pos, "Total Nodes       " + std::to_string(TotalNodes));
    	pos.y += screenFont().getHeight();
    	drawString(pos, "Average Nodes   " + std::to_string(TotalNodes / std::size(walkers)));
    }
    
    void zp::RandomWalker3DX::setRandomStartPosition(Node3D& node) const
    {
    	Random rng;
    	Vector3D<double> pos, vel;
    	int posID = Desc.NodeStartPosID;
    
    	if (posID == RANDOM)
    		posID = rng.uniformInt<int>(1, 6);
    
    	switch (posID)
    	{
    	case CENTER:
    		pos.clear();
    		vel = getRandomVelocity(1.0, node.Radius * 2);
    		break;
    	case LEFT:
    		pos.x = -BORDER;
    		pos.y = rng.uniformReal<double>(-BORDER, BORDER);
    		pos.z = rng.uniformReal<double>(-BORDER, BORDER);
    		vel.x = node.Radius * 2;
    		break;
    	case RIGHT:
    		pos.x = BORDER;
    		pos.y = rng.uniformReal<double>(-BORDER, BORDER);
    		pos.z = rng.uniformReal<double>(-BORDER, BORDER);
    		vel.x = -node.Radius * 2;
    		break;
    	case TOP:
    		pos.x = rng.uniformReal<double>(-BORDER, BORDER);
    		pos.y = -BORDER;
    		pos.z = rng.uniformReal<double>(-BORDER, BORDER);
    		vel.y = node.Radius * 2;
    		break;
    	case BOTTOM:
    		pos.x = rng.uniformReal<double>(-BORDER, BORDER);
    		pos.y = BORDER;
    		pos.z = rng.uniformReal<double>(-BORDER, BORDER);
    		vel.y = -node.Radius * 2;
    		break;
    	case FRONT:
    		pos.x = rng.uniformReal<double>(-BORDER, BORDER);
    		pos.y = rng.uniformReal<double>(-BORDER, BORDER);
    		pos.z = -BORDER;
    		vel.z = node.Radius * 2;
    		break;
    	case BACK:
    		pos.x = rng.uniformReal<double>(-BORDER, BORDER);
    		pos.y = rng.uniformReal<double>(-BORDER, BORDER);
    		pos.z = BORDER;
    		vel.z = -node.Radius * 2;
    		break;
    	default:
    		pos.clear();
    		vel = getRandomVelocity(node.Heading, node.Radius * 2);
    	}
    
    	node.Pos = pos;
    	node.Vel = vel;
    }
    
    zp::Vector3D<double> zp::RandomWalker3DX::getRandomVelocityInt(const double scale) const
    {
    	Random rng;
    	auto p = false;
    
    	p = rng.uniformBool();
    	const auto x = p ? -1 : 1;
    	p = rng.uniformBool();
    	const auto y = p ? -1 : 1;
    	p = rng.uniformBool();
    	const auto z = p ? -1 : 1;
    
    	return Vector3D<double>(x, y, z) * scale;
    }
    
    zp::Vector3D<double> zp::RandomWalker3DX::getRandomVelocity(const double heading, const double scale) const
    {
    	Random rng;
    	const double angle = heading * math::TAU<double>;
    	const double phi = rng.uniformReal<double>(-angle / 2, angle / 2);
    	const double theta = rng.uniformReal<double>(-angle / 2, angle / 2);
    
    	return math::toCartesian<double>(scale, phi, theta);
    }
    
    zp::Vector3D<double> zp::RandomWalker3DX::getRandomAngle(const double heading) const
    {
    	Random rng;
    	const double angle = heading * math::TAU<double>;
    	const double x = rng.uniformReal<double>(-angle / 2, angle / 2);
    	const double y = rng.uniformReal<double>(-angle / 2, angle / 2);
    	const double z = rng.uniformReal<double>(-angle / 2, angle / 2);
    
    	return { x, y, z };
    }
    
    
    
    
    
    
    


  • Vielen Danke fürs Lesen! 🙂



  • @zeropage
    Sieht doch soweit ganz gut aus. Wenn es jetzt noch sauber dokumentiert wäre, wäre ich rundrum glücklich.

    Ein paar Kleinigkeiten.

    • Schaue dir mal std::ranges::sort an.
    • Du hast einige constexpr Funktionen. Möchtest du diese auch zur Compilerzeit einsetzen?
    • Einen leeren Destruktor würde ich entfernen.
    • Ein nacktes Enum würde ich nicht mehr nutzen, sondern eher ein enum class.
    • Du gehst sparsam mit Pointer um. Super!

    Ein Tipp: Schaue dir mal Cppcheck an. Dieses Programm dient zur statischen Codeanalyse und lehrt dich nebenbei auch neuere Syntax.


Anmelden zum Antworten