From 2654d1178bd4a6a163521296528730f49aa32182 Mon Sep 17 00:00:00 2001 From: MrPlatnum Date: Mon, 15 Sep 2025 10:08:33 +0200 Subject: [PATCH] fix --- src/app/services/metrics-tracker.service.ts | 385 +++++++++++--------- 1 file changed, 217 insertions(+), 168 deletions(-) diff --git a/src/app/services/metrics-tracker.service.ts b/src/app/services/metrics-tracker.service.ts index 6218193..86d347c 100644 --- a/src/app/services/metrics-tracker.service.ts +++ b/src/app/services/metrics-tracker.service.ts @@ -3,206 +3,255 @@ import { inject, Injectable } from '@angular/core'; import { tap } from 'rxjs'; import { v4 as uuidv4 } from 'uuid'; + export interface InteractionEvent { - timestamp: number; - type: string; - elementId?: string; - elementTag?: string; - elementClasses?: string; - value?: any; + timestamp: number; + type: string; + elementId?: string; + elementTag?: string; + elementClasses?: string; + value?: any; } + export interface DeviceOrientation { - timestamp: number; - alpha: number | null; - beta: number | null; - gamma: number | null; + timestamp: number; + alpha: number | null; + beta: number | null; + gamma: number | null; } + export interface ArTrackingData { - timestamp: number; - anchor: string | 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; + timestamp: number; + anchor: string | 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[]; - arTrackingData: ArTrackingData[]; + interactions: InteractionEvent[]; + deviceOrientations: DeviceOrientation[]; + arTrackingData: ArTrackingData[]; } + @Injectable({ - providedIn: 'root' + providedIn: 'root' }) export class MetricsTrackerService { - private http = inject(HttpClient); - private serverUrl = '/api/log'; - private deviceId: string; + private http = inject(HttpClient); + private serverUrl = '/api/log'; + private deviceId: string; - private metricsLog: MetricsLog = { - interactions: [], - deviceOrientations: [], - arTrackingData: [] - }; - private xrFrameSubscriptionId: number | null = null; - private lastDeviceOrientation: DeviceOrientationEvent | null = null; - private modelViewerElement: any = null; - private xrFrameCount = 0; + private metricsLog: MetricsLog = { + interactions: [], + deviceOrientations: [], + arTrackingData: [] + }; - constructor() { - this.handleDeviceOrientation = this.handleDeviceOrientation.bind(this); - this.onXrFrame = this.onXrFrame.bind(this); - const storedId = localStorage.getItem('device-uuid'); - this.deviceId = storedId || uuidv4(); - if (!storedId) { - localStorage.setItem('device-uuid', this.deviceId); - } + private xrFrameSubscriptionId: number | null = null; + private lastDeviceOrientation: DeviceOrientationEvent | null = null; + private modelViewerElement: any = null; + private xrFrameCount = 0; - if (typeof window !== 'undefined') { - window.addEventListener('deviceorientation', this.handleDeviceOrientation, true); - } + + constructor() { + this.handleDeviceOrientation = this.handleDeviceOrientation.bind(this); + this.onXrFrame = this.onXrFrame.bind(this); + + + const storedId = localStorage.getItem('device-uuid'); + this.deviceId = storedId || uuidv4(); + if (!storedId) { + localStorage.setItem('device-uuid', this.deviceId); } - private handleDeviceOrientation(event: DeviceOrientationEvent): void { - this.lastDeviceOrientation = event; + + if (typeof window !== 'undefined') { + window.addEventListener('deviceorientation', this.handleDeviceOrientation, true); } + } - public logInteraction(event: Event): void { - 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; + private handleDeviceOrientation(event: DeviceOrientationEvent): void { + this.lastDeviceOrientation = event; + } - if (this.modelViewerElement.xrSession) { - this.startXrTracking(this.modelViewerElement.xrSession); - } - } - private startXrTracking(xrSession: any): void { - if (this.xrFrameSubscriptionId === null) { - this.xrFrameCount = 0; - this.xrFrameSubscriptionId = xrSession.requestAnimationFrame(this.onXrFrame); - } - } + public logInteraction(event: Event): void { + const timestamp = Date.now(); + const target = event.target as HTMLElement; + let interaction: InteractionEvent; - private onXrFrame(time: DOMHighResTimeStamp, frame: any): void { - const session = frame.session; - this.xrFrameSubscriptionId = session.requestAnimationFrame(this.onXrFrame); - this.xrFrameCount++; - if (this.xrFrameCount % 30 !== 0) { - return; - } - - 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.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) { - const payload = { - testName, - deviceId: this.deviceId, - metricsLog: this.metricsLog, - ...(formData && { formData }) - }; - - this.stopTracking(); - return this.http.post(this.serverUrl, payload).pipe( - tap({ - next: () => this.resetMetrics(), - error: (err) => console.error(`Failed to send metrics for '${testName}':`, err) - }) - ); - } - - public resetMetrics(): void { - this.metricsLog = { interactions: [], deviceOrientations: [], arTrackingData: [] }; + if (target) { + // Standard DOM event with a target + interaction = { + timestamp, + type: event.type, + elementId: target.id || undefined, + elementTag: target.tagName, + elementClasses: target.className, + value: (target as any).value ?? undefined + }; + } else if (event instanceof CustomEvent) { + // CustomEvent, likely without a target + interaction = { + timestamp, + type: event.type, + value: event.detail + }; + } else { + // Fallback for other events without a target + interaction = { + timestamp, + type: event.type + }; } - 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); + this.metricsLog.interactions.push(interaction); + } - 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) - }; + public startTracking(modelViewerElement: any): void { + this.stopTracking(); + if (!modelViewerElement) { + console.error("startTracking called with no modelViewerElement."); + return; } + this.modelViewerElement = modelViewerElement; + + + if (this.modelViewerElement.xrSession) { + this.startXrTracking(this.modelViewerElement.xrSession); + } + } + + + private startXrTracking(xrSession: any): void { + if (this.xrFrameSubscriptionId === null) { + this.xrFrameCount = 0; + this.xrFrameSubscriptionId = xrSession.requestAnimationFrame(this.onXrFrame); + } + } + + + private onXrFrame(time: DOMHighResTimeStamp, frame: any): void { + const session = frame.session; + this.xrFrameSubscriptionId = session.requestAnimationFrame(this.onXrFrame); + + + this.xrFrameCount++; + if (this.xrFrameCount % 30 !== 0) { + return; + } + + + 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.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) { + const payload = { + testName, + deviceId: this.deviceId, + metricsLog: this.metricsLog, + ...(formData && { formData }) + }; + + this.stopTracking(); + return this.http.post(this.serverUrl, payload).pipe( + tap({ + next: () => this.resetMetrics(), + error: (err) => console.error(`Failed to send metrics for '${testName}':`, err) + }) + ); + } + + + public resetMetrics(): void { + 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) + }; + } }