Warning, /firebird/firebird-ng/src/app/services/game-controller.service.ts is written in an unsupported language. File is not indexed.
0001 import { Injectable } from '@angular/core';
0002 import * as THREE from "three";
0003 import {BehaviorSubject, Observable, Subject} from "rxjs";
0004 import {ThreeService} from "./three.service";
0005 import {LocalStorageService} from "./local-storage.service";
0006
0007
0008 export enum GamepadButtonIndexes {
0009 ButtonA = 0,
0010 ButtonB = 1,
0011 ButtonX = 2,
0012 ButtonY = 3,
0013 ButtonLB = 4,
0014 ButtonRB = 5,
0015 ButtonLT = 6,
0016 ButtonRT = 7,
0017 Select = 8,
0018 Start = 9,
0019 }
0020
0021 export class GamepadObservableButton {
0022 private onPressSubject = new Subject<boolean>();
0023 public onPress = this.onPressSubject.asObservable();
0024 public state: GamepadButton = {pressed: false, touched: false, value: 0};
0025
0026 constructor(public index: GamepadButtonIndexes) {
0027 }
0028
0029 updateState(newState: GamepadButton) {
0030 if(this.onPressSubject.observed) {
0031 if(newState.pressed != this.state.pressed) {
0032 this.onPressSubject.next(newState.pressed);
0033 }
0034 }
0035 this.state = newState;
0036 }
0037
0038 updateFromGamepadState(gamepad: Gamepad) {
0039 this.updateState(gamepad.buttons[this.index]);
0040 }
0041 }
0042
0043 @Injectable({
0044 providedIn: 'root'
0045 })
0046 export class GameControllerService {
0047
0048 private xAxisSubject = new BehaviorSubject<number>(0);
0049 public xAxisChanged = this.xAxisSubject.asObservable();
0050 public xAxis = 0;
0051
0052 private yAxisSubject = new BehaviorSubject<number>(0);
0053 private yAxisChanged = this.yAxisSubject.asObservable();
0054 public yAxis: number = 0;
0055 public buttons: GamepadButton[] = [];
0056 public prevButtons: GamepadButton[] = [];
0057
0058
0059 public buttonA: GamepadObservableButton = new GamepadObservableButton(GamepadButtonIndexes.ButtonA);
0060 public buttonB: GamepadObservableButton = new GamepadObservableButton(GamepadButtonIndexes.ButtonB);
0061 public buttonX: GamepadObservableButton = new GamepadObservableButton(GamepadButtonIndexes.ButtonX);
0062 public buttonY: GamepadObservableButton = new GamepadObservableButton(GamepadButtonIndexes.ButtonY);
0063 public buttonLB: GamepadObservableButton = new GamepadObservableButton(GamepadButtonIndexes.ButtonLB);
0064 public buttonRB: GamepadObservableButton = new GamepadObservableButton(GamepadButtonIndexes.ButtonRB);
0065 public buttonLT: GamepadObservableButton = new GamepadObservableButton(GamepadButtonIndexes.ButtonLT);
0066 public buttonRT: GamepadObservableButton = new GamepadObservableButton(GamepadButtonIndexes.ButtonRT);
0067 public buttonStart: GamepadObservableButton = new GamepadObservableButton(GamepadButtonIndexes.Start);
0068 public buttonSelect: GamepadObservableButton = new GamepadObservableButton(GamepadButtonIndexes.Select);
0069
0070 public activeGamepad: Gamepad|null = null;
0071 private frameCallbackRef: (() => void) | null = null;
0072 private isControllerEnabled: boolean = false;
0073
0074 animationLoopHandler () {
0075 // Only process if controller is enabled
0076 if (!this.isControllerEnabled) {
0077 return;
0078 }
0079
0080 const epsilon = 0.01;
0081 const gamepads = navigator.getGamepads();
0082 for (const gamepad of gamepads) {
0083 if (gamepad) {
0084
0085 this.activeGamepad = gamepad;
0086 // Example: Using left joystick to control OrbitControls
0087 // Axis 0: Left joystick horizontal (left/right)
0088 // Axis 1: Left joystick vertical (up/down)
0089 this.xAxis = gamepad.axes[0];
0090 this.yAxis = gamepad.axes[1];
0091
0092 if(Math.abs(this.xAxis - this.xAxisSubject.value) > epsilon) {
0093 this.xAxisSubject.next(this.xAxis);
0094 }
0095
0096 if(Math.abs(this.yAxis - this.yAxisSubject.value) > epsilon) {
0097 this.yAxisSubject.next(this.yAxis);
0098 }
0099
0100 const xAxis = gamepad.axes[0];
0101 const yAxis = gamepad.axes[1];
0102
0103 if (Math.abs(xAxis) > 0.1 || Math.abs(yAxis) > 0.1) {
0104 this.rotateCamera(xAxis, yAxis);
0105 }
0106
0107 // Zooming using buttons (X and A)
0108 const zoomInButton = gamepad.buttons[2]; // X button
0109 const zoomOutButton = gamepad.buttons[0]; // A button
0110
0111 if (zoomInButton.pressed) {
0112 this.zoom(0.99);
0113 }
0114
0115 if (zoomOutButton.pressed) {
0116 this.zoom(1.01);
0117 }
0118
0119 // Strafe functionality
0120 // B button for strafe left
0121 if (this.buttonB.state.pressed) {
0122 this.strafe(-0.5);
0123 }
0124
0125 // RT button for strafe right
0126 if (this.buttonRT.state.pressed) {
0127 this.strafe(0.5);
0128 }
0129
0130 // Default view on LT button press
0131 if (this.buttonLT.state.pressed) {
0132 this.resetToDefaultView();
0133 }
0134
0135 this.buttonSelect.updateFromGamepadState(gamepad);
0136 this.buttonStart.updateFromGamepadState(gamepad);
0137
0138 this.buttonA.updateFromGamepadState(gamepad);
0139 this.buttonB.updateFromGamepadState(gamepad);
0140 this.buttonY.updateFromGamepadState(gamepad);
0141 this.buttonX.updateFromGamepadState(gamepad);
0142
0143 this.buttonLB.updateFromGamepadState(gamepad);
0144 this.buttonRB.updateFromGamepadState(gamepad);
0145 this.buttonLT.updateFromGamepadState(gamepad);
0146 this.buttonRT.updateFromGamepadState(gamepad);
0147
0148 break; // Only use the first connected gamepad
0149 }
0150 }
0151 };
0152
0153
0154 rotateCamera(xAxisChange: number, yAxisChange: number) {
0155 let orbitControls = this.three.controls;
0156 let camera = this.three.camera;
0157
0158 const offset = new THREE.Vector3(); // Offset of the camera from the target
0159 const quat = new THREE.Quaternion().setFromUnitVectors(camera.up, new THREE.Vector3(0, 1, 0));
0160 const quatInverse = quat.clone().invert();
0161
0162 const currentPosition = camera.position.clone().sub(orbitControls.target);
0163 currentPosition.applyQuaternion(quat); // Apply the quaternion
0164
0165 // Spherical coordinates
0166 const spherical = new THREE.Spherical().setFromVector3(currentPosition);
0167
0168 // Adjusting spherical coordinates
0169 spherical.theta -= xAxisChange * 0.023; // Azimuth angle change
0170 spherical.phi += yAxisChange * 0.023; // Polar angle change, for rotating up/down
0171
0172 // Ensure phi is within bounds to avoid flipping
0173 spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi));
0174
0175 // Convert back to Cartesian coordinates
0176 const newPostion = new THREE.Vector3().setFromSpherical(spherical);
0177 newPostion.applyQuaternion(quatInverse);
0178
0179 camera.position.copy(newPostion.add(orbitControls.target));
0180 camera.lookAt(orbitControls.target);
0181 orbitControls.update();
0182 }
0183
0184 zoom(factor: number) {
0185 let orbitControls = this.three.controls;
0186 let camera = this.three.camera;
0187 orbitControls.object.position.subVectors(camera.position, orbitControls.target).multiplyScalar(factor).add(orbitControls.target);
0188 orbitControls.update();
0189 }
0190
0191 strafe(amount: number) {
0192 const camera = this.three.camera;
0193 const controls = this.three.controls;
0194
0195 // Move along Z axis (right = positive Z, left = negative Z)
0196 const offset = new THREE.Vector3(0, 0, amount * 10); // Scale for reasonable movement
0197
0198 // Move camera and target together
0199 camera.position.add(offset);
0200 controls.target.add(offset);
0201 controls.update();
0202 }
0203
0204 resetToDefaultView() {
0205 const camera = this.three.camera;
0206 const controls = this.three.controls;
0207
0208 // Reset camera to default position
0209 camera.position.set(-7000, 0, 0);
0210 controls.target.set(0, 0, 0);
0211
0212 // Update controls
0213 controls.update();
0214
0215 console.log('[GameController] Camera reset to default view');
0216 }
0217
0218 constructor(
0219 private three: ThreeService,
0220 private localStorageService: LocalStorageService
0221 ) {
0222 // Check if controller should be enabled on initialization
0223 this.isControllerEnabled = this.localStorageService.useController?.value ?? false;
0224
0225 // Create the callback reference
0226 this.frameCallbackRef = () => { this.animationLoopHandler(); };
0227
0228 // Only attach handler if controller is enabled
0229 if (this.isControllerEnabled) {
0230 this.three.addFrameCallback(this.frameCallbackRef);
0231 console.log('[GameController] Controller enabled on initialization');
0232 }
0233
0234 // Subscribe to changes in the use controller setting
0235 this.localStorageService.useController?.changes$.subscribe((enabled) => {
0236 this.setControllerEnabled(enabled);
0237 });
0238
0239 this.xAxisChanged.subscribe((data)=>{
0240 if (this.isControllerEnabled) {
0241 console.log(`[joystick] x: ${data}`);
0242 }
0243 });
0244 this.yAxisChanged.subscribe((data)=>{
0245 if (this.isControllerEnabled) {
0246 console.log(`[joystick] y: ${data}`);
0247 }
0248 });
0249 }
0250
0251 private setControllerEnabled(enabled: boolean) {
0252 if (this.isControllerEnabled === enabled) {
0253 return;
0254 }
0255
0256 this.isControllerEnabled = enabled;
0257
0258 if (enabled) {
0259 // Attach the frame callback
0260 if (this.frameCallbackRef) {
0261 this.three.addFrameCallback(this.frameCallbackRef);
0262 console.log('[GameController] Controller enabled');
0263 }
0264 } else {
0265 // Remove the frame callback
0266 if (this.frameCallbackRef) {
0267 this.three.removeFrameCallback(this.frameCallbackRef);
0268 console.log('[GameController] Controller disabled');
0269 }
0270 }
0271 }
0272 }