|
1 | 1 | import macro from 'vtk.js/Sources/macros';
|
2 | 2 | import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
|
3 |
| -import vtkBoundingBox from 'vtk.js/Sources/Common/DataModel/BoundingBox'; |
4 |
| -import { getPixelWorldHeightAtCoord } from 'vtk.js/Sources/Widgets/Core/WidgetManager'; |
5 |
| -import vtkWidgetRepresentation, { |
6 |
| - allocateArray, |
7 |
| -} from 'vtk.js/Sources/Widgets/Representations/WidgetRepresentation'; |
8 |
| -import { RenderingTypes } from 'vtk.js/Sources/Widgets/Core/WidgetManager/Constants'; |
9 |
| -import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData'; |
10 |
| -import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor'; |
11 | 3 | import vtkGlyph3DMapper from 'vtk.js/Sources/Rendering/Core/Glyph3DMapper';
|
12 |
| -import { |
13 |
| - OrientationModes, |
14 |
| - ScaleModes, |
15 |
| -} from 'vtk.js/Sources/Rendering/Core/Glyph3DMapper/Constants'; |
| 4 | +import vtkGlyphRepresentation from 'vtk.js/Sources/Widgets/Representations/GlyphRepresentation'; |
| 5 | +import { Behavior } from 'vtk.js/Sources/Widgets/Representations/WidgetRepresentation/Constants'; |
| 6 | +import { allocateArray } from 'vtk.js/Sources/Widgets/Representations/WidgetRepresentation'; |
16 | 7 | import vtkCylinderSource from 'vtk.js/Sources/Filters/Sources/CylinderSource';
|
17 |
| -import { vec3 } from 'gl-matrix'; |
| 8 | +import { OrientationModes } from '@kitware/vtk.js/Rendering/Core/Glyph3DMapper/Constants'; |
| 9 | +import { getPixelWorldHeightAtCoord } from 'vtk.js/Sources/Widgets/Core/WidgetManager'; |
18 | 10 |
|
19 | 11 | function vtkLineGlyphRepresentation(publicAPI, model) {
|
20 | 12 | model.classHierarchy.push('vtkLineGlyphRepresentation');
|
21 |
| - const superClass = { ...publicAPI }; |
22 |
| - |
23 |
| - const internalPolyData = vtkPolyData.newInstance({ mtime: 0 }); |
24 |
| - |
25 |
| - function allocateSize(polyData, pointCount, close = false) { |
26 |
| - const glyphCount = pointCount + (close ? 0 : -1); |
27 |
| - if ( |
28 |
| - !polyData.getPoints() || |
29 |
| - polyData.getPoints().length !== glyphCount * 3 |
30 |
| - ) { |
31 |
| - allocateArray(polyData, 'points', glyphCount).getData(); |
32 |
| - } |
33 |
| - |
34 |
| - const cellSize = glyphCount + 1; |
35 |
| - const oldSize = Array.from(polyData.getLines().getCellSizes())[0]; |
36 |
| - if (oldSize !== cellSize) { |
37 |
| - const lines = allocateArray(polyData, 'lines', cellSize + 1); // +1 for to hold number of elements per cell |
38 |
| - const cellArray = lines.getData(); |
39 |
| - cellArray[0] = cellSize; |
40 |
| - for (let i = 1; i <= cellSize; i++) { |
41 |
| - cellArray[i] = i - 1; |
42 |
| - } |
43 |
| - if (close) { |
44 |
| - cellArray[cellSize] = 0; |
45 |
| - } |
46 |
| - lines.setData(cellArray); |
47 |
| - } |
48 |
| - } |
49 |
| - |
50 |
| - /** |
51 |
| - * Change the segments' thickness. |
52 |
| - * @param {number} lineThickness |
53 |
| - */ |
54 |
| - function applyLineThickness(lineThickness) { |
55 |
| - let scaledLineThickness = lineThickness; |
56 |
| - if (publicAPI.getScaleInPixels() && internalPolyData) { |
57 |
| - const center = vtkBoundingBox.getCenter(internalPolyData.getBounds()); |
58 |
| - scaledLineThickness *= getPixelWorldHeightAtCoord( |
59 |
| - center, |
60 |
| - model.displayScaleParams |
61 |
| - ); |
62 |
| - } |
63 |
| - model._pipeline.glyph.setRadius(scaledLineThickness); |
64 |
| - } |
65 |
| - |
66 |
| - model._pipeline = { |
67 |
| - source: publicAPI, |
68 |
| - glyph: vtkCylinderSource.newInstance({ |
69 |
| - direction: [1, 0, 0], |
70 |
| - center: [0.5, 0, 0], |
71 |
| - capping: false, |
72 |
| - }), |
73 |
| - mapper: vtkGlyph3DMapper.newInstance({ |
74 |
| - orientationArray: 'directions', |
75 |
| - orientationMode: OrientationModes.DIRECTION, |
76 |
| - scaleArray: 'lengths', |
77 |
| - scaleMode: ScaleModes.SCALE_BY_COMPONENTS, |
78 |
| - }), |
79 |
| - actor: vtkActor.newInstance({ parentProp: publicAPI }), |
80 |
| - }; |
81 |
| - |
82 |
| - vtkWidgetRepresentation.connectPipeline(model._pipeline); |
83 |
| - publicAPI.addActor(model._pipeline.actor); |
84 | 13 |
|
85 |
| - // -------------------------------------------------------------------------- |
86 |
| - publicAPI.requestData = (inData, outData) => { |
87 |
| - const state = inData[0]; |
88 |
| - outData[0] = internalPolyData; |
89 |
| - |
90 |
| - const originStates = publicAPI.getRepresentationStates(state); |
91 |
| - const points = originStates |
92 |
| - .map((subState) => subState.getOrigin()) |
93 |
| - .filter(Boolean); // filter out states that return invalid origins |
94 |
| - const pointCount = points.length; |
95 |
| - |
96 |
| - allocateSize(internalPolyData, pointCount, model.close && pointCount > 2); |
97 |
| - |
98 |
| - const glyphPositions = internalPolyData.getPoints().getData(); |
99 |
| - // There can be only one line. |
100 |
| - const segments = internalPolyData.getLines().getData(); |
| 14 | + publicAPI.setGlyphResolution = macro.chain( |
| 15 | + publicAPI.setGlyphResolution, |
| 16 | + model._pipeline.glyph.setResolution |
| 17 | + ); |
| 18 | +} |
101 | 19 |
|
102 |
| - const directions = allocateArray( |
103 |
| - internalPolyData, |
104 |
| - 'directions', |
105 |
| - segments.length - 1, |
106 |
| - undefined, |
107 |
| - 3 |
108 |
| - ).getData(); |
109 |
| - const lengths = allocateArray( |
110 |
| - internalPolyData, |
111 |
| - 'lengths', |
112 |
| - segments.length - 1, |
113 |
| - undefined, |
| 20 | +function cylinderScale(publicAPI, model) { |
| 21 | + return (polyData, states) => { |
| 22 | + model._pipeline.mapper.setScaleArray('scale'); |
| 23 | + model._pipeline.mapper.setScaleFactor(1); |
| 24 | + model._pipeline.mapper.setScaling(true); |
| 25 | + model._pipeline.mapper.setScaleMode( |
| 26 | + vtkGlyph3DMapper.ScaleModes.SCALE_BY_COMPONENTS |
| 27 | + ); |
| 28 | + const scales = allocateArray( |
| 29 | + polyData, |
| 30 | + 'scale', |
| 31 | + states.length, |
| 32 | + 'Float32Array', |
114 | 33 | 3
|
115 | 34 | ).getData();
|
116 |
| - |
117 |
| - const tempVector = []; // scratch |
118 |
| - for (let point = 1; point < segments.length - 1; point++) { |
119 |
| - const glyph = (point - 1) * 3; // start of glyph's 3 components in the arrays |
120 |
| - |
121 |
| - // With cylinder glyph's offset center, position at state origins. |
122 |
| - const origin = points[segments[point]]; |
123 |
| - [ |
124 |
| - glyphPositions[glyph], |
125 |
| - glyphPositions[glyph + 1], |
126 |
| - glyphPositions[glyph + 2], |
127 |
| - ] = origin; |
128 |
| - |
129 |
| - // Orient glyphs to next point. |
130 |
| - const target = points[segments[point + 1]]; |
131 |
| - const direction = vtkMath.subtract(target, origin, tempVector); |
132 |
| - [directions[glyph], directions[glyph + 1], directions[glyph + 2]] = |
133 |
| - direction; |
134 |
| - |
135 |
| - // Scale to span between points. |
136 |
| - const distance = vec3.length(direction); |
137 |
| - lengths[glyph] = distance; |
138 |
| - lengths[glyph + 1] = 1; |
139 |
| - lengths[glyph + 2] = 1; |
| 35 | + let j = 0; |
| 36 | + for (let i = 0; i < states.length; ++i) { |
| 37 | + const state = states[i]; |
| 38 | + const origin = state.getOrigin(); |
| 39 | + const nextOrigin = |
| 40 | + states[i === states.length - 1 ? 0 : i + 1].getOrigin(); |
| 41 | + |
| 42 | + const direction = vtkMath.subtract(nextOrigin, origin, []); |
| 43 | + const length = vtkMath.normalize(direction); |
| 44 | + |
| 45 | + let scaleFactor = state.getActive() ? model.activeScaleFactor : 1; |
| 46 | + if (publicAPI.getScaleInPixels()) { |
| 47 | + scaleFactor *= getPixelWorldHeightAtCoord( |
| 48 | + state.getOrigin(), |
| 49 | + model.displayScaleParams |
| 50 | + ); |
| 51 | + } |
| 52 | + const scale = state.getScale3?.() ?? [ |
| 53 | + model.lineThickness, |
| 54 | + model.lineThickness, |
| 55 | + 1, |
| 56 | + ]; |
| 57 | + scales[j++] = length * scale[0]; |
| 58 | + scales[j++] = scaleFactor * scale[1]; |
| 59 | + scales[j++] = scaleFactor * scale[2]; |
140 | 60 | }
|
141 |
| - |
142 |
| - internalPolyData.getPoints().modified(); |
143 |
| - internalPolyData.modified(); |
144 |
| - |
145 |
| - const lineThickness = state.getLineThickness?.() ?? model.lineThickness; |
146 |
| - applyLineThickness(lineThickness); |
147 | 61 | };
|
| 62 | +} |
148 | 63 |
|
149 |
| - // return array of all states |
150 |
| - publicAPI.getSelectedState = () => model.inputData[0]; |
151 |
| - |
152 |
| - publicAPI.updateActorVisibility = (renderingType, ctxVisible, hVisible) => { |
153 |
| - const state = model.inputData[0]; |
154 |
| - |
155 |
| - // Make lines/tubes thicker for picking |
156 |
| - let lineThickness = state.getLineThickness?.() ?? model.lineThickness; |
157 |
| - if (renderingType === RenderingTypes.PICKING_BUFFER) { |
158 |
| - lineThickness = Math.max(4, lineThickness); |
| 64 | +function cylinderDirection(publicAPI, model) { |
| 65 | + return (polyData, states) => { |
| 66 | + model._pipeline.mapper.setOrientationArray('orientation'); |
| 67 | + model._pipeline.mapper.setOrientationMode(OrientationModes.MATRIX); |
| 68 | + const orientation = allocateArray( |
| 69 | + polyData, |
| 70 | + 'orientation', |
| 71 | + states.length, |
| 72 | + 'Float32Array', |
| 73 | + 9 |
| 74 | + ).getData(); |
| 75 | + for (let i = 0; i < states.length; ++i) { |
| 76 | + const state = states[i]; |
| 77 | + const origin = state.getOrigin(); |
| 78 | + const nextOrigin = |
| 79 | + states[i === states.length - 1 ? 0 : i + 1].getOrigin(); |
| 80 | + |
| 81 | + const direction = vtkMath.subtract(nextOrigin, origin, []); |
| 82 | + vtkMath.normalize(direction); |
| 83 | + const right = [1, 0, 0]; |
| 84 | + const up = [0, 1, 0]; |
| 85 | + vtkMath.perpendiculars(direction, up, right, 0); |
| 86 | + |
| 87 | + orientation.set(direction, 9 * i); |
| 88 | + orientation.set(up, 9 * i + 3); |
| 89 | + orientation.set(right, 9 * i + 6); |
159 | 90 | }
|
160 |
| - applyLineThickness(lineThickness); |
161 |
| - |
162 |
| - return superClass.updateActorVisibility( |
163 |
| - renderingType, |
164 |
| - ctxVisible, |
165 |
| - hVisible |
166 |
| - ); |
167 | 91 | };
|
168 | 92 | }
|
169 | 93 |
|
170 | 94 | // ----------------------------------------------------------------------------
|
171 | 95 | // Object factory
|
172 | 96 | // ----------------------------------------------------------------------------
|
173 | 97 |
|
174 |
| -const DEFAULT_VALUES = { |
175 |
| - close: false, |
176 |
| - lineThickness: 1, |
177 |
| - scaleInPixels: true, |
178 |
| -}; |
| 98 | +function defaultValues(publicAPI, model, initialValues) { |
| 99 | + return { |
| 100 | + behavior: Behavior.CONTEXT, |
| 101 | + glyphResolution: 32, |
| 102 | + lineThickness: 1, |
| 103 | + ...initialValues, |
| 104 | + _pipeline: { |
| 105 | + glyph: |
| 106 | + initialValues?.pipeline?.glyph ?? |
| 107 | + vtkCylinderSource.newInstance({ |
| 108 | + direction: [1, 0, 0], |
| 109 | + center: [0.5, 0, 0], |
| 110 | + capping: false, |
| 111 | + }), |
| 112 | + ...initialValues?.pipeline, |
| 113 | + }, |
| 114 | + applyMixin: { |
| 115 | + noScale: cylinderScale(publicAPI, model), |
| 116 | + noOrientation: cylinderDirection(publicAPI, model), |
| 117 | + }, |
| 118 | + }; |
| 119 | +} |
179 | 120 |
|
180 | 121 | // ----------------------------------------------------------------------------
|
181 | 122 |
|
182 | 123 | export function extend(publicAPI, model, initialValues = {}) {
|
183 |
| - const newDefault = { ...DEFAULT_VALUES, ...initialValues }; |
184 |
| - vtkWidgetRepresentation.extend(publicAPI, model, newDefault); |
185 |
| - macro.setGet(publicAPI, model, ['close', 'lineThickness']); |
| 124 | + vtkGlyphRepresentation.extend( |
| 125 | + publicAPI, |
| 126 | + model, |
| 127 | + defaultValues(publicAPI, model, initialValues) |
| 128 | + ); |
| 129 | + macro.setGet(publicAPI, model, ['glyphResolution', 'lineThickness']); |
| 130 | + macro.get(publicAPI, model._pipeline, ['glyph', 'mapper', 'actor']); |
186 | 131 |
|
187 | 132 | vtkLineGlyphRepresentation(publicAPI, model);
|
188 | 133 | }
|
|
0 commit comments