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: styling for 360 icons #3757

Merged
merged 14 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading