Skip to content

Commit

Permalink
feat: support styling for point cloud on reearth/core (#549)
Browse files Browse the repository at this point in the history
  • Loading branch information
keiya01 authored Mar 16, 2023
1 parent 392fc70 commit f410d63
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 26 deletions.
89 changes: 75 additions & 14 deletions src/core/engines/Cesium/Feature/Tileset/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
Cesium3DTileset,
Cesium3DTile,
Cesium3DTileFeature,
Model,
Cesium3DTilePointFeature,
} from "cesium";
import { MutableRefObject, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { CesiumComponentRef, useCesium } from "resium";
Expand All @@ -21,8 +23,15 @@ import { requestIdleCallbackWithRequiredWork } from "@reearth/util/idle";

import type { ComputedFeature, ComputedLayer, Feature, EvalFeature, SceneProperty } from "../../..";
import { layerIdField, sampleTerrainHeightFromCartesian } from "../../common";
import type { InternalCesium3DTileFeature } from "../../types";
import { lookupFeatures, translationWithClamping } from "../../utils";
import { attachTag, extractSimpleLayer, extractSimpleLayerData, toColor } from "../utils";
import {
attachTag,
extractSimpleLayer,
extractSimpleLayerData,
generateIDWithMD5,
toColor,
} from "../utils";

import { useClippingBox } from "./useClippingBox";

Expand All @@ -45,12 +54,13 @@ const useData = (layer: ComputedLayer | undefined) => {

const makeFeatureFrom3DTile = (
id: string,
feature: Cesium3DTileFeature,
feature: InternalCesium3DTileFeature,
coordinates: number[],
): Feature => {
const properties = Object.fromEntries(
feature.getPropertyIds().map(id => [id, feature.getProperty(id)]),
);
const properties =
feature instanceof Model
? {}
: Object.fromEntries(feature.getPropertyIds().map(id => [id, feature.getProperty(id)]));
return {
type: "feature",
id,
Expand All @@ -67,13 +77,39 @@ const makeFeatureFrom3DTile = (
};
};

const getBuiltinFeatureId = (f: InternalCesium3DTileFeature) => {
return (f instanceof Model ? f.id : f instanceof Cesium3DTileFeature ? f.featureId : "") ?? "";
};

type CachedFeature = {
feature: Feature;
raw: Cesium3DTileFeature;
raw: InternalCesium3DTileFeature;
};

const MAX_NUMBER_OF_CONCURRENT_COMPUTING_FEATURES = 5000;

type StyleProperty<N = string> = { name: N; convert?: "color" | "vec2" | "vec4" };

const COMMON_STYLE_PROPERTIES: StyleProperty<"color" | "show">[] = [
{ name: "color", convert: "color" },
{ name: "show" },
];
const MODEL_STYLE_PROPERTIES: StyleProperty<"pointSize" | "meta">[] = [
{ name: "pointSize" },
{ name: "meta" },
];
// TODO: Add more styles. And it has not been tested yet.
const POINT_STYLE_PROPERTIES: StyleProperty<"pointSize">[] = [{ name: "pointSize" }];

// TODO: Implement other convert type
const convertStyle = (val: any, convert: StyleProperty["convert"]) => {
if (convert === "color") {
return toColor(val);
}

return val;
};

const useFeature = ({
id,
tileset,
Expand Down Expand Up @@ -101,14 +137,33 @@ const useFeature = ({
const computedFeature = evalFeature(layer, feature?.feature);
const style = computedFeature?.["3dtiles"];

const show = style?.show;
if (show !== undefined) {
feature.raw.show = show;
const raw = feature.raw;

COMMON_STYLE_PROPERTIES.forEach(({ name, convert }) => {
const val = convertStyle(style?.[name], convert);
if (val !== undefined) {
raw[name] = val;
}
});

if (raw instanceof Cesium3DTilePointFeature) {
POINT_STYLE_PROPERTIES.forEach(({ name, convert }) => {
const val = convertStyle(style?.[name], convert);
if (val !== undefined) {
raw[name] = val;
}
});
}

const color = toColor(style?.color);
if (color !== undefined) {
feature.raw.color = color;
if ("style" in raw) {
raw.style = new Cesium3DTileStyle(
// TODO: Convert value if it's necessary
MODEL_STYLE_PROPERTIES.reduce((res, { name, convert }) => {
const val = convertStyle(style?.[name as keyof typeof style], convert);
if (!val) return res;
return { ...res, [name]: val };
}, {}),
);
}

return computedFeature;
Expand All @@ -130,7 +185,10 @@ const useFeature = ({
const tempComputedFeatures: ComputedFeature[] = [];
lookupFeatures(t.content, async (tileFeature, content) => {
const coordinates = content.tile.boundingSphere.center;
const id = `${coordinates.x}-${coordinates.y}-${coordinates.z}-${tileFeature.featureId}`;
const featureId = getBuiltinFeatureId(tileFeature);
const id = generateIDWithMD5(
`${coordinates.x}-${coordinates.y}-${coordinates.z}-${featureId}`,
);
const feature = (() => {
const normalFeature = makeFeatureFrom3DTile(id, tileFeature, [
coordinates.x,
Expand Down Expand Up @@ -163,7 +221,10 @@ const useFeature = ({
const featureIds: string[] = [];
lookupFeatures(t.content, (tileFeature, content) => {
const coordinates = content.tile.boundingSphere.center;
const id = `${coordinates.x}-${coordinates.y}-${coordinates.z}-${tileFeature.featureId}`;
const featureId = getBuiltinFeatureId(tileFeature);
const id = generateIDWithMD5(
`${coordinates.x}-${coordinates.y}-${coordinates.z}-${featureId}`,
);
featureIds.push(id);

// Only delete cached id in here, removing cached feature lazily.
Expand Down
26 changes: 22 additions & 4 deletions src/core/engines/Cesium/Feature/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
TimeInterval as CesiumTimeInterval,
TimeIntervalCollection as CesiumTimeIntervalCollection,
DistanceDisplayCondition as CesiumDistanceDisplayCondition,
Model,
Cesium3DTilePointFeature,
} from "cesium";
import md5 from "js-md5";
import {
Expand All @@ -25,6 +27,7 @@ import { type CesiumComponentRef, Entity } from "resium";
import { Data, Layer, LayerSimple, TimeInterval } from "@reearth/core/mantle";

import type { ComputedFeature, ComputedLayer, FeatureComponentProps, Geometry } from "../..";
import type { InternalCesium3DTileFeature } from "../types";

export type FeatureProps<P = any> = {
id: string;
Expand Down Expand Up @@ -81,7 +84,7 @@ function EntityExtComponent(
}

export function attachTag(
entity: CesiumEntity | Cesium3DTileset | Cesium3DTileFeature | null | undefined,
entity: CesiumEntity | Cesium3DTileset | InternalCesium3DTileFeature | null | undefined,
tag: Tag,
) {
if (!entity) return;
Expand All @@ -91,7 +94,11 @@ export function attachTag(
return;
}

if (entity instanceof Cesium3DTileFeature) {
if (
entity instanceof Cesium3DTileFeature ||
entity instanceof Cesium3DTilePointFeature ||
entity instanceof Model
) {
(entity as any)[tagKey] = tag;
return;
}
Expand All @@ -107,15 +114,26 @@ export function attachTag(
}

export function getTag(
entity: CesiumEntity | Cesium3DTileset | Cesium3DTileFeature | null | undefined,
entity:
| CesiumEntity
| Cesium3DTileset
| Cesium3DTileFeature
| Cesium3DTilePointFeature
| Model
| null
| undefined,
): Tag | undefined {
if (!entity) return;

if (entity instanceof Cesium3DTileset) {
return (entity as any)[tagKey];
}

if (entity instanceof Cesium3DTileFeature) {
if (
entity instanceof Cesium3DTileFeature ||
entity instanceof Cesium3DTilePointFeature ||
entity instanceof Model
) {
return (entity as any)[tagKey];
}

Expand Down
31 changes: 28 additions & 3 deletions src/core/engines/Cesium/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
Ion,
Cesium3DTileset,
JulianDate,
Cesium3DTilePointFeature,
Model,
} from "cesium";
import type { Viewer as CesiumViewer } from "cesium";
import CesiumDnD, { Context } from "cesium-dnd";
Expand All @@ -32,6 +34,7 @@ import type {
import { useCameraLimiter } from "./cameraLimiter";
import { getCamera, isDraggable, isSelectable, getLocationFromScreen } from "./common";
import { getTag, type Context as FeatureContext } from "./Feature";
import { InternalCesium3DTileFeature } from "./types";
import useEngineRef from "./useEngineRef";
import { convertCartesian3ToPosition, findEntity, getEntityContent } from "./utils";

Expand Down Expand Up @@ -165,7 +168,7 @@ export default ({
}
}, [camera, engineAPI]);

const prevSelectedEntity = useRef<Entity | Cesium3DTileset | Cesium3DTileFeature>();
const prevSelectedEntity = useRef<Entity | Cesium3DTileset | InternalCesium3DTileFeature>();
// manage layer selection
useEffect(() => {
const viewer = cesium.current?.cesiumElement;
Expand Down Expand Up @@ -354,7 +357,10 @@ export default ({
return;
}

if (target && target instanceof Cesium3DTileFeature) {
if (
target &&
(target instanceof Cesium3DTileFeature || target instanceof Cesium3DTilePointFeature)
) {
const tag = getTag(target);
if (tag) {
onLayerSelect?.(tag.layerId, String(tag.featureId), {
Expand All @@ -371,6 +377,23 @@ export default ({
return;
}

if (
target &&
"content" in target &&
target.content &&
typeof target.content === "object" &&
"_model" in target.content &&
target.content._model instanceof Model
) {
const model = target.content._model;
const tag = getTag(model);
if (tag) {
onLayerSelect?.(tag.layerId, String(tag.featureId));
prevSelectedEntity.current = model;
}
return;
}

// Check imagery layer
// ref: https://github.com/CesiumGS/cesium/blob/96b978e0c53aba3bc4f1191111e0be61781ae9dd/packages/widgets/Source/Viewer/Viewer.js#L167
if (target === undefined && e.position) {
Expand Down Expand Up @@ -500,7 +523,9 @@ export default ({
};
};

function tileProperties(t: Cesium3DTileFeature): { key: string; value: any }[] {
function tileProperties(
t: Cesium3DTileFeature | Cesium3DTilePointFeature,
): { key: string; value: any }[] {
return t
.getPropertyIds()
.reduce<{ key: string; value: any }[]>(
Expand Down
3 changes: 3 additions & 0 deletions src/core/engines/Cesium/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type { Cesium3DTileFeature, Cesium3DTilePointFeature, Model } from "cesium";

export type InternalCesium3DTileFeature = Cesium3DTileFeature | Cesium3DTilePointFeature | Model;
18 changes: 16 additions & 2 deletions src/core/engines/Cesium/useEngineRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,14 @@ export default function useEngineRef(

const layerOrFeatureId = target;
const entityFromFeatureId = findEntity(viewer, undefined, layerOrFeatureId);
if (entityFromFeatureId && !(entityFromFeatureId instanceof Cesium.Cesium3DTileFeature)) {
if (
entityFromFeatureId &&
!(
entityFromFeatureId instanceof Cesium.Cesium3DTileFeature ||
entityFromFeatureId instanceof Cesium.Cesium3DTilePointFeature ||
entityFromFeatureId instanceof Cesium.Model
)
) {
viewer.flyTo(entityFromFeatureId, options);
} else {
for (const ds of [viewer.dataSourceDisplay.dataSources, viewer.dataSources]) {
Expand All @@ -121,7 +128,14 @@ export default function useEngineRef(
}

const entityFromLayerId = findEntity(viewer, layerOrFeatureId);
if (entityFromLayerId && !(entityFromLayerId instanceof Cesium.Cesium3DTileFeature)) {
if (
entityFromLayerId &&
!(
entityFromLayerId instanceof Cesium.Cesium3DTileFeature ||
entityFromLayerId instanceof Cesium.Cesium3DTilePointFeature ||
entityFromLayerId instanceof Cesium.Model
)
) {
viewer.flyTo(entityFromLayerId, options);
}
}
Expand Down
14 changes: 11 additions & 3 deletions src/core/engines/Cesium/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import {
Cesium3DTileContent,
Cesium3DTileFeature,
JulianDate,
Model,
} from "cesium";

import { InfoboxProperty } from "@reearth/core/Crust/Infobox";
import { DefaultInfobox } from "@reearth/core/Map";

import { getTag } from "./Feature";
import type { InternalCesium3DTileFeature } from "./types";

export const convertCartesian3ToPosition = (
cesium?: CesiumViewer,
Expand Down Expand Up @@ -49,9 +51,15 @@ export const translationWithClamping = (

export function lookupFeatures(
c: Cesium3DTileContent,
cb: (feature: Cesium3DTileFeature, content: Cesium3DTileContent) => void | Promise<void>,
cb: (feature: InternalCesium3DTileFeature, content: Cesium3DTileContent) => void | Promise<void>,
) {
if (!c) return;

// Use model, if featuresLength is 0.
if (!c.featuresLength && "_model" in c && c._model instanceof Model) {
return cb(c._model, c);
}

const length = c.featuresLength;
for (let i = 0; i < length; i++) {
const f = c.getFeature(i);
Expand All @@ -68,7 +76,7 @@ const findFeatureFrom3DTile = (
tile: Cesium3DTile,
featureId?: string,
): Cesium3DTileFeature | void => {
let target: Cesium3DTileFeature | undefined = undefined;
let target: InternalCesium3DTileFeature | undefined = undefined;
lookupFeatures(tile.content, f => {
const tag = getTag(f);
if (tag?.featureId === featureId) {
Expand All @@ -92,7 +100,7 @@ export function findEntity(
viewer: CesiumViewer,
layerId?: string,
featureId?: string,
): Entity | Cesium3DTileset | Cesium3DTileFeature | undefined {
): Entity | Cesium3DTileset | InternalCesium3DTileFeature | undefined {
const id = featureId ?? layerId;
const keyName = featureId ? "featureId" : "layerId";
if (!id) return;
Expand Down
2 changes: 2 additions & 0 deletions src/core/mantle/types/appearance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ export type Cesium3DTilesAppearance = {
edgeColor?: string;
tileset?: string;
experimental_clipping?: EXPERIMENTAL_clipping;
pointSize?: number;
meta?: unknown;
};

export type LegacyPhotooverlayAppearance = {
Expand Down

0 comments on commit f410d63

Please sign in to comment.