Skip to content

Commit

Permalink
feat: styling for 360 icons (#3757)
Browse files Browse the repository at this point in the history
* feat: styling for 360 icons
  • Loading branch information
haakonflatval-cognite authored Oct 3, 2023
1 parent 298253f commit 39b9b80
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 45 deletions.
1 change: 1 addition & 0 deletions viewer/api-entry-points/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export {
Image360Collection,
Image360EnteredDelegate,
Image360ExitedDelegate,
Image360IconStyle,
Image360AnnotationIntersection,
Image360AnnotationAppearance,
Image360Annotation,
Expand Down
2 changes: 1 addition & 1 deletion viewer/packages/360-images/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Copyright 2022 Cognite AS
*/

export { Image360 } from './src/entity/Image360';
export { Image360, Image360IconStyle } from './src/entity/Image360';
export { Image360Revision } from './src/entity/Image360Revision';
export {
Image360Collection,
Expand Down
24 changes: 24 additions & 0 deletions viewer/packages/360-images/src/entity/Image360.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@
import { Image360Revision } from './Image360Revision';
import { Image360Visualization } from './Image360Visualization';

import { Color } from 'three';

/**
* Image360 icon style
*/
export type Image360IconStyle = {
/**
* A color tint to apply to the 360 icon
*/
color?: Color;
};

/**
* A single 360 image "station", which may consist of several revisions
* captured in approximately the same location
Expand Down Expand Up @@ -46,4 +58,16 @@ export interface Image360 {
* @returns The active revision.
*/
getActiveRevision(): Image360Revision;

/**
* Get a copy of the color assigned to the icon of this entity
*
* @returns The currently assign color, or 'default' if none is assigned
*/
getIconColor(): Color | 'default';

/**
* Assign a color to the icon of this entity
*/
setIconColor(color: Color | 'default'): void;
}
13 changes: 13 additions & 0 deletions viewer/packages/360-images/src/entity/Image360Entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import { Image360VisualizationBox } from './Image360VisualizationBox';
import { ImageAnnotationObject } from '../annotation/ImageAnnotationObject';
import { Overlay3DIcon } from '@reveal/3d-overlays';
import { Image360AnnotationFilter } from '../annotation/Image360AnnotationFilter';
import { Color } from 'three';

import cloneDeep from 'lodash/cloneDeep';

export class Image360Entity implements Image360 {
private readonly _revisions: Image360RevisionEntity[];
Expand All @@ -20,6 +23,7 @@ export class Image360Entity implements Image360 {
private readonly _image360Icon: Overlay3DIcon;
private readonly _image360VisualizationBox: Image360VisualizationBox;
private _activeRevision: Image360RevisionEntity;
private _iconColor: Color | 'default' = 'default';

/**
* Get a copy of the model-to-world transformation matrix
Expand Down Expand Up @@ -137,6 +141,15 @@ export class Image360Entity implements Image360 {
this._image360VisualizationBox.unloadImages();
}

public getIconColor(): Color | 'default' {
return cloneDeep(this._iconColor);
}

public setIconColor(color: Color | 'default'): void {
this._iconColor = color;
this._image360Icon.setColor(color);
}

public activateAnnotations(): void {
const setAndShowAnnotations = async () => {
const annotations = await this._activeRevision.getAnnotations();
Expand Down
19 changes: 12 additions & 7 deletions viewer/packages/360-images/src/icons/IconCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ export class IconCollection {
spriteTexture: sharedTexture,
minPixelSize: IconCollection.MinPixelSize,
maxPixelSize: this._maxPixelSize,
radius: this._iconRadius
radius: this._iconRadius,
maskTexture: sharedTexture
});
iconsSprites.setPoints(points);

const spriteTexture = this.createHoverIconTexture();
this._hoverSprite = this.createHoverSprite(spriteTexture);
Expand Down Expand Up @@ -133,7 +133,11 @@ export class IconCollection {

this._icons.forEach(icon => (icon.culled = true));
selectedIcons.forEach(icon => (icon.culled = false));
iconSprites.setPoints(selectedIcons.filter(icon => icon.getVisible()).map(icon => icon.getPosition()));
const visibleIcons = selectedIcons.filter(icon => icon.getVisible());
iconSprites.setPoints(
visibleIcons.map(icon => icon.getPosition()),
visibleIcons.map(icon => icon.getColor())
);
};
}

Expand All @@ -159,11 +163,12 @@ export class IconCollection {

this._icons.forEach(icon => (icon.culled = true));
closestPoints.forEach(icon => (icon.culled = false));

const closestVisibleReversedPoints = closestPoints.filter(icon => icon.getVisible()).reverse();

iconSprites.setPoints(
closestPoints
.filter(icon => icon.getVisible())
.reverse()
.map(p => p.getPosition())
closestVisibleReversedPoints.map(p => p.getPosition()),
closestVisibleReversedPoints.map(p => p.getColor())
);
};
}
Expand Down
98 changes: 69 additions & 29 deletions viewer/packages/360-images/unit-tests/Image360Entity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,39 +12,79 @@ import { DeviceDescriptor, SceneHandler } from '@reveal/utilities';
import { Historical360ImageSet } from '@reveal/data-providers/src/types';
import { Image360AnnotationFilter } from '../src/annotation/Image360AnnotationFilter';

function createMockImage360(options?: { customTranslation?: THREE.Matrix4 }) {
const image360Descriptor: Historical360ImageSet = {
id: '0',
label: 'testEntity',
collectionId: '0',
collectionLabel: 'test_collection',
transform: new THREE.Matrix4(),
imageRevisions: [
{
timestamp: undefined,
faceDescriptors: []
}
]
};

const mockSceneHandler = new Mock<SceneHandler>().setup(p => p.addCustomObject(It.IsAny())).returns();
const mock360ImageProvider = new Mock<Image360Provider<any>>();
const mock360ImageIcon = new Overlay3DIcon(
{ position: new THREE.Vector3(), minPixelSize: 10, maxPixelSize: 10, iconRadius: 10 },
{}
);

const testTranslation = options?.customTranslation ?? new THREE.Matrix4();
const desktopDevice: DeviceDescriptor = { deviceType: 'desktop' };

return new Image360Entity(
image360Descriptor,
mockSceneHandler.object(),
mock360ImageProvider.object(),
new Image360AnnotationFilter({}),
testTranslation,
mock360ImageIcon,
desktopDevice
);
}

describe(Image360Entity.name, () => {
test('transformation should be respected', () => {
const image360Descriptor: Historical360ImageSet = {
id: '0',
label: 'testEntity',
collectionId: '0',
collectionLabel: 'test_collection',
transform: new THREE.Matrix4(),
imageRevisions: [
{
timestamp: undefined,
faceDescriptors: []
}
]
};

const mockSceneHandler = new Mock<SceneHandler>().setup(p => p.addCustomObject(It.IsAny())).returns();
const mock360ImageProvider = new Mock<Image360Provider<any>>();
const mock360ImageIcon = new Mock<Overlay3DIcon>().object();

const testTranslation = new THREE.Matrix4().makeTranslation(4, 5, 6);
const desktopDevice: DeviceDescriptor = { deviceType: 'desktop' };

const entity = new Image360Entity(
image360Descriptor,
mockSceneHandler.object(),
mock360ImageProvider.object(),
new Image360AnnotationFilter({}),
testTranslation,
mock360ImageIcon,
desktopDevice
);
const entity = createMockImage360({ customTranslation: testTranslation });

expect(entity.transform.equals(testTranslation)).toBeTrue();
});

test('set icon color is returned in getter', () => {
const entity = createMockImage360();

const originalColor = entity.getIconColor();

expect(originalColor).toBe('default');

const testColor = new THREE.Color(0.2, 0.3, 0.4);
entity.setIconColor(testColor);

const gottenColor = entity.getIconColor();

expect(gottenColor).not.toBe('default');
expect((gottenColor as THREE.Color).toArray()).toEqual(testColor.toArray());
});

test('setting undefined icon color resets image360 icon color', () => {
const entity = createMockImage360();

const testColor = new THREE.Color(0.2, 0.3, 0.4);
entity.setIconColor(testColor);

const firstColor = entity.getIconColor();

expect(firstColor).not.toBe('default');

entity.setIconColor('default');
const secondColor = entity.getIconColor();

expect(secondColor).toBe('default');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,14 @@ export default class Image360VisualTestFixture extends StreamingVisualTestFixtur

camera.near = 0.01;
camera.updateProjectionMatrix();

camera.position.set(11.67, 4.15, -2.89);
camera.rotation.set(-0.4, 0.84, 0.3);

const desktopDevice: DeviceDescriptor = { deviceType: 'desktop' };

const { facade, entities } = await this.setup360Images(cogniteClient, sceneHandler, onBeforeRender, desktopDevice);
entities[1].setIconColor(new THREE.Color(1.0, 0.0, 1.0));

const icons = entities.map(entity => entity.icon);
sceneHandler.addCustomObject(this.getOctreeVisualizationObject(icons));
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 9 additions & 3 deletions viewer/packages/3d-overlays/src/Overlay3DIcon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class Overlay3DIcon<ContentType = DefaultOverlay3DContentType> implements
private readonly _hoverSprite?: THREE.Sprite;
private readonly _content: ContentType;
private readonly _raycastBoundingSphere = new Sphere();
private readonly _defaultColor: Color;

private _adaptiveScale = 1;
private _visible = true;
Expand All @@ -58,6 +59,7 @@ export class Overlay3DIcon<ContentType = DefaultOverlay3DContentType> implements
this._hoverSprite = hoverSprite;
this._content = content;
this._color = color ?? this._color;
this._defaultColor = this._color;

this._setAdaptiveScale = this.setupAdaptiveScaling(position);

Expand Down Expand Up @@ -127,9 +129,13 @@ export class Overlay3DIcon<ContentType = DefaultOverlay3DContentType> implements
}
}

setColor(color: Color): void {
this._color = color;
this._events.parametersChange.fire({ color, visble: this.getVisible() });
setColor(color: Color | 'default'): void {
if (color === 'default') {
this._color = this._defaultColor;
} else {
this._color = color;
}
this._events.parametersChange.fire({ color: this._color, visble: this.getVisible() });
}

getColor(): Color {
Expand Down
10 changes: 5 additions & 5 deletions viewer/packages/3d-overlays/src/overlay3DIcon.frag
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ in vec3 vColor;
out vec4 fragmentColor;

void main() {
vec4 colorSample = texture(colorTexture, gl_PointCoord);
vec4 colorSample = texture(colorTexture, gl_PointCoord);

float computedAlpha = colorSample.a;
vec3 computedColor = colorSample.rgb;

#if defined(isMaskDefined)
vec4 maskSample = texture(maskTexture, gl_PointCoord);

computedAlpha = colorSample.a + (1. - colorSample.a) * maskSample.r;
computedColor = mix(vColor * maskSample.r, colorSample.rgb, colorSample.a);
computedColor = mix(colorSample.rgb, vColor, maskSample.r);
#endif

fragmentColor = vec4(computedColor * colorTint, computedAlpha * collectionOpacity);
}
}
7 changes: 7 additions & 0 deletions viewer/reveal.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -794,10 +794,12 @@ export type HtmlOverlayToolOptions = {
// @public
export interface Image360 {
getActiveRevision(): Image360Revision;
getIconColor(): Color | 'default';
getRevisions(): Image360Revision[];
readonly id: string;
readonly image360Visualization: Image360Visualization;
readonly label: string | undefined;
setIconColor(color: Color | 'default'): void;
readonly transform: THREE.Matrix4;
}

Expand Down Expand Up @@ -867,6 +869,11 @@ export type Image360EnteredDelegate = (image360: Image360, revision: Image360Rev
// @public
export type Image360ExitedDelegate = () => void;

// @public
export type Image360IconStyle = {
color?: Color;
};

// @public
export interface Image360Revision {
readonly date: Date | undefined;
Expand Down

0 comments on commit 39b9b80

Please sign in to comment.