From 4b4759b29e2958acaef09211d46750ee63a0de93 Mon Sep 17 00:00:00 2001 From: MrPlatnum Date: Mon, 15 Sep 2025 09:51:27 +0200 Subject: [PATCH] test --- .../text-legibility-assessment.component.html | 7 +- src/app/services/metrics-tracker.service.ts | 193 ++++++++++-------- 2 files changed, 116 insertions(+), 84 deletions(-) diff --git a/src/app/components/test-suite/assessments/text-legibility-assessment/text-legibility-assessment.component.html b/src/app/components/test-suite/assessments/text-legibility-assessment/text-legibility-assessment.component.html index e232848..e613299 100644 --- a/src/app/components/test-suite/assessments/text-legibility-assessment/text-legibility-assessment.component.html +++ b/src/app/components/test-suite/assessments/text-legibility-assessment/text-legibility-assessment.component.html @@ -11,8 +11,9 @@
-
+ \ No newline at end of file diff --git a/src/app/services/metrics-tracker.service.ts b/src/app/services/metrics-tracker.service.ts index 84e8373..6218193 100644 --- a/src/app/services/metrics-tracker.service.ts +++ b/src/app/services/metrics-tracker.service.ts @@ -1,9 +1,8 @@ import { HttpClient } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; -import { interval, Subscription, tap } from 'rxjs'; +import { tap } from 'rxjs'; import { v4 as uuidv4 } from 'uuid'; -// --- Data Interfaces (Unchanged) --- export interface InteractionEvent { timestamp: number; type: string; @@ -23,22 +22,17 @@ export interface DeviceOrientation { export interface ArTrackingData { timestamp: number; anchor: string | null; - cameraOrbit: { - theta: number; - phi: number; - radius: number; - } | null; - cameraTarget: { - x: number; - y: number; - z: number; - } | null; + cameraPosition: { x: number; y: number; z: number; w?: number } | null; + cameraOrientation: { x: number; y: number; z: number; w: number } | null; + cameraEuler: { pitch: number; yaw: number; roll: number } | null; + cameraOrbit: { theta: number; phi: number; radius: number } | null; + cameraTarget: { x: number; y: number; z: number } | null; } export interface MetricsLog { interactions: InteractionEvent[]; deviceOrientations: DeviceOrientation[]; - arData: ArTrackingData[]; + arTrackingData: ArTrackingData[]; } @Injectable({ @@ -47,29 +41,28 @@ export interface MetricsLog { export class MetricsTrackerService { private http = inject(HttpClient); private serverUrl = '/api/log'; - private deviceId: string; private metricsLog: MetricsLog = { interactions: [], deviceOrientations: [], - arData: [] + arTrackingData: [] }; - private trackingSubscription: Subscription | null = null; + private xrFrameSubscriptionId: number | null = null; private lastDeviceOrientation: DeviceOrientationEvent | null = null; + private modelViewerElement: any = null; + private xrFrameCount = 0; constructor() { this.handleDeviceOrientation = this.handleDeviceOrientation.bind(this); - + this.onXrFrame = this.onXrFrame.bind(this); + const storedId = localStorage.getItem('device-uuid'); - if (storedId) { - this.deviceId = storedId; - } else { - this.deviceId = uuidv4(); + this.deviceId = storedId || uuidv4(); + if (!storedId) { localStorage.setItem('device-uuid', this.deviceId); } - console.log('Device ID for this session:', this.deviceId); if (typeof window !== 'undefined') { window.addEventListener('deviceorientation', this.handleDeviceOrientation, true); @@ -81,74 +74,96 @@ export class MetricsTrackerService { } public logInteraction(event: Event): void { - if (event.target) { - const target = event.target as HTMLElement; - const interaction: InteractionEvent = { - timestamp: Date.now(), - type: event.type, - elementId: target.id || undefined, - elementTag: target.tagName, - elementClasses: target.className, - value: (target as any).value ?? undefined - }; - this.metricsLog.interactions.push(interaction); - } else if (event instanceof CustomEvent && event.detail) { - const interaction: InteractionEvent = { - timestamp: Date.now(), - type: event.type, - value: event.detail - }; - this.metricsLog.interactions.push(interaction); - } else { - console.warn("logInteraction called with an unknown event type:", event); - } + const target = event.target as HTMLElement; + const interaction: InteractionEvent = { + timestamp: Date.now(), + type: event.type, + elementId: target.id || undefined, + elementTag: target.tagName, + elementClasses: target.className, + value: (target as any).value ?? undefined + }; + this.metricsLog.interactions.push(interaction); } - + public startTracking(modelViewerElement: any): void { this.stopTracking(); - if (!modelViewerElement) { console.error("startTracking called with no modelViewerElement."); return; } + this.modelViewerElement = modelViewerElement; - console.log("Starting a new periodic metrics tracking subscription."); - this.trackingSubscription = interval(500).subscribe(() => { + if (this.modelViewerElement.xrSession) { + this.startXrTracking(this.modelViewerElement.xrSession); + } + } - const timestamp = Date.now(); + private startXrTracking(xrSession: any): void { + if (this.xrFrameSubscriptionId === null) { + this.xrFrameCount = 0; + this.xrFrameSubscriptionId = xrSession.requestAnimationFrame(this.onXrFrame); + } + } - // Capture AR Data - const anchor = modelViewerElement.getAnchor ? modelViewerElement.getAnchor() : 'getAnchor not available'; - const orbit = modelViewerElement.getCameraOrbit ? modelViewerElement.getCameraOrbit() : null; - const target = modelViewerElement.getCameraTarget ? modelViewerElement.getCameraTarget() : null; + private onXrFrame(time: DOMHighResTimeStamp, frame: any): void { + const session = frame.session; + this.xrFrameSubscriptionId = session.requestAnimationFrame(this.onXrFrame); - const arData: ArTrackingData = { - timestamp: timestamp, - anchor: anchor.includes('not placed') ? null : anchor, - cameraOrbit: orbit ? { theta: orbit.theta, phi: orbit.phi, radius: orbit.radius } : null, - cameraTarget: target ? { x: target.x, y: target.y, z: target.z } : null, - }; - this.metricsLog.arData.push(arData); + this.xrFrameCount++; + if (this.xrFrameCount % 30 !== 0) { + return; + } - // Capture Device Orientation - if (this.lastDeviceOrientation) { - const orientation: DeviceOrientation = { - timestamp: timestamp, - alpha: this.lastDeviceOrientation.alpha, - beta: this.lastDeviceOrientation.beta, - gamma: this.lastDeviceOrientation.gamma - }; - this.metricsLog.deviceOrientations.push(orientation); - } - }); + const referenceSpace = this.modelViewerElement?.renderer.xr.getReferenceSpace(); + if (!referenceSpace) return; + + const viewerPose = frame.getViewerPose(referenceSpace); + const timestamp = Date.now(); + let position = null, orientation = null, euler = null; + + if (viewerPose) { + const { transform } = viewerPose; + position = { x: transform.position.x, y: transform.position.y, z: transform.position.z, w: transform.position.w }; + orientation = { x: transform.orientation.x, y: transform.orientation.y, z: transform.orientation.z, w: transform.orientation.w }; + euler = this.quaternionToEuler(orientation.x, orientation.y, orientation.z, orientation.w); + } + + const anchorRaw = this.modelViewerElement.getAnchor ? this.modelViewerElement.getAnchor() : null; + const anchor = (typeof anchorRaw === 'string' && anchorRaw.includes('not placed')) ? null : anchorRaw; + const orbit = this.modelViewerElement.getCameraOrbit ? this.modelViewerElement.getCameraOrbit() : null; + const target = this.modelViewerElement.getCameraTarget ? this.modelViewerElement.getCameraTarget() : null; + + const arData: ArTrackingData = { + timestamp, + anchor, + cameraPosition: position, + cameraOrientation: orientation, + cameraEuler: euler, + cameraOrbit: orbit ? { theta: orbit.theta, phi: orbit.phi, radius: orbit.radius } : null, + cameraTarget: target ? { x: target.x, y: target.y, z: target.z } : null, + }; + this.metricsLog.arTrackingData.push(arData); + + if (this.lastDeviceOrientation) { + this.metricsLog.deviceOrientations.push({ + timestamp, + alpha: this.lastDeviceOrientation.alpha, + beta: this.lastDeviceOrientation.beta, + gamma: this.lastDeviceOrientation.gamma + }); + } } public stopTracking(): void { - if (this.trackingSubscription) { - console.log("Stopping existing tracking subscription."); - this.trackingSubscription.unsubscribe(); - this.trackingSubscription = null; + if (this.xrFrameSubscriptionId !== null) { + const xrSession = this.modelViewerElement?.xrSession; + if (xrSession) { + xrSession.cancelAnimationFrame(this.xrFrameSubscriptionId); + } + this.xrFrameSubscriptionId = null; } + this.modelViewerElement = null; } public sendMetricsToServer(testName: string, formData?: any) { @@ -158,20 +173,36 @@ export class MetricsTrackerService { metricsLog: this.metricsLog, ...(formData && { formData }) }; - console.log("Sending final payload:", payload); - this.stopTracking(); + + this.stopTracking(); return this.http.post(this.serverUrl, payload).pipe( tap({ - next: (response) => { - console.log(`Metrics for '${testName}' sent successfully:`, response); - this.resetMetrics(); - }, + next: () => this.resetMetrics(), error: (err) => console.error(`Failed to send metrics for '${testName}':`, err) }) ); } public resetMetrics(): void { - this.metricsLog = { interactions: [], deviceOrientations: [], arData: [] }; + this.metricsLog = { interactions: [], deviceOrientations: [], arTrackingData: [] }; + } + + private quaternionToEuler(x: number, y: number, z: number, w: number): { pitch: number, yaw: number, roll: number } { + const sinr_cosp = 2 * (w * x + y * z); + const cosr_cosp = 1 - 2 * (x * x + y * y); + const roll = Math.atan2(sinr_cosp, cosr_cosp); + + const sinp = 2 * (w * y - z * x); + const pitch = Math.abs(sinp) >= 1 ? (Math.PI / 2) * Math.sign(sinp) : Math.asin(sinp); + + const siny_cosp = 2 * (w * z + x * y); + const cosy_cosp = 1 - 2 * (y * y + z * z); + const yaw = Math.atan2(siny_cosp, cosy_cosp); + + return { + pitch: pitch * (180 / Math.PI), + yaw: yaw * (180 / Math.PI), + roll: roll * (180 / Math.PI) + }; } }