Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions Sources/Rendering/Core/Renderer/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,45 @@ export interface vtkRenderer extends vtkViewport {
*/
resetCamera(bounds?: Bounds): boolean;

/**
* Zoom the camera so the given screen-space box fills the viewport.
*
* Matches the VTK C++ vtkRenderer::ZoomToBoxUsingViewAngle behavior.
*
* @param {Object} box Screen-space rectangle with x, y, width, height
* in display (pixel) coordinates.
* @param {Number} [offsetRatio=1.0] Scale factor applied to the zoom
* (values < 1 leave a margin around the box).
*/
zoomToBoxUsingViewAngle(
box: { x: number; y: number; width: number; height: number },
offsetRatio?: number
): void;

/**
* Automatically set up the camera based on the visible actors, using a
* screen-space bounding box to zoom closer to the data.
*
* This method first calls resetCamera to ensure all bounds are visible, then
* projects the bounding box corners to screen space and zooms so the actors
* fill the specified fraction of the viewport. This correctly accounts for
* viewport aspect ratio for both perspective and parallel projection.
*
* Matches the VTK C++ vtkRenderer::ResetCameraScreenSpace behavior.
*
* @param {Bounds} [bounds] Optional bounding box to use. If not provided,
* the visible prop bounds are computed automatically.
* @param {Number} [offsetRatio=0.9] Fraction of screen space to fill
* (0.9 = 90%, leaving 10% margin at the edges).
*/
resetCameraScreenSpace(bounds?: Bounds | null, offsetRatio?: number): boolean;

/**
* Overload that accepts only offsetRatio (bounds are computed automatically).
* @param {Number} offsetRatio Fraction of screen space to fill.
*/
resetCameraScreenSpace(offsetRatio?: number): boolean;

/**
* Reset the camera clipping range based on a bounding box.
* @param {Bounds} [bounds]
Expand Down
143 changes: 143 additions & 0 deletions Sources/Rendering/Core/Renderer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,149 @@ function vtkRenderer(publicAPI, model) {
return true;
};

// Port of VTK C++ vtkRenderer::ZoomToBoxUsingViewAngle.
publicAPI.zoomToBoxUsingViewAngle = (box, ratioOrOffsetRatio = 1.0) => {
let view = null;
if (model._renderWindow && model._renderWindow.getViews) {
const views = model._renderWindow.getViews();
if (views.length > 0) {
view = views[0];
}
}

if (!view || !view.getViewportSize) {
return;
}

const size = view.getViewportSize(publicAPI);
if (!size || size[0] <= 0 || size[1] <= 0) {
return;
}

const zf1 = size[0] / box.width;
const zf2 = size[1] / box.height;
const zoomFactor = Math.min(zf1, zf2);
publicAPI.getActiveCamera().zoom(zoomFactor * ratioOrOffsetRatio);
};

// Port of VTK C++ vtkRenderer::ResetCameraScreenSpace.
// Uses a screen-space bounding box to zoom closer to the data.
publicAPI.resetCameraScreenSpace = (bounds = null, offsetRatio = 0.9) => {
let effectiveBounds = bounds;
let effectiveOffsetRatio = offsetRatio;
if (typeof bounds === 'number') {
effectiveOffsetRatio = bounds;
effectiveBounds = null;
}

const boundsToUse = effectiveBounds || publicAPI.computeVisiblePropBounds();

if (!vtkMath.areBoundsInitialized(boundsToUse)) {
vtkDebugMacro('Cannot reset camera!');
return false;
}

// Make sure all bounds are visible to project into screen space
publicAPI.resetCamera(boundsToUse);

// Expand bounds by camera model transform matrix
const expandedBounds = [...boundsToUse];
const modelTransformMatrix = publicAPI
.getActiveCamera()
.getModelTransformMatrix();
if (modelTransformMatrix) {
vtkBoundingBox.transformBounds(
boundsToUse,
modelTransformMatrix,
expandedBounds
);
}

// Get the view from the render window to access viewport size
let view = null;
if (model._renderWindow && model._renderWindow.getViews) {
const views = model._renderWindow.getViews();
if (views.length > 0) {
view = views[0];
}
}

if (!view || !view.getViewportSize) {
return true;
}

const size = view.getViewportSize(publicAPI);
if (!size || size[0] <= 0 || size[1] <= 0) {
return true;
}

const aspect = size[0] / size[1];

// Compute the screen-space bounding box by projecting all 8 corners
let xmin = Number.MAX_VALUE;
let ymin = Number.MAX_VALUE;
let xmax = -Number.MAX_VALUE;
let ymax = -Number.MAX_VALUE;

for (let i = 0; i < 2; ++i) {
for (let j = 0; j < 2; ++j) {
for (let k = 0; k < 2; ++k) {
const nd = publicAPI.worldToNormalizedDisplay(
expandedBounds[i],
expandedBounds[2 + j],
expandedBounds[4 + k],
aspect
);
const dx = nd[0] * size[0];
const dy = nd[1] * size[1];
xmin = Math.min(dx, xmin);
xmax = Math.max(dx, xmax);
ymin = Math.min(dy, ymin);
ymax = Math.max(dy, ymax);
}
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use vtkBoundingBox.transformBounds(boundsToUse, publicAPI.getActiveCamera().getModelTransformMatrix, boundsToUse)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done I think


// Project the focal point in screen space
const fp = model.activeCamera.getFocalPoint();
const fpNd = publicAPI.worldToNormalizedDisplay(
fp[0],
fp[1],
fp[2],
aspect
);
const fpDisplayX = fpNd[0] * size[0];
const fpDisplayY = fpNd[1] * size[1];

// The focal point must be at the center of the box
const xCenterFocalPoint = Math.trunc(fpDisplayX);
const yCenterFocalPoint = Math.trunc(fpDisplayY);
const xCenterBox = Math.trunc((xmin + xmax) / 2);
const yCenterBox = Math.trunc((ymin + ymax) / 2);

const xDiff = 2 * (xCenterFocalPoint - xCenterBox);
const yDiff = 2 * (yCenterFocalPoint - yCenterBox);

xmin += Math.min(xDiff, 0);
xmax += Math.max(xDiff, 0);
ymin += Math.min(yDiff, 0);
ymax += Math.max(yDiff, 0);

const boxWidth = xmax - xmin;
const boxHeight = ymax - ymin;

if (boxWidth > 0 && boxHeight > 0) {
publicAPI.zoomToBoxUsingViewAngle(
{ x: xmin, y: ymin, width: boxWidth, height: boxHeight },
effectiveOffsetRatio
);
}

publicAPI.invokeEvent(RESET_CAMERA_EVENT);

return true;
};

Comment thread
aerogt3 marked this conversation as resolved.
publicAPI.resetCameraClippingRange = (bounds = null) => {
const boundsToUse = bounds || publicAPI.computeVisiblePropBounds();

Expand Down
Loading