Skip to content

Commit

Permalink
feat: brownian distribution scene (#224)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
alvarosabu authored Jan 1, 2025
1 parent bf142bc commit 8938c6a
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script setup lang="ts">
import Imagotype from './Imagotype.vue';
import BrownianDistributionGroup from './BrownianDistributionGroup.vue'
</script>

<template>
<TresPerspectiveCamera
:position="[0, 0, 22]"
:fov="45"
:near="0.1"
:far="1000"
:look-at="[0, 5, 0]"
/>
<OrbitControls />
<TresAmbientLight :intensity="0.5" />
<Imagotype />
<BrownianDistributionGroup />
<TresDirectionalLight
:position="[0, 8, 4]"
:intensity="0.7"
cast-shadow
/>
<TresDirectionalLight
:position="[0, 2, 4]"
:intensity="1"
cast-shadow
/>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<script setup lang="ts">
import { useDark } from '@vueuse/core'
import { MathUtils, Vector3, Euler } from 'three'
import { colors } from './constants'
import { BoxGeometry, CylinderGeometry, SphereGeometry, MeshToonMaterial } from 'three'
const { lerp } = MathUtils
const COUNT = 2000
const brownian = (stepSize: number, xMin: number, xMax: number, yMin: number, yMax: number, zMin: number, zMax: number) => {
let x = 0; let y = 0; let z = 0
const r = () => (Math.random() - 0.5) * 2 * stepSize
const isInBounds = () => xMin < x && x < xMax && yMin < y && y < yMax && zMin < z && z < zMax
const reset = () => {
x = lerp(xMin, xMax, Math.random())
y = lerp(yMin, yMax, Math.random())
z = lerp(zMin, zMax, Math.random())
}
reset()
return () => {
x += r()
y += r()
z += r()
if (!isInBounds()) { reset() }
return [x, y, z]
}
}
const sphereGeometry = new SphereGeometry()
const cubeGeometry = new BoxGeometry()
const pyramidGeometry = new CylinderGeometry(0, 0.6, 1)
const isDark = useDark()
const mainMaterial = new MeshToonMaterial({
color: isDark.value ? colors.DARK : colors.LIGHT,
})
const hoverMaterial = new MeshToonMaterial({
color: colors.YELLOW,
})
const getPosition = brownian(2, -60, 60, -40, 40, -30, 0)
const getRotation = brownian(1, -20, 20, -10, 10, -20, 0)
const objectPositions = Array.from({ length: COUNT }).map(() => new Vector3(...getPosition()))
const objectRotations = Array.from({ length: COUNT }).map(() => new Euler(...getRotation()))
function onPointerEnter(ev: ThreeEvent<PointerEvent>) {
if (ev.eventObject.material !== hoverMaterial) {
ev.eventObject.userData.material = ev.eventObject.material
}
ev.eventObject.material = hoverMaterial
}
function onPointerLeave(ev: ThreeEvent<PointerEvent>) {
ev.eventObject.material = ev.eventObject.userData.material ?? mainMaterial
}
watch(isDark, (newVal) => {
mainMaterial.color.set(newVal ? colors.DARK : colors.LIGHT)
})
</script>

<template>
<TresGroup :position="[0, 0, -30]">
<TresGroup :position="[0, 0, -30]">
<TresMesh
v-for="position, i of objectPositions"
:key="i"
:geometry="[sphereGeometry, cubeGeometry, pyramidGeometry][i % 3]"
:material="mainMaterial"
:position="position"
:rotation="objectRotations[i]"
@pointer-enter="onPointerEnter"
@pointer-leave="onPointerLeave"
/>
</TresGroup>
</TresGroup>
</template>
63 changes: 63 additions & 0 deletions components/content/brownian-distribution/Imagotype.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<script setup lang="ts">
import { MathUtils } from 'three'
import { useDark } from '@vueuse/core'
import { PI, colors } from './constants'
const { clamp } = MathUtils
const isDark = useDark()
const pyramidRef = ref()
const boxRef = ref()
const sphereRef = ref()
const { onBeforeRender } = useLoop()
onBeforeRender(({ elapsed }) => {
if (!pyramidRef.value || !boxRef.value || !sphereRef.value) return
elapsed = elapsed * 3 + 7
pyramidRef.value.position.y = Math.tan(clamp((1 + elapsed) % 9, 0, PI))
boxRef.value.position.y = Math.tan(clamp((0.5 + elapsed) % 9, 0, PI))
sphereRef.value.position.y = Math.tan(clamp(elapsed % 9, 0, PI))
const scale0 = Math.abs(Math.cos(clamp((1 + elapsed) % 9, 0, PI)))
const scale1 = Math.abs(Math.cos(clamp((0.5 + elapsed) % 9, 0, PI)))
const scale2 = Math.abs(Math.cos(clamp(elapsed % 9, 0, PI)))
pyramidRef.value.scale.set(scale0, scale0, scale0)
boxRef.value.scale.set(scale1, scale1, scale1)
sphereRef.value.scale.set(scale2, scale2, scale2)
})
watch(isDark, (newVal) => {
boxRef.value.material.color.set(newVal ? colors.LIGHT : colors.DARK)
})
</script>

<template>
<TresGroup name="imago">
<TresMesh
name="pyramid"
:position="[-1.5, 0, 0]"
ref="pyramidRef"
>
<TresCylinderGeometry :args="[0, 0.60, 1]" />
<TresMeshToonMaterial :color="colors.TEAL" />
</TresMesh>
<TresMesh
name="box"
ref="boxRef"
>
<TresBoxGeometry :args="[1, 1, 1]" />
<TresMeshToonMaterial :color="isDark ? colors.LIGHT : colors.DARK" />
</TresMesh>
<TresMesh
name="sphere"
:position="[1.5, 0, 0]"
ref="sphereRef"
>
<TresSphereGeometry :args="[0.5, 32, 32]" />
<TresMeshToonMaterial :color="colors.ORANGE" />
</TresMesh>
</TresGroup>
</template>

13 changes: 13 additions & 0 deletions components/content/brownian-distribution/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

export const colors = {
TEAL: '#7fdac6',
ORANGE: '#eeac35',
PURPLE: '#9b51e0',
YELLOW: '#f7d060',
BLUE: '#00b4d8',
RED: '#ef476f',
DARK: '#1e1f22',
LIGHT: '#f8f8f8',
}

export const PI = Math.PI
29 changes: 29 additions & 0 deletions components/content/brownian-distribution/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script setup lang="ts">
import BrownianDistributionExperiment from './BrownianDistributionExperiment.vue'
import { useDark, useToggle } from '@vueuse/core'
import { colors } from './constants'
const isDark = useDark()
const toggleDark = useToggle(isDark)
</script>

<template>
<TresCanvas :clear-color="isDark ? colors.DARK : colors.LIGHT" window-size>
<BrownianDistributionExperiment />
</TresCanvas>
<button
title="Toggle dark mode"
class="rounded-full fixed z-10 bottom-5 right-15 p-1"
:class="{ 'bg-white': !isDark, 'bg-dark': isDark }"
@click="toggleDark()"
>
<i
v-if="isDark"
class="i-carbon-sun w-5 h-5 "
/>
<i
v-else
class="i-carbon-moon w-5 h-5 "
/>
</button>
</template>
9 changes: 9 additions & 0 deletions content/experiments/brownian-distribution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: Basic Brownian distribution
author: andretchen0
description: Basic scene with grouping/parenting and Brownian distribution of instances
tags: ['useLoop']
---

::brownian-distribution
::
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.vue"
},
"dependencies": {
"@tresjs/post-processing": "1.0.0",
"@tresjs/post-processing": "1.0.0-next.1",
"mdast-util-to-string": "^4.0.0",
"three": "^0.171.0",
"three-custom-shader-material": "^6.2.1",
Expand Down Expand Up @@ -39,4 +39,4 @@
"postprocessing": "6.36.5",
"vite-svg-loader": "^5.1.0"
}
}
}
43 changes: 8 additions & 35 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added public/brownian-distribution.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 8938c6a

Please sign in to comment.