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

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,7 +74,6 @@ export class MetricsTrackerService {
}
public logInteraction(event: Event): void {
if (event.target) {
const target = event.target as HTMLElement;
const interaction: InteractionEvent = {
timestamp: Date.now(),
@ -92,63 +84,86 @@ export class MetricsTrackerService {
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);
}
}
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);
}
}
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;
// 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;
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: timestamp,
anchor: anchor.includes('not placed') ? null : anchor,
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.arData.push(arData);
this.metricsLog.arTrackingData.push(arData);
// Capture Device Orientation
if (this.lastDeviceOrientation) {
const orientation: DeviceOrientation = {
timestamp: timestamp,
this.metricsLog.deviceOrientations.push({
timestamp,
alpha: this.lastDeviceOrientation.alpha,
beta: this.lastDeviceOrientation.beta,
gamma: this.lastDeviceOrientation.gamma
};
this.metricsLog.deviceOrientations.push(orientation);
}
});
}
}
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();
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)
};
}
}