From 9b8c4891f4fea521e3ba42b678d6814953d9726d Mon Sep 17 00:00:00 2001 From: akastijn Date: Sat, 27 Dec 2025 21:55:00 +0100 Subject: [PATCH] Add toggle for showing only intersecting particles --- .../render-container.component.html | 33 +++++++----- .../render-container.component.ts | 28 ++++++---- .../services/intersection-plane.service.ts | 12 +++++ .../services/particle-manager.service.ts | 52 ++++++++++++++++++- 4 files changed, 100 insertions(+), 25 deletions(-) diff --git a/frontend/src/app/pages/particles/components/render-container/render-container.component.html b/frontend/src/app/pages/particles/components/render-container/render-container.component.html index c3d47df..4d8525c 100644 --- a/frontend/src/app/pages/particles/components/render-container/render-container.component.html +++ b/frontend/src/app/pages/particles/components/render-container/render-container.component.html @@ -8,12 +8,17 @@
+ +
@@ -21,33 +26,33 @@ @if (isPlaneLocked) {
diff --git a/frontend/src/app/pages/particles/components/render-container/render-container.component.ts b/frontend/src/app/pages/particles/components/render-container/render-container.component.ts index b68ea6d..a517a80 100644 --- a/frontend/src/app/pages/particles/components/render-container/render-container.component.ts +++ b/frontend/src/app/pages/particles/components/render-container/render-container.component.ts @@ -1,4 +1,4 @@ -import {AfterViewInit, Component, ElementRef, OnDestroy, ViewChild} from '@angular/core'; +import {AfterViewInit, Component, ElementRef, inject, OnDestroy, ViewChild} from '@angular/core'; import {MatMiniFabButton} from '@angular/material/button'; import {IntersectionPlaneService, PlaneOrientation} from '../../services/intersection-plane.service'; @@ -9,6 +9,7 @@ import {PlayerModelService} from '../../services/player-model.service'; import {InputHandlerService} from '../../services/input-handler.service'; import {FormsModule} from '@angular/forms'; import {MatFormField, MatInput, MatLabel} from '@angular/material/input'; +import {ParticleManagerService} from '../../services/particle-manager.service'; @Component({ selector: 'app-render-container', @@ -20,20 +21,18 @@ import {MatFormField, MatInput, MatLabel} from '@angular/material/input'; MatInput, MatFormField, MatLabel -], + ], 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, - ) { - } + private readonly intersectionPlaneService = inject(IntersectionPlaneService); + private readonly playerModelService = inject(PlayerModelService); + private readonly inputHandlerService = inject(InputHandlerService); + private readonly rendererService = inject(RendererService); + private readonly particleManagerService = inject(ParticleManagerService); ngAfterViewInit(): void { this.initializeScene(); @@ -99,6 +98,10 @@ export class RenderContainerComponent implements AfterViewInit, OnDestroy { this.rendererService.resetCamera(); } + public toggleShowParticlesWhenIntersectingPlane(): void { + this.particleManagerService.onlyIntersectingParticles = !this.particleManagerService.onlyIntersectingParticles; + } + /** * Get the current plane orientation */ @@ -106,6 +109,13 @@ export class RenderContainerComponent implements AfterViewInit, OnDestroy { return this.intersectionPlaneService.getCurrentOrientation(); } + /** + * Retrieves the value indicating whether only intersecting particles are being considered. + */ + public get onlyIntersecting(): boolean { + return this.particleManagerService.onlyIntersectingParticles; + } + /** * Set the plane orientation */ diff --git a/frontend/src/app/pages/particles/services/intersection-plane.service.ts b/frontend/src/app/pages/particles/services/intersection-plane.service.ts index 1c27ab5..aba22f4 100644 --- a/frontend/src/app/pages/particles/services/intersection-plane.service.ts +++ b/frontend/src/app/pages/particles/services/intersection-plane.service.ts @@ -1,6 +1,7 @@ import {Injectable} from '@angular/core'; import * as THREE from 'three'; import {RendererService} from './renderer.service'; +import {Subject} from 'rxjs'; /** * Represents the possible orientations of the intersection plane @@ -27,6 +28,10 @@ export class IntersectionPlaneService { private planeLocked: boolean = false; private opacity: number = 0.05; + // Emits whenever plane position, orientation, or lock-affecting orientation updates change visuals + public readonly planeChanged$ = new Subject(); + private lastPlaneSignature: string | null = null; + constructor(private rendererService: RendererService) { } @@ -144,6 +149,13 @@ export class IntersectionPlaneService { this.intersectionPlane.position.x = -position; break; } + + // Notify listeners only if signature changed to avoid spamming during animation frames + const signature = `${this.currentOrientation}|${this.planePosition}`; + if (signature !== this.lastPlaneSignature) { + this.lastPlaneSignature = signature; + this.planeChanged$.next(); + } } /** diff --git a/frontend/src/app/pages/particles/services/particle-manager.service.ts b/frontend/src/app/pages/particles/services/particle-manager.service.ts index eee13fb..3c54084 100644 --- a/frontend/src/app/pages/particles/services/particle-manager.service.ts +++ b/frontend/src/app/pages/particles/services/particle-manager.service.ts @@ -1,7 +1,8 @@ -import {Injectable} from '@angular/core'; +import {inject, Injectable} from '@angular/core'; import * as THREE from 'three'; import {RendererService} from './renderer.service'; import {Particle, ParticleData, ParticleInfo, ParticleType} from '../models/particle.model'; +import {IntersectionPlaneService, PlaneOrientation} from './intersection-plane.service'; /** * Service responsible for managing particles in the scene @@ -33,8 +34,18 @@ export class ParticleManagerService { private selectedColor: string = '#ff0000'; private selectedParticle: Particle = Particle.DUST; private selectedSize: number = 1; + private onlyIntersecting: boolean = false; - constructor(private rendererService: RendererService) { + private readonly rendererService = inject(RendererService); + private readonly intersectionPlaneService = inject(IntersectionPlaneService); + + constructor() { + this.intersectionPlaneService.planeChanged$.subscribe(() => { + if (this.onlyIntersecting) { + this.clearParticleVisuals(); + this.renderFrameParticles(this.currentFrame); + } + }); } /** @@ -88,7 +99,34 @@ export class ParticleManagerService { renderFrameParticles(frameId: string): void { if (!this.particleData.frames[frameId]) return; + const filter = this.onlyIntersecting; + const orientation = this.intersectionPlaneService.getCurrentOrientation(); + const offset16 = this.intersectionPlaneService.getPlanePosition(); + const planePos = offset16 / 16; // convert from 1/16th units to world units + const epsilon = 0.02; // tolerance for intersection + + const isOnPlane = (p: ParticleInfo) => { + if (!filter) return true; + switch (orientation) { + case PlaneOrientation.VERTICAL_ABOVE: + case PlaneOrientation.VERTICAL_BELOW: + // Horizontal plane at y = 0.8 +/- planePos + return Math.abs(p.y - (0.8 + (orientation === PlaneOrientation.VERTICAL_BELOW ? planePos : -planePos))) <= epsilon; + case PlaneOrientation.HORIZONTAL_FRONT: + return Math.abs(p.z - planePos) <= epsilon; + case PlaneOrientation.HORIZONTAL_BEHIND: + return Math.abs(p.z + planePos) <= epsilon; + case PlaneOrientation.HORIZONTAL_RIGHT: + return Math.abs(p.x - planePos) <= epsilon; + case PlaneOrientation.HORIZONTAL_LEFT: + return Math.abs(p.x + planePos) <= epsilon; + } + }; + for (const particleInfo of this.particleData.frames[frameId]) { + if (!isOnPlane(particleInfo)) { + continue; + } const particleGeometry = new THREE.SphereGeometry(0.03 * (particleInfo.size ?? 1), 16, 16); const color = this.getColor(particleInfo); @@ -245,4 +283,14 @@ export class ParticleManagerService { generateJson(): string { return JSON.stringify(this.particleData, null, 2); } + + public get onlyIntersectingParticles(): boolean { + return this.onlyIntersecting; + } + + public set onlyIntersectingParticles(value: boolean) { + this.onlyIntersecting = value; + this.clearParticleVisuals(); + this.renderFrameParticles(this.currentFrame); + } }