Modularize renderer and plane control functionality into RenderContainerComponent

This commit is contained in:
Teriuihi 2025-06-22 19:23:54 +02:00
parent 9808b5d63d
commit ecd9b3d824
7 changed files with 217 additions and 172 deletions

View File

@ -0,0 +1,41 @@
<div #rendererContainer class="renderer-container">
<div class="plane-controls-overlay">
<button mat-mini-fab color="primary" (click)="togglePlaneLock()"
[matTooltip]="isPlaneLocked ? 'Unlock Plane' : 'Lock Plane'">
<mat-icon>{{ isPlaneLocked ? 'lock' : 'lock_open' }}</mat-icon>
</button>
<div *ngIf="isPlaneLocked" class="plane-orientation-buttons">
<button mat-mini-fab color="warn" (click)="setPlaneOrientation(planeOrientations.VERTICAL_ABOVE)"
[class.active]="currentPlaneOrientation === planeOrientations.VERTICAL_ABOVE"
matTooltip="Vertical Above">
<mat-icon>arrow_upward</mat-icon>
</button>
<button mat-mini-fab color="warn" (click)="setPlaneOrientation(planeOrientations.VERTICAL_BELOW)"
[class.active]="currentPlaneOrientation === planeOrientations.VERTICAL_BELOW"
matTooltip="Vertical Below">
<mat-icon>arrow_downward</mat-icon>
</button>
<button mat-mini-fab color="primary" (click)="setPlaneOrientation(planeOrientations.HORIZONTAL_FRONT)"
[class.active]="currentPlaneOrientation === planeOrientations.HORIZONTAL_FRONT"
matTooltip="Horizontal Front">
<mat-icon>arrow_forward</mat-icon>
</button>
<button mat-mini-fab color="primary" (click)="setPlaneOrientation(planeOrientations.HORIZONTAL_BEHIND)"
[class.active]="currentPlaneOrientation === planeOrientations.HORIZONTAL_BEHIND"
matTooltip="Horizontal Behind">
<mat-icon>arrow_back</mat-icon>
</button>
<button mat-mini-fab color="accent" (click)="setPlaneOrientation(planeOrientations.HORIZONTAL_RIGHT)"
[class.active]="currentPlaneOrientation === planeOrientations.HORIZONTAL_RIGHT"
matTooltip="Horizontal Right">
<mat-icon>arrow_right</mat-icon>
</button>
<button mat-mini-fab color="accent" (click)="setPlaneOrientation(planeOrientations.HORIZONTAL_LEFT)"
[class.active]="currentPlaneOrientation === planeOrientations.HORIZONTAL_LEFT"
matTooltip="Horizontal Left">
<mat-icon>arrow_left</mat-icon>
</button>
</div>
</div>
</div>

View File

