Skip to content

Commit

Permalink
feat: BackgroundComponent
Browse files Browse the repository at this point in the history
  • Loading branch information
ScarletFlash committed Jul 6, 2024
1 parent 4056996 commit a205f97
Show file tree
Hide file tree
Showing 14 changed files with 400 additions and 7 deletions.
6 changes: 5 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ module.exports = TypeScriptESLint.config(
],
processor: AngularESLint.processInlineTemplates,
rules: {
'@angular-eslint/use-component-view-encapsulation': 'off',
'@angular-eslint/use-injectable-provided-in': 'off',
'@angular-eslint/no-host-metadata-property': 'off',
curly: 'error',
'max-depth': [
'error',
Expand Down Expand Up @@ -234,7 +237,8 @@ module.exports = TypeScriptESLint.config(
files: ['**/*.html'],
extends: [...AngularESLint.configs.templateAll],
rules: {
'@angular-eslint/template/i18n': 'off'
'@angular-eslint/template/i18n': 'off',
'@angular-eslint/template/prefer-self-closing-tags': 'off'
}
}
);
57 changes: 57 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@angular/ssr": "^18.0.0",
"express": "^4.0.0",
"rxjs": "~7.8.0",
"three": "^0.165.0",
"tslib": "^2.0.0",
"zone.js": "~0.14.0"
},
Expand All @@ -37,6 +38,7 @@
"@angular/compiler-cli": "^18.0.0",
"@types/express": "^4.0.0",
"@types/node": "^20.0.0",
"@types/three": "^0.165.0",
"angular-eslint": "18.0.0",
"autoprefixer": "^10.0.0",
"eslint": "^9.0.0",
Expand Down
6 changes: 5 additions & 1 deletion src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
<router-outlet />
<main class="size-full bg-white">
<app-background>
<router-outlet />
</app-background>
</main>
6 changes: 4 additions & 2 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { BackgroundComponent } from './background/background.component';

@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet],
imports: [RouterOutlet, BackgroundComponent],
templateUrl: './app.component.html',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
Expand Down
15 changes: 15 additions & 0 deletions src/app/background/background.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<section class="size-full relative">
<section
#canvas_container
class="size-full bg-black absolute top-0 left-0 right-0 bottom-0 after:block after:absolute after:top-0 after:left-0 after:right-0 after:bottom-0 after:size-full after:bg-[radial-gradient(circle,rgba(0,0,0,0)_50%,rgba(0,0,0,9)_75%,rgba(0,0,0,1)_100%)]"
>
<canvas
#canvas
class="size-full"
></canvas>
</section>

<section class="size-full absolute z-10">
<ng-content></ng-content>
</section>
</section>
54 changes: 54 additions & 0 deletions src/app/background/background.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
afterRender,
ChangeDetectionStrategy,
Component,
HostListener,
ViewChild,
ViewEncapsulation,
type ElementRef
} from '@angular/core';
import type { Dimensions } from '../declarations/dimensions.interface';
import { BackgroundService } from './background.service';

@Component({
selector: 'app-background',
standalone: true,
imports: [],
templateUrl: './background.component.html',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
viewProviders: [BackgroundService]
})
export class BackgroundComponent {
@ViewChild('canvas_container', { static: true })
public canvasContainerRef?: ElementRef<HTMLCanvasElement>;

@ViewChild('canvas', { static: true })
public canvasRef?: ElementRef<HTMLCanvasElement>;

private get desiredCanvasDimensions(): Dimensions {
if (this.canvasContainerRef === undefined) {
throw new Error('Canvas container element not found');
}

return {
widthPx: this.canvasContainerRef.nativeElement.clientWidth,
heightPx: this.canvasContainerRef.nativeElement.clientHeight
};
}

constructor(private readonly backgroundService: BackgroundService) {
afterRender(() => {
if (this.canvasRef === undefined) {
throw new Error('<canvas> is not found');
}

this.backgroundService.attachCanvas(this.canvasRef.nativeElement);
});
}

@HostListener('window:resize')
public onResize(): void {
this.backgroundService.setContainerSize(this.desiredCanvasDimensions);
}
}
23 changes: 23 additions & 0 deletions src/app/background/background.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Injectable, type OnDestroy } from '@angular/core';
import type { Dimensions } from '../declarations/dimensions.interface';
import { SceneContentManager } from './scene/scene-content-manager';

@Injectable()
export class BackgroundService implements OnDestroy {
private readonly sceneContentManager: SceneContentManager = new SceneContentManager();

public attachCanvas(canvas: HTMLCanvasElement): void {
if (typeof window === 'undefined') {
return;
}
this.sceneContentManager.initialize(canvas);
}

public setContainerSize(dimensions: Dimensions): void {
this.sceneContentManager.setContainerSize(dimensions);
}

public ngOnDestroy(): void {
this.sceneContentManager.destroy();
}
}
19 changes: 19 additions & 0 deletions src/app/background/scene/grid-plane.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Mesh, PlaneGeometry } from 'three';
import type { Dimensions } from '../../declarations/dimensions.interface';
import { GridMaterial } from './resourses/grid.material';

export class GridPlane extends Mesh<PlaneGeometry, GridMaterial> {
constructor(dimensions: Dimensions) {
const material: GridMaterial = new GridMaterial(dimensions);
const geometry: PlaneGeometry = new PlaneGeometry(10, 10);
super(geometry, material);
this.rotation.x = -Math.PI / 3;
this.translateZ(1);
}

public dispose(): void {
this.removeFromParent();
this.geometry.dispose();
this.material.dispose();
}
}
77 changes: 77 additions & 0 deletions src/app/background/scene/render-loop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { type Camera, Color, type Scene, WebGLRenderer } from 'three';
import type { Dimensions } from '../../declarations/dimensions.interface';

class NotInitializedError extends Error {
constructor() {
super('WebGLRenderer is not initialized');
}
}

class NotDestroyedError extends Error {
constructor() {
super('WebGLRenderer is not destroyed');
}
}

export class RenderLoop {
private webGLRenderer: WebGLRenderer | null = null;

constructor(
private readonly scene: Scene,
private readonly camera: Camera
) {}

public initialize(canvas: HTMLCanvasElement): void {
if (this.webGLRenderer !== null) {
throw new NotDestroyedError();
}

const webGLRenderer: WebGLRenderer = new WebGLRenderer({
canvas,
antialias: true
});
webGLRenderer.setClearColor(new Color(0x000000), 0);
this.webGLRenderer = webGLRenderer;
}

public destroy(): void {
if (this.webGLRenderer === null) {
return;
}

this.webGLRenderer.dispose();
this.webGLRenderer = null;
}

public setDimensions({ widthPx, heightPx }: Dimensions): void {
if (this.webGLRenderer === null) {
throw new NotInitializedError();
}

this.webGLRenderer.setSize(widthPx, heightPx);
}

public start(onRender?: (timeSinceLastFrameMs?: DOMHighResTimeStamp) => void): void {
if (this.webGLRenderer === null) {
throw new NotInitializedError();
}

const webGLRenderer: WebGLRenderer = this.webGLRenderer;
webGLRenderer.setAnimationLoop((timeSinceLastFrameMs: DOMHighResTimeStamp) => {
webGLRenderer.render(this.scene, this.camera);

if (typeof onRender !== 'function') {
return;
}
onRender(timeSinceLastFrameMs);
});
}

public stop(): void {
if (this.webGLRenderer === null) {
throw new NotInitializedError();
}

this.webGLRenderer.setAnimationLoop(null);
}
}
Loading

0 comments on commit a205f97

Please sign in to comment.