+
+
+```
+
# Gizmos
#### GizmoHelper
@@ -969,6 +1086,7 @@ A box buffer geometry with rounded corners, done with extrusion.
args={[1, 1, 1]} // Width, height, depth. Default is [1, 1, 1]
radius={0.05} // Radius of the rounded corners. Default is 0.05
smoothness={4} // The number of curve segments. Default is 4
+ bevelSegments={4} // The number of bevel segments. Default is 4, setting it to 0 removes the bevel, as a result the texture is applied to the whole geometry.
creaseAngle={0.4} // Smooth normals everywhere except faces that meet at an angle greater than the crease angle
{...meshProps} // All THREE.Mesh props are valid
>
@@ -1134,13 +1252,13 @@ export type FacemeshProps = {
/** a landmark index (to get the position from) or a vec3 to be the origin of the mesh. default: undefined (ie. the bbox center) */
origin?: number | THREE.Vector3
/** A facial transformation matrix, as returned by FaceLandmarkerResult.facialTransformationMatrixes (see: https://developers.google.com/mediapipe/solutions/vision/face_landmarker/web_js#handle_and_display_results) */
- facialTransformationMatrix?: typeof FacemeshDatas.SAMPLE_FACELANDMARKER_RESULT.facialTransformationMatrixes[0]
+ facialTransformationMatrix?: (typeof FacemeshDatas.SAMPLE_FACELANDMARKER_RESULT.facialTransformationMatrixes)[0]
/** Apply position offset extracted from `facialTransformationMatrix` */
offset?: boolean
/** Offset sensitivity factor, less is more sensible */
offsetScalar?: number
/** Fface blendshapes, as returned by FaceLandmarkerResult.faceBlendshapes (see: https://developers.google.com/mediapipe/solutions/vision/face_landmarker/web_js#handle_and_display_results) */
- faceBlendshapes?: typeof FacemeshDatas.SAMPLE_FACELANDMARKER_RESULT.faceBlendshapes[0]
+ faceBlendshapes?: (typeof FacemeshDatas.SAMPLE_FACELANDMARKER_RESULT.faceBlendshapes)[0]
/** whether to enable eyes (nb. `faceBlendshapes` is required for), default: true */
eyes?: boolean
/** Force `origin` to be the middle of the 2 eyes (nb. `eyes` is required for), default: false */
@@ -1184,6 +1302,7 @@ api.eyeRightRef.current.irisDirRef.current.localToWorld(new THREE.Vector3(0, 0,
#### Image
+
@@ -1192,10 +1311,26 @@ api.eyeRightRef.current.irisDirRef.current.localToWorld(new THREE.Vector3(0, 0,
A shader-based image component with auto-cover (similar to css/background: cover).
+```tsx
+export type ImageProps = Omit & {
+ segments?: number
+ scale?: number | [number, number]
+ color?: Color
+ zoom?: number
+ radius?: number
+ grayscale?: number
+ toneMapped?: boolean
+ transparent?: boolean
+ opacity?: number
+ side?: THREE.Side
+}
+```
+
```jsx
function Foo() {
const ref = useRef()
useFrame(() => {
+ ref.current.material.radius = ... // between 0 and 1
ref.current.material.zoom = ... // 1 and higher
ref.current.material.grayscale = ... // between 0 and 1
ref.current.material.color.set(...) // mix-in color
@@ -1210,6 +1345,20 @@ To make the material transparent:
```
+You can have custom planes, for instance a rounded-corner plane.
+
+```jsx
+import { extend } from '@react-three/fiber'
+import { Image } from '@react-three/drei'
+import { easing, geometry } from 'maath'
+
+extend({ RoundedPlaneGeometry: geometry.RoundedPlaneGeometry })
+
+
+
+
+```
+
#### Text
[](https://drei.vercel.app/?path=/story/abstractions-text--text-st) 
@@ -1402,12 +1551,14 @@ Abstracts [THREE.EdgesGeometry](https://threejs.org/docs/#api/en/geometries/Edge
-An ornamental component that extracts the geometry from its parent and displays an [inverted-hull outline](https://bnpr.gitbook.io/bnpr/outline/inverse-hull-method).
+An ornamental component that extracts the geometry from its parent and displays an [inverted-hull outline](https://bnpr.gitbook.io/bnpr/outline/inverse-hull-method). Supported parents are ``, `` and ``.
```tsx
type OutlinesProps = JSX.IntrinsicElements['group'] & {
/** Outline color, default: black */
color: ReactThreeFiber.Color
+ /** Line thickness is independent of zoom, default: false */
+ screenspace: boolean
/** Outline opacity, default: 1 */
opacity: number
/** Outline transparency, default: false */
@@ -1651,10 +1802,12 @@ The decal box has to intersect the surface, otherwise it will not be visible. if
position={[0, 0, 0]} // Position of the decal
rotation={[0, 0, 0]} // Rotation of the decal (can be a vector or a degree in radians)
scale={1} // Scale of the decal
- polygonOffset
- polygonOffsetFactor={-1} // The mesh should take precedence over the original
>
-
+
```
@@ -1729,6 +1882,46 @@ type AsciiRendererProps = {
```
+#### Splat
+
+
+
+
+
+A declarative abstraction around [antimatter15/splat](https://github.com/antimatter15/splat). It supports re-use, multiple splats with correct depth sorting, splats can move and behave as a regular object3d's, supports alphahash & alphatest, and stream-loading.
+
+```tsx
+type SplatProps = {
+ /** Url towards a *.splat file, no support for *.ply */
+ src: string
+ /** Whether to use tone mapping, default: false */
+ toneMapped?: boolean
+ /** Alpha test value, , default: 0 */
+ alphaTest?: number
+ /** Whether to use alpha hashing, default: false */
+ alphaHash?: boolean
+ /** Chunk size for lazy loading, prevents chokings the worker, default: 25000 (25kb) */
+ chunkSize?: number
+} & JSX.IntrinsicElements['mesh']
+```
+
+```jsx
+
+```
+
+In order to depth sort multiple splats correectly you can either use alphaTest, for instance with a low value. But keep in mind that this can show a slight outline under some viewing conditions.
+
+```jsx
+
+
+```
+
+You can also use alphaHash, but this can be slower and create some noise, you would typically get rid of the noise in postprocessing with a TAA pass. You don't have to use alphaHash on all splats.
+
+```jsx
+
+```
+
# Shaders
#### MeshReflectorMaterial
@@ -2251,6 +2444,16 @@ Enable shadows using the `castShadow` and `recieveShadow` prop.
> Note: Html 'blending' mode only correctly occludes rectangular HTML elements by default. Use the `geometry` prop to swap the backing geometry to a custom one if your Html has a different shape.
+If transform mode is enabled, the dimensions of the rendered html will depend on the position relative to the camera, the camera fov and the distanceFactor. For example, an Html component placed at (0,0,0) and with a distanceFactor of 10, rendered inside a scene with a perspective camera positioned at (0,0,2.45) and a FOV of 75, will have the same dimensions as a "plain" html element like in [this example](https://codesandbox.io/s/drei-html-magic-number-6mzt6m).
+
+A caveat of transform mode is that on some devices and browsers, the rendered html may appear blurry, as discussed in [#859](https://github.com/pmndrs/drei/issues/859). The issue can be at least mitigated by scaling down the Html parent and scaling up the html children:
+
+```jsx
+
+ Some text
+
+```
+
#### CycleRaycast

@@ -2349,7 +2552,7 @@ type Props = {
onLoopEnd?: Function
/** Event callback when each frame changes */
onFrame?: Function
- /** Control when the animation runs */
+ /** @deprecated Control when the animation runs*/
play?: boolean
/** Control when the animation pauses */
pause?: boolean
@@ -2357,6 +2560,18 @@ type Props = {
flipX?: boolean
/** Sets the alpha value to be used when running an alpha test. https://threejs.org/docs/#api/en/materials/Material.alphaTest */
alphaTest?: number
+ /** Displays the texture on a SpriteGeometry always facing the camera, if set to false, it renders on a PlaneGeometry */
+ asSprite?: boolean
+ /** Allows for manual update of the sprite animation e.g: via ScrollControls */
+ offset?: number
+ /** Allows the sprite animation to start from the end towards the start */
+ playBackwards: boolean
+ /** Allows the animation to be paused after it ended so it can be restarted on demand via auto */
+ resetOnEnd?: boolean
+ /** An array of items to create instances from */
+ instanceItems?: any[]
+ /** The max number of items to instance (optional) */
+ maxItems?: number
}
```
@@ -2380,6 +2595,37 @@ Notes:
/>
```
+ScrollControls example
+
+```jsx
+;
+
+
+
+
+
+function FireScroll() {
+ const sprite = useSpriteAnimator()
+ const scroll = useScroll()
+ const ref = React.useRef()
+ useFrame(() => {
+ if (sprite && scroll) {
+ sprite.current = scroll.offset
+ }
+ })
+
+ return null
+}
+```
+
#### Stats
[](https://drei.vercel.app/?path=/story/misc-stats--default-story)
@@ -2795,6 +3041,8 @@ useGLTF(url, '/draco-gltf')
useGLTF.preload(url)
```
+If you want to use your own draco decoder globally, you can pass it through `useGLTF.setDecoderPath(path)`:
+
> **Note** If you are using the CDN loaded draco binaries, you can get a small speedup in loading time by prefetching them.
>
> You can accomplish this by adding two ` ` tags to your `` tag, as below. The version in those URLs must exactly match what [useGLTF](src/core/useGLTF.tsx#L18) uses for this to work. If you're using create-react-app, `public/index.html` file contains the `` tag.
@@ -3110,7 +3358,12 @@ A wrapper around [THREE.LineSegments](https://threejs.org/docs/#api/en/objects/L
##### Prop based:
```jsx
-
+
@@ -3222,6 +3475,14 @@ export interface BVHOptions {
maxDepth?: number
/** The number of triangles to aim for in a leaf node, default: 10 */
maxLeafTris?: number
+ /** If false then an index buffer is created if it does not exist and is rearranged */
+ /** to hold the bvh structure. If false then a separate buffer is created to store the */
+ /** structure and the index buffer (or lack thereof) is retained. This can be used */
+ /** when the existing index layout is important or groups are being used so a */
+ /** single BVH hierarchy can be created to improve performance. */
+ /** default: false */
+ /** Note: This setting is experimental */
+ indirect?: boolean
}
export type BvhProps = BVHOptions &
@@ -3300,7 +3561,7 @@ function App() {
const [dpr, setDpr] = useState(1.5)
return (
- setDpr(2)} onDecline={() => setDpr(1)} >
+ setDpr(2)} onDecline={() => setDpr(1)} />
```
You can also use the `onChange` callback to get notified when the average changes in whichever direction. This allows you to make gradual changes. It gives you a `factor` between 0 and 1, which is increased by incline and decreased by decline. The `factor` is initially 0.5 by default. If your app starts with lowest defaults and gradually increases quality set `factor` to 0. If it starts with highest defaults and decreases quality, set it to 1. If it starts in the middle and can either increase or decrease, set it to 0.5.
@@ -3308,18 +3569,16 @@ You can also use the `onChange` callback to get notified when the average change
The following starts at the highest dpr (2) and clamps the gradual dpr between 0.5 at the lowest and 2 at the highest. If the app is in trouble it will reduce `factor` by `step` until it is either 0 or the app has found its sweet spot above that.
```jsx
-import round from 'lodash/round'
-
-const [dpr, set] = useState(2)
+const [dpr, setDpr] = useState(2)
return (
- setDpr(round(0.5 + 1.5 * factor, 1))} >
+ setDpr(Math.floor(0.5 + 1.5 * factor, 1))} />
```
If you still experience flip flops despite the bounds you can define a limit of `flipflops`. If it is met `onFallback` will be triggered which typically sets a lowest possible baseline for the app. After the fallback has been called PerformanceMonitor will shut down.
```jsx
- setDpr(1)}>
+ setDpr(1)} />
```
PerformanceMonitor can also have children, if you wrap your app in it you get to use `usePerformanceMonitor` which allows individual components down the nested tree to respond to performance changes on their own.
@@ -3379,6 +3638,7 @@ type HudProps = {
+
@@ -3394,30 +3654,53 @@ integrations into your view.
> versions of `@react-three/fiber`.
```tsx
-
+export type ViewProps = {
+ /** Root element type, default: div */
+ as?: string
+ /** CSS id prop */
+ id?: string
+ /** CSS classname prop */
+ className?: string
+ /** CSS style prop */
+ style?: React.CSSProperties
+ /** If the view is visible or not, default: true */
+ visible?: boolean
/** Views take over the render loop, optional render index (1 by default) */
index?: number
- /** If you know your view is always at the same place set this to 1 to avoid needless getBoundingClientRect overhead. The default is Infinity, which is best for css animations */
+ /** If you know your view is always at the same place set this to 1 to avoid needless getBoundingClientRect overhead */
frames?: number
/** The scene to render, if you leave this undefined it will render the default scene */
children?: React.ReactNode
-/>
+ /** The tracking element, the view will be cut according to its whereabouts
+ * @deprecated
+ */
+ track: React.MutableRefObject
+}
+
+export type ViewportProps = { Port: () => React.ReactNode } & React.ForwardRefExoticComponent<
+ ViewProps & React.RefAttributes
+>
```
+You can define as many views as you like, directly mix them into your dom graph, right where you want them to appear. `View` is an unstyled HTML DOM element (by default a div, and it takes the same properties as one). Use `View.Port` inside the canvas to output them. The canvas should ideally fill the entire screen with absolute positioning, underneath HTML or on top of it, as you prefer.
+
```jsx
-const container = useRef()
-const tracking = useRef()
return (
Html content here
-
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+)
```
#### RenderTexture
@@ -3463,6 +3746,90 @@ type Props = JSX.IntrinsicElements['texture'] & {
```
+#### RenderCubeTexture
+
+This component allows you to render a live scene into a cubetexture which you can then apply to a material, for instance as an environment map (via the envMap property). The contents of it run inside a portal and are separate from the rest of the canvas, therefore you can have events in there, environment maps, etc.
+
+```tsx
+export type RenderCubeTextureProps = Omit & {
+ /** Optional stencil buffer, defaults to false */
+ stencilBuffer?: boolean
+ /** Optional depth buffer, defaults to true */
+ depthBuffer?: boolean
+ /** Optional generate mipmaps, defaults to false */
+ generateMipmaps?: boolean
+ /** Optional render priority, defaults to 0 */
+ renderPriority?: number
+ /** Optional event priority, defaults to 0 */
+ eventPriority?: number
+ /** Optional frame count, defaults to Infinity. If you set it to 1, it would only render a single frame, etc */
+ frames?: number
+ /** Optional event compute, defaults to undefined */
+ compute?: ComputeFunction
+ /** Flip cubemap, see https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLCubeRenderTarget.js */
+ flip?: boolean
+ /** Cubemap resolution (for each of the 6 takes), null === full screen resolution, default: 896 */
+ resolution?: number
+ /** Children will be rendered into a portal */
+ children: React.ReactNode
+ near?: number
+ far?: number
+ position?: ReactThreeFiber.Vector3
+ rotation?: ReactThreeFiber.Euler
+ scale?: ReactThreeFiber.Vector3
+ quaternion?: ReactThreeFiber.Quaternion
+ matrix?: ReactThreeFiber.Matrix4
+ matrixAutoUpdate?: boolean
+}
+
+export type RenderCubeTextureApi = {
+ scene: THREE.Scene
+ fbo: THREE.WebGLCubeRenderTarget
+ camera: THREE.CubeCamera
+}
+```
+
+```jsx
+const api = useRef(null!)
+// ...
+
+
+
+
+
+```
+
+### Fisheye
+
+
+
+
+
+```tsx
+export type FisheyeProps = JSX.IntrinsicElements['mesh'] & {
+ /** Zoom factor, 0..1, 0 */
+ zoom?: number
+ /** Number of segments, 64 */
+ segments?: number
+ /** Cubemap resolution (for each of the 6 takes), null === full screen resolution, default: 896 */
+ resolution?: number
+ /** Children will be projected into the fisheye */
+ children: React.ReactNode
+ /** Optional render priority, defaults to 1 */
+ renderPriority?: number
+}
+```
+
+This component will take over system rendering. It portals its children into a cubemap which is then projected onto a sphere. The sphere is rendered out on the screen, filling it. You can lower the resolution to increase performance. Six renders per frame are necessary to construct a full fisheye view, and since each facet of the cubemap only takes a portion of the screen full resolution is not necessary. You can also reduce the amount of segments (resulting in edgier rounds).
+
+```jsx
+
+
+
+
+
+```
+
#### Mask
@@ -3723,15 +4090,19 @@ For instance, one could want the Html component to be pinned to `positive x`, `p
-Calculates a boundary box and centers the camera accordingly. If you are using camera controls, make sure to pass them the `makeDefault` prop. `fit` fits the current view on first render. `clip` sets the cameras near/far planes. `observe` will trigger on window resize.
+Calculates a boundary box and centers the camera accordingly. If you are using camera controls, make sure to pass them the `makeDefault` prop. `fit` fits the current view on first render. `clip` sets the cameras near/far planes. `observe` will trigger on window resize. To control the damping animation, use `maxDuration` to set the animation length in seconds, and `interpolateFunc` to define how the animation changes over time (should be an increasing function in [0, 1] interval, `interpolateFunc(0) === 0`, `interpolateFunc(1) === 1`).
```jsx
-
+const interpolateFunc = (t: number) => 1 - Math.exp(-5 * t) + 0.007 * t // Matches the default Bounds behavior
+const interpolateFunc1 = (t: number) => -t * t * t + 2 * t * t // Start smoothly, finish linearly
+const interpolateFunc2 = (t: number) => -t * t * t + t * t + t // Start linearly, finish smoothly
+
+
```
-The Bounds component also acts as a context provider, use the `useBounds` hook to refresh the bounds, fit the camera, clip near/far planes, go to camera orientations or focus objects. `refresh(object?: THREE.Object3D | THREE.Box3)` will recalculate bounds, since this can be expensive only call it when you know the view has changed. `clip` sets the cameras near/far planes. `to` sets a position and target for the camera. `fit` zooms and centers the view.
+The Bounds component also acts as a context provider, use the `useBounds` hook to refresh the bounds, fit the camera, clip near/far planes, go to camera orientations or focus objects. `refresh(object?: THREE.Object3D | THREE.Box3)` will recalculate bounds, since this can be expensive only call it when you know the view has changed. `reset` centers the view. `moveTo` changes the camera position. `lookAt` changes the camera orientation, with the respect to up-vector, if specified. `clip` sets the cameras near/far planes. `fit` centers the view for non-orthographic cameras (same as reset) or zooms the view for orthographic cameras.
```jsx
function Foo() {
@@ -3744,10 +4115,17 @@ function Foo() {
// bounds.refresh(ref.current).clip().fit()
// bounds.refresh(new THREE.Box3()).clip().fit()
- // Or, send the camera to a specific orientatin
- // bounds.to({position: [0, 10, 10], target: {[5, 5, 0]}})
+ // Or, move the camera to a specific position, and change its orientation
+ // bounds.moveTo([0, 10, 10]).lookAt({ target: [5, 5, 0], up: [0, -1, 0] })
+
+ // For orthographic cameras, reset has to be used to center the view (fit would only change its zoom to match the bounding box)
+ // bounds.refresh().reset().clip().fit()
+ }, [...])
+}
+
+
```
#### CameraShake
@@ -3865,7 +4243,7 @@ For a little more realistic results enable accumulative shadows, which requires
```jsx
-
+
```
@@ -4351,21 +4729,63 @@ attribute vec3 color;
[](https://drei.pmnd.rs/?path=/story/staging-cloud--cloud-st) 
+
+
Particle based cloud.
-👉 Note: ` ` component is not meant to be used in production environments as it relies on third-party CDN.
+```tsx
+type CloudsProps = JSX.IntrinsicElements['group'] & {
+ /** Optional cloud texture, points to a default hosted on rawcdn.githack */
+ texture?: string
+ /** Maximum number of segments, default: 200 (make this tight to save memory!) */
+ limit?: number
+ /** How many segments it renders, default: undefined (all) */
+ range?: number
+ /** Which material it will override, default: MeshLambertMaterial */
+ material?: typeof Material
+}
+
+type CloudProps = JSX.IntrinsicElements['group'] & {
+ /** A seeded random will show the same cloud consistently, default: Math.random() */
+ seed?: number
+ /** How many segments or particles the cloud will have, default: 20 */
+ segments?: number
+ /** The box3 bounds of the cloud, default: [5, 1, 1] */
+ bounds?: ReactThreeFiber.Vector3
+ /** How to arrange segment volume inside the bounds, default: inside (cloud are smaller at the edges) */
+ concentrate?: 'random' | 'inside' | 'outside'
+ /** The general scale of the segments */
+ scale?: ReactThreeFiber.Vector3
+ /** The volume/thickness of the segments, default: 6 */
+ volume?: number
+ /** The smallest volume when distributing clouds, default: 0.25 */
+ smallestVolume?: number
+ /** An optional function that allows you to distribute points and volumes (overriding all settings), default: null
+ * Both point and volume are factors, point x/y/z can be between -1 and 1, volume between 0 and 1 */
+ distribute?: (cloud: CloudState, index: number) => { point: Vector3; volume?: number }
+ /** Growth factor for animated clouds (speed > 0), default: 4 */
+ growth?: number
+ /** Animation factor, default: 0 */
+ speed?: number
+ /** Camera distance until the segments will fade, default: 10 */
+ fade?: number
+ /** Opacity, default: 1 */
+ opacity?: number
+ /** Color, default: white */
+ color?: ReactThreeFiber.Color
+}
+```
+
+Use the `` provider to glob all clouds into a single, instanced draw call.
```jsx
-
+
+
+
+
```
#### useEnvironment
@@ -4451,3 +4871,21 @@ return (
...
)
```
+
+#### ShadowAlpha
+
+Makes an object's shadow respect its opacity and alphaMap.
+
+```jsx
+
+
+
+
+
+
+```
+
+> Note: This component uses Screendoor transparency using a dither pattern. This pattern is notacible when the camera gets close to the shadow.
diff --git a/package.json b/package.json
index b9c6274f5..d011f854c 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"main": "index.cjs.js",
"module": "index.js",
"types": "index.d.ts",
+ "react-native": "native/index.cjs.js",
"sideEffects": false,
"commitlint": {
"extends": [
@@ -49,34 +50,34 @@
"test": "npm run eslint:ci && (cd test/e2e; ./e2e.sh)",
"typecheck": "tsc --noEmit --emitDeclarationOnly false --strict --jsx react",
"typegen": "tsc --emitDeclarationOnly",
- "storybook": "NODE_OPTIONS=\"--openssl-legacy-provider\" storybook dev -p 6006",
- "build-storybook": "NODE_OPTIONS=\"--openssl-legacy-provider\" storybook build",
+ "storybook": "cross-env NODE_OPTIONS=\"--openssl-legacy-provider\" storybook dev -p 6006",
+ "build-storybook": "cross-env NODE_OPTIONS=\"--openssl-legacy-provider\" storybook build",
"copy": "copyfiles package.json README.md LICENSE dist && json -I -f dist/package.json -e \"this.private=false; this.devDependencies=undefined; this.optionalDependencies=undefined; this.scripts=undefined; this.husky=undefined; this.prettier=undefined; this.jest=undefined; this['lint-staged']=undefined;\"",
"release": "semantic-release"
},
"dependencies": {
"@babel/runtime": "^7.11.2",
- "@mediapipe/tasks-vision": "0.10.2",
+ "@mediapipe/tasks-vision": "0.10.8",
"@react-spring/three": "~9.6.1",
"@use-gesture/react": "^10.2.24",
"camera-controls": "^2.4.2",
+ "cross-env": "^7.0.3",
"detect-gpu": "^5.0.28",
"glsl-noise": "^0.0.0",
- "lodash.clamp": "^4.0.3",
- "lodash.omit": "^4.5.0",
- "lodash.pick": "^4.4.0",
- "maath": "^0.6.0",
+ "maath": "^0.10.7",
"meshline": "^3.1.6",
"react-composer": "^5.0.3",
"react-merge-refs": "^1.1.0",
- "stats-gl": "^1.0.4",
+ "stats-gl": "^2.0.0",
"stats.js": "^0.17.0",
"suspend-react": "^0.1.3",
- "three-mesh-bvh": "^0.6.0",
- "three-stdlib": "^2.23.9",
+ "three-mesh-bvh": "^0.7.0",
+ "three-stdlib": "^2.29.4",
"troika-three-text": "^0.47.2",
+ "tunnel-rat": "^0.1.2",
"utility-types": "^3.10.0",
- "zustand": "^3.5.13"
+ "uuid": "^9.0.1",
+ "zustand": "^3.7.1"
},
"devDependencies": {
"@babel/core": "^7.14.3",
@@ -103,9 +104,8 @@
"@storybook/react-vite": "^7.0.12",
"@storybook/theming": "^7.0.12",
"@types/jest": "^26.0.10",
- "@types/lodash-es": "^4.17.3",
- "@types/react": "^17.0.5",
- "@types/react-dom": "^17.0.5",
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
"@types/three": "^0.149.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
diff --git a/src/core/AccumulativeShadows.tsx b/src/core/AccumulativeShadows.tsx
index a91f2fe1a..5d7786d37 100644
--- a/src/core/AccumulativeShadows.tsx
+++ b/src/core/AccumulativeShadows.tsx
@@ -4,6 +4,7 @@ import { extend, ReactThreeFiber, useFrame, useThree } from '@react-three/fiber'
import { shaderMaterial } from './shaderMaterial'
import { DiscardMaterial } from '../materials/DiscardMaterial'
import { ForwardRefComponent } from '../helpers/ts-utils'
+import { version } from '../helpers/constants'
function isLight(object: any): object is THREE.Light {
return object.isLight
@@ -72,11 +73,13 @@ declare global {
}
}
-export const accumulativeContext = React.createContext(null as unknown as AccumulativeContext)
+export const accumulativeContext = /* @__PURE__ */ React.createContext(
+ null as unknown as AccumulativeContext
+)
-const SoftShadowMaterial = shaderMaterial(
+const SoftShadowMaterial = /* @__PURE__ */ shaderMaterial(
{
- color: new THREE.Color(),
+ color: /* @__PURE__ */ new THREE.Color(),
blend: 2.0,
alphaTest: 0.75,
opacity: 0,
@@ -97,14 +100,14 @@ const SoftShadowMaterial = shaderMaterial(
vec4 sampledDiffuseColor = texture2D(map, vUv);
gl_FragColor = vec4(color * sampledDiffuseColor.r * blend, max(0.0, (1.0 - (sampledDiffuseColor.r + sampledDiffuseColor.g + sampledDiffuseColor.b) / alphaTest)) * opacity);
#include
- #include <${parseInt(THREE.REVISION.replace(/\D+/g, '')) >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
+ #include <${version >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
}`
)
export const AccumulativeShadows: ForwardRefComponent<
JSX.IntrinsicElements['group'] & AccumulativeShadowsProps,
AccumulativeContext
-> = React.forwardRef(
+> = /* @__PURE__ */ React.forwardRef(
(
{
children,
@@ -253,7 +256,7 @@ export type RandomizedLightProps = {
export const RandomizedLight: ForwardRefComponent<
JSX.IntrinsicElements['group'] & RandomizedLightProps,
AccumulativeLightContext
-> = React.forwardRef(
+> = /* @__PURE__ */ React.forwardRef(
(
{
castShadow = true,
@@ -266,7 +269,7 @@ export const RandomizedLight: ForwardRefComponent<
position = [0, 0, 0],
radius = 1,
amount = 8,
- intensity = parseInt(THREE.REVISION.replace(/\D+/g, '')) >= 155 ? Math.PI : 1,
+ intensity = version >= 155 ? Math.PI : 1,
ambient = 0.5,
...props
}: JSX.IntrinsicElements['group'] & RandomizedLightProps,
@@ -304,8 +307,8 @@ export const RandomizedLight: ForwardRefComponent<
React.useImperativeHandle(forwardRef, () => api, [api])
React.useLayoutEffect(() => {
const group = gLights.current
- if (parent) parent.lights.set(group.uuid, api)
- return () => void parent.lights.delete(group.uuid)
+ if (parent) parent.lights?.set(group.uuid, api)
+ return () => void parent?.lights?.delete(group.uuid)
}, [parent, api])
return (
diff --git a/src/core/ArcballControls.tsx b/src/core/ArcballControls.tsx
index 4c0e79daa..3c8c3eb38 100644
--- a/src/core/ArcballControls.tsx
+++ b/src/core/ArcballControls.tsx
@@ -23,55 +23,55 @@ export type ArcballControlsProps = Omit<
'ref'
>
-export const ArcballControls: ForwardRefComponent = forwardRef<
- ArcballControlsImpl,
- ArcballControlsProps
->(({ camera, makeDefault, regress, domElement, onChange, onStart, onEnd, ...restProps }, ref) => {
- const invalidate = useThree((state) => state.invalidate)
- const defaultCamera = useThree((state) => state.camera)
- const gl = useThree((state) => state.gl)
- const events = useThree((state) => state.events) as EventManager
- const set = useThree((state) => state.set)
- const get = useThree((state) => state.get)
- const performance = useThree((state) => state.performance)
- const explCamera = camera || defaultCamera
- const explDomElement = (domElement || events.connected || gl.domElement) as HTMLElement
- const controls = useMemo(() => new ArcballControlsImpl(explCamera), [explCamera])
+export const ArcballControls: ForwardRefComponent =
+ /* @__PURE__ */ forwardRef(
+ ({ camera, makeDefault, regress, domElement, onChange, onStart, onEnd, ...restProps }, ref) => {
+ const invalidate = useThree((state) => state.invalidate)
+ const defaultCamera = useThree((state) => state.camera)
+ const gl = useThree((state) => state.gl)
+ const events = useThree((state) => state.events) as EventManager
+ const set = useThree((state) => state.set)
+ const get = useThree((state) => state.get)
+ const performance = useThree((state) => state.performance)
+ const explCamera = camera || defaultCamera
+ const explDomElement = (domElement || events.connected || gl.domElement) as HTMLElement
+ const controls = useMemo(() => new ArcballControlsImpl(explCamera), [explCamera])
- useFrame(() => {
- if (controls.enabled) controls.update()
- }, -1)
+ useFrame(() => {
+ if (controls.enabled) controls.update()
+ }, -1)
- useEffect(() => {
- controls.connect(explDomElement)
- return () => void controls.dispose()
- }, [explDomElement, regress, controls, invalidate])
+ useEffect(() => {
+ controls.connect(explDomElement)
+ return () => void controls.dispose()
+ }, [explDomElement, regress, controls, invalidate])
- useEffect(() => {
- const callback = (e: Event) => {
- invalidate()
- if (regress) performance.regress()
- if (onChange) onChange(e)
- }
+ useEffect(() => {
+ const callback = (e: Event) => {
+ invalidate()
+ if (regress) performance.regress()
+ if (onChange) onChange(e)
+ }
- controls.addEventListener('change', callback)
- if (onStart) controls.addEventListener('start', onStart)
- if (onEnd) controls.addEventListener('end', onEnd)
+ controls.addEventListener('change', callback)
+ if (onStart) controls.addEventListener('start', onStart)
+ if (onEnd) controls.addEventListener('end', onEnd)
- return () => {
- controls.removeEventListener('change', callback)
- if (onStart) controls.removeEventListener('start', onStart)
- if (onEnd) controls.removeEventListener('end', onEnd)
- }
- }, [onChange, onStart, onEnd])
+ return () => {
+ controls.removeEventListener('change', callback)
+ if (onStart) controls.removeEventListener('start', onStart)
+ if (onEnd) controls.removeEventListener('end', onEnd)
+ }
+ }, [onChange, onStart, onEnd])
- useEffect(() => {
- if (makeDefault) {
- const old = get().controls
- set({ controls })
- return () => set({ controls: old })
- }
- }, [makeDefault, controls])
+ useEffect(() => {
+ if (makeDefault) {
+ const old = get().controls
+ set({ controls })
+ return () => set({ controls: old })
+ }
+ }, [makeDefault, controls])
- return
-})
+ return
+ }
+ )
diff --git a/src/core/BBAnchor.tsx b/src/core/BBAnchor.tsx
index ad1c16eab..a1bcb6e42 100644
--- a/src/core/BBAnchor.tsx
+++ b/src/core/BBAnchor.tsx
@@ -2,15 +2,15 @@ import * as React from 'react'
import * as THREE from 'three'
import { useFrame, GroupProps } from '@react-three/fiber'
-const boundingBox = new THREE.Box3()
-const boundingBoxSize = new THREE.Vector3()
+const boundingBox = /* @__PURE__ */ new THREE.Box3()
+const boundingBoxSize = /* @__PURE__ */ new THREE.Vector3()
export interface BBAnchorProps extends GroupProps {
anchor: THREE.Vector3 | [number, number, number]
}
export const BBAnchor = ({ anchor, ...props }: BBAnchorProps) => {
- const ref = React.useRef(null!)
+ const ref = React.useRef(null!)
const parentRef = React.useRef(null)
// Reattach group created by this component to the parent's parent,
@@ -29,9 +29,9 @@ export const BBAnchor = ({ anchor, ...props }: BBAnchorProps) => {
boundingBox.getSize(boundingBoxSize)
ref.current.position.set(
- parentRef.current.position.x + (boundingBoxSize.x * anchor[0]) / 2,
- parentRef.current.position.y + (boundingBoxSize.y * anchor[1]) / 2,
- parentRef.current.position.z + (boundingBoxSize.z * anchor[2]) / 2
+ parentRef.current.position.x + (boundingBoxSize.x * (Array.isArray(anchor) ? anchor[0] : anchor.x)) / 2,
+ parentRef.current.position.y + (boundingBoxSize.y * (Array.isArray(anchor) ? anchor[1] : anchor.y)) / 2,
+ parentRef.current.position.z + (boundingBoxSize.z * (Array.isArray(anchor) ? anchor[2] : anchor.z)) / 2
)
}
})
diff --git a/src/core/Billboard.tsx b/src/core/Billboard.tsx
index 140bc28aa..b5b10e714 100644
--- a/src/core/Billboard.tsx
+++ b/src/core/Billboard.tsx
@@ -1,7 +1,6 @@
import * as React from 'react'
-import { Group } from 'three'
+import { Group, Quaternion } from 'three'
import { useFrame } from '@react-three/fiber'
-import mergeRefs from 'react-merge-refs'
import { ForwardRefComponent } from '../helpers/ts-utils'
export type BillboardProps = {
@@ -20,23 +19,36 @@ export type BillboardProps = {
*
* ```
*/
-export const Billboard: ForwardRefComponent = React.forwardRef(
- function Billboard({ follow = true, lockX = false, lockY = false, lockZ = false, ...props }, ref) {
- const localRef = React.useRef()
- useFrame(({ camera }) => {
- if (!follow || !localRef.current) return
+export const Billboard: ForwardRefComponent = /* @__PURE__ */ React.forwardRef<
+ Group,
+ BillboardProps
+>(function Billboard({ children, follow = true, lockX = false, lockY = false, lockZ = false, ...props }, fref) {
+ const inner = React.useRef(null!)
+ const localRef = React.useRef(null!)
+ const q = new Quaternion()
- // save previous rotation in case we're locking an axis
- const prevRotation = localRef.current.rotation.clone()
+ useFrame(({ camera }) => {
+ if (!follow || !localRef.current) return
- // always face the camera
- camera.getWorldQuaternion(localRef.current.quaternion)
+ // save previous rotation in case we're locking an axis
+ const prevRotation = localRef.current.rotation.clone()
- // readjust any axis that is locked
- if (lockX) localRef.current.rotation.x = prevRotation.x
- if (lockY) localRef.current.rotation.y = prevRotation.y
- if (lockZ) localRef.current.rotation.z = prevRotation.z
- })
- return
- }
-)
+ // always face the camera
+ localRef.current.updateMatrix()
+ localRef.current.updateWorldMatrix(false, false)
+ localRef.current.getWorldQuaternion(q)
+ camera.getWorldQuaternion(inner.current.quaternion).premultiply(q.invert())
+
+ // readjust any axis that is locked
+ if (lockX) localRef.current.rotation.x = prevRotation.x
+ if (lockY) localRef.current.rotation.y = prevRotation.y
+ if (lockZ) localRef.current.rotation.z = prevRotation.z
+ })
+
+ React.useImperativeHandle(fref, () => localRef.current, [])
+ return (
+
+ {children}
+
+ )
+})
diff --git a/src/core/Bounds.tsx b/src/core/Bounds.tsx
index 96b7399c3..73a1b38cd 100644
--- a/src/core/Bounds.tsx
+++ b/src/core/Bounds.tsx
@@ -1,5 +1,6 @@
import * as React from 'react'
import * as THREE from 'three'
+
import { useFrame, useThree } from '@react-three/fiber'
export type SizeProps = {
@@ -11,19 +12,28 @@ export type SizeProps = {
export type BoundsApi = {
getSize: () => SizeProps
- refresh(object?: THREE.Object3D | THREE.Box3): any
- clip(): any
- fit(): any
- to: ({ position, target }: { position: [number, number, number]; target?: [number, number, number] }) => any
+ refresh(object?: THREE.Object3D | THREE.Box3): BoundsApi
+ reset(): BoundsApi
+ moveTo(position: THREE.Vector3 | [number, number, number]): BoundsApi
+ lookAt({
+ target,
+ up,
+ }: {
+ target?: THREE.Vector3 | [number, number, number]
+ up?: THREE.Vector3 | [number, number, number]
+ }): BoundsApi
+ to({ position, target }: { position: [number, number, number]; target: [number, number, number] }): BoundsApi
+ fit(): BoundsApi
+ clip(): BoundsApi
}
export type BoundsProps = JSX.IntrinsicElements['group'] & {
- damping?: number
+ maxDuration?: number
+ margin?: number
+ observe?: boolean
fit?: boolean
clip?: boolean
- observe?: boolean
- margin?: number
- eps?: number
+ interpolateFunc?: (t: number) => number
onFit?: (data: SizeProps) => void
}
@@ -35,49 +45,83 @@ type ControlsProto = {
removeEventListener: (event: string, callback: (event: any) => void) => void
}
+type OriginT = {
+ camPos: THREE.Vector3
+ camRot: THREE.Quaternion
+ camZoom: number
+}
+
+type GoalT = {
+ camPos: THREE.Vector3 | undefined
+ camRot: THREE.Quaternion | undefined
+ camZoom: number | undefined
+ camUp: THREE.Vector3 | undefined
+ target: THREE.Vector3 | undefined
+}
+
+// eslint-disable-next-line no-shadow
+enum AnimationState {
+ NONE = 0,
+ START = 1,
+ ACTIVE = 2,
+}
+
const isOrthographic = (def: THREE.Camera): def is THREE.OrthographicCamera =>
def && (def as THREE.OrthographicCamera).isOrthographicCamera
const isBox3 = (def: any): def is THREE.Box3 => def && (def as THREE.Box3).isBox3
+const interpolateFuncDefault = (t: number) => {
+ // Imitates the previously used THREE.MathUtils.damp
+ return 1 - Math.exp(-5 * t) + 0.007 * t
+}
+
const context = React.createContext(null!)
-export function Bounds({ children, damping = 6, fit, clip, observe, margin = 1.2, eps = 0.01, onFit }: BoundsProps) {
+export function Bounds({
+ children,
+ maxDuration = 1.0,
+ margin = 1.2,
+ observe,
+ fit,
+ clip,
+ interpolateFunc = interpolateFuncDefault,
+ onFit,
+}: BoundsProps) {
const ref = React.useRef(null!)
- const { camera, invalidate, size, controls: controlsImpl } = useThree()
- const controls = controlsImpl as unknown as ControlsProto
+
+ const { camera, size, invalidate } = useThree()
+ const controls = useThree((state) => state.controls as unknown as ControlsProto)
const onFitRef = React.useRef<((data: SizeProps) => void) | undefined>(onFit)
onFitRef.current = onFit
- function equals(a, b) {
- return Math.abs(a.x - b.x) < eps && Math.abs(a.y - b.y) < eps && Math.abs(a.z - b.z) < eps
- }
-
- function damp(v, t, lambda, delta) {
- v.x = THREE.MathUtils.damp(v.x, t.x, lambda, delta)
- v.y = THREE.MathUtils.damp(v.y, t.y, lambda, delta)
- v.z = THREE.MathUtils.damp(v.z, t.z, lambda, delta)
- }
-
- const [current] = React.useState(() => ({
- animating: false,
- focus: new THREE.Vector3(),
- camera: new THREE.Vector3(),
- zoom: 1,
- }))
- const [goal] = React.useState(() => ({ focus: new THREE.Vector3(), camera: new THREE.Vector3(), zoom: 1 }))
+ const origin = React.useRef({
+ camPos: new THREE.Vector3(),
+ camRot: new THREE.Quaternion(),
+ camZoom: 1,
+ })
+ const goal = React.useRef({
+ camPos: undefined,
+ camRot: undefined,
+ camZoom: undefined,
+ camUp: undefined,
+ target: undefined,
+ })
+ const animationState = React.useRef(AnimationState.NONE)
+ const t = React.useRef(0) // represent animation state from 0 to 1
const [box] = React.useState(() => new THREE.Box3())
const api: BoundsApi = React.useMemo(() => {
function getSize() {
- const size = box.getSize(new THREE.Vector3())
+ const boxSize = box.getSize(new THREE.Vector3())
const center = box.getCenter(new THREE.Vector3())
- const maxSize = Math.max(size.x, size.y, size.z)
+ const maxSize = Math.max(boxSize.x, boxSize.y, boxSize.z)
const fitHeightDistance = isOrthographic(camera)
? maxSize * 4
: maxSize / (2 * Math.atan((Math.PI * camera.fov) / 360))
const fitWidthDistance = isOrthographic(camera) ? maxSize * 4 : fitHeightDistance / camera.aspect
const distance = margin * Math.max(fitHeightDistance, fitWidthDistance)
- return { box, size, center, distance }
+
+ return { box, size: boxSize, center, distance }
}
return {
@@ -95,109 +139,153 @@ export function Bounds({ children, damping = 6, fit, clip, observe, margin = 1.2
box.setFromCenterAndSize(new THREE.Vector3(), new THREE.Vector3(max, max, max))
}
- if (controls?.constructor.name === 'OrthographicTrackballControls') {
- // Put camera on a sphere along which it should move
- const { distance } = getSize()
- const direction = camera.position.clone().sub(controls.target).normalize().multiplyScalar(distance)
- const newPos = controls.target.clone().add(direction)
- camera.position.copy(newPos)
- }
+ origin.current.camPos.copy(camera.position)
+ origin.current.camRot.copy(camera.quaternion)
+ isOrthographic(camera) && (origin.current.camZoom = camera.zoom)
+
+ goal.current.camPos = undefined
+ goal.current.camRot = undefined
+ goal.current.camZoom = undefined
+ goal.current.camUp = undefined
+ goal.current.target = undefined
return this
},
- clip() {
- const { distance } = getSize()
- if (controls) controls.maxDistance = distance * 10
- camera.near = distance / 100
- camera.far = distance * 100
- camera.updateProjectionMatrix()
- if (controls) controls.update()
- invalidate()
+ reset() {
+ const { center, distance } = getSize()
+
+ const direction = camera.position.clone().sub(center).normalize()
+ goal.current.camPos = center.clone().addScaledVector(direction, distance)
+ goal.current.target = center.clone()
+ const mCamRot = new THREE.Matrix4().lookAt(goal.current.camPos, goal.current.target, camera.up)
+ goal.current.camRot = new THREE.Quaternion().setFromRotationMatrix(mCamRot)
+
+ animationState.current = AnimationState.START
+ t.current = 0
+
return this
},
- to({ position, target }: { position: [number, number, number]; target?: [number, number, number] }) {
- current.camera.copy(camera.position)
- const { center } = getSize()
- goal.camera.set(...position)
+ moveTo(position: THREE.Vector3 | [number, number, number]) {
+ goal.current.camPos = Array.isArray(position) ? new THREE.Vector3(...position) : position.clone()
- if (target) {
- goal.focus.set(...target)
- } else {
- goal.focus.copy(center)
- }
+ animationState.current = AnimationState.START
+ t.current = 0
- if (damping) {
- current.animating = true
+ return this
+ },
+ lookAt({
+ target,
+ up,
+ }: {
+ target: THREE.Vector3 | [number, number, number]
+ up?: THREE.Vector3 | [number, number, number]
+ }) {
+ goal.current.target = Array.isArray(target) ? new THREE.Vector3(...target) : target.clone()
+ if (up) {
+ goal.current.camUp = Array.isArray(up) ? new THREE.Vector3(...up) : up.clone()
} else {
- camera.position.set(...position)
+ goal.current.camUp = camera.up.clone()
}
+ const mCamRot = new THREE.Matrix4().lookAt(
+ goal.current.camPos || camera.position,
+ goal.current.target,
+ goal.current.camUp
+ )
+ goal.current.camRot = new THREE.Quaternion().setFromRotationMatrix(mCamRot)
+
+ animationState.current = AnimationState.START
+ t.current = 0
return this
},
+ /**
+ * @deprecated Use moveTo and lookAt instead
+ */
+ to({ position, target }: { position: [number, number, number]; target?: [number, number, number] }) {
+ return this.moveTo(position).lookAt({ target })
+ },
fit() {
- current.camera.copy(camera.position)
- if (controls) current.focus.copy(controls.target)
+ if (!isOrthographic(camera)) {
+ // For non-orthographic cameras, fit should behave exactly like reset
+ return this.reset()
+ }
- const { center, distance } = getSize()
- const direction = center.clone().sub(camera.position).normalize().multiplyScalar(distance)
-
- goal.camera.copy(center).sub(direction)
- goal.focus.copy(center)
-
- if (isOrthographic(camera)) {
- current.zoom = camera.zoom
-
- let maxHeight = 0,
- maxWidth = 0
- const vertices = [
- new THREE.Vector3(box.min.x, box.min.y, box.min.z),
- new THREE.Vector3(box.min.x, box.max.y, box.min.z),
- new THREE.Vector3(box.min.x, box.min.y, box.max.z),
- new THREE.Vector3(box.min.x, box.max.y, box.max.z),
- new THREE.Vector3(box.max.x, box.max.y, box.max.z),
- new THREE.Vector3(box.max.x, box.max.y, box.min.z),
- new THREE.Vector3(box.max.x, box.min.y, box.max.z),
- new THREE.Vector3(box.max.x, box.min.y, box.min.z),
- ]
- // Transform the center and each corner to camera space
- center.applyMatrix4(camera.matrixWorldInverse)
- for (const v of vertices) {
- v.applyMatrix4(camera.matrixWorldInverse)
- maxHeight = Math.max(maxHeight, Math.abs(v.y - center.y))
- maxWidth = Math.max(maxWidth, Math.abs(v.x - center.x))
- }
- maxHeight *= 2
- maxWidth *= 2
- const zoomForHeight = (camera.top - camera.bottom) / maxHeight
- const zoomForWidth = (camera.right - camera.left) / maxWidth
- goal.zoom = Math.min(zoomForHeight, zoomForWidth) / margin
- if (!damping) {
- camera.zoom = goal.zoom
- camera.updateProjectionMatrix()
- }
+ // For orthographic cameras, fit should only modify the zoom value
+ let maxHeight = 0,
+ maxWidth = 0
+ const vertices = [
+ new THREE.Vector3(box.min.x, box.min.y, box.min.z),
+ new THREE.Vector3(box.min.x, box.max.y, box.min.z),
+ new THREE.Vector3(box.min.x, box.min.y, box.max.z),
+ new THREE.Vector3(box.min.x, box.max.y, box.max.z),
+ new THREE.Vector3(box.max.x, box.max.y, box.max.z),
+ new THREE.Vector3(box.max.x, box.max.y, box.min.z),
+ new THREE.Vector3(box.max.x, box.min.y, box.max.z),
+ new THREE.Vector3(box.max.x, box.min.y, box.min.z),
+ ]
+
+ // Transform the center and each corner to camera space
+ const pos = goal.current.camPos || camera.position
+ const target = goal.current.target || controls?.target
+ const up = goal.current.camUp || camera.up
+ const mCamWInv = target
+ ? new THREE.Matrix4().lookAt(pos, target, up).setPosition(pos).invert()
+ : camera.matrixWorldInverse
+ for (const v of vertices) {
+ v.applyMatrix4(mCamWInv)
+ maxHeight = Math.max(maxHeight, Math.abs(v.y))
+ maxWidth = Math.max(maxWidth, Math.abs(v.x))
}
+ maxHeight *= 2
+ maxWidth *= 2
+ const zoomForHeight = (camera.top - camera.bottom) / maxHeight
+ const zoomForWidth = (camera.right - camera.left) / maxWidth
- if (damping) {
- current.animating = true
- } else {
- camera.position.copy(goal.camera)
- camera.lookAt(goal.focus)
- if (controls) {
- controls.target.copy(goal.focus)
- controls.update()
- }
+ goal.current.camZoom = Math.min(zoomForHeight, zoomForWidth) / margin
+
+ animationState.current = AnimationState.START
+ t.current = 0
+
+ onFitRef.current && onFitRef.current(this.getSize())
+
+ return this
+ },
+ clip() {
+ const { distance } = getSize()
+
+ camera.near = distance / 100
+ camera.far = distance * 100
+ camera.updateProjectionMatrix()
+
+ if (controls) {
+ controls.maxDistance = distance * 10
+ controls.update()
}
- if (onFitRef.current) onFitRef.current(this.getSize())
+
invalidate()
+
return this
},
}
- }, [box, camera, controls, margin, damping, invalidate])
+ }, [box, camera, controls, margin, invalidate])
React.useLayoutEffect(() => {
if (controls) {
// Try to prevent drag hijacking
- const callback = () => (current.animating = false)
+ const callback = () => {
+ if (controls && goal.current.target && animationState.current !== AnimationState.NONE) {
+ const front = new THREE.Vector3().setFromMatrixColumn(camera.matrix, 2)
+ const d0 = origin.current.camPos.distanceTo(controls.target)
+ const d1 = (goal.current.camPos || origin.current.camPos).distanceTo(goal.current.target)
+ const d = (1 - t.current) * d0 + t.current * d1
+
+ controls.target.copy(camera.position).addScaledVector(front, -d)
+ controls.update()
+ }
+
+ animationState.current = AnimationState.NONE
+ }
+
controls.addEventListener('start', callback)
return () => controls.removeEventListener('start', callback)
}
@@ -208,35 +296,49 @@ export function Bounds({ children, damping = 6, fit, clip, observe, margin = 1.2
React.useLayoutEffect(() => {
if (observe || count.current++ === 0) {
api.refresh()
- if (fit) api.fit()
+ if (fit) api.reset().fit()
if (clip) api.clip()
}
}, [size, clip, fit, observe, camera, controls])
useFrame((state, delta) => {
- if (current.animating) {
- damp(current.focus, goal.focus, damping, delta)
- damp(current.camera, goal.camera, damping, delta)
- current.zoom = THREE.MathUtils.damp(current.zoom, goal.zoom, damping, delta)
- camera.position.copy(current.camera)
-
- if (isOrthographic(camera)) {
- camera.zoom = current.zoom
+ // This [additional animation step START] is needed to guarantee that delta used in animation isn't absurdly high (2-3 seconds) which is actually possible if rendering happens on demand...
+ if (animationState.current === AnimationState.START) {
+ animationState.current = AnimationState.ACTIVE
+ invalidate()
+ } else if (animationState.current === AnimationState.ACTIVE) {
+ t.current += delta / maxDuration
+
+ if (t.current >= 1) {
+ goal.current.camPos && camera.position.copy(goal.current.camPos)
+ goal.current.camRot && camera.quaternion.copy(goal.current.camRot)
+ goal.current.camUp && camera.up.copy(goal.current.camUp)
+ goal.current.camZoom && isOrthographic(camera) && (camera.zoom = goal.current.camZoom)
+
+ camera.updateMatrixWorld()
camera.updateProjectionMatrix()
- }
- if (!controls) {
- camera.lookAt(current.focus)
+ if (controls && goal.current.target) {
+ controls.target.copy(goal.current.target)
+ controls.update()
+ }
+
+ animationState.current = AnimationState.NONE
} else {
- controls.target.copy(current.focus)
- controls.update()
+ const k = interpolateFunc(t.current)
+
+ goal.current.camPos && camera.position.lerpVectors(origin.current.camPos, goal.current.camPos, k)
+ goal.current.camRot && camera.quaternion.slerpQuaternions(origin.current.camRot, goal.current.camRot, k)
+ goal.current.camUp && camera.up.set(0, 1, 0).applyQuaternion(camera.quaternion)
+ goal.current.camZoom &&
+ isOrthographic(camera) &&
+ (camera.zoom = (1 - k) * origin.current.camZoom + k * goal.current.camZoom)
+
+ camera.updateMatrixWorld()
+ camera.updateProjectionMatrix()
}
invalidate()
- if (isOrthographic(camera) && !(Math.abs(current.zoom - goal.zoom) < eps)) return
- if (!isOrthographic(camera) && !equals(current.camera, goal.camera)) return
- if (controls && !equals(current.focus, goal.focus)) return
- current.animating = false
}
})
diff --git a/src/core/CameraControls.tsx b/src/core/CameraControls.tsx
index cc3375de9..f1bfdc5f7 100644
--- a/src/core/CameraControls.tsx
+++ b/src/core/CameraControls.tsx
@@ -38,7 +38,7 @@ export type CameraControlsProps = Omit<
'ref'
>
-export const CameraControls: ForwardRefComponent = forwardRef<
+export const CameraControls: ForwardRefComponent = /* @__PURE__ */ forwardRef<
CameraControlsImpl,
CameraControlsProps
>((props, ref) => {
diff --git a/src/core/CameraShake.tsx b/src/core/CameraShake.tsx
index f356ab3f8..37d23cf48 100644
--- a/src/core/CameraShake.tsx
+++ b/src/core/CameraShake.tsx
@@ -28,77 +28,75 @@ export interface CameraShakeProps {
rollFrequency?: number
}
-export const CameraShake: ForwardRefComponent = React.forwardRef<
- ShakeController | undefined,
- CameraShakeProps
->(
- (
- {
- intensity = 1,
- decay,
- decayRate = 0.65,
- maxYaw = 0.1,
- maxPitch = 0.1,
- maxRoll = 0.1,
- yawFrequency = 0.1,
- pitchFrequency = 0.1,
- rollFrequency = 0.1,
- },
- ref
- ) => {
- const camera = useThree((state) => state.camera)
- const defaultControls = useThree((state) => state.controls) as unknown as ControlsProto
- const intensityRef = React.useRef(intensity)
- const initialRotation = React.useRef(camera.rotation.clone())
- const [yawNoise] = React.useState(() => new SimplexNoise())
- const [pitchNoise] = React.useState(() => new SimplexNoise())
- const [rollNoise] = React.useState(() => new SimplexNoise())
+export const CameraShake: ForwardRefComponent =
+ /* @__PURE__ */ React.forwardRef(
+ (
+ {
+ intensity = 1,
+ decay,
+ decayRate = 0.65,
+ maxYaw = 0.1,
+ maxPitch = 0.1,
+ maxRoll = 0.1,
+ yawFrequency = 0.1,
+ pitchFrequency = 0.1,
+ rollFrequency = 0.1,
+ },
+ ref
+ ) => {
+ const camera = useThree((state) => state.camera)
+ const defaultControls = useThree((state) => state.controls) as unknown as ControlsProto
+ const intensityRef = React.useRef(intensity)
+ const initialRotation = React.useRef(camera.rotation.clone())
+ const [yawNoise] = React.useState(() => new SimplexNoise())
+ const [pitchNoise] = React.useState(() => new SimplexNoise())
+ const [rollNoise] = React.useState(() => new SimplexNoise())
- const constrainIntensity = () => {
- if (intensityRef.current < 0 || intensityRef.current > 1) {
- intensityRef.current = intensityRef.current < 0 ? 0 : 1
+ const constrainIntensity = () => {
+ if (intensityRef.current < 0 || intensityRef.current > 1) {
+ intensityRef.current = intensityRef.current < 0 ? 0 : 1
+ }
}
- }
- React.useImperativeHandle(
- ref,
- () => ({
- getIntensity: (): number => intensityRef.current,
- setIntensity: (val: number): void => {
- intensityRef.current = val
- constrainIntensity()
- },
- }),
- []
- )
+ React.useImperativeHandle(
+ ref,
+ () => ({
+ getIntensity: (): number => intensityRef.current,
+ setIntensity: (val: number): void => {
+ intensityRef.current = val
+ constrainIntensity()
+ },
+ }),
+ []
+ )
- React.useEffect(() => {
- if (defaultControls) {
- const callback = () => void (initialRotation.current = camera.rotation.clone())
- defaultControls.addEventListener('change', callback)
- callback()
- return () => void defaultControls.removeEventListener('change', callback)
- }
- }, [camera, defaultControls])
+ React.useEffect(() => {
+ if (defaultControls) {
+ const callback = () => void (initialRotation.current = camera.rotation.clone())
+ defaultControls.addEventListener('change', callback)
+ callback()
+ return () => void defaultControls.removeEventListener('change', callback)
+ }
+ }, [camera, defaultControls])
- useFrame((state, delta) => {
- const shake = Math.pow(intensityRef.current, 2)
- const yaw = maxYaw * shake * yawNoise.noise(state.clock.elapsedTime * yawFrequency, 1)
- const pitch = maxPitch * shake * pitchNoise.noise(state.clock.elapsedTime * pitchFrequency, 1)
- const roll = maxRoll * shake * rollNoise.noise(state.clock.elapsedTime * rollFrequency, 1)
+ useFrame((state, delta) => {
+ const shake = Math.pow(intensityRef.current, 2)
+ const yaw = maxYaw * shake * yawNoise.noise(state.clock.elapsedTime * yawFrequency, 1)
+ const pitch = maxPitch * shake * pitchNoise.noise(state.clock.elapsedTime * pitchFrequency, 1)
+ const roll = maxRoll * shake * rollNoise.noise(state.clock.elapsedTime * rollFrequency, 1)
- camera.rotation.set(
- initialRotation.current.x + pitch,
- initialRotation.current.y + yaw,
- initialRotation.current.z + roll
- )
+ camera.rotation.set(
+ initialRotation.current.x + pitch,
+ initialRotation.current.y + yaw,
+ initialRotation.current.z + roll
+ )
- if (decay && intensityRef.current > 0) {
- intensityRef.current -= decayRate * delta
- constrainIntensity()
- }
- })
+ if (decay && intensityRef.current > 0) {
+ intensityRef.current -= decayRate * delta
+ constrainIntensity()
+ }
+ })
- return null
- }
-)
+ return null
+ }
+ )
diff --git a/src/core/CatmullRomLine.tsx b/src/core/CatmullRomLine.tsx
index 0d44f41cb..1c3040327 100644
--- a/src/core/CatmullRomLine.tsx
+++ b/src/core/CatmullRomLine.tsx
@@ -11,41 +11,43 @@ type Props = Omit & {
segments?: number
}
-export const CatmullRomLine: ForwardRefComponent = React.forwardRef(function CatmullRomLine(
- { points, closed = false, curveType = 'centripetal', tension = 0.5, segments = 20, vertexColors, ...rest },
- ref
-) {
- const curve = React.useMemo(() => {
- const mappedPoints = points.map((pt) =>
- pt instanceof Vector3 ? pt : new Vector3(...(pt as [number, number, number]))
- )
-
- return new CatmullRomCurve3(mappedPoints, closed, curveType, tension)
- }, [points, closed, curveType, tension])
-
- const segmentedPoints = React.useMemo(() => curve.getPoints(segments), [curve, segments])
-
- const interpolatedVertexColors = React.useMemo(() => {
- if (!vertexColors || vertexColors.length < 2) return undefined
-
- if (vertexColors.length === segments + 1) return vertexColors
-
- const mappedColors = vertexColors.map((color) =>
- color instanceof Color ? color : new Color(...(color as [number, number, number]))
- )
- if (closed) mappedColors.push(mappedColors[0].clone())
-
- const iColors: Color[] = [mappedColors[0]]
- const divisions = segments / (mappedColors.length - 1)
- for (let i = 1; i < segments; i++) {
- const alpha = (i % divisions) / divisions
- const colorIndex = Math.floor(i / divisions)
- iColors.push(mappedColors[colorIndex].clone().lerp(mappedColors[colorIndex + 1], alpha))
- }
- iColors.push(mappedColors[mappedColors.length - 1])
-
- return iColors
- }, [vertexColors, segments])
-
- return
-})
+export const CatmullRomLine: ForwardRefComponent = /* @__PURE__ */ React.forwardRef(
+ function CatmullRomLine(
+ { points, closed = false, curveType = 'centripetal', tension = 0.5, segments = 20, vertexColors, ...rest },
+ ref
+ ) {
+ const curve = React.useMemo(() => {
+ const mappedPoints = points.map((pt) =>
+ pt instanceof Vector3 ? pt : new Vector3(...(pt as [number, number, number]))
+ )
+
+ return new CatmullRomCurve3(mappedPoints, closed, curveType, tension)
+ }, [points, closed, curveType, tension])
+
+ const segmentedPoints = React.useMemo(() => curve.getPoints(segments), [curve, segments])
+
+ const interpolatedVertexColors = React.useMemo(() => {
+ if (!vertexColors || vertexColors.length < 2) return undefined
+
+ if (vertexColors.length === segments + 1) return vertexColors
+
+ const mappedColors = vertexColors.map((color) =>
+ color instanceof Color ? color : new Color(...(color as [number, number, number]))
+ )
+ if (closed) mappedColors.push(mappedColors[0].clone())
+
+ const iColors: Color[] = [mappedColors[0]]
+ const divisions = segments / (mappedColors.length - 1)
+ for (let i = 1; i < segments; i++) {
+ const alpha = (i % divisions) / divisions
+ const colorIndex = Math.floor(i / divisions)
+ iColors.push(mappedColors[colorIndex].clone().lerp(mappedColors[colorIndex + 1], alpha))
+ }
+ iColors.push(mappedColors[mappedColors.length - 1])
+
+ return iColors
+ }, [vertexColors, segments])
+
+ return
+ }
+)
diff --git a/src/core/Caustics.tsx b/src/core/Caustics.tsx
index fdea4d809..e75b36c10 100644
--- a/src/core/Caustics.tsx
+++ b/src/core/Caustics.tsx
@@ -11,6 +11,7 @@ import { shaderMaterial } from './shaderMaterial'
import { Edges } from './Edges'
import { FullScreenQuad } from 'three-stdlib'
import { ForwardRefComponent } from '../helpers/ts-utils'
+import { version } from '../helpers/constants'
type CausticsMaterialType = THREE.ShaderMaterial & {
cameraMatrixWorld?: THREE.Matrix4
@@ -99,13 +100,13 @@ function createNormalMaterial(side = THREE.FrontSide) {
})
}
-const CausticsProjectionMaterial = shaderMaterial(
+const CausticsProjectionMaterial = /* @__PURE__ */ shaderMaterial(
{
causticsTexture: null,
causticsTextureB: null,
- color: new THREE.Color(),
- lightProjMatrix: new THREE.Matrix4(),
- lightViewMatrix: new THREE.Matrix4(),
+ color: /* @__PURE__ */ new THREE.Color(),
+ lightProjMatrix: /* @__PURE__ */ new THREE.Matrix4(),
+ lightViewMatrix: /* @__PURE__ */ new THREE.Matrix4(),
},
`varying vec3 vWorldPosition;
void main() {
@@ -128,22 +129,22 @@ const CausticsProjectionMaterial = shaderMaterial(
vec3 back = texture2D(causticsTextureB, lightSpacePos.xy).rgb;
gl_FragColor = vec4((front + back) * color, 1.0);
#include
- #include <${parseInt(THREE.REVISION.replace(/\D+/g, '')) >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
+ #include <${version >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
}`
)
-const CausticsMaterial = shaderMaterial(
+const CausticsMaterial = /* @__PURE__ */ shaderMaterial(
{
- cameraMatrixWorld: new THREE.Matrix4(),
- cameraProjectionMatrixInv: new THREE.Matrix4(),
+ cameraMatrixWorld: /* @__PURE__ */ new THREE.Matrix4(),
+ cameraProjectionMatrixInv: /* @__PURE__ */ new THREE.Matrix4(),
normalTexture: null,
depthTexture: null,
- lightDir: new THREE.Vector3(0, 1, 0),
- lightPlaneNormal: new THREE.Vector3(0, 1, 0),
+ lightDir: /* @__PURE__ */ new THREE.Vector3(0, 1, 0),
+ lightPlaneNormal: /* @__PURE__ */ new THREE.Vector3(0, 1, 0),
lightPlaneConstant: 0,
near: 0.1,
far: 100,
- modelMatrix: new THREE.Matrix4(),
+ modelMatrix: /* @__PURE__ */ new THREE.Matrix4(),
worldRadius: 1 / 40,
ior: 1.1,
bounces: 0,
@@ -276,9 +277,9 @@ const CAUSTICPROPS = {
generateMipmaps: true,
}
-const causticsContext = React.createContext(null)
+const causticsContext = /* @__PURE__ */ React.createContext(null)
-export const Caustics: ForwardRefComponent = React.forwardRef(
+export const Caustics: ForwardRefComponent = /* @__PURE__ */ React.forwardRef(
(
{
debug,
diff --git a/src/core/Center.tsx b/src/core/Center.tsx
index 654bdb496..cf160816b 100644
--- a/src/core/Center.tsx
+++ b/src/core/Center.tsx
@@ -42,77 +42,75 @@ export type CenterProps = {
cacheKey?: any
}
-export const Center: ForwardRefComponent = React.forwardRef<
- Group,
- JSX.IntrinsicElements['group'] & CenterProps
->(function Center(
- {
- children,
- disable,
- disableX,
- disableY,
- disableZ,
- left,
- right,
- top,
- bottom,
- front,
- back,
- onCentered,
- precise = true,
- cacheKey = 0,
- ...props
- },
- fRef
-) {
- const ref = React.useRef(null!)
- const outer = React.useRef(null!)
- const inner = React.useRef(null!)
- React.useLayoutEffect(() => {
- outer.current.matrixWorld.identity()
- const box3 = new Box3().setFromObject(inner.current, precise)
- const center = new Vector3()
- const sphere = new Sphere()
- const width = box3.max.x - box3.min.x
- const height = box3.max.y - box3.min.y
- const depth = box3.max.z - box3.min.z
- box3.getCenter(center)
- box3.getBoundingSphere(sphere)
- const vAlign = top ? height / 2 : bottom ? -height / 2 : 0
- const hAlign = left ? -width / 2 : right ? width / 2 : 0
- const dAlign = front ? depth / 2 : back ? -depth / 2 : 0
+export const Center: ForwardRefComponent =
+ /* @__PURE__ */ React.forwardRef(function Center(
+ {
+ children,
+ disable,
+ disableX,
+ disableY,
+ disableZ,
+ left,
+ right,
+ top,
+ bottom,
+ front,
+ back,
+ onCentered,
+ precise = true,
+ cacheKey = 0,
+ ...props
+ },
+ fRef
+ ) {
+ const ref = React.useRef(null!)
+ const outer = React.useRef(null!)
+ const inner = React.useRef(null!)
+ React.useLayoutEffect(() => {
+ outer.current.matrixWorld.identity()
+ const box3 = new Box3().setFromObject(inner.current, precise)
+ const center = new Vector3()
+ const sphere = new Sphere()
+ const width = box3.max.x - box3.min.x
+ const height = box3.max.y - box3.min.y
+ const depth = box3.max.z - box3.min.z
+ box3.getCenter(center)
+ box3.getBoundingSphere(sphere)
+ const vAlign = top ? height / 2 : bottom ? -height / 2 : 0
+ const hAlign = left ? -width / 2 : right ? width / 2 : 0
+ const dAlign = front ? depth / 2 : back ? -depth / 2 : 0
- outer.current.position.set(
- disable || disableX ? 0 : -center.x + hAlign,
- disable || disableY ? 0 : -center.y + vAlign,
- disable || disableZ ? 0 : -center.z + dAlign
- )
+ outer.current.position.set(
+ disable || disableX ? 0 : -center.x + hAlign,
+ disable || disableY ? 0 : -center.y + vAlign,
+ disable || disableZ ? 0 : -center.z + dAlign
+ )
- // Only fire onCentered if the bounding box has changed
- if (typeof onCentered !== 'undefined') {
- onCentered({
- parent: ref.current.parent!,
- container: ref.current,
- width,
- height,
- depth,
- boundingBox: box3,
- boundingSphere: sphere,
- center: center,
- verticalAlignment: vAlign,
- horizontalAlignment: hAlign,
- depthAlignment: dAlign,
- })
- }
- }, [cacheKey, onCentered, top, left, front, disable, disableX, disableY, disableZ, precise, right, bottom, back])
+ // Only fire onCentered if the bounding box has changed
+ if (typeof onCentered !== 'undefined') {
+ onCentered({
+ parent: ref.current.parent!,
+ container: ref.current,
+ width,
+ height,
+ depth,
+ boundingBox: box3,
+ boundingSphere: sphere,
+ center: center,
+ verticalAlignment: vAlign,
+ horizontalAlignment: hAlign,
+ depthAlignment: dAlign,
+ })
+ }
+ }, [cacheKey, onCentered, top, left, front, disable, disableX, disableY, disableZ, precise, right, bottom, back])
- React.useImperativeHandle(fRef, () => ref.current, [])
+ React.useImperativeHandle(fRef, () => ref.current, [])
- return (
-
-
- {children}
+ return (
+
+
+ {children}
+
-
- )
-})
+ )
+ })
diff --git a/src/core/Clone.tsx b/src/core/Clone.tsx
index 6e07c25fd..b617322ab 100644
--- a/src/core/Clone.tsx
+++ b/src/core/Clone.tsx
@@ -1,6 +1,5 @@
import * as THREE from 'three'
import * as React from 'react'
-import pick from 'lodash.pick'
import { MeshProps } from '@react-three/fiber'
import { SkeletonUtils } from 'three-stdlib'
import { ForwardRefComponent } from '../helpers/ts-utils'
@@ -60,7 +59,11 @@ function createSpread(
receiveShadow,
}: Omit & Partial
) {
- let spread = pick(child, keys)
+ let spread: Record<(typeof keys)[number], any> = {}
+ for (const key of keys) {
+ spread[key] = child[key]
+ }
+
if (deep) {
if (spread.geometry && deep !== 'materialsOnly') spread.geometry = spread.geometry.clone()
if (spread.material && deep !== 'geometriesOnly') spread.material = spread.material.clone()
@@ -79,7 +82,7 @@ function createSpread(
}
export const Clone: ForwardRefComponent & CloneProps, THREE.Group> =
- React.forwardRef(
+ /* @__PURE__ */ React.forwardRef(
(
{
isChild = false,
@@ -120,11 +123,11 @@ export const Clone: ForwardRefComponent
return (
- {(object?.children).map((child) => {
+ {object.children.map((child) => {
if (child.type === 'Bone') return
return
})}
diff --git a/src/core/Cloud.tsx b/src/core/Cloud.tsx
index d113e3225..d050178b8 100644
--- a/src/core/Cloud.tsx
+++ b/src/core/Cloud.tsx
@@ -1,61 +1,320 @@
import * as React from 'react'
-import { Group, Texture } from 'three'
-import { useFrame } from '@react-three/fiber'
-import { Billboard } from './Billboard'
-import { Plane } from './shapes'
+import {
+ REVISION,
+ DynamicDrawUsage,
+ Color,
+ Group,
+ Texture,
+ Vector3,
+ InstancedMesh,
+ Material,
+ MeshLambertMaterial,
+ Matrix4,
+ Quaternion,
+ BufferAttribute,
+} from 'three'
+import { MaterialNode, extend, applyProps, useFrame, ReactThreeFiber } from '@react-three/fiber'
import { useTexture } from './useTexture'
+import { v4 } from 'uuid'
+import { setUpdateRange } from '../helpers/deprecated'
+
+declare global {
+ namespace JSX {
+ interface IntrinsicElements {
+ cloudMaterial: MaterialNode
+ }
+ }
+}
const CLOUD_URL = 'https://rawcdn.githack.com/pmndrs/drei-assets/9225a9f1fbd449d9411125c2f419b843d0308c9f/cloud.png'
-export function Cloud({
- opacity = 0.5,
- speed = 0.4,
- width = 10,
- depth = 1.5,
- segments = 20,
- texture = CLOUD_URL,
- color = '#ffffff',
- depthTest = true,
- ...props
-}) {
- const group = React.useRef()
- const cloudTexture = useTexture(texture) as Texture
- const clouds = React.useMemo(
- () =>
- [...new Array(segments)].map((_, index) => ({
- x: width / 2 - Math.random() * width,
- y: width / 2 - Math.random() * width,
- scale: 0.4 + Math.sin(((index + 1) / segments) * Math.PI) * ((0.2 + Math.random()) * 10),
- density: Math.max(0.2, Math.random()),
- rotation: Math.max(0.002, 0.005 * Math.random()) * speed,
- })),
- [width, segments, speed]
- )
- useFrame((state) =>
- group.current?.children.forEach((cloud, index) => {
- cloud.children[0].rotation.z += clouds[index].rotation
- cloud.children[0].scale.setScalar(
- clouds[index].scale + (((1 + Math.sin(state.clock.getElapsedTime() / 10)) / 2) * index) / 10
- )
+type CloudState = {
+ uuid: string
+ index: number
+ segments: number
+ dist: number
+ matrix: Matrix4
+ bounds: Vector3
+ position: Vector3
+ volume: number
+ length: number
+ ref: React.MutableRefObject
+ speed: number
+ growth: number
+ opacity: number
+ fade: number
+ density: number
+ rotation: number
+ rotationFactor: number
+ color: Color
+}
+
+type CloudsProps = JSX.IntrinsicElements['group'] & {
+ /** Optional cloud texture, points to a default hosted on rawcdn.githack */
+ texture?: string
+ /** Maximum number of segments, default: 200 (make this tight to save memory!) */
+ limit?: number
+ /** How many segments it renders, default: undefined (all) */
+ range?: number
+ /** Which material it will override, default: MeshLambertMaterial */
+ material?: typeof Material
+}
+
+type CloudProps = JSX.IntrinsicElements['group'] & {
+ /** A seeded random will show the same cloud consistently, default: Math.random() */
+ seed?: number
+ /** How many segments or particles the cloud will have, default: 20 */
+ segments?: number
+ /** The box3 bounds of the cloud, default: [5, 1, 1] */
+ bounds?: ReactThreeFiber.Vector3
+ /** How to arrange segment volume inside the bounds, default: inside (cloud are smaller at the edges) */
+ concentrate?: 'random' | 'inside' | 'outside'
+ /** The general scale of the segments */
+ scale?: ReactThreeFiber.Vector3
+ /** The volume/thickness of the segments, default: 6 */
+ volume?: number
+ /** The smallest volume when distributing clouds, default: 0.25 */
+ smallestVolume?: number
+ /** An optional function that allows you to distribute points and volumes (overriding all settings), default: null
+ * Both point and volume are factors, point x/y/z can be between -1 and 1, volume between 0 and 1 */
+ distribute?: (cloud: CloudState, index: number) => { point: Vector3; volume?: number }
+ /** Growth factor for animated clouds (speed > 0), default: 4 */
+ growth?: number
+ /** Animation factor, default: 0 */
+ speed?: number
+ /** Camera distance until the segments will fade, default: 10 */
+ fade?: number
+ /** Opacity, default: 1 */
+ opacity?: number
+ /** Color, default: white */
+ color?: ReactThreeFiber.Color
+}
+
+const parentMatrix = /* @__PURE__ */ new Matrix4()
+const translation = /* @__PURE__ */ new Vector3()
+const rotation = /* @__PURE__ */ new Quaternion()
+const cpos = /* @__PURE__ */ new Vector3()
+const cquat = /* @__PURE__ */ new Quaternion()
+const scale = /* @__PURE__ */ new Vector3()
+
+const context = /* @__PURE__ */ React.createContext>(null!)
+export const Clouds = /* @__PURE__ */ React.forwardRef(
+ ({ children, material = MeshLambertMaterial, texture = CLOUD_URL, range, limit = 200, ...props }, fref) => {
+ const CloudMaterial = React.useMemo(() => {
+ return class extends (material as typeof Material) {
+ constructor() {
+ super()
+ const opaque_fragment = parseInt(REVISION.replace(/\D+/g, '')) >= 154 ? 'opaque_fragment' : 'output_fragment'
+ this.onBeforeCompile = (shader) => {
+ shader.vertexShader =
+ `attribute float opacity;
+ varying float vOpacity;
+ ` +
+ shader.vertexShader.replace(
+ '#include ',
+ `#include
+ vOpacity = opacity;
+ `
+ )
+ shader.fragmentShader =
+ `varying float vOpacity;
+ ` +
+ shader.fragmentShader.replace(
+ `#include <${opaque_fragment}>`,
+ `#include <${opaque_fragment}>
+ gl_FragColor = vec4(outgoingLight, diffuseColor.a * vOpacity);
+ `
+ )
+ }
+ }
+ }
+ }, [material])
+
+ extend({ CloudMaterial })
+
+ const instance = React.useRef(null!)
+ const clouds = React.useRef([])
+ const opacities = React.useMemo(() => new Float32Array(Array.from({ length: limit }, () => 1)), [limit])
+ const colors = React.useMemo(() => new Float32Array(Array.from({ length: limit }, () => [1, 1, 1]).flat()), [limit])
+ const cloudTexture = useTexture(texture) as Texture
+
+ let t = 0
+ let index = 0
+ let config: CloudState
+ const qat = new Quaternion()
+ const dir = new Vector3(0, 0, 1)
+ const pos = new Vector3()
+
+ useFrame((state, delta) => {
+ t = state.clock.getElapsedTime()
+ parentMatrix.copy(instance.current.matrixWorld).invert()
+ state.camera.matrixWorld.decompose(cpos, cquat, scale)
+
+ for (index = 0; index < clouds.current.length; index++) {
+ config = clouds.current[index]
+ config.ref.current.matrixWorld.decompose(translation, rotation, scale)
+ translation.add(pos.copy(config.position).applyQuaternion(rotation).multiply(scale))
+ rotation.copy(cquat).multiply(qat.setFromAxisAngle(dir, (config.rotation += delta * config.rotationFactor)))
+ scale.multiplyScalar(config.volume + ((1 + Math.sin(t * config.density * config.speed)) / 2) * config.growth)
+ config.matrix.compose(translation, rotation, scale).premultiply(parentMatrix)
+ config.dist = translation.distanceTo(cpos)
+ }
+
+ // Depth-sort. Instances have no specific draw order, w/o sorting z would be random
+ clouds.current.sort((a, b) => b.dist - a.dist)
+ for (index = 0; index < clouds.current.length; index++) {
+ config = clouds.current[index]
+ opacities[index] = config.opacity * (config.dist < config.fade - 1 ? config.dist / config.fade : 1)
+ instance.current.setMatrixAt(index, config.matrix)
+ instance.current.setColorAt(index, config.color)
+ }
+
+ // Update instance
+ instance.current.geometry.attributes.opacity.needsUpdate = true
+ instance.current.instanceMatrix.needsUpdate = true
+ if (instance.current.instanceColor) instance.current.instanceColor.needsUpdate = true
+ })
+
+ React.useLayoutEffect(() => {
+ const count = Math.min(limit, range !== undefined ? range : limit, clouds.current.length)
+ instance.current.count = count
+ setUpdateRange(instance.current.instanceMatrix, { offset: 0, count: count * 16 })
+ if (instance.current.instanceColor) {
+ setUpdateRange(instance.current.instanceColor, { offset: 0, count: count * 3 })
+ }
+ setUpdateRange(instance.current.geometry.attributes.opacity as BufferAttribute, { offset: 0, count: count })
})
- )
- return (
-
-
- {clouds.map(({ x, y, scale, density }, index) => (
-
-
-
-
-
- ))}
+
+ let imageBounds = [cloudTexture!.image.width ?? 1, cloudTexture!.image.height ?? 1]
+ let max = Math.max(imageBounds[0], imageBounds[1])
+ imageBounds = [imageBounds[0] / max, imageBounds[1] / max]
+
+ return (
+
+
+ {children}
+
+
+
+
+
+
+
+
-
- )
-}
+ )
+ }
+)
+
+export const CloudInstance = /* @__PURE__ */ React.forwardRef(
+ (
+ {
+ opacity = 1,
+ speed = 0,
+ bounds = [5, 1, 1],
+ segments = 20,
+ color = '#ffffff',
+ fade = 10,
+ volume = 6,
+ smallestVolume = 0.25,
+ distribute = null,
+ growth = 4,
+ concentrate = 'inside',
+ seed = Math.random(),
+ ...props
+ },
+ fref
+ ) => {
+ function random() {
+ const x = Math.sin(seed++) * 10000
+ return x - Math.floor(x)
+ }
+
+ const parent = React.useContext(context)
+ const ref = React.useRef(null!)
+ const [uuid] = React.useState(() => v4())
+ const clouds: CloudState[] = React.useMemo(() => {
+ return [...new Array(segments)].map(
+ (_, index) =>
+ ({
+ segments,
+ bounds: new Vector3(1, 1, 1),
+ position: new Vector3(),
+ uuid,
+ index,
+ ref,
+ dist: 0,
+ matrix: new Matrix4(),
+ color: new Color(),
+ rotation: index * (Math.PI / segments),
+ } as CloudState)
+ )
+ }, [segments, uuid])
+
+ React.useLayoutEffect(() => {
+ clouds.forEach((cloud, index) => {
+ applyProps(cloud as any, {
+ volume,
+ color,
+ speed,
+ growth,
+ opacity,
+ fade,
+ bounds,
+ density: Math.max(0.5, random()),
+ rotationFactor: Math.max(0.2, 0.5 * random()) * speed,
+ })
+ // Only distribute randomly if there are multiple segments
+
+ const distributed = distribute?.(cloud, index)
+
+ if (distributed || segments > 1)
+ cloud.position.copy(cloud.bounds).multiply(
+ distributed?.point ??
+ ({
+ x: random() * 2 - 1,
+ y: random() * 2 - 1,
+ z: random() * 2 - 1,
+ } as Vector3)
+ )
+ const xDiff = Math.abs(cloud.position.x)
+ const yDiff = Math.abs(cloud.position.y)
+ const zDiff = Math.abs(cloud.position.z)
+ const max = Math.max(xDiff, yDiff, zDiff)
+ cloud.length = 1
+ if (xDiff === max) cloud.length -= xDiff / cloud.bounds.x
+ if (yDiff === max) cloud.length -= yDiff / cloud.bounds.y
+ if (zDiff === max) cloud.length -= zDiff / cloud.bounds.z
+ cloud.volume =
+ (distributed?.volume !== undefined
+ ? distributed.volume
+ : Math.max(
+ Math.max(0, smallestVolume),
+ concentrate === 'random' ? random() : concentrate === 'inside' ? cloud.length : 1 - cloud.length
+ )) * volume
+ })
+ }, [concentrate, bounds, fade, color, opacity, growth, volume, seed, segments, speed])
+
+ React.useLayoutEffect(() => {
+ const temp = clouds
+ parent.current = [...parent.current, ...temp]
+ return () => {
+ parent.current = parent.current.filter((item) => item.uuid !== uuid)
+ }
+ }, [clouds])
+
+ React.useImperativeHandle(fref, () => ref.current, [])
+ return
+ }
+)
+
+export const Cloud = /* @__PURE__ */ React.forwardRef((props, fref) => {
+ const parent = React.useContext(context)
+ if (parent) return
+ else
+ return (
+
+
+
+ )
+})
diff --git a/src/core/ContactShadows.tsx b/src/core/ContactShadows.tsx
index e546d01d3..c6d789879 100644
--- a/src/core/ContactShadows.tsx
+++ b/src/core/ContactShadows.tsx
@@ -25,7 +25,7 @@ export type ContactShadowsProps = {
export const ContactShadows: ForwardRefComponent<
Omit & ContactShadowsProps,
THREE.Group
-> = React.forwardRef(
+> = /* @__PURE__ */ React.forwardRef(
(
{
scale = 10,
diff --git a/src/core/CubeCamera.tsx b/src/core/CubeCamera.tsx
index f870dbbe5..895fc631a 100644
--- a/src/core/CubeCamera.tsx
+++ b/src/core/CubeCamera.tsx
@@ -12,7 +12,7 @@ type Props = Omit & {
} & CubeCameraOptions
export function CubeCamera({ children, frames = Infinity, resolution, near, far, envMap, fog, ...props }: Props) {
- const ref = React.useRef()
+ const ref = React.useRef(null!)
const { fbo, camera, update } = useCubeCamera({
resolution,
near,
diff --git a/src/core/CubicBezierLine.tsx b/src/core/CubicBezierLine.tsx
index 143eb34d3..db1da4c1a 100644
--- a/src/core/CubicBezierLine.tsx
+++ b/src/core/CubicBezierLine.tsx
@@ -12,7 +12,7 @@ type Props = Omit & {
segments?: number
}
-export const CubicBezierLine: ForwardRefComponent = React.forwardRef(
+export const CubicBezierLine: ForwardRefComponent = /* @__PURE__ */ React.forwardRef(
function CubicBezierLine({ start, end, midA, midB, segments = 20, ...rest }, ref) {
const points = React.useMemo(() => {
const startV = start instanceof Vector3 ? start : new Vector3(...start)
diff --git a/src/core/CurveModifier.tsx b/src/core/CurveModifier.tsx
index d9ca33c08..b6012113d 100644
--- a/src/core/CurveModifier.tsx
+++ b/src/core/CurveModifier.tsx
@@ -11,8 +11,8 @@ export interface CurveModifierProps {
export type CurveModifierRef = Pick
-export const CurveModifier: ForwardRefComponent = React.forwardRef(
- ({ children, curve }: CurveModifierProps, ref) => {
+export const CurveModifier: ForwardRefComponent =
+ /* @__PURE__ */ React.forwardRef(({ children, curve }: CurveModifierProps, ref) => {
const [scene] = React.useState(() => new THREE.Scene())
const [obj, set] = React.useState()
const modifier = React.useRef()
@@ -40,5 +40,4 @@ export const CurveModifier: ForwardRefComponent }
>
)
- }
-)
+ })
diff --git a/src/core/Decal.tsx b/src/core/Decal.tsx
index bd6f60655..77ec4039a 100644
--- a/src/core/Decal.tsx
+++ b/src/core/Decal.tsx
@@ -31,81 +31,82 @@ function vecToArray(vec: number[] | FIBER.Vector3 | FIBER.Euler | number = [0, 0
}
}
-export const Decal: ForwardRefComponent = React.forwardRef(
- function Decal(
- { debug, depthTest = false, polygonOffsetFactor = -10, map, mesh, children, position, rotation, scale, ...props },
- forwardRef
- ) {
- const ref = React.useRef(null!)
- React.useImperativeHandle(forwardRef, () => ref.current)
- const helper = React.useRef(null!)
+export const Decal: ForwardRefComponent = /* @__PURE__ */ React.forwardRef<
+ THREE.Mesh,
+ DecalProps
+>(function Decal(
+ { debug, depthTest = false, polygonOffsetFactor = -10, map, mesh, children, position, rotation, scale, ...props },
+ forwardRef
+) {
+ const ref = React.useRef(null!)
+ React.useImperativeHandle(forwardRef, () => ref.current)
+ const helper = React.useRef(null!)
- React.useLayoutEffect(() => {
- const parent = mesh?.current || ref.current.parent
- const target = ref.current
- if (!(parent instanceof THREE.Mesh)) {
- throw new Error('Decal must have a Mesh as parent or specify its "mesh" prop')
- }
+ React.useLayoutEffect(() => {
+ const parent = mesh?.current || ref.current.parent
+ const target = ref.current
+ if (!(parent instanceof THREE.Mesh)) {
+ throw new Error('Decal must have a Mesh as parent or specify its "mesh" prop')
+ }
- const state = {
- position: new THREE.Vector3(),
- rotation: new THREE.Euler(),
- scale: new THREE.Vector3(1, 1, 1),
- }
+ const state = {
+ position: new THREE.Vector3(),
+ rotation: new THREE.Euler(),
+ scale: new THREE.Vector3(1, 1, 1),
+ }
- if (parent) {
- applyProps(state as any, { position, scale })
+ if (parent) {
+ applyProps(state as any, { position, scale })
- // Zero out the parents matrix world for this operation
- const matrixWorld = parent.matrixWorld.clone()
- parent.matrixWorld.identity()
+ // Zero out the parents matrix world for this operation
+ const matrixWorld = parent.matrixWorld.clone()
+ parent.matrixWorld.identity()
- if (!rotation || typeof rotation === 'number') {
- const o = new THREE.Object3D()
+ if (!rotation || typeof rotation === 'number') {
+ const o = new THREE.Object3D()
- o.position.copy(state.position)
- o.lookAt(parent.position)
- if (typeof rotation === 'number') o.rotateZ(rotation)
- applyProps(state as any, { rotation: o.rotation })
- } else {
- applyProps(state as any, { rotation })
- }
+ o.position.copy(state.position)
+ o.lookAt(parent.position)
+ if (typeof rotation === 'number') o.rotateZ(rotation)
+ applyProps(state as any, { rotation: o.rotation })
+ } else {
+ applyProps(state as any, { rotation })
+ }
- target.geometry = new DecalGeometry(parent, state.position, state.rotation, state.scale)
- if (helper.current) {
- applyProps(helper.current as any, state)
- // Prevent the helpers from blocking rays
- helper.current.traverse((child) => (child.raycast = () => null))
- }
- // Reset parents matix-world
- parent.matrixWorld = matrixWorld
+ target.geometry = new DecalGeometry(parent, state.position, state.rotation, state.scale)
+ if (helper.current) {
+ applyProps(helper.current as any, state)
+ // Prevent the helpers from blocking rays
+ helper.current.traverse((child) => (child.raycast = () => null))
+ }
+ // Reset parents matix-world
+ parent.matrixWorld = matrixWorld
- return () => {
- target.geometry.dispose()
- }
+ return () => {
+ target.geometry.dispose()
}
- }, [mesh, ...vecToArray(position), ...vecToArray(scale), ...vecToArray(rotation)])
+ }
+ }, [mesh, ...vecToArray(position), ...vecToArray(scale), ...vecToArray(rotation)])
- // }
- return (
-
- {children}
- {debug && (
-
-
-
-
-
- )}
-
- )
- }
-)
+ // }
+ return (
+
+ {children}
+ {debug && (
+
+
+
+
+
+ )}
+
+ )
+})
diff --git a/src/core/Detailed.tsx b/src/core/Detailed.tsx
index efb40f1e8..1cc78b146 100644
--- a/src/core/Detailed.tsx
+++ b/src/core/Detailed.tsx
@@ -10,7 +10,7 @@ type Props = JSX.IntrinsicElements['lOD'] & {
distances: number[]
}
-export const Detailed: ForwardRefComponent = React.forwardRef(
+export const Detailed: ForwardRefComponent = /* @__PURE__ */ React.forwardRef(
({ children, hysteresis = 0, distances, ...props }: Props, ref) => {
const lodRef = React.useRef(null!)
React.useLayoutEffect(() => {
diff --git a/src/core/DeviceOrientationControls.tsx b/src/core/DeviceOrientationControls.tsx
index 7f252591a..76ed16056 100644
--- a/src/core/DeviceOrientationControls.tsx
+++ b/src/core/DeviceOrientationControls.tsx
@@ -16,7 +16,7 @@ export type DeviceOrientationControlsProps = ReactThreeFiber.Object3DNode<
export const DeviceOrientationControls: ForwardRefComponent<
DeviceOrientationControlsProps,
DeviceOrientationControlsImp
-> = React.forwardRef(
+> = /* @__PURE__ */ React.forwardRef(
(props: DeviceOrientationControlsProps, ref) => {
const { camera, onChange, makeDefault, ...rest } = props
const defaultCamera = useThree((state) => state.camera)
diff --git a/src/core/Edges.tsx b/src/core/Edges.tsx
index f1983671a..7ffdc3cd1 100644
--- a/src/core/Edges.tsx
+++ b/src/core/Edges.tsx
@@ -8,7 +8,7 @@ type Props = JSX.IntrinsicElements['lineSegments'] & {
color?: ReactThreeFiber.Color
}
-export const Edges: ForwardRefComponent = React.forwardRef(
+export const Edges: ForwardRefComponent = /* @__PURE__ */ React.forwardRef(
(
{ userData, children, geometry, threshold = 15, color = 'black', ...props }: Props,
fref: React.ForwardedRef
diff --git a/src/core/Effects.tsx b/src/core/Effects.tsx
index ea365452f..6da57bacd 100644
--- a/src/core/Effects.tsx
+++ b/src/core/Effects.tsx
@@ -37,7 +37,7 @@ export const isWebGL2Available = () => {
}
}
-export const Effects: ForwardRefComponent = React.forwardRef(
+export const Effects: ForwardRefComponent = /* @__PURE__ */ React.forwardRef(
(
{
children,
diff --git a/src/core/Example.tsx b/src/core/Example.tsx
index 3d7127170..d2bc14cd5 100644
--- a/src/core/Example.tsx
+++ b/src/core/Example.tsx
@@ -18,7 +18,7 @@ export type ExampleApi = {
decr: (x?: number) => void
}
-export const Example = React.forwardRef(
+export const Example = /* @__PURE__ */ React.forwardRef(
({ font, color = '#cbcbcb', bevelSize = 0.04, debug = false, children, ...props }, fref) => {
const [counter, setCounter] = React.useState(0)
diff --git a/src/core/FirstPersonControls.tsx b/src/core/FirstPersonControls.tsx
index 482f2de88..d69c9ff0c 100644
--- a/src/core/FirstPersonControls.tsx
+++ b/src/core/FirstPersonControls.tsx
@@ -9,26 +9,28 @@ export type FirstPersonControlsProps = Object3DNode =
- React.forwardRef(({ domElement, makeDefault, ...props }, ref) => {
- const camera = useThree((state) => state.camera)
- const gl = useThree((state) => state.gl)
- const events = useThree((state) => state.events) as EventManager
- const get = useThree((state) => state.get)
- const set = useThree((state) => state.set)
- const explDomElement = (domElement || events.connected || gl.domElement) as HTMLElement
- const [controls] = React.useState(() => new FirstPersonControlImpl(camera, explDomElement))
+ /* @__PURE__ */ React.forwardRef(
+ ({ domElement, makeDefault, ...props }, ref) => {
+ const camera = useThree((state) => state.camera)
+ const gl = useThree((state) => state.gl)
+ const events = useThree((state) => state.events) as EventManager
+ const get = useThree((state) => state.get)
+ const set = useThree((state) => state.set)
+ const explDomElement = (domElement || events.connected || gl.domElement) as HTMLElement
+ const [controls] = React.useState(() => new FirstPersonControlImpl(camera, explDomElement))
- React.useEffect(() => {
- if (makeDefault) {
- const old = get().controls
- set({ controls })
- return () => set({ controls: old })
- }
- }, [makeDefault, controls])
+ React.useEffect(() => {
+ if (makeDefault) {
+ const old = get().controls
+ set({ controls })
+ return () => set({ controls: old })
+ }
+ }, [makeDefault, controls])
- useFrame((_, delta) => {
- controls.update(delta)
- }, -1)
+ useFrame((_, delta) => {
+ controls.update(delta)
+ }, -1)
- return controls ? : null
- })
+ return controls ? : null
+ }
+ )
diff --git a/src/core/Fisheye.tsx b/src/core/Fisheye.tsx
new file mode 100644
index 000000000..dfa3c2618
--- /dev/null
+++ b/src/core/Fisheye.tsx
@@ -0,0 +1,110 @@
+/**
+ * Event compute by Garrett Johnson https://twitter.com/garrettkjohnson
+ * https://discourse.threejs.org/t/how-to-use-three-raycaster-with-a-sphere-projected-envmap/56803/10
+ */
+
+import * as THREE from 'three'
+import * as React from 'react'
+import { useFrame, useThree } from '@react-three/fiber'
+import { RenderCubeTexture, RenderCubeTextureApi } from './RenderCubeTexture'
+
+export type FisheyeProps = JSX.IntrinsicElements['mesh'] & {
+ /** Zoom factor, 0..1, 0 */
+ zoom?: number
+ /** Number of segments, 64 */
+ segments?: number
+ /** Cubemap resolution (for each of the 6 takes), null === full screen resolution, default: 896 */
+ resolution?: number
+ /** Children will be projected into the fisheye */
+ children: React.ReactNode
+ /** Optional render priority, defaults to 1 */
+ renderPriority?: number
+}
+
+export function Fisheye({
+ renderPriority = 1,
+ zoom = 0,
+ segments = 64,
+ children,
+ resolution = 896,
+ ...props
+}: FisheyeProps) {
+ const sphere = React.useRef(null!)
+ const cubeApi = React.useRef(null!)
+
+ // This isn't more than a simple sphere and a fixed orthographc camera
+ // pointing at it. A virtual scene is portalled into the environment map
+ // of its material. The cube-camera filming that scene is being synced to
+ // the portals default camera with the component.
+
+ const { width, height } = useThree((state) => state.size)
+ const [orthoC] = React.useState(() => new THREE.OrthographicCamera())
+
+ React.useLayoutEffect(() => {
+ orthoC.position.set(0, 0, 100)
+ orthoC.zoom = 100
+ orthoC.left = width / -2
+ orthoC.right = width / 2
+ orthoC.top = height / 2
+ orthoC.bottom = height / -2
+ orthoC.updateProjectionMatrix()
+ }, [width, height])
+
+ const radius = (Math.sqrt(width * width + height * height) / 100) * (0.5 + zoom / 2)
+ const normal = new THREE.Vector3()
+ const sph = new THREE.Sphere(new THREE.Vector3(), radius)
+ const normalMatrix = new THREE.Matrix3()
+
+ const compute = React.useCallback((event, state, prev) => {
+ // Raycast from the render camera to the sphere and get the surface normal
+ // of the point hit in world space of the sphere scene
+ // We have to set the raycaster using the orthocam and pointer
+ // to perform sphere interscetions.
+ state.pointer.set((event.offsetX / state.size.width) * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1)
+ state.raycaster.setFromCamera(state.pointer, orthoC)
+ if (!state.raycaster.ray.intersectSphere(sph, normal)) return
+ else normal.normalize()
+ // Get the matrix for transforming normals into world space
+ normalMatrix.getNormalMatrix(cubeApi.current.camera.matrixWorld)
+ // Get the ray
+ cubeApi.current.camera.getWorldPosition(state.raycaster.ray.origin)
+ state.raycaster.ray.direction.set(0, 0, 1).reflect(normal)
+ state.raycaster.ray.direction.x *= -1 // flip across X to accommodate the "flip" of the env map
+ state.raycaster.ray.direction.applyNormalMatrix(normalMatrix).multiplyScalar(-1)
+ return undefined
+ }, [])
+
+ useFrame((state) => {
+ // Take over rendering
+ if (renderPriority) state.gl.render(sphere.current, orthoC)
+ }, renderPriority)
+
+ return (
+ <>
+
+
+
+
+ {children}
+
+
+
+
+ >
+ )
+}
+
+function UpdateCubeCamera({ api }: { api: React.MutableRefObject }) {
+ const t = new THREE.Vector3()
+ const r = new THREE.Quaternion()
+ const s = new THREE.Vector3()
+ const e = new THREE.Euler(0, Math.PI, 0)
+ useFrame((state) => {
+ // Read out the cameras whereabouts, state.camera is the one *within* the portal
+ state.camera.matrixWorld.decompose(t, r, s)
+ // Apply its position and rotation, flip the Y axis
+ api.current.camera.position.copy(t)
+ api.current.camera.quaternion.setFromEuler(e).premultiply(r)
+ })
+ return null
+}
diff --git a/src/core/Float.tsx b/src/core/Float.tsx
index fe8c3ad87..311df711f 100644
--- a/src/core/Float.tsx
+++ b/src/core/Float.tsx
@@ -13,7 +13,10 @@ export type FloatProps = JSX.IntrinsicElements['group'] & {
floatingRange?: [number?, number?]
}
-export const Float: ForwardRefComponent = React.forwardRef(
+export const Float: ForwardRefComponent = /* @__PURE__ */ React.forwardRef<
+ THREE.Group,
+ FloatProps
+>(
(
{
children,
diff --git a/src/core/FlyControls.tsx b/src/core/FlyControls.tsx
index 46d5ff5d1..081e4378c 100644
--- a/src/core/FlyControls.tsx
+++ b/src/core/FlyControls.tsx
@@ -10,7 +10,7 @@ export type FlyControlsProps = ReactThreeFiber.Object3DNode = React.forwardRef<
+export const FlyControls: ForwardRefComponent = /* @__PURE__ */ React.forwardRef<
FlyControlsImpl,
FlyControlsProps
>(({ domElement, ...props }, fref) => {
diff --git a/src/core/GizmoHelper.tsx b/src/core/GizmoHelper.tsx
index fe7ff5f3b..9873a8451 100644
--- a/src/core/GizmoHelper.tsx
+++ b/src/core/GizmoHelper.tsx
@@ -9,18 +9,18 @@ type GizmoHelperContext = {
tweenCamera: (direction: Vector3) => void
}
-const Context = React.createContext({} as GizmoHelperContext)
+const Context = /* @__PURE__ */ React.createContext({} as GizmoHelperContext)
export const useGizmoContext = () => {
return React.useContext(Context)
}
const turnRate = 2 * Math.PI // turn rate in angles per second
-const dummy = new Object3D()
-const matrix = new Matrix4()
-const [q1, q2] = [new Quaternion(), new Quaternion()]
-const target = new Vector3()
-const targetPosition = new Vector3()
+const dummy = /* @__PURE__ */ new Object3D()
+const matrix = /* @__PURE__ */ new Matrix4()
+const [q1, q2] = [/* @__PURE__ */ new Quaternion(), /* @__PURE__ */ new Quaternion()]
+const target = /* @__PURE__ */ new Vector3()
+const targetPosition = /* @__PURE__ */ new Vector3()
type ControlsProto = { update(): void; target: THREE.Vector3 }
@@ -61,7 +61,7 @@ export const GizmoHelper = ({
// @ts-ignore
const defaultControls = useThree((state) => state.controls) as ControlsProto
const invalidate = useThree((state) => state.invalidate)
- const gizmoRef = React.useRef()
+ const gizmoRef = React.useRef(null!)
const virtualCam = React.useRef(null!)
const animating = React.useRef(false)
diff --git a/src/core/GizmoViewcube.tsx b/src/core/GizmoViewcube.tsx
index fb46a4b85..44fe74f60 100644
--- a/src/core/GizmoViewcube.tsx
+++ b/src/core/GizmoViewcube.tsx
@@ -21,7 +21,7 @@ const colors = { bg: '#f0f0f0', hover: '#999', text: 'black', stroke: 'black' }
const defaultFaces = ['Right', 'Left', 'Top', 'Bottom', 'Front', 'Back']
const makePositionVector = (xyz: number[]) => new Vector3(...xyz).multiplyScalar(0.38)
-const corners: Vector3[] = [
+const corners: Vector3[] = /* @__PURE__ */ [
[1, 1, 1],
[1, 1, -1],
[1, -1, 1],
@@ -34,7 +34,7 @@ const corners: Vector3[] = [
const cornerDimensions: XYZ = [0.25, 0.25, 0.25]
-const edges: Vector3[] = [
+const edges: Vector3[] = /* @__PURE__ */ [
[1, 1, 0],
[1, 0, 1],
[1, 0, -1],
@@ -49,7 +49,7 @@ const edges: Vector3[] = [
[-1, -1, 0],
].map(makePositionVector)
-const edgeDimensions = edges.map(
+const edgeDimensions = /* @__PURE__ */ edges.map(
(edge) => edge.toArray().map((axis: number): number => (axis == 0 ? 0.5 : 0.25)) as XYZ
)
diff --git a/src/core/Gltf.tsx b/src/core/Gltf.tsx
index ce4b605a7..363d0d007 100644
--- a/src/core/Gltf.tsx
+++ b/src/core/Gltf.tsx
@@ -9,7 +9,7 @@ type GltfProps = Omit &
src: string
}
-export const Gltf: ForwardRefComponent = React.forwardRef(
+export const Gltf: ForwardRefComponent = /* @__PURE__ */ React.forwardRef(
({ src, ...props }: GltfProps, ref: React.Ref) => {
const { scene } = useGLTF(src)
return
diff --git a/src/core/Grid.tsx b/src/core/Grid.tsx
index 4b3df2a8e..3e82a6344 100644
--- a/src/core/Grid.tsx
+++ b/src/core/Grid.tsx
@@ -10,6 +10,7 @@ import mergeRefs from 'react-merge-refs'
import { extend, useFrame } from '@react-three/fiber'
import { shaderMaterial } from './shaderMaterial'
import { ForwardRefComponent } from '../helpers/ts-utils'
+import { version } from '../helpers/constants'
export type GridMaterialType = {
/** Cell size, default: 0.5 */
@@ -49,7 +50,7 @@ declare global {
}
}
-const GridMaterial = shaderMaterial(
+const GridMaterial = /* @__PURE__ */ shaderMaterial(
{
cellSize: 0.5,
sectionSize: 1,
@@ -57,12 +58,12 @@ const GridMaterial = shaderMaterial(
fadeStrength: 1,
cellThickness: 0.5,
sectionThickness: 1,
- cellColor: new THREE.Color(),
- sectionColor: new THREE.Color(),
+ cellColor: /* @__PURE__ */ new THREE.Color(),
+ sectionColor: /* @__PURE__ */ new THREE.Color(),
infiniteGrid: false,
followCamera: false,
- worldCamProjPosition: new THREE.Vector3(),
- worldPlanePosition: new THREE.Vector3(),
+ worldCamProjPosition: /* @__PURE__ */ new THREE.Vector3(),
+ worldPlanePosition: /* @__PURE__ */ new THREE.Vector3(),
},
/* glsl */ `
varying vec3 localPosition;
@@ -121,13 +122,13 @@ const GridMaterial = shaderMaterial(
if (gl_FragColor.a <= 0.0) discard;
#include
- #include <${parseInt(THREE.REVISION.replace(/\D+/g, '')) >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
+ #include <${version >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
}
`
)
export const Grid: ForwardRefComponent & GridProps, THREE.Mesh> =
- React.forwardRef(
+ /* @__PURE__ */ React.forwardRef(
(
{
args,
diff --git a/src/core/Image.tsx b/src/core/Image.tsx
index 590cfc8c9..71c395a5d 100644
--- a/src/core/Image.tsx
+++ b/src/core/Image.tsx
@@ -1,24 +1,29 @@
import * as React from 'react'
import * as THREE from 'three'
-import { Color, extend } from '@react-three/fiber'
+import { Color, extend, useThree } from '@react-three/fiber'
import { shaderMaterial } from './shaderMaterial'
import { useTexture } from './useTexture'
import { ForwardRefComponent } from '../helpers/ts-utils'
+import { version } from '../helpers/constants'
export type ImageProps = Omit & {
segments?: number
scale?: number | [number, number]
color?: Color
zoom?: number
+ radius?: number
grayscale?: number
toneMapped?: boolean
transparent?: boolean
opacity?: number
+ side?: THREE.Side
} & ({ texture: THREE.Texture; url?: never } | { texture?: never; url: string }) // {texture: THREE.Texture} XOR {url: string}
type ImageMaterialType = JSX.IntrinsicElements['shaderMaterial'] & {
scale?: number[]
imageBounds?: number[]
+ radius?: number
+ resolution?: number
color?: Color
map: THREE.Texture
zoom?: number
@@ -33,22 +38,37 @@ declare global {
}
}
-const ImageMaterialImpl = shaderMaterial(
- { color: new THREE.Color('white'), scale: [1, 1], imageBounds: [1, 1], map: null, zoom: 1, grayscale: 0, opacity: 1 },
+const ImageMaterialImpl = /* @__PURE__ */ shaderMaterial(
+ {
+ color: /* @__PURE__ */ new THREE.Color('white'),
+ scale: /* @__PURE__ */ new THREE.Vector2(1, 1),
+ imageBounds: /* @__PURE__ */ new THREE.Vector2(1, 1),
+ resolution: 1024,
+ map: null,
+ zoom: 1,
+ radius: 0,
+ grayscale: 0,
+ opacity: 1,
+ },
/* glsl */ `
varying vec2 vUv;
+ varying vec2 vPos;
void main() {
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.);
vUv = uv;
+ vPos = position.xy;
}
`,
/* glsl */ `
// mostly from https://gist.github.com/statico/df64c5d167362ecf7b34fca0b1459a44
varying vec2 vUv;
+ varying vec2 vPos;
uniform vec2 scale;
uniform vec2 imageBounds;
+ uniform float resolution;
uniform vec3 color;
uniform sampler2D map;
+ uniform float radius;
uniform float zoom;
uniform float grayscale;
uniform float opacity;
@@ -59,6 +79,14 @@ const ImageMaterialImpl = shaderMaterial(
vec2 aspect(vec2 size) {
return size / min(size.x, size.y);
}
+
+ const float PI = 3.14159265;
+
+ // from https://iquilezles.org/articles/distfunctions
+ float udRoundBox( vec2 p, vec2 b, float r ) {
+ return length(max(abs(p)-b+r,0.0))-r;
+ }
+
void main() {
vec2 s = aspect(scale);
vec2 i = aspect(imageBounds);
@@ -68,15 +96,20 @@ const ImageMaterialImpl = shaderMaterial(
vec2 offset = (rs < ri ? vec2((new.x - s.x) / 2.0, 0.0) : vec2(0.0, (new.y - s.y) / 2.0)) / new;
vec2 uv = vUv * s / new + offset;
vec2 zUv = (uv - vec2(0.5, 0.5)) / zoom + vec2(0.5, 0.5);
- gl_FragColor = toGrayscale(texture2D(map, zUv) * vec4(color, opacity), grayscale);
+
+ vec2 res = vec2(scale * resolution);
+ vec2 halfRes = 0.5 * res;
+ float b = udRoundBox(vUv.xy * res - halfRes, halfRes, resolution * radius);
+ vec3 a = mix(vec3(1.0,0.0,0.0), vec3(0.0,0.0,0.0), smoothstep(0.0, 1.0, b));
+ gl_FragColor = toGrayscale(texture2D(map, zUv) * vec4(color, opacity * a), grayscale);
#include
- #include <${parseInt(THREE.REVISION.replace(/\D+/g, '')) >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
+ #include <${version >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
}
`
)
-const ImageBase: ForwardRefComponent, THREE.Mesh> = React.forwardRef(
+const ImageBase: ForwardRefComponent, THREE.Mesh> = /* @__PURE__ */ React.forwardRef(
(
{
children,
@@ -86,16 +119,35 @@ const ImageBase: ForwardRefComponent, THREE.Mesh> = Reac
zoom = 1,
grayscale = 0,
opacity = 1,
+ radius = 0,
texture,
toneMapped,
transparent,
+ side,
...props
}: Omit,
- ref: React.ForwardedRef
+ fref: React.ForwardedRef
) => {
extend({ ImageMaterial: ImageMaterialImpl })
+ const ref = React.useRef(null!)
+ const size = useThree((state) => state.size)
const planeBounds = Array.isArray(scale) ? [scale[0], scale[1]] : [scale, scale]
const imageBounds = [texture!.image.width, texture!.image.height]
+ const resolution = Math.max(size.width, size.height)
+ React.useImperativeHandle(fref, () => ref.current, [])
+ React.useLayoutEffect(() => {
+ // Support arbitrary plane geometries (for instance with rounded corners)
+ // @ts-ignore
+ if (ref.current.geometry.parameters) {
+ // @ts-ignore
+ ref.current.material.scale.set(
+ // @ts-ignore
+ planeBounds[0] * ref.current.geometry.parameters.width,
+ // @ts-ignore
+ planeBounds[1] * ref.current.geometry.parameters.height
+ )
+ }
+ }, [])
return (
@@ -107,8 +159,12 @@ const ImageBase: ForwardRefComponent, THREE.Mesh> = Reac
opacity={opacity}
scale={planeBounds}
imageBounds={imageBounds}
+ resolution={resolution}
+ radius={radius}
toneMapped={toneMapped}
transparent={transparent}
+ side={side}
+ key={ImageMaterialImpl.key}
/>
{children}
@@ -116,23 +172,24 @@ const ImageBase: ForwardRefComponent, THREE.Mesh> = Reac
}
)
-const ImageWithUrl: ForwardRefComponent = React.forwardRef(
+const ImageWithUrl: ForwardRefComponent = /* @__PURE__ */ React.forwardRef(
({ url, ...props }: ImageProps, ref: React.ForwardedRef) => {
const texture = useTexture(url!)
return
}
)
-const ImageWithTexture: ForwardRefComponent = React.forwardRef(
+const ImageWithTexture: ForwardRefComponent = /* @__PURE__ */ React.forwardRef(
({ url: _url, ...props }: ImageProps, ref: React.ForwardedRef) => {
return
}
)
-export const Image: ForwardRefComponent = React.forwardRef(
- (props, ref) => {
- if (props.url) return
- else if (props.texture) return
- else throw new Error(' requires a url or texture')
- }
-)
+export const Image: ForwardRefComponent = /* @__PURE__ */ React.forwardRef<
+ THREE.Mesh,
+ ImageProps
+>((props, ref) => {
+ if (props.url) return
+ else if (props.texture) return
+ else throw new Error(' requires a url or texture')
+})
diff --git a/src/core/Instances.tsx b/src/core/Instances.tsx
index 9515b2046..9a7817cbc 100644
--- a/src/core/Instances.tsx
+++ b/src/core/Instances.tsx
@@ -4,6 +4,7 @@ import { ReactThreeFiber, extend, useFrame } from '@react-three/fiber'
import mergeRefs from 'react-merge-refs'
import Composer from 'react-composer'
import { ForwardRefComponent } from '../helpers/ts-utils'
+import { setUpdateRange } from '../helpers/deprecated'
declare global {
namespace JSX {
@@ -33,10 +34,18 @@ type InstancedMesh = Omit()
+function isFunctionChild(
+ value: any
+): value is (
+ props: React.ForwardRefExoticComponent & React.RefAttributes>
+) => React.ReactNode {
+ return typeof value === 'function'
+}
+
+const _instanceLocalMatrix = /* @__PURE__ */ new THREE.Matrix4()
+const _instanceWorldMatrix = /* @__PURE__ */ new THREE.Matrix4()
+const _instanceIntersects: THREE.Intersection[] = []
+const _mesh = /* @__PURE__ */ new THREE.Mesh()
class PositionMesh extends THREE.Group {
color: THREE.Color
@@ -84,15 +93,15 @@ class PositionMesh extends THREE.Group {
}
}
-const globalContext = /*@__PURE__*/ React.createContext(null!)
-const parentMatrix = /*@__PURE__*/ new THREE.Matrix4()
-const instanceMatrix = /*@__PURE__*/ new THREE.Matrix4()
-const tempMatrix = /*@__PURE__*/ new THREE.Matrix4()
-const translation = /*@__PURE__*/ new THREE.Vector3()
-const rotation = /*@__PURE__*/ new THREE.Quaternion()
-const scale = /*@__PURE__*/ new THREE.Vector3()
+const globalContext = /* @__PURE__ */ React.createContext(null!)
+const parentMatrix = /* @__PURE__ */ new THREE.Matrix4()
+const instanceMatrix = /* @__PURE__ */ new THREE.Matrix4()
+const tempMatrix = /* @__PURE__ */ new THREE.Matrix4()
+const translation = /* @__PURE__ */ new THREE.Vector3()
+const rotation = /* @__PURE__ */ new THREE.Quaternion()
+const scale = /* @__PURE__ */ new THREE.Vector3()
-export const Instance = React.forwardRef(({ context, children, ...props }: InstanceProps, ref) => {
+export const Instance = /* @__PURE__ */ React.forwardRef(({ context, children, ...props }: InstanceProps, ref) => {
React.useMemo(() => extend({ PositionMesh }), [])
const group = React.useRef()
const { subscribe, getParent } = React.useContext(context || globalContext)
@@ -104,7 +113,7 @@ export const Instance = React.forwardRef(({ context, children, ...props }: Insta
)
})
-export const Instances: ForwardRefComponent = React.forwardRef<
+export const Instances: ForwardRefComponent = /* @__PURE__ */ React.forwardRef<
InstancedMesh,
InstancesProps
>(({ children, range, limit = 1000, frames = Infinity, ...props }, ref) => {
@@ -129,18 +138,18 @@ export const Instances: ForwardRefComponent = Rea
parentRef.current.instanceMatrix.needsUpdate = true
})
+ let iterations = 0
let count = 0
- let updateRange = 0
useFrame(() => {
- if (frames === Infinity || count < frames) {
+ if (frames === Infinity || iterations < frames) {
parentRef.current.updateMatrix()
parentRef.current.updateMatrixWorld()
parentMatrix.copy(parentRef.current.matrixWorld).invert()
- updateRange = Math.min(limit, range !== undefined ? range : limit, instances.length)
- parentRef.current.count = updateRange
- parentRef.current.instanceMatrix.updateRange.count = updateRange * 16
- parentRef.current.instanceColor.updateRange.count = updateRange * 3
+ count = Math.min(limit, range !== undefined ? range : limit, instances.length)
+ parentRef.current.count = count
+ setUpdateRange(parentRef.current.instanceMatrix, { offset: 0, count: count * 16 })
+ setUpdateRange(parentRef.current.instanceColor, { offset: 0, count: count * 3 })
for (let i = 0; i < instances.length; i++) {
const instance = instances[i].current
@@ -153,7 +162,7 @@ export const Instances: ForwardRefComponent = Rea
instance.color.toArray(colors, i * 3)
parentRef.current.instanceColor.needsUpdate = true
}
- count++
+ iterations++
}
})
@@ -191,7 +200,7 @@ export const Instances: ForwardRefComponent = Rea
itemSize={3}
usage={THREE.DynamicDrawUsage}
/>
- {typeof children === 'function' ? (
+ {isFunctionChild(children) ? (
{children(instance)}
) : (
{children}
@@ -205,30 +214,29 @@ export interface MergedProps extends InstancesProps {
children: React.ReactNode
}
-export const Merged: ForwardRefComponent = React.forwardRef(function Merged(
- { meshes, children, ...props },
- ref
-) {
- const isArray = Array.isArray(meshes)
- // Filter out meshes from collections, which may contain non-meshes
- if (!isArray) for (const key of Object.keys(meshes)) if (!meshes[key].isMesh) delete meshes[key]
- return (
-
- (
-
- ))}
- >
- {(args) =>
- isArray
- ? children(...args)
- : children(
- Object.keys(meshes)
- .filter((key) => meshes[key].isMesh)
- .reduce((acc, key, i) => ({ ...acc, [key]: args[i] }), {})
- )
- }
-
-
- )
-})
+export const Merged: ForwardRefComponent = /* @__PURE__ */ React.forwardRef(
+ function Merged({ meshes, children, ...props }, ref) {
+ const isArray = Array.isArray(meshes)
+ // Filter out meshes from collections, which may contain non-meshes
+ if (!isArray) for (const key of Object.keys(meshes)) if (!meshes[key].isMesh) delete meshes[key]
+ return (
+
+ (
+
+ ))}
+ >
+ {(args) =>
+ isArray
+ ? children(...args)
+ : children(
+ Object.keys(meshes)
+ .filter((key) => meshes[key].isMesh)
+ .reduce((acc, key, i) => ({ ...acc, [key]: args[i] }), {})
+ )
+ }
+
+
+ )
+ }
+)
diff --git a/src/core/Lightformer.tsx b/src/core/Lightformer.tsx
index eb61e85ad..178d51986 100644
--- a/src/core/Lightformer.tsx
+++ b/src/core/Lightformer.tsx
@@ -15,7 +15,7 @@ export type LightProps = JSX.IntrinsicElements['mesh'] & {
target?: [number, number, number] | THREE.Vector3
}
-export const Lightformer: ForwardRefComponent = React.forwardRef(
+export const Lightformer: ForwardRefComponent = /* @__PURE__ */ React.forwardRef(
(
{
args,
diff --git a/src/core/Line.tsx b/src/core/Line.tsx
index 99da1ee65..965c69dbb 100644
--- a/src/core/Line.tsx
+++ b/src/core/Line.tsx
@@ -1,5 +1,5 @@
import * as React from 'react'
-import { Vector2, Vector3, Color, ColorRepresentation } from 'three'
+import { Vector2, Vector3, Vector4, Color, ColorRepresentation } from 'three'
import { ReactThreeFiber, useThree } from '@react-three/fiber'
import {
LineGeometry,
@@ -13,7 +13,7 @@ import { ForwardRefComponent } from '../helpers/ts-utils'
export type LineProps = {
points: Array
- vertexColors?: Array
+ vertexColors?: Array
lineWidth?: number
segments?: boolean
} & Omit &
@@ -22,18 +22,19 @@ export type LineProps = {
color?: ColorRepresentation
}
-export const Line: ForwardRefComponent = React.forwardRef<
+export const Line: ForwardRefComponent = /* @__PURE__ */ React.forwardRef<
Line2 | LineSegments2,
LineProps
>(function Line({ points, color = 'black', vertexColors, linewidth, lineWidth, segments, dashed, ...rest }, ref) {
const size = useThree((state) => state.size)
const line2 = React.useMemo(() => (segments ? new LineSegments2() : new Line2()), [segments])
const [lineMaterial] = React.useState(() => new LineMaterial())
+ const itemSize = (vertexColors?.[0] as number[] | undefined)?.length === 4 ? 4 : 3
const lineGeom = React.useMemo(() => {
const geom = segments ? new LineSegmentsGeometry() : new LineGeometry()
const pValues = points.map((p) => {
const isArray = Array.isArray(p)
- return p instanceof Vector3
+ return p instanceof Vector3 || p instanceof Vector4
? [p.x, p.y, p.z]
: p instanceof Vector2
? [p.x, p.y, 0]
@@ -48,11 +49,11 @@ export const Line: ForwardRefComponent = React
if (vertexColors) {
const cValues = vertexColors.map((c) => (c instanceof Color ? c.toArray() : c))
- geom.setColors(cValues.flat())
+ geom.setColors(cValues.flat(), itemSize)
}
return geom
- }, [points, segments, vertexColors])
+ }, [points, segments, vertexColors, itemSize])
React.useLayoutEffect(() => {
line2.computeLineDistances()
@@ -83,6 +84,7 @@ export const Line: ForwardRefComponent = React
resolution={[size.width, size.height]}
linewidth={linewidth ?? lineWidth}
dashed={dashed}
+ transparent={itemSize === 4}
{...rest}
/>
diff --git a/src/core/MapControls.tsx b/src/core/MapControls.tsx
index 00ffc7ba3..83f4f8c1e 100644
--- a/src/core/MapControls.tsx
+++ b/src/core/MapControls.tsx
@@ -17,7 +17,7 @@ export type MapControlsProps = ReactThreeFiber.Overwrite<
}
>
-export const MapControls: ForwardRefComponent = React.forwardRef<
+export const MapControls: ForwardRefComponent = /* @__PURE__ */ React.forwardRef<
MapControlsImpl,
MapControlsProps
>((props = { enableDamping: true }, ref) => {
diff --git a/src/core/MarchingCubes.tsx b/src/core/MarchingCubes.tsx
index 6fe29ea27..27f661d38 100644
--- a/src/core/MarchingCubes.tsx
+++ b/src/core/MarchingCubes.tsx
@@ -10,7 +10,7 @@ type Api = {
getParent: () => React.MutableRefObject
}
-const globalContext = React.createContext(null!)
+const globalContext = /* @__PURE__ */ React.createContext(null!)
export type MarchingCubesProps = {
resolution?: number
@@ -19,39 +19,41 @@ export type MarchingCubesProps = {
enableColors?: boolean
} & JSX.IntrinsicElements['group']
-export const MarchingCubes: ForwardRefComponent = React.forwardRef(
- (
- {
- resolution = 28,
- maxPolyCount = 10000,
- enableUvs = false,
- enableColors = false,
- children,
- ...props
- }: MarchingCubesProps,
- ref
- ) => {
- const marchingCubesRef = React.useRef(null!)
- const marchingCubes = React.useMemo(
- () => new MarchingCubesImpl(resolution, null as unknown as THREE.Material, enableUvs, enableColors, maxPolyCount),
- [resolution, maxPolyCount, enableUvs, enableColors]
- )
- const api = React.useMemo(() => ({ getParent: () => marchingCubesRef }), [])
+export const MarchingCubes: ForwardRefComponent =
+ /* @__PURE__ */ React.forwardRef(
+ (
+ {
+ resolution = 28,
+ maxPolyCount = 10000,
+ enableUvs = false,
+ enableColors = false,
+ children,
+ ...props
+ }: MarchingCubesProps,
+ ref
+ ) => {
+ const marchingCubesRef = React.useRef(null!)
+ const marchingCubes = React.useMemo(
+ () =>
+ new MarchingCubesImpl(resolution, null as unknown as THREE.Material, enableUvs, enableColors, maxPolyCount),
+ [resolution, maxPolyCount, enableUvs, enableColors]
+ )
+ const api = React.useMemo(() => ({ getParent: () => marchingCubesRef }), [])
- useFrame(() => {
- marchingCubes.update()
- marchingCubes.reset()
- }, -1) // To make sure the reset runs before the balls or planes are added
+ useFrame(() => {
+ marchingCubes.update()
+ marchingCubes.reset()
+ }, -1) // To make sure the reset runs before the balls or planes are added
- return (
- <>
-
- {children}
-
- >
- )
- }
-)
+ return (
+ <>
+
+ {children}
+
+ >
+ )
+ }
+ )
type MarchingCubeProps = {
strength?: number
@@ -59,7 +61,7 @@ type MarchingCubeProps = {
color?: Color
} & JSX.IntrinsicElements['group']
-export const MarchingCube: ForwardRefComponent = React.forwardRef(
+export const MarchingCube: ForwardRefComponent = /* @__PURE__ */ React.forwardRef(
({ strength = 0.5, subtract = 12, color, ...props }: MarchingCubeProps, ref) => {
const { getParent } = React.useContext(globalContext)
const parentRef = React.useMemo(() => getParent(), [getParent])
@@ -80,7 +82,7 @@ type MarchingPlaneProps = {
subtract?: number
} & JSX.IntrinsicElements['group']
-export const MarchingPlane: ForwardRefComponent = React.forwardRef(
+export const MarchingPlane: ForwardRefComponent = /* @__PURE__ */ React.forwardRef(
({ planeType: _planeType = 'x', strength = 0.5, subtract = 12, ...props }: MarchingPlaneProps, ref) => {
const { getParent } = React.useContext(globalContext)
const parentRef = React.useMemo(() => getParent(), [getParent])
diff --git a/src/core/Mask.tsx b/src/core/Mask.tsx
index 2ef248e83..4e04769ba 100644
--- a/src/core/Mask.tsx
+++ b/src/core/Mask.tsx
@@ -11,7 +11,7 @@ type Props = Omit & {
depthWrite?: boolean
}
-export const Mask: ForwardRefComponent = React.forwardRef(
+export const Mask: ForwardRefComponent = /* @__PURE__ */ React.forwardRef(
({ id = 1, colorWrite = false, depthWrite = false, ...props }: Props, fref: React.ForwardedRef) => {
const ref = React.useRef(null!)
const spread = React.useMemo(
diff --git a/src/core/MeshDiscardMaterial.tsx b/src/core/MeshDiscardMaterial.tsx
index 083cc1aa3..fcd291771 100644
--- a/src/core/MeshDiscardMaterial.tsx
+++ b/src/core/MeshDiscardMaterial.tsx
@@ -12,7 +12,9 @@ declare global {
}
export const MeshDiscardMaterial: ForwardRefComponent =
- React.forwardRef((props: JSX.IntrinsicElements['shaderMaterial'], fref: React.ForwardedRef) => {
- extend({ DiscardMaterialImpl })
- return
- })
+ /* @__PURE__ */ React.forwardRef(
+ (props: JSX.IntrinsicElements['shaderMaterial'], fref: React.ForwardedRef) => {
+ extend({ DiscardMaterialImpl })
+ return
+ }
+ )
diff --git a/src/core/MeshDistortMaterial.tsx b/src/core/MeshDistortMaterial.tsx
index 7ec81a113..29f180c94 100644
--- a/src/core/MeshDistortMaterial.tsx
+++ b/src/core/MeshDistortMaterial.tsx
@@ -1,5 +1,5 @@
import * as React from 'react'
-import { MeshPhysicalMaterial, MeshPhysicalMaterialParameters, Shader } from 'three'
+import { IUniform, MeshPhysicalMaterial, MeshPhysicalMaterialParameters } from 'three'
import { useFrame } from '@react-three/fiber'
// eslint-disable-next-line
// @ts-ignore
@@ -42,7 +42,8 @@ class DistortMaterialImpl extends MeshPhysicalMaterial {
this._radius = { value: 1 }
}
- onBeforeCompile(shader: Shader) {
+ // FIXME Use `THREE.WebGLProgramParametersWithUniforms` type when able to target @types/three@0.160.0
+ onBeforeCompile(shader: { vertexShader: string; uniforms: { [uniform: string]: IUniform } }) {
shader.uniforms.time = this._time
shader.uniforms.radius = this._radius
shader.uniforms.distort = this._distort
@@ -89,7 +90,7 @@ class DistortMaterialImpl extends MeshPhysicalMaterial {
}
}
-export const MeshDistortMaterial: ForwardRefComponent = React.forwardRef(
+export const MeshDistortMaterial: ForwardRefComponent = /* @__PURE__ */ React.forwardRef(
({ speed = 1, ...props }: Props, ref) => {
const [material] = React.useState(() => new DistortMaterialImpl())
useFrame((state) => material && (material.time = state.clock.getElapsedTime() * speed))
diff --git a/src/core/MeshPortalMaterial.tsx b/src/core/MeshPortalMaterial.tsx
index af76dce61..12f3806e5 100644
--- a/src/core/MeshPortalMaterial.tsx
+++ b/src/core/MeshPortalMaterial.tsx
@@ -11,15 +11,16 @@ import { useFBO } from './useFBO'
import { RenderTexture } from './RenderTexture'
import { shaderMaterial } from './shaderMaterial'
import { FullScreenQuad } from 'three-stdlib'
+import { version } from '../helpers/constants'
-const PortalMaterialImpl = shaderMaterial(
+const PortalMaterialImpl = /* @__PURE__ */ shaderMaterial(
{
blur: 0,
map: null,
sdf: null,
blend: 0,
size: 0,
- resolution: new THREE.Vector2(),
+ resolution: /* @__PURE__ */ new THREE.Vector2(),
},
`varying vec2 vUv;
void main() {
@@ -42,7 +43,7 @@ const PortalMaterialImpl = shaderMaterial(
float alpha = 1.0 - smoothstep(0.0, 1.0, clamp(d/k + 1.0, 0.0, 1.0));
gl_FragColor = vec4(t.rgb, blur == 0.0 ? t.a : t.a * alpha);
#include
- #include <${parseInt(THREE.REVISION.replace(/\D+/g, '')) >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
+ #include <${version >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
}`
)
@@ -81,7 +82,7 @@ export type PortalProps = JSX.IntrinsicElements['shaderMaterial'] & {
events?: boolean
}
-export const MeshPortalMaterial = React.forwardRef(
+export const MeshPortalMaterial = /* @__PURE__ */ React.forwardRef(
(
{
children,
@@ -180,11 +181,10 @@ export const MeshPortalMaterial = React.forwardRef(
return (
@@ -261,9 +261,8 @@ function ManagePortalScene({
vec4 ta = texture2D(a, vUv);
vec4 tb = texture2D(b, vUv);
gl_FragColor = mix(tb, ta, blend);
- #include <${
- parseInt(THREE.REVISION.replace(/\D+/g, '')) >= 154 ? 'colorspace_fragment' : 'encodings_fragment'
- }>
+ #include
+ #include <${version >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
}`,
})
)
diff --git a/src/core/MeshReflectorMaterial.tsx b/src/core/MeshReflectorMaterial.tsx
index 001710193..6bec45cfd 100644
--- a/src/core/MeshReflectorMaterial.tsx
+++ b/src/core/MeshReflectorMaterial.tsx
@@ -47,135 +47,158 @@ declare global {
}
}
-extend({ MeshReflectorMaterialImpl })
-
-export const MeshReflectorMaterial: ForwardRefComponent = React.forwardRef<
- MeshReflectorMaterialImpl,
- Props
->(
- (
- {
- mixBlur = 0,
- mixStrength = 1,
- resolution = 256,
- blur = [0, 0],
- minDepthThreshold = 0.9,
- maxDepthThreshold = 1,
- depthScale = 0,
- depthToBlurRatioBias = 0.25,
- mirror = 0,
- distortion = 1,
- mixContrast = 1,
- distortionMap,
- reflectorOffset = 0,
- ...props
- },
- ref
- ) => {
- const gl = useThree(({ gl }) => gl)
- const camera = useThree(({ camera }) => camera)
- const scene = useThree(({ scene }) => scene)
- blur = Array.isArray(blur) ? blur : [blur, blur]
- const hasBlur = blur[0] + blur[1] > 0
- const materialRef = React.useRef(null!)
- const [reflectorPlane] = React.useState(() => new Plane())
- const [normal] = React.useState(() => new Vector3())
- const [reflectorWorldPosition] = React.useState(() => new Vector3())
- const [cameraWorldPosition] = React.useState(() => new Vector3())
- const [rotationMatrix] = React.useState(() => new Matrix4())
- const [lookAtPosition] = React.useState(() => new Vector3(0, 0, -1))
- const [clipPlane] = React.useState(() => new Vector4())
- const [view] = React.useState(() => new Vector3())
- const [target] = React.useState(() => new Vector3())
- const [q] = React.useState(() => new Vector4())
- const [textureMatrix] = React.useState(() => new Matrix4())
- const [virtualCamera] = React.useState(() => new PerspectiveCamera())
+export const MeshReflectorMaterial: ForwardRefComponent =
+ /* @__PURE__ */ React.forwardRef(
+ (
+ {
+ mixBlur = 0,
+ mixStrength = 1,
+ resolution = 256,
+ blur = [0, 0],
+ minDepthThreshold = 0.9,
+ maxDepthThreshold = 1,
+ depthScale = 0,
+ depthToBlurRatioBias = 0.25,
+ mirror = 0,
+ distortion = 1,
+ mixContrast = 1,
+ distortionMap,
+ reflectorOffset = 0,
+ ...props
+ },
+ ref
+ ) => {
+ extend({ MeshReflectorMaterialImpl })
+ const gl = useThree(({ gl }) => gl)
+ const camera = useThree(({ camera }) => camera)
+ const scene = useThree(({ scene }) => scene)
+ blur = Array.isArray(blur) ? blur : [blur, blur]
+ const hasBlur = blur[0] + blur[1] > 0
+ const materialRef = React.useRef(null!)
+ const [reflectorPlane] = React.useState(() => new Plane())
+ const [normal] = React.useState(() => new Vector3())
+ const [reflectorWorldPosition] = React.useState(() => new Vector3())
+ const [cameraWorldPosition] = React.useState(() => new Vector3())
+ const [rotationMatrix] = React.useState(() => new Matrix4())
+ const [lookAtPosition] = React.useState(() => new Vector3(0, 0, -1))
+ const [clipPlane] = React.useState(() => new Vector4())
+ const [view] = React.useState(() => new Vector3())
+ const [target] = React.useState(() => new Vector3())
+ const [q] = React.useState(() => new Vector4())
+ const [textureMatrix] = React.useState(() => new Matrix4())
+ const [virtualCamera] = React.useState(() => new PerspectiveCamera())
- const beforeRender = React.useCallback(() => {
- // TODO: As of R3f 7-8 this should be __r3f.parent
- const parent = (materialRef.current as any).parent || (materialRef.current as any)?.__r3f.parent
- if (!parent) return
+ const beforeRender = React.useCallback(() => {
+ // TODO: As of R3f 7-8 this should be __r3f.parent
+ const parent = (materialRef.current as any).parent || (materialRef.current as any)?.__r3f.parent
+ if (!parent) return
- reflectorWorldPosition.setFromMatrixPosition(parent.matrixWorld)
- cameraWorldPosition.setFromMatrixPosition(camera.matrixWorld)
- rotationMatrix.extractRotation(parent.matrixWorld)
- normal.set(0, 0, 1)
- normal.applyMatrix4(rotationMatrix)
- reflectorWorldPosition.addScaledVector(normal, reflectorOffset)
- view.subVectors(reflectorWorldPosition, cameraWorldPosition)
- // Avoid rendering when reflector is facing away
- if (view.dot(normal) > 0) return
- view.reflect(normal).negate()
- view.add(reflectorWorldPosition)
- rotationMatrix.extractRotation(camera.matrixWorld)
- lookAtPosition.set(0, 0, -1)
- lookAtPosition.applyMatrix4(rotationMatrix)
- lookAtPosition.add(cameraWorldPosition)
- target.subVectors(reflectorWorldPosition, lookAtPosition)
- target.reflect(normal).negate()
- target.add(reflectorWorldPosition)
- virtualCamera.position.copy(view)
- virtualCamera.up.set(0, 1, 0)
- virtualCamera.up.applyMatrix4(rotationMatrix)
- virtualCamera.up.reflect(normal)
- virtualCamera.lookAt(target)
- virtualCamera.far = camera.far // Used in WebGLBackground
- virtualCamera.updateMatrixWorld()
- virtualCamera.projectionMatrix.copy(camera.projectionMatrix)
- // Update the texture matrix
- textureMatrix.set(0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0)
- textureMatrix.multiply(virtualCamera.projectionMatrix)
- textureMatrix.multiply(virtualCamera.matrixWorldInverse)
- textureMatrix.multiply(parent.matrixWorld)
- // Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html
- // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
- reflectorPlane.setFromNormalAndCoplanarPoint(normal, reflectorWorldPosition)
- reflectorPlane.applyMatrix4(virtualCamera.matrixWorldInverse)
- clipPlane.set(reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant)
- const projectionMatrix = virtualCamera.projectionMatrix
- q.x = (Math.sign(clipPlane.x) + projectionMatrix.elements[8]) / projectionMatrix.elements[0]
- q.y = (Math.sign(clipPlane.y) + projectionMatrix.elements[9]) / projectionMatrix.elements[5]
- q.z = -1.0
- q.w = (1.0 + projectionMatrix.elements[10]) / projectionMatrix.elements[14]
- // Calculate the scaled plane vector
- clipPlane.multiplyScalar(2.0 / clipPlane.dot(q))
- // Replacing the third row of the projection matrix
- projectionMatrix.elements[2] = clipPlane.x
- projectionMatrix.elements[6] = clipPlane.y
- projectionMatrix.elements[10] = clipPlane.z + 1.0
- projectionMatrix.elements[14] = clipPlane.w
- }, [camera, reflectorOffset])
+ reflectorWorldPosition.setFromMatrixPosition(parent.matrixWorld)
+ cameraWorldPosition.setFromMatrixPosition(camera.matrixWorld)
+ rotationMatrix.extractRotation(parent.matrixWorld)
+ normal.set(0, 0, 1)
+ normal.applyMatrix4(rotationMatrix)
+ reflectorWorldPosition.addScaledVector(normal, reflectorOffset)
+ view.subVectors(reflectorWorldPosition, cameraWorldPosition)
+ // Avoid rendering when reflector is facing away
+ if (view.dot(normal) > 0) return
+ view.reflect(normal).negate()
+ view.add(reflectorWorldPosition)
+ rotationMatrix.extractRotation(camera.matrixWorld)
+ lookAtPosition.set(0, 0, -1)
+ lookAtPosition.applyMatrix4(rotationMatrix)
+ lookAtPosition.add(cameraWorldPosition)
+ target.subVectors(reflectorWorldPosition, lookAtPosition)
+ target.reflect(normal).negate()
+ target.add(reflectorWorldPosition)
+ virtualCamera.position.copy(view)
+ virtualCamera.up.set(0, 1, 0)
+ virtualCamera.up.applyMatrix4(rotationMatrix)
+ virtualCamera.up.reflect(normal)
+ virtualCamera.lookAt(target)
+ virtualCamera.far = camera.far // Used in WebGLBackground
+ virtualCamera.updateMatrixWorld()
+ virtualCamera.projectionMatrix.copy(camera.projectionMatrix)
+ // Update the texture matrix
+ textureMatrix.set(0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0)
+ textureMatrix.multiply(virtualCamera.projectionMatrix)
+ textureMatrix.multiply(virtualCamera.matrixWorldInverse)
+ textureMatrix.multiply(parent.matrixWorld)
+ // Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html
+ // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
+ reflectorPlane.setFromNormalAndCoplanarPoint(normal, reflectorWorldPosition)
+ reflectorPlane.applyMatrix4(virtualCamera.matrixWorldInverse)
+ clipPlane.set(
+ reflectorPlane.normal.x,
+ reflectorPlane.normal.y,
+ reflectorPlane.normal.z,
+ reflectorPlane.constant
+ )
+ const projectionMatrix = virtualCamera.projectionMatrix
+ q.x = (Math.sign(clipPlane.x) + projectionMatrix.elements[8]) / projectionMatrix.elements[0]
+ q.y = (Math.sign(clipPlane.y) + projectionMatrix.elements[9]) / projectionMatrix.elements[5]
+ q.z = -1.0
+ q.w = (1.0 + projectionMatrix.elements[10]) / projectionMatrix.elements[14]
+ // Calculate the scaled plane vector
+ clipPlane.multiplyScalar(2.0 / clipPlane.dot(q))
+ // Replacing the third row of the projection matrix
+ projectionMatrix.elements[2] = clipPlane.x
+ projectionMatrix.elements[6] = clipPlane.y
+ projectionMatrix.elements[10] = clipPlane.z + 1.0
+ projectionMatrix.elements[14] = clipPlane.w
+ }, [camera, reflectorOffset])
- const [fbo1, fbo2, blurpass, reflectorProps] = React.useMemo(() => {
- const parameters = {
- minFilter: LinearFilter,
- magFilter: LinearFilter,
- type: HalfFloatType,
- }
- const fbo1 = new WebGLRenderTarget(resolution, resolution, parameters)
- fbo1.depthBuffer = true
- fbo1.depthTexture = new DepthTexture(resolution, resolution)
- fbo1.depthTexture.format = DepthFormat
- fbo1.depthTexture.type = UnsignedShortType
- const fbo2 = new WebGLRenderTarget(resolution, resolution, parameters)
- const blurpass = new BlurPass({
+ const [fbo1, fbo2, blurpass, reflectorProps] = React.useMemo(() => {
+ const parameters = {
+ minFilter: LinearFilter,
+ magFilter: LinearFilter,
+ type: HalfFloatType,
+ }
+ const fbo1 = new WebGLRenderTarget(resolution, resolution, parameters)
+ fbo1.depthBuffer = true
+ fbo1.depthTexture = new DepthTexture(resolution, resolution)
+ fbo1.depthTexture.format = DepthFormat
+ fbo1.depthTexture.type = UnsignedShortType
+ const fbo2 = new WebGLRenderTarget(resolution, resolution, parameters)
+ const blurpass = new BlurPass({
+ gl,
+ resolution,
+ width: blur[0],
+ height: blur[1],
+ minDepthThreshold,
+ maxDepthThreshold,
+ depthScale,
+ depthToBlurRatioBias,
+ })
+ const reflectorProps = {
+ mirror,
+ textureMatrix,
+ mixBlur,
+ tDiffuse: fbo1.texture,
+ tDepth: fbo1.depthTexture,
+ tDiffuseBlur: fbo2.texture,
+ hasBlur,
+ mixStrength,
+ minDepthThreshold,
+ maxDepthThreshold,
+ depthScale,
+ depthToBlurRatioBias,
+ distortion,
+ distortionMap,
+ mixContrast,
+ 'defines-USE_BLUR': hasBlur ? '' : undefined,
+ 'defines-USE_DEPTH': depthScale > 0 ? '' : undefined,
+ 'defines-USE_DISTORTION': distortionMap ? '' : undefined,
+ }
+ return [fbo1, fbo2, blurpass, reflectorProps]
+ }, [
gl,
+ blur,
+ textureMatrix,
resolution,
- width: blur[0],
- height: blur[1],
- minDepthThreshold,
- maxDepthThreshold,
- depthScale,
- depthToBlurRatioBias,
- })
- const reflectorProps = {
mirror,
- textureMatrix,
- mixBlur,
- tDiffuse: fbo1.texture,
- tDepth: fbo1.depthTexture,
- tDiffuseBlur: fbo2.texture,
hasBlur,
+ mixBlur,
mixStrength,
minDepthThreshold,
maxDepthThreshold,
@@ -184,65 +207,44 @@ export const MeshReflectorMaterial: ForwardRefComponent 0 ? '' : undefined,
- 'defines-USE_DISTORTION': distortionMap ? '' : undefined,
- }
- return [fbo1, fbo2, blurpass, reflectorProps]
- }, [
- gl,
- blur,
- textureMatrix,
- resolution,
- mirror,
- hasBlur,
- mixBlur,
- mixStrength,
- minDepthThreshold,
- maxDepthThreshold,
- depthScale,
- depthToBlurRatioBias,
- distortion,
- distortionMap,
- mixContrast,
- ])
+ ])
- useFrame(() => {
- // TODO: As of R3f 7-8 this should be __r3f.parent
- const parent = (materialRef.current as any).parent || (materialRef.current as any)?.__r3f.parent
- if (!parent) return
+ useFrame(() => {
+ // TODO: As of R3f 7-8 this should be __r3f.parent
+ const parent = (materialRef.current as any).parent || (materialRef.current as any)?.__r3f.parent
+ if (!parent) return
- parent.visible = false
- const currentXrEnabled = gl.xr.enabled
- const currentShadowAutoUpdate = gl.shadowMap.autoUpdate
- beforeRender()
- gl.xr.enabled = false
- gl.shadowMap.autoUpdate = false
- gl.setRenderTarget(fbo1)
- gl.state.buffers.depth.setMask(true)
- if (!gl.autoClear) gl.clear()
- gl.render(scene, virtualCamera)
- if (hasBlur) blurpass.render(gl, fbo1, fbo2)
- gl.xr.enabled = currentXrEnabled
- gl.shadowMap.autoUpdate = currentShadowAutoUpdate
- parent.visible = true
- gl.setRenderTarget(null)
- })
+ parent.visible = false
+ const currentXrEnabled = gl.xr.enabled
+ const currentShadowAutoUpdate = gl.shadowMap.autoUpdate
+ beforeRender()
+ gl.xr.enabled = false
+ gl.shadowMap.autoUpdate = false
+ gl.setRenderTarget(fbo1)
+ gl.state.buffers.depth.setMask(true)
+ if (!gl.autoClear) gl.clear()
+ gl.render(scene, virtualCamera)
+ if (hasBlur) blurpass.render(gl, fbo1, fbo2)
+ gl.xr.enabled = currentXrEnabled
+ gl.shadowMap.autoUpdate = currentShadowAutoUpdate
+ parent.visible = true
+ gl.setRenderTarget(null)
+ })
- return (
-
- )
- }
-)
+ return (
+
+ )
+ }
+ )
diff --git a/src/core/MeshTransmissionMaterial.tsx b/src/core/MeshTransmissionMaterial.tsx
index e1702200e..971f43630 100644
--- a/src/core/MeshTransmissionMaterial.tsx
+++ b/src/core/MeshTransmissionMaterial.tsx
@@ -122,6 +122,10 @@ class MeshTransmissionMaterialImpl extends THREE.MeshPhysicalMaterial {
...this.uniforms,
}
+ // Fix for r153-r156 anisotropy chunks
+ // https://github.com/mrdoob/three.js/pull/26716
+ if ((this as any).anisotropy > 0) shader.defines.USE_ANISOTROPY = ''
+
// If the transmission sampler is active inject a flag
if (transmissionSampler) shader.defines.USE_SAMPLER = ''
// Otherwise we do use use .transmission and must therefore force USE_TRANSMISSION
@@ -372,7 +376,7 @@ class MeshTransmissionMaterialImpl extends THREE.MeshPhysicalMaterial {
export const MeshTransmissionMaterial: ForwardRefComponent<
MeshTransmissionMaterialProps,
JSX.IntrinsicElements['meshTransmissionMaterial']
-> = React.forwardRef(
+> = /* @__PURE__ */ React.forwardRef(
(
{
buffer,
@@ -454,7 +458,7 @@ export const MeshTransmissionMaterial: ForwardRefComponent<
= React.forwardRef(
+export const MeshWobbleMaterial: ForwardRefComponent = /* @__PURE__ */ React.forwardRef(
({ speed = 1, ...props }: Props, ref) => {
const [material] = React.useState(() => new WobbleMaterialImpl())
useFrame((state) => material && (material.time = state.clock.getElapsedTime() * speed))
diff --git a/src/core/MotionPathControls.tsx b/src/core/MotionPathControls.tsx
new file mode 100644
index 000000000..b1cb52349
--- /dev/null
+++ b/src/core/MotionPathControls.tsx
@@ -0,0 +1,184 @@
+/* eslint-disable prettier/prettier */
+import * as THREE from 'three'
+import * as React from 'react'
+import { useFrame, useThree } from '@react-three/fiber'
+import { easing, misc } from 'maath'
+
+type MotionPathProps = JSX.IntrinsicElements['group'] & {
+ /** An optional array of THREE curves */
+ curves?: THREE.Curve[]
+ /** Show debug helpers */
+ debug?: boolean
+ /** The target object that is moved, default: null (the default camera) */
+ object?: React.MutableRefObject
+ /** An object where the target looks towards, can also be a vector, default: null */
+ focus?: [x: number, y: number, z: number] | React.MutableRefObject
+ /** Position between 0 (start) and end (1), if this is not set useMotion().current must be used, default: null */
+ offset?: number
+ /** Optionally smooth the curve, default: false */
+ smooth?: boolean | number
+ /** Damping tolerance, default: 0.00001 */
+ eps?: number
+ /** Damping factor for movement along the curve, default: 0.1 */
+ damping?: number
+ /** Damping factor for lookAt, default: 0.1 */
+ focusDamping?: number
+ /** Damping maximum speed, default: Infinity */
+ maxSpeed?: number
+}
+
+type MotionState = {
+ /** The user-defined, mutable, current goal position along the curve, it may be >1 or <0 */
+ current: number
+ /** The combined curve */
+ path: THREE.CurvePath
+ /** The focus object */
+ focus: React.MutableRefObject | [x: number, y: number, z: number] | undefined
+ /** The target object that is moved along the curve */
+ object: React.MutableRefObject
+ /** The 0-1 normalised and damped current goal position along curve */
+ offset: number
+ /** The current point on the curve */
+ point: THREE.Vector3
+ /** The current tangent on the curve */
+ tangent: THREE.Vector3
+ /** The next point on the curve */
+ next: THREE.Vector3
+}
+
+const isObject3DRef = (ref: any): ref is React.MutableRefObject =>
+ ref?.current instanceof THREE.Object3D
+
+const context = /* @__PURE__ */ React.createContext(null!)
+
+export function useMotion() {
+ return React.useContext(context) as MotionState
+}
+
+function Debug({ points = 50 }: { points?: number }) {
+ const { path } = useMotion()
+ const [dots, setDots] = React.useState([])
+ const [material] = React.useState(() => new THREE.MeshBasicMaterial({ color: 'black' }))
+ const [geometry] = React.useState(() => new THREE.SphereGeometry(0.025, 16, 16))
+ const last = React.useRef[]>([])
+ React.useEffect(() => {
+ if (path.curves !== last.current) {
+ setDots(path.getPoints(points))
+ last.current = path.curves
+ }
+ })
+ return (
+ <>
+ {dots.map((item: { x: any; y: any; z: any }, index: any) => (
+
+ ))}
+ >
+ )
+}
+
+export const MotionPathControls = /* @__PURE__ */ React.forwardRef(
+ (
+ {
+ children,
+ curves = [],
+ object,
+ debug = false,
+ smooth = false,
+ focus,
+ offset = undefined,
+ eps = 0.00001,
+ damping = 0.1,
+ focusDamping = 0.1,
+ maxSpeed = Infinity,
+ ...props
+ }: MotionPathProps,
+ fref
+ ) => {
+ const { camera } = useThree()
+ const ref = React.useRef()
+ const [path] = React.useState(() => new THREE.CurvePath())
+
+ const pos = React.useRef(offset ?? 0)
+ const state = React.useMemo(
+ () => ({
+ focus,
+ object: object?.current instanceof THREE.Object3D ? object : { current: camera },
+ path,
+ current: pos.current,
+ offset: pos.current,
+ point: new THREE.Vector3(),
+ tangent: new THREE.Vector3(),
+ next: new THREE.Vector3(),
+ }),
+ [focus, object]
+ )
+
+ React.useLayoutEffect(() => {
+ path.curves = []
+ const _curves = curves.length > 0 ? curves : ref.current?.__r3f.objects
+ for (var i = 0; i < _curves.length; i++) path.add(_curves[i])
+
+ //Smoothen curve
+ if (smooth) {
+ const points = path.getPoints(typeof smooth === 'number' ? smooth : 1)
+ const catmull = new THREE.CatmullRomCurve3(points)
+ path.curves = [catmull]
+ }
+ path.updateArcLengths()
+ })
+
+ React.useImperativeHandle(fref, () => ref.current, [])
+
+ React.useLayoutEffect(() => {
+ // When offset changes, normalise pos to avoid overshoot spinning
+ pos.current = misc.repeat(pos.current, 1)
+ }, [offset])
+
+ let last = 0
+ const [vec] = React.useState(() => new THREE.Vector3())
+
+ useFrame((_state, delta) => {
+ last = state.offset
+ easing.damp(
+ pos,
+ 'current',
+ offset !== undefined ? offset : state.current,
+ damping,
+ delta,
+ maxSpeed,
+ undefined,
+ eps
+ )
+ state.offset = misc.repeat(pos.current, 1)
+
+ if (path.getCurveLengths().length > 0) {
+ path.getPointAt(state.offset, state.point)
+ path.getTangentAt(state.offset, state.tangent).normalize()
+ path.getPointAt(misc.repeat(pos.current - (last - state.offset), 1), state.next)
+ const target = object?.current instanceof THREE.Object3D ? object.current : camera
+ target.position.copy(state.point)
+ //@ts-ignore
+ if (focus) {
+ easing.dampLookAt(
+ target,
+ isObject3DRef(focus) ? focus.current.getWorldPosition(vec) : focus,
+ focusDamping,
+ delta,
+ maxSpeed,
+ undefined,
+ eps
+ )
+ }
+ }
+ })
+
+ return (
+