Skip to content

Commit d8847bb

Browse files
committed
feat: add resetCameraScreenSpace method with offsetRatio input
Port of VTK C++ vtkRenderer::ResetCameraScreenSpace. Uses a screen-space bounding box to zoom closer to the data, correctly accounting for viewport aspect ratio for both perspective and parallel projection. Also adds zoomToBoxUsingViewAngle as a standalone public method. Closes #1285
1 parent 327921d commit d8847bb

18 files changed

Lines changed: 304 additions & 166 deletions

File tree

CONTRIBUTING.md

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,34 +33,27 @@ Please follow the coding style:
3333
$ git checkout -b new_feature
3434
```
3535

36-
6. Start hacking. Additional information on how to create class/test/example can be found
36+
4. Start hacking. Additional information on how to create class/test/example can be found
3737
[here](https://kitware.github.io/vtk-js/docs/) in the __Development__ section.
3838

3939
```sh
4040
$ edit file1 file2 file3
4141
$ git add file1 file2 file3
4242
```
4343

44-
7. Verify you have correctly formatted code, and that all tests pass:
45-
46-
```sh
47-
$ npm run reformat
48-
$ npm run test
49-
```
50-
51-
8. Use Commitizen to create commits
44+
5. Use Commitizen to create commits
5245

5346
```sh
5447
$ npm run commit
5548
```
5649

57-
9. Push commits in your feature branch to your fork in GitHub:
50+
6. Push commits in your feature branch to your fork in GitHub:
5851

5952
```sh
6053
$ git push origin new_feature
6154
```
6255

63-
10. Visit your fork in Github, browse to the "**Pull Requests**" link on the left, and use the
56+
7. Visit your fork in Github, browse to the "**Pull Requests**" link on the left, and use the
6457
"**New Pull Request**" button in the upper right to create a Pull Request.
6558

6659
For more information see:
@@ -69,7 +62,7 @@ Please follow the coding style:
6962
If committing changes to `vtk.js@next` or `vtk.js@next-major`, then set your base branch to be `next`
7063
or `next-major`, respectively. For more info see the section on release channels below.
7164

72-
11. vtk.js uses GitHub for code review and Github Actions to validate proposed patches before they are merged.
65+
8. vtk.js uses GitHub for code review and Github Actions to validate proposed patches before they are merged.
7366

7467
## Release Channels
7568

Sources/Filters/General/ImageMarchingSquares/index.js

Lines changed: 35 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -86,65 +86,57 @@ function vtkImageMarchingSquares(publicAPI, model) {
8686
/**
8787
* Retrieve pixel coordinates.
8888
* @param {Vector3} ijk origin of the pixel
89+
* @param {Vector3} origin origin of the image
90+
* @param {Vector3} spacing spacing of the image
8991
* @param {number} kernelX index of the X element
9092
* @param {number} kernelY index of the Y element
91-
* @param {Function} indexToWorld function to convert index to world coordinates
9293
*/
93-
publicAPI.getPixelPoints = (ijk, kernelX, kernelY, indexToWorld) => {
94-
// pixelPts = a flattened world coordinates array of [ (i,j,k), (i+1,j,k), (i,j+1,k), (i+1,j+1,k)]
95-
96-
// 0: i, j, k
97-
const neighborIJK = [...ijk];
98-
indexToWorld(neighborIJK, pixelPts);
99-
100-
// 1: i+1, j, k
101-
neighborIJK[kernelX] += 1;
102-
const temp = indexToWorld(neighborIJK, []);
103-
pixelPts[3] = temp[0];
104-
pixelPts[4] = temp[1];
105-
pixelPts[5] = temp[2];
106-
107-
// 2: i+1, j+1, k
108-
neighborIJK[kernelY] += 1;
109-
indexToWorld(neighborIJK, temp);
110-
pixelPts[9] = temp[0];
111-
pixelPts[10] = temp[1];
112-
pixelPts[11] = temp[2];
113-
114-
// 3: i, j+1, k
115-
neighborIJK[kernelX] -= 1;
116-
indexToWorld(neighborIJK, temp);
117-
pixelPts[6] = temp[0];
118-
pixelPts[7] = temp[1];
119-
pixelPts[8] = temp[2];
94+
publicAPI.getPixelPoints = (ijk, origin, spacing, kernelX, kernelY) => {
95+
const i = ijk[kernelX];
96+
const j = ijk[kernelY];
97+
98+
// (i,i+1),(j,j+1),(k,k+1) - i varies fastest; then j; then k
99+
pixelPts[0] = origin[kernelX] + i * spacing[kernelX]; // 0
100+
pixelPts[1] = origin[kernelY] + j * spacing[kernelY];
101+
102+
pixelPts[2] = pixelPts[0] + spacing[kernelX]; // 1
103+
pixelPts[3] = pixelPts[1];
104+
105+
pixelPts[4] = pixelPts[0]; // 2
106+
pixelPts[5] = pixelPts[1] + spacing[kernelY];
107+
108+
pixelPts[6] = pixelPts[2]; // 3
109+
pixelPts[7] = pixelPts[5];
120110
};
121111

122112
/**
123113
* Produce points and lines for the polydata.
124114
* @param {number[]} cVal list of contour values
125115
* @param {Vector3} ijk origin of the pixel
126116
* @param {Vector3} dims dimensions of the image
117+
* @param {Vector3} origin origin of the image
127118
* @param {Vector3} spacing sapcing of the image
128119
* @param {TypedArray} scalars list of scalar values
129120
* @param {number[]} points list of points
130121
* @param {number[]} lines list of lines
131122
* @param {Vector3} increments IJK slice increments
132123
* @param {number} kernelX index of the X element
133124
* @param {number} kernelY index of the Y element
134-
* @param {Function} indexToWorld function to convert index to world coordinates
135125
*/
136126
publicAPI.produceLines = (
137127
cVal,
138128
ijk,
139129
dims,
130+
origin,
131+
spacing,
140132
scalars,
141133
points,
142134
lines,
143135
increments,
144136
kernelX,
145-
kernelY,
146-
indexToWorld
137+
kernelY
147138
) => {
139+
const k = ijk[model.slicingMode];
148140
const CASE_MASK = [1, 2, 8, 4]; // case table is actually for quad
149141
const xyz = [];
150142
let pId;
@@ -163,8 +155,9 @@ function vtkImageMarchingSquares(publicAPI, model) {
163155
return; // don't get the pixel coordinates, nothing to do
164156
}
165157

166-
publicAPI.getPixelPoints(ijk, kernelX, kernelY, indexToWorld);
158+
publicAPI.getPixelPoints(ijk, origin, spacing, kernelX, kernelY);
167159

160+
const z = origin[model.slicingMode] + k * spacing[model.slicingMode];
168161
for (let idx = 0; pixelLines[idx] >= 0; idx += 2) {
169162
lines.push(2);
170163
for (let eid = 0; eid < 2; eid++) {
@@ -180,11 +173,11 @@ function vtkImageMarchingSquares(publicAPI, model) {
180173
const t =
181174
(cVal - pixelScalars[edgeVerts[0]]) /
182175
(pixelScalars[edgeVerts[1]] - pixelScalars[edgeVerts[0]]);
183-
const x0 = pixelPts.slice(edgeVerts[0] * 3, (edgeVerts[0] + 1) * 3);
184-
const x1 = pixelPts.slice(edgeVerts[1] * 3, (edgeVerts[1] + 1) * 3);
185-
xyz[0] = x0[0] + t * (x1[0] - x0[0]);
186-
xyz[1] = x0[1] + t * (x1[1] - x0[1]);
187-
xyz[2] = x0[2] + t * (x1[2] - x0[2]);
176+
const x0 = pixelPts.slice(edgeVerts[0] * 2, (edgeVerts[0] + 1) * 2);
177+
const x1 = pixelPts.slice(edgeVerts[1] * 2, (edgeVerts[1] + 1) * 2);
178+
xyz[kernelX] = x0[0] + t * (x1[0] - x0[0]);
179+
xyz[kernelY] = x0[1] + t * (x1[1] - x0[1]);
180+
xyz[model.slicingMode] = z;
188181
pId = points.length / 3;
189182
points.push(xyz[0], xyz[1], xyz[2]);
190183

@@ -218,12 +211,13 @@ function vtkImageMarchingSquares(publicAPI, model) {
218211
console.time('msquares');
219212

220213
// Retrieve output and volume data
214+
const origin = input.getOrigin();
215+
const spacing = input.getSpacing();
221216
const dims = input.getDimensions();
222217
const extent = input.getExtent();
223218
const increments = input.computeIncrements(extent);
224219
const scalars = input.getPointData().getScalars().getData();
225220
const [kernelX, kernelY] = getKernels();
226-
const indexToWorld = input.indexToWorld;
227221

228222
// Points - dynamic array
229223
const points = [];
@@ -250,13 +244,14 @@ function vtkImageMarchingSquares(publicAPI, model) {
250244
model.contourValues[cv],
251245
ijk,
252246
dims,
247+
origin,
248+
spacing,
253249
scalars,
254250
points,
255251
lines,
256252
increments,
257253
kernelX,
258-
kernelY,
259-
indexToWorld
254+
kernelY
260255
);
261256
}
262257
}

Sources/IO/Geometry/IFCImporter/index.js

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@ function vtkIFCImporter(publicAPI, model) {
4545
const pointValues = new Float32Array(vertices.length / 2);
4646
const normalsArray = new Float32Array(vertices.length / 2);
4747

48-
for (let i = 0, p = 0; i < vertices.length; p += 3) {
49-
pointValues[p] = vertices[i++];
50-
pointValues[p + 1] = vertices[i++];
51-
pointValues[p + 2] = vertices[i++];
52-
53-
normalsArray[p] = vertices[i++];
54-
normalsArray[p + 1] = vertices[i++];
55-
normalsArray[p + 2] = vertices[i++];
48+
for (let i = 0; i < vertices.length; i += 6) {
49+
pointValues[i / 2] = vertices[i];
50+
pointValues[i / 2 + 1] = vertices[i + 1];
51+
pointValues[i / 2 + 2] = vertices[i + 2];
52+
53+
normalsArray[i / 2] = vertices[i + 3];
54+
normalsArray[i / 2 + 1] = vertices[i + 4];
55+
normalsArray[i / 2 + 2] = vertices[i + 5];
5656
}
5757

5858
const nCells = indices.length;
@@ -91,42 +91,48 @@ function vtkIFCImporter(publicAPI, model) {
9191
const colorArray = new Float32Array(vertices.length / 2);
9292

9393
if (userMatrix) {
94-
for (let i = 0, p = 0; i < vertices.length; p += 3) {
95-
pointValues[p] = vertices[i++];
96-
pointValues[p + 1] = vertices[i++];
97-
pointValues[p + 2] = vertices[i++];
98-
99-
normalsArray[p] = vertices[i++];
100-
normalsArray[p + 1] = vertices[i++];
101-
normalsArray[p + 2] = vertices[i++];
102-
103-
colorArray[p] = color.x;
104-
colorArray[p + 1] = color.y;
105-
colorArray[p + 2] = color.z;
106-
}
107-
108-
vtkMatrixBuilder
94+
const transformMatrix = vtkMatrixBuilder
10995
.buildFromRadian()
110-
.setMatrix(userMatrix)
111-
.apply(pointValues);
96+
.setMatrix(userMatrix);
11297

113-
vtkMatrixBuilder
98+
const normalMatrix = vtkMatrixBuilder
11499
.buildFromRadian()
115-
.multiply3x3(mat3.fromMat4(mat3.create(), userMatrix))
116-
.apply(normalsArray);
100+
.multiply3x3(mat3.fromMat4(mat3.create(), userMatrix));
101+
102+
for (let i = 0; i < vertices.length; i += 6) {
103+
const point = [vertices[i], vertices[i + 1], vertices[i + 2]];
104+
const normal = [vertices[i + 3], vertices[i + 4], vertices[i + 5]];
105+
106+
transformMatrix.apply(point);
107+
normalMatrix.apply(normal);
108+
109+
pointValues[i / 2] = point[0];
110+
pointValues[i / 2 + 1] = point[1];
111+
pointValues[i / 2 + 2] = point[2];
112+
113+
normalsArray[i / 2] = normal[0];
114+
normalsArray[i / 2 + 1] = normal[1];
115+
normalsArray[i / 2 + 2] = normal[2];
116+
117+
const colorIndex = i / 2;
118+
colorArray[colorIndex] = color.x;
119+
colorArray[colorIndex + 1] = color.y;
120+
colorArray[colorIndex + 2] = color.z;
121+
}
117122
} else {
118-
for (let i = 0, p = 0; i < vertices.length; p += 3) {
119-
pointValues[p] = vertices[i++];
120-
pointValues[p + 1] = vertices[i++];
121-
pointValues[p + 2] = vertices[i++];
122-
123-
normalsArray[p] = vertices[i++];
124-
normalsArray[p + 1] = vertices[i++];
125-
normalsArray[p + 2] = vertices[i++];
126-
127-
colorArray[p] = color.x;
128-
colorArray[p + 1] = color.y;
129-
colorArray[p + 2] = color.z;
123+
for (let i = 0; i < vertices.length; i += 6) {
124+
pointValues[i / 2] = vertices[i];
125+
pointValues[i / 2 + 1] = vertices[i + 1];
126+
pointValues[i / 2 + 2] = vertices[i + 2];
127+
128+
normalsArray[i / 2] = vertices[i + 3];
129+
normalsArray[i / 2 + 1] = vertices[i + 4];
130+
normalsArray[i / 2 + 2] = vertices[i + 5];
131+
132+
const colorIndex = i / 2;
133+
colorArray[colorIndex] = color.x;
134+
colorArray[colorIndex + 1] = color.y;
135+
colorArray[colorIndex + 2] = color.z;
130136
}
131137
}
132138

Sources/Rendering/Core/Renderer/index.d.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,45 @@ export interface vtkRenderer extends vtkViewport {
611611
*/
612612
resetCamera(bounds?: Bounds): boolean;
613613

614+
/**
615+
* Zoom the camera so the given screen-space box fills the viewport.
616+
*
617+
* Matches the VTK C++ vtkRenderer::ZoomToBoxUsingViewAngle behavior.
618+
*
619+
* @param {{ x: number; y: number; width: number; height: number }} box
620+
* Screen-space rectangle in display (pixel) coordinates.
621+
* @param {Number} [offsetRatio=1.0] Scale factor applied to the zoom
622+
* (values < 1 leave a margin around the box).
623+
*/
624+
zoomToBoxUsingViewAngle(
625+
box: { x: number; y: number; width: number; height: number },
626+
offsetRatio?: number
627+
): void;
628+
629+
/**
630+
* Automatically set up the camera based on the visible actors, using a
631+
* screen-space bounding box to zoom closer to the data.
632+
*
633+
* This method first calls resetCamera to ensure all bounds are visible, then
634+
* projects the bounding box corners to screen space and zooms so the actors
635+
* fill the specified fraction of the viewport. This correctly accounts for
636+
* viewport aspect ratio for both perspective and parallel projection.
637+
*
638+
* Matches the VTK C++ vtkRenderer::ResetCameraScreenSpace behavior.
639+
*
640+
* @param {Bounds} [bounds] Optional bounding box to use. If not provided,
641+
* the visible prop bounds are computed automatically.
642+
* @param {Number} [offsetRatio=0.9] Fraction of screen space to fill
643+
* (0.9 = 90%, leaving 10% margin at the edges).
644+
*/
645+
resetCameraScreenSpace(bounds?: Bounds | null, offsetRatio?: number): boolean;
646+
647+
/**
648+
* Overload that accepts only offsetRatio (bounds are computed automatically).
649+
* @param {Number} offsetRatio Fraction of screen space to fill.
650+
*/
651+
resetCameraScreenSpace(offsetRatio?: number): boolean;
652+
614653
/**
615654
* Reset the camera clipping range based on a bounding box.
616655
* @param {Bounds} [bounds]

0 commit comments

Comments
 (0)