Skip to content

Commit

Permalink
feat: add Svg component (#1091)
Browse files Browse the repository at this point in the history
  • Loading branch information
RodrigoHamuy committed Oct 17, 2022
1 parent bc78f69 commit 7b7d486
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 0 deletions.
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) => (
<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

1 comment on commit 7b7d486

@vercel
Copy link

@vercel vercel bot commented on 7b7d486 Oct 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.