AltitudeWeb/frontend/src/app/particles/services/intersection-plane.service.ts
Teriuihi 9808b5d63d Add manual plane orientation controls with lock/unlock functionality
Implemented a UI overlay in `ParticlesComponent` for manual plane orientation selection with buttons for different orientations. Added lock/unlock toggle to control automatic orientation adjustment. Refactored `IntersectionPlaneService` to support locked state and manual orientation updates. Updated styles and layout to integrate the new controls seamlessly.
2025-06-22 19:16:32 +02:00

256 lines
7.5 KiB
TypeScript

import {Injectable} from '@angular/core';
import * as THREE from 'three';
import {RendererService} from './renderer.service';
/**
* Represents the possible orientations of the intersection plane
*/
export enum PlaneOrientation {
VERTICAL_ABOVE,
VERTICAL_BELOW,
HORIZONTAL_FRONT,
HORIZONTAL_BEHIND,
HORIZONTAL_RIGHT,
HORIZONTAL_LEFT
}
/**
* Service responsible for managing the intersection plane
*/
@Injectable({
providedIn: 'root'
})
export class IntersectionPlaneService {
private intersectionPlane!: THREE.Mesh;
private planePosition: number = 0; // Position in 1/16th of a block
private currentOrientation: PlaneOrientation = PlaneOrientation.HORIZONTAL_FRONT;
private planeLocked: boolean = false;
constructor(private rendererService: RendererService) {
}
/**
* Creates the intersection plane and adds it to the scene
*/
createIntersectionPlane(): THREE.Mesh {
const planeGeometry = new THREE.PlaneGeometry(3, 3);
const planeMaterial = new THREE.MeshBasicMaterial({
color: 0x00AA00,
transparent: true,
opacity: 0.05,
side: THREE.DoubleSide
});
this.intersectionPlane = new THREE.Mesh(planeGeometry, planeMaterial);
this.intersectionPlane.position.z = 0;
// Center the plane vertically with the player (player is about 2 blocks tall)
this.intersectionPlane.position.y = 1;
this.rendererService.scene.add(this.intersectionPlane);
return this.intersectionPlane;
}
/**
* Determines the plane orientation based on camera position
*/
private determinePlaneOrientation(camera: THREE.Camera): PlaneOrientation {
// Check if camera is looking from above or below first
const verticalAngle = Math.atan2(
camera.position.y,
Math.sqrt(camera.position.x * camera.position.x + camera.position.z * camera.position.z)
);
// Threshold angle for considering the camera to be above/below (about 45 degrees)
const verticalThreshold = Math.PI / 4;
if (verticalAngle > verticalThreshold) {
return PlaneOrientation.VERTICAL_ABOVE;
} else if (verticalAngle < -verticalThreshold) {
return PlaneOrientation.VERTICAL_BELOW;
} else {
// Calculate the angle between camera and player (in the XZ plane)
const cameraAngle = Math.atan2(
camera.position.x,
camera.position.z
);
// Determine which quadrant the camera is in with a 45-degree offset
const quadrant = Math.floor((cameraAngle + Math.PI + Math.PI / 4) / (Math.PI / 2)) % 4;
switch (quadrant) {
case 0:
return PlaneOrientation.HORIZONTAL_FRONT;
case 1:
return PlaneOrientation.HORIZONTAL_RIGHT;
case 2:
return PlaneOrientation.HORIZONTAL_BEHIND;
case 3:
return PlaneOrientation.HORIZONTAL_LEFT;
default:
return PlaneOrientation.HORIZONTAL_FRONT;
}
}
}
/**
* Updates the plane orientation based on camera position
*/
public updatePlaneOrientation(camera: THREE.Camera): void {
if (!this.intersectionPlane) return;
if (!this.planeLocked) {
this.currentOrientation = this.determinePlaneOrientation(camera);
}
this.updateIntersectionPlaneOrientation()
//Restrict plane position to the new bounds and update it
this.planePosition = Math.max(this.getMinOffset(), Math.min(this.getMaxOffset(), this.planePosition));
this.updatePlanePosition(this.planePosition);
}
/**
* Updates the plane position based on slider value
*/
public updatePlanePosition(value: number): void {
this.planePosition = value;
// Convert from 1/16th block to Three.js units
const position = (this.planePosition / 16)
this.intersectionPlane.position.y = 0.8;
this.intersectionPlane.position.x = 0;
this.intersectionPlane.position.z = 0;
// Position based on the current orientation
switch (this.currentOrientation) {
case PlaneOrientation.VERTICAL_ABOVE:
this.intersectionPlane.position.y = 0.8 - position;
break;
case PlaneOrientation.VERTICAL_BELOW:
this.intersectionPlane.position.y = 0.8 + position;
break;
case PlaneOrientation.HORIZONTAL_FRONT:
this.intersectionPlane.position.z = position;
break;
case PlaneOrientation.HORIZONTAL_BEHIND:
this.intersectionPlane.position.z = -position;
break;
case PlaneOrientation.HORIZONTAL_RIGHT:
this.intersectionPlane.position.x = position;
break;
case PlaneOrientation.HORIZONTAL_LEFT:
this.intersectionPlane.position.x = -position;
break;
}
}
/**
* Updates the plane material color
*/
private updatePlaneMaterial(color: number): void {
this.intersectionPlane.material = new THREE.MeshBasicMaterial({
color: color,
transparent: true,
opacity: 0.05,
side: THREE.DoubleSide
});
}
/**
* Gets the intersection plane
*/
public getIntersectionPlane(): THREE.Mesh {
return this.intersectionPlane;
}
/**
* Gets the current plane position
*/
public getPlanePosition(): number {
return this.planePosition;
}
public getMaxOffset(): number {
switch (this.currentOrientation) {
case PlaneOrientation.VERTICAL_ABOVE:
case PlaneOrientation.VERTICAL_BELOW:
return 16;
case PlaneOrientation.HORIZONTAL_FRONT:
case PlaneOrientation.HORIZONTAL_BEHIND:
return 8;
case PlaneOrientation.HORIZONTAL_RIGHT:
case PlaneOrientation.HORIZONTAL_LEFT:
return 8;
}
}
public getMinOffset(): number {
return this.getMaxOffset() * -1;
}
/**
* Gets the current plane orientation
*/
public getCurrentOrientation(): PlaneOrientation {
return this.currentOrientation;
}
/**
* Sets the plane orientation manually
*/
public setPlaneOrientation(orientation: PlaneOrientation): void {
this.currentOrientation = orientation;
this.updateIntersectionPlaneOrientation();
this.updatePlanePosition(this.planePosition);
}
private updateIntersectionPlaneOrientation() {
switch (this.currentOrientation) {
case PlaneOrientation.VERTICAL_ABOVE:
this.intersectionPlane.rotation.x = -Math.PI / 2;
this.intersectionPlane.rotation.y = 0;
this.updatePlaneMaterial(0xAA0000);
break;
case PlaneOrientation.VERTICAL_BELOW:
this.intersectionPlane.rotation.x = Math.PI / 2;
this.intersectionPlane.rotation.y = 0;
this.updatePlaneMaterial(0xAA0000);
break;
case PlaneOrientation.HORIZONTAL_FRONT:
this.intersectionPlane.rotation.x = 0;
this.intersectionPlane.rotation.y = 0;
this.updatePlaneMaterial(0x00AA00);
break;
case PlaneOrientation.HORIZONTAL_BEHIND:
this.intersectionPlane.rotation.x = 0;
this.intersectionPlane.rotation.y = Math.PI;
this.updatePlaneMaterial(0x00AA00);
break;
case PlaneOrientation.HORIZONTAL_RIGHT:
this.intersectionPlane.rotation.x = 0;
this.intersectionPlane.rotation.y = Math.PI / 2;
this.updatePlaneMaterial(0x0000AA);
break;
case PlaneOrientation.HORIZONTAL_LEFT:
this.intersectionPlane.rotation.x = 0;
this.intersectionPlane.rotation.y = -Math.PI / 2;
this.updatePlaneMaterial(0x0000AA);
break;
}
}
/**
* Gets whether the plane orientation is locked
*/
public isPlaneLocked(): boolean {
return this.planeLocked;
}
/**
* Sets whether the plane orientation is locked
*/
public setPlaneLocked(locked: boolean): void {
this.planeLocked = locked;
}
}