From 8f73c46839234fc791cf45c7bcb4ff11cafd95fc Mon Sep 17 00:00:00 2001 From: Rodri Date: Tue, 30 Jan 2024 23:40:05 +0000 Subject: [PATCH] feat: add ScreenSizer (#1760) Co-authored-by: drcmda --- .storybook/stories/ScreenSizer.stories.tsx | 65 ++++++++++++++++++++++ README.md | 17 ++++++ src/core/ScreenSizer.tsx | 43 ++++++++++++++ src/core/calculateScaleFactor.ts | 33 +++++++++++ src/core/index.ts | 4 ++ src/web/pivotControls/index.tsx | 42 ++------------ 6 files changed, 168 insertions(+), 36 deletions(-) create mode 100644 .storybook/stories/ScreenSizer.stories.tsx create mode 100644 src/core/ScreenSizer.tsx create mode 100644 src/core/calculateScaleFactor.ts diff --git a/.storybook/stories/ScreenSizer.stories.tsx b/.storybook/stories/ScreenSizer.stories.tsx new file mode 100644 index 000000000..5fcd9c958 --- /dev/null +++ b/.storybook/stories/ScreenSizer.stories.tsx @@ -0,0 +1,65 @@ +import * as React from 'react' +import { Vector3 } from 'three' +import { Box, Html, ScreenSizer } from '../../src' + +import { Setup } from '../Setup' + +export default { + title: 'Abstractions/ScreenSizer', + component: ScreenSizer, + decorators: [(storyFn) => {storyFn()}], +} + +export const ScreenSizerStory = ({ scale }) => ( + <> + + + + Normal Box + + + + + + + Box wrapped in ScreenSizer + + + + + Zoom in/out to see the difference + + +) + +ScreenSizerStory.args = { + scale: 1, +} diff --git a/README.md b/README.md index 9c20a613b..9c92c7813 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ The `native` route of the library **does not** export `Html` or `Loader`. The de
  • PositionalAudio
  • Billboard
  • ScreenSpace
  • +
  • ScreenSizer
  • Effects
  • GradientTexture
  • Edges
  • @@ -1485,6 +1486,22 @@ Adds a `` that aligns objects to screen space. ``` +#### ScreenSizer + +[![](https://img.shields.io/badge/-storybook-%23ff69b4)](https://drei.pmnd.rs/?path=/story/abstractions-screensizer--screen-sizer-story) + +Adds a `` that scales objects to screen space. + +```jsx + + + +``` + #### GradientTexture

    diff --git a/src/core/ScreenSizer.tsx b/src/core/ScreenSizer.tsx new file mode 100644 index 000000000..57bc6563e --- /dev/null +++ b/src/core/ScreenSizer.tsx @@ -0,0 +1,43 @@ +import { Object3DProps, useFrame } from '@react-three/fiber' +import { ForwardRefComponent } from 'helpers/ts-utils' +import * as React from 'react' +import { forwardRef, useRef } from 'react' +import mergeRefs from 'react-merge-refs' +import { Object3D, Vector3 } from 'three' +import { calculateScaleFactor } from './calculateScaleFactor' + +const worldPos = /* @__PURE__ */ new Vector3() + +export interface ScreenSizerProps extends Object3DProps { + /** Scale factor. Defaults to 1, which equals 1 pixel size. */ + scale: number +} + +/** + * Wraps children in an `Object3D` and attempts to scale from + * world units to screen units * scale factor. + * + * For example, this will render a box of roughly 1x1 pixel size, + * independently of how far the camera is. + * + * ```jsx + * + * + * + * ``` + */ +export const ScreenSizer: ForwardRefComponent = /* @__PURE__ */ forwardRef< + Object3D, + ScreenSizerProps +>(({ scale = 1, ...props }, ref) => { + const container = useRef(null) + + useFrame((state) => { + const obj = container.current + if (!obj) return + const sf = calculateScaleFactor(obj.getWorldPosition(worldPos), scale, state.camera, state.size) + obj.scale.setScalar(sf * scale) + }) + + return +}) diff --git a/src/core/calculateScaleFactor.ts b/src/core/calculateScaleFactor.ts new file mode 100644 index 000000000..9c15c784d --- /dev/null +++ b/src/core/calculateScaleFactor.ts @@ -0,0 +1,33 @@ +import { Size } from '@react-three/fiber' +import * as THREE from 'three' + +const tV0 = /* @__PURE__ */ new THREE.Vector3() +const tV1 = /* @__PURE__ */ new THREE.Vector3() +const tV2 = /* @__PURE__ */ new THREE.Vector3() + +const getPoint2 = (point3: THREE.Vector3, camera: THREE.Camera, size: Size) => { + const widthHalf = size.width / 2 + const heightHalf = size.height / 2 + camera.updateMatrixWorld(false) + const vector = point3.project(camera) + vector.x = vector.x * widthHalf + widthHalf + vector.y = -(vector.y * heightHalf) + heightHalf + return vector +} + +const getPoint3 = (point2: THREE.Vector3, camera: THREE.Camera, size: Size, zValue: number = 1) => { + const vector = tV0.set((point2.x / size.width) * 2 - 1, -(point2.y / size.height) * 2 + 1, zValue) + vector.unproject(camera) + return vector +} + +export const calculateScaleFactor = (point3: THREE.Vector3, radiusPx: number, camera: THREE.Camera, size: Size) => { + const point2 = getPoint2(tV2.copy(point3), camera, size) + let scale = 0 + for (let i = 0; i < 2; ++i) { + const point2off = tV1.copy(point2).setComponent(i, point2.getComponent(i) + radiusPx) + const point3off = getPoint3(point2off, camera, size, point2off.z) + scale = Math.max(scale, point3.distanceTo(point3off)) + } + return scale +} diff --git a/src/core/index.ts b/src/core/index.ts index 88bc66dfa..167d7a86c 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,6 +1,7 @@ // Abstractions export * from './Billboard' export * from './ScreenSpace' +export * from './ScreenSizer' export * from './QuadraticBezierLine' export * from './CubicBezierLine' export * from './CatmullRomLine' @@ -143,3 +144,6 @@ export * from './Mask' export * from './Hud' export * from './Fisheye' export * from './MeshPortalMaterial' + +// Others +export * from './calculateScaleFactor' diff --git a/src/web/pivotControls/index.tsx b/src/web/pivotControls/index.tsx index f0de05258..30e9ba9d2 100644 --- a/src/web/pivotControls/index.tsx +++ b/src/web/pivotControls/index.tsx @@ -1,43 +1,13 @@ -import * as THREE from 'three' +import { useFrame, useThree } from '@react-three/fiber' import * as React from 'react' -import { Size, extend, useFrame, useThree } from '@react-three/fiber' +import * as THREE from 'three' +import { ForwardRefComponent } from '../../helpers/ts-utils' import { AxisArrow } from './AxisArrow' -import { PlaneSlider } from './PlaneSlider' import { AxisRotator } from './AxisRotator' -import { context, OnDragStartProps } from './context' -import { ForwardRefComponent } from '../../helpers/ts-utils' - -const tV0 = /* @__PURE__ */ new THREE.Vector3() -const tV1 = /* @__PURE__ */ new THREE.Vector3() -const tV2 = /* @__PURE__ */ new THREE.Vector3() - -const getPoint2 = (point3: THREE.Vector3, camera: THREE.Camera, size: Size) => { - const widthHalf = size.width / 2 - const heightHalf = size.height / 2 - camera.updateMatrixWorld(false) - const vector = point3.project(camera) - vector.x = vector.x * widthHalf + widthHalf - vector.y = -(vector.y * heightHalf) + heightHalf - return vector -} - -const getPoint3 = (point2: THREE.Vector3, camera: THREE.Camera, size: Size, zValue: number = 1) => { - const vector = tV0.set((point2.x / size.width) * 2 - 1, -(point2.y / size.height) * 2 + 1, zValue) - vector.unproject(camera) - return vector -} - -export const calculateScaleFactor = (point3: THREE.Vector3, radiusPx: number, camera: THREE.Camera, size: Size) => { - const point2 = getPoint2(tV2.copy(point3), camera, size) - let scale = 0 - for (let i = 0; i < 2; ++i) { - const point2off = tV1.copy(point2).setComponent(i, point2.getComponent(i) + radiusPx) - const point3off = getPoint3(point2off, camera, size, point2off.z) - scale = Math.max(scale, point3.distanceTo(point3off)) - } - return scale -} +import { PlaneSlider } from './PlaneSlider' +import { OnDragStartProps, context } from './context' +import { calculateScaleFactor } from '../../core/calculateScaleFactor' const mL0 = /* @__PURE__ */ new THREE.Matrix4() const mW0 = /* @__PURE__ */ new THREE.Matrix4()