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.
256 lines
7.5 KiB
TypeScript
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;
|
|
}
|
|
}
|