@ -0,0 +1,46 @@
.renderer-container {
height: 1000px;
border: 1px solid #ccc;
border-radius: 4px;
overflow: hidden;
background-color: #f0f0f0;
display: flex;
justify-content: center;
align-items: center;
position: relative; /* Added for absolute positioning of overlay */
}
.plane-controls-overlay {
position: absolute;
top: 20px;
right: 20px;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 10px;
z-index: 10;
}
.plane-orientation-buttons {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(3, 1fr);
gap: 10px;
margin-top: 10px;
}
.plane-orientation-buttons button {
opacity: 0.7;
transition: opacity 0.2s, transform 0.2s;
}
.plane-orientation-buttons button:hover {
opacity: 1;
transform: scale(1.1);
}
.plane-orientation-buttons button.active {
opacity: 1;
transform: scale(1.1);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RenderContainerComponent } from './render-container.component';
describe('RenderContainerComponent', () => {
let component: RenderContainerComponent;
let fixture: ComponentFixture<RenderContainerComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RenderContainerComponent]
})
.compileComponents();
fixture = TestBed.createComponent(RenderContainerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,101 @@
import {AfterViewInit, Component, ElementRef, OnDestroy, ViewChild} from '@angular/core';
import {MatMiniFabButton} from '@angular/material/button';
import {NgIf} from '@angular/common';
import {IntersectionPlaneService, PlaneOrientation} from '../../services/intersection-plane.service';
import {MatIcon} from '@angular/material/icon';
import {MatTooltip} from '@angular/material/tooltip';
import {RendererService} from '../../services/renderer.service';
import {PlayerModelService} from '../../services/player-model.service';
import {InputHandlerService} from '../../services/input-handler.service';
@Component({
selector: 'app-render-container',
imports: [
MatIcon,
MatMiniFabButton,
MatTooltip,
NgIf
],
templateUrl: './render-container.component.html',
styleUrl: './render-container.component.scss'
})
export class RenderContainerComponent implements AfterViewInit, OnDestroy {
@ViewChild('rendererContainer') rendererContainer!: ElementRef;
constructor(
private intersectionPlaneService: IntersectionPlaneService,
private playerModelService: PlayerModelService,
private inputHandlerService: InputHandlerService,
private rendererService: RendererService,
) {
}
ngAfterViewInit(): void {
this.initializeScene();
this.animate();
}
/**
* Clean up resources when component is destroyed
*/
ngOnDestroy(): void {
if (this.rendererService.renderer) {
this.inputHandlerService.cleanup(this.rendererService.renderer.domElement);
}
}
/**
* Initialize the 3D scene and all related components
*/
private initializeScene(): void {
this.rendererService.initializeRenderer(this.rendererContainer);
this.playerModelService.createPlayerModel();
this.intersectionPlaneService.createIntersectionPlane();
this.inputHandlerService.initializeInputHandlers(this.rendererService.renderer.domElement);
}
/**
* Animation loop
*/
private animate(): void {
requestAnimationFrame(this.animate.bind(this));
this.intersectionPlaneService.updatePlaneOrientation(this.rendererService.camera);
this.rendererService.render();
}
/**
* Get whether the plane is locked
*/
public get isPlaneLocked(): boolean {
return this.intersectionPlaneService.isPlaneLocked();
}
/**
* Toggle the plane locked state
*/
public togglePlaneLock(): void {
const newLockedState = !this.isPlaneLocked;
this.intersectionPlaneService.setPlaneLocked(newLockedState);
}
/**
* Get the current plane orientation
*/
public get currentPlaneOrientation(): PlaneOrientation {
return this.intersectionPlaneService.getCurrentOrientation();
}
/**
* Set the plane orientation
*/
public setPlaneOrientation(orientation: PlaneOrientation): void {
this.intersectionPlaneService.setPlaneOrientation(orientation);
}
/**
* Get all available plane orientations
*/
public get planeOrientations(): typeof PlaneOrientation {
return PlaneOrientation;
}
}

View File

@ -14,46 +14,7 @@
<app-particle-properties></app-particle-properties>
</div>
<div class="flex middle-column">
<div #rendererContainer class="renderer-container">
<div class="plane-controls-overlay">
<button mat-mini-fab color="primary" (click)="togglePlaneLock()" [matTooltip]="isPlaneLocked ? 'Unlock Plane' : 'Lock Plane'">
<mat-icon>{{ isPlaneLocked ? 'lock' : 'lock_open' }}</mat-icon>
</button>
<div *ngIf="isPlaneLocked" class="plane-orientation-buttons">
<button mat-mini-fab color="warn" (click)="setPlaneOrientation(planeOrientations.VERTICAL_ABOVE)"
[class.active]="currentPlaneOrientation === planeOrientations.VERTICAL_ABOVE"
matTooltip="Vertical Above">
<mat-icon>arrow_upward</mat-icon>
</button>
<button mat-mini-fab color="warn" (click)="setPlaneOrientation(planeOrientations.VERTICAL_BELOW)"
[class.active]="currentPlaneOrientation === planeOrientations.VERTICAL_BELOW"
matTooltip="Vertical Below">
<mat-icon>arrow_downward</mat-icon>
</button>
<button mat-mini-fab color="primary" (click)="setPlaneOrientation(planeOrientations.HORIZONTAL_FRONT)"
[class.active]="currentPlaneOrientation === planeOrientations.HORIZONTAL_FRONT"
matTooltip="Horizontal Front">
<mat-icon>arrow_forward</mat-icon>
</button>
<button mat-mini-fab color="primary" (click)="setPlaneOrientation(planeOrientations.HORIZONTAL_BEHIND)"
[class.active]="currentPlaneOrientation === planeOrientations.HORIZONTAL_BEHIND"
matTooltip="Horizontal Behind">
<mat-icon>arrow_back</mat-icon>
</button>
<button mat-mini-fab color="accent" (click)="setPlaneOrientation(planeOrientations.HORIZONTAL_RIGHT)"
[class.active]="currentPlaneOrientation === planeOrientations.HORIZONTAL_RIGHT"
matTooltip="Horizontal Right">
<mat-icon>arrow_right</mat-icon>
</button>
<button mat-mini-fab color="accent" (click)="setPlaneOrientation(planeOrientations.HORIZONTAL_LEFT)"
[class.active]="currentPlaneOrientation === planeOrientations.HORIZONTAL_LEFT"
matTooltip="Horizontal Left">
<mat-icon>arrow_left</mat-icon>
</button>
</div>
</div>
</div>
<app-render-container></app-render-container>
<div class="plane-controls">
<label>Plane Position (Z-axis):</label>
<mat-slider [min]="minOffset" [max]="maxOffset" step="1" #planeSlider>

View File

@ -4,53 +4,6 @@
display: flex;
}
.renderer-container {
height: 1000px;
border: 1px solid #ccc;
border-radius: 4px;
overflow: hidden;
background-color: #f0f0f0;
display: flex;
justify-content: center;
align-items: center;
position: relative; /* Added for absolute positioning of overlay */
}
.plane-controls-overlay {
position: absolute;
top: 20px;
right: 20px;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 10px;
z-index: 10;
}
.plane-orientation-buttons {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(3, 1fr);
gap: 10px;
margin-top: 10px;
}
.plane-orientation-buttons button {
opacity: 0.7;
transition: opacity 0.2s, transform 0.2s;
}
.plane-orientation-buttons button:hover {
opacity: 1;
transform: scale(1.1);
}
.plane-orientation-buttons button.active {
opacity: 1;
transform: scale(1.1);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
.side-column {
flex: 1;
flex-direction: column;

View File

@ -1,4 +1,4 @@
import {AfterViewInit, Component, ElementRef, OnDestroy, ViewChild} from '@angular/core';
import {Component, ElementRef, ViewChild} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
@ -13,18 +13,15 @@ import {MatIconModule} from '@angular/material/icon';
import {HeaderComponent} from '../header/header.component';
// Services
import {RendererService} from './services/renderer.service';
import {PlayerModelService} from './services/player-model.service';
import {IntersectionPlaneService, PlaneOrientation} from './services/intersection-plane.service';
import {IntersectionPlaneService} from './services/intersection-plane.service';
import {ParticleManagerService} from './services/particle-manager.service';
import {InputHandlerService} from './services/input-handler.service';
// Models
import {PropertiesComponent} from './components/properties/properties.component';
import {ParticleComponent} from './components/particle/particle.component';
import {FramesComponent} from './components/frames/frames.component';
import {MatSnackBar} from '@angular/material/snack-bar';
import {MatTooltip} from '@angular/material/tooltip';
import {RenderContainerComponent} from './components/render-container/render-container.component';
@Component({
selector: 'app-particles',
@ -46,52 +43,21 @@ import {MatTooltip} from '@angular/material/tooltip';
PropertiesComponent,
ParticleComponent,
FramesComponent,
MatTooltip,
RenderContainerComponent,
],
templateUrl: './particles.component.html',
styleUrl: './particles.component.scss'
})
export class ParticlesComponent implements AfterViewInit, OnDestroy {
@ViewChild('rendererContainer') rendererContainer!: ElementRef;
export class ParticlesComponent {
@ViewChild('planeSlider') planeSlider!: ElementRef;
constructor(
private rendererService: RendererService,
private playerModelService: PlayerModelService,
private intersectionPlaneService: IntersectionPlaneService,
private particleManagerService: ParticleManagerService,
private inputHandlerService: InputHandlerService,
private matSnackBar: MatSnackBar,
) {
}
/**
* Initialize Three.js scene after view is initialized
*/
ngAfterViewInit(): void {
this.initializeScene();
this.animate();
}
/**
* Clean up resources when component is destroyed
*/
ngOnDestroy(): void {
if (this.rendererService.renderer) {
this.inputHandlerService.cleanup(this.rendererService.renderer.domElement);
}
}
/**
* Initialize the 3D scene and all related components
*/
private initializeScene(): void {
this.rendererService.initializeRenderer(this.rendererContainer);
this.playerModelService.createPlayerModel();
this.intersectionPlaneService.createIntersectionPlane();
this.inputHandlerService.initializeInputHandlers(this.rendererService.renderer.domElement);
}
/**
* Update plane position based on slider
*/
@ -120,16 +86,6 @@ export class ParticlesComponent implements AfterViewInit, OnDestroy {
return this.intersectionPlaneService.getMinOffset();
}
/**
* Animation loop
*/
private animate(): void {
requestAnimationFrame(this.animate.bind(this));
this.intersectionPlaneService.updatePlaneOrientation(this.rendererService.camera);
this.rendererService.render();
}
/**
* Generate JSON output
*/
@ -142,40 +98,4 @@ export class ParticlesComponent implements AfterViewInit, OnDestroy {
this.matSnackBar.open('Copied to clipboard', '', {duration: 2000})
});
}
/**
* Get whether the plane is locked
*/
public get isPlaneLocked(): boolean {
return this.intersectionPlaneService.isPlaneLocked();
}
/**
* Toggle the plane locked state
*/
public togglePlaneLock(): void {
const newLockedState = !this.isPlaneLocked;
this.intersectionPlaneService.setPlaneLocked(newLockedState);
}
/**
* Get the current plane orientation
*/
public get currentPlaneOrientation(): PlaneOrientation {
return this.intersectionPlaneService.getCurrentOrientation();
}
/**
* Set the plane orientation
*/
public setPlaneOrientation(orientation: PlaneOrientation): void {
this.intersectionPlaneService.setPlaneOrientation(orientation);
}
/**
* Get all available plane orientations
*/
public get planeOrientations(): typeof PlaneOrientation {
return PlaneOrientation;
}
}