diff --git a/react-components/src/components/RevealToolbar/RevealToolbar.tsx b/react-components/src/components/RevealToolbar/RevealToolbar.tsx
index 74777b0d1dd..7e0a3e52992 100644
--- a/react-components/src/components/RevealToolbar/RevealToolbar.tsx
+++ b/react-components/src/components/RevealToolbar/RevealToolbar.tsx
@@ -2,9 +2,10 @@
* Copyright 2023 Cognite AS
*/
-import { type ReactElement } from 'react';
+import { type ReactElement, type JSX } from 'react';
import { Button, ToolBar, type ToolBarProps } from '@cognite/cogs.js';
import { FitModelsButton } from './FitModelsButton';
+import { SlicerButton } from './SlicerButton';
const defaultStyle: ToolBarProps = {
style: {
@@ -14,32 +15,34 @@ const defaultStyle: ToolBarProps = {
}
};
-export const RevealToolbar = (toolBarProps: ToolBarProps): ReactElement => {
- if (toolBarProps.className === undefined && toolBarProps.style === undefined) {
- toolBarProps = { ...toolBarProps, ...defaultStyle };
- }
- return (
-
- <>
-
+const defaultContent = (
+ <>
+
+
+
-
+
+
-
-
+
-
+
+
-
-
+
-
+
+
+ >
+);
-
-
- >
-
- );
+export const RevealToolbar = (
+ props: ToolBarProps & { toolBarContent?: JSX.Element }
+): ReactElement => {
+ if (props.className === undefined && props.style === undefined) {
+ props = { ...props, ...defaultStyle };
+ }
+ return {props.toolBarContent ?? defaultContent};
};
RevealToolbar.FitModelsButton = FitModelsButton;
diff --git a/react-components/src/components/RevealToolbar/SlicerButton.tsx b/react-components/src/components/RevealToolbar/SlicerButton.tsx
new file mode 100644
index 00000000000..e16d4cdcb9b
--- /dev/null
+++ b/react-components/src/components/RevealToolbar/SlicerButton.tsx
@@ -0,0 +1,113 @@
+/*!
+ * Copyright 2023 Cognite AS
+ */
+
+import { type ReactElement, useState, useEffect } from 'react';
+
+import { Box3, Plane, Vector3 } from 'three';
+
+import { useReveal } from '../RevealContainer/RevealContext';
+import { Button, Dropdown, Menu, RangeSlider } from '@cognite/cogs.js';
+
+import styled from 'styled-components';
+
+type SliceState = {
+ minHeight: number;
+ maxHeight: number;
+ topRatio: number;
+ bottomRatio: number;
+};
+
+export const SlicerButton = (): ReactElement => {
+ const viewer = useReveal();
+
+ const [sliceState, setSliceState] = useState({
+ minHeight: 0,
+ maxHeight: 0,
+ topRatio: 1,
+ bottomRatio: 0
+ });
+
+ const { minHeight, maxHeight, topRatio, bottomRatio } = sliceState;
+
+ // Heuristic to increase chance that update is propagated even
+ // if multiple additions/deletions of models occur.
+ const lastModel =
+ viewer.models.length === 0 ? undefined : viewer.models[viewer.models.length - 1];
+
+ useEffect(() => {
+ const box = new Box3();
+ viewer.models.forEach((model) => box.union(model.getModelBoundingBox()));
+
+ const newMaxY = box.max.y;
+ const newMinY = box.min.y;
+
+ if (maxHeight !== newMaxY || minHeight !== newMinY) {
+ setSliceState(getNewSliceState(sliceState, newMinY, newMaxY));
+ }
+ }, [viewer, viewer.models.length, lastModel]);
+
+ function changeSlicingState(newValues: number[]): void {
+ viewer.setGlobalClippingPlanes([
+ new Plane(new Vector3(0, 1, 0), -(minHeight + newValues[0] * (maxHeight - minHeight))),
+ new Plane(new Vector3(0, -1, 0), minHeight + newValues[1] * (maxHeight - minHeight))
+ ]);
+
+ setSliceState({
+ maxHeight,
+ minHeight,
+ bottomRatio: newValues[0],
+ topRatio: newValues[1]
+ });
+ }
+
+ return (
+ document.body}
+ content={
+
+
+
+ }
+ placement="right-end">
+
+
+ );
+};
+
+function getNewSliceState(oldSliceState: SliceState, newMin: number, newMax: number): SliceState {
+ function getRatioForNewRange(ratio: number): number {
+ if (ratio === 0 || ratio === 1) {
+ return ratio;
+ }
+
+ const oldMin = oldSliceState.minHeight;
+ const oldMax = oldSliceState.maxHeight;
+
+ const position = oldMin + ratio * (oldMax - oldMin);
+ const newRatio = (position - newMin) / (newMax - newMin);
+
+ return Math.min(1.0, Math.max(0.0, newRatio));
+ }
+
+ return {
+ maxHeight: newMax,
+ minHeight: newMin,
+ topRatio: getRatioForNewRange(oldSliceState.topRatio),
+ bottomRatio: getRatioForNewRange(oldSliceState.bottomRatio)
+ };
+}
+
+const StyledMenu = styled(Menu)`
+ height: 512px;
+ padding: 12px;
+ min-width: 0px;
+`;
diff --git a/react-components/src/index.ts b/react-components/src/index.ts
index 855a901fa2a..4da6c7a598a 100644
--- a/react-components/src/index.ts
+++ b/react-components/src/index.ts
@@ -29,5 +29,7 @@ export type {
AddReveal3DModelOptions
} from './components/Reveal3DResources/types';
export { RevealToolbar } from './components/RevealToolbar/RevealToolbar';
+export { SlicerButton } from './components/RevealToolbar/SlicerButton';
+export { FitModelsButton } from './components/RevealToolbar/FitModelsButton';
export { useFdmAssetMappings } from './hooks/useFdmAssetMappings';
export { type FdmAssetMappingsConfig } from './hooks/types';