Skip to content

Commit 0641186

Browse files
feat: Create and export RoundedBoxGeometry (#2464)
* feat: Create and export RoundedBoxGeometry * fix: formatting, linting and test workflow --------- Co-authored-by: Faraz Shaikh <[email protected]>
1 parent 47255c9 commit 0641186

File tree

5 files changed

+101
-28
lines changed

5 files changed

+101
-28
lines changed

.storybook/stories/RoundedBox.stories.tsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Meta, StoryObj } from '@storybook/react-vite'
55
import { Setup } from '../Setup'
66
import { useTurntable } from '../useTurntable'
77

8-
import { RoundedBox } from '../../src'
8+
import { RoundedBox, RoundedBoxGeometry } from '../../src'
99

1010
export default {
1111
title: 'Shapes/RoundedBox',
@@ -20,6 +20,7 @@ export default {
2020
} satisfies Meta<typeof RoundedBox>
2121

2222
type Story = StoryObj<typeof RoundedBox>
23+
type GeometryStory = StoryObj<typeof RoundedBoxGeometry>
2324

2425
function RoundedBoxScene(props: React.ComponentProps<typeof RoundedBox>) {
2526
const ref = useTurntable<React.ComponentRef<typeof RoundedBox>>()
@@ -65,3 +66,30 @@ export const RoundedBoxSt2 = {
6566
render: (args) => <RoundedBoxScene2 {...args} />,
6667
name: 'Solid',
6768
} satisfies Story
69+
70+
function RoundedBoxGeometryScene(props: React.ComponentProps<typeof RoundedBoxGeometry>) {
71+
const ref = useTurntable<React.ComponentRef<typeof RoundedBox>>()
72+
73+
return (
74+
<>
75+
<spotLight position={[35, 35, 35]} intensity={2 * Math.PI} decay={0} />
76+
<mesh ref={ref}>
77+
<RoundedBoxGeometry {...props} />
78+
<meshPhongMaterial color="#f3f3f3" />
79+
</mesh>
80+
</>
81+
)
82+
}
83+
84+
export const RoundedBoxGeometrySt = {
85+
args: {
86+
args: [20, 20, 20],
87+
radius: 2,
88+
smoothness: 8,
89+
bevelSegments: 2,
90+
steps: 1,
91+
creaseAngle: 0.1,
92+
},
93+
render: (args) => <RoundedBoxGeometryScene {...args} />,
94+
name: 'From Geometry',
95+
} satisfies GeometryStory

docs/shapes/rounded-box.mdx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ A box buffer geometry with rounded corners, done with extrusion.
99
<RoundedBox
1010
args={[1, 1, 1]} // Width, height, depth. Default is [1, 1, 1]
1111
radius={0.05} // Radius of the rounded corners. Default is 0.05
12+
steps={1} // Extrusion steps. Default is 1
1213
smoothness={4} // The number of curve segments. Default is 4
1314
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.
1415
creaseAngle={0.4} // Smooth normals everywhere except faces that meet at an angle greater than the crease angle
@@ -17,3 +18,23 @@ A box buffer geometry with rounded corners, done with extrusion.
1718
<meshPhongMaterial color="#f3f3f3" wireframe />
1819
</RoundedBox>
1920
```
21+
22+
Geometry is also available. Useful for '@react-three/csg'
23+
24+
```jsx
25+
<mesh>
26+
<RoundedBoxGeometry
27+
args={[1, 1, 1]}
28+
radius={0.05}
29+
steps={1}
30+
smoothness={4}
31+
bevelSegments={4}
32+
creaseAngle={0.4}
33+
/>
34+
<meshPhongMaterial color="#f3f3f3" wireframe />
35+
</mesh>
36+
```
37+
38+
> **Tip:** If you animate `args` every frame, memoise the
39+
> `[width, height, depth]` tuple with `React.useMemo` to avoid replacing the
40+
> geometry each tick.

src/core/Caustics.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
// TODO: ESLint thinks Caustics is executed in a loop.
2-
/* eslint-disable react-hooks/rules-of-hooks */
3-
41
/** Author: @N8Programs https://github.com/N8python
52
* https://github.com/N8python/caustics
63
*/

src/core/Image.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ const ImageBase: ForwardRefComponent<Omit<ImageProps, 'url'>, THREE.Mesh> = /* @
145145
planeBounds[1] * ref.current.geometry.parameters.height
146146
)
147147
}
148-
/* eslint react-hooks/exhaustive-deps: 1 */
149148
}, [planeBounds[0], planeBounds[1]])
150149
return (
151150
<mesh ref={ref} scale={Array.isArray(scale) ? [...scale, 1] : scale} {...props}>

src/core/RoundedBox.tsx

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ export type RoundedBoxProps = {
2525
creaseAngle?: number
2626
} & Omit<ThreeElements['mesh'], 'ref' | 'args'>
2727

28+
export type RoundedBoxGeometryProps = Omit<RoundedBoxProps, 'children'> &
29+
Omit<ThreeElements['extrudeGeometry'], 'args' | 'ref'>
30+
2831
export const RoundedBox: ForwardRefComponent<RoundedBoxProps, Mesh> = /* @__PURE__ */ React.forwardRef<
2932
Mesh,
3033
RoundedBoxProps
@@ -41,32 +44,57 @@ export const RoundedBox: ForwardRefComponent<RoundedBoxProps, Mesh> = /* @__PURE
4144
},
4245
ref
4346
) {
44-
const shape = React.useMemo(() => createShape(width, height, radius), [width, height, radius])
45-
const params = React.useMemo(
46-
() => ({
47-
depth: depth - radius * 2,
48-
bevelEnabled: true,
49-
bevelSegments: bevelSegments * 2,
50-
steps,
51-
bevelSize: radius - eps,
52-
bevelThickness: radius,
53-
curveSegments: smoothness,
54-
}),
55-
[depth, radius, smoothness]
56-
)
57-
const geomRef = React.useRef<ExtrudeGeometry>(null!)
58-
59-
React.useLayoutEffect(() => {
60-
if (geomRef.current) {
61-
geomRef.current.center()
62-
toCreasedNormals(geomRef.current, creaseAngle)
63-
}
64-
}, [shape, params])
65-
6647
return (
6748
<mesh ref={ref} {...rest}>
68-
<extrudeGeometry ref={geomRef} args={[shape, params]} />
49+
<RoundedBoxGeometry
50+
args={[width, height, depth]}
51+
radius={radius}
52+
steps={steps}
53+
smoothness={smoothness}
54+
bevelSegments={bevelSegments}
55+
creaseAngle={creaseAngle}
56+
/>
6957
{children}
7058
</mesh>
7159
)
7260
})
61+
62+
export const RoundedBoxGeometry: ForwardRefComponent<RoundedBoxGeometryProps, ExtrudeGeometry> =
63+
/* @__PURE__ */ React.forwardRef<ExtrudeGeometry, RoundedBoxGeometryProps>(function RoundedBoxGeometry(
64+
{
65+
args: [width = 1, height = 1, depth = 1] = [],
66+
radius = 0.05,
67+
steps = 1,
68+
smoothness = 4,
69+
bevelSegments = 4,
70+
creaseAngle = 0.4,
71+
...rest
72+
},
73+
ref
74+
) {
75+
const shape = React.useMemo(() => createShape(width, height, radius), [width, height, radius])
76+
const params = React.useMemo(
77+
() => ({
78+
depth: depth - radius * 2,
79+
bevelEnabled: true,
80+
bevelSegments: bevelSegments * 2,
81+
steps,
82+
bevelSize: radius - eps,
83+
bevelThickness: radius,
84+
curveSegments: smoothness,
85+
}),
86+
[depth, radius, smoothness, bevelSegments, steps]
87+
)
88+
const geomRef = React.useRef<ExtrudeGeometry>(null!)
89+
90+
React.useLayoutEffect(() => {
91+
if (geomRef.current) {
92+
geomRef.current.center()
93+
toCreasedNormals(geomRef.current, creaseAngle)
94+
}
95+
}, [shape, params, creaseAngle])
96+
97+
React.useImperativeHandle(ref, () => geomRef.current)
98+
99+
return <extrudeGeometry ref={geomRef} args={[shape, params]} {...rest} />
100+
})

0 commit comments

Comments
 (0)