test
parent
4cdca6b4bf
commit
4b4759b29e
|
|
@ -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>
|
||||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue