@@ -464,6 +464,149 @@ function vtkRenderer(publicAPI, model) {
464464 return true ;
465465 } ;
466466
467+ // Port of VTK C++ vtkRenderer::ZoomToBoxUsingViewAngle.
468+ publicAPI . zoomToBoxUsingViewAngle = ( box , ratioOrOffsetRatio = 1.0 ) => {
469+ let view = null ;
470+ if ( model . _renderWindow && model . _renderWindow . getViews ) {
471+ const views = model . _renderWindow . getViews ( ) ;
472+ if ( views . length > 0 ) {
473+ view = views [ 0 ] ;
474+ }
475+ }
476+
477+ if ( ! view || ! view . getViewportSize ) {
478+ return ;
479+ }
480+
481+ const size = view . getViewportSize ( publicAPI ) ;
482+ if ( ! size || size [ 0 ] <= 0 || size [ 1 ] <= 0 ) {
483+ return ;
484+ }
485+
486+ const zf1 = size [ 0 ] / box . width ;
487+ const zf2 = size [ 1 ] / box . height ;
488+ const zoomFactor = Math . min ( zf1 , zf2 ) ;
489+ publicAPI . getActiveCamera ( ) . zoom ( zoomFactor * ratioOrOffsetRatio ) ;
490+ } ;
491+
492+ // Port of VTK C++ vtkRenderer::ResetCameraScreenSpace.
493+ // Uses a screen-space bounding box to zoom closer to the data.
494+ publicAPI . resetCameraScreenSpace = ( bounds = null , offsetRatio = 0.9 ) => {
495+ let effectiveBounds = bounds ;
496+ let effectiveOffsetRatio = offsetRatio ;
497+ if ( typeof bounds === 'number' ) {
498+ effectiveOffsetRatio = bounds ;
499+ effectiveBounds = null ;
500+ }
501+
502+ const boundsToUse = effectiveBounds || publicAPI . computeVisiblePropBounds ( ) ;
503+
504+ if ( ! vtkMath . areBoundsInitialized ( boundsToUse ) ) {
505+ vtkDebugMacro ( 'Cannot reset camera!' ) ;
506+ return false ;
507+ }
508+
509+ // Make sure all bounds are visible to project into screen space
510+ publicAPI . resetCamera ( boundsToUse ) ;
511+
512+ // Expand bounds by camera model transform matrix
513+ const expandedBounds = [ ...boundsToUse ] ;
514+ const modelTransformMatrix = publicAPI
515+ . getActiveCamera ( )
516+ . getModelTransformMatrix ( ) ;
517+ if ( modelTransformMatrix ) {
518+ vtkBoundingBox . transformBounds (
519+ boundsToUse ,
520+ modelTransformMatrix ,
521+ expandedBounds
522+ ) ;
523+ }
524+
525+ // Get the view from the render window to access viewport size
526+ let view = null ;
527+ if ( model . _renderWindow && model . _renderWindow . getViews ) {
528+ const views = model . _renderWindow . getViews ( ) ;
529+ if ( views . length > 0 ) {
530+ view = views [ 0 ] ;
531+ }
532+ }
533+
534+ if ( ! view || ! view . getViewportSize ) {
535+ return true ;
536+ }
537+
538+ const size = view . getViewportSize ( publicAPI ) ;
539+ if ( ! size || size [ 0 ] <= 0 || size [ 1 ] <= 0 ) {
540+ return true ;
541+ }
542+
543+ const aspect = size [ 0 ] / size [ 1 ] ;
544+
545+ // Compute the screen-space bounding box by projecting all 8 corners
546+ let xmin = Number . MAX_VALUE ;
547+ let ymin = Number . MAX_VALUE ;
548+ let xmax = - Number . MAX_VALUE ;
549+ let ymax = - Number . MAX_VALUE ;
550+
551+ for ( let i = 0 ; i < 2 ; ++ i ) {
552+ for ( let j = 0 ; j < 2 ; ++ j ) {
553+ for ( let k = 0 ; k < 2 ; ++ k ) {
554+ const nd = publicAPI . worldToNormalizedDisplay (
555+ expandedBounds [ i ] ,
556+ expandedBounds [ 2 + j ] ,
557+ expandedBounds [ 4 + k ] ,
558+ aspect
559+ ) ;
560+ const dx = nd [ 0 ] * size [ 0 ] ;
561+ const dy = nd [ 1 ] * size [ 1 ] ;
562+ xmin = Math . min ( dx , xmin ) ;
563+ xmax = Math . max ( dx , xmax ) ;
564+ ymin = Math . min ( dy , ymin ) ;
565+ ymax = Math . max ( dy , ymax ) ;
566+ }
567+ }
568+ }
569+
570+ // Project the focal point in screen space
571+ const fp = model . activeCamera . getFocalPoint ( ) ;
572+ const fpNd = publicAPI . worldToNormalizedDisplay (
573+ fp [ 0 ] ,
574+ fp [ 1 ] ,
575+ fp [ 2 ] ,
576+ aspect
577+ ) ;
578+ const fpDisplayX = fpNd [ 0 ] * size [ 0 ] ;
579+ const fpDisplayY = fpNd [ 1 ] * size [ 1 ] ;
580+
581+ // The focal point must be at the center of the box
582+ const xCenterFocalPoint = Math . trunc ( fpDisplayX ) ;
583+ const yCenterFocalPoint = Math . trunc ( fpDisplayY ) ;
584+ const xCenterBox = Math . trunc ( ( xmin + xmax ) / 2 ) ;
585+ const yCenterBox = Math . trunc ( ( ymin + ymax ) / 2 ) ;
586+
587+ const xDiff = 2 * ( xCenterFocalPoint - xCenterBox ) ;
588+ const yDiff = 2 * ( yCenterFocalPoint - yCenterBox ) ;
589+
590+ xmin += Math . min ( xDiff , 0 ) ;
591+ xmax += Math . max ( xDiff , 0 ) ;
592+ ymin += Math . min ( yDiff , 0 ) ;
593+ ymax += Math . max ( yDiff , 0 ) ;
594+
595+ const boxWidth = xmax - xmin ;
596+ const boxHeight = ymax - ymin ;
597+
598+ if ( boxWidth > 0 && boxHeight > 0 ) {
599+ publicAPI . zoomToBoxUsingViewAngle (
600+ { x : xmin , y : ymin , width : boxWidth , height : boxHeight } ,
601+ effectiveOffsetRatio
602+ ) ;
603+ }
604+
605+ publicAPI . invokeEvent ( RESET_CAMERA_EVENT ) ;
606+
607+ return true ;
608+ } ;
609+
467610 publicAPI . resetCameraClippingRange = ( bounds = null ) => {
468611 const boundsToUse = bounds || publicAPI . computeVisiblePropBounds ( ) ;
469612
0 commit comments