-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(VolumeRepresentation): initial add
- Loading branch information
Showing
5 changed files
with
305 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray'; | ||
import vtkVolume, { | ||
IVolumeInitialValues, | ||
} from '@kitware/vtk.js/Rendering/Core/Volume'; | ||
import vtkVolumeMapper, { | ||
IVolumeMapperInitialValues, | ||
} from '@kitware/vtk.js/Rendering/Core/VolumeMapper'; | ||
import { IVolumePropertyInitialValues } from '@kitware/vtk.js/Rendering/Core/VolumeProperty'; | ||
import { Vector2 } from '@kitware/vtk.js/types'; | ||
import { | ||
forwardRef, | ||
PropsWithChildren, | ||
useCallback, | ||
useEffect, | ||
useImperativeHandle, | ||
useMemo, | ||
useState, | ||
} from 'react'; | ||
import { IDownstream, IRepresentation } from '../types'; | ||
import { compareShallowObject } from '../utils/comparators'; | ||
import useBooleanAccumulator from '../utils/useBooleanAccumulator'; | ||
import useComparableEffect from '../utils/useComparableEffect'; | ||
import useLatest from '../utils/useLatest'; | ||
import { | ||
DownstreamContext, | ||
RepresentationContext, | ||
useRendererContext, | ||
} from './contexts'; | ||
import useColorTransferFunction from './modules/useColorTransferFunction'; | ||
import useDataRange from './modules/useDataRange'; | ||
import useMapper from './modules/useMapper'; | ||
import usePiecewiseFunction from './modules/usePiecewiseFunction'; | ||
import useProp from './modules/useProp'; | ||
|
||
export interface VolumeRepresentationProps extends PropsWithChildren { | ||
/** | ||
* The ID used to identify this component. | ||
*/ | ||
id?: string; | ||
|
||
/** | ||
* Properties to set to the mapper | ||
*/ | ||
mapper?: IVolumeMapperInitialValues; | ||
|
||
/** | ||
* An opational mapper instanc | ||
*/ | ||
mapperInstance?: vtkVolumeMapper; | ||
|
||
/** | ||
* Properties to set to the volume actor | ||
*/ | ||
actor?: IVolumeInitialValues; | ||
|
||
/** | ||
* Properties to set to the volume.property | ||
*/ | ||
property?: IVolumePropertyInitialValues; | ||
|
||
/** | ||
* Preset name for the lookup table color map | ||
*/ | ||
colorMapPreset?: string; | ||
|
||
/** | ||
* Data range use for the colorMap | ||
*/ | ||
colorDataRange?: 'auto' | Vector2; | ||
|
||
/** | ||
* Event callback for when data is made available. | ||
* | ||
* By the time this callback is invoked, you can be sure that: | ||
* - the mapper has the input data | ||
* - the actor is visible (unless explicitly marked as not visible) | ||
* - initial properties are set | ||
*/ | ||
onDataAvailable?: () => void; | ||
} | ||
|
||
const DefaultProps = { | ||
colorMapPreset: 'erdc_rainbow_bright', | ||
colorDataRange: 'auto' as const, | ||
}; | ||
|
||
export default forwardRef(function VolumeRepresentation( | ||
props: VolumeRepresentationProps, | ||
fwdRef | ||
) { | ||
const [modifiedRef, trackModified, resetModified] = useBooleanAccumulator(); | ||
const [dataAvailable, setDataAvailable] = useState(false); | ||
|
||
// --- mapper --- // | ||
|
||
const getInternalMapper = useMapper( | ||
() => vtkVolumeMapper.newInstance(), | ||
props.mapper, | ||
trackModified | ||
); | ||
|
||
const { mapperInstance } = props; | ||
const getMapper = useCallback(() => { | ||
if (mapperInstance) { | ||
return mapperInstance; | ||
} | ||
return getInternalMapper(); | ||
}, [mapperInstance, getInternalMapper]); | ||
|
||
// --- data range --- // | ||
|
||
const getDataArray = useCallback( | ||
() => | ||
getMapper()?.getInputData()?.getPointData().getScalars() as | ||
| vtkDataArray | ||
| undefined, | ||
[getMapper] | ||
); | ||
|
||
const { dataRange, updateDataRange } = useDataRange(getDataArray); | ||
|
||
const rangeFromProps = props.colorDataRange ?? DefaultProps.colorDataRange; | ||
const colorDataRange = rangeFromProps === 'auto' ? dataRange : rangeFromProps; | ||
|
||
// --- LUT --- // | ||
|
||
const getLookupTable = useColorTransferFunction( | ||
props.colorMapPreset ?? DefaultProps.colorMapPreset, | ||
colorDataRange, | ||
trackModified | ||
); | ||
|
||
// --- PWF --- // | ||
|
||
const getPiecewiseFunction = usePiecewiseFunction( | ||
colorDataRange, | ||
trackModified | ||
); | ||
|
||
// --- actor --- // | ||
|
||
const actorProps = { | ||
...props.actor, | ||
visibility: dataAvailable && (props.actor?.visibility ?? true), | ||
}; | ||
const getActor = useProp({ | ||
constructor: () => vtkVolume.newInstance(), | ||
id: props.id, | ||
props: actorProps, | ||
trackModified, | ||
}); | ||
|
||
useEffect(() => { | ||
getActor().setMapper(getMapper()); | ||
}, [getActor, getMapper]); | ||
|
||
useEffect(() => { | ||
getActor().getProperty().setRGBTransferFunction(0, getLookupTable()); | ||
getActor().getProperty().setScalarOpacity(0, getPiecewiseFunction()); | ||
getActor().getProperty().setInterpolationTypeToLinear(); | ||
}, [getActor, getLookupTable, getPiecewiseFunction]); | ||
|
||
// set actor property props | ||
const { property: propertyProps } = props; | ||
useComparableEffect( | ||
() => { | ||
if (!propertyProps) return; | ||
trackModified(getActor().getProperty().set(propertyProps)); | ||
}, | ||
[propertyProps], | ||
([cur], [prev]) => compareShallowObject(cur, prev) | ||
); | ||
|
||
// --- events --- // | ||
|
||
const onDataAvailable = useLatest(props.onDataAvailable); | ||
useEffect(() => { | ||
if (dataAvailable) { | ||
// trigger onDataAvailable after making updates to the actor and mapper | ||
onDataAvailable.current?.(); | ||
} | ||
// onDataAvailable is a ref | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [dataAvailable]); | ||
|
||
// --- // | ||
|
||
const renderer = useRendererContext(); | ||
|
||
useEffect(() => { | ||
if (modifiedRef.current) { | ||
renderer.requestRender(); | ||
resetModified(); | ||
} | ||
}); | ||
|
||
const representation = useMemo<IRepresentation>( | ||
() => ({ | ||
dataChanged: () => { | ||
updateDataRange(); | ||
renderer.requestRender(); | ||
}, | ||
dataAvailable: (available = true) => { | ||
setDataAvailable(available); | ||
representation.dataChanged(); | ||
}, | ||
getActor, | ||
getMapper, | ||
getData: () => { | ||
return getMapper().getInputData(); | ||
}, | ||
}), | ||
[renderer, updateDataRange, getActor, getMapper] | ||
); | ||
|
||
const downstream = useMemo<IDownstream>( | ||
() => ({ | ||
setInputData: (...args) => getMapper().setInputData(...args), | ||
setInputConnection: (...args) => getMapper().setInputConnection(...args), | ||
}), | ||
[getMapper] | ||
); | ||
|
||
useImperativeHandle(fwdRef, () => representation); | ||
|
||
return ( | ||
<RepresentationContext.Provider value={representation}> | ||
<DownstreamContext.Provider value={downstream}> | ||
{props.children} | ||
</DownstreamContext.Provider> | ||
</RepresentationContext.Provider> | ||
); | ||
}); |
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,26 @@ | ||
import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray'; | ||
import { useCallback, useEffect, useState } from 'react'; | ||
|
||
const DEFAULT_RANGE: [number, number] = [0, 1]; | ||
|
||
export default function useDataRange( | ||
getDataArray: () => vtkDataArray | null | undefined, | ||
defaultRange = DEFAULT_RANGE | ||
) { | ||
const [dataRange, setRange] = useState<[number, number]>(defaultRange); | ||
|
||
const updateDataRange = useCallback(() => { | ||
const range = getDataArray()?.getRange(); | ||
if (!range) return; | ||
setRange(range); | ||
}, [getDataArray]); | ||
|
||
useEffect(() => { | ||
updateDataRange(); | ||
}, [updateDataRange]); | ||
|
||
return { | ||
dataRange, | ||
updateDataRange, | ||
}; | ||
} |
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,42 @@ | ||
import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction'; | ||
import { Vector2 } from '@kitware/vtk.js/types'; | ||
import { compareVector2 } from '../../utils/comparators'; | ||
import deletionRegistry from '../../utils/DeletionRegistry'; | ||
import { BooleanAccumulator } from '../../utils/useBooleanAccumulator'; | ||
import useComparableEffect from '../../utils/useComparableEffect'; | ||
import useGetterRef from '../../utils/useGetterRef'; | ||
import useUnmount from '../../utils/useUnmount'; | ||
|
||
export default function usePiecewiseFunction( | ||
range: Vector2, | ||
trackModified: BooleanAccumulator | ||
) { | ||
const [pwfRef, getPWF] = useGetterRef(() => { | ||
const func = vtkPiecewiseFunction.newInstance(); | ||
deletionRegistry.register(func, () => func.delete()); | ||
return func; | ||
}); | ||
|
||
useComparableEffect( | ||
() => { | ||
if (!range) return; | ||
const pwf = getPWF(); | ||
pwf.setNodes([ | ||
{ x: range[0], y: 0, midpoint: 0.5, sharpness: 0 }, | ||
{ x: range[1], y: 1, midpoint: 0.5, sharpness: 0 }, | ||
]); | ||
trackModified(true); | ||
}, | ||
[range] as const, | ||
([curRange], [oldRange]) => compareVector2(curRange, oldRange) | ||
); | ||
|
||
useUnmount(() => { | ||
if (pwfRef.current) { | ||
deletionRegistry.markForDeletion(pwfRef.current); | ||
pwfRef.current = null; | ||
} | ||
}); | ||
|
||
return getPWF; | ||
} |
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