Beispiel für ein erstes Mandelbrot?



  • @hustbaer sagte in Beispiel für ein erstes Mandelbrot?:

    @TGGC sagte in Beispiel für ein erstes Mandelbrot?:

    1. Wer zum Teufel lädt ein Bild nicht aus einer Datei sondern hardcoded das?

    Falls du das Mini-Bild für den Cursor meinst: ich!

    Da schliesse ich mich an, das habe ich auch schon so gemacht. Im Besonderen z.B. auch eine hardcodierte Schachbretttextur, die immer dann verwendet wurde, wenn die Texturdatei nicht geladen werden konnte.



  • Was mich an dem Code stört, es wird mit komplexen Zahlen gerechnet, aber es wird kein Datentyp std::complex<T> verwendet. Das macht auch die Formeln und den Code komplexer als es sein müsste.

    Beispiel wie man es mit std::complex sehr viel näher am Lehrbuch formulieren kann.

    #include <complex>
      
    std::size_t mandelbrot(const std::complex<double>& value, const std::size_t N) {
        // c ist das was Du als imag und real bezeichnet hast.
        std::complex<double>    z = value;
    
        for (std::size_t n = 0; n < N; ++n) {
            if (abs(z) > 4.0) return n;
    
            z = c + (z*z);
        }
    
        return N;
    }
    


    • inkonstistente Namensgebung, manche Member enden mit einem Underscore, andere nicht.
    • x und y als Member von Size sind ok, aber width and height hätten mir besser gefallen.
    • getMandelbrotSet() liest sich wie ein getter, gibt aber nichts zurück
    • rePlot() braucht zwei Parameter, um zu unterscheiden, ob der Cursor angezeigt werden soll oder nicht. Hier täte es vllt auch ein Cursor const* Obwohl der Cursor auch noch für updateWindowTitle gebraucht wird. Hm....
    • west const ;). Is aber wohl Geschmacksfrage.


  • Was sollen eigentlich die Kommentare

    // Console:: 
    

    ?

    Außerdem solltest du die Berechnungsfunktionen und UI (Console) strikt trennen.

    Edit: Für reine Berechnungsfunktionen benutze ich calc(ulate) als Namenspräfix.



  • @john-0 sagte in Beispiel für ein erstes Mandelbrot?:

    Was mich an dem Code stört, es wird mit komplexen Zahlen gerechnet, aber es wird kein Datentyp std::complex<T> verwendet. Das macht auch die Formeln und den Code komplexer als es sein müsste.

    Beispiel wie man es mit std::complex sehr viel näher am Lehrbuch formulieren kann.

    Je nachdem, wen man fragt. In einem Beitrag in stackoverflow wurde genau andersrum argumentiert. Da kam einer mit std::complex, wurde dann aber wie bei mir aufgedröselt. Hatte auch viel Zustimmung. Aber eigentlich würde mir es mit complex besser gefallen. Dachte aber, das wäre nicht klug 😉

    @DocShoe sagte in Beispiel für ein erstes Mandelbrot?:

    • inkonstistente Namensgebung, manche Member enden mit einem Underscore, andere nicht.

    Stimmt. Habe ich auch schon dran gedacht.

    • x und y als Member von Size sind ok, aber width and height hätten mir besser gefallen.

    Ist doch so?

    struct Size
    	{
    		int width = 0;
    		int height = 0;
    	};
    

    Oder meinst Du den Cursor?

    • getMandelbrotSet() liest sich wie ein getter, gibt aber nichts zurück

    Was schlägst Du vor? calculateMandelbrotSet() oder ähnliches?

    • rePlot() braucht zwei Parameter, um zu unterscheiden, ob der Cursor angezeigt werden soll oder nicht. Hier täte es vllt auch ein Cursor const* Obwohl der Cursor auch noch für updateWindowTitle gebraucht wird. Hm....

    Hier verstehe ich nicht ganz. Wenn der Cursor gezeichnet oder seine Werte angezeigt werden soll, muss ich ihn doch der Funktion übergeben? Und ob er gezeichnet werden soll, ist doch nur ein vordefinierter Parameter? Soll er gezeichnet werden, muss man gar nichts übergeben?

    @Th69 sagte in Beispiel für ein erstes Mandelbrot?:

    Was sollen eigentlich die Kommentare

    // Console:: 
    

    ?

    Ja, ist ein bißchen blöd. Ich hatte oben versprochen, alle Console-Funktionen zur Unterscheidung zu kommentieren. Was soll man aber kommentieren, was schon dasteht? War also nur der Ordnung halber. Naja.

    Außerdem solltest du die Berechnungsfunktionen und UI (Console) strikt trennen.

    Habe ich nicht?

    Edit: Für reine Berechnungsfunktionen benutze ich calc(ulate) als Namenspräfix.

    Jepp, bezieht sich auf den Hinweis von DocShoe wg getMandelbrotSet()?



  • @zeropage sagte in Beispiel für ein erstes Mandelbrot?:

    In einem Beitrag in stackoverflow wurde genau andersrum argumentiert. Da kam einer mit std::complex, wurde dann aber wie bei mir aufgedröselt. Hatte auch viel Zustimmung.

    Was waren denn die Gegenargumente? Link?



  • Find ich jetzt nicht mehr. Aber ein Argument war Optimierung. Betrachten wir es als erledigt 😉 Gefällt mir ja selbst nicht wirklich, wie ich es gemacht habe.



  • @Swordfish sagte in Beispiel für ein erstes Mandelbrot?:

    Was waren denn die Gegenargumente? Link?

    Vermutlich genau die selbe Art unterschiedlicher Denkansätze, wie ich schon beispielhaft versucht habe, zu demonstrieren. Soll man die Formel möglichst weit auflösen, so das man etwa fast kostenlos das Divergenzkriterium testen kann? Oder benutzt man einen möglichst flexiblen aber selbsterklärenden Code, den der Compiler aber höchstwahrscheinlich nicht bis zu diesem Grad optimieren wird. Es gibt keine übergreifende Antwort dazu.



  • @zeropage Vielleicht interessant wenn man es auf Geschwindigkeit anlegt: Mandelbrot set. Crunching numbers with SSE, AVX and OpenCL



  • Aha, danke 😌



  • @zeropage Ich interpretiere das als "Nein". In dem Fall würd' ich es "schön" schreiben 🙂



  • Liest sich aber interessant. Was man so alles machen kann...



  • @zeropage sagte in Beispiel für ein erstes Mandelbrot?:

    Außerdem solltest du die Berechnungsfunktionen und UI (Console) strikt trennen.

    Habe ich nicht?

    Edit: Für reine Berechnungsfunktionen benutze ich calc(ulate) als Namenspräfix.

    Jepp, bezieht sich auf den Hinweis von DocShoe wg getMandelbrotSet()?

    Du hast noch "Console.h" in deiner mandelbrot-Headerdatei eingebunden (und verwendest es auch). Dadurch kann die Klasse nicht einfach in anderen Projekten verwendet werden.
    Du solltest die größeren (privaten) Funktionen auch in eine CPP-Datei auslagern (so werden sie ja bisher alle als inline kompiliert).

    Und ja, ich meinte calc(ulate) als Hinweis wegen getMandelbrotSet().



  • @Th69 sagte in Beispiel für ein erstes Mandelbrot?:

    Du hast noch "Console.h" in deiner mandelbrot-Headerdatei eingebunden (und verwendest es auch). Dadurch kann die Klasse nicht einfach in anderen Projekten verwendet werden.

    Ah, verstehe.
    Console.h brauche ich ja für die "Farbe". Sollte ich in diesem Beispiel dann ein Template für nehmen? Und die Funktion plotMandelbrotSet()aus dem namespace nehmen und woanders hin setzen?



  • Du könntest eine eigene spezialisierte Mandelbrot_Console-Datei erstellen.
    Und als Farbe (Color) in deiner Mandelbrot-Klasse könntest du einen eigenen Datentyp benutzen (oder als Template-Parameter [falls du sowohl Farbpaletten als auch z.B. RGB unterstützen möchtest]?).
    Mathematisch /Technisch würde aber ein reiner Indexwert reichen (und nur die Anzeige sorgt dann für die passende Farbumwandlung - dies wäre dann auch wieder strikte Trennung von Logik und UI ;-).



  • Gut, danke schon mal an alle 🙂

    Ich habe versucht, alle(?) Hinweise in diesem Thread umzusetzen, nur mit std::complex hakt es noch. Dazu später mehr.

    Die Klasse 'Mandelbrot' sieht jetzt so aus:

    #pragma once
    #include <vector>
    #include <complex>
    
    namespace param // damit ich 'Area' überall benutzen kann und Mandelbrot es nehmen kann
    {
    	struct Area
    	{
    		double x0 = 0;
    		double x = 0;
    		double y0 = 0;
    		double y = 0;
    	};
    }
    
    class Mandelbrot
    {
    	struct Size
    	{
    		int width = 0;
    		int height = 0;
    	};
    
    	struct SizeFactor
    	{
    		double x = 0;
    		double y = 0;
    	};
    
    	struct Dot
    	{
    		int x = 0;
    		int y = 0;
    		std::size_t index = 0;
    	};
    
    public:
    	Mandelbrot(const int width, const int height) :
    		size_{ width, height },
    		mandelbrotSet_(width * height),
    		maxIterations_(300),
    		iterations_(100)
    	{
    		setArea(-2., 1., -1., 1.);
    	}
    
    	int width() const { return size_.width; }
    	int height() const { return size_.height; }
    	param::Area area() const { return area_; }
    	SizeFactor sizeFactor() const { return sizeFactor_; }
    	std::size_t iterations() const { return iterations_; }
    	std::size_t maxIterations() const { return maxIterations_; }
    	std::vector<Dot> mandelbrotSet() const { return mandelbrotSet_; }
    
    	void setArea(const double x0, const double x, const double y0, const double y)
    	{
    		setArea({ x0, x, y0, y });
    	}
    
    	void setArea(const param::Area& area)
    	{
    		area_ = area;
    		sizeFactor_.x = (area_.x - area_.x0) / (size_.width - 1.);
    		sizeFactor_.y = (area_.y - area_.y0) / (size_.height - 1.);
    		calcMandelbrotSet();
    	}
    
    	void setIterations(const std::size_t iterations)
    	{
    		if (iterations > maxIterations_)
    			iterations_ = maxIterations_;
    		else
    			iterations_ = iterations;
    
    		calcMandelbrotSet();
    	}
    
    private:
    	Size size_;
    	SizeFactor sizeFactor_;
    	Dot dot_;
    	param::Area area_;
    	std::size_t iterations_ = 0;
    	const std::size_t maxIterations_ = 0;
    	std::vector<Dot> mandelbrotSet_;
    
    	std::size_t mandelbrot(const std::complex<double>& c, const std::size_t N)
    	{
    		std::complex<double> z = c;
    		for (std::size_t n = 0; n < N; ++n)
    		{
    			if (std::abs(z) > 4.)  return n;
    			z = (z * z) + c;
    		}
    		return N;
    	}
    
    	void calcMandelbrotSet()
    	{
    		std::size_t n = 0;
    		for (int y = 0; y < size_.height; y++)
    		{
    			for (int x = 0; x < size_.width; x++)
    			{
    				const double real = area_.x0 + x * sizeFactor_.x; // current real value
    				const double imag = area_.y0 + y * sizeFactor_.y; // current imaginary value
    				const std::complex<double> c(real, imag);
    
    				mandelbrotSet_[n++] = { x, y, mandelbrot(c, iterations_) };
    			}
    		}
    	}
    
    	/*std::size_t mandelbrot(const double real, const double imag, const std::size_t N) const
    	{
    		double zReal = real; // real value
    		double zImag = imag; // imaginary value
    
    		for (std::size_t n = 0; n < N; ++n)
    		{
    			const double r2 = zReal * zReal;
    			const double i2 = zImag * zImag;
    			const double v = r2 + i2;
    
    			if (v > 4.) return n;
    
    			zImag = 2. * zReal * zImag + imag;
    			zReal = r2 - i2 + real;
    		}
    		return N;
    	}
    		void calcMandelbrotSet()
    	{
    		std::size_t n = 0;
    		for (int y = 0; y < size_.height; y++)
    		{
    			for (int x = 0; x < size_.width; x++)
    			{
    				const double real = area_.x0 + x * sizeFactor_.x; // current real value
    				const double imag = area_.y0 + y * sizeFactor_.y; // current imaginary value
    
    				mandelbrotSet_[n++] = { x, y, mandelbrot(real, imag, iterations_) };
    			}
    		}
    	}*/
    };
    

    Der namespace 'areaHandling' 🙄 heißt jetzt 'mandelbrot' 🙄 und sieht so aus:

    #pragma once
    #include "Console.h"
    #include "Mandelbrot.h"
    
    using Color = Char;
    using namespace param;
    
    namespace mandelbrot
    {
    	class Cursor
    	{
    		struct Size
    		{
    			int width = 0;
    			int height = 0;
    			struct Center
    			{
    				int x = 0;
    				int y = 0;
    			} center;
    		};
    
    		struct Position
    		{
    			int x = 0;
    			int y = 0;
    			double fx = 0;
    			double fy = 0;
    		};
    
    	public:	
    		Size size;
    		Position pos;
    		Color dot;
    		std::vector<bool> image;
    
    		Cursor() :
    			dot{ '#', console::DGREY, console::GREY },
    			size{ 7, 7, { 3, 3 } },
    			image{
    			0, 0, 0, 1, 0, 0, 0,
    			0, 0, 0, 1, 0, 0, 0,
    			0, 0, 0, 0, 0, 0, 0,
    			1, 1, 0, 0, 0, 1, 1,
    			0, 0, 0, 0, 0, 0, 0,
    			0, 0, 0, 1, 0, 0, 0,
    			0, 0, 0, 1, 0, 0, 0 } {}
    	};
    
    	Cursor cursor;                // ist das so in Ordnung als
    	std::vector<Color> colors;    // namespace-globale Variablen?
    
    	void plotMandelbrotSet(const Mandelbrot& mb)
    	{
    		for (const auto& dot : mb.mandelbrotSet())
    		{
    			Color color;
    			if (dot.index == mb.iterations())
    				color = Console::windowColor();
    			else
    				color = mandelbrot::colors[dot.index];
    
    			Console::putChar(dot.x, dot.y, color);
    		}
    	}
    
    	void plotCursor()
    	{
    		std::size_t n = 0;
    		for (int i = 0; i < cursor.size.height; ++i)
    			for (int j = 0; j < cursor.size.width; ++j)
    			{
    				const int x = cursor.pos.x + j - cursor.size.center.x;
    				const int y = cursor.pos.y + i - cursor.size.center.y;
    				if (cursor.image[n++])
    					Console::putChar(x, y, cursor.dot);
    			}
    	}
    	
    	void updateWindowTitle(const Mandelbrot& mb)
    	{
    		const std::string x0 = " area: xMin: " + std::to_string(mb.area().x0);
    		const std::string x = "   xMax: " + std::to_string(mb.area().x);
    		const std::string y0 = "   yMin: " + std::to_string(mb.area().y0);
    		const std::string y = "   yMax: " + std::to_string(mb.area().y);
    		const std::string iter = "     iterations: " + std::to_string(mb.iterations());
    		const std::string cx = "     cursor: x: " + std::to_string(cursor.pos.fx);
    		const std::string cy = "   y: " + std::to_string(cursor.pos.fy);
    
    		Console::setTitle(x0 + x + y0 + y + iter + cx + cy);
    	}
    
    	void rePlot(const Mandelbrot& mb, const bool showCursor = true)
    	{
    		plotMandelbrotSet(mb);
    		if (showCursor) plotCursor();
    		updateWindowTitle(mb);
    		Console::writeBuffer();  
    	}
    
    	void moveCursor(const Mandelbrot& mb, const int x, const int y)
    	{
    		cursor.pos.x += x;
    		cursor.pos.y += y;
    		cursor.pos.fx = cursor.pos.x * mb.sizeFactor().x + mb.area().x0;
    		cursor.pos.fy = cursor.pos.y * mb.sizeFactor().y + mb.area().y0;
    	}
    
    	void setCursorToZero(const Mandelbrot& mb)
    	{
    		const double fx0 = 1. / mb.sizeFactor().x * 2.;
    		const double fy0 = 1. / mb.sizeFactor().y;
    		moveCursor(mb, (int)std::round(fx0), (int)std::round(fy0));
    	}
    
    	void addIterations(Mandelbrot& mb, const std::size_t iterStep)
    	{
    		mb.setIterations(mb.iterations() + iterStep);
    	}
    
    	void subIterations(Mandelbrot& mb, const std::size_t iterStep)
    	{
    		mb.setIterations(mb.iterations() - iterStep);
    	}
    
    	void cursorLeft(Mandelbrot& mb, const int cursorStep)
    	{
    		moveCursor(mb, -cursorStep, 0);
    		if (cursor.pos.x < 0)
    		{
    			Area area = {
    				mb.area().x0 - mb.sizeFactor().x * cursorStep,
    				mb.area().x  - mb.sizeFactor().x * cursorStep,
    				mb.area().y0,
    				mb.area().y };
    
    			mb.setArea(area);
    			moveCursor(mb, cursorStep, 0);
    		}
    	}
    
    	void cursorRight(Mandelbrot& mb, const int cursorStep)
    	{
    		moveCursor(mb, cursorStep, 0);
    		if (cursor.pos.x >= mb.width())
    		{
    			Area area = {
    				mb.area().x0 + mb.sizeFactor().x * cursorStep,
    				mb.area().x  + mb.sizeFactor().x * cursorStep,
    				mb.area().y0,
    				mb.area().y };
    
    			mb.setArea(area);
    			moveCursor(mb, -cursorStep, 0);
    		}
    	}
    
    	void cursorUp(Mandelbrot& mb, const int cursorStep)
    	{
    		moveCursor(mb, 0, -cursorStep);
    		if (cursor.pos.y < 0)
    		{
    			Area area = {
    				mb.area().x0,
    				mb.area().x,
    				mb.area().y0 - mb.sizeFactor().y * cursorStep,
    				mb.area().y  - mb.sizeFactor().y * cursorStep };
    
    			mb.setArea(area);
    			moveCursor(mb, 0, cursorStep);
    		}
    	}
    
    	void cursorDown(Mandelbrot& mb, const int cursorStep)
    	{
    		moveCursor(mb, 0, cursorStep);
    		if (cursor.pos.y >= mb.height())
    		{
    			Area area = {
    				mb.area().x0,
    				mb.area().x,
    				mb.area().y0 + mb.sizeFactor().y * cursorStep,
    				mb.area().y  + mb.sizeFactor().y * cursorStep };
    
    			mb.setArea(area);
    			moveCursor(mb, 0, -cursorStep);
    		}
    	}
    
    	void zoomToArea(Mandelbrot& mb, const Area& lim, const int zoomSteps)
    	{
    		Area area = mb.area();
    		const double dx0 = (area.x0 - lim.x0) / zoomSteps;
    		const double dx = (area.x - lim.x) / zoomSteps;
    		const double dy0 = (area.y0 - lim.y0) / zoomSteps;
    		const double dy = (area.y - lim.y) / zoomSteps;
    
    		const int zoomLimit = 5; 
    		for (int i = 0; i < zoomSteps - zoomLimit; ++i)
    		{
    			area.x0 -= dx0,	
    			area.x -= dx,
    			area.y0 -= dy0,
    			area.y -= dy;
    			mb.setArea(area);
    			rePlot(mb, false);
    		}
    	}
    
    	void zoomToCursor(Mandelbrot& mb, const int zoomSteps, const int zoomIterStep)
    	{
    		Area area = {
    			cursor.pos.fx,
    			cursor.pos.fx,
    			cursor.pos.fy,
    			cursor.pos.fy };
    
    		zoomToArea(mb, area, zoomSteps);
    		mb.setIterations(mb.iterations() + zoomIterStep);
    	}
    
    	void zoomIn(Mandelbrot& mb, const int zoomSteps, int zoomStep)
    	{
    
    	}
    
    	void zoomOut(Mandelbrot& mb, const int zoomSteps, int zoomStep)
    	{
    
    	}
    
    	void explore(Mandelbrot& mb, bool& exploring)
    	{
    		setCursorToZero(mb);
    		rePlot(mb);
    		
    		const std::size_t iterStep = 10;	  // add this to iterations in addIterations
    		const std::size_t zoomIterStep = 50;  // add this to iterations in zoomToCursor
    		const int zoomSteps = 30;             // amount of zoomSteps in zoomToCursor
    		int cursorStep = 1, zoomStep = 1;
    
    		bool shiftKey = false;
    		bool getKey = true;
    		while (getKey)
    		{
    			switch (Console::getKeyCode()) // Console::
    			{
    			case VK_ADD:
    				addIterations(mb, iterStep);
    				rePlot(mb);
    				break;
    
    			case VK_SUBTRACT:
    				subIterations(mb, iterStep);
    				rePlot(mb);
    				break;
    
    			case VK_LEFT:
    				cursorLeft(mb, cursorStep);
    				rePlot(mb);
    				break;
    
    			case VK_RIGHT:
    				cursorRight(mb, cursorStep);
    				rePlot(mb);
    				break;
    
    			case VK_UP:
    				cursorUp(mb, cursorStep);
    				rePlot(mb);
    				break;
    
    			case VK_DOWN:
    				cursorDown(mb, cursorStep);
    				rePlot(mb);
    				break;
    
    			case VK_SHIFT: // change cursorStep
    				if (!shiftKey) 
    				{
    					cursorStep = 10;
    					shiftKey = true;
    				}
    				else
    				{
    					cursorStep = 1;
    					shiftKey = false;
    				}
    				break;
    
    			case 'Z':
    				zoomToCursor(mb, zoomSteps, zoomIterStep);
    				rePlot(mb);
    				break;
    
    			case 'A': // not yet implemented
    				zoomIn(mb, zoomSteps, zoomStep);
    				rePlot(mb);
    				break;
    
    			case 'Y': // not yet implemented
    				zoomOut(mb, zoomSteps, zoomStep);
    				rePlot(mb);
    				break;
    
    			case VK_ESCAPE:
    				getKey = false; //return to MandelBrot
    				exploring = false; //return to EXIT
    				break;
    
    			default:
    				break;
    			}
    		}
    	}
    
    	void createColorsForMandelbrot(const Mandelbrot& mb, const std::vector<Color>& givenColors)
    	{
    		const std::size_t limit = mb.maxIterations();
    
    		if (givenColors.size() >= limit)
    		{
    			mandelbrot::colors = givenColors;
    			return;
    		}
    
    		mandelbrot::colors.resize(limit);
    		std::size_t n = 0;
    		const std::size_t num_intervals = givenColors.size() - 1;
    		const std::size_t sub_interval_length = limit / num_intervals;
    		const std::size_t num_long_intervals = limit % num_intervals;
    		const float hop_step = 1.f * num_intervals / num_long_intervals;
    
    		float hop_counter = 0;
    		for (std::size_t i = 0; i < num_intervals; ++i)
    		{
    			if (i >= hop_counter)
    			{
    				for (std::size_t j = 0; j < sub_interval_length + 1; ++j)
    					mandelbrot::colors[n++] = givenColors[i];
    
    				hop_counter += hop_step;
    			}
    			else
    			{
    				for (std::size_t j = 0; j < sub_interval_length; ++j)
    					mandelbrot::colors[n++] = givenColors[i];
    			}
    		}
    	}
    
    	void exploreMandelbrot(Mandelbrot& mb, const std::vector<Color>& givenColors)
    	{
    		createColorsForMandelbrot(mb, givenColors);
    
    		bool running = true;
    		while (running)
    			explore(mb, running);
    	}
    }
    
    

    Jetzt dauert aber die Berechnung mit direkt komplexen Zahlen wie eine Ewigkeit gegenüber den aufgedröselten im auskommentierten Bereich, die sofort, augenblicklich geschehen ist.

    Es ist bestimmt irgendwas albernes, wofür ich mich wieder ein halbes Jahr schämen werde, aber was ist da falsch?

    Werde jetzt auch Getränke zu mir nehmen, also nicht wundern, wenn ich nicht gleich antworte 😉


Anmelden zum Antworten