Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(frontend): add basic line drawing #28

Merged
merged 8 commits into from
Jan 31, 2020
106 changes: 88 additions & 18 deletions apps/frontend/src/app/scene-viewer-container/grid.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import { Injectable } from '@angular/core';
import { intToRgba } from '@talus/model';
import { Coord, Grid, MeshData, nodeToMesh } from '@talus/vdb';
import {
add,
areEqual,
Coord,
DDA,
Grid,
MeshData,
nodeToMesh,
Ray,
TimeSpan,
Vec3,
VolumeRayIntersector,
Voxel,
} from '@talus/vdb';

const COLOR_FACTOR = 1 / 255;

/**
* Keeps the mutable state of the single grid. This state is not part of the store, due to
Expand All @@ -13,19 +28,6 @@ export class GridService {
grid = new Grid(0);
accessor = this.grid.getAccessor();

colors = {
0: [0, 0, 0, 1],
1: [0, 0, 1, 1],
2: [0, 1, 0, 1],
3: [0, 1, 1, 1],
4: [1, 0, 0, 1],
5: [1, 0, 1, 1],
6: [1, 1, 0, 1],
7: [1, 1, 1, 1],
};

private readonly alphaFactor = 1 / 255;

/**
* Sets a new voxel via accessor to share access path.
* @returns origin of `InternalNode1` of affected node (node containing added voxel).
Expand Down Expand Up @@ -85,14 +87,82 @@ export class GridService {
return nodeToMesh(internal1, this.valueToColor);
}

selectLine(points: Coord[], newValue: number): VoxelChange[] {
const start = points[0];
const startCenter = add(start, [0.5, 0.5, 0.5]);
const end = points[1];
const endCenter = add(end, [0.5, 0.5, 0.5]);

// Set start & end to ensure leaf-nodes are created in the grid.
// Otherwise, it could happen that the start/end point is in a new leaf which
// doesn't yet exist and therefore doesn't cause any intersection.
this.setVoxels(points, [newValue, newValue]);

const ray = this.createIntersectionRay(startCenter, endCenter);

const totalTimeSpan = TimeSpan.inf();
if (!this.findTotalTimeSpan(ray, totalTimeSpan)) {
return [];
}

return this.setVoxelsAlongRayUntilLastVoxel(ray, totalTimeSpan, end, newValue);
}

private createIntersectionRay(startXyz: Coord, endXyz: Coord): Ray {
const eye = new Vec3(startXyz[0], startXyz[1], startXyz[2]);
const direction = new Vec3(
endXyz[0] - startXyz[0],
endXyz[1] - startXyz[1],
endXyz[2] - startXyz[2],
);

return new Ray(eye, direction);
}

private findTotalTimeSpan(ray: Ray, timeSpanRef: TimeSpan): boolean {
const intersector = new VolumeRayIntersector(this.grid);

// Does ray intersect
if (!intersector.setIndexRay(ray)) {
return false;
}

return intersector.marchUntilEnd(timeSpanRef);
}

private setVoxelsAlongRayUntilLastVoxel(
ray: Ray,
timeSpan: TimeSpan,
lastVoxel: Coord,
newValue: number,
): VoxelChange[] {
const dda = new DDA(Voxel.LOG2DIM);
dda.init(ray, timeSpan.t0, timeSpan.t1);

const coords: Coord[] = [];
const values: number[] = [];

do {
const voxelCoord = dda.getVoxel();
coords.push(voxelCoord);
values.push(newValue);

if (areEqual(lastVoxel, voxelCoord)) {
break;
}
} while (dda.nextStep());

return this.setVoxels(coords, values);
}

private valueToColor = (value: number): [number, number, number, number] => {
const rgba = intToRgba(value);

return [
rgba.r * this.alphaFactor,
rgba.g * this.alphaFactor,
rgba.b * this.alphaFactor,
rgba.a * this.alphaFactor,
rgba.r * COLOR_FACTOR,
rgba.g * COLOR_FACTOR,
rgba.b * COLOR_FACTOR,
rgba.a * COLOR_FACTOR,
];
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,9 @@ export const paintVoxel = createAction(
);
export const paintVoxelFailed = createAction(`${actionTypePrefix} Paint voxel failed`);
export const voxelPainted = createAction(`${actionTypePrefix} Voxel painted`, props<VoxelChange>());

// Select line point
export const selectLinePoint = createAction(
`${actionTypePrefix} Select line point`,
props<{ xyz: Coord; newValue: number }>(),
);
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,22 @@ describe('SceneViewerContainerComponent', () => {
it.each([
[
[1, 0.2, 0.9],
[1, 0.2, 0.9],
[1, 0, 0], // fraction part is removed
[1, 0, 0],
],
[
[0.99999999, 0.2, 0.9],
[1, 0.2, 0.9],
[1, 0, 0],
[1, 0, 0],
],
[
[0.2, 0.99999999, 0.9],
[0.2, 1, 0.9],
[0, 1, 0],
[0, 1, 0],
],
[
[0.2, 0.9, 0.0000000001],
[0.2, 0.9, 0],
[0, 0, 0],
[0, 0, 1],
],
[
Expand All @@ -111,12 +111,12 @@ describe('SceneViewerContainerComponent', () => {
],
[
[-0, -2, 3],
[-0, -3, 2],
[0, -3, 2],
[0, 0, -1],
],
[
[0.5, 1.4, -1],
[0.5, 1.4, -1],
[0, 1, -1],
[0, 0, 1],
],
])(
Expand Down Expand Up @@ -150,37 +150,37 @@ describe('SceneViewerContainerComponent', () => {
it.each([
[
[1.0000000000001, 0.5, 0.5],
[0, 0.5, 0.5],
[0, 0, 0], // fraction part is removed
[1, 0, 0],
],
[
[0.999999999999, 0.5, 0.5],
[1, 0.5, 0.5],
[1, 0, 0],
[-1, 0, 0],
],
[
[1.0000000000001, -0.5, 0.5],
[1, -1.5, 0.5],
[1, -1, 0],
[-1, 0, 0],
],
[
[-0.999999999999, -0.5, 0.5],
[-1, -1.5, 0.5],
[-1, -1, 0],
[-1, 0, 0],
],
[
[-2.0000000000001, -0.5, 0.5],
[-3, -1.5, 0.5],
[-3, -1, 0],
[1, 0, 0],
],
[
[0.5, 0.999999999999, 0.5],
[0.5, 0, 0.5],
[0, 0, 0],
[0, 1, 0],
],
[
[0.5461420538559825, 0.4841910809236776, -2],
[0.5461420538559825, 0.4841910809236776, -2],
[0, 0, -2],
[0, 0, -1],
],
])(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, ViewChild } from '@a
import { select, Store } from '@ngrx/store';
import { Rgba, rgbaToInt, Tool } from '@talus/model';
import { UiPointerButton, UiPointerPickInfo, UiSceneViewerComponent } from '@talus/ui';
import { Coord } from '@talus/vdb';
import { Coord, removeFraction } from '@talus/vdb';
import { combineLatest, Observable } from 'rxjs';
import * as fromApp from '../app.reducer';
import { paintVoxel, removeVoxel, setVoxel, setVoxels } from './scene-viewer-container.actions';
import {
paintVoxel,
removeVoxel,
selectLinePoint,
setVoxel,
setVoxels,
} from './scene-viewer-container.actions';

@Component({
selector: 'fe-scene-viewer-container',
Expand Down Expand Up @@ -70,21 +76,22 @@ export class SceneViewerContainerComponent implements AfterViewInit {
return;
}

const colorInt = rgbaToInt(selectedColor);
const newValue = rgbaToInt(selectedColor);

switch (selectedToolId) {
case Tool.SetVoxel:
case Tool.SelectLinePoint:
this.store.dispatch(
setVoxel({ xyz: this.calcVoxelToAddPosition(pickInfo), newValue: colorInt }),
selectLinePoint({ xyz: this.calcVoxelToAddPosition(pickInfo), newValue }),
);
break;
case Tool.SetVoxel:
this.store.dispatch(setVoxel({ xyz: this.calcVoxelToAddPosition(pickInfo), newValue }));
break;
case Tool.RemoveVoxel:
this.store.dispatch(removeVoxel({ xyz: this.calcClickedVoxelPosition(pickInfo) }));
break;
case Tool.PaintVoxel:
this.store.dispatch(
paintVoxel({ xyz: this.calcClickedVoxelPosition(pickInfo), newValue: colorInt }),
);
this.store.dispatch(paintVoxel({ xyz: this.calcClickedVoxelPosition(pickInfo), newValue }));
break;
}
}
Expand All @@ -108,6 +115,8 @@ export class SceneViewerContainerComponent implements AfterViewInit {
: pickedIntegerPoint[2],
];

removeFraction(newPoint);

return newPoint;
}

Expand All @@ -126,6 +135,8 @@ export class SceneViewerContainerComponent implements AfterViewInit {
: pickedIntegerPoint[2],
];

removeFraction(newPoint);

return newPoint;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { select, Store } from '@ngrx/store';
import { UiSceneViewerService } from '@talus/ui';
import { Coord } from '@talus/vdb';
import { of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { catchError, map, withLatestFrom } from 'rxjs/operators';
import * as fromApp from '../app.reducer';
import { GridService } from './grid.service';
import {
paintVoxel,
paintVoxelFailed,
removeVoxel,
removeVoxelFailed,
selectLinePoint,
setVoxel,
setVoxelFailed,
setVoxels,
Expand All @@ -26,6 +29,7 @@ export class SceneViewerContainerEffects {
private actions$: Actions,
private gridService: GridService,
private sceneViewerService: UiSceneViewerService,
private store: Store<fromApp.State>,
) {}

setVoxel$ = createEffect(() =>
Expand Down Expand Up @@ -64,6 +68,19 @@ export class SceneViewerContainerEffects {
),
);

selectLinePoint$ = createEffect(() =>
this.actions$.pipe(
ofType(selectLinePoint),
withLatestFrom(this.store.pipe(select(fromApp.selectSceneViewerContainerState))),
map(([action, state]) =>
!state.selectingPoints
? this.gridService.selectLine(state.selectedPoints, action.newValue)
: [],
),
map(voxelChanges => voxelsSet({ voxelChanges })),
),
);

updateGridMesh$ = createEffect(
() =>
this.actions$.pipe(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { createReducer, on } from '@ngrx/store';
import { Coord } from '@talus/vdb';
import * as menuBarContainerActions from '../menu-bar-container/menu-bar-container.actions';
import { voxelRemoved, voxelSet } from './scene-viewer-container.actions';
import { selectLinePoint, voxelRemoved, voxelSet } from './scene-viewer-container.actions';

export const featureKey = 'sceneViewerContainer';

export interface State {
isDarkTheme: boolean;
selectedPoints: Coord[];
selectingPoints: boolean;
voxelCount: number;
}

export const initialState: State = {
isDarkTheme: true,
selectedPoints: [],
selectingPoints: false,
voxelCount: 0,
};

Expand All @@ -29,6 +34,14 @@ export const reducer = createReducer(
};
}),

on(selectLinePoint, (state, { xyz }) => {
return {
...state,
selectedPoints: state.selectingPoints ? [state.selectedPoints[0], xyz] : [xyz],
selectingPoints: !state.selectingPoints,
};
}),

on(menuBarContainerActions.setDarkTheme, state => {
return { ...state, isDarkTheme: true };
}),
Expand Down
5 changes: 5 additions & 0 deletions apps/frontend/src/app/tools-panel/tools-panel.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export class ToolsPanelComponent {
selectedToolId$: Observable<Tool>;

tools: UiToolbarToolConfig<Tool>[] = [
{
icon: 'timeline',
tooltip: '@@@ Select line point',
value: Tool.SelectLinePoint,
},
{
icon: 'add_circle_outline',
tooltip: '@@@ Set voxel',
Expand Down
2 changes: 1 addition & 1 deletion libs/model/src/lib/rgba.value.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('Rgba', () => {
a: 255,
},
],
])('should convert integer to `rgba` and back', (int: number, expectedRgba: Rgba) => {
])('should convert integer (%i) to `rgba` %j and back', (int: number, expectedRgba: Rgba) => {
const rgba = intToRgba(int);

expect(rgba).toEqual(expectedRgba);
Expand Down
Loading