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