Excluded the rendering in an own component
This commit is contained in:
@@ -11,8 +11,9 @@
|
|||||||
<button matButton="filled" (click)="onFractalTypeChange(2)">{{ 'FRACTAL3D.JULIA' | translate }}</button>
|
<button matButton="filled" (click)="onFractalTypeChange(2)">{{ 'FRACTAL3D.JULIA' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="canvas-container">
|
<app-babylon-canvas
|
||||||
<canvas #renderCanvas></canvas>
|
[config]="fractalConfig"
|
||||||
</div>
|
[renderCallback]="onRender">
|
||||||
|
</app-babylon-canvas>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {AfterViewInit, Component, ElementRef, inject, NgZone, OnDestroy, ViewChild} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {ArcRotateCamera, Engine, MeshBuilder, Scene, ShaderMaterial, Vector2, Vector3} from '@babylonjs/core';
|
import {ArcRotateCamera, Camera, ShaderMaterial} from '@babylonjs/core';
|
||||||
import {MANDELBULB_FRAGMENT, MANDELBULB_VERTEX} from './fractal.shader';
|
import {MANDELBULB_FRAGMENT, MANDELBULB_VERTEX} from './fractal.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';
|
||||||
@@ -7,6 +7,7 @@ import {TranslatePipe} from '@ngx-translate/core';
|
|||||||
import {AlgorithmInformation} from '../information/information.models';
|
import {AlgorithmInformation} from '../information/information.models';
|
||||||
import {UrlConstants} from '../../../constants/UrlConstants';
|
import {UrlConstants} from '../../../constants/UrlConstants';
|
||||||
import {MatButton} from '@angular/material/button';
|
import {MatButton} from '@angular/material/button';
|
||||||
|
import {BabylonCanvas, RenderCallback, RenderConfig} from '../../../shared/rendering/canvas/babylon-canvas.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-fractal3d',
|
selector: 'app-fractal3d',
|
||||||
@@ -17,15 +18,13 @@ import {MatButton} from '@angular/material/button';
|
|||||||
MatCardHeader,
|
MatCardHeader,
|
||||||
MatCardTitle,
|
MatCardTitle,
|
||||||
TranslatePipe,
|
TranslatePipe,
|
||||||
MatButton
|
MatButton,
|
||||||
|
BabylonCanvas
|
||||||
],
|
],
|
||||||
templateUrl: './fractal3d.component.html',
|
templateUrl: './fractal3d.component.html',
|
||||||
styleUrl: './fractal3d.component.scss',
|
styleUrl: './fractal3d.component.scss',
|
||||||
})
|
})
|
||||||
export class Fractal3dComponent implements AfterViewInit, OnDestroy {
|
export class Fractal3dComponent {
|
||||||
readonly ngZone = inject(NgZone);
|
|
||||||
|
|
||||||
@ViewChild('renderCanvas') canvasRef!: ElementRef<HTMLCanvasElement>;
|
|
||||||
|
|
||||||
algoInformation: AlgorithmInformation = {
|
algoInformation: AlgorithmInformation = {
|
||||||
title: 'FRACTAL3D.EXPLANATION.TITLE',
|
title: 'FRACTAL3D.EXPLANATION.TITLE',
|
||||||
@@ -51,100 +50,35 @@ export class Fractal3dComponent implements AfterViewInit, OnDestroy {
|
|||||||
disclaimerListEntry: ['FRACTAL3D.EXPLANATION.DISCLAIMER_1', 'FRACTAL3D.EXPLANATION.DISCLAIMER_2', 'FRACTAL3D.EXPLANATION.DISCLAIMER_3', 'FRACTAL3D.EXPLANATION.DISCLAIMER_4']
|
disclaimerListEntry: ['FRACTAL3D.EXPLANATION.DISCLAIMER_1', 'FRACTAL3D.EXPLANATION.DISCLAIMER_2', 'FRACTAL3D.EXPLANATION.DISCLAIMER_3', 'FRACTAL3D.EXPLANATION.DISCLAIMER_4']
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fractalConfig: RenderConfig = {
|
||||||
|
mode: '3D',
|
||||||
|
vertexShader: MANDELBULB_VERTEX,
|
||||||
|
fragmentShader: MANDELBULB_FRAGMENT,
|
||||||
|
uniformNames: ["power", "fractalType"]
|
||||||
|
};
|
||||||
|
|
||||||
private readonly fractalPower = 8;
|
private readonly fractalPower = 8;
|
||||||
private engine!: Engine;
|
|
||||||
private scene!: Scene;
|
|
||||||
private shaderMaterial!: ShaderMaterial;
|
|
||||||
private time = 0;
|
private time = 0;
|
||||||
private triggerCamUpdate = false;
|
private oldType = 0;
|
||||||
private cameraPosition: number = 3.5;
|
|
||||||
public currentFractalType = 0;
|
public currentFractalType = 0;
|
||||||
|
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
onRender: RenderCallback = (material: ShaderMaterial, camera: Camera) => {
|
||||||
this.ngZone.runOutsideAngular(() => {
|
this.time += 0.005;
|
||||||
this.initBabylon();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private initBabylon(): void {
|
if (this.oldType != this.currentFractalType && camera instanceof ArcRotateCamera) {
|
||||||
const canvas = this.canvasRef.nativeElement;
|
this.oldType = this.currentFractalType;
|
||||||
this.engine = new Engine(canvas, true);
|
camera.radius = this.currentFractalType == 1 ? 15 : 4;
|
||||||
this.scene = new Scene(this.engine);
|
}
|
||||||
|
|
||||||
const camera = new ArcRotateCamera("Camera", 0, Math.PI / 2, 4, Vector3.Zero(), this.scene);
|
material.setFloat("time", this.time);
|
||||||
camera.wheelPrecision = 100;
|
material.setFloat("power", this.fractalPower);
|
||||||
camera.minZ = 0.1;
|
material.setInt("fractalType", this.currentFractalType);
|
||||||
camera.maxZ = 100;
|
};
|
||||||
camera.lowerRadiusLimit = 1.5;
|
|
||||||
camera.upperRadiusLimit = 20;
|
|
||||||
camera.attachControl(this.canvasRef.nativeElement, true);
|
|
||||||
|
|
||||||
canvas.addEventListener('wheel', (evt: WheelEvent) => {
|
|
||||||
evt.preventDefault();
|
|
||||||
}, { passive: false });
|
|
||||||
|
|
||||||
const plane = MeshBuilder.CreatePlane("plane", { size: 10 }, this.scene);
|
|
||||||
plane.parent = camera;
|
|
||||||
plane.position.z = 1;
|
|
||||||
plane.alwaysSelectAsActiveMesh = true;
|
|
||||||
|
|
||||||
this.shaderMaterial = new ShaderMaterial(
|
|
||||||
"mandelbulbShader",
|
|
||||||
this.scene,
|
|
||||||
{
|
|
||||||
vertexSource: MANDELBULB_VERTEX,
|
|
||||||
fragmentSource: MANDELBULB_FRAGMENT
|
|
||||||
},
|
|
||||||
{
|
|
||||||
attributes: ["position", "uv"],
|
|
||||||
uniforms: ["time", "resolution", "cameraPosition", "targetPosition", "power", "fractalType"]
|
|
||||||
}
|
|
||||||
);
|
|
||||||
this.shaderMaterial.disableDepthWrite = true;
|
|
||||||
this.shaderMaterial.backFaceCulling = false;
|
|
||||||
|
|
||||||
plane.material = this.shaderMaterial;
|
|
||||||
|
|
||||||
this.engine.runRenderLoop(() => {
|
|
||||||
this.time += 0.005;
|
|
||||||
|
|
||||||
if (this.triggerCamUpdate)
|
|
||||||
{
|
|
||||||
this.triggerCamUpdate = false;
|
|
||||||
camera.radius = this.cameraPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.shaderMaterial) {
|
|
||||||
this.shaderMaterial.setFloat("time", this.time);
|
|
||||||
this.shaderMaterial.setVector2("resolution", new Vector2(canvas.width, canvas.height));
|
|
||||||
this.shaderMaterial.setVector3("cameraPosition", camera.position);
|
|
||||||
this.shaderMaterial.setVector3("targetPosition", camera.target);
|
|
||||||
this.shaderMaterial.setFloat("power", this.fractalPower);
|
|
||||||
this.shaderMaterial.setInt("fractalType", this.currentFractalType);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scene.render();
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('resize', () => this.engine.resize());
|
|
||||||
}
|
|
||||||
|
|
||||||
onFractalTypeChange(type: number): void {
|
onFractalTypeChange(type: number): void {
|
||||||
this.currentFractalType = type;
|
this.currentFractalType = type;
|
||||||
if (type === 0 ||type === 2)
|
|
||||||
{
|
|
||||||
this.cameraPosition = 4;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.cameraPosition = 15;
|
|
||||||
}
|
|
||||||
this.triggerCamUpdate = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
if (this.engine) {
|
|
||||||
this.engine.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<div class="canvas-container">
|
||||||
|
<canvas #renderCanvas></canvas>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.canvas-container { width: 100%; height: 1000px; }
|
||||||
|
canvas { width: 100%; height: 100%; touch-action: none; border-width: 0; border-color: transparent; border-style: hidden; }
|
||||||
154
src/app/shared/rendering/canvas/babylon-canvas.component.ts
Normal file
154
src/app/shared/rendering/canvas/babylon-canvas.component.ts
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
import {AfterViewInit, Component, ElementRef, inject, Input, NgZone, OnDestroy, ViewChild} from '@angular/core';
|
||||||
|
import {ArcRotateCamera, Camera, Engine, MeshBuilder, Scene, ShaderMaterial, Vector2, Vector3} from '@babylonjs/core';
|
||||||
|
|
||||||
|
export interface RenderConfig {
|
||||||
|
mode: '2D' | '3D';
|
||||||
|
vertexShader: string;
|
||||||
|
fragmentShader: string;
|
||||||
|
uniformNames: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RenderCallback = (material: ShaderMaterial, camera: Camera, canvas: HTMLCanvasElement, scene: Scene) => void;
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-babylon-canvas',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './babylon-canvas.component.html',
|
||||||
|
styleUrl: './babylon-canvas.component.scss',
|
||||||
|
})
|
||||||
|
export class BabylonCanvas implements AfterViewInit, OnDestroy {
|
||||||
|
readonly ngZone = inject(NgZone);
|
||||||
|
|
||||||
|
@ViewChild('renderCanvas', { static: true }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
||||||
|
|
||||||
|
@Input({ required: true }) config!: RenderConfig;
|
||||||
|
@Input() renderCallback?: RenderCallback;
|
||||||
|
|
||||||
|
private engine!: Engine;
|
||||||
|
private scene!: Scene;
|
||||||
|
private shaderMaterial!: ShaderMaterial;
|
||||||
|
private camera!: Camera;
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
this.ngZone.runOutsideAngular(() => {
|
||||||
|
this.initBabylon();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
//if something changes during runtime, new materials are necessary ans needs maybe build here
|
||||||
|
}*/
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
const canvas = this.canvasRef?.nativeElement;
|
||||||
|
if (canvas) {
|
||||||
|
//remove listener if needed
|
||||||
|
}
|
||||||
|
if (this.engine) {
|
||||||
|
this.engine.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private initBabylon(): void {
|
||||||
|
const canvas = this.canvasRef.nativeElement;
|
||||||
|
this.engine = new Engine(canvas, true);
|
||||||
|
this.scene = new Scene(this.engine);
|
||||||
|
this.setupCamera(canvas);
|
||||||
|
canvas.addEventListener('wheel', (evt: WheelEvent) => evt.preventDefault(), { passive: false });
|
||||||
|
this.createShaderMaterial();
|
||||||
|
this.createFullScreenRect();
|
||||||
|
this.addRenderLoop(canvas);
|
||||||
|
this.addResizeHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupCamera(canvas: HTMLCanvasElement) {
|
||||||
|
if (this.config.mode === '3D') {
|
||||||
|
this.setup3dCamera(canvas);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setup2dCamera(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setup2dCamera(canvas: HTMLCanvasElement) {
|
||||||
|
const cam = new ArcRotateCamera("Camera2D", -Math.PI / 2, Math.PI / 2, 10, Vector3.Zero(), this.scene);
|
||||||
|
cam.mode = Camera.ORTHOGRAPHIC_CAMERA;
|
||||||
|
|
||||||
|
const aspect = canvas.width / canvas.height;
|
||||||
|
const viewSize = 10;
|
||||||
|
cam.orthoLeft = -viewSize * aspect / 2;
|
||||||
|
cam.orthoRight = viewSize * aspect / 2;
|
||||||
|
cam.orthoTop = viewSize / 2;
|
||||||
|
cam.orthoBottom = -viewSize / 2;
|
||||||
|
|
||||||
|
cam.attachControl(canvas, true, false);
|
||||||
|
this.camera = cam;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setup3dCamera(canvas: HTMLCanvasElement) {
|
||||||
|
const cam = new ArcRotateCamera("Camera", 0, Math.PI / 2, 4, Vector3.Zero(), this.scene);
|
||||||
|
cam.wheelPrecision = 100;
|
||||||
|
cam.minZ = 0.1;
|
||||||
|
cam.maxZ = 100;
|
||||||
|
cam.lowerRadiusLimit = 1.5;
|
||||||
|
cam.upperRadiusLimit = 20;
|
||||||
|
cam.attachControl(canvas, true);
|
||||||
|
this.camera = cam;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createFullScreenRect() {
|
||||||
|
const plane = MeshBuilder.CreatePlane("plane", {size: 100}, this.scene);
|
||||||
|
|
||||||
|
if (this.config.mode === '3D') {
|
||||||
|
plane.parent = this.camera;
|
||||||
|
plane.position.z = 1;
|
||||||
|
} else {
|
||||||
|
plane.lookAt(this.camera.position);
|
||||||
|
}
|
||||||
|
plane.alwaysSelectAsActiveMesh = true;
|
||||||
|
|
||||||
|
plane.material = this.shaderMaterial;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createShaderMaterial() {
|
||||||
|
this.shaderMaterial = new ShaderMaterial(
|
||||||
|
"shaderMaterial",
|
||||||
|
this.scene,
|
||||||
|
{
|
||||||
|
vertexSource: this.config.vertexShader,
|
||||||
|
fragmentSource: this.config.fragmentShader
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributes: ["position", "uv"],
|
||||||
|
uniforms: ["time", "resolution", "cameraPosition", "targetPosition", ...this.config.uniformNames]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.shaderMaterial.disableDepthWrite = true;
|
||||||
|
this.shaderMaterial.backFaceCulling = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addRenderLoop(canvas: HTMLCanvasElement) {
|
||||||
|
this.engine.runRenderLoop(() => {
|
||||||
|
|
||||||
|
// callback call to call specific uniforms
|
||||||
|
if (this.renderCallback) {
|
||||||
|
this.renderCallback(this.shaderMaterial, this.camera, canvas, this.scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
// default uniforms which maybe each scene has
|
||||||
|
this.shaderMaterial.setVector2("resolution", new Vector2(canvas.width, canvas.height));
|
||||||
|
this.shaderMaterial.setVector3("cameraPosition", this.camera.position);
|
||||||
|
|
||||||
|
this.scene.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private addResizeHandler() {
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.engine.resize();
|
||||||
|
if (this.config.mode === '2D' && this.camera instanceof ArcRotateCamera && this.camera.mode === Camera.ORTHOGRAPHIC_CAMERA) {
|
||||||
|
//maybe update the aspect ratio here
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user