Fixed some styling issues
This commit is contained in:
1 parent
f656206691
commit
ed0e370e9d
11 files changed
+20
-12
No files matched your search
@@ -11,4 +11,5 @@ canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-width: 0;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="canvas-container">
|
||||
<canvas #renderCanvas></canvas>
|
||||
</div>
|
||||
Whitespace-only changes.
@@ -0,0 +1,197 @@
|
||||
import {AfterViewInit, Component, ElementRef, EventEmitter, inject, Input, NgZone, OnDestroy, Output, ViewChild} from '@angular/core';
|
||||
import {ArcRotateCamera, Camera, MeshBuilder, Scene, ShaderLanguage, ShaderMaterial, Vector2, Vector3, WebGPUEngine} from '@babylonjs/core';
|
||||
|
||||
export interface RenderConfig {
|
||||
mode: '2D' | '3D';
|
||||
shaderLanguage?: number; //0 GLSL, 1 WGSL
|
||||
initialViewSize: number;
|
||||
vertexShader?: string;
|
||||
fragmentShader?: string;
|
||||
uniformNames?: string[];
|
||||
uniformBufferNames?: string[];
|
||||
}
|
||||
|
||||
export type RenderCallback = (material: ShaderMaterial, camera: Camera, canvas: HTMLCanvasElement, scene: Scene) => void;
|
||||
|
||||
export interface SceneEventData {
|
||||
scene: Scene;
|
||||
engine: WebGPUEngine;
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
@Output() sceneReady = new EventEmitter<SceneEventData>();
|
||||
@Output() sceneResized = new EventEmitter<SceneEventData>();
|
||||
|
||||
private engine!: WebGPUEngine;
|
||||
private scene!: Scene;
|
||||
private shaderMaterial!: ShaderMaterial;
|
||||
private camera!: Camera;
|
||||
|
||||
//Listener
|
||||
private readonly resizeHandler = () => this.handleResize();
|
||||
private readonly wheelHandler = (evt: WheelEvent) => evt.preventDefault();
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.initBabylon().then(() => { console.log("Engine initialized"); });
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
window.removeEventListener('resize', this.resizeHandler);
|
||||
|
||||
const canvas = this.canvasRef?.nativeElement;
|
||||
if (canvas) {
|
||||
canvas.removeEventListener('wheel', this.wheelHandler);
|
||||
}
|
||||
|
||||
if (this.engine) {
|
||||
this.engine.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private async initBabylon(): Promise<void> {
|
||||
const canvas = this.canvasRef.nativeElement;
|
||||
this.engine = new WebGPUEngine(canvas);
|
||||
await this.engine.initAsync().then(() => {
|
||||
this.scene = new Scene(this.engine);
|
||||
this.setupCamera(canvas);
|
||||
this.addListener(canvas);
|
||||
this.createShaderMaterial();
|
||||
this.createFullScreenRect();
|
||||
this.sceneReady.emit({
|
||||
scene: this.scene,
|
||||
engine: this.engine
|
||||
});
|
||||
this.addRenderLoop(canvas);
|
||||
});
|
||||
}
|
||||
|
||||
private addListener(canvas: HTMLCanvasElement) {
|
||||
canvas.addEventListener('wheel', this.wheelHandler, {passive: false});
|
||||
window.addEventListener('resize', this.resizeHandler);
|
||||
}
|
||||
|
||||
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 = this.config?.initialViewSize ?? 10;
|
||||
cam.orthoLeft = -viewSize * aspect / 2;
|
||||
cam.orthoRight = viewSize * aspect / 2;
|
||||
cam.orthoTop = viewSize / 2;
|
||||
cam.orthoBottom = -viewSize / 2;
|
||||
|
||||
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.pinchPrecision = 200;
|
||||
cam.minZ = 0.1;
|
||||
cam.maxZ = 100;
|
||||
cam.lowerRadiusLimit = 1.5;
|
||||
cam.upperRadiusLimit = 20;
|
||||
cam.radius = this.config?.initialViewSize ?? 1;
|
||||
cam.attachControl(canvas, true);
|
||||
this.camera = cam;
|
||||
}
|
||||
|
||||
private createFullScreenRect() {
|
||||
if (!this.config.vertexShader || !this.config.fragmentShader) {
|
||||
return;
|
||||
}
|
||||
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() {
|
||||
if (!this.config.vertexShader || !this.config.fragmentShader || !this.config.uniformNames) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.shaderMaterial = new ShaderMaterial(
|
||||
"shaderMaterial",
|
||||
this.scene,
|
||||
{
|
||||
vertexSource: this.config.vertexShader,
|
||||
fragmentSource: this.config.fragmentShader
|
||||
},
|
||||
{
|
||||
attributes: ["position", "uv"],
|
||||
uniforms: ["resolution", "cameraPosition", ...this.config.uniformNames],
|
||||
uniformBuffers: this.config.uniformBufferNames ?? [],
|
||||
shaderLanguage: this.config.shaderLanguage ?? ShaderLanguage.GLSL
|
||||
}
|
||||
);
|
||||
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
|
||||
if (this.shaderMaterial) {
|
||||
this.shaderMaterial.setVector2("resolution", new Vector2(canvas.width, canvas.height));
|
||||
this.shaderMaterial.setVector3("cameraPosition", this.camera.position);
|
||||
}
|
||||
|
||||
this.scene.render();
|
||||
});
|
||||
}
|
||||
|
||||
private handleResize(): void {
|
||||
if (this.engine) {
|
||||
this.engine.resize();
|
||||
}
|
||||
|
||||
if (this.config.mode === '2D' && this.camera instanceof ArcRotateCamera) {
|
||||
const viewSize = this.config?.initialViewSize ?? 10;
|
||||
this.camera.orthoLeft = -viewSize / 2;
|
||||
this.camera.orthoRight = viewSize / 2;
|
||||
this.camera.orthoTop = viewSize / 2;
|
||||
this.camera.orthoBottom = -viewSize / 2;
|
||||
}
|
||||
|
||||
this.sceneResized?.emit({
|
||||
scene: this.scene,
|
||||
engine: this.engine
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user