implemented test suite
This commit is contained in:
1 parent
ff12bb40ad
commit
458a1f360e
29 files changed
+1233
-382
No files matched your search
+25
-4
@@ -1,5 +1,26 @@
|
||||
.ar-controls {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.annotation {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.ar-hotspot {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ar-prompt {
|
||||
display: none;
|
||||
}
|
||||
|
||||
*/
|
||||
model-viewer[ar-status="session-started"]:not([ar-tracking="tracking"]) .ar-prompt {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
model-viewer[ar-tracking="tracking"] .ar-hotspot,
|
||||
model-viewer[ar-tracking="tracking"] .ar-controls {
|
||||
display: block;
|
||||
}
|
||||
|
||||
model-viewer[ar-tracking="tracking"] .ar-controls {
|
||||
display: flex;
|
||||
}
|
||||
+72
-55
@@ -1,56 +1,73 @@
|
||||
<div class="w-full h-screen relative">
|
||||
<div class="absolute top-0 left-0 p-6 z-10 bg-white bg-opacity-80 rounded-br-lg">
|
||||
<h3 class="text-lg font-bold">Assessment: Text Legibility Optimization</h3>
|
||||
<p class="text-sm mt-2"><strong>Objective:</strong> Determine optimal text scaling for ceiling-AR applications.</p>
|
||||
<p class="text-sm"><strong>Procedure:</strong> Adjust text size using the WebXR slider until it is clearly legible.</p>
|
||||
</div>
|
||||
|
||||
<model-viewer
|
||||
#modelViewer
|
||||
id="text-model"
|
||||
src="https://modelviewer.dev/shared-assets/models/Astronaut.glb"
|
||||
ar
|
||||
ar-modes="webxr"
|
||||
ar-placement="ceiling"
|
||||
camera-orbit="0deg 75deg 2m"
|
||||
reveal="manual"
|
||||
class="w-full h-full"
|
||||
>
|
||||
<button slot="ar-button" class="bg-blue-500 text-white font-bold py-2 px-4 rounded-lg absolute bottom-4 right-4 z-10">
|
||||
Enter WebXR - Text Size Test
|
||||
</button>
|
||||
|
||||
<!-- Hotspot with annotation text -->
|
||||
<button class="hotspot" slot="hotspot-text" data-position="0.2 0.1 0.1" data-normal="0 1 0">
|
||||
<div
|
||||
class="annotation"
|
||||
[ngStyle]="{'font-size.px': textSize}"
|
||||
style="background: rgba(255,255,255,0.9); padding: 10px; border-radius: 5px; color: black; width: 250px;"
|
||||
>
|
||||
This sample text represents typical information display requirements for ceiling-AR applications.
|
||||
Text must remain legible despite challenging viewing angles inherent to overhead displays.
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<!-- UI container for the text size slider, visible only in WebXR -->
|
||||
<div
|
||||
*ngIf="isInWebXR"
|
||||
class="absolute bottom-24 left-1/2 -translate-x-1/2 flex flex-col items-center space-y-4 bg-black bg-opacity-60 p-5 rounded-xl"
|
||||
>
|
||||
<div class="flex flex-col items-center text-white w-64">
|
||||
<label for="text-size-slider" class="text-sm font-medium mb-2">Text Size: {{ textSize }}px</label>
|
||||
<input
|
||||
id="text-size-slider"
|
||||
type="range"
|
||||
min="10"
|
||||
max="32"
|
||||
step="1"
|
||||
[(ngModel)]="textSize"
|
||||
(input)="updateTextSize()"
|
||||
class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full h-full relative">
|
||||
<model-viewer
|
||||
#modelViewer
|
||||
class="absolute inset-0 w-full h-full"
|
||||
src="https://modelviewer.dev/shared-assets/models/Astronaut.glb"
|
||||
ar
|
||||
ar-modes="webxr"
|
||||
ar-placement="ceiling"
|
||||
reveal="manual"
|
||||
camera-orbit="0deg 75deg 2m">
|
||||
|
||||
<button
|
||||
slot="ar-button"
|
||||
class="text-xl absolute top-4 left-1/2 -translate-x-1/2 bg-blue-500 text-white py-2 px-4 rounded-lg z-10">
|
||||
WebXR Umgebung laden
|
||||
</button>
|
||||
<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, 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. 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>
|
||||
</model-viewer>
|
||||
</div>
|
||||
|
||||
</button>
|
||||
<div class="ar-controls absolute bottom-24 left-1/2 -translate-x-1/2 bg-black bg-opacity-70 p-4 rounded-xl space-y-4 text-white z-10">
|
||||
|
||||
<!-- Phase: Min -->
|
||||
<ng-container *ngIf="phase==='min'">
|
||||
<div>Minimum setzen</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<button (click)="decrease()" class="w-10 h-10 bg-gray-600 rounded-full">–</button>
|
||||
<span>{{ currentSize }}px</span>
|
||||
<button (click)="increase()" class="w-10 h-10 bg-gray-600 rounded-full">+</button>
|
||||
</div>
|
||||
<button (click)="nextPhase()" class="bg-green-600 py-2 px-6 rounded-lg">Minimum bestätigen</button>
|
||||
</ng-container>
|
||||
|
||||
<!-- Phase: Confirmed Min -->
|
||||
<ng-container *ngIf="phase==='confirmedMin'">
|
||||
<div>Minimum bestätigt: {{ currentSize }}px</div>
|
||||
<button (click)="resetToMid()" class="bg-gray-600 py-2 px-4 rounded-lg">Reset</button>
|
||||
<button (click)="nextPhase()" class="bg-blue-600 py-2 px-6 rounded-lg">Weiter zu Maximum</button>
|
||||
</ng-container>
|
||||
|
||||
<!-- Phase: Max -->
|
||||
<ng-container *ngIf="phase==='max'">
|
||||
<div>Maximum setzen</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<button (click)="decrease()" class="w-10 h-10 bg-gray-600 rounded-full">–</button>
|
||||
<span>{{ currentSize }}px</span>
|
||||
<button (click)="increase()" class="w-10 h-10 bg-gray-600 rounded-full">+</button>
|
||||
</div>
|
||||
<button (click)="nextPhase()" class="bg-green-600 py-2 px-6 rounded-lg">Maximum bestätigen</button>
|
||||
</ng-container>
|
||||
|
||||
<!-- Phase: Confirmed Max -->
|
||||
<ng-container *ngIf="phase==='confirmedMax'">
|
||||
<div>Maximum bestätigt: {{ currentSize }}px</div>
|
||||
<button (click)="nextPhase()" class="bg-blue-600 py-2 px-6 rounded-lg">Weiter zu optiomal</button>
|
||||
</ng-container>
|
||||
|
||||
<!-- Phase: Comfortable -->
|
||||
<ng-container *ngIf="phase==='comfortable'">
|
||||
<div>Optimale Größe setzen</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<button (click)="decrease()" class="w-10 h-10 bg-gray-600 rounded-full">–</button>
|
||||
<span>{{ currentSize }}px</span>
|
||||
<button (click)="increase()" class="w-10 h-10 bg-gray-600 rounded-full">+</button>
|
||||
</div>
|
||||
<button (click)="finishAssessment()" class="bg-green-600 py-2 px-6 rounded-lg">Optimale Größe setzen</button>
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
</model-viewer>
|
||||
</div>
|
||||
+99
-17
@@ -1,42 +1,124 @@
|
||||
import { Component, AfterViewInit, ViewChild, ElementRef, ChangeDetectorRef, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
AfterViewInit,
|
||||
ViewChild,
|
||||
ElementRef,
|
||||
ChangeDetectorRef,
|
||||
CUSTOM_ELEMENTS_SCHEMA,
|
||||
EventEmitter,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import '../../../../../assets/scripts/model-viewer';
|
||||
|
||||
@Component({
|
||||
selector: 'app-text-legibility-assessment',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule],
|
||||
templateUrl: './text-legibility-assessment.component.html',
|
||||
styleUrl: './text-legibility-assessment.component.css',
|
||||
styleUrls: ['./text-legibility-assessment.component.css'],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class TextLegibilityAssessmentComponent implements AfterViewInit {
|
||||
@ViewChild('modelViewer') modelViewerRef!: ElementRef<any>;
|
||||
@Output() testComplete = new EventEmitter<void>();
|
||||
@Output() redoTest = new EventEmitter<number>();
|
||||
|
||||
isInWebXR = false;
|
||||
textSize = 16;
|
||||
minSize = 2;
|
||||
maxSize = 64;
|
||||
currentSize = 16;
|
||||
comfortableSize = 16;
|
||||
|
||||
phase:
|
||||
| 'min'
|
||||
| 'confirmedMin'
|
||||
| 'max'
|
||||
| 'confirmedMax'
|
||||
| 'comfortable'
|
||||
| 'confirmedComfort'
|
||||
| 'finished' = 'min';
|
||||
|
||||
private offsetApplied = false;
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {}
|
||||
|
||||
ngAfterViewInit() {
|
||||
const modelViewerElement = this.modelViewerRef.nativeElement;
|
||||
const mv = this.modelViewerRef.nativeElement;
|
||||
mv.setAttribute('scale', '0.25 0.25 0.25');
|
||||
|
||||
modelViewerElement.addEventListener('enter-vr', () => {
|
||||
this.isInWebXR = true;
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
|
||||
modelViewerElement.addEventListener('exit-vr', () => {
|
||||
this.isInWebXR = false;
|
||||
this.resetTextSize();
|
||||
this.cdr.detectChanges();
|
||||
mv.addEventListener('ar-status', async (e: any) => {
|
||||
if (e.detail.status === 'session-started' && !this.offsetApplied) {
|
||||
await mv.updateComplete;
|
||||
const anchor = mv.getAnchor();
|
||||
if (!anchor.includes('not placed')) {
|
||||
const [x, y, z] = anchor.split(' ').map(parseFloat);
|
||||
mv.setAttribute('ar-anchor', `${x} ${y + 3.0} ${z}`);
|
||||
this.offsetApplied = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateTextSize() {
|
||||
decrease() {
|
||||
if (this.currentSize > this.minSize) {
|
||||
this.currentSize--;
|
||||
this.syncComfortable();
|
||||
}
|
||||
}
|
||||
|
||||
resetTextSize() {
|
||||
this.textSize = 16;
|
||||
increase() {
|
||||
if (this.currentSize < this.maxSize) {
|
||||
this.currentSize++;
|
||||
this.syncComfortable();
|
||||
}
|
||||
}
|
||||
|
||||
private syncComfortable() {
|
||||
if (this.phase === 'comfortable') {
|
||||
this.comfortableSize = this.currentSize;
|
||||
}
|
||||
}
|
||||
|
||||
nextPhase() {
|
||||
switch (this.phase) {
|
||||
case 'min':
|
||||
this.phase = 'confirmedMin';
|
||||
break;
|
||||
case 'confirmedMin':
|
||||
this.phase = 'max';
|
||||
this.currentSize = this.maxSize;
|
||||
break;
|
||||
case 'max':
|
||||
this.phase = 'confirmedMax';
|
||||
break;
|
||||
case 'confirmedMax':
|
||||
this.phase = 'comfortable';
|
||||
this.currentSize = Math.floor((this.minSize + this.maxSize) / 2);
|
||||
this.comfortableSize = this.currentSize;
|
||||
break;
|
||||
case 'comfortable':
|
||||
this.phase = 'confirmedComfort';
|
||||
this.comfortableSize = this.currentSize;
|
||||
break;
|
||||
case 'confirmedComfort':
|
||||
this.phase = 'finished';
|
||||
this.testComplete.emit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
resetToMid() {
|
||||
this.currentSize = Math.floor((this.minSize + this.maxSize) / 2);
|
||||
this.phase = 'min';
|
||||
}
|
||||
|
||||
retry() {
|
||||
this.redoTest.emit(1);
|
||||
}
|
||||
|
||||
finishAssessment() {
|
||||
this.testComplete.emit();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user