Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Svg component #1091

Merged
merged 11 commits into from Oct 17, 2022
85 changes: 85 additions & 0 deletions .storybook/stories/Svg.stories.tsx
@@ -0,0 +1,85 @@
import { useEffect } from '@storybook/addons'
import { useArgs } from '@storybook/client-api'
import { ComponentMeta } from '@storybook/react'
import * as React from 'react'
import { ComponentProps, FC, Suspense } from 'react'
import { MathUtils, NoToneMapping, Vector3 } from 'three'
import { Svg } from '../../src'
import { Setup } from '../Setup'

const svgRecord = {
Tiger: 'models/svg/tiger.svg',
'Three.js': 'models/svg/threejs.svg',
'Joins and caps': 'models/svg/lineJoinsAndCaps.svg',
Hexagon: 'models/svg/hexagon.svg',
Energy: 'models/svg/energy.svg',
'Test 1': 'models/svg/tests/1.svg',
'Test 2': 'models/svg/tests/2.svg',
'Test 3': 'models/svg/tests/3.svg',
'Test 4': 'models/svg/tests/4.svg',
'Test 5': 'models/svg/tests/5.svg',
'Test 6': 'models/svg/tests/6.svg',
'Test 7': 'models/svg/tests/7.svg',
'Test 8': 'models/svg/tests/8.svg',
'Test 9': 'models/svg/tests/9.svg',
Units: 'models/svg/tests/units.svg',
Ordering: 'models/svg/tests/ordering.svg',
Defs: 'models/svg/tests/testDefs/Svg-defs.svg',
Defs2: 'models/svg/tests/testDefs/Svg-defs2.svg',
Defs3: 'models/svg/tests/testDefs/Wave-defs.svg',
Defs4: 'models/svg/tests/testDefs/defs4.svg',
Defs5: 'models/svg/tests/testDefs/defs5.svg',
'Style CSS inside defs': 'models/svg/style-css-inside-defs.svg',
'Multiple CSS classes': 'models/svg/multiple-css-classes.svg',
'Zero Radius': 'models/svg/zero-radius.svg',
'Styles in svg tag': 'models/svg/tests/styles.svg',
'Round join': 'models/svg/tests/roundJoinPrecisionIssue.svg',
}

const url = 'https://threejs.org/examples'

export default {
title: 'Abstractions/Svg',
component: Svg,
decorators: [
(storyFn) => (
<Setup
gl={{ toneMapping: NoToneMapping }}
onCreated={(st) => st.gl.setClearColor('#ccc')}
cameraPosition={new Vector3(0, 0, 200)}
lights={false}
>
{storyFn()}
</Setup>
),
],
args: {
src: `${url}/${svgRecord.Tiger}`,
svg: svgRecord.Tiger,
skipFill: false,
skipStrokes: false,
strokesWireframe: false,
fillWireframe: false,
},
argTypes: {
svg: {
options: svgRecord,
control: {
type: 'select',
},
},
},
} as ComponentMeta<typeof Svg>

export const SvgSt: FC<{ svg: string } & ComponentProps<typeof Svg>> = ({ svg, ...props }) => {
const [_, updateArgs] = useArgs()
useEffect(() => {
updateArgs({ src: `${url}/${svg || svgRecord.Tiger}` })
}, [svg])
return (
<Suspense fallback={null}>
<Svg {...props} position={[-70, 70, 0]} scale={0.25} />
<gridHelper args={[160, 10]} rotation={[MathUtils.DEG2RAD * 90, 0, 0]} />
</Suspense>
)
}
13 changes: 13 additions & 0 deletions README.md
Expand Up @@ -75,6 +75,7 @@ The `native` route of the library **does not** export `Html` or `Loader`. The de
<li><a href="#useanimations">useAnimations</a></li>
<li><a href="#marchingcubes">MarchingCubes</a></li>
<li><a href="#decal">Decal</a></li>
<li><a href="#svg">Svg</a></li>
</ul>
<li><a href="#shaders">Shaders</a></li>
<ul>
Expand Down Expand Up @@ -1165,6 +1166,18 @@ If declarative composition is not possible, use the `mesh` prop to define the su
</Decal>
```

#### Svg

[![](https://img.shields.io/badge/-storybook-%23ff69b4)](https://drei.pmnd.rs/?path=/story/abstractions-svg--svg-st)

Wrapper around the `three` [svg loader](https://threejs.org/examples/?q=sv#webgl_loader_svg) demo.

Accepts an SVG url or svg raw data.

```js
<Svg src={urlOrRawSvgString} />
```

# Shaders

#### MeshReflectorMaterial
Expand Down
79 changes: 79 additions & 0 deletions src/core/Svg.tsx
@@ -0,0 +1,79 @@
import { Object3DProps, useLoader } from '@react-three/fiber'
import * as React from 'react'
import { forwardRef, Fragment, useEffect, useMemo } from 'react'
import { DoubleSide, Object3D } from 'three'
import { SVGLoader } from 'three-stdlib'

export interface SvgProps extends Object3DProps {
/** src can be a URL or SVG data */
src: string
skipFill?: boolean
skipStrokes?: boolean
strokesWireframe?: boolean
fillWireframe?: boolean
}

export const Svg = forwardRef<Object3D, SvgProps>(function R3FSvg(
{ src, skipFill, skipStrokes, strokesWireframe, fillWireframe, ...props },
ref
) {
const svg = useLoader(SVGLoader, !src.startsWith('<svg') ? src : `data:image/svg+xml;utf8,${src}`)

const strokeGeometries = useMemo(
() =>
skipStrokes
? []
: svg.paths.map((path) =>
path.userData?.style.stroke === undefined || path.userData.style.stroke === 'none'
? null
: path.subPaths.map((subPath) => SVGLoader.pointsToStroke(subPath.getPoints(), path.userData!.style))
),
[svg, skipStrokes]
)

useEffect(() => {
return () => strokeGeometries.forEach((group) => group && group.map((g) => g.dispose()))
}, [strokeGeometries])

return (
<object3D ref={ref} {...props}>
<object3D scale={[1, -1, 1]}>
{svg.paths.map((path, p) => (
joshuaellis marked this conversation as resolved.
Show resolved Hide resolved
<Fragment key={p}>
{!skipFill &&
path.userData?.style.fill !== undefined &&
path.userData.style.fill !== 'none' &&
SVGLoader.createShapes(path).map((shape, s) => (
<mesh key={s}>
<shapeGeometry args={[shape]} />
<meshBasicMaterial
color={path.userData!.style.fill}
opacity={path.userData!.style.fillOpacity}
transparent={true}
side={DoubleSide}
depthWrite={false}
wireframe={fillWireframe}
/>
</mesh>
))}
{!skipStrokes &&
path.userData?.style.stroke !== undefined &&
path.userData.style.stroke !== 'none' &&
path.subPaths.map((_subPath, s) => (
<mesh key={s} geometry={strokeGeometries[p]![s]}>
<meshBasicMaterial
color={path.userData!.style.stroke}
opacity={path.userData!.style.strokeOpacity}
transparent={true}
side={DoubleSide}
depthWrite={false}
wireframe={strokesWireframe}
/>
</mesh>
))}
</Fragment>
))}
</object3D>
</object3D>
)
})
1 change: 1 addition & 0 deletions src/core/index.ts
Expand Up @@ -17,6 +17,7 @@ export * from './ComputedAttribute'
export * from './Clone'
export * from './MarchingCubes'
export * from './Decal'
export * from './Svg'

// Cameras
export * from './OrthographicCamera'
Expand Down