diff --git a/.storybook/stories/Resize.stories.tsx b/.storybook/stories/Resize.stories.tsx
new file mode 100644
index 000000000..c5a00bea4
--- /dev/null
+++ b/.storybook/stories/Resize.stories.tsx
@@ -0,0 +1,45 @@
+import * as THREE from 'three'
+import * as React from 'react'
+import { withKnobs } from '@storybook/addon-knobs'
+
+import { Setup } from '../Setup'
+
+import { Box, Resize, ResizeProps } from '../../src'
+
+export default {
+ title: 'Staging/Resize',
+ component: Resize,
+ decorators: [
+ withKnobs,
+ (storyFn) => (
+
+ {storyFn()}
+
+ ),
+ ],
+}
+
+export const ResizeSt = ({ width, height, depth }: ResizeProps) => (
+ <>
+
+
+
+
+
+
+
+ >
+)
+ResizeSt.args = {
+ width: undefined,
+ height: undefined,
+ depth: undefined,
+}
+
+ResizeSt.argTypes = {
+ width: { control: { type: 'boolean' } },
+ height: { control: { type: 'boolean' } },
+ depth: { control: { type: 'boolean' } },
+}
+
+ResizeSt.storyName = 'Default'
diff --git a/README.md b/README.md
index d1e6f9d33..42d9710b1 100644
--- a/README.md
+++ b/README.md
@@ -3122,6 +3122,41 @@ function ScaledModel() {
```
+#### Resize
+
+[![](https://img.shields.io/badge/-storybook-%23ff69b4)](https://drei.pmnd.rs/?path=/story/staging-resize)
+
+Calculates a boundary box and scales its children so the highest dimension is constrained by 1. NB: proportions are preserved.
+
+```tsx
+export type ResizeProps = JSX.IntrinsicElements['group'] & {
+ /** constrained by width dimension (x axis), undefined */
+ width?: boolean
+ /** constrained by height dimension (y axis), undefined */
+ height?: boolean
+ /** constrained by depth dimension (z axis), undefined */
+ depth?: boolean
+ /** You can optionally pass the Box3, otherwise will be computed, undefined */
+ box3?: THREE.Box3
+ /** See https://threejs.org/docs/index.html?q=box3#api/en/math/Box3.setFromObject */
+ precise?: boolean
+}
+```
+
+```jsx
+
+
+
+```
+
+You can also specify the dimension to be constrained by:
+
+```jsx
+
+
+
+```
+
#### BBAnchor
[![](https://img.shields.io/badge/-storybook-%23ff69b4)](https://drei.vercel.app/?path=/story/misc-bbanchor--bb-anchor-with-html)
diff --git a/src/core/Resize.tsx b/src/core/Resize.tsx
new file mode 100644
index 000000000..50f6e8412
--- /dev/null
+++ b/src/core/Resize.tsx
@@ -0,0 +1,49 @@
+import * as THREE from 'three'
+import * as React from 'react'
+
+export type ResizeProps = JSX.IntrinsicElements['group'] & {
+ /** Whether to fit into width (x axis), undefined */
+ width?: boolean
+ /** Whether to fit into height (y axis), undefined */
+ height?: boolean
+ /** Whether to fit into depth (z axis), undefined */
+ depth?: boolean
+ /** You can optionally pass the Box3, otherwise will be computed, undefined */
+ box3?: THREE.Box3
+ /** See https://threejs.org/docs/index.html?q=box3#api/en/math/Box3.setFromObject */
+ precise?: boolean
+}
+
+export const Resize = React.forwardRef(
+ ({ children, width, height, depth, box3, precise = true, ...props }, fRef) => {
+ const ref = React.useRef(null!)
+ const outer = React.useRef(null!)
+ const inner = React.useRef(null!)
+
+ React.useLayoutEffect(() => {
+ outer.current.matrixWorld.identity()
+
+ box3 ||= new THREE.Box3().setFromObject(inner.current, precise)
+ const w = box3.max.x - box3.min.x
+ const h = box3.max.y - box3.min.y
+ const d = box3.max.z - box3.min.z
+
+ let dimension = Math.max(w, h, d)
+ if (width) dimension = w
+ if (height) dimension = h
+ if (depth) dimension = d
+
+ outer.current.scale.setScalar(1 / dimension)
+ }, [width, height, depth, box3, precise])
+
+ React.useImperativeHandle(fRef, () => ref.current, [])
+
+ return (
+
+
+ {children}
+
+
+ )
+ }
+)
diff --git a/src/core/index.ts b/src/core/index.ts
index 8370c5348..35124fda6 100644
--- a/src/core/index.ts
+++ b/src/core/index.ts
@@ -93,6 +93,7 @@ export * from './ScreenQuad'
// Staging/Prototyping
export * from './Center'
+export * from './Resize'
export * from './Bounds'
export * from './CameraShake'
export * from './Float'