Skip to content

Commit

Permalink
refactor(VtkThreeView): separate cvr/color effects
Browse files Browse the repository at this point in the history
  • Loading branch information
floryst committed Feb 8, 2024
1 parent 2b94349 commit 0f54cf2
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 303 deletions.
305 changes: 3 additions & 302 deletions src/components/VtkThreeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,15 @@ import {
ref,
toRefs,
watch,
Ref,
nextTick,
} from 'vue';
import { computedWithControl } from '@vueuse/core';
import { vec3 } from 'gl-matrix';
import vtkVolumeRepresentationProxy from '@kitware/vtk.js/Proxy/Representations/VolumeRepresentationProxy';
import { Mode as LookupTableProxyMode } from '@kitware/vtk.js/Proxy/Core/LookupTableProxy';
import vtkPiecewiseFunctionProxy from '@kitware/vtk.js/Proxy/Core/PiecewiseFunctionProxy';
import vtkVolumeMapper from '@kitware/vtk.js/Rendering/Core/VolumeMapper';
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
import { getDiagonalLength } from '@kitware/vtk.js/Common/DataModel/BoundingBox';
import type { Vector3 } from '@kitware/vtk.js/types';
import { useProxyRepresentation } from '@/src/composables/useProxyRepresentations';
import { useProxyManager } from '@/src/composables/useProxyManager';
import ViewOverlayGrid from '@/src/components/ViewOverlayGrid.vue';
import { useCvrEffect } from '@/src/composables/useCvrEffect';
import { useColoringEffect } from '@/src/composables/useColoringEffect';
import { useResizeObserver } from '../composables/useResizeObserver';
import { useCurrentImage } from '../composables/useCurrentImage';
import { useCameraOrientation } from '../composables/useCameraOrientation';
Expand All @@ -84,305 +77,13 @@ import { usePersistCameraConfig } from '../composables/usePersistCameraConfig';
import { LPSAxisDir } from '../types/lps';
import { useViewProxy } from '../composables/useViewProxy';
import { ViewProxyType } from '../core/proxies';
import { VolumeColorConfig } from '../store/view-configs/types';
import useVolumeColoringStore, {
DEFAULT_AMBIENT,
DEFAULT_DIFFUSE,
DEFAULT_SPECULAR,
} from '../store/view-configs/volume-coloring';
import { getShiftedOpacityFromPreset } from '../utils/vtk-helpers';
import useVolumeColoringStore from '../store/view-configs/volume-coloring';
import CropTool from './tools/crop/CropTool.vue';
import PanTool from './tools/PanTool.vue';
import { useCropStore, croppingPlanesEqual } from '../store/tools/crop';
import { isViewAnimating } from '../composables/isViewAnimating';
import { ColoringConfig } from '../types/views';
import useViewCameraStore from '../store/view-configs/camera';
import { Maybe } from '../types';
import { useResetViewsEvents } from './tools/ResetViews.vue';
function useCvrEffect(
config: Ref<Maybe<VolumeColorConfig>>,
imageRep: Ref<Maybe<vtkVolumeRepresentationProxy>>,
viewProxy: Ref<vtkLPSView3DProxy>
) {
const cvrParams = computed(() => config.value?.cvr);
const repMapper = computedWithControl(
imageRep,
() => imageRep.value?.getMapper() as vtkVolumeMapper | undefined
);
const image = computedWithControl(
imageRep,
() => imageRep.value?.getInputDataSet() as vtkImageData | null | undefined
);
const volume = computedWithControl(
imageRep,
() => imageRep.value?.getVolumes()[0]
);
const renderer = computed(() => viewProxy.value.getRenderer());
const isAnimating = isViewAnimating(viewProxy);
const cvrEnabled = computed(() => {
const enabled = !!cvrParams.value?.enabled;
const animating = isAnimating.value;
return enabled && !animating;
});
const requestRender = () => {
if (!isAnimating.value) {
viewProxy.value.renderLater();
}
};
// lights
const volumeCenter = computed(() => {
if (!volume.value) return null;
const volumeBounds = volume.value.getBounds();
return [
(volumeBounds[0] + volumeBounds[1]) / 2,
(volumeBounds[2] + volumeBounds[3]) / 2,
(volumeBounds[4] + volumeBounds[5]) / 2,
] as Vector3;
});
const lightFollowsCamera = computed(
() => cvrParams.value?.lightFollowsCamera ?? true
);
watch(
[volumeCenter, renderer, cvrEnabled, lightFollowsCamera],
([center, ren, enabled, lightFollowsCamera_]) => {
if (!center) return;
if (ren.getLights().length === 0) {
ren.createLight();
}
const light = ren.getLights()[0];
if (enabled) {
light.setFocalPoint(...center);
light.setColor(1, 1, 1);
light.setIntensity(1);
light.setConeAngle(90);
light.setPositional(true);
ren.setTwoSidedLighting(false);
if (lightFollowsCamera_) {
light.setLightTypeToHeadLight();
ren.updateLightsGeometryToFollowCamera();
} else {
light.setLightTypeToSceneLight();
}
} else {
light.setPositional(false);
}
requestRender();
},
{ immediate: true }
);
// sampling distance
const volumeQuality = computed(() => cvrParams.value?.volumeQuality);
watch(
[volume, image, repMapper, volumeQuality, cvrEnabled, isAnimating],
([volume_, image_, mapper, volumeQuality_, enabled, animating]) => {
if (!volume_ || !mapper || volumeQuality_ == null || !image_) return;
if (animating) {
mapper.setSampleDistance(0.75);
mapper.setMaximumSamplesPerRay(1000);
mapper.setGlobalIlluminationReach(0);
mapper.setComputeNormalFromOpacity(false);
} else {
const dims = image_.getDimensions();
const spacing = image_.getSpacing();
const spatialDiagonal = vec3.length(
vec3.fromValues(
dims[0] * spacing[0],
dims[1] * spacing[1],
dims[2] * spacing[2]
)
);
// Use the average spacing for sampling by default
let sampleDistance = spacing.reduce((a, b) => a + b) / 3.0;
// Adjust the volume sampling by the quality slider value
sampleDistance /= volumeQuality_ > 1 ? 0.5 * volumeQuality_ ** 2 : 1.0;
const samplesPerRay = spatialDiagonal / sampleDistance + 1;
mapper.setMaximumSamplesPerRay(samplesPerRay);
mapper.setSampleDistance(sampleDistance);
// Adjust the global illumination reach by volume quality slider
mapper.setGlobalIlluminationReach(enabled ? 0.25 * volumeQuality_ : 0);
mapper.setComputeNormalFromOpacity(!enabled && volumeQuality_ > 2);
}
requestRender();
},
{ immediate: true }
);
// volume properties
const ambient = computed(() => cvrParams.value?.ambient ?? 0);
const diffuse = computed(() => cvrParams.value?.diffuse ?? 0);
const specular = computed(() => cvrParams.value?.specular ?? 0);
watch(
[volume, image, ambient, diffuse, specular, cvrEnabled],
([volume_, image_, ambient_, diffuse_, specular_, enabled]) => {
if (!volume_ || !image_) return;
const property = volume_.getProperty();
property.setScalarOpacityUnitDistance(
0,
(0.5 * getDiagonalLength(image_.getBounds())) /
Math.max(...image_.getDimensions())
);
property.setShade(true);
property.setUseGradientOpacity(0, !enabled);
property.setGradientOpacityMinimumValue(0, 0.0);
const dataRange = image_.getPointData().getScalars().getRange();
property.setGradientOpacityMaximumValue(
0,
(dataRange[1] - dataRange[0]) * 0.01
);
property.setGradientOpacityMinimumOpacity(0, 0.0);
property.setGradientOpacityMaximumOpacity(0, 1.0);
// do not toggle these parameters when animating
property.setAmbient(enabled ? ambient_ : DEFAULT_AMBIENT);
property.setDiffuse(enabled ? diffuse_ : DEFAULT_DIFFUSE);
property.setSpecular(enabled ? specular_ : DEFAULT_SPECULAR);
requestRender();
},
{ immediate: true }
);
// volumetric scattering blending
const useVolumetricScatteringBlending = computed(
() => cvrParams.value?.useVolumetricScatteringBlending ?? false
);
const volumetricScatteringBlending = computed(
() => cvrParams.value?.volumetricScatteringBlending ?? 0
);
watch(
[
useVolumetricScatteringBlending,
volumetricScatteringBlending,
repMapper,
cvrEnabled,
],
([useVsb, vsb, mapper, enabled]) => {
if (!mapper) return;
if (enabled && useVsb) {
mapper.setVolumetricScatteringBlending(vsb);
} else {
mapper.setVolumetricScatteringBlending(0);
}
requestRender();
},
{ immediate: true }
);
// local ambient occlusion
const useLocalAmbientOcclusion = computed(
() => cvrParams.value?.useLocalAmbientOcclusion ?? false
);
const laoKernelSize = computed(() => cvrParams.value?.laoKernelSize ?? 0);
const laoKernelRadius = computed(() => cvrParams.value?.laoKernelRadius ?? 0);
watch(
[
useLocalAmbientOcclusion,
laoKernelSize,
laoKernelRadius,
repMapper,
cvrEnabled,
],
([useLao, kernelSize, kernelRadius, mapper, enabled]) => {
if (!mapper) return;
if (enabled && useLao) {
mapper.setLocalAmbientOcclusion(true);
mapper.setLAOKernelSize(kernelSize);
mapper.setLAOKernelRadius(kernelRadius);
} else {
mapper.setLocalAmbientOcclusion(false);
mapper.setLAOKernelSize(0);
mapper.setLAOKernelRadius(0);
}
requestRender();
},
{ immediate: true }
);
}
function useColoringEffect(
config: Ref<Maybe<ColoringConfig>>,
imageRep: Ref<Maybe<vtkVolumeRepresentationProxy>>,
viewProxy: Ref<vtkLPSView3DProxy>
) {
const colorBy = computed(() => config.value?.colorBy);
const colorTransferFunction = computed(() => config.value?.transferFunction);
const opacityFunction = computed(() => config.value?.opacityFunction);
const proxyManager = useProxyManager();
watch(
[imageRep, colorBy, colorTransferFunction, opacityFunction],
([rep, colorBy_, colorFunc, opacityFunc]) => {
if (!rep || !colorBy_ || !colorFunc || !opacityFunc || !proxyManager) {
return;
}
const { arrayName, location } = colorBy_;
const lut = proxyManager.getLookupTable(arrayName);
lut.setMode(LookupTableProxyMode.Preset);
lut.setPresetName(colorFunc.preset);
lut.setDataRange(...colorFunc.mappingRange);
const pwf = proxyManager.getPiecewiseFunction(arrayName);
pwf.setMode(opacityFunc.mode);
pwf.setDataRange(...opacityFunc.mappingRange);
switch (opacityFunc.mode) {
case vtkPiecewiseFunctionProxy.Mode.Gaussians:
pwf.setGaussians(opacityFunc.gaussians);
break;
case vtkPiecewiseFunctionProxy.Mode.Points: {
const opacityPoints = getShiftedOpacityFromPreset(
opacityFunc.preset,
opacityFunc.mappingRange,
opacityFunc.shift,
opacityFunc.shiftAlpha
);
if (opacityPoints) {
pwf.setPoints(opacityPoints);
}
break;
}
case vtkPiecewiseFunctionProxy.Mode.Nodes:
pwf.setNodes(opacityFunc.nodes);
break;
default:
}
if (rep) {
// control color range manually
rep.setRescaleOnColorBy(false);
rep.setColorBy(arrayName, location);
}
// Need to trigger a render for when we are restoring from a state file
viewProxy.value.renderLater();
},
{ immediate: true }
);
}
export default defineComponent({
props: {
id: {
Expand Down
Loading

0 comments on commit 0f54cf2

Please sign in to comment.