First image of fractal

Next the nasty stuff like movement and ui :-D
This commit is contained in:
2026-02-11 08:31:27 +01:00
parent 12ebbb09ce
commit ba3dc4d928
11 changed files with 133 additions and 33 deletions

15
package-lock.json generated
View File

@@ -17,9 +17,9 @@
"@angular/material": "~21.1.0",
"@angular/platform-browser": "~21.1.0",
"@angular/router": "~21.1.0",
"@babylonjs/core": "^8.50.5",
"@ngx-translate/core": "~17.0.0",
"@ngx-translate/http-loader": "~17.0.0",
"babylonjs": "^8.50.5",
"inquirer": "^13.2.2",
"rxjs": "~7.8.2",
"swiper": "~12.1.0",
@@ -1150,6 +1150,12 @@
"node": ">=6.9.0"
}
},
"node_modules/@babylonjs/core": {
"version": "8.50.5",
"resolved": "https://registry.npmjs.org/@babylonjs/core/-/core-8.50.5.tgz",
"integrity": "sha512-rrHAYJmsZOuvC6wdNhGNgdn9JlMxBhk30YhBWGPSxYnnUD7/n/U9Pb3a8k0lpTHipbCfjJ+J5r6GdXtPEikhEg==",
"license": "Apache-2.0"
},
"node_modules/@emnapi/core": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
@@ -6449,13 +6455,6 @@
}
}
},
"node_modules/babylonjs": {
"version": "8.50.5",
"resolved": "https://registry.npmjs.org/babylonjs/-/babylonjs-8.50.5.tgz",
"integrity": "sha512-r4CvDBY798k0E8FqOrj2pg5If7MJhELACIpQw8r1TT4TXNWNXxlqrNmKtSzxauWFQgankwVbKhe41YA8k+tQMw==",
"hasInstallScript": true,
"license": "Apache-2.0"
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",

View File

@@ -20,9 +20,9 @@
"@angular/material": "~21.1.0",
"@angular/platform-browser": "~21.1.0",
"@angular/router": "~21.1.0",
"@babylonjs/core": "^8.50.5",
"@ngx-translate/core": "~17.0.0",
"@ngx-translate/http-loader": "~17.0.0",
"babylonjs": "^8.50.5",
"inquirer": "^13.2.2",
"rxjs": "~7.8.2",
"swiper": "~12.1.0",

View File

@@ -12,6 +12,7 @@ export const routes: Routes = [
{ 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.FRACTAL.PATH, component: RouterConstants.FRACTAL.COMPONENT}
{ path: RouterConstants.FRACTAL.PATH, component: RouterConstants.FRACTAL.COMPONENT},
{ path: RouterConstants.FRACTAL3d.PATH, component: RouterConstants.FRACTAL3d.COMPONENT}
];

View File

@@ -7,6 +7,7 @@ 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';
import {Fractal3dComponent} from '../pages/algorithms/fractal3d/fractal3d.component';
export class RouterConstants {
@@ -58,6 +59,12 @@ export class RouterConstants {
COMPONENT: FractalComponent
};
static readonly FRACTAL3d = {
PATH: 'algorithms/fractal3d',
LINK: '/algorithms/fractal3d',
COMPONENT: Fractal3dComponent
};
static readonly IMPRINT = {
PATH: 'imprint',
LINK: '/imprint',

View File

@@ -1,14 +1,25 @@
const mandelbulbFragmentShader = `
export const MANDELBULB_VERTEX = /* glsl */`
precision highp float;
attribute vec3 position;
attribute vec2 uv;
varying vec2 vUV;
void main(void) {
gl_Position = vec4(position, 1.0);
vUV = uv;
}
`;
export const MANDELBULB_FRAGMENT = /* glsl */`
precision highp float;
// Uniforms (Werte von der CPU)
// Uniforms
uniform float time;
uniform vec2 resolution;
uniform vec3 cameraPosition;
uniform vec3 targetPosition;
uniform float power; // Die "Potenz" des Fraktals (z.B. 8.0)
uniform float power;
// Hilfsfunktion: Rotation
mat2 rot(float a) {
float s = sin(a), c = cos(a);
return mat2(c, -s, s, c);
@@ -20,26 +31,21 @@ float map(vec3 pos) {
float dr = 1.0;
float r = 0.0;
// Die Fraktal-Iteration
for (int i = 0; i < 8; i++) {
r = length(z);
if (r > 2.0) break;
// Konvertierung in Polarkoordinaten
float theta = acos(z.y / r);
float phi = atan(z.z, z.x);
dr = pow(r, power - 1.0) * power * dr + 1.0;
// Skalieren und Rotieren (Power)
float zr = pow(r, power);
theta = theta * power;
phi = phi * power;
// Zurück zu Kartesischen Koordinaten
z = zr * vec3(sin(theta) * cos(phi), cos(theta), sin(theta) * sin(phi));
// Den ursprünglichen Punkt addieren (wie beim Mandelbrot z = z^2 + c)
z += pos;
}
return 0.5 * log(r) * r / dr;
@@ -51,14 +57,13 @@ float raymarch(vec3 ro, vec3 rd) {
for(int i = 0; i < 100; i++) {
vec3 pos = ro + t * rd;
float d = map(pos);
if(d < 0.001) return t; // Getroffen!
if(t > 10.0) break; // Zu weit weg
if(d < 0.001) return t;
if(t > 10.0) break;
t += d;
}
return -1.0; // Nichts getroffen
return -1.0;
}
// --- Normalenberechnung für Licht ---
vec3 getNormal(vec3 p) {
float d = map(p);
vec2 e = vec2(0.001, 0.0);
@@ -70,10 +75,8 @@ vec3 getNormal(vec3 p) {
}
void main(void) {
// UV Koordinaten zentrieren (-1 bis 1) und Aspekt korrigieren
vec2 uv = (gl_FragCoord.xy - 0.5 * resolution.xy) / resolution.y;
// Kamera Setup (LookAt)
vec3 ro = cameraPosition; // Ray Origin
vec3 ta = targetPosition; // Target LookAt
@@ -83,21 +86,19 @@ void main(void) {
vec3 rd = normalize(fwd + uv.x * right + uv.y * up); // Ray Direction
// Rendern
float t = raymarch(ro, rd);
vec3 color = vec3(0.0); // Hintergrund Schwarz
vec3 color = vec3(0.0);
if(t > 0.0) {
vec3 pos = ro + t * rd;
vec3 nor = getNormal(pos);
// Einfaches Licht (Phong)
//easy phong light
vec3 lightDir = normalize(vec3(1.0, 1.0, -1.0));
float diff = max(dot(nor, lightDir), 0.0);
float amb = 0.2; // Ambient
// Farbe basierend auf Position (gibt diesen Alien-Look)
vec3 baseColor = vec3(0.5) + 0.5 * cos(vec3(0.0, 0.4, 0.8) + length(pos) * 2.0);
color = baseColor * (diff + amb);

View File

@@ -1 +1,3 @@
<p>fractal3d works!</p>
<div class="canvas-container">
<canvas #renderCanvas></canvas>
</div>

View File

@@ -0,0 +1,2 @@
.canvas-container { width: 100%; height: 600px; overflow: hidden; }
canvas { width: 100%; height: 100%; touch-action: none; }

View File

@@ -1,4 +1,6 @@
import { Component } from '@angular/core';
import {AfterViewInit, Component, ElementRef, inject, NgZone, OnDestroy, ViewChild} from '@angular/core';
import {Engine, FreeCamera, MeshBuilder, Scene, ShaderMaterial, Vector2, Vector3} from '@babylonjs/core';
import {MANDELBULB_FRAGMENT, MANDELBULB_VERTEX} from './fractal.shader';
@Component({
selector: 'app-fractal3d',
@@ -6,6 +8,78 @@ import { Component } from '@angular/core';
templateUrl: './fractal3d.component.html',
styleUrl: './fractal3d.component.scss',
})
export class Fractal3dComponent {
export class Fractal3dComponent implements AfterViewInit, OnDestroy {
readonly ngZone = inject(NgZone);
@ViewChild('renderCanvas') canvasRef!: ElementRef<HTMLCanvasElement>;
private engine!: Engine;
private scene!: Scene;
private shaderMaterial!: ShaderMaterial;
private cameraPos = new Vector3(0, 0, -3.5);
private targetPos = new Vector3(0, 0, 0);
private fractalPower = 8.0;
private time = 0;
ngAfterViewInit(): void {
this.ngZone.runOutsideAngular(() => {
this.initBabylon();
});
}
private initBabylon(): void {
const canvas = this.canvasRef.nativeElement;
this.engine = new Engine(canvas, true);
this.scene = new Scene(this.engine);
const camera = new FreeCamera("camera1", new Vector3(0, 0, -1), this.scene);
camera.setTarget(Vector3.Zero());
const plane = MeshBuilder.CreatePlane("plane", { size: 2 }, this.scene);
this.shaderMaterial = new ShaderMaterial(
"mandelbulbShader",
this.scene,
{
vertexSource: MANDELBULB_VERTEX,
fragmentSource: MANDELBULB_FRAGMENT
},
{
attributes: ["position", "uv"],
uniforms: ["time", "resolution", "cameraPosition", "targetPosition", "power"]
}
);
plane.material = this.shaderMaterial;
this.engine.runRenderLoop(() => {
this.time += 0.01;
this.updateShaderUniforms(canvas);
this.scene.render();
});
window.addEventListener('resize', () => this.engine.resize());
}
private updateShaderUniforms(canvas: HTMLCanvasElement): void {
if (!this.shaderMaterial) return;
this.shaderMaterial.setFloat("time", this.time);
this.shaderMaterial.setVector2("resolution", new Vector2(canvas.width, canvas.height));
this.shaderMaterial.setVector3("cameraPosition", this.cameraPos);
this.shaderMaterial.setVector3("targetPosition", this.targetPos);
this.shaderMaterial.setFloat("power", this.fractalPower);
// Optional: Hier könnte man cameraPos basierend auf Mausbewegung ändern (Orbiting)
// z.B.:
// this.cameraPos.x = Math.sin(this.time * 0.5) * 3.5;
// this.cameraPos.z = Math.cos(this.time * 0.5) * 3.5;
}
ngOnDestroy(): void {
if (this.engine) {
this.engine.dispose();
}
}
}

View File

@@ -38,6 +38,12 @@ export class AlgorithmsService {
title: 'ALGORITHM.FRACTAL.TITLE',
description: 'ALGORITHM.FRACTAL.DESCRIPTION',
routerLink: RouterConstants.FRACTAL.LINK
},
{
id: 'fractal3d',
title: 'ALGORITHM.FRACTAL3D.TITLE',
description: 'ALGORITHM.FRACTAL3D.DESCRIPTION',
routerLink: RouterConstants.FRACTAL3d.LINK
}
];

View File

@@ -417,6 +417,10 @@
"TITLE": "Fraktale",
"DESCRIPTION": "Visualisierung von komplexe, geometrische Mustern, die sich selbst in immer kleineren Maßstäben ähneln (Selbstähnlichkeit)."
},
"FRACTAL3D": {
"TITLE": "Fraktale 3D",
"DESCRIPTION": "3D-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"

View File

@@ -417,6 +417,10 @@
"TITLE": "Fractals",
"DESCRIPTION": "Visualisation of complex geometric patterns that resemble each other on increasingly smaller scales (self-similarity)."
},
"FRACTAL3D": {
"TITLE": "Fractals 3D",
"DESCRIPTION": "3D Visualisation of complex geometric patterns that resemble each other on increasingly smaller scales (self-similarity)."
},
"NOTE": "Note",
"GRID_HEIGHT": "Height",
"GRID_WIDTH": "Width"