Changed 2d fractals to webgl for more performance
This commit is contained in:
@@ -4,52 +4,36 @@
|
|||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<app-information [algorithmInformation]="algoInformation"/>
|
<app-information [algorithmInformation]="algoInformation"/>
|
||||||
<div class="controls-panel">
|
<div class="controls-container">
|
||||||
<mat-form-field appearance="fill">
|
<div class="controls-panel">
|
||||||
<mat-label>{{ 'FRACTAL.ALGORITHM' | translate }}</mat-label>
|
<mat-form-field appearance="fill">
|
||||||
<mat-select [(ngModel)]="selectedAlgorithm"
|
<mat-label>{{ 'FRACTAL.ALGORITHM' | translate }}</mat-label>
|
||||||
(selectionChange)="onAlgorithmChange()">
|
<mat-select [value]="'Mandelbrot'" (selectionChange)="onAlgorithmChange($event.value)">
|
||||||
@for (algo of algoInformation.entries; track algo.name) {
|
<mat-option value="Mandelbrot">Mandelbrot</mat-option>
|
||||||
<mat-option [value]="algo.name">{{ algo.name }}</mat-option>
|
<mat-option value="Julia">Julia</mat-option>
|
||||||
}
|
<mat-option value="Burning Ship">Burning Ship</mat-option>
|
||||||
</mat-select>
|
<mat-option value="Newton">Newton</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field appearance="fill">
|
||||||
|
<mat-label>{{ 'FRACTAL.COLOR_SCHEME' | translate }}</mat-label>
|
||||||
|
<mat-select [value]="'Blue-Gold'" (selectionChange)="onColorChanged($event.value)">
|
||||||
|
<mat-option value="Blue-Gold">Blue-Gold</mat-option>
|
||||||
|
<mat-option value="Greyscale">Greyscale</mat-option>
|
||||||
|
<mat-option value="Fire">Fire</mat-option>
|
||||||
|
<mat-option value="Rainbow">Rainbow</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
</mat-form-field>
|
<button mat-raised-button color="primary" (click)="onReset()">
|
||||||
<mat-form-field appearance="fill">
|
<mat-icon>undo</mat-icon> {{ 'FRACTAL.RESET' | translate }}
|
||||||
<mat-label>{{ 'FRACTAL.COLOR_SCHEME' | translate }}</mat-label>
|
</button>
|
||||||
<mat-select [(ngModel)]="selectedColorScheme"
|
</div>
|
||||||
(selectionChange)="onColorChanged()"
|
|
||||||
[disabled]="selectedAlgorithm === 'Newton'">
|
|
||||||
@for (name of FRACTAL_COLOR_SCHEMES; track name) {
|
|
||||||
<mat-option [value]="name">{{ name }}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>{{ 'FRACTAL.MAX_ITERATION' | translate }}</mat-label>
|
|
||||||
<input
|
|
||||||
matInput
|
|
||||||
type="number"
|
|
||||||
[disabled]="selectedAlgorithm === 'Newton'"
|
|
||||||
[min]="MIN_ITERATION"
|
|
||||||
[max]="MAX_ITERATION"
|
|
||||||
[(ngModel)]="currentIteration"
|
|
||||||
(blur)="onIterationChanged()"
|
|
||||||
(keyup.enter)="onIterationChanged()"
|
|
||||||
/>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
<div class="canvas-container">
|
|
||||||
<canvas #fractalCanvas
|
|
||||||
width="1000"
|
|
||||||
height="1000"
|
|
||||||
(mousedown)="onMouseDown($event)"
|
|
||||||
(mousemove)="onMouseMove($event)"
|
|
||||||
(mouseup)="onMouseUp()"
|
|
||||||
(mouseleave)="onMouseUp()"
|
|
||||||
(wheel)="onWheel($event)">
|
|
||||||
</canvas>
|
|
||||||
</div>
|
</div>
|
||||||
|
<app-babylon-canvas
|
||||||
|
[config]="renderConfig"
|
||||||
|
[renderCallback]="onRender"
|
||||||
|
(sceneReady)="onSceneReady($event)"
|
||||||
|
/>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import {AfterViewInit, Component, ElementRef, inject, ViewChild} from '@angular/core';
|
import { Component} from '@angular/core';
|
||||||
import {Information} from '../information/information';
|
import {Information} from '../information/information';
|
||||||
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
|
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
|
||||||
import {TranslatePipe} from '@ngx-translate/core';
|
import {TranslatePipe} from '@ngx-translate/core';
|
||||||
import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
import {MatFormField, MatLabel} from '@angular/material/input';
|
||||||
import {MatOption} from '@angular/material/core';
|
import {MatOption} from '@angular/material/core';
|
||||||
import {MatSelect} from '@angular/material/select';
|
import {MatSelect} from '@angular/material/select';
|
||||||
import {AlgorithmInformation} from '../information/information.models';
|
import {AlgorithmInformation} from '../information/information.models';
|
||||||
import {UrlConstants} from '../../../constants/UrlConstants';
|
import {UrlConstants} from '../../../constants/UrlConstants';
|
||||||
import {FormsModule} from '@angular/forms';
|
import {FormsModule} from '@angular/forms';
|
||||||
import {FractalService} from './service/fractal.service';
|
import {BabylonCanvas, RenderCallback, RenderConfig} from '../../../shared/rendering/canvas/babylon-canvas.component';
|
||||||
import {DEFAULT_ITERATION, FRACTAL_COLOR_SCHEMES, FractalConfig, MAX_ITERATION, MIN_ITERATION} from './fractal.model';
|
import {FRACTAL2D_FRAGMENT, FRACTAL2D_VERTEX} from './fractal.shader';
|
||||||
|
import {PointerEventTypes, PointerInfo, Scene, ShaderMaterial, Vector2} from '@babylonjs/core';
|
||||||
|
import {MatButton} from '@angular/material/button';
|
||||||
|
import {MatIcon} from '@angular/material/icon';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-fractal',
|
selector: 'app-fractal',
|
||||||
@@ -25,12 +28,14 @@ import {DEFAULT_ITERATION, FRACTAL_COLOR_SCHEMES, FractalConfig, MAX_ITERATION,
|
|||||||
MatOption,
|
MatOption,
|
||||||
MatSelect,
|
MatSelect,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
MatInput
|
BabylonCanvas,
|
||||||
|
MatButton,
|
||||||
|
MatIcon
|
||||||
],
|
],
|
||||||
templateUrl: './fractal.component.html',
|
templateUrl: './fractal.component.html',
|
||||||
styleUrl: './fractal.component.scss',
|
styleUrl: './fractal.component.scss',
|
||||||
})
|
})
|
||||||
export class FractalComponent implements AfterViewInit {
|
export class FractalComponent {
|
||||||
algoInformation: AlgorithmInformation = {
|
algoInformation: AlgorithmInformation = {
|
||||||
title: 'FRACTAL.EXPLANATION.TITLE',
|
title: 'FRACTAL.EXPLANATION.TITLE',
|
||||||
entries: [
|
entries: [
|
||||||
@@ -56,7 +61,7 @@ export class FractalComponent implements AfterViewInit {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
disclaimer: 'FRACTAL.EXPLANATION.DISCLAIMER',
|
disclaimer: 'FRACTAL.EXPLANATION.DISCLAIMER',
|
||||||
disclaimerBottom: '',
|
disclaimerBottom: 'FRACTAL.EXPLANATION.DISCLAIMER_BOTTOM',
|
||||||
disclaimerListEntry: [
|
disclaimerListEntry: [
|
||||||
'FRACTAL.EXPLANATION.DISCLAIMER_1',
|
'FRACTAL.EXPLANATION.DISCLAIMER_1',
|
||||||
'FRACTAL.EXPLANATION.DISCLAIMER_2',
|
'FRACTAL.EXPLANATION.DISCLAIMER_2',
|
||||||
@@ -64,126 +69,163 @@ export class FractalComponent implements AfterViewInit {
|
|||||||
'FRACTAL.EXPLANATION.DISCLAIMER_4'
|
'FRACTAL.EXPLANATION.DISCLAIMER_4'
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
private readonly fractalService = inject(FractalService);
|
|
||||||
|
|
||||||
config: FractalConfig = {
|
renderConfig: RenderConfig = {
|
||||||
algorithm: 'Mandelbrot',
|
mode: '2D',
|
||||||
width: 1000,
|
initialViewSize: 100,
|
||||||
height: 1000,
|
vertexShader: FRACTAL2D_VERTEX,
|
||||||
maxIterations: DEFAULT_ITERATION,
|
fragmentShader: FRACTAL2D_FRAGMENT,
|
||||||
zoom: 1,
|
uniformNames: ["worldViewProjection", "time", "targetPosition","center", "zoom", "maxIterations", "algorithm", "colorScheme", "juliaC"]
|
||||||
offsetX: -0.5,
|
|
||||||
offsetY: 0,
|
|
||||||
colorScheme: FRACTAL_COLOR_SCHEMES[0]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- State ---
|
||||||
private isDragging = false;
|
private isDragging = false;
|
||||||
private dragStartX = 0;
|
private dragStartPoint: { x: number, y: number } | null = null;
|
||||||
private dragStartY = 0;
|
|
||||||
|
|
||||||
@ViewChild('fractalCanvas') canvasRef!: ElementRef<HTMLCanvasElement>;
|
selectedAlgorithm = 0;
|
||||||
|
selectedColorScheme = 0;
|
||||||
|
|
||||||
selectedAlgorithm: string = this.config.algorithm;
|
zoom = 0.2;
|
||||||
currentIteration: number = this.config.maxIterations;
|
offsetX = 0.0;
|
||||||
selectedColorScheme: string = 'Blue-Gold';
|
offsetY = 0.0;
|
||||||
|
maxIterations = 100;
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
juliaReal = -0.7;
|
||||||
this.draw();
|
juliaImag = 0.27015;
|
||||||
}
|
|
||||||
|
|
||||||
resetView(): void{
|
// --- Render Callback ---
|
||||||
this.isDragging = false;
|
onRender: RenderCallback = (material: ShaderMaterial) => {
|
||||||
this.dragStartX = 0;
|
material.setVector2("center", new Vector2(this.offsetX, this.offsetY));
|
||||||
this.dragStartY = 0;
|
material.setFloat("zoom", this.zoom);
|
||||||
this.config.offsetX = -0.5;
|
material.setInt("maxIterations", this.maxIterations);
|
||||||
this.config.offsetY = 0;
|
material.setInt("algorithm", this.selectedAlgorithm);
|
||||||
this.config.zoom = 1;
|
material.setInt("colorScheme", this.selectedColorScheme);
|
||||||
}
|
material.setVector2("juliaC", new Vector2(this.juliaReal, this.juliaImag));
|
||||||
|
};
|
||||||
|
|
||||||
onAlgorithmChange(): void {
|
onAlgorithmChange(algoName: string): void {
|
||||||
this.config.algorithm = this.selectedAlgorithm as any;
|
this.onReset()
|
||||||
this.resetView();
|
|
||||||
this.draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
onColorChanged(): void {
|
switch(algoName) {
|
||||||
this.config.colorScheme = this.selectedColorScheme as any;
|
case 'Mandelbrot': this.selectedAlgorithm = 0; break;
|
||||||
this.draw();
|
case 'Julia': this.selectedAlgorithm = 1; break;
|
||||||
}
|
case 'Burning Ship': this.selectedAlgorithm = 2; break;
|
||||||
|
case 'Newton': this.selectedAlgorithm = 3; break;
|
||||||
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
|
onColorChanged(schemeName: string): void {
|
||||||
onMouseDown(event: MouseEvent): void {
|
switch(schemeName) {
|
||||||
|
case 'Blue-Gold': this.selectedColorScheme = 0; break;
|
||||||
|
case 'Greyscale': this.selectedColorScheme = 1; break;
|
||||||
|
case 'Fire': this.selectedColorScheme = 2; break;
|
||||||
|
case 'Rainbow': this.selectedColorScheme = 3; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onReset(): void {
|
||||||
|
this.zoom = 0.2;
|
||||||
|
this.offsetX = 0.0;
|
||||||
|
this.offsetY = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSceneReady(scene: Scene): void {
|
||||||
|
scene.onPointerObservable.add((pointerInfo) => {
|
||||||
|
switch (pointerInfo.type) {
|
||||||
|
|
||||||
|
case PointerEventTypes.POINTERDOWN:
|
||||||
|
this.onPointerDown(pointerInfo);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PointerEventTypes.POINTERUP:
|
||||||
|
this.onPointerUp();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PointerEventTypes.POINTERMOVE:
|
||||||
|
this.onPointerMove(pointerInfo);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PointerEventTypes.POINTERWHEEL:
|
||||||
|
this.onPointerWheel(pointerInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPointerDown(info: PointerInfo): void {
|
||||||
|
if (info.event.button !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.isDragging = true;
|
this.isDragging = true;
|
||||||
this.dragStartX = event.clientX;
|
this.dragStartPoint = { x: info.event.clientX, y: info.event.clientY };
|
||||||
this.dragStartY = event.clientY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseUp(): void {
|
private onPointerUp(): void {
|
||||||
this.isDragging = false;
|
this.isDragging = false;
|
||||||
|
this.dragStartPoint = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseMove(event: MouseEvent): void {
|
private onPointerMove(info: PointerInfo): void {
|
||||||
if (!this.isDragging) return;
|
if (!this.isDragging || !this.dragStartPoint) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const deltaX = event.clientX - this.dragStartX;
|
const event = info.event as PointerEvent;
|
||||||
const deltaY = event.clientY - this.dragStartY;
|
|
||||||
|
|
||||||
const reScale = 4 / (this.config.width * this.config.zoom);
|
const deltaX = event.clientX - this.dragStartPoint.x;
|
||||||
const imScale = 4 / (this.config.height * this.config.zoom);
|
const deltaY = event.clientY - this.dragStartPoint.y;
|
||||||
|
|
||||||
this.config.offsetX -= deltaX * reScale;
|
const element = event.target as HTMLElement;
|
||||||
this.config.offsetY -= deltaY * imScale;
|
const height = element.clientHeight;
|
||||||
|
|
||||||
this.dragStartX = event.clientX;
|
const scaleFactor = 1 / (height * this.zoom);
|
||||||
this.dragStartY = event.clientY;
|
|
||||||
|
|
||||||
this.draw();
|
this.offsetX += deltaX * scaleFactor;
|
||||||
|
this.offsetY += deltaY * scaleFactor;
|
||||||
|
|
||||||
|
this.dragStartPoint = { x: event.clientX, y: event.clientY };
|
||||||
}
|
}
|
||||||
|
|
||||||
onWheel(event: WheelEvent): void {
|
private onPointerWheel(info: PointerInfo): void {
|
||||||
|
const event = info.event as WheelEvent;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
const element = event.target as HTMLElement;
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
|
||||||
|
const mouseXPixels = -(event.clientX - rect.left - rect.width / 2);
|
||||||
|
const mouseYPixels = -(event.clientY - rect.top - rect.height / 2);
|
||||||
|
|
||||||
|
const mouseXView = mouseXPixels / rect.height;
|
||||||
|
const mouseYView = mouseYPixels / rect.height;
|
||||||
|
|
||||||
|
const mouseXWorld = mouseXView / this.zoom + this.offsetX;
|
||||||
|
const mouseYWorld = mouseYView / this.zoom + this.offsetY;
|
||||||
|
|
||||||
const zoomFactor = 1.1;
|
const zoomFactor = 1.1;
|
||||||
const zoomIn = event.deltaY < 0;
|
if (event.deltaY < 0) {
|
||||||
|
this.zoom *= zoomFactor;
|
||||||
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 {
|
} else {
|
||||||
this.config.zoom /= zoomFactor;
|
this.zoom /= zoomFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newReScale = 4 / (this.config.width * this.config.zoom);
|
const optimalIterations = this.getIterationsForZoom(this.zoom);
|
||||||
const newImScale = 4 / (this.config.height * this.config.zoom);
|
this.maxIterations = Math.min(optimalIterations, 3000);
|
||||||
|
|
||||||
this.config.offsetX = mouseRe - (mouseX - this.config.width / 2) * newReScale;
|
this.offsetX = mouseXWorld - mouseXView / this.zoom;
|
||||||
this.config.offsetY = mouseIm - (mouseY - this.config.height / 2) * newImScale;
|
this.offsetY = mouseYWorld - mouseYView / this.zoom;
|
||||||
|
|
||||||
this.draw();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly MIN_ITERATION = MIN_ITERATION;
|
private getIterationsForZoom(zoom: number): number {
|
||||||
protected readonly MAX_ITERATION = MAX_ITERATION;
|
const baseIterations = 100;
|
||||||
protected readonly FRACTAL_COLOR_SCHEMES = FRACTAL_COLOR_SCHEMES;
|
const factor = 200;
|
||||||
|
|
||||||
|
if (zoom <= 1) {
|
||||||
|
return baseIterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.floor(baseIterations + Math.log10(zoom) * factor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
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;
|
|
||||||
155
src/app/pages/algorithms/fractal/fractal.shader.ts
Normal file
155
src/app/pages/algorithms/fractal/fractal.shader.ts
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
export const FRACTAL2D_VERTEX = `
|
||||||
|
precision highp float;
|
||||||
|
attribute vec3 position;
|
||||||
|
attribute vec2 uv;
|
||||||
|
uniform mat4 worldViewProjection;
|
||||||
|
varying vec2 vUV;
|
||||||
|
void main() {
|
||||||
|
gl_Position = worldViewProjection * vec4(position, 1.0);
|
||||||
|
vUV = uv;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const FRACTAL2D_FRAGMENT = `
|
||||||
|
precision highp float;
|
||||||
|
varying vec2 vUV;
|
||||||
|
|
||||||
|
uniform vec2 resolution;
|
||||||
|
uniform vec2 center; // OffsetX, OffsetY
|
||||||
|
uniform float zoom;
|
||||||
|
uniform int maxIterations;
|
||||||
|
uniform int algorithm; // 0:Mandel, 1:Julia, 2:Ship, 3:Newton
|
||||||
|
uniform int colorScheme; // 0:BlueGold, 1:Greyscale, 2:Fire, 3:Rainbow
|
||||||
|
uniform vec2 juliaC;
|
||||||
|
|
||||||
|
vec3 hsv2rgb(vec3 c) {
|
||||||
|
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
||||||
|
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
||||||
|
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Color Schemes ---
|
||||||
|
vec3 getColor(float t, int iter, int maxIter, int root) {
|
||||||
|
if (iter >= maxIter) return vec3(0.0);
|
||||||
|
|
||||||
|
// special newton coloring
|
||||||
|
if (algorithm == 3) {
|
||||||
|
float val = 1.0 - (float(iter) / 20.0); // Weicheres Shading für Newton
|
||||||
|
val = clamp(val, 0.0, 1.0);
|
||||||
|
if (root == 1) return vec3(val, 0.0, 0.0); // Rot
|
||||||
|
if (root == 2) return vec3(0.0, val, 0.0); // Grün
|
||||||
|
if (root == 3) return vec3(0.0, 0.0, val); // Blau
|
||||||
|
return vec3(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// default color
|
||||||
|
if (colorScheme == 0) { // Blue-Gold
|
||||||
|
return vec3(
|
||||||
|
9.0 * (1.0-t)*t*t*t,
|
||||||
|
15.0 * (1.0-t)*(1.0-t)*t*t,
|
||||||
|
8.5 * (1.0-t)*(1.0-t)*(1.0-t)*t
|
||||||
|
) * 3.0;
|
||||||
|
}
|
||||||
|
if (colorScheme == 1) { // Greyscale
|
||||||
|
return vec3(t);
|
||||||
|
}
|
||||||
|
if (colorScheme == 2) { // Fire
|
||||||
|
float f = sqrt(t);
|
||||||
|
return vec3(f * 2.0, (f - 0.3) * 3.0, (f - 0.6) * 6.0);
|
||||||
|
}
|
||||||
|
if (colorScheme == 3) { // Rainbow
|
||||||
|
return hsv2rgb(vec3(t * 5.0, 1.0, 1.0));
|
||||||
|
}
|
||||||
|
return vec3(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
float aspect = resolution.x / resolution.y;
|
||||||
|
vec2 uv = (vUV - 0.5) * vec2(aspect, 1.0);
|
||||||
|
vec2 c = uv / zoom + center;
|
||||||
|
|
||||||
|
vec2 z = c;
|
||||||
|
// For Julia is c fix, z changes. For Mandel is z=0, c changes.
|
||||||
|
if (algorithm == 1) {
|
||||||
|
z = c;
|
||||||
|
c = juliaC;
|
||||||
|
} else if (algorithm != 3) {
|
||||||
|
z = vec2(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int iter = 0;
|
||||||
|
int root = 0;
|
||||||
|
|
||||||
|
// --- Algorithms ---
|
||||||
|
if (algorithm == 3) { // Newton: z^3 - 1
|
||||||
|
z = c;
|
||||||
|
for(int i=0; i<100; i++) {
|
||||||
|
if (i >= maxIterations) break;
|
||||||
|
|
||||||
|
// z^3 - 1
|
||||||
|
// z_new = z - (z^3 - 1) / (3*z^2)
|
||||||
|
// simplified: z_new = (2*z^3 + 1) / (3*z^2)
|
||||||
|
|
||||||
|
float zx2 = z.x * z.x;
|
||||||
|
float zy2 = z.y * z.y;
|
||||||
|
float denom = 3.0 * (zx2 + zy2) * (zx2 + zy2); // |3z^2|^2 simplified
|
||||||
|
|
||||||
|
// z -= (z^3-1)/(3z^2)
|
||||||
|
|
||||||
|
vec2 z2 = vec2(z.x*z.x - z.y*z.y, 2.0*z.x*z.y);
|
||||||
|
vec2 z3 = vec2(z2.x*z.x - z2.y*z.y, z2.x*z.y + z2.y*z.x);
|
||||||
|
|
||||||
|
vec2 num = z3 - vec2(1.0, 0.0);
|
||||||
|
vec2 den = 3.0 * z2;
|
||||||
|
|
||||||
|
// Division Complex: (a+bi)/(c+di) = ((ac+bd) + (bc-ad)i) / (c^2+d^2)
|
||||||
|
float d = den.x*den.x + den.y*den.y;
|
||||||
|
if(d < 0.000001) { iter=maxIterations; break; }
|
||||||
|
|
||||||
|
vec2 div = vec2(
|
||||||
|
(num.x*den.x + num.y*den.y)/d,
|
||||||
|
(num.y*den.x - num.x*den.y)/d
|
||||||
|
);
|
||||||
|
|
||||||
|
z -= div;
|
||||||
|
iter++;
|
||||||
|
|
||||||
|
// Roots check
|
||||||
|
// 1. (1, 0)
|
||||||
|
if (distance(z, vec2(1.0, 0.0)) < 0.001) { root = 1; break; }
|
||||||
|
// 2. (-0.5, 0.866)
|
||||||
|
if (distance(z, vec2(-0.5, 0.866)) < 0.001) { root = 2; break; }
|
||||||
|
// 3. (-0.5, -0.866)
|
||||||
|
if (distance(z, vec2(-0.5, -0.866)) < 0.001) { root = 3; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // Mandelbrot (0), Julia (1), Burning Ship (2)
|
||||||
|
for(int i=0; i<2000; i++) {
|
||||||
|
if (i >= maxIterations) break;
|
||||||
|
|
||||||
|
float x2 = z.x * z.x;
|
||||||
|
float y2 = z.y * z.y;
|
||||||
|
|
||||||
|
if (x2 + y2 > 4.0) {
|
||||||
|
iter = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
iter = i;
|
||||||
|
|
||||||
|
if (algorithm == 2) { // Burning Ship
|
||||||
|
z.y = abs(z.y);
|
||||||
|
z.x = abs(z.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// z = z^2 + c
|
||||||
|
float nextX = x2 - y2 + c.x;
|
||||||
|
z.y = 2.0 * z.x * z.y + c.y;
|
||||||
|
z.x = nextX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float t = float(iter) / float(maxIterations);
|
||||||
|
vec3 color = getColor(t, iter, maxIterations, root);
|
||||||
|
gl_FragColor = vec4(color, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -1,256 +0,0 @@
|
|||||||
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<ArrayBuffer>) {
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<app-babylon-canvas
|
<app-babylon-canvas
|
||||||
[config]="fractalConfig"
|
[config]="fractalConfig"
|
||||||
[renderCallback]="onRender">
|
[renderCallback]="onRender"
|
||||||
</app-babylon-canvas>
|
/>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.canvas-container { width: 100%; height: 1000px; }
|
|
||||||
canvas { width: 100%; height: 100%; touch-action: none; border-width: 0; border-color: transparent; border-style: hidden; }
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {ArcRotateCamera, Camera, ShaderMaterial} from '@babylonjs/core';
|
import {ArcRotateCamera, Camera, ShaderMaterial} from '@babylonjs/core';
|
||||||
import {MANDELBULB_FRAGMENT, MANDELBULB_VERTEX} from './fractal.shader';
|
import {MANDELBULB_FRAGMENT, MANDELBULB_VERTEX} from './fractal3d.shader';
|
||||||
import {Information} from '../information/information';
|
import {Information} from '../information/information';
|
||||||
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
|
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
|
||||||
import {TranslatePipe} from '@ngx-translate/core';
|
import {TranslatePipe} from '@ngx-translate/core';
|
||||||
@@ -52,9 +52,10 @@ export class Fractal3dComponent {
|
|||||||
|
|
||||||
fractalConfig: RenderConfig = {
|
fractalConfig: RenderConfig = {
|
||||||
mode: '3D',
|
mode: '3D',
|
||||||
|
initialViewSize: 4,
|
||||||
vertexShader: MANDELBULB_VERTEX,
|
vertexShader: MANDELBULB_VERTEX,
|
||||||
fragmentShader: MANDELBULB_FRAGMENT,
|
fragmentShader: MANDELBULB_FRAGMENT,
|
||||||
uniformNames: ["power", "fractalType"]
|
uniformNames: ["time", "power", "fractalType"]
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly fractalPower = 8;
|
private readonly fractalPower = 8;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import {AfterViewInit, Component, ElementRef, inject, Input, NgZone, OnDestroy, ViewChild} from '@angular/core';
|
import {AfterViewInit, Component, ElementRef, EventEmitter, inject, Input, NgZone, OnDestroy, Output, ViewChild} from '@angular/core';
|
||||||
import {ArcRotateCamera, Camera, Engine, MeshBuilder, Scene, ShaderMaterial, Vector2, Vector3} from '@babylonjs/core';
|
import {ArcRotateCamera, Camera, Engine, MeshBuilder, Scene, ShaderMaterial, Vector2, Vector3} from '@babylonjs/core';
|
||||||
|
|
||||||
export interface RenderConfig {
|
export interface RenderConfig {
|
||||||
mode: '2D' | '3D';
|
mode: '2D' | '3D';
|
||||||
|
initialViewSize: number;
|
||||||
vertexShader: string;
|
vertexShader: string;
|
||||||
fragmentShader: string;
|
fragmentShader: string;
|
||||||
uniformNames: string[];
|
uniformNames: string[];
|
||||||
@@ -24,6 +25,8 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy {
|
|||||||
@Input({ required: true }) config!: RenderConfig;
|
@Input({ required: true }) config!: RenderConfig;
|
||||||
@Input() renderCallback?: RenderCallback;
|
@Input() renderCallback?: RenderCallback;
|
||||||
|
|
||||||
|
@Output() sceneReady = new EventEmitter<Scene>();
|
||||||
|
|
||||||
private engine!: Engine;
|
private engine!: Engine;
|
||||||
private scene!: Scene;
|
private scene!: Scene;
|
||||||
private shaderMaterial!: ShaderMaterial;
|
private shaderMaterial!: ShaderMaterial;
|
||||||
@@ -57,6 +60,7 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy {
|
|||||||
canvas.addEventListener('wheel', (evt: WheelEvent) => evt.preventDefault(), { passive: false });
|
canvas.addEventListener('wheel', (evt: WheelEvent) => evt.preventDefault(), { passive: false });
|
||||||
this.createShaderMaterial();
|
this.createShaderMaterial();
|
||||||
this.createFullScreenRect();
|
this.createFullScreenRect();
|
||||||
|
this.sceneReady.emit(this.scene);
|
||||||
this.addRenderLoop(canvas);
|
this.addRenderLoop(canvas);
|
||||||
this.addResizeHandler();
|
this.addResizeHandler();
|
||||||
}
|
}
|
||||||
@@ -75,13 +79,12 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy {
|
|||||||
cam.mode = Camera.ORTHOGRAPHIC_CAMERA;
|
cam.mode = Camera.ORTHOGRAPHIC_CAMERA;
|
||||||
|
|
||||||
const aspect = canvas.width / canvas.height;
|
const aspect = canvas.width / canvas.height;
|
||||||
const viewSize = 10;
|
const viewSize = this.config?.initialViewSize ?? 10;
|
||||||
cam.orthoLeft = -viewSize * aspect / 2;
|
cam.orthoLeft = -viewSize * aspect / 2;
|
||||||
cam.orthoRight = viewSize * aspect / 2;
|
cam.orthoRight = viewSize * aspect / 2;
|
||||||
cam.orthoTop = viewSize / 2;
|
cam.orthoTop = viewSize / 2;
|
||||||
cam.orthoBottom = -viewSize / 2;
|
cam.orthoBottom = -viewSize / 2;
|
||||||
|
|
||||||
cam.attachControl(canvas, true, false);
|
|
||||||
this.camera = cam;
|
this.camera = cam;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,12 +95,13 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy {
|
|||||||
cam.maxZ = 100;
|
cam.maxZ = 100;
|
||||||
cam.lowerRadiusLimit = 1.5;
|
cam.lowerRadiusLimit = 1.5;
|
||||||
cam.upperRadiusLimit = 20;
|
cam.upperRadiusLimit = 20;
|
||||||
|
cam.radius = this.config?.initialViewSize ?? 1;
|
||||||
cam.attachControl(canvas, true);
|
cam.attachControl(canvas, true);
|
||||||
this.camera = cam;
|
this.camera = cam;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createFullScreenRect() {
|
private createFullScreenRect() {
|
||||||
const plane = MeshBuilder.CreatePlane("plane", {size: 100}, this.scene);
|
const plane = MeshBuilder.CreatePlane("plane", {size: 110}, this.scene);
|
||||||
|
|
||||||
if (this.config.mode === '3D') {
|
if (this.config.mode === '3D') {
|
||||||
plane.parent = this.camera;
|
plane.parent = this.camera;
|
||||||
@@ -120,7 +124,7 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributes: ["position", "uv"],
|
attributes: ["position", "uv"],
|
||||||
uniforms: ["time", "resolution", "cameraPosition", "targetPosition", ...this.config.uniformNames]
|
uniforms: ["resolution", "cameraPosition", ...this.config.uniformNames]
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
this.shaderMaterial.disableDepthWrite = true;
|
this.shaderMaterial.disableDepthWrite = true;
|
||||||
|
|||||||
@@ -380,6 +380,7 @@
|
|||||||
"FRACTAL": {
|
"FRACTAL": {
|
||||||
"TITLE": "Fraktale",
|
"TITLE": "Fraktale",
|
||||||
"ALGORITHM": "Algorithmen",
|
"ALGORITHM": "Algorithmen",
|
||||||
|
"RESET": "Reset",
|
||||||
"COLOR_SCHEME": "Farbschema",
|
"COLOR_SCHEME": "Farbschema",
|
||||||
"MAX_ITERATION": "Maximale Auflösung",
|
"MAX_ITERATION": "Maximale Auflösung",
|
||||||
"EXPLANATION": {
|
"EXPLANATION": {
|
||||||
@@ -392,7 +393,8 @@
|
|||||||
"DISCLAIMER_1": "Unendliche Tiefe: Egal wie weit du hineinzoomst, es erscheinen immer neue, komplexe Strukturen, die dem Ganzen oft ähneln (Selbstähnlichkeit).",
|
"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_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_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)."
|
"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).",
|
||||||
|
"DISCLAIMER_BOTTOM": "Grafikkarten rechnen standardmäßig mit 32-Bit Fließkommazahlen (float). Diese haben nur etwa 7 Stellen Genauigkeit. Bei sehr hohem Zoom (> 100.000) ist der Unterschied zwischen zwei Pixeln so winzig, dass die Grafikkarte ihn nicht mehr darstellen kann. Sie berechnet für 10 Pixel nebeneinander exakt denselben Wert -> Du siehst Blöcke oder Treppenstufen."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"FRACTAL3D": {
|
"FRACTAL3D": {
|
||||||
|
|||||||
@@ -379,6 +379,7 @@
|
|||||||
"FRACTAL": {
|
"FRACTAL": {
|
||||||
"TITLE": "Fractals",
|
"TITLE": "Fractals",
|
||||||
"ALGORITHM": "Algorithms",
|
"ALGORITHM": "Algorithms",
|
||||||
|
"RESET": "Reset",
|
||||||
"COLOR_SCHEME": "Color Scheme",
|
"COLOR_SCHEME": "Color Scheme",
|
||||||
"MAX_ITERATION": "Max. Resolution",
|
"MAX_ITERATION": "Max. Resolution",
|
||||||
"EXPLANATION": {
|
"EXPLANATION": {
|
||||||
@@ -391,9 +392,9 @@
|
|||||||
"DISCLAIMER_1": "Infinite Depth: No matter how far you zoom in, new complex structures appear that often resemble the whole (self-similarity).",
|
"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_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_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."
|
"DISCLAIMER_4": "Computational Load: Since hundreds of calculations are performed for every single pixel, fractals are a classic benchmark for GPU and processor performance.",
|
||||||
|
"DISCLAIMER_BOTTOM": "Graphics cards calculate with 32-bit floating point numbers (float) by default. These only have about 7 digits of accuracy. At very high zoom levels (> 100,000), the difference between two pixels is so tiny that the graphics card can no longer display it. It calculates exactly the same value for 10 pixels next to each other -> you see blocks or stair steps."
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
"FRACTAL3D": {
|
"FRACTAL3D": {
|
||||||
"TITLE": "3D Fractals",
|
"TITLE": "3D Fractals",
|
||||||
|
|||||||
Reference in New Issue
Block a user