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