-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(react-components): add threed resource container (#3451)
* feat: add container for CAD model * feat: add 360 image collection container * fix: add 3d resource container * feat: add CameraController component * fix: add missing import * feat: infer model type using Reveal's determineModelType * fix: from QA --------- Co-authored-by: cognite-bulldozer[bot] <51074376+cognite-bulldozer[bot]@users.noreply.github.com>
- Loading branch information
1 parent
578286e
commit d2ca002
Showing
19 changed files
with
479 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
react-components/src/components/CameraController/CameraController.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/*! | ||
* Copyright 2023 Cognite AS | ||
*/ | ||
import { type ReactElement, useEffect, useContext, useRef } from 'react'; | ||
import { useReveal } from '../RevealContainer/RevealContext'; | ||
import { ModelsLoadingStateContext } from '../Reveal3DResources/ModelsLoadingContext'; | ||
import { type CameraState } from '@cognite/reveal'; | ||
|
||
export type CameraControllerProps = { | ||
initialFitCamera?: FittingStrategy; | ||
}; | ||
|
||
type FittingStrategy = | ||
| { to: 'cameraState'; state: CameraState } | ||
| { to: 'allModels' } | ||
| { to: 'none' }; | ||
|
||
export function CameraController({ initialFitCamera }: CameraControllerProps): ReactElement { | ||
const initialCameraSet = useRef(false); | ||
const viewer = useReveal(); | ||
const { modelsAdded } = useContext(ModelsLoadingStateContext); | ||
|
||
const fittingStrategy: Required<FittingStrategy> = initialFitCamera ?? { to: 'allModels' }; | ||
|
||
useEffect(() => { | ||
if (initialCameraSet.current) return; | ||
if (fittingStrategy.to === 'none') { | ||
initialCameraSet.current = true; | ||
return; | ||
} | ||
if (fittingStrategy.to === 'cameraState') { | ||
viewer.cameraManager.setCameraState(fittingStrategy.state); | ||
initialCameraSet.current = true; | ||
return; | ||
} | ||
if (fittingStrategy.to === 'allModels' && modelsAdded) { | ||
viewer.fitCameraToModels(viewer.models, 0, true); | ||
initialCameraSet.current = true; | ||
} | ||
}, [modelsAdded]); | ||
|
||
return <></>; | ||
} |
38 changes: 38 additions & 0 deletions
38
react-components/src/components/Image360CollectionContainer/Image360CollectionContainer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/*! | ||
* Copyright 2023 Cognite AS | ||
*/ | ||
import { type ReactElement, useEffect, useRef } from 'react'; | ||
import { useReveal } from '../RevealContainer/RevealContext'; | ||
import { type Image360Collection } from '@cognite/reveal'; | ||
|
||
type Image360CollectionContainerProps = { | ||
siteId: string; | ||
onLoad?: () => void; | ||
}; | ||
|
||
export function Image360CollectionContainer({ | ||
siteId, | ||
onLoad | ||
}: Image360CollectionContainerProps): ReactElement { | ||
const modelRef = useRef<Image360Collection>(); | ||
const viewer = useReveal(); | ||
|
||
useEffect(() => { | ||
add360Collection().catch(console.error); | ||
return remove360Collection; | ||
}, [siteId]); | ||
|
||
return <></>; | ||
|
||
async function add360Collection(): Promise<void> { | ||
const image360Collection = await viewer.add360ImageSet('events', { site_id: siteId }); | ||
modelRef.current = image360Collection; | ||
onLoad?.(); | ||
} | ||
|
||
function remove360Collection(): void { | ||
if (modelRef.current === undefined) return; | ||
viewer.remove360ImageSet(modelRef.current); | ||
modelRef.current = undefined; | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
react-components/src/components/PointCloudContainer/PointCloudContainer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/*! | ||
* Copyright 2023 Cognite AS | ||
*/ | ||
|
||
import { type CognitePointCloudModel, type AddModelOptions } from '@cognite/reveal'; | ||
import { useEffect, useRef, type ReactElement } from 'react'; | ||
import { type Matrix4 } from 'three'; | ||
import { useReveal } from '../RevealContainer/RevealContext'; | ||
|
||
type Cognite3dModelProps = { | ||
addModelOptions: AddModelOptions; | ||
transform?: Matrix4; | ||
onLoad?: () => void; | ||
}; | ||
|
||
export function PointCloudContainer({ | ||
addModelOptions, | ||
transform, | ||
onLoad | ||
}: Cognite3dModelProps): ReactElement { | ||
const modelRef = useRef<CognitePointCloudModel>(); | ||
const viewer = useReveal(); | ||
const { modelId, revisionId } = addModelOptions; | ||
|
||
useEffect(() => { | ||
addModel(modelId, revisionId, transform).catch(console.error); | ||
return removeModel; | ||
}, [modelId, revisionId]); | ||
|
||
useEffect(() => { | ||
if (modelRef.current === undefined || transform === undefined) return; | ||
modelRef.current.setModelTransformation(transform); | ||
}, [transform]); | ||
|
||
return <></>; | ||
|
||
async function addModel(modelId: number, revisionId: number, transform?: Matrix4): Promise<void> { | ||
const pointCloudModel = await viewer.addPointCloudModel({ modelId, revisionId }); | ||
if (transform !== undefined) { | ||
pointCloudModel.setModelTransformation(transform); | ||
} | ||
modelRef.current = pointCloudModel; | ||
onLoad?.(); | ||
} | ||
|
||
function removeModel(): void { | ||
if (modelRef.current === undefined || !viewer.models.includes(modelRef.current)) return; | ||
viewer.removeModel(modelRef.current); | ||
modelRef.current = undefined; | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
react-components/src/components/Reveal3DResources/ModelsLoadingContext.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/*! | ||
* Copyright 2023 Cognite AS | ||
*/ | ||
import { createContext } from 'react'; | ||
|
||
export type ModelsLoadingState = { | ||
modelsAdded: boolean; | ||
setModelsAdded: (value: boolean) => void; | ||
}; | ||
export const ModelsLoadingStateContext = createContext<ModelsLoadingState>({ | ||
modelsAdded: false, | ||
setModelsAdded: () => {} | ||
}); |
103 changes: 103 additions & 0 deletions
103
react-components/src/components/Reveal3DResources/Reveal3DResources.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
/*! | ||
* Copyright 2023 Cognite AS | ||
*/ | ||
import { useRef, type ReactElement, useContext, useState, useEffect } from 'react'; | ||
import { type Cognite3DViewer } from '@cognite/reveal'; | ||
import { ModelsLoadingStateContext } from './ModelsLoadingContext'; | ||
import { CadModelContainer } from '../CadModelContainer/CadModelContainer'; | ||
import { PointCloudContainer } from '../PointCloudContainer/PointCloudContainer'; | ||
import { Image360CollectionContainer } from '../Image360CollectionContainer/Image360CollectionContainer'; | ||
import { useReveal } from '../RevealContainer/RevealContext'; | ||
import { | ||
type AddReveal3DModelOptions, | ||
type AddImageCollection360Options, | ||
type TypedReveal3DModel, | ||
type AddResourceOptions | ||
} from './types'; | ||
|
||
export type Reveal3DResourcesProps = { | ||
resources: AddResourceOptions[]; | ||
}; | ||
|
||
export const Reveal3DResources = ({ resources }: Reveal3DResourcesProps): ReactElement => { | ||
const [reveal3DModels, setReveal3DModels] = useState<TypedReveal3DModel[]>([]); | ||
const { setModelsAdded } = useContext(ModelsLoadingStateContext); | ||
const viewer = useReveal(); | ||
const numModelsLoaded = useRef(0); | ||
|
||
useEffect(() => { | ||
getTypedModels(resources, viewer).then(setReveal3DModels).catch(console.error); | ||
}, [resources]); | ||
|
||
const image360CollectionAddOptions = resources.filter( | ||
(resource): resource is AddImageCollection360Options => | ||
(resource as AddImageCollection360Options).siteId !== undefined | ||
); | ||
|
||
const onModelLoaded = (): void => { | ||
numModelsLoaded.current += 1; | ||
if (numModelsLoaded.current === resources.length) { | ||
setModelsAdded(true); | ||
} | ||
}; | ||
|
||
return ( | ||
<> | ||
{reveal3DModels | ||
.filter(({ type }) => type === 'cad') | ||
.map((addModelOption, index) => { | ||
return ( | ||
<CadModelContainer | ||
key={`${addModelOption.modelId}/${addModelOption.revisionId}/${index}`} | ||
addModelOptions={addModelOption} | ||
transform={addModelOption.transform} | ||
onLoad={onModelLoaded} | ||
/> | ||
); | ||
})} | ||
{reveal3DModels | ||
.filter(({ type }) => type === 'pointcloud') | ||
.map((addModelOption, index) => { | ||
return ( | ||
<PointCloudContainer | ||
key={`${addModelOption.modelId}/${addModelOption.revisionId}/${index}`} | ||
addModelOptions={addModelOption} | ||
transform={addModelOption.transform} | ||
onLoad={onModelLoaded} | ||
/> | ||
); | ||
})} | ||
{image360CollectionAddOptions.map((addModelOption) => { | ||
return ( | ||
<Image360CollectionContainer | ||
key={`${addModelOption.siteId}`} | ||
siteId={addModelOption.siteId} | ||
onLoad={onModelLoaded} | ||
/> | ||
); | ||
})} | ||
</> | ||
); | ||
}; | ||
|
||
async function getTypedModels( | ||
resources: AddResourceOptions[], | ||
viewer: Cognite3DViewer | ||
): Promise<TypedReveal3DModel[]> { | ||
return await Promise.all( | ||
resources | ||
.filter( | ||
(resource): resource is AddReveal3DModelOptions => | ||
(resource as AddReveal3DModelOptions).modelId !== undefined && | ||
(resource as AddReveal3DModelOptions).revisionId !== undefined | ||
) | ||
.map(async (addModelOptions) => { | ||
const type = await viewer.determineModelType( | ||
addModelOptions.modelId, | ||
addModelOptions.revisionId | ||
); | ||
const typedModel: TypedReveal3DModel = { ...addModelOptions, type }; | ||
return typedModel; | ||
}) | ||
); | ||
} |
15 changes: 15 additions & 0 deletions
15
react-components/src/components/Reveal3DResources/types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/*! | ||
* Copyright 2023 Cognite AS | ||
*/ | ||
|
||
import { type AddModelOptions, type SupportedModelTypes } from '@cognite/reveal'; | ||
import { type Matrix4 } from 'three'; | ||
|
||
export type AddImageCollection360Options = { | ||
siteId: string; | ||
}; | ||
|
||
export type AddResourceOptions = AddReveal3DModelOptions | AddImageCollection360Options; | ||
|
||
export type AddReveal3DModelOptions = AddModelOptions & { transform?: Matrix4 }; | ||
export type TypedReveal3DModel = AddReveal3DModelOptions & { type: SupportedModelTypes | '' }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.