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
99 changes: 99 additions & 0 deletions .storybook/stories/Svg.stories.tsx
@@ -0,0 +1,99 @@
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, SvgProps } 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'

interface SvgStoryProps extends SvgProps {
svg: string
fillWireframe: boolean
strokesWireframe: boolean
}

const args: SvgStoryProps = {
src: `${url}/${svgRecord.Tiger}`,
svg: svgRecord.Tiger,
skipFill: false,
skipStrokes: false,
fillWireframe: false,
strokesWireframe: false,
}

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,
argTypes: {
svg: {
options: svgRecord,
control: {
type: 'select',
},
},
},
}

export const SvgSt: FC<SvgStoryProps> = ({ svg, fillWireframe, strokesWireframe, ...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}
fillMaterial={{ wireframe: fillWireframe }}
strokeMaterial={{ wireframe: strokesWireframe }}
/>
<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 @@ -1166,6 +1167,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
81 changes: 81 additions & 0 deletions src/core/Svg.tsx
@@ -0,0 +1,81 @@
import { MeshBasicMaterialProps, MeshProps, 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 Omit<Object3DProps, 'ref'> {
/** src can be a URL or SVG data */
src: string
skipFill?: boolean
skipStrokes?: boolean
fillMaterial?: MeshBasicMaterialProps
strokeMaterial?: MeshBasicMaterialProps
fillMeshProps?: MeshProps
strokeMeshProps?: MeshProps
}

export const Svg = forwardRef<Object3D, SvgProps>(function R3FSvg(
{ src, skipFill, skipStrokes, fillMaterial, strokeMaterial, fillMeshProps, strokeMeshProps, ...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} {...fillMeshProps}>
<shapeGeometry args={[shape]} />
<meshBasicMaterial
color={path.userData!.style.fill}
opacity={path.userData!.style.fillOpacity}
transparent={true}
side={DoubleSide}
depthWrite={false}
{...fillMaterial}
/>
</mesh>
))}
{!skipStrokes &&
path.userData?.style.stroke !== undefined &&
path.userData.style.stroke !== 'none' &&
path.subPaths.map((_subPath, s) => (
<mesh key={s} geometry={strokeGeometries[p]![s]} {...strokeMeshProps}>
<meshBasicMaterial
color={path.userData!.style.stroke}
opacity={path.userData!.style.strokeOpacity}
transparent={true}
side={DoubleSide}
depthWrite={false}
{...strokeMaterial}
/>
</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