Excluded the rendering in an own component

This commit is contained in:
lobo committed 2026-02-12 09:13:35 +01:00
1 parent ea15e66c50
commit cc6997e732
5 files changed
+187 -93

No files matched your search

@@ -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; }
@@ -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
}
});
}
}