codepens/three-js-dancing-tree-for-xmas/dist/script.js

346 lines
9.9 KiB
JavaScript
Raw Normal View History

2023-10-06 23:12:53 +02:00
import * as THREE from "https://cdn.skypack.dev/three@0.135.0";
import {
BloomEffect,
EffectComposer,
EffectPass,
RenderPass,
BlendFunction,
KernelSize } from
"https://cdn.skypack.dev/postprocessing";
import { gsap } from "https://cdn.skypack.dev/gsap@3.8.0";
class World {
constructor({
canvas,
width,
height,
cameraPosition,
fieldOfView = 75,
nearPlane = 0.1,
farPlane = 100 })
{
this.parameters = {
count: 1500,
max: 12.5 * Math.PI,
a: 2,
c: 4.5 };
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color("#00101a");
this.clock = new THREE.Clock();
this.data = 0;
this.time = { current: 0, t0: 0, t1: 0, t: 0, frequency: 0.0005 };
this.angle = { x: 0, z: 0 };
this.width = width || window.innerWidth;
this.height = height || window.innerHeight;
this.aspectRatio = this.width / this.height;
this.fieldOfView = fieldOfView;
this.camera = new THREE.PerspectiveCamera(
this.fieldOfView,
this.aspectRatio,
nearPlane,
farPlane);
this.camera.position.set(
cameraPosition.x,
cameraPosition.y,
cameraPosition.z);
this.scene.add(this.camera);
this.renderer = new THREE.WebGLRenderer({
canvas,
powerPreference: "high-performance",
antialias: false,
stencil: false,
depth: false });
this.pixelRatio = Math.min(window.devicePixelRatio, 2);
this.renderer.setPixelRatio(this.pixelRatio);
this.renderer.setSize(this.width, this.height);
this.timer = 0;
this.addToScene();
this.addButton();
this.render();
this.postProcessing();
this.listenToResize();
}
start() {}
render() {
this.renderer.render(this.scene, this.camera);
this.composer && this.composer.render();
}
loop() {
this.time.elapsed = this.clock.getElapsedTime();
this.time.delta = Math.min(
60,
(this.time.current - this.time.elapsed) * 1000);
if (this.analyser && this.isRunning) {
this.time.t = this.time.elapsed - this.time.t0 + this.time.t1;
this.data = this.analyser.getAverageFrequency();
this.data *= this.data / 2000;
this.angle.x += this.time.delta * 0.001 * 0.63;
this.angle.z += this.time.delta * 0.001 * 0.39;
const justFinished = this.isRunning && !this.sound.isPlaying;
if (justFinished) {
this.time.t1 = this.time.t;
this.audioBtn.textContent = "Play again";
this.audioBtn.disabled = false;
this.isRunning = false;
const tl = gsap.timeline();
this.angle.x = 0;
this.angle.z = 0;
tl.to(this.camera.position, {
x: 0,
z: 4.5,
duration: 4,
ease: "expo.in" });
tl.to(this.audioBtn, {
opacity: () => 1,
duration: 1,
ease: "power1.out" });
} else {
this.camera.position.x = Math.sin(this.angle.x) * this.parameters.a;
this.camera.position.z = Math.min(
Math.max(Math.cos(this.angle.z) * this.parameters.c, -4.5),
4.5);
}
}
this.camera.lookAt(this.scene.position);
this.spiralMaterial.uniforms.uTime.value +=
this.time.delta * this.time.frequency * (1 + this.data * 0.2);
this.extMaterial.uniforms.uTime.value +=
this.time.delta * this.time.frequency;
//this.mesh.rotation.y += 0.0001 * this.time.delta * data
for (const octa of this.octas.children) {
octa.rotation.y += this.data ?
0.001 * this.time.delta * this.data / 5 :
0.001 * this.time.delta;
}
this.octas.rotation.y -= 0.0002 * this.time.delta;
this.externalSphere.rotation.y += 0.0001 * this.time.delta;
this.render();
this.time.current = this.time.elapsed;
requestAnimationFrame(this.loop.bind(this));
}
listenToResize() {
window.addEventListener("resize", () => {
// Update sizes
this.width = window.innerWidth;
this.height = window.innerHeight;
// Update camera
this.camera.aspect = this.width / this.height;
this.camera.updateProjectionMatrix();
// Update renderer
this.renderer.setSize(this.width, this.height);
this.composer.setSize(this.width, this.height);
});
}
addSpiral() {
this.spiralMaterial = new THREE.ShaderMaterial({
vertexShader: document.getElementById("vertexShader").textContent,
fragmentShader: document.getElementById("fragmentShader").textContent,
uniforms: {
uTime: { value: 0 },
uSize: { value: 0.045 } },
depthWrite: false,
blending: THREE.AdditiveBlending });
const count = this.parameters.count; //2000
const scales = new Float32Array(count * 1);
const colors = new Float32Array(count * 3);
const phis = new Float32Array(count);
const randoms = new Float32Array(count);
const randoms1 = new Float32Array(count);
const colorChoices = ["pink", "green", "cyan", "wheat", "red"];
const squareGeometry = new THREE.PlaneGeometry(1, 1);
this.instancedGeometry = new THREE.InstancedBufferGeometry();
Object.keys(squareGeometry.attributes).forEach(attr => {
this.instancedGeometry.attributes[attr] = squareGeometry.attributes[attr];
});
this.instancedGeometry.index = squareGeometry.index;
this.instancedGeometry.maxInstancedCount = count;
for (let i = 0; i < count; i++) {
const i3 = 3 * i;
const colorIndex = Math.floor(Math.random() * colorChoices.length);
const color = new THREE.Color(colorChoices[colorIndex]);
phis[i] = Math.random() * this.parameters.max;
randoms[i] = Math.random();
scales[i] = Math.random();
colors[i3 + 0] = color.r;
colors[i3 + 1] = color.g;
colors[i3 + 2] = color.b;
}
this.instancedGeometry.setAttribute(
"phi",
new THREE.InstancedBufferAttribute(phis, 1, false));
this.instancedGeometry.setAttribute(
"random",
new THREE.InstancedBufferAttribute(randoms, 1, false));
this.instancedGeometry.setAttribute(
"aScale",
new THREE.InstancedBufferAttribute(scales, 1, false));
this.instancedGeometry.setAttribute(
"aColor",
new THREE.InstancedBufferAttribute(colors, 3, false));
this.spiral = new THREE.Mesh(this.instancedGeometry, this.spiralMaterial);
this.scene.add(this.spiral);
}
addExternalSphere() {
this.extMaterial = new THREE.ShaderMaterial({
vertexShader: document.getElementById("vertexShaderExt").textContent,
fragmentShader: document.getElementById("fragmentShaderExt").textContent,
uniforms: {
uTime: { value: 0 },
uColor: { value: new THREE.Color("orange") } },
wireframe: true,
transparent: true });
const geometry = new THREE.SphereGeometry(6, 128, 128);
this.externalSphere = new THREE.Mesh(geometry, this.extMaterial);
this.scene.add(this.externalSphere);
}
addOctahedron({ color = "white", scale, position = [0, 0, 0] }) {
const octa = new THREE.Mesh(
this.octaGeometry,
new THREE.MeshBasicMaterial({
wireframe: true,
color }));
octa.scale.set(...scale);
octa.position.set(...position);
this.octas.add(octa);
}
addOctahedrons() {
this.octas = new THREE.Group();
this.octaGeometry = new THREE.OctahedronGeometry(0.2, 0);
this.addOctahedron({ color: "red", scale: [1, 1.4, 1] });
this.addOctahedron({
color: "tomato",
position: [0, 0.85, 0],
scale: [0.5, 0.7, 0.5] });
this.addOctahedron({
color: "red",
position: [1, -0.75, 0],
scale: [0.5, 0.7, 0.5] });
this.addOctahedron({
color: "tomato",
position: [-0.75, -1.75, 0],
scale: [1, 1.2, 1] });
this.addOctahedron({
color: "red",
position: [0.5, -1.2, 0.5],
scale: [0.25, 0.37, 0.25] });
this.scene.add(this.octas);
}
addToScene() {
this.addSpiral();
this.addExternalSphere();
this.addOctahedrons();
}
addButton() {
this.audioBtn = document.querySelector("button");
this.audioBtn.addEventListener("click", () => {
this.audioBtn.disabled = true;
if (this.analyser) {
this.sound.play();
this.time.t0 = this.time.elapsed;
this.data = 0;
this.isRunning = true;
gsap.to(this.audioBtn, {
opacity: 0,
duration: 1,
ease: "power1.out" });
} else {
this.audioBtn.textContent = "Loading...";
this.loadMusic().then(() => {
console.log("music loaded");
});
}
});
}
loadMusic() {
return new Promise(resolve => {
const listener = new THREE.AudioListener();
this.camera.add(listener);
// create a global audio source
this.sound = new THREE.Audio(listener);
const audioLoader = new THREE.AudioLoader();
audioLoader.load(
"https://assets.codepen.io/74321/short-snow_01.mp3",
buffer => {
this.sound.setBuffer(buffer);
this.sound.setLoop(false);
this.sound.setVolume(0.5);
this.sound.play();
this.analyser = new THREE.AudioAnalyser(this.sound, 32);
// get the average frequency of the sound
const data = this.analyser.getAverageFrequency();
this.isRunning = true;
this.t0 = this.time.elapsed;
resolve(data);
},
progress => {
gsap.to(this.audioBtn, {
opacity: () => 1 - progress.loaded / progress.total,
duration: 1,
ease: "power1.out" });
},
error => {
console.log(error);
});
});
}
postProcessing() {
this.composer = new EffectComposer(this.renderer);
this.composer.addPass(new RenderPass(this.scene, this.camera));
this.composer.addPass(
new EffectPass(
this.camera,
new BloomEffect({
blendFunction: BlendFunction.SCREEN,
kernelSize: KernelSize.MEDIUM,
luminanceThreshold: 0.4,
intensity: 2.6,
luminanceSmoothing: 0.4,
height: 480 })));
}}
const world = new World({
canvas: document.querySelector("canvas.webgl"),
cameraPosition: { x: 0, y: 0, z: 4.5 } });
world.loop();