From 5bae4de1005cda00d7ad6ecbb25a96e64441b78f Mon Sep 17 00:00:00 2001 From: andretchen0 Date: Wed, 4 Dec 2024 02:23:43 -0600 Subject: [PATCH] feat(Outline): add component, demo, docs (#532) * squash * chore: lint * feat(Outline): add component, demo, docs * chore(shaderMaterial): destructure import * docs(Outline): update playground demo * chore: lint --------- Co-authored-by: alvarosabu --- docs/.vitepress/config.ts | 1 + .../theme/components/OutlineDemo.vue | 23 ++++ docs/component-list/components.ts | 1 + docs/guide/abstractions/outline.md | 22 +++ .../src/pages/abstractions/OutlineDemo.vue | 31 +++++ .../vue/src/router/routes/abstractions.ts | 5 + .../Outline/OutlineMaterialImpl.ts | 59 ++++++++ src/core/abstractions/Outline/component.vue | 126 ++++++++++++++++++ src/core/abstractions/index.ts | 2 + src/utils/shaderMaterial.ts | 31 ++--- 10 files changed, 286 insertions(+), 15 deletions(-) create mode 100644 docs/.vitepress/theme/components/OutlineDemo.vue create mode 100644 docs/guide/abstractions/outline.md create mode 100644 playground/vue/src/pages/abstractions/OutlineDemo.vue create mode 100644 src/core/abstractions/Outline/OutlineMaterialImpl.ts create mode 100644 src/core/abstractions/Outline/component.vue diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 39094c28..1ae04d5e 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -78,6 +78,7 @@ export default defineConfig({ { text: 'Edges', link: '/guide/abstractions/edges' }, { text: 'PositionalAudio', link: '/guide/abstractions/positional-audio' }, { text: 'AnimatedSprite', link: '/guide/abstractions/animated-sprite' }, + { text: 'Outline', link: '/guide/abstractions/outline' }, { text: 'Image', link: '/guide/abstractions/image' }, { text: 'Billboard', link: '/guide/abstractions/billboard' }, ], diff --git a/docs/.vitepress/theme/components/OutlineDemo.vue b/docs/.vitepress/theme/components/OutlineDemo.vue new file mode 100644 index 00000000..9720009f --- /dev/null +++ b/docs/.vitepress/theme/components/OutlineDemo.vue @@ -0,0 +1,23 @@ + + + diff --git a/docs/component-list/components.ts b/docs/component-list/components.ts index 22c720cc..d9f9583f 100644 --- a/docs/component-list/components.ts +++ b/docs/component-list/components.ts @@ -17,6 +17,7 @@ export default [ }, { text: 'Sampler', link: '/guide/abstractions/sampler' }, { text: 'PositionalAudio', link: '/guide/abstractions/positional-audio' }, + { text: 'Outline', link: '/guide/abstractions/outline' }, { text: 'Image', link: '/guide/abstractions/image' }, { text: 'Billboard', link: '/guide/abstractions/billboard' }, ], diff --git a/docs/guide/abstractions/outline.md b/docs/guide/abstractions/outline.md new file mode 100644 index 00000000..537531b6 --- /dev/null +++ b/docs/guide/abstractions/outline.md @@ -0,0 +1,22 @@ +# Outline + +`` creates an inverted-hull outline using its parent's geometry. Supported parents are `` and ``. + + + + + +## Usage + +<<< @/.vitepress/theme/components/OutlineDemo.vue + +## Props + +| Props | Description | Default | +|--------------|--------------------------------------------------------------------| ------- | +| color | Outline color | `'black'` | +| screenspace | Whether line thickness is independent of zoom | `false` | +| opacity | Outline opacity | `1` | +| transparent | Outline transparency | `false` | +| thickness | Outline thickness | `0.05` | +| angle | Geometry crease angle (`0` is no crease). See [BufferGeometryUtils.toCreasedNormals](https://threejs.org/docs/#examples/en/utils/BufferGeometryUtils.toCreasedNormals) | `Math.PI` | diff --git a/playground/vue/src/pages/abstractions/OutlineDemo.vue b/playground/vue/src/pages/abstractions/OutlineDemo.vue new file mode 100644 index 00000000..a0820829 --- /dev/null +++ b/playground/vue/src/pages/abstractions/OutlineDemo.vue @@ -0,0 +1,31 @@ + + + diff --git a/playground/vue/src/router/routes/abstractions.ts b/playground/vue/src/router/routes/abstractions.ts index ad09164d..9930ec71 100644 --- a/playground/vue/src/router/routes/abstractions.ts +++ b/playground/vue/src/router/routes/abstractions.ts @@ -64,6 +64,11 @@ export const abstractionsRoutes = [ name: 'AnimatedSprite', component: () => import('../../pages/abstractions/AnimatedSpriteDemo.vue'), }, + { + path: '/abstractions/outline', + name: 'Outline', + component: () => import('../../pages/abstractions/OutlineDemo.vue'), + }, { path: '/abstractions/image', name: 'Image', diff --git a/src/core/abstractions/Outline/OutlineMaterialImpl.ts b/src/core/abstractions/Outline/OutlineMaterialImpl.ts new file mode 100644 index 00000000..2284601a --- /dev/null +++ b/src/core/abstractions/Outline/OutlineMaterialImpl.ts @@ -0,0 +1,59 @@ +import { Color, Vector2 } from 'three' +import { shaderMaterial } from '../../../utils/shaderMaterial' + +// NOTE: Source +// https://github.com/pmndrs/drei/blob/master/src/core/Outlines.tsx + +const OutlineMaterialImpl = shaderMaterial( + { + screenspace: false, + color: new Color('black'), + opacity: 1, + thickness: 0.05, + size: new Vector2(1, 1), + }, + `#include + #include + #include + uniform float thickness; + uniform bool screenspace; + uniform vec2 size; + void main() { + #if defined (USE_SKINNING) + #include + #include + #include + #include + #include + #endif + #include + #include + #include + #include + vec4 tNormal = vec4(normal, 0.0); + vec4 tPosition = vec4(transformed, 1.0); + #ifdef USE_INSTANCING + tNormal = instanceMatrix * tNormal; + tPosition = instanceMatrix * tPosition; + #endif + if (screenspace) { + vec3 newPosition = tPosition.xyz + tNormal.xyz * thickness; + gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0); + } else { + vec4 clipPosition = projectionMatrix * modelViewMatrix * tPosition; + vec4 clipNormal = projectionMatrix * modelViewMatrix * tNormal; + vec2 offset = normalize(clipNormal.xy) * thickness / size * clipPosition.w * 2.0; + clipPosition.xy += offset; + gl_Position = clipPosition; + } + }`, + `uniform vec3 color; + uniform float opacity; + void main(){ + gl_FragColor = vec4(color, opacity); + #include + #include + }`, +) + +export default OutlineMaterialImpl diff --git a/src/core/abstractions/Outline/component.vue b/src/core/abstractions/Outline/component.vue new file mode 100644 index 00000000..981b69db --- /dev/null +++ b/src/core/abstractions/Outline/component.vue @@ -0,0 +1,126 @@ + + + diff --git a/src/core/abstractions/index.ts b/src/core/abstractions/index.ts index 627987eb..f7dd073f 100644 --- a/src/core/abstractions/index.ts +++ b/src/core/abstractions/index.ts @@ -5,6 +5,7 @@ import Image from './Image/component.vue' import Lensflare from './Lensflare/component.vue' import Levioso from './Levioso.vue' import MouseParallax from './MouseParallax.vue' +import Outline from './Outline/component.vue' import PositionalAudio from './PositionalAudio.vue' import Reflector from './Reflector.vue' import Text3D from './Text3D.vue' @@ -26,6 +27,7 @@ export { Lensflare, Levioso, MouseParallax, + Outline, PositionalAudio, Reflector, Sampler, diff --git a/src/utils/shaderMaterial.ts b/src/utils/shaderMaterial.ts index 6e6ff864..ec9eff80 100644 --- a/src/utils/shaderMaterial.ts +++ b/src/utils/shaderMaterial.ts @@ -22,22 +22,23 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import * as THREE from 'three' +import type { Color, CubeTexture, Matrix3, Matrix4, Quaternion, Texture, Vector2, Vector3, Vector4 } from 'three' +import { MathUtils, ShaderMaterial, UniformsUtils } from 'three' export function shaderMaterial( uniforms: { [name: string]: - | THREE.CubeTexture - | THREE.Texture + | CubeTexture + | Texture | Int32Array | Float32Array - | THREE.Matrix4 - | THREE.Matrix3 - | THREE.Quaternion - | THREE.Vector4 - | THREE.Vector3 - | THREE.Vector2 - | THREE.Color + | Matrix4 + | Matrix3 + | Quaternion + | Vector4 + | Vector3 + | Vector2 + | Color | number | boolean | Array @@ -45,16 +46,16 @@ export function shaderMaterial( }, vertexShader: string, fragmentShader: string, - onInit?: (material?: THREE.ShaderMaterial) => void, + onInit?: (material?: ShaderMaterial) => void, ) { - const material = class extends THREE.ShaderMaterial { + const material = class extends ShaderMaterial { public key: string = '' constructor(parameters = {}) { const entries = Object.entries(uniforms) // Create unforms and shaders super({ uniforms: entries.reduce((acc, [name, value]) => { - const uniform = THREE.UniformsUtils.clone({ [name]: { value } }) + const uniform = UniformsUtils.clone({ [name]: { value } }) return { ...acc, ...uniform, @@ -76,7 +77,7 @@ export function shaderMaterial( // Call onInit if (onInit) { onInit(this) } } - } as unknown as typeof THREE.ShaderMaterial & { key: string } - material.key = THREE.MathUtils.generateUUID() + } as unknown as typeof ShaderMaterial & { key: string } + material.key = MathUtils.generateUUID() return material }