diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index d739666..0ad4a7e 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -11,6 +11,7 @@ export const routes: Routes = [ { path: RouterConstants.SORTING.PATH, component: RouterConstants.SORTING.COMPONENT}, { path: RouterConstants.IMPRINT.PATH, component: RouterConstants.IMPRINT.COMPONENT}, { path: RouterConstants.GOL.PATH, component: RouterConstants.GOL.COMPONENT}, - { path: RouterConstants.LABYRINTH.PATH, component: RouterConstants.LABYRINTH.COMPONENT} + { path: RouterConstants.LABYRINTH.PATH, component: RouterConstants.LABYRINTH.COMPONENT}, + { path: RouterConstants.FRACTAL.PATH, component: RouterConstants.FRACTAL.COMPONENT} ]; diff --git a/src/app/constants/RouterConstants.ts b/src/app/constants/RouterConstants.ts index 05f9d04..b60e8fa 100644 --- a/src/app/constants/RouterConstants.ts +++ b/src/app/constants/RouterConstants.ts @@ -6,6 +6,7 @@ import {PathfindingComponent} from '../pages/algorithms/pathfinding/pathfinding. import {SortingComponent} from '../pages/algorithms/sorting/sorting.component'; import {ConwayGolComponent} from '../pages/algorithms/conway-gol/conway-gol.component'; import {LabyrinthComponent} from '../pages/algorithms/pathfinding/labyrinth/labyrinth.component'; +import {FractalComponent} from '../pages/algorithms/fractal/fractal.component'; export class RouterConstants { @@ -51,6 +52,12 @@ export class RouterConstants { COMPONENT: LabyrinthComponent }; + static readonly FRACTAL = { + PATH: 'algorithms/fractal', + LINK: '/algorithms/fractal', + COMPONENT: FractalComponent + }; + static readonly IMPRINT = { PATH: 'imprint', LINK: '/imprint', diff --git a/src/app/constants/UrlConstants.ts b/src/app/constants/UrlConstants.ts index 87164ee..c44df1a 100644 --- a/src/app/constants/UrlConstants.ts +++ b/src/app/constants/UrlConstants.ts @@ -10,4 +10,8 @@ static readonly CONWAYS_WIKI = 'https://de.wikipedia.org/wiki/Conways_Spiel_des_Lebens' static readonly PRIMS_WIKI = 'https://de.wikipedia.org/wiki/Algorithmus_von_Prim' static readonly KRUSKAL_WIKI = 'https://de.wikipedia.org/wiki/Algorithmus_von_Kruskal' + static readonly MANDELBROT_WIKI = 'https://de.wikipedia.org/wiki/Mandelbrot-Menge' + static readonly JULIA_WIKI = 'https://de.wikipedia.org/wiki/Julia-Menge' + static readonly NEWTON_FRACTAL_WIKI = 'https://de.wikipedia.org/wiki/Newtonfraktal' + static readonly BURNING_SHIP_WIKI = 'https://de.wikipedia.org/wiki/Burning_ship_(Fraktal)' } diff --git a/src/app/pages/algorithms/fractal/fractal.component.html b/src/app/pages/algorithms/fractal/fractal.component.html new file mode 100644 index 0000000..8ff55e7 --- /dev/null +++ b/src/app/pages/algorithms/fractal/fractal.component.html @@ -0,0 +1,55 @@ + + + {{ 'FRACTAL.TITLE' | translate }} + + + +
+ + {{ 'FRACTAL.ALGORITHM' | translate }} + + @for (algo of algoInformation.entries; track algo.name) { + {{ algo.name }} + } + + + + + {{ 'FRACTAL.COLOR_SCHEME' | translate }} + + @for (name of FRACTAL_COLOR_SCHEMES; track name) { + {{ name }} + } + + + + + {{ 'FRACTAL.MAX_ITERATION' | translate }} + + +
+
+ + +
+
+
diff --git a/src/app/pages/algorithms/fractal/fractal.component.scss b/src/app/pages/algorithms/fractal/fractal.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/algorithms/fractal/fractal.component.ts b/src/app/pages/algorithms/fractal/fractal.component.ts new file mode 100644 index 0000000..8c6999b --- /dev/null +++ b/src/app/pages/algorithms/fractal/fractal.component.ts @@ -0,0 +1,189 @@ +import {AfterViewInit, Component, ElementRef, inject, ViewChild} from '@angular/core'; +import {Information} from '../information/information'; +import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatFormField, MatInput, MatLabel} from '@angular/material/input'; +import {MatOption} from '@angular/material/core'; +import {MatSelect} from '@angular/material/select'; +import {AlgorithmInformation} from '../information/information.models'; +import {UrlConstants} from '../../../constants/UrlConstants'; +import {FormsModule} from '@angular/forms'; +import {FractalService} from './service/fractal.service'; +import {DEFAULT_ITERATION, FRACTAL_COLOR_SCHEMES, FractalConfig, MAX_ITERATION, MIN_ITERATION} from './fractal.model'; + +@Component({ + selector: 'app-fractal', + imports: [ + Information, + MatCard, + MatCardContent, + MatCardHeader, + MatCardTitle, + TranslatePipe, + MatFormField, + MatLabel, + MatOption, + MatSelect, + FormsModule, + MatInput + ], + templateUrl: './fractal.component.html', + styleUrl: './fractal.component.scss', +}) +export class FractalComponent implements AfterViewInit { + algoInformation: AlgorithmInformation = { + title: 'FRACTAL.EXPLANATION.TITLE', + entries: [ + { + name: 'Mandelbrot', + description: 'FRACTAL.EXPLANATION.MANDELBROT_EXPLANATION', + link: UrlConstants.MANDELBROT_WIKI + }, + { + name: 'Julia', + description: 'FRACTAL.EXPLANATION.JULIA_EXPLANATION', + link: UrlConstants.JULIA_WIKI + }, + { + name: 'Burning Ship', + description: 'FRACTAL.EXPLANATION.BURNING_SHIP_EXPLANATION', + link: UrlConstants.BURNING_SHIP_WIKI + }, + { + name: 'Newton', + description: 'FRACTAL.EXPLANATION.NEWTON_EXPLANATION', + link: UrlConstants.NEWTON_FRACTAL_WIKI + } + ], + disclaimer: 'FRACTAL.EXPLANATION.DISCLAIMER', + disclaimerBottom: '', + disclaimerListEntry: [ + 'FRACTAL.EXPLANATION.DISCLAIMER_1', + 'FRACTAL.EXPLANATION.DISCLAIMER_2', + 'FRACTAL.EXPLANATION.DISCLAIMER_3', + 'FRACTAL.EXPLANATION.DISCLAIMER_4' + ] + }; + private readonly fractalService = inject(FractalService); + + config: FractalConfig = { + algorithm: 'Mandelbrot', + width: 1000, + height: 1000, + maxIterations: DEFAULT_ITERATION, + zoom: 1, + offsetX: -0.5, + offsetY: 0, + colorScheme: FRACTAL_COLOR_SCHEMES[0] + }; + private isDragging = false; + private dragStartX = 0; + private dragStartY = 0; + + @ViewChild('fractalCanvas') canvasRef!: ElementRef; + + selectedAlgorithm: string = this.config.algorithm; + currentIteration: number = this.config.maxIterations; + selectedColorScheme: string = 'Blue-Gold'; + + ngAfterViewInit(): void { + this.draw(); + } + + resetView(): void{ + this.isDragging = false; + this.dragStartX = 0; + this.dragStartY = 0; + this.config.offsetX = -0.5; + this.config.offsetY = 0; + this.config.zoom = 1; + } + + onAlgorithmChange(): void { + this.config.algorithm = this.selectedAlgorithm as any; + this.resetView(); + this.draw(); + } + + onColorChanged(): void { + this.config.colorScheme = this.selectedColorScheme as any; + this.draw(); + } + + onIterationChanged(): void { + this.config.maxIterations = Math.max(Math.min(this.currentIteration, MAX_ITERATION), MIN_ITERATION ); + this.draw(); + } + + private draw(): void { + const canvas = this.canvasRef.nativeElement; + const ctx = canvas.getContext('2d'); + if (ctx) { + this.fractalService.draw(ctx, this.config); + } + } + + //movement + onMouseDown(event: MouseEvent): void { + this.isDragging = true; + this.dragStartX = event.clientX; + this.dragStartY = event.clientY; + } + + onMouseUp(): void { + this.isDragging = false; + } + + onMouseMove(event: MouseEvent): void { + if (!this.isDragging) return; + + const deltaX = event.clientX - this.dragStartX; + const deltaY = event.clientY - this.dragStartY; + + const reScale = 4 / (this.config.width * this.config.zoom); + const imScale = 4 / (this.config.height * this.config.zoom); + + this.config.offsetX -= deltaX * reScale; + this.config.offsetY -= deltaY * imScale; + + this.dragStartX = event.clientX; + this.dragStartY = event.clientY; + + this.draw(); + } + + onWheel(event: WheelEvent): void { + event.preventDefault(); + + const zoomFactor = 1.1; + const zoomIn = event.deltaY < 0; + + const rect = this.canvasRef.nativeElement.getBoundingClientRect(); + const mouseX = event.clientX - rect.left; + const mouseY = event.clientY - rect.top; + + const reScale = 4 / (this.config.width * this.config.zoom); + const imScale = 4 / (this.config.height * this.config.zoom); + + const mouseRe = (mouseX - this.config.width / 2) * reScale + this.config.offsetX; + const mouseIm = (mouseY - this.config.height / 2) * imScale + this.config.offsetY; + + if (zoomIn) { + this.config.zoom *= zoomFactor; + } else { + this.config.zoom /= zoomFactor; + } + + const newReScale = 4 / (this.config.width * this.config.zoom); + const newImScale = 4 / (this.config.height * this.config.zoom); + + this.config.offsetX = mouseRe - (mouseX - this.config.width / 2) * newReScale; + this.config.offsetY = mouseIm - (mouseY - this.config.height / 2) * newImScale; + + this.draw(); + } + + protected readonly MIN_ITERATION = MIN_ITERATION; + protected readonly MAX_ITERATION = MAX_ITERATION; + protected readonly FRACTAL_COLOR_SCHEMES = FRACTAL_COLOR_SCHEMES; +} diff --git a/src/app/pages/algorithms/fractal/fractal.model.ts b/src/app/pages/algorithms/fractal/fractal.model.ts new file mode 100644 index 0000000..52f541f --- /dev/null +++ b/src/app/pages/algorithms/fractal/fractal.model.ts @@ -0,0 +1,33 @@ +export interface FractalConfig { + algorithm: 'Mandelbrot' | 'Julia' | 'Burning Ship' | 'Newton'; + width: number; + height: number; + maxIterations: number; + zoom: number; + offsetX: number; + offsetY: number; + cReal?: number; + cImag?: number; + colorScheme: FractalColorScheme; +} + +export const FRACTAL_COLOR_SCHEMES = [ + 'Blue-Gold', + 'Fire', + 'Rainbow', + 'Greyscale', +] as const; + +export type FractalColorScheme = typeof FRACTAL_COLOR_SCHEMES[number]; +export class ComplexNumber { + constructor(public re: number, public im: number) {} + + add(other: ComplexNumber): ComplexNumber { + return new ComplexNumber(this.re + other.re, this.im + other.im); + } + // Für Newton brauchen wir später auch Multiplikation und Division +} + +export const DEFAULT_ITERATION = 100; +export const MIN_ITERATION = 20; +export const MAX_ITERATION = 1000; diff --git a/src/app/pages/algorithms/fractal/service/fractal.service.ts b/src/app/pages/algorithms/fractal/service/fractal.service.ts new file mode 100644 index 0000000..967855b --- /dev/null +++ b/src/app/pages/algorithms/fractal/service/fractal.service.ts @@ -0,0 +1,256 @@ +import { Injectable } from '@angular/core'; +import {FractalColorScheme, FractalConfig} from '../fractal.model'; + +@Injectable({ + providedIn: 'root' +}) +export class FractalService { + + private currentPalette: Uint8ClampedArray = new Uint8ClampedArray(0); + private lastScheme: FractalColorScheme | null = null; + private lastMaxIter: number = 0; + + draw(ctx: CanvasRenderingContext2D, config: FractalConfig): void { + const width = config.width; + const height = config.height; + + this.updateColorPalette(config); + + const imageData = ctx.createImageData(width, height); + const data = imageData.data; + + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { + + const re = (x - width / 2) * (4 / width / config.zoom) + config.offsetX; + const im = (y - height / 2) * (4 / height / config.zoom) + config.offsetY; + + let iterations = 0; + const pixelIndex = (y * width + x) * 4; + switch (config.algorithm) { + case 'Mandelbrot': + iterations = this.calculateMandelbrot(re, im, config.maxIterations); + this.colorizePixel(data, pixelIndex, iterations); + break; + case 'Julia': + { const cRe = config.cReal ?? -0.7; + const cIm = config.cImag ?? 0.27015; + iterations = this.calculateJulia(re, im, cRe, cIm, config.maxIterations); + this.colorizePixel(data, pixelIndex, iterations); + break; } + case 'Burning Ship': + iterations = this.calculateBurningShip(re, im, config.maxIterations); + this.colorizePixel(data, pixelIndex, iterations); + break; + case 'Newton': + this.handleNewtonFractal(re, im, pixelIndex, config, data); + break; + } + } + } + + ctx.putImageData(imageData, 0, 0); + } + + private handleNewtonFractal(re: number, im: number, pixelIndex:number, config: FractalConfig, data: Uint8ClampedArray) { + + const result = this.calculateNewton(re, im, config.maxIterations); + if (result === config.maxIterations) { + data[pixelIndex] = 0; + data[pixelIndex + 1] = 0; + data[pixelIndex + 2] = 0; + } else if (result >= 2000) { + const light = 255 - (result - 2000) * 10; + data[pixelIndex] = 0; + data[pixelIndex + 1] = 0; + data[pixelIndex + 2] = Math.max(0, light); + } else if (result >= 1000) { + const light = 255 - (result - 1000) * 10; + data[pixelIndex] = 0; + data[pixelIndex + 1] = Math.max(0, light); + data[pixelIndex + 2] = 0; + } else { + const light = 255 - result * 10; + data[pixelIndex] = Math.max(0, light); + data[pixelIndex + 1] = 0; + data[pixelIndex + 2] = 0; + } + data[pixelIndex + 3] = 255; + } + + private updateColorPalette(config: FractalConfig) { + if (this.lastScheme !== config.colorScheme || this.lastMaxIter !== config.maxIterations) { + this.currentPalette = this.generatePalette(config.colorScheme, config.maxIterations); + this.lastScheme = config.colorScheme; + this.lastMaxIter = config.maxIterations; + } + } + + private calculateMandelbrot(cRe: number, cIm: number, maxIter: number): number { + let zRe = 0; + let zIm = 0; + let n = 0; + + while (zRe * zRe + zIm * zIm <= 4 && n < maxIter) { + const zReNew = zRe * zRe - zIm * zIm + cRe; + zIm = 2 * zRe * zIm + cIm; + zRe = zReNew; + n++; + } + return n; + } + + private calculateJulia(zRe: number, zIm: number, cRe: number, cIm: number, maxIter: number): number { + let n = 0; + while (zRe * zRe + zIm * zIm <= 4 && n < maxIter) { + const zReNew = zRe * zRe - zIm * zIm + cRe; + zIm = 2 * zRe * zIm + cIm; + zRe = zReNew; + n++; + } + return n; + } + + private calculateBurningShip(cRe: number, cIm: number, maxIter: number): number { + let zRe = 0; + let zIm = 0; + let n = 0; + + while (zRe * zRe + zIm * zIm <= 4 && n < maxIter) { + const zReAbs = Math.abs(zRe); + const zImAbs = Math.abs(zIm); + + const zReNew = zReAbs * zReAbs - zImAbs * zImAbs + cRe; + zIm = 2 * zReAbs * zImAbs + cIm; + zRe = zReNew; + n++; + } + return n; + } + + private colorizePixel(data: Uint8ClampedArray, pixelIndex: number, iterations: number): void { + const paletteIndex = iterations * 4; + data[pixelIndex] = this.currentPalette[paletteIndex]; // R + data[pixelIndex + 1] = this.currentPalette[paletteIndex + 1]; // G + data[pixelIndex + 2] = this.currentPalette[paletteIndex + 2]; // B + data[pixelIndex + 3] = 255; + } + + // z^3 - 1 + // 1. 1 + 0i (right) + // 2. -0.5 + 0.866i (upper left) + // 3. -0.5 - 0.866i (lower left) + private calculateNewton(x0: number, y0: number, maxIter: number): number { + let x = x0; + let y = y0; + const tolerance = 0.000001; + + for (let i = 0; i < maxIter; i++) { + const x2 = x * x; + const y2 = y * y; + + if (x2 + y2 < 0.0000001) return maxIter; + + const oldX = x; + const oldY = y; + + const z3Re = x*x*x - 3*x*y*y; + const z3Im = 3*x*x*y - y*y*y; + + const fRe = z3Re - 1; + const fIm = z3Im; + + const fPrimeRe = 3 * (x2 - y2); + const fPrimeIm = 3 * (2 * x * y); + + const divDenom = fPrimeRe * fPrimeRe + fPrimeIm * fPrimeIm; + if (divDenom === 0) return maxIter; + + const divRe = (fRe * fPrimeRe + fIm * fPrimeIm) / divDenom; + const divIm = (fIm * fPrimeRe - fRe * fPrimeIm) / divDenom; + + x = oldX - divRe; + y = oldY - divIm; + + if ((x - 1)*(x - 1) + y*y < tolerance) return i; + + if ((x + 0.5)*(x + 0.5) + (y - 0.866)*(y - 0.866) < tolerance) return i + 1000; + + if ((x + 0.5)*(x + 0.5) + (y + 0.866)*(y + 0.866) < tolerance) return i + 2000; + } + + return maxIter; + } + + // --- Paletten-Generator --- + private generatePalette(scheme: FractalColorScheme, maxIter: number): Uint8ClampedArray { + const palette = new Uint8ClampedArray((maxIter + 1) * 4); + + for (let i = 0; i <= maxIter; i++) { + let r = 0, g = 0, b = 0; + + if (i === maxIter) { + r = 0; g = 0; b = 0; + } else { + const t = i / maxIter; + + switch (scheme) { + case 'Greyscale': + r = g = b = t * 255; + break; + + case 'Blue-Gold': + r = 9 * (1 - t) * t * t * t * 255; + g = 15 * (1 - t) * (1 - t) * t * t * 255; + b = 8.5 * (1 - t) * (1 - t) * (1 - t) * t * 255; + r = Math.min(255, r * 2); + g = Math.min(255, g * 2); + b = Math.min(255, b * 4) + 40; + break; + + case 'Fire': + if (i === maxIter) { + r = 0; g = 0; b = 0; + } else { + const t = Math.sqrt(i / maxIter); + + r = t * 255 * 2; + g = (t - 0.3) * 255 * 3; + b = (t - 0.6) * 255 * 6; + } + break; + + case 'Rainbow': + { const hue = (i * 5) % 360; + const rgb = this.hsvToRgb(hue, 1, 1); + r = rgb[0]; g = rgb[1]; b = rgb[2]; + break; } + } + } + + const index = i * 4; + palette[index] = Math.min(255, Math.max(0, r)); + palette[index + 1] = Math.min(255, Math.max(0, g)); + palette[index + 2] = Math.min(255, Math.max(0, b)); + palette[index + 3] = 255; // Alpha + } + + return palette; + } + + private hsvToRgb(h: number, s: number, v: number): [number, number, number] { + let r = 0, g = 0, b = 0; + const c = v * s; + const x = c * (1 - Math.abs(((h / 60) % 2) - 1)); + const m = v - c; + + if (h >= 0 && h < 60) { r = c; g = x; b = 0; } + else if (h >= 60 && h < 120) { r = x; g = c; b = 0; } + else if (h >= 120 && h < 180) { r = 0; g = c; b = x; } + else if (h >= 180 && h < 240) { r = 0; g = x; b = c; } + else if (h >= 240 && h < 300) { r = x; g = 0; b = c; } + else if (h >= 300 && h < 360) { r = c; g = 0; b = x; } + + return [(r + m) * 255, (g + m) * 255, (b + m) * 255]; + } +} diff --git a/src/app/pages/algorithms/service/algorithms.service.ts b/src/app/pages/algorithms/service/algorithms.service.ts index 8643b1b..dc51478 100644 --- a/src/app/pages/algorithms/service/algorithms.service.ts +++ b/src/app/pages/algorithms/service/algorithms.service.ts @@ -32,6 +32,12 @@ export class AlgorithmsService { title: 'ALGORITHM.LABYRINTH.TITLE', description: 'ALGORITHM.LABYRINTH.DESCRIPTION', routerLink: RouterConstants.LABYRINTH.LINK + }, + { + id: 'fractal', + title: 'ALGORITHM.FRACTAL.TITLE', + description: 'ALGORITHM.FRACTAL.DESCRIPTION', + routerLink: RouterConstants.FRACTAL.LINK } ]; diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 297d6dc..19264c7 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -377,6 +377,24 @@ "DISCLAIMER_4": "Anwendung: Solche Labyrinthe sind die perfekte Testumgebung für Pfadfindungsalgorithmen wie Dijkstra oder A*." } }, + "FRACTAL": { + "TITLE": "Fraktale", + "ALGORITHM": "Algorithmen", + "COLOR_SCHEME": "Farbschema", + "MAX_ITERATION": "Maximale Auflösung", + "EXPLANATION": { + "TITLE": "Mathematische Kunst", + "MANDELBROT_EXPLANATION": "basiert auf der iterativen Formel 'z_{n+1} = z_n^2 + c'. Sie prüft für jeden Punkt in der komplexen Ebene, ob die Zahlenfolge stabil bleibt oder ins Unendliche entkommt. Vorteil: Gilt als 'Apfelmännchen' und Mutter der Fraktale. Sie bietet eine unendliche Vielfalt an selbstähnlichen Strukturen, in die man ewig hineinzoomen kann.", + "JULIA_EXPLANATION": "nutzt dieselbe Formel wie Mandelbrot, fixiert jedoch den Parameter 'c' und variiert den Startwert. Je nach Wahl von 'c' entstehen filigrane, wolkenartige Gebilde oder zusammenhanglose 'Staubwolken'. Vorteil: Ermöglicht eine enorme ästhetische Varianz, da jede Koordinate der Mandelbrot-Menge ein völlig eigenes, einzigartiges Julia-Fraktal erzeugt.", + "NEWTON_EXPLANATION": "entsteht durch die Visualisierung des Newton-Verfahrens zur Nullstellen-Suche einer komplexen Funktion. Jeder Pixel wird danach eingefärbt, zu welcher Nullstelle der Algorithmus konvergiert. Vorteil: Erzeugt faszinierende, sternförmige Symmetrien und komplexe Grenzen, an denen sich die Einzugsgebiete der Nullstellen auf chaotische Weise treffen.", + "BURNING_SHIP_EXPLANATION": "ist eine Variation des Mandelbrots, bei der vor jedem Iterationsschritt der Absolutbetrag der Real- und Imaginärteile genommen wird: '(|Re(z)| + i|Im(z)|)^2 + c'. Vorteil: Erzeugt eine markante, asymmetrische Struktur, die einem brennenden Schiff mit Segeln ähnelt. Das Fraktal wirkt düsterer und 'mechanischer' als die klassischen Mengen.", + "DISCLAIMER": "Alle diese Fraktale basieren auf dem Prinzip der Iteration und dem Chaos-Effekt. Das bedeutet für deine Visualisierung:", + "DISCLAIMER_1": "Unendliche Tiefe: Egal wie weit du hineinzoomst, es erscheinen immer neue, komplexe Strukturen, die dem Ganzen oft ähneln (Selbstähnlichkeit).", + "DISCLAIMER_2": "Fluchtzeit-Algorithmus: Die Farben geben meist an, wie schnell eine Folge einen bestimmten Schwellenwert überschreitet – je schneller, desto 'heißer' oder heller die Farbe.", + "DISCLAIMER_3": "Komplexe Zahlen: Die Berechnung findet nicht in einem normalen Koordinatensystem statt, sondern in der komplexen Ebene mit realen und imaginären Anteilen.", + "DISCLAIMER_4": "Rechenintensität: Da für jeden Pixel hunderte Berechnungen durchgeführt werden, sind Fraktale ein klassischer Benchmark für die Performance von Grafikprozessoren (GPUs)." + } + }, "ALGORITHM": { "TITLE": "Algorithmen", "PATHFINDING": { @@ -395,6 +413,10 @@ "TITLE": "Labyrinth-Erzeugung", "DESCRIPTION": "Visualisierung verschiedener Laybrinth-Erzeugungs-Algorithmen." }, + "FRACTAL": { + "TITLE": "Fraktale", + "DESCRIPTION": "Visualisierung von komplexe, geometrische Mustern, die sich selbst in immer kleineren Maßstäben ähneln (Selbstähnlichkeit)." + }, "NOTE": "HINWEIS", "GRID_HEIGHT": "Höhe", "GRID_WIDTH": "Beite" diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 91bb333..f7acef4 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -376,6 +376,25 @@ "DISCLAIMER_4": "Application: Such labyrinths are the perfect test environment for pathfinding algorithms such as Dijkstra or A*." } }, + "FRACTAL": { + "TITLE": "Fractals", + "ALGORITHM": "Algorithms", + "COLOR_SCHEME": "Color Scheme", + "MAX_ITERATION": "Max. Resolution", + "EXPLANATION": { + "TITLE": "Mathematical Art", + "MANDELBROT_EXPLANATION": "is based on the iterative formula 'z_{n+1} = z_n^2 + c'. It checks for every point in the complex plane whether the sequence remains stable or escapes to infinity. Advantage: Known as the 'Apple Man', it is the mother of all fractals, offering infinite variety and self-similar structures to zoom into forever.", + "JULIA_EXPLANATION": "uses the same formula as Mandelbrot but fixes the parameter 'c' and varies the starting value. Depending on the choice of 'c', it creates delicate, cloud-like structures or disconnected 'dust'. Advantage: Allows for immense aesthetic variance, as every coordinate in the Mandelbrot set produces its own unique Julia fractal.", + "NEWTON_EXPLANATION": "is created by visualizing Newton's method for finding roots of a complex function. Each pixel is colored based on which root the algorithm converges to. Advantage: Produces fascinating star-shaped symmetries and complex boundaries where the attraction basins of the roots meet in a chaotic dance.", + "BURNING_SHIP_EXPLANATION": "is a variation of the Mandelbrot set where the absolute values of the real and imaginary parts are taken before each iteration: '(|Re(z)| + i|Im(z)|)^2 + c'. Advantage: Generates a striking, asymmetrical structure resembling a ship on fire. It feels more 'mechanical' and darker compared to the classical sets.", + "DISCLAIMER": "All these fractals are based on the principle of iteration and the butterfly effect. This means for your visualization:", + "DISCLAIMER_1": "Infinite Depth: No matter how far you zoom in, new complex structures appear that often resemble the whole (self-similarity).", + "DISCLAIMER_2": "Escape-Time Algorithm: Colors usually represent how quickly a sequence exceeds a certain threshold—the faster it escapes, the 'hotter' or brighter the color.", + "DISCLAIMER_3": "Complex Numbers: Calculations don't happen in a standard coordinate system, but in the complex plane using real and imaginary components.", + "DISCLAIMER_4": "Computational Load: Since hundreds of calculations are performed for every single pixel, fractals are a classic benchmark for GPU and processor performance." + } + + }, "ALGORITHM": { "TITLE": "Algorithms", "PATHFINDING": { @@ -394,6 +413,10 @@ "TITLE": "Maze Generation", "DESCRIPTION": "Visualizing various maze generation algorithms." }, + "FRACTAL": { + "TITLE": "Fractals", + "DESCRIPTION": "Visualisation of complex geometric patterns that resemble each other on increasingly smaller scales (self-similarity)." + }, "NOTE": "Note", "GRID_HEIGHT": "Height", "GRID_WIDTH": "Width"