codepens/moving-mirror-web-audio-api.../dist/script.js

1009 lines
27 KiB
JavaScript

/*
Audio-Reactive 3D Visuals ✨ -
Move your mouse over ✨
Inspired by the Ultrafragola Mirror.
No 3D models have been downloaded here, everything is created with three.js.
Thanks to:
@EllenProbst for modifying audio data -
https://codepen.io/EllenProbst/pen/RQQmJK
@prisoner849 for round-edged box and closed tube geoms -
https://discourse.threejs.org/t/round-edged-box
https://discourse.threejs.org/t/end-caps-of-tubegeometry/9655/6
@marco_fugaro for calc vertex normals in MeshPhysicalMaterial -
https://discourse.threejs.org/t/calculating-vertex-normals-after-displacement-in-the-vertex-shader/16989
Music: 'You're So Vain' by Carly Simon
October 2020, Anna the Scavenger
*/
'use strict';
let scene, camera, renderer, container;
let cloth, mirror, wavyFrame;
let sculptureLeft, sculptureRight;
let isSculptureDancing = false;
let materials;
// AUDIO REACTIVE MESHES
let audioSpheres = [];
let audioPastilles = [];
// AUDIO
const audioURL = "https://assets.codepen.io/911157/Carly_Simon_Youre_So_Vain.mp3";
let isAudioPlaying = false;
let analyser;
let audioData = [];
let freqData = [];
// MOUSE
let mouseX = 0;
let time = 0;
// LANDSCAPE / PORTRAIT
let isMobile = /(Android|iPhone|iOS|iPod|iPad)/i.test(navigator.userAgent);
let windowRatio = window.innerWidth / window.innerHeight;
let isLandscape = (windowRatio > 1) ? true : false;
let paramsLandscape = {
mirrorY: 40,
centerLeftX: -220,
centerLeftY: -100
};
let paramsPortrait = {
mirrorY: 40,
centerLeftX: -160,
centerLeftY: -70
};
let params = isLandscape ? paramsLandscape : paramsPortrait;
// BACKGROUND RADIAL GRADIENT
let bgColors = [
// center
[230, 146, 181],
[230, 146, 181],
// outside
[230, 230, 230],
[125, 150, 150]
];
window.onload = function() {
introAnimation();
init();
render();
};
function introAnimation() {
const overlayContainer = document.querySelector("#overlay-container");
const sceneContainer = document.querySelector("#scene-container");
overlayContainer.style.animation = "fadeOut 1.1s ease-out forwards";
sceneContainer.style.animation = "fadeIn 1.1s ease-in forwards";
const credits = document.querySelector('#credits');
credits.style.animation = "fadeIn3 8s ease-out forwards";
const btnPlay = document.querySelector("#btn-play");
btnPlay.addEventListener("click", onClick);
btnPlay.style.animation = "fadeIn2 2.5s ease-in forwards";
}
function init() {
// CAMERA, LIGHTS, RENDERER
initSceneBasics();
// ALL MATERIALS AND MESHES
materials = initMaterials();
let count = 140;
initSprinkles(count);
initMirror();
initWalls();
initCloth();
initSculptureLeft();
initSculptureRight();
initPastilles();
window.addEventListener("resize", onWindowResize);
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("touchmove", onTouchMove);
}
function initSceneBasics() {
container = document.querySelector("#scene-container");
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 1800);
const cameraZ = isLandscape ? 500 : 700;
camera.position.set(0, 20, cameraZ);
camera.lookAt(0, 0, 0);
scene.add(camera);
const dirLight = new THREE.DirectionalLight(0xffffff, 1.7, 1.7);
dirLight.position.set(0, 50, -50);
scene.add(dirLight);
const dirLight3 = new THREE.DirectionalLight(0xffffff, 1.2, 1.2);
dirLight3.position.set(0, 10, -100);
dirLight3.target.position.set(50, 0, -100);
scene.add(dirLight3.target);
scene.add(dirLight3);
const dirLight2 = new THREE.DirectionalLight(0xff00ff, 1.4, 1.4);
dirLight2.position.set(-40, 0, 50);
scene.add(dirLight2);
const hemLight = new THREE.HemisphereLight(0xffffbb, 0x080820, 1.5);
hemLight.position.set(20, 50, 50);
scene.add(hemLight);
renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.physicallyCorrectLights = true;
renderer.outputEncoding = THREE.GammaEncoding;
renderer.gammaFactor = 2.2;
renderer.setPixelRatio(window.devicePixelRatio > 1.5 ? Math.min(window.devicePixelRatio, 1.5) : Math.min(window.devicePixelRatio, 1.25));
container.appendChild(renderer.domElement);
}
function SprinkleGeometry(arc = 0.5, radius = 35, thickness = 2.5) {
this.arc = arc * Math.PI;
this.torusRadius = radius;
this.thickness = thickness;
// SPRINKLE MIDDLE
this.ArcGeom = new THREE.TorusBufferGeometry(this.torusRadius, this.thickness, 10, 15, this.arc);
let sphereRadius = this.thickness;
// SPRINKLE END 1 - HALF CIRCLE
this.End1Geom = new THREE.SphereBufferGeometry(sphereRadius, 10, 10, 0, Math.PI);
this.End1Geom.translate(0, this.torusRadius, 0);
this.End1Geom.rotateY(- Math.PI / 2);
this.End1Geom.rotateZ(- (0.5 * Math.PI - this.arc));
// SPRINKLE END 1 - FULL CIRCLE
this.End1SphereGeom = new THREE.SphereBufferGeometry(sphereRadius, 10, 10, 0, Math.PI * 2);
this.End1SphereGeom.translate(0, this.torusRadius, 0);
// SPRINKLE END 2 - HALF CIRCLE
this.End2Geom = new THREE.SphereBufferGeometry(sphereRadius, 10, 10, 0, Math.PI);
this.End2Geom.rotateX(Math.PI / 2);
this.End2Geom.translate(this.torusRadius, 0, 0);
}
function initSprinkles(n) {
let count = n;
const iPinkMat = new THREE.MeshPhongMaterial({
color: 0xffffff,
shininess: 100,
specular: 0x383636
});
const iGreenMat = new THREE.MeshPhongMaterial({
color: 0xffffff,
shininess: 100,
specular: 0x383636
});
const iBlackMat = new THREE.MeshPhongMaterial({
color: 0xffffff,
shininess: 100,
specular: 0x383636
});
// Assign random colors to the sprinkles
let color = new THREE.Color();
let color1 = new THREE.Color();
let color2 = new THREE.Color();
let aColorFloat32 = new Float32Array(count * 3);
let aColorEnd1Float32 = new Float32Array(count * 3);
let aColorEnd2Float32 = new Float32Array(count * 3);
let sprinklePalette = [
0xffffff, // white
0xffe9c9,
0xffe9c9,
0x6e9377, // green
0x6e9377,
0xff84bd, // pink
0xff84bd,
0xff9977, // peach
0x000000, // black
];
for (let i = 0; i < count; i ++) {
color.setHex(sprinklePalette[Math.floor(Math.random() * sprinklePalette.length)]);
color1.setHex(sprinklePalette[Math.floor(Math.random() * sprinklePalette.length)]);
color2.setHex(sprinklePalette[Math.floor(Math.random() * sprinklePalette.length)]);
color.toArray(aColorFloat32, i * 3);
color1.toArray(aColorEnd1Float32, i * 3);
color2.toArray(aColorEnd2Float32, i * 3);
}
const sprinkleArcGeom = new SprinkleGeometry().ArcGeom;
const sprinkleEnd1Geom = new SprinkleGeometry().End1Geom;
const sprinkleEnd2Geom = new SprinkleGeometry().End2Geom;
sprinkleArcGeom.setAttribute('color', new THREE.InstancedBufferAttribute(aColorFloat32, 3));
iPinkMat.vertexColors = true;
sprinkleEnd1Geom.setAttribute('color', new THREE.InstancedBufferAttribute(aColorEnd1Float32, 3));
iGreenMat.vertexColors = true;
sprinkleEnd2Geom.setAttribute('color', new THREE.InstancedBufferAttribute(aColorEnd2Float32, 3));
iBlackMat.vertexColors = true;
let instancedSprinkleArc = new THREE.InstancedMesh(sprinkleArcGeom, iPinkMat, count);
let instancedSprinkleEnd1 = new THREE.InstancedMesh(sprinkleEnd1Geom, iGreenMat, count);
let instancedSprinkleEnd2 = new THREE.InstancedMesh(sprinkleEnd2Geom, iBlackMat, count);
let dummy = new THREE.Object3D();
for (let i = 0; i < count; i ++) {
let theta = THREE.Math.randFloatSpread(90);
let phi = THREE.Math.randFloatSpread(90);
dummy.position.set(
Math.floor(Math.random() * (375 - (-375)) - 375),
Math.floor(Math.random() * (100 - (-50)) - 50),
Math.floor(Math.random() * (-70 - (-270)) - 270)
);
dummy.rotation.set(
Math.random() * 2 * Math.PI,
Math.random() * 2 * Math.PI,
0
);
dummy.updateMatrix();
instancedSprinkleArc.setMatrixAt( i, dummy.matrix );
instancedSprinkleEnd1.setMatrixAt( i, dummy.matrix );
instancedSprinkleEnd2.setMatrixAt( i, dummy.matrix );
}
const sprinklesGroup = new THREE.Group();
sprinklesGroup.add(instancedSprinkleArc);
sprinklesGroup.add(instancedSprinkleEnd1);
sprinklesGroup.add(instancedSprinkleEnd2);
scene.add(sprinklesGroup);
}
function closedCapsTube(pts, thickness, segments) {
const closedTubeGroup = new THREE.Group();
// CURVE SHAPING THE SCULPTURE
let points = pts;
let pScale = 1.5;
for (let i = 0; i < points.length; i++) {
let x = points[i][0] * pScale;
let y = points[i][1] * pScale;
let z = points[i][2] * pScale;
points[i] = new THREE.Vector3(x, z, -y);
}
let curve = new THREE.CatmullRomCurve3(points);
// console.log(points);
let p = curve.getPoints(20);
let curveSmooth = new THREE.CatmullRomCurve3(p);
let geom = new THREE.TubeBufferGeometry(curveSmooth, 180, thickness, 20, false);
let tube = new THREE.Mesh(geom, materials.peachStandard);
closedTubeGroup.add(tube);
// TUBE START POINTS
let pos = geom.attributes.position;
let startPoints = [];
startPoints.push(curve.getPoint(0));
for (let i = 0; i <= geom.parameters.radialSegments; i++) {
startPoints.push(new THREE.Vector3().fromBufferAttribute(pos, i));
}
let pointsStartGeom = new THREE.BufferGeometry().setFromPoints(startPoints);
let psgPos = pointsStartGeom.attributes.position;
let indexStart = [];
for (let i = 1; i < psgPos.count - 1; i++) {
indexStart.push(0, i, i + 1);
}
pointsStartGeom.setIndex(indexStart);
pointsStartGeom.computeVertexNormals();
// CAP MESH START
let capStart = new THREE.Mesh(pointsStartGeom, materials.peachStandard);
closedTubeGroup.add(capStart);
// TUBE END POINTS
let endPoints = [];
endPoints.push(curve.getPoint(1));
for (let i = (geom.parameters.radialSegments + 1) * geom.parameters.tubularSegments; i < pos.count; i++) {
endPoints.push(new THREE.Vector3().fromBufferAttribute(pos, i));
}
let pointsEndGeom = new THREE.BufferGeometry().setFromPoints(endPoints);
let pegPos = pointsEndGeom.attributes.position;
let indexEnd = [];
for (let i = 1; i < pegPos.count - 1; i++) {
indexEnd.push(0, i + 1, i);
}
pointsEndGeom.setIndex(indexEnd);
pointsEndGeom.computeVertexNormals();
// CAP MESH END
let capEnd = new THREE.Mesh(pointsEndGeom, materials.peachStandard);
closedTubeGroup.add(capEnd);
return closedTubeGroup;
}
function initSculptureRight() {
sculptureRight = new THREE.Group();
let points = getExportedPoints().sculpturePoints;
const tubeBig = closedCapsTube(points, 0.75, 240);
const scale = 11;
tubeBig.scale.set(scale, scale, scale);
tubeBig.position.set(0, 70, 0);
sculptureRight.add(tubeBig);
const sphereGeom = new THREE.SphereBufferGeometry(25, 20, 15);
const sphereCheck2 = new THREE.Mesh(sphereGeom, materials.checkerboardPhong);
sphereCheck2.position.set(55, 30, 15);
sphereCheck2.rotation.x = Math.PI / 2.5;
sphereCheck2.rotation.z = -Math.PI / 1.25;
sculptureRight.add(sphereCheck2);
const sphereCheck3 = sphereCheck2.clone();
sphereCheck3.position.set(-10, 60, -30);
sphereCheck3.rotation.x = Math.PI / 3.5;
sphereCheck3.rotation.z = -Math.PI / 1.5;
sculptureRight.add(sphereCheck3);
const sphere = new THREE.Mesh(sphereGeom, materials.chestnut);
sphere.position.set(17, -45, 0);
sphere.scale.set(0.55, 0.55, 0.55);
sculptureRight.add(sphere);
sculptureRight.position.x = 210;
sculptureRight.position.y = -70;
scene.add(sculptureRight);
audioSpheres.push(sphereCheck2, sphereCheck3);
}
function initSculptureLeft() {
sculptureLeft = new THREE.Group();
const doodle = new THREE.Group();
let thickness = 2.5;
let t = thickness;
const torusGeom = new THREE.TorusBufferGeometry(22, t, 16, 20, Math.PI);
const torusGeom2 = new THREE.TorusBufferGeometry(22, t, 16, 20, Math.PI / 2);
const torusGeom3 = new THREE.TorusBufferGeometry(5, t, 16, 20, Math.PI / 2);
const torusGeom4 = new THREE.TorusBufferGeometry(15, t, 16, 20, Math.PI / 2);
const torusGeom5 = new THREE.TorusBufferGeometry(40, t, 16, 20, 0.35 * Math.PI);
const cylinderGeom = new THREE.CylinderBufferGeometry(t, t, 40, 11, 1);
const torus = new THREE.Mesh(torusGeom, materials.peachStandard);
torus.position.set(0, 55, 0);
torus.rotation.x = Math.PI;
doodle.add(torus);
const torus2 = torus.clone();
torus2.rotation.x = 0;
torus2.position.copy(torus.position);
torus2.position.x = torus.position.x + 2 * torusGeom.parameters.radius;
doodle.add(torus2);
const torus3 = new THREE.Mesh(torusGeom2, materials.peachStandard);
torus3.rotation.x = Math.PI;
torus3.position.copy(torus2.position);
doodle.add(torus3);
const torus4 = new THREE.Mesh(torusGeom3, materials.peachStandard);
torus4.position.copy(torus3.position);
torus4.position.y = torus3.position.y - torusGeom2.parameters.radius - 2 * torusGeom3.parameters.tube;
torus4.rotation.z = Math.PI / 2;
doodle.add(torus4);
const cylinder = new THREE.Mesh(cylinderGeom, materials.peachStandard);
cylinder.position.copy(torus4.position);
cylinder.position.x = torus4.position.x - torusGeom3.parameters.radius;
cylinder.position.y = torus4.position.y - 19;
doodle.add(cylinder);
const torus5 = new THREE.Mesh(torusGeom4, materials.peachStandard);
torus5.position.copy(cylinder.position);
torus5.position.x = cylinder.position.x - torusGeom4.parameters.radius;
torus5.position.y = cylinder.position.y - 20;
torus5.rotation.x = Math.PI;
doodle.add(torus5);
const torus6 = new THREE.Mesh(torusGeom3, materials.peachStandard);
torus6.position.copy(torus5.position);
torus6.position.x = torus5.position.x;
torus6.rotation.y = -Math.PI;
torus6.rotation.x = Math.PI;
torus6.position.y = torus5.position.y - torusGeom4.parameters.radius + 2 * thickness;
doodle.add(torus6);
const torus7 = new THREE.Mesh(torusGeom5, materials.black);
torus7.position.copy(torus6.position);
torus7.position.x = torus6.position.x - 110;
torus7.position.y = torus6.position.y - 4;
doodle.add(torus7);
const curve = new THREE.Curve();
sculptureLeft.add(doodle);
const roundedBoxGeom = roundEdgedBoxGeom(50, 40, 40, 4, 5);
roundedBoxGeom.computeVertexNormals();
roundedBoxGeom.translate(0, .5, 0);
const roundedBox = new THREE.Mesh(roundedBoxGeom, materials.chestnut);
roundedBox.rotation.x = - 0.4 * Math.PI;
sculptureLeft.add(roundedBox);
const sphereGeom = new THREE.SphereBufferGeometry(25, 20, 15);
const sphereCheck = new THREE.Mesh(sphereGeom, materials.checkerboardPhong);
sphereCheck.position.set(-30, 70, 0);
sphereCheck.rotation.x = Math.PI / 1.2;
sculptureLeft.add(sphereCheck);
audioSpheres.push(sphereCheck);
let sphere = new THREE.Mesh(sphereGeom, materials.peachStandard);
sphere.position.set(-45, 20, 0);
sphere.scale.set(0.65, 0.65, 0.65);
sculptureLeft.add(sphere);
let sphere2 = new THREE.Mesh(sphereGeom, materials.green);
sphere2.position.set(30, 86.5, 0);
sphere2.scale.set(0.4, 0.4, 0.4);
sculptureLeft.add(sphere2);
let sphere3 = new THREE.Mesh(sphereGeom, materials.black);
sphere3.position.set(8, -30, 0);
sphere3.scale.set(0.4, 0.4, 0.4);
sculptureLeft.add(sphere3);
let sphere4 = new THREE.Mesh(sphereGeom, materials.black);
sphere4.position.set(40, 45, 0);
sphere4.scale.set(0.4, 0.4, 0.4);
sculptureLeft.add(sphere4);
sculptureLeft.position.x = -210;
sculptureLeft.position.y = -70;
scene.add(sculptureLeft);
}
function initWalls() {
// WALLS
let cameraZ = camera.position.z;
let planeZ = -500;
let distance = cameraZ - planeZ;
let viewWidth = window.innerWidth;
let viewHeight = window.innerHeight;
let aspect = camera.aspect;
let vFov = camera.fov * Math.PI / 180;
let planeHeightAtDistance = 2 * Math.tan(vFov / 2) * distance;
let planeWidthAtDistance = planeHeightAtDistance * aspect;
let planeWidth = planeWidthAtDistance;
let planeHeight = planeHeightAtDistance;
let scale = 7;
let planeGeom = new THREE.PlaneBufferGeometry(scale * planeWidth, scale * planeHeight);
planeGeom.translate(0, 0.3 * planeHeight, 0);
let beigeBasic = new THREE.MeshBasicMaterial({color: 0x91827f})
let wallFront = new THREE.Mesh(planeGeom, beigeBasic);
wallFront.position.z = 300;
wallFront.position.y = 0;
wallFront.rotateY(Math.PI);
scene.add(wallFront);
let wallWidth = planeGeom.parameters.width;
let wallHeight = planeGeom.parameters.width;
let wallLeft = new THREE.Mesh(planeGeom, beigeBasic);
wallLeft.position.copy(wallFront.position);
wallLeft.position.x = wallFront.position.x - wallWidth / 2;
wallLeft.position.z = wallFront.position.z - wallWidth / 2;
wallLeft.rotation.y = Math.PI / 2;
let wallRight = new THREE.Mesh(planeGeom, beigeBasic);
wallRight.position.copy(wallFront.position);
wallRight.position.x = wallFront.position.x + wallWidth / 2;
wallRight.position.z = wallFront.position.z - wallWidth / 2;
wallRight.rotation.y = -Math.PI / 2;
scene.add(wallLeft, wallRight);
}
function initCloth() {
const SIZE = 4;
const RESOLUTION = 64;
const geometry = new THREE.PlaneBufferGeometry(SIZE, SIZE, RESOLUTION, RESOLUTION).rotateX(-Math.PI / 2);
const material = new THREE.ShaderMaterial({
lights: true,
side: THREE.DoubleSide,
extensions: {
derivatives: true,
},
defines: {
STANDARD: '',
PHYSICAL: '',
},
uniforms: {
...THREE.ShaderLib.physical.uniforms,
roughness: {value: 0.0},
diffuse: {value: new THREE.Color(0xffffff)},
amplitude: {value: 0.4},
frequency: {value: 0.55},
speed: {value: 0.3},
time: {value: 0.0},
},
vertexShader: monkeyPatch(THREE.ShaderChunk.meshphysical_vert, {
header: `
uniform float time;
uniform float amplitude;
uniform float speed;
uniform float frequency;
varying vec2 vUv;
${noise()}
// the function which defines the displacement
float f(vec3 point) {
return noise(vec3(point.x * frequency, point.z * frequency, time * speed)) * amplitude;
}
`,
// adapted from http://tonfilm.blogspot.com/2007/01/calculate-normals-in-shader.html
main: `
vUv = uv;
float displacement = f(position);
vec3 displaced = position + normal * f(position);
float offset = ${SIZE / RESOLUTION};
vec3 neighbour1 = position + vec3(offset, 0, 0);
vec3 neighbour2 = position + vec3(0, 0, -offset);
vec3 displacedNeighbour1 = neighbour1 + normal * f(neighbour1);
vec3 displacedNeighbour2 = neighbour2 + normal * f(neighbour2);
// https://i.ya-webdesign.com/images/vector-normals-tangent-16.png
vec3 tangent = displacedNeighbour1 - displaced;
vec3 bitangent = displacedNeighbour2 - displaced;
// https://upload.wikimedia.org/wikipedia/commons/d/d2/Right_hand_rule_cross_product.svg
vec3 displacedNormal = normalize(cross(tangent, bitangent));
`,
'#include <defaultnormal_vertex>': THREE.ShaderChunk.defaultnormal_vertex.replace(
// transformedNormal will be used in the lighting calculations
'vec3 transformedNormal = objectNormal;',
`vec3 transformedNormal = displacedNormal;`
),
// transformed is the output position
'#include <displacementmap_vertex>': `
transformed += normal * displacement;
`,
}),
fragmentShader: THREE.ShaderChunk.meshphysical_frag,
});
let scale = 40;
cloth = new THREE.Mesh(geometry, material);
cloth.position.set(0, params.mirrorY - 100, 160);
cloth.rotation.y = Math.PI / 4;
cloth.scale.set(scale, scale, scale);
scene.add(cloth);
}
function initPastilles() {
const cylinderGeom = new THREE.CylinderBufferGeometry(14, 14, 5, 20);
const checkMat = materials.checkerboardPhong;
let pastillesNum = 7;
for (let i = 0; i < pastillesNum; i++) {
let p = new THREE.Mesh(cylinderGeom, checkMat);
p.position.x = -110 + 35 * i;
p.position.y = Math.floor(Math.random() * (-120 - (-160)) - 180);
p.position.z = Math.floor(Math.random() * (50 - (20)) - 20);
p.rotation.x = Math.PI * Math.random();
p.rotation.y = Math.PI * Math.random();
audioPastilles.push(p);
scene.add(p);
}
}
function generateCurve() {
let points = getExportedPoints().wavyFramePoints;
let scale = 7.75;
for (let i = 0; i < points.length; i++) {
let x = points[i][0] * scale;
let y = points[i][1] * scale;
let z = points[i][2] * scale;
points[i] = new THREE.Vector3(x, z, -y);
}
let curve = new THREE.CatmullRomCurve3(points);
return curve;
}
function initMirror() {
// MIRROR WAVY FRAME
let curve = generateCurve();
let segments = 240;
let tubeRadius = 2.0;
let tubeGeom = new THREE.TubeBufferGeometry(curve, segments, tubeRadius, 4, true);
tubeGeom.rotateX(Math.PI / 2);
let baseScale = 2;
let zFactor = -4;
let scaleXFactor = 0.3;
let scaleYFactor = 0.15;
let scaleZFactor = 0.15;
let numFrames = 5;
wavyFrame = new THREE.Group();
for (let i = 0; i < numFrames; i++) {
let frame = new THREE.Mesh(tubeGeom, materials.peachStandard);
frame.position.set(0, 0, 0 + i * zFactor);
frame.scale.x = baseScale + scaleXFactor * i;
frame.scale.y = baseScale + scaleYFactor * i;
frame.scale.z = baseScale + scaleZFactor * (i + 0.1 * i);
wavyFrame.add(frame);
}
wavyFrame.position.set(0, params.mirrorY, 45);
scene.add(wavyFrame);
// measurements of last (biggest) frame - for mirror dimensions
let frameChild = wavyFrame.children[numFrames - 1].geometry;
frameChild.computeBoundingBox();
let bBox = frameChild.boundingBox;
let frameWidth = (bBox.max.x - bBox.min.x) * (baseScale + scaleXFactor * (numFrames - 1));
let frameHeight = (bBox.max.y - bBox.min.y) * (baseScale + scaleXFactor * (numFrames - 1));
// MIRROR REFLECTOR
let WIDTH = window.innerWidth;
let HEIGHT = window.innerHeight;
let mirrorWidth = 0.85 * frameWidth;
let mirrorHeight = 0.95 * frameHeight;
let mirrorGeom = new THREE.PlaneBufferGeometry(mirrorWidth, mirrorHeight);
mirror = new THREE.Reflector(mirrorGeom, {
clipBias: 0.003,
textureWidth: WIDTH * window.devicePixelRatio,
textureHeight: HEIGHT * window.devicePixelRatio,
color: 0xc97c7c
});
mirror.position.copy(wavyFrame.position);
mirror.position.y = wavyFrame.position.y + 0;
mirror.position.z = wavyFrame.position.z - 200;
scene.add(mirror);
}
// AUDIO
function initAudio() {
const audio = new Audio();
audio.src = audioURL;
audio.controls = true;
audio.autoplay = true;
audio.crossOrigin = "anonymous";
audio.loop = true;
document.body.appendChild(audio);
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const source = audioCtx.createMediaElementSource(audio);
const volumeControl = audioCtx.createGain();
source.connect(audioCtx.destination);
source.connect(volumeControl);
analyser = audioCtx.createAnalyser();
volumeControl.connect(analyser);
analyser.connect(audioCtx.destination);
analyser.fftSize = 128;
freqData = new Uint8Array(analyser.frequencyBinCount);
getAudioData(freqData);
volumeControl.gain.value = audio.volume;
}
function getAudioData(data) {
let frequencyArray = splitFrenquencyArray(data, 3);
// Make average of frequency array
for (let i = 0; i < frequencyArray.length; i++) {
let average = 0;
for (let j = 0; j < frequencyArray[i].length; j++) {
average += frequencyArray[i][j];
}
audioData[i] = average / frequencyArray[i].length;
}
return audioData;
}
function splitFrenquencyArray(arr, n) {
let tab = Object.keys(arr).map(function(key) {
return arr[key];
});
let len = tab.length;
let result = [];
let i = 0;
while (i < len) {
let size = Math.ceil((len - i) / n--);
result.push(tab.slice(i, i + size));
i += size;
}
return result;
}
function abs(a) {
return Math.abs(a);
}
function render() {
update();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
function update() {
cloth.material.uniforms.time.value = 0.1 + 3.0 * mouseX;
cloth.material.uniforms.diffuse.value.g = 0.0 + 1.0 * abs(mouseX * mouseX * mouseX);
cloth.material.uniforms.diffuse.value.r = 0.0 + 1.0 * abs(mouseX * mouseX * mouseX);
cloth.material.uniforms.diffuse.value.b = 0.0 + 1.0 * abs(mouseX * mouseX * mouseX);
mirror.rotation.y = 0.2 * Math.PI * mouseX;
wavyFrame.rotation.y = 0.2 * Math.PI * mouseX;
if (isAudioPlaying) {
updateAudio();
sculptureLeft.position.x = -210 * Math.cos(1.5 * Math.PI * mouseX);
sculptureLeft.position.z = -280 * Math.sin(1.5 *Math.PI * mouseX);
sculptureRight.position.x = 210 * Math.cos(1.5 * Math.PI * mouseX);
sculptureRight.position.z = 290 * Math.sin(1.5 *Math.PI * mouseX);
}
}
function updateAudio() {
getAudioData(freqData);
analyser.getByteFrequencyData(freqData);
audioSpheres.forEach(sphere => {
sphere.scale.x = 0.9 + audioData[0] / 500;
sphere.scale.y = 0.9 + audioData[0] / 500;
sphere.scale.z = 0.9 + audioData[0] / 500;
});
// audioPastilles.forEach((pastille, i) => (pastille.position.y = audioData[0] / 10 - 0.9 * 170));
audioPastilles.forEach(pastille => {
pastille.scale.x = 0.9 + audioData[0] / 500;
pastille.scale.y = 0.9 + audioData[0] / 500;
pastille.scale.z = 0.9 + audioData[0] / 500;
});
}
// *** EVENTS ***
function onMouseMove(event) {
mouseX = (event.clientX / window.innerWidth) * 2 - 1;
radialGradientChange(bgColors, container);
}
function onTouchMove(event) {
let x = event.changedTouches[0].clientX;
mouseX = (x / window.innerWidth) * 2 - 1;
radialGradientChange(bgColors, container);
}
function onWindowResize() {
const renderTarget = mirror.getRenderTarget();
renderTarget.setSize(window.innerWidth * window.devicePixelRatio, window.innerHeight * window.devicePixelRatio);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onClick(e) {
e.preventDefault();
initAudio();
isAudioPlaying = !isAudioPlaying;
e.target.remove();
}
// BACKGROUND GRADIENT
function radialGradientChange(colorsArr, element) {
let colors = colorsArr;
element.style.background = `radial-gradient(circle,
rgba(
${colors[0][0] - (colors[0][0]-colors[1][0]) * Math.abs(mouseX)},
${colors[0][1] - (colors[0][1]-colors[1][1]) * Math.abs(mouseX)},
${colors[0][2] - (colors[0][2]-colors[1][2]) * Math.abs(mouseX)},
1) 0%,
rgba(
${colors[2][0] - (colors[2][0]-colors[3][0]) * Math.abs(mouseX)},
${colors[2][1] - (colors[2][1]-colors[3][1]) * Math.abs(mouseX)},
${colors[2][2] - (colors[2][2]-colors[3][2]) * Math.abs(mouseX)},
1) 100%)`;
}