diff --git a/firebird-ng/src/app/app.component.html b/firebird-ng/src/app/app.component.html index aa624de..a386dbf 100644 --- a/firebird-ng/src/app/app.component.html +++ b/firebird-ng/src/app/app.component.html @@ -34,18 +34,18 @@ - - + + + + + + + + + + + + diff --git a/firebird-ng/src/app/app.routes.ts b/firebird-ng/src/app/app.routes.ts index 91201eb..ea4aab0 100644 --- a/firebird-ng/src/app/app.routes.ts +++ b/firebird-ng/src/app/app.routes.ts @@ -4,9 +4,8 @@ import {FileBrowserComponent} from "./file-browser/file-browser.component"; import {InputConfigComponent} from "./input-config/input-config.component"; export const routes: Routes = [ - { path: '', redirectTo: '/config', pathMatch: 'full' }, + { path: '', redirectTo: '/display', pathMatch: 'full' }, { path: 'config', component: InputConfigComponent }, - { path: 'files', loadComponent: () => import('./file-browser/file-browser.component').then(m => m.FileBrowserComponent) diff --git a/firebird-ng/src/app/game-controller.service.ts b/firebird-ng/src/app/game-controller.service.ts index 961e0f4..71c921d 100644 --- a/firebird-ng/src/app/game-controller.service.ts +++ b/firebird-ng/src/app/game-controller.service.ts @@ -1,9 +1,121 @@ import { Injectable } from '@angular/core'; +import * as THREE from "three"; +import {BehaviorSubject, Observable, Subject} from "rxjs"; + + +export enum ControllerButtonIndexes { + ButtonA = 0, + ButtonB = 1, + ButtonX = 2, + ButtonY = 3, + ButtonLB = 4, + ButtonRB = 5, + ButtonLT = 6, + ButtonRT = 7, + Select = 8, + Start = 9, +} + @Injectable({ providedIn: 'root' }) export class GameControllerService { - constructor() { } + private xAxisSubject = new BehaviorSubject(0); + public xAxisChanged = this.xAxisSubject.asObservable(); + public xAxis = 0; + + private yAxisSubject = new BehaviorSubject(0); + private yAxisChanged = this.yAxisSubject.asObservable(); + public yAxis: number = 0; + public buttons: GamepadButton[] = []; + public prevButtons: GamepadButton[] = []; + + private buttonASubject = new Subject(); + public buttonAPressed = this.buttonASubject.asObservable(); + public buttonA: GamepadButton = {pressed: false, touched: false, value: 0}; + private buttonBSubject = new Subject(); + public buttonBPressed = this.buttonBSubject.asObservable(); + public buttonB: GamepadButton = {pressed: false, touched: false, value: 0}; + private buttonXSubject = new Subject(); + public buttonXPressed = this.buttonXSubject.asObservable(); + public buttonX: GamepadButton = {pressed: false, touched: false, value: 0}; + private buttonYSubject = new Subject(); + public buttonYPressed = this.buttonYSubject.asObservable(); + public buttonY: GamepadButton = {pressed: false, touched: false, value: 0}; + private buttonLBSubject = new Subject(); + public buttonLBPressed = this.buttonLBSubject.asObservable(); + public buttonLB: GamepadButton = {pressed: false, touched: false, value: 0}; + private buttonRBSubject = new Subject(); + public buttonRBPressed = this.buttonRBSubject.asObservable(); + public buttonRB: GamepadButton = {pressed: false, touched: false, value: 0}; + private buttonLTSubject = new Subject(); + public buttonLTPressed = this.buttonLTSubject.asObservable(); + public buttonLT: GamepadButton = {pressed: false, touched: false, value: 0}; + private buttonRTSubject = new Subject(); + public buttonRTPressed = this.buttonRTSubject.asObservable(); + public buttonRT: GamepadButton = {pressed: false, touched: false, value: 0}; + private buttonSelectSubject = new Subject(); + public buttonSelectPressed = this.buttonSelectSubject.asObservable(); + public buttonSelect: GamepadButton = {pressed: false, touched: false, value: 0}; + private buttonStartSubject = new Subject(); + public buttonStartPressed = this.buttonStartSubject.asObservable(); + public buttonStart: GamepadButton = {pressed: false, touched: false, value: 0}; + + public activeGamepad: Gamepad|null = null; + + animationLoopHandler () { + + const epsilon = 0.01; + const gamepads = navigator.getGamepads(); + for (const gamepad of gamepads) { + if (gamepad) { + + this.activeGamepad = gamepad; + // Example: Using left joystick to control OrbitControls + // Axis 0: Left joystick horizontal (left/right) + // Axis 1: Left joystick vertical (up/down) + this.xAxis = gamepad.axes[0]; + this.yAxis = gamepad.axes[1]; + + if(Math.abs(this.xAxis - this.xAxisSubject.value) > epsilon) { + this.xAxisSubject.next(this.xAxis); + } + + if(Math.abs(this.yAxis - this.yAxisSubject.value) > epsilon) { + this.yAxisSubject.next(this.yAxis); + } + + this.buttonA = gamepad.buttons[ControllerButtonIndexes.ButtonA]; + this.buttonB = gamepad.buttons[ControllerButtonIndexes.ButtonB]; + this.buttonX = gamepad.buttons[ControllerButtonIndexes.ButtonX]; + this.buttonY = gamepad.buttons[ControllerButtonIndexes.ButtonY]; + this.buttonLB = gamepad.buttons[ControllerButtonIndexes.ButtonLB]; + this.buttonRB = gamepad.buttons[ControllerButtonIndexes.ButtonRB]; + this.buttonLT = gamepad.buttons[ControllerButtonIndexes.ButtonLT]; + this.buttonRT = gamepad.buttons[ControllerButtonIndexes.ButtonRT]; + this.buttonSelect = gamepad.buttons[ControllerButtonIndexes.Select]; + this.buttonStart = gamepad.buttons[ControllerButtonIndexes.Start]; + + if (this.buttonA.pressed !== this.buttonASubject.observed) this.buttonASubject.next(this.buttonA.pressed); + if (this.buttonB.pressed !== this.buttonBSubject.observed) this.buttonASubject.next(this.buttonB.pressed); + if (this.buttonX.pressed !== this.buttonXSubject.observed) this.buttonASubject.next(this.buttonX.pressed); + if (this.buttonY.pressed !== this.buttonYSubject.observed) this.buttonASubject.next(this.buttonY.pressed); + if (this.buttonLB.pressed !== this.buttonLBSubject.observed) this.buttonASubject.next(this.buttonLB.pressed); + if (this.buttonRB.pressed !== this.buttonRBSubject.observed) this.buttonASubject.next(this.buttonRB.pressed); + if (this.buttonLT.pressed !== this.buttonLTSubject.observed) this.buttonASubject.next(this.buttonLT.pressed); + if (this.buttonRT.pressed !== this.buttonRTSubject.observed) this.buttonASubject.next(this.buttonRT.pressed); + if (this.buttonSelect.pressed !== this.buttonSelectSubject.observed) this.buttonASubject.next(this.buttonSelect.pressed); + if (this.buttonStart.pressed !== this.buttonStartSubject.observed) this.buttonASubject.next(this.buttonStart.pressed); + + break; // Only use the first connected gamepad + } + } + }; + + constructor() { + // Run it on contruction so if we have an active controller we set up values + this.animationLoopHandler(); + } } diff --git a/firebird-ng/src/app/main-display/main-display.component.ts b/firebird-ng/src/app/main-display/main-display.component.ts index 83a040f..1342b8d 100644 --- a/firebird-ng/src/app/main-display/main-display.component.ts +++ b/firebird-ng/src/app/main-display/main-display.component.ts @@ -2,7 +2,7 @@ import {Component, OnInit} from '@angular/core'; import {EventDisplayService, PhoenixUIModule} from 'phoenix-ui-components'; import {ClippingSetting, Configuration, PhoenixLoader, PhoenixMenuNode, PresetView} from 'phoenix-event-display'; import * as THREE from 'three'; -import {Color, DoubleSide, MeshPhongMaterial,} from "three"; +import {Color, DoubleSide, Line, MeshPhongMaterial,} from "three"; import {GeometryService} from '../geometry.service'; import {ActivatedRoute} from '@angular/router'; import {ThreeGeometryProcessor} from "../three-geometry.processor"; @@ -19,6 +19,11 @@ import { import {mergeMeshList, MergeResult} from "../utils/three-geometry-merge"; import {PhoenixThreeFacade} from "../utils/phoenix-three-facade"; import {BehaviorSubject, Subject} from "rxjs"; +import {GameControllerService} from "../game-controller.service"; +import {LineMaterial} from "three/examples/jsm/lines/LineMaterial"; +import {Line2} from "three/examples/jsm/lines/Line2"; +import {LineGeometry} from "three/examples/jsm/lines/LineGeometry"; +// import { LineMaterial } from 'three/addons/lines/LineMaterial.js'; @Component({ @@ -55,6 +60,7 @@ export class MainDisplayComponent implements OnInit { constructor( private geomService: GeometryService, private eventDisplay: EventDisplayService, + private controller: GameControllerService, private route: ActivatedRoute) { this.threeFacade = new PhoenixThreeFacade(this.eventDisplay); } @@ -195,7 +201,53 @@ export class MainDisplayComponent implements OnInit { } }; - handleGamepadInput () { + rotateCamera(xAxisChange: number, yAxisChange: number) { + let orbitControls = this.threeFacade.activeOrbitControls; + let camera = this.threeFacade.mainCamera; + + const offset = new THREE.Vector3(); // Offset of the camera from the target + const quat = new THREE.Quaternion().setFromUnitVectors(camera.up, new THREE.Vector3(0, 1, 0)); + const quatInverse = quat.clone().invert(); + + const currentPosition = camera.position.clone().sub(orbitControls.target); + currentPosition.applyQuaternion(quat); // Apply the quaternion + + // Spherical coordinates + const spherical = new THREE.Spherical().setFromVector3(currentPosition); + + // Adjusting spherical coordinates + spherical.theta -= xAxisChange * 0.01; // Azimuth angle change + spherical.phi += yAxisChange * 0.01; // Polar angle change, for rotating up/down + + // Ensure phi is within bounds to avoid flipping + spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi)); + + // Convert back to Cartesian coordinates + const newPostion = new THREE.Vector3().setFromSpherical(spherical); + newPostion.applyQuaternion(quatInverse); + + camera.position.copy(newPostion.add(orbitControls.target)); + camera.lookAt(orbitControls.target); + orbitControls.update(); + } + + zoom(factor: number) { + let orbitControls = this.threeFacade.activeOrbitControls; + let camera = this.threeFacade.mainCamera; + orbitControls.object.position.subVectors(camera.position, orbitControls.target).multiplyScalar(factor).add(orbitControls.target); + orbitControls.update(); + } + + handleGamepadInputV2 () { + this.controller.animationLoopHandler(); + } + + logCamera() { + console.log(this.threeFacade.mainCamera); + } + + + handleGamepadInputV1 () { // Update stats display that showing FPS, etc. if (this.stats) { @@ -215,84 +267,20 @@ export class MainDisplayComponent implements OnInit { let camera = this.threeFacade.mainCamera; if (Math.abs(xAxis) > 0.1 || Math.abs(yAxis) > 0.1) { - const offset = new THREE.Vector3(); // Offset of the camera from the target - const quat = new THREE.Quaternion().setFromUnitVectors(camera.up, new THREE.Vector3(0, 1, 0)); - const quatInverse = quat.clone().invert(); - - const currentPosition = camera.position.clone().sub(controls.target); - currentPosition.applyQuaternion(quat); // Apply the quaternion - - // Spherical coordinates - const spherical = new THREE.Spherical().setFromVector3(currentPosition); - - // Adjusting spherical coordinates - spherical.theta -= xAxis * 0.01; // Azimuth angle change - spherical.phi += yAxis * 0.01; // Polar angle change, for rotating up/down - - // Ensure phi is within bounds to avoid flipping - spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi)); + this.rotateCamera(xAxis, yAxis); - // Convert back to Cartesian coordinates - const newPostion = new THREE.Vector3().setFromSpherical(spherical); - newPostion.applyQuaternion(quatInverse); - - camera.position.copy(newPostion.add(controls.target)); - camera.lookAt(controls.target); - controls.update(); } - // // Assume button indices 4 and 5 are the shoulder buttons - // const zoomInButton = gamepad.buttons[0]; - // const zoomOutButton = gamepad.buttons[2]; - // - // if (zoomInButton.pressed) { - // camera.zoom *= 1.05; - // console.log(camera); - // // Updating camera clipping planes dynamically - // //camera.near = 0.1; // Be cautious with making this too small, which can cause z-fighting - // //camera.far = 100000; // Large value to ensure distant objects are rendered - // - // camera.updateProjectionMatrix(); - // } - // - // if (zoomOutButton.pressed) { - // camera.zoom /= 1.05; - // // Updating camera clipping planes dynamically - // //camera.near = 0.1; // Be cautious with making this too small, which can cause z-fighting - // //camera.far = 100000; // Large value to ensure distant objects are rendered - // camera.updateProjectionMatrix(); - // console.log(camera); - // } - // - // // Optionally: Map other axes/buttons to other camera controls like zoom or pan - // if (gamepad.axes.length > 2) { - // // Additional axes for more control, e.g., zoom with third axis - // const zoomAxis = gamepad.axes[2]; // Typically the right stick vertical - // camera.position.z += zoomAxis * 0.1; // Adjust zoom sensitivity - // } - - - // Zooming using buttons const zoomInButton = gamepad.buttons[2]; const zoomOutButton = gamepad.buttons[0]; if (zoomInButton.pressed) { - controls.object.position.subVectors(camera.position, controls.target).multiplyScalar(0.99).add(controls.target); - controls.update(); + this.zoom(0.99); } if (zoomOutButton.pressed) { - controls.object.position.subVectors(camera.position, controls.target).multiplyScalar(1.01).add(controls.target); - controls.update(); - } - - // Zooming using the third axis of the gamepad - const zoomAxis = gamepad.axes[2]; // Typically the right stick vertical - if (Math.abs(zoomAxis) > 0.1) { - let zoomFactor = zoomAxis < 0 ? 0.95 : 1.05; - controls.object.position.subVectors(camera.position, controls.target).multiplyScalar(zoomFactor).add(controls.target); - controls.update(); + this.zoom(1.01); } break; // Only use the first connected gamepad @@ -300,6 +288,11 @@ export class MainDisplayComponent implements OnInit { } }; + updateProjectionMatrix() { + let camera = this.threeFacade.mainCamera; + camera.updateProjectionMatrix(); + } + ngOnInit() { @@ -322,7 +315,7 @@ export class MainDisplayComponent implements OnInit { defaultEventFile: { // (Assuming the file exists in the `src/assets` directory of the app) //eventFile: 'assets/herwig_18x275_5evt.json', - eventFile: 'assets/events/pythia8.json', + eventFile: 'assets/events/py8_all_dis-cc_beam-18x275_minq2-1000_nevt-20.evt.json', eventType: 'json' // or zip }, } @@ -346,9 +339,13 @@ export class MainDisplayComponent implements OnInit { // container: document.getElementById("lil-gui-place") ?? undefined, }); + gui.title("Debug"); gui.add(this, "produceRenderOrder"); gui.add(this, "logGamepadStates").name( 'Log controls' ); + gui.add(this, "logCamera").name( 'Log camera' ); + gui.add(this, "updateProjectionMatrix").name( 'Update Projection Matrix' ); + gui.close(); // Set default clipping this.eventDisplay.getUIManager().setClipping(true); @@ -359,51 +356,88 @@ export class MainDisplayComponent implements OnInit { this.eventDisplay.getLoadingManager().addLoadListenerWithCheck(() => { console.log('Loading default configuration.'); this.loaded = true; - }); - this.eventDisplay - .getLoadingManager() - .addProgressListener((progress) => (this.loadingProgress = progress)); - this.stats = (this.eventDisplay.getUIManager() as any).stats; + console.log(threeManager.getSceneManager().getEventData()); + let mcTracksGroup = threeManager.getSceneManager().getObjectByName("mc_tracks"); + if(mcTracksGroup) { + for(let trackGroup of mcTracksGroup.children) { - threeManager.setAnimationLoop(()=>{this.handleGamepadInput()}); + for(let obj of trackGroup.children) { + if(obj.type == "Line") { - let beSubject = new BehaviorSubject('a'); - beSubject.next('b'); - beSubject.subscribe(value => { - console.log('BehaviorSubject: Subscription received the value ', value); + let material = new LineMaterial( { - // Subscription received B. It would not happen - // for an Observable or Subject by default. - }); + color: 0xffff00, + linewidth: 50, // in world units with size attenuation, pixels otherwise + vertexColors: true, + + //resolution: // to be set by renderer, eventually + dashed: true, + alphaToCoverage: true, - beSubject.next('c'); -// Subscription received C. + } ); + let positions = (obj.userData as any).pos; + let flat = []; + for(let position of positions) { - beSubject.next('d'); -// Subscription received D. + flat.push(position[0], position[1], position[2]); + } + const geometry = new LineGeometry(); + geometry.setPositions( flat ); + // geometry.setColors( colors ); - // Subject. + let matLine = new LineMaterial( { - let subject = new Subject(); + color: 0xffff00, + linewidth: 10, // in world units with size attenuation, pixels otherwise + //vertexColors: true, + worldUnits: true, + //needsUpdate=: true; + //resolution: // to be set by renderer, eventually + dashed: true, + //dashScale: 100, + dashSize: 100, + gapSize: 100, + alphaToCoverage: true, - subject.next('b'); + } ); - subject.subscribe(value => { - console.log('Subject: Subscription received the value ', value); + let line = new Line2( geometry, matLine ); + + line.scale.set( 1, 1, 1 ); + line.computeLineDistances(); + line.visible = true; + trackGroup.add( line ); + obj.visible = false; + } + + if(obj.type == "Mesh") { + obj.visible = false; + } + } + console.log(trackGroup); + + } + + } - // Subscription won't receive anything at this point. }); - subject.next('c'); -// Subscription received C. - subject.next('d'); + this.eventDisplay + .getLoadingManager() + .addProgressListener((progress) => (this.loadingProgress = progress)); + + this.stats = (this.eventDisplay.getUIManager() as any).stats; + + + threeManager.setAnimationLoop(()=>{this.handleGamepadInputV1()}); + //const events_url = "https://eic.github.io/epic/artifacts/sim_dis_10x100_minQ2=1000_epic_craterlake.edm4hep.root/sim_dis_10x100_minQ2=1000_epic_craterlake.edm4hep.root" @@ -419,9 +453,9 @@ export class MainDisplayComponent implements OnInit { console.log(`geometry query: ${geometryAddress}`); let jsonGeometry; - this.loadGeometry().then(jsonGeom => { - jsonGeometry = jsonGeom; - }); + // this.loadGeometry().then(jsonGeom => { + // jsonGeometry = jsonGeom; + // });