-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(frontend): load obj in web worker & display progress-spinner (#42)
* refactor(frontend): emit only if connection status changes * doc(ui): add how to add new module & component with `ng g` * refactor(shared): export `Nil` type * feat(ui): add fullscreen-overlay module * feat(ui): add simple progress-spinner component * feat(ui): set `ChangeDetectionStrategy.OnPush` * doc(kafka): stop/remove all docker containers with values * refactor(ui): export progress-spinner module & include it in specs * feat(frontend): add web worker for loading obj file * feat(frontend): show progress-spinner while loading obj in worker * test(frontend): provide mocks * style(frontend): fix order
- Loading branch information
1 parent
637a158
commit f0c11db
Showing
27 changed files
with
468 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
.../src/app/scene-viewer-container/load-file-container/load-file-container.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { OverlayRef } from '@angular/cdk/overlay'; | ||
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; | ||
import { provideMockStore } from '@ngrx/store/testing'; | ||
import { UiProgressSpinnerModule, UiSceneViewerTestModule, UI_OVERLAY_DATA } from '@talus/ui'; | ||
import { of } from 'rxjs'; | ||
import * as fromApp from '../../app.reducer'; | ||
import { initialMockState } from '../../testing'; | ||
import { LoadFileContainerComponent } from './load-file-container.component'; | ||
import { LoadFileService } from './load-file.service'; | ||
|
||
describe('LoadFileContainerComponent', () => { | ||
let component: LoadFileContainerComponent; | ||
let fixture: ComponentFixture<LoadFileContainerComponent>; | ||
|
||
beforeEach(async(() => { | ||
TestBed.configureTestingModule({ | ||
declarations: [LoadFileContainerComponent], | ||
imports: [UiSceneViewerTestModule, UiProgressSpinnerModule], | ||
providers: [ | ||
{ provide: UI_OVERLAY_DATA, useValue: {} }, | ||
{ provide: OverlayRef, useValue: {} }, | ||
{ | ||
provide: LoadFileService, | ||
useValue: { | ||
load: () => of({ coords: [], isConverting: false, isLoading: false, progress: 100 }), | ||
}, | ||
}, | ||
provideMockStore<fromApp.State>({ | ||
initialState: initialMockState, | ||
}), | ||
], | ||
}).compileComponents(); | ||
})); | ||
|
||
beforeEach(() => { | ||
fixture = TestBed.createComponent(LoadFileContainerComponent); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
}); |
74 changes: 74 additions & 0 deletions
74
...ntend/src/app/scene-viewer-container/load-file-container/load-file-container.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { OverlayRef } from '@angular/cdk/overlay'; | ||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; | ||
import { Action, Store } from '@ngrx/store'; | ||
import { rgbaToInt } from '@talus/model'; | ||
import { finalizeWithValue } from '@talus/shared'; | ||
import { UI_OVERLAY_DATA } from '@talus/ui'; | ||
import { Coord } from '@talus/vdb'; | ||
import { tap } from 'rxjs/operators'; | ||
import * as fromApp from '../../app.reducer'; | ||
import { setVoxels } from '../scene-viewer-container.actions'; | ||
import { LoadFileService } from './load-file.service'; | ||
|
||
@Component({ | ||
selector: 'fe-load-file-container', | ||
template: ` | ||
<ui-progress-spinner | ||
*ngIf="load$ | async" | ||
[mode]="mode" | ||
[status]="status" | ||
[value]="progress" | ||
></ui-progress-spinner> | ||
`, | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
}) | ||
export class LoadFileContainerComponent { | ||
mode: 'indeterminate' | 'determinate' = 'indeterminate'; | ||
progress = 0; | ||
status = 'Loading file...'; | ||
|
||
load$ = this.loadFileService.load(this.data).pipe( | ||
tap(status => { | ||
if (status.isConverting) { | ||
this.mode = 'determinate'; | ||
this.status = 'Converting...'; | ||
} | ||
|
||
this.progress = status.progress; | ||
}), | ||
finalizeWithValue(status => { | ||
this.status = 'Adding voxels...'; | ||
|
||
setTimeout(() => { | ||
const action = this.getActionFromCoords(status.coords); | ||
this.store.dispatch(action); | ||
|
||
this.overlayRef.detach(); | ||
}, 500); | ||
}), | ||
); | ||
|
||
constructor( | ||
@Inject(UI_OVERLAY_DATA) private readonly data: File, | ||
private readonly overlayRef: OverlayRef, | ||
private readonly store: Store<fromApp.State>, | ||
private readonly loadFileService: LoadFileService, | ||
) {} | ||
|
||
private getActionFromCoords(coords: Coord[]): Action { | ||
const colors: number[] = []; | ||
const defaultColor = rgbaToInt({ r: 0, g: 255, b: 0, a: 255 }); | ||
const scaleFactor = 50; | ||
|
||
for (let i = 0; i < coords.length; i++) { | ||
coords[i] = [ | ||
coords[i][0] * scaleFactor, | ||
coords[i][1] * scaleFactor, | ||
coords[i][2] * scaleFactor, | ||
]; | ||
colors.push(defaultColor); | ||
} | ||
|
||
return setVoxels({ coords, newValues: colors, needsSync: true }); | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
...frontend/src/app/scene-viewer-container/load-file-container/load-file-container.module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { CommonModule } from '@angular/common'; | ||
import { NgModule } from '@angular/core'; | ||
import { UiProgressSpinnerModule } from '@talus/ui'; | ||
import { LoadFileContainerComponent } from './load-file-container.component'; | ||
import { LoadFileService } from './load-file.service'; | ||
|
||
@NgModule({ | ||
declarations: [LoadFileContainerComponent], | ||
imports: [CommonModule, UiProgressSpinnerModule], | ||
exports: [LoadFileContainerComponent], | ||
providers: [LoadFileService], | ||
}) | ||
export class LoadFileContainerModule {} |
31 changes: 31 additions & 0 deletions
31
apps/frontend/src/app/scene-viewer-container/load-file-container/load-file.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { Injectable } from '@angular/core'; | ||
import { Observable } from 'rxjs'; | ||
import { LoadFileStatus } from './load-file.worker'; | ||
|
||
@Injectable() | ||
export class LoadFileService { | ||
private worker: Worker; | ||
|
||
constructor() { | ||
if (typeof Worker !== 'undefined') { | ||
this.worker = new Worker('./load-file.worker', { type: 'module' }); | ||
} else { | ||
// Add a fallback so that program still executes correctly. | ||
console.log('Web workers are not supported in this environment.'); | ||
} | ||
} | ||
|
||
load(file: File): Observable<LoadFileStatus> { | ||
return new Observable<LoadFileStatus>(subscriber => { | ||
this.worker.onmessage = ({ data: status }) => { | ||
subscriber.next(status); | ||
|
||
if (status.progress === 100) { | ||
subscriber.complete(); | ||
} | ||
}; | ||
|
||
this.worker.postMessage(file); | ||
}); | ||
} | ||
} |
76 changes: 76 additions & 0 deletions
76
apps/frontend/src/app/scene-viewer-container/load-file-container/load-file.worker.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/// <reference lib="webworker" /> | ||
|
||
import { AssetContainer } from '@babylonjs/core'; | ||
import { NullEngine } from '@babylonjs/core/Engines/nullEngine'; | ||
import { SceneLoader } from '@babylonjs/core/Loading/sceneLoader'; | ||
import { VertexBuffer } from '@babylonjs/core/Meshes/buffer'; | ||
import { Scene } from '@babylonjs/core/scene'; | ||
import { FloatArray } from '@babylonjs/core/types'; | ||
// https://doc.babylonjs.com/how_to/obj | ||
import '@babylonjs/loaders/OBJ/objFileLoader'; | ||
import { Coord } from '@talus/vdb'; | ||
|
||
export interface LoadFileStatus { | ||
coords: Coord[]; | ||
isConverting: boolean; | ||
isLoading: boolean; | ||
progress: number; | ||
} | ||
|
||
addEventListener('message', ({ data: file }) => { | ||
loadFile(file); | ||
}); | ||
|
||
function loadFile(file: File): void { | ||
// SceneLoader needs a scene. A scene object can't be passed/cloned to the worker. | ||
// Therefore, create a no-op scene. | ||
const nullScene = new Scene(new NullEngine()); | ||
|
||
postStatusMessage({ coords: [], isConverting: false, isLoading: true, progress: 0 }); | ||
SceneLoader.LoadAssetContainer('file:', file, nullScene, onSuccess); | ||
|
||
function onSuccess(assets: AssetContainer): void { | ||
postStatusMessage({ coords: [], isConverting: true, isLoading: false, progress: 0 }); | ||
|
||
const positions = getPositions(assets); | ||
const coords = floatArrayToCoords(positions); | ||
|
||
postStatusMessage({ coords, isConverting: false, isLoading: false, progress: 100 }); | ||
} | ||
} | ||
|
||
function postStatusMessage(status: LoadFileStatus): void { | ||
postMessage(status); | ||
} | ||
|
||
function getPositions(assets: AssetContainer): FloatArray { | ||
if (assets.meshes.length < 1) { | ||
return []; | ||
} | ||
|
||
const positions = assets.meshes[0].getVerticesData(VertexBuffer.PositionKind); | ||
if (!positions) { | ||
return []; | ||
} | ||
|
||
return positions; | ||
} | ||
|
||
function floatArrayToCoords(positions: FloatArray): Coord[] { | ||
const coords: Coord[] = []; | ||
|
||
const progressFactor = 100 / positions.length; | ||
let progress = 0; | ||
|
||
for (let i = 0; i < positions.length; i += 3) { | ||
coords.push([positions[i], positions[i + 1], positions[i + 2]]); | ||
|
||
const currentProgress = Math.trunc(i * progressFactor); | ||
if (progress !== currentProgress) { | ||
progress = currentProgress; | ||
postStatusMessage({ coords: [], isConverting: true, isLoading: false, progress }); | ||
} | ||
} | ||
|
||
return coords; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"extends": "../../tsconfig.json", | ||
"compilerOptions": { | ||
"outDir": "../../out-tsc/worker", | ||
"lib": ["es2018", "webworker"], | ||
"types": [] | ||
}, | ||
"include": ["src/**/*.worker.ts"] | ||
} |
Oops, something went wrong.