Skip to content

Commit 6fa0e3c

Browse files
feat: add shock wave (#164)
* init shockwave effect * wip shock wave effect * add demo playground * wip demo doc * add depthpickingpass * wip component depth picking pass fix issue / types * wip doc demo * wip doc * wip doc * fix doc * fix documentation * fix reviews PR --------- Co-authored-by: Tino Koch <17991193+Tinoooo@users.noreply.github.com>
1 parent ab9c8a0 commit 6fa0e3c

File tree

9 files changed

+639
-0
lines changed

9 files changed

+639
-0
lines changed

docs/.vitepress/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export default defineConfig({
5757
{ text: 'Chromatic Aberration', link: '/guide/pmndrs/chromatic-aberration' },
5858
{ text: 'Sepia', link: '/guide/pmndrs/sepia' },
5959
{ text: 'Scanline', link: '/guide/pmndrs/scanline' },
60+
{ text: 'Shock Wave', link: '/guide/pmndrs/shock-wave' },
6061
{ text: 'Pixelation', link: '/guide/pmndrs/pixelation' },
6162
{ text: 'Vignette', link: '/guide/pmndrs/vignette' },
6263
{ text: 'Barrel blur', link: '/guide/pmndrs/barrel-blur' },
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
<script setup lang="ts">
2+
import { ContactShadows, Environment, OrbitControls } from '@tresjs/cientos'
3+
import { TresCanvas } from '@tresjs/core'
4+
import { TresLeches, useControls } from '@tresjs/leches'
5+
import { NoToneMapping, Shape, Vector3 } from 'three'
6+
import { computed, onUnmounted, reactive, ref, shallowRef } from 'vue'
7+
import { DepthPickingPassPmndrs, EffectComposerPmndrs, ShockWavePmndrs } from '@tresjs/post-processing'
8+
import { useElementBounding, useMouse, useParentElement } from '@vueuse/core'
9+
import { gsap } from 'gsap'
10+
11+
import '@tresjs/leches/styles'
12+
13+
const gl = {
14+
clearColor: '#8D404A',
15+
toneMapping: NoToneMapping,
16+
multisampling: 8,
17+
}
18+
19+
const shockWaveEffectRef = shallowRef(null)
20+
const elCanvasRef = ref(null)
21+
const depthPickingPassRef = ref(null)
22+
const meshHeartRef = ref(null)
23+
const mousePosition = ref(new Vector3())
24+
25+
function createHeartShape(scale: number) {
26+
const shape = new Shape()
27+
const x = 0
28+
const y = 0
29+
30+
shape.moveTo(x, y)
31+
shape.bezierCurveTo(x, y, x - 0.5 * scale, y + 2.5 * scale, x - 2.5 * scale, y + 2.5 * scale)
32+
shape.bezierCurveTo(x - 6.5 * scale, y + 2.5 * scale, x - 6.5 * scale, y - 1.5 * scale, x - 6.5 * scale, y - 1.5 * scale)
33+
shape.bezierCurveTo(x - 6.5 * scale, y - 4.5 * scale, x - 3.5 * scale, y - 7 * scale, x, y - 9.5 * scale)
34+
shape.bezierCurveTo(x + 3.5 * scale, y - 7 * scale, x + 6.5 * scale, y - 4.5 * scale, x + 6.5 * scale, y - 1.5 * scale)
35+
shape.bezierCurveTo(x + 6.5 * scale, y - 1.5 * scale, x + 6.5 * scale, y + 2.5 * scale, x + 2.5 * scale, y + 2.5 * scale)
36+
shape.bezierCurveTo(x + 0.5 * scale, y + 2.5 * scale, x, y, x, y)
37+
38+
return shape
39+
}
40+
41+
const heartShapeFront = createHeartShape(0.35)
42+
43+
const parentEl = useParentElement()
44+
const { x, y } = useMouse({ target: parentEl })
45+
const { width, height, left, top } = useElementBounding(parentEl)
46+
47+
const extrudeSettings = reactive({
48+
depth: 0.1,
49+
bevelEnabled: true,
50+
bevelSegments: 2,
51+
steps: 2,
52+
bevelSize: 0.25,
53+
bevelThickness: 0.25,
54+
})
55+
56+
const materialProps = reactive({
57+
color: '#FF9999',
58+
reflectivity: 0.75,
59+
ior: 1.5,
60+
roughness: 0.75,
61+
clearcoat: 0.01,
62+
clearcoatRoughness: 0.15,
63+
transmission: 0.7,
64+
})
65+
66+
let tl: gsap.core.Timeline
67+
68+
const ctx = gsap.context(() => { })
69+
70+
const { amplitude, waveSize, speed, maxRadius } = useControls({
71+
amplitude: { value: 0.4, step: 0.01, max: 1.0 },
72+
waveSize: { value: 0.5, step: 0.01, max: 1.0 },
73+
speed: { value: 1.5, step: 0.1, max: 10.0 },
74+
maxRadius: { value: 0.2, step: 0.01, max: 2 },
75+
})
76+
77+
const cursorX = computed(() => ((x.value - left.value - width.value) / width.value) * 2.0 + 1.0)
78+
const cursorY = computed(() => -((y.value - top.value - height.value) / height.value) * 2.0 - 1.0)
79+
80+
async function updateMousePosition() {
81+
if (!elCanvasRef.value || !shockWaveEffectRef.value || !depthPickingPassRef.value) { return }
82+
83+
const ndcPosition = new Vector3(cursorX.value, cursorY.value, 0)
84+
85+
// Read depth from depth picking pass
86+
ndcPosition.z = await depthPickingPassRef.value.pass.readDepth(ndcPosition)
87+
88+
ndcPosition.z = ndcPosition.z * 2.0 - 1.0
89+
90+
mousePosition.value.copy(ndcPosition.unproject(elCanvasRef.value.context.camera.value))
91+
}
92+
93+
function triggerShockWave() {
94+
if (!meshHeartRef.value || !shockWaveEffectRef.value) { return }
95+
96+
updateMousePosition()
97+
98+
shockWaveEffectRef.value.effect.explode()
99+
100+
const duration = getActiveDuration()
101+
102+
const durationSeconds = duration / 1000
103+
104+
ctx.add(() => {
105+
tl?.kill()
106+
107+
tl = gsap.timeline()
108+
109+
tl.to(meshHeartRef.value.scale, {
110+
duration: durationSeconds / 9,
111+
x: 0.8,
112+
y: 0.8,
113+
z: 0.8,
114+
ease: 'power2.inOut',
115+
}).to(meshHeartRef.value.scale, {
116+
duration: durationSeconds / 9,
117+
x: 1.2,
118+
y: 1.2,
119+
z: 1.2,
120+
ease: 'power2.inOut',
121+
}).to(meshHeartRef.value.scale, {
122+
duration: durationSeconds / 9,
123+
x: 1,
124+
y: 1,
125+
z: 1,
126+
ease: 'power2.inOut',
127+
})
128+
})
129+
130+
// Fallback for onFinish explode Shock Wave
131+
// setTimeout(() => {
132+
// console.log('Explode effect animation done')
133+
// }, duration)
134+
}
135+
136+
function getActiveDuration() {
137+
// This function retrieves the duration for emitting the shock wave.
138+
// For more details, see: https://github.com/pmndrs/postprocessing/blob/3d3df0576b6d49aec9e763262d5a1ff7429fd91a/src/effects/ShockWaveEffectRef.js#L258-L301
139+
140+
// To reduce the duration of the animation, you can decrease the values of maxRadius and waveSize.
141+
// Note that the speed affects how quickly the shock wave radius increases over time, but not the total duration of the emit explode.
142+
143+
// Retrieve the values dynamically
144+
const radiusMax = maxRadius.value.value
145+
const wave = waveSize.value.value
146+
147+
// Duration formula: 2 * maxRadius + 3 * waveSize
148+
const duration = 2 * radiusMax + 3 * wave
149+
150+
// Convert to milliseconds
151+
return duration * 1000
152+
}
153+
154+
onUnmounted(() => {
155+
ctx.revert()
156+
})
157+
</script>
158+
159+
<template>
160+
<TresLeches style="left: initial;right:10px; top:10px;" />
161+
162+
<p class="doc-shock-wave-instructions text-xs font-semibold text-zinc-600">Click on the heart to distribute love</p>
163+
164+
<TresCanvas
165+
ref="elCanvasRef"
166+
v-bind="gl"
167+
>
168+
<TresPerspectiveCamera
169+
:position="[0, 0, 10]"
170+
/>
171+
172+
<OrbitControls make-default auto-rotate />
173+
174+
<TresMesh ref="meshHeartRef" :position-y="2" @click="triggerShockWave">
175+
<TresExtrudeGeometry :args="[heartShapeFront, extrudeSettings]" />
176+
<TresMeshPhysicalMaterial
177+
v-bind="materialProps"
178+
/>
179+
</TresMesh>
180+
181+
<TresDirectionalLight
182+
:position="[5, 5, 7.5]"
183+
:intensity="2"
184+
/>
185+
186+
<ContactShadows
187+
:opacity="1"
188+
:position-y="-2.75"
189+
:blur=".5"
190+
/>
191+
192+
<Suspense>
193+
<Environment preset="night" />
194+
</Suspense>
195+
196+
<Suspense>
197+
<EffectComposerPmndrs>
198+
<DepthPickingPassPmndrs ref="depthPickingPassRef" />
199+
<ShockWavePmndrs ref="shockWaveEffectRef" :position="mousePosition" :amplitude="amplitude.value" :waveSize="waveSize.value" :speed="speed.value" :maxRadius="maxRadius.value" />
200+
</EffectComposerPmndrs>
201+
</Suspense>
202+
</TresCanvas>
203+
</template>
204+
205+
<style scoped>
206+
.doc-shock-wave-instructions {
207+
position: absolute;
208+
bottom: 0;
209+
left: 0;
210+
padding: 0.65rem 0.85rem;
211+
text-align: center;
212+
color: #fff;
213+
z-index: 2;
214+
border-radius: 0px 10px 0px 0px;
215+
background: linear-gradient(90deg, hsla(24, 100%, 83%, 1) 0%, hsla(341, 91%, 68%, 1) 100%);
216+
margin: 0;
217+
}
218+
</style>

docs/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ declare module 'vue' {
2626
PixelationDemo: typeof import('./.vitepress/theme/components/pmdrs/PixelationDemo.vue')['default']
2727
PixelationThreeDemo: typeof import('./.vitepress/theme/components/three/PixelationThreeDemo.vue')['default']
2828
ScanlineDemo: typeof import('./.vitepress/theme/components/pmdrs/ScanlineDemo.vue')['default']
29+
ShockWaveDemo: typeof import('./.vitepress/theme/components/pmdrs/ShockWaveDemo.vue')['default']
2930
SepiaDemo: typeof import('./.vitepress/theme/components/pmdrs/SepiaDemo.vue')['default']
3031
SMAAThreeDemo: typeof import('./.vitepress/theme/components/three/SMAAThreeDemo.vue')['default']
3132
TiltShiftDemo: typeof import('./.vitepress/theme/components/pmdrs/TiltShiftDemo.vue')['default']

0 commit comments

Comments
 (0)