diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index aaaa31c9..41eb2b4f 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -78,6 +78,7 @@ export default defineConfig({ { text: 'Edges', link: '/guide/abstractions/edges' }, { text: 'PositionalAudio', link: '/guide/abstractions/positional-audio' }, { text: 'AnimatedSprite', link: '/guide/abstractions/animated-sprite' }, + { text: 'ScreenSizer', link: '/guide/abstractions/screen-sizer' }, { text: 'ScreenSpace', link: '/guide/abstractions/screen-space' }, { text: 'Outline', link: '/guide/abstractions/outline' }, { text: 'Image', link: '/guide/abstractions/image' }, diff --git a/docs/.vitepress/theme/components/ScreenSizerDemo.vue b/docs/.vitepress/theme/components/ScreenSizerDemo.vue new file mode 100644 index 00000000..b481e008 --- /dev/null +++ b/docs/.vitepress/theme/components/ScreenSizerDemo.vue @@ -0,0 +1,22 @@ + + + diff --git a/docs/guide/abstractions/screen-sizer.md b/docs/guide/abstractions/screen-sizer.md new file mode 100644 index 00000000..f4936fa2 --- /dev/null +++ b/docs/guide/abstractions/screen-sizer.md @@ -0,0 +1,17 @@ +# ScreenSizer + + + + + +Adds a `` wrapper that scales to "screen space". By default `1` THREE world unit will be translated to 1 screen pixel. + +E.g. a BoxGeometry with a height, width, and depth of 100 each, will be scaled to 100 screen pixels in each dimension. + +## Usage + +<<< @/.vitepress/theme/components/ScreenSizerDemo.vue + +## Props + +Inherits all props from `THREE.Object3D`. diff --git a/playground/vue/src/pages/abstractions/ScreenSizerDemo.vue b/playground/vue/src/pages/abstractions/ScreenSizerDemo.vue new file mode 100644 index 00000000..2645e727 --- /dev/null +++ b/playground/vue/src/pages/abstractions/ScreenSizerDemo.vue @@ -0,0 +1,23 @@ + + + diff --git a/playground/vue/src/router/routes/abstractions.ts b/playground/vue/src/router/routes/abstractions.ts index cb002504..7650ae4b 100644 --- a/playground/vue/src/router/routes/abstractions.ts +++ b/playground/vue/src/router/routes/abstractions.ts @@ -84,4 +84,9 @@ export const abstractionsRoutes = [ name: 'Billboard', component: () => import('../../pages/abstractions/BillboardDemo.vue'), }, + { + path: '/abstractions/screen-sizer', + name: 'ScreenSizer', + component: () => import('../../pages/abstractions/ScreenSizerDemo.vue'), + }, ] diff --git a/src/core/abstractions/ScreenSizer.vue b/src/core/abstractions/ScreenSizer.vue new file mode 100644 index 00000000..be9df500 --- /dev/null +++ b/src/core/abstractions/ScreenSizer.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/core/abstractions/index.ts b/src/core/abstractions/index.ts index 76d59d71..9d04d0fa 100644 --- a/src/core/abstractions/index.ts +++ b/src/core/abstractions/index.ts @@ -13,6 +13,7 @@ import Text3D from './Text3D.vue' import { useAnimations } from './useAnimations' import Fbo from './useFBO/component.vue' import Sampler from './useSurfaceSampler/component.vue' +import ScreenSizer from './ScreenSizer.vue' import Edges from './Edges.vue' export * from '../staging/useEnvironment' @@ -32,6 +33,7 @@ export { PositionalAudio, Reflector, Sampler, + ScreenSizer, ScreenSpace, Text3D, useAnimations, diff --git a/src/utils/calculateScaleFactor.ts b/src/utils/calculateScaleFactor.ts new file mode 100644 index 00000000..801f8c3c --- /dev/null +++ b/src/utils/calculateScaleFactor.ts @@ -0,0 +1,40 @@ +import * as THREE from 'three' + +// NOTE: Source +// https://github.com/pmndrs/drei/blob/f8e5653f7f60d3782301c13e781c9966370b8fda/src/core/calculateScaleFactor.ts#L24 + +const tV0 = new THREE.Vector3() +const tV1 = new THREE.Vector3() +const tV2 = new THREE.Vector3() + +interface Size { + width: number + height: number +} + +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 +}