Skip to content

Commit f9c8393

Browse files
authored
feat: dancing cube boy (#263)
* feat: add Cube Boy Dancefloor experiment with animations, lighting, and music * refactor: clean up Cube Boy component by removing unused animation logic and console logs
1 parent 558016f commit f9c8393

11 files changed

Lines changed: 456 additions & 0 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<script setup lang="ts">
2+
import { useAnimations, useGLTF } from '@tresjs/cientos'
3+
import { computed } from 'vue'
4+
import type { AnimationAction } from 'three'
5+
6+
const { state: model, nodes } = useGLTF('/models/cube-boy/cube-boy-dance.glb', {
7+
draco: true,
8+
})
9+
10+
const rig = computed(() => nodes.value.Rig)
11+
12+
const animations = computed(() => model.value?.animations || [])
13+
14+
watch(animations, (animations) => {
15+
console.log('animations', animations)
16+
}, { immediate: true })
17+
18+
const { actions } = useAnimations(animations, rig)
19+
const currentAction = ref<AnimationAction>()
20+
21+
22+
watch(actions, (actions) => {
23+
if (Object.keys(actions || {}).length === 0) { return }
24+
25+
currentAction.value = actions.Wave
26+
currentAction.value?.reset().play()
27+
}, { immediate: true })
28+
29+
</script>
30+
31+
<template>
32+
<primitive v-if="rig" :object="rig" />
33+
</template>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<script setup lang="ts">
2+
3+
const cellColor = shallowRef('#000000')
4+
const sectionColor = shallowRef('#000000')
5+
const cellThickness = shallowRef(0)
6+
const sectionThickness = shallowRef(0)
7+
const cellSize = shallowRef(0)
8+
const sectionSize = shallowRef(0)
9+
10+
let elapsed = 0
11+
setInterval(() => {
12+
elapsed += 1000 / 30
13+
cellColor.value = Math.cos(elapsed * 0.01) > 0 ? '#FFFF00' : '#FF0000'
14+
sectionColor.value = Math.sin(elapsed * 0.01) > 0 ? '#FF0000' : '#00FF00'
15+
sectionThickness.value = Math.cos(elapsed * 0.003) + 1
16+
cellThickness.value = Math.cos(elapsed * 0.001) + 1
17+
cellSize.value = Math.sin(elapsed * 0.0001) + 2
18+
sectionSize.value = Math.cos(elapsed * 0.0001) + 2
19+
}, 1000 / 30)
20+
</script>
21+
22+
<template>
23+
<CubeBoyDancefloorGrid
24+
:args="[10.5, 10.5]"
25+
:cell-size="cellSize"
26+
:cell-color="cellColor"
27+
:cell-thickness="cellThickness"
28+
:section-size="sectionSize"
29+
:section-thickness="sectionThickness"
30+
:section-color="sectionColor"
31+
:infinite-grid="true"
32+
:fade-from="0"
33+
:fade-distance="12"
34+
:fade-strength="1"
35+
/>
36+
</template>
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
<script setup lang="ts">
2+
import { shaderMaterial } from './shaderMaterial'
3+
import type { ColorRepresentation, PlaneGeometry, ShaderMaterial, Side, Uniform } from 'three'
4+
import { BackSide, Color, Mesh, Plane, Vector3 } from 'three'
5+
import { extend, useLoop } from '@tresjs/core'
6+
import { shallowRef } from 'vue'
7+
8+
/**
9+
Based on
10+
https://github.com/Fyrestar/THREE.InfiniteGridHelper by https://github.com/Fyrestar
11+
and https://github.com/threlte/threlte/blob/main/packages/extras/src/lib/components/Grid/Grid.svelte
12+
by https://github.com/grischaerbe and https://github.com/jerzakm
13+
*/
14+
15+
export interface GridMaterialType {
16+
/** Cell size, default: 0.5 */
17+
cellSize?: number
18+
/** Cell thickness, default: 0.5 */
19+
cellThickness?: number
20+
/** Cell color, default: black */
21+
cellColor?: ColorRepresentation
22+
/** Section size, default: 1 */
23+
sectionSize?: number
24+
/** Section thickness, default: 1 */
25+
sectionThickness?: number
26+
/** Section color, default: #2080ff */
27+
sectionColor?: ColorRepresentation
28+
/** Follow camera, default: false */
29+
followCamera?: boolean
30+
/** Display the grid infinitely, default: false */
31+
infiniteGrid?: boolean
32+
/** Fade distance, default: 100 */
33+
fadeDistance?: number
34+
/** Fade strength, default: 1 */
35+
fadeStrength?: number
36+
/** Fade from camera (1) or origin (0), or somewhere in between, default: camera */
37+
fadeFrom?: number
38+
/** Material side, default: THREE.BackSide */
39+
side?: Side
40+
}
41+
42+
export type GridProps = GridMaterialType & {
43+
/** Default plane-geometry arguments */
44+
args?: ConstructorParameters<typeof PlaneGeometry>
45+
}
46+
47+
const props = withDefaults(defineProps<GridProps>(), {
48+
cellColor: '#000000',
49+
sectionColor: '#0000ff',
50+
cellSize: 0.5,
51+
sectionSize: 1,
52+
followCamera: false,
53+
infiniteGrid: false,
54+
fadeDistance: 100,
55+
fadeStrength: 1,
56+
fadeFrom: 1,
57+
cellThickness: 0.5,
58+
sectionThickness: 1,
59+
side: BackSide,
60+
})
61+
62+
const GridMaterial = shaderMaterial(
63+
{
64+
cellSize: 0.5,
65+
sectionSize: 1,
66+
fadeDistance: 100,
67+
fadeStrength: 1,
68+
fadeFrom: 1,
69+
cellThickness: 0.5,
70+
sectionThickness: 1,
71+
cellColor: new Color(),
72+
sectionColor: new Color(),
73+
infiniteGrid: false,
74+
followCamera: false,
75+
worldCamProjPosition: new Vector3(),
76+
worldPlanePosition: new Vector3(),
77+
},
78+
/* glsl */ `
79+
varying vec3 localPosition;
80+
varying vec4 worldPosition;
81+
82+
uniform vec3 worldCamProjPosition;
83+
uniform vec3 worldPlanePosition;
84+
uniform float fadeDistance;
85+
uniform bool infiniteGrid;
86+
uniform bool followCamera;
87+
88+
void main() {
89+
localPosition = position.xzy;
90+
if (infiniteGrid) localPosition *= 1.0 + fadeDistance;
91+
92+
worldPosition = modelMatrix * vec4(localPosition, 1.0);
93+
if (followCamera) {
94+
worldPosition.xyz += (worldCamProjPosition - worldPlanePosition);
95+
localPosition = (inverse(modelMatrix) * worldPosition).xyz;
96+
}
97+
98+
gl_Position = projectionMatrix * viewMatrix * worldPosition;
99+
}
100+
`,
101+
/* glsl */ `
102+
varying vec3 localPosition;
103+
varying vec4 worldPosition;
104+
105+
uniform vec3 worldCamProjPosition;
106+
uniform float cellSize;
107+
uniform float sectionSize;
108+
uniform vec3 cellColor;
109+
uniform vec3 sectionColor;
110+
uniform float fadeDistance;
111+
uniform float fadeStrength;
112+
uniform float fadeFrom;
113+
uniform float cellThickness;
114+
uniform float sectionThickness;
115+
116+
float getGrid(float size, float thickness) {
117+
vec2 r = localPosition.xz / size;
118+
vec2 grid = abs(fract(r - 0.5) - 0.5) / fwidth(r);
119+
float line = min(grid.x, grid.y) + 1.0 - thickness;
120+
return 1.0 - min(line, 1.0);
121+
}
122+
123+
void main() {
124+
float g1 = getGrid(cellSize, cellThickness);
125+
float g2 = getGrid(sectionSize, sectionThickness);
126+
127+
vec3 from = worldCamProjPosition*vec3(fadeFrom);
128+
float dist = distance(from, worldPosition.xyz);
129+
float d = 1.0 - min(dist / fadeDistance, 1.0);
130+
vec3 color = mix(cellColor, sectionColor, min(1.0, sectionThickness * g2));
131+
132+
gl_FragColor = vec4(color, (g1 + g2) * pow(d, fadeStrength));
133+
gl_FragColor.a = mix(0.75 * gl_FragColor.a, gl_FragColor.a, g2);
134+
if (gl_FragColor.a <= 0.0) discard;
135+
136+
#include <tonemapping_fragment>
137+
#include <colorspace_fragment>
138+
}
139+
`,
140+
)
141+
extend({ GridMaterial })
142+
143+
const ref = shallowRef<Mesh>(new Mesh())
144+
const plane = new Plane()
145+
const upVector = new Vector3(0, 1, 0)
146+
const zeroVector = new Vector3(0, 0, 0)
147+
148+
const { onBeforeRender } = useLoop()
149+
150+
onBeforeRender((state) => {
151+
if (!state.camera) { return }
152+
plane.setFromNormalAndCoplanarPoint(upVector, zeroVector).applyMatrix4(ref.value.matrixWorld)
153+
154+
const gridMaterial = ref.value.material as ShaderMaterial
155+
const worldCamProjPosition = gridMaterial.uniforms.worldCamProjPosition as Uniform<Vector3>
156+
const worldPlanePosition = gridMaterial.uniforms.worldPlanePosition as Uniform<Vector3>
157+
158+
plane.projectPoint(state.camera.value!.position, worldCamProjPosition.value)
159+
worldPlanePosition.value.set(0, 0, 0).applyMatrix4(ref.value.matrixWorld)
160+
})
161+
</script>
162+
163+
<template>
164+
<TresMesh ref="ref" :frustum-culled="false">
165+
<TresGridMaterial
166+
:transparent="true"
167+
:extensions-derivatives="true"
168+
:side="props.side"
169+
:cell-size="props.cellSize"
170+
:section-size="props.sectionSize"
171+
:cell-color="props.cellColor"
172+
:section-color="props.sectionColor"
173+
:cell-thickness="props.cellThickness"
174+
:section-thickness="props.sectionThickness"
175+
:fade-distance="props.fadeDistance"
176+
:fade-strength="props.fadeStrength"
177+
:fade-from="props.fadeFrom"
178+
:infinite-grid="props.infiniteGrid"
179+
:follow-camera="props.followCamera"
180+
/>
181+
<TresPlaneGeometry :args="props.args" />
182+
</TresMesh>
183+
</template>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script setup lang="ts">
2+
3+
</script>
4+
<template>
5+
<TresAmbientLight :intensity="0.4" />
6+
<TresDirectionalLight
7+
:position="[5, 5, 5]"
8+
:intensity="1"
9+
color="red"
10+
cast-shadow
11+
/>
12+
<TresDirectionalLight
13+
:position="[-5, 5, -5]"
14+
:intensity="1"
15+
color="blue"
16+
cast-shadow
17+
/>
18+
</template>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<script lang="ts" setup>
2+
import { MathUtils } from 'three'
3+
4+
const playing = shallowRef(false)
5+
6+
function onClick() {
7+
playing.value = !playing.value
8+
}
9+
10+
const attribution = 'Just Enough by Yarin Primak from Artlist.io'
11+
const attributionLength = shallowRef(0)
12+
const attributionDisplay = computed(() => attribution.slice(0, Math.min(attribution.length, attributionLength.value)))
13+
const typing = shallowRef(false)
14+
let attributionLengthTarget = 0
15+
let timeout: ReturnType<typeof setTimeout> = setTimeout(() => { }, 0)
16+
17+
function nextLetter() {
18+
attributionLength.value = MathUtils.clamp(
19+
attributionLength.value + Math.sign(attributionLengthTarget - attributionLength.value),
20+
0,
21+
attribution.length,
22+
)
23+
typing.value = attributionLengthTarget != attributionLength.value
24+
if (typing.value) {
25+
clearTimeout(timeout)
26+
timeout = setTimeout(nextLetter, 25)
27+
}
28+
}
29+
30+
watch(playing, (p) => {
31+
if (p) {
32+
attributionLengthTarget = attribution.length
33+
}
34+
else {
35+
attributionLengthTarget = 0
36+
}
37+
nextLetter()
38+
})
39+
</script>
40+
41+
<template>
42+
<div
43+
:class="[
44+
'flex justify-end items-center gap-1 fixed right-0 bottom-0 z-10',
45+
'my-3 mb-24 pr-3 border border-r-0 border-white text-white font-mono',
46+
'whitespace-pre-wrap cursor-pointer transition-colors duration-500',
47+
typing ? 'text-cyan-400 bg-indigo-950' : 'bg-purple-900 hover:bg-purple-800'
48+
]"
49+
>
50+
<UButton
51+
:icon="playing ? 'i-lucide-volume-2' : 'i-lucide-volume-x'"
52+
size="lg"
53+
variant="ghost"
54+
color="white"
55+
class=" hover:text-yellow-400 transition-colors duration-500"
56+
@click="onClick"
57+
/>
58+
<audio v-if="playing" autoplay loop
59+
src="/music/yarin-primak-just-enough.mp3">
60+
<a href="https://artlist.io/royalty-free-music/song/just-enough/137412"> Download audio </a>
61+
</audio>
62+
<ULink
63+
to="https://artlist.io/royalty-free-music/song/just-enough/137412"
64+
class="text-white hover:text-yellow-400 transition-colors duration-500"
65+
external
66+
>
67+
{{ attributionDisplay }}
68+
</ULink>
69+
</div>
70+
</template>
71+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script setup lang="ts">
2+
3+
</script>
4+
5+
<template>
6+
<TresCanvas window-size :clear-color="'#000'">
7+
<!-- Experiment content will go here -->
8+
<TresPerspectiveCamera :position="[9,9,9]" :fov="50" />
9+
<OrbitControls />
10+
<CubeBoyDancefloorCubeBoy />
11+
<CubeBoyDancefloorDanceFloor />
12+
<CubeBoyDancefloorLighting />
13+
<TheScreenshot />
14+
</TresCanvas>
15+
<CubeBoyDancefloorMusicPlayer />
16+
</template>

0 commit comments

Comments
 (0)