master
MrPlatnum 2025-09-15 09:51:27 +02:00
parent 4cdca6b4bf
commit 4b4759b29e
2 changed files with 116 additions and 84 deletions

View File

@ -11,8 +11,9 @@
<button slot="hotspot-text" class="hotspot ar-hotspot" data-position="-0.1 0.93 0.1" data-normal="0 1 0">
<div class="annotation" [ngStyle]="{'font-size.px': currentSize}"
style="background:rgba(255,255,255,0.9);padding:8px;border-radius:4px;color:black;">
Lorem ipsum dolor sit amet...
</div>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. </div>
</button>
<div
@ -91,4 +92,4 @@
</div>
</model-viewer>
</div>
</div>

View File

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