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