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.
- Schaue dir mal