Skip to content

Commit 8938c6a

Browse files
authored
feat: brownian distribution scene (#224)
* feat: base scene with animated imagotype * feat: brownian distribution geometries on background * chore: update lock * feat: implement dark mode support in Brownian distribution components - Added dark mode functionality using `useDark` from VueUse in BrownianDistributionGroup.vue and Imagotype.vue. - Updated materials' colors based on the dark mode state. - Introduced a toggle button for dark mode in index.vue, allowing users to switch themes dynamically.
1 parent bf142bc commit 8938c6a

File tree

9 files changed

+231
-37
lines changed

9 files changed

+231
-37
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<script setup lang="ts">
2+
import Imagotype from './Imagotype.vue';
3+
import BrownianDistributionGroup from './BrownianDistributionGroup.vue'
4+
</script>
5+
6+
<template>
7+
<TresPerspectiveCamera
8+
:position="[0, 0, 22]"
9+
:fov="45"
10+
:near="0.1"
11+
:far="1000"
12+
:look-at="[0, 5, 0]"
13+
/>
14+
<OrbitControls />
15+
<TresAmbientLight :intensity="0.5" />
16+
<Imagotype />
17+
<BrownianDistributionGroup />
18+
<TresDirectionalLight
19+
:position="[0, 8, 4]"
20+
:intensity="0.7"
21+
cast-shadow
22+
/>
23+
<TresDirectionalLight
24+
:position="[0, 2, 4]"
25+
:intensity="1"
26+
cast-shadow
27+
/>
28+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<script setup lang="ts">
2+
import { useDark } from '@vueuse/core'
3+
import { MathUtils, Vector3, Euler } from 'three'
4+
5+
import { colors } from './constants'
6+
import { BoxGeometry, CylinderGeometry, SphereGeometry, MeshToonMaterial } from 'three'
7+
const { lerp } = MathUtils
8+
const COUNT = 2000
9+
10+
const brownian = (stepSize: number, xMin: number, xMax: number, yMin: number, yMax: number, zMin: number, zMax: number) => {
11+
let x = 0; let y = 0; let z = 0
12+
const r = () => (Math.random() - 0.5) * 2 * stepSize
13+
const isInBounds = () => xMin < x && x < xMax && yMin < y && y < yMax && zMin < z && z < zMax
14+
const reset = () => {
15+
x = lerp(xMin, xMax, Math.random())
16+
y = lerp(yMin, yMax, Math.random())
17+
z = lerp(zMin, zMax, Math.random())
18+
}
19+
reset()
20+
return () => {
21+
x += r()
22+
y += r()
23+
z += r()
24+
if (!isInBounds()) { reset() }
25+
return [x, y, z]
26+
}
27+
}
28+
29+
const sphereGeometry = new SphereGeometry()
30+
const cubeGeometry = new BoxGeometry()
31+
const pyramidGeometry = new CylinderGeometry(0, 0.6, 1)
32+
33+
const isDark = useDark()
34+
35+
const mainMaterial = new MeshToonMaterial({
36+
color: isDark.value ? colors.DARK : colors.LIGHT,
37+
})
38+
39+
const hoverMaterial = new MeshToonMaterial({
40+
color: colors.YELLOW,
41+
})
42+
43+
const getPosition = brownian(2, -60, 60, -40, 40, -30, 0)
44+
const getRotation = brownian(1, -20, 20, -10, 10, -20, 0)
45+
const objectPositions = Array.from({ length: COUNT }).map(() => new Vector3(...getPosition()))
46+
const objectRotations = Array.from({ length: COUNT }).map(() => new Euler(...getRotation()))
47+
48+
function onPointerEnter(ev: ThreeEvent<PointerEvent>) {
49+
if (ev.eventObject.material !== hoverMaterial) {
50+
ev.eventObject.userData.material = ev.eventObject.material
51+
}
52+
ev.eventObject.material = hoverMaterial
53+
}
54+
55+
function onPointerLeave(ev: ThreeEvent<PointerEvent>) {
56+
ev.eventObject.material = ev.eventObject.userData.material ?? mainMaterial
57+
}
58+
59+
watch(isDark, (newVal) => {
60+
mainMaterial.color.set(newVal ? colors.DARK : colors.LIGHT)
61+
})
62+
</script>
63+
64+
<template>
65+
<TresGroup :position="[0, 0, -30]">
66+
<TresGroup :position="[0, 0, -30]">
67+
<TresMesh
68+
v-for="position, i of objectPositions"
69+
:key="i"
70+
:geometry="[sphereGeometry, cubeGeometry, pyramidGeometry][i % 3]"
71+
:material="mainMaterial"
72+
:position="position"
73+
:rotation="objectRotations[i]"
74+
@pointer-enter="onPointerEnter"
75+
@pointer-leave="onPointerLeave"
76+
/>
77+
</TresGroup>
78+
</TresGroup>
79+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<script setup lang="ts">
2+
import { MathUtils } from 'three'
3+
import { useDark } from '@vueuse/core'
4+
5+
import { PI, colors } from './constants'
6+
const { clamp } = MathUtils
7+
8+
const isDark = useDark()
9+
10+
const pyramidRef = ref()
11+
const boxRef = ref()
12+
const sphereRef = ref()
13+
14+
const { onBeforeRender } = useLoop()
15+
16+
onBeforeRender(({ elapsed }) => {
17+
if (!pyramidRef.value || !boxRef.value || !sphereRef.value) return
18+
elapsed = elapsed * 3 + 7
19+
pyramidRef.value.position.y = Math.tan(clamp((1 + elapsed) % 9, 0, PI))
20+
boxRef.value.position.y = Math.tan(clamp((0.5 + elapsed) % 9, 0, PI))
21+
sphereRef.value.position.y = Math.tan(clamp(elapsed % 9, 0, PI))
22+
23+
const scale0 = Math.abs(Math.cos(clamp((1 + elapsed) % 9, 0, PI)))
24+
const scale1 = Math.abs(Math.cos(clamp((0.5 + elapsed) % 9, 0, PI)))
25+
const scale2 = Math.abs(Math.cos(clamp(elapsed % 9, 0, PI)))
26+
pyramidRef.value.scale.set(scale0, scale0, scale0)
27+
boxRef.value.scale.set(scale1, scale1, scale1)
28+
sphereRef.value.scale.set(scale2, scale2, scale2)
29+
})
30+
31+
watch(isDark, (newVal) => {
32+
boxRef.value.material.color.set(newVal ? colors.LIGHT : colors.DARK)
33+
})
34+
</script>
35+
36+
<template>
37+
<TresGroup name="imago">
38+
<TresMesh
39+
name="pyramid"
40+
:position="[-1.5, 0, 0]"
41+
ref="pyramidRef"
42+
>
43+
<TresCylinderGeometry :args="[0, 0.60, 1]" />
44+
<TresMeshToonMaterial :color="colors.TEAL" />
45+
</TresMesh>
46+
<TresMesh
47+
name="box"
48+
ref="boxRef"
49+
>
50+
<TresBoxGeometry :args="[1, 1, 1]" />
51+
<TresMeshToonMaterial :color="isDark ? colors.LIGHT : colors.DARK" />
52+
</TresMesh>
53+
<TresMesh
54+
name="sphere"
55+
:position="[1.5, 0, 0]"
56+
ref="sphereRef"
57+
>
58+
<TresSphereGeometry :args="[0.5, 32, 32]" />
59+
<TresMeshToonMaterial :color="colors.ORANGE" />
60+
</TresMesh>
61+
</TresGroup>
62+
</template>
63+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
export const colors = {
3+
TEAL: '#7fdac6',
4+
ORANGE: '#eeac35',
5+
PURPLE: '#9b51e0',
6+
YELLOW: '#f7d060',
7+
BLUE: '#00b4d8',
8+
RED: '#ef476f',
9+
DARK: '#1e1f22',
10+
LIGHT: '#f8f8f8',
11+
}
12+
13+
export const PI = Math.PI
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script setup lang="ts">
2+
import BrownianDistributionExperiment from './BrownianDistributionExperiment.vue'
3+
import { useDark, useToggle } from '@vueuse/core'
4+
import { colors } from './constants'
5+
const isDark = useDark()
6+
const toggleDark = useToggle(isDark)
7+
8+
</script>
9+
10+
<template>
11+
<TresCanvas :clear-color="isDark ? colors.DARK : colors.LIGHT" window-size>
12+
<BrownianDistributionExperiment />
13+
</TresCanvas>
14+
<button
15+
title="Toggle dark mode"
16+
class="rounded-full fixed z-10 bottom-5 right-15 p-1"
17+
:class="{ 'bg-white': !isDark, 'bg-dark': isDark }"
18+
@click="toggleDark()"
19+
>
20+
<i
21+
v-if="isDark"
22+
class="i-carbon-sun w-5 h-5 "
23+
/>
24+
<i
25+
v-else
26+
class="i-carbon-moon w-5 h-5 "
27+
/>
28+
</button>
29+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: Basic Brownian distribution
3+
author: andretchen0
4+
description: Basic scene with grouping/parenting and Brownian distribution of instances
5+
tags: ['useLoop']
6+
---
7+
8+
::brownian-distribution
9+
::

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.vue"
1010
},
1111
"dependencies": {
12-
"@tresjs/post-processing": "1.0.0",
12+
"@tresjs/post-processing": "1.0.0-next.1",
1313
"mdast-util-to-string": "^4.0.0",
1414
"three": "^0.171.0",
1515
"three-custom-shader-material": "^6.2.1",
@@ -39,4 +39,4 @@
3939
"postprocessing": "6.36.5",
4040
"vite-svg-loader": "^5.1.0"
4141
}
42-
}
42+
}

pnpm-lock.yaml

+8-35
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/brownian-distribution.png

249 KB
Loading

0 commit comments

Comments
 (0)