<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.148.0/build/three.module.js",
"addons/": "https://cdn.jsdelivr.net/npm/three@0.148.0/examples/jsm/"
}
}
</script>
<div class="backPage">
<div class="boundA">
<p class="p1 big">
DEMO
</p>
</div>
</div>
<div class="container" id="container" style="filter: grayscale(0.4);"></div>
<div class="panelPage" style="">
<div class="uiPanel">
<div class="checkBoxes">
<div class="checkBox">
<p class="p1 small">INVERT</p>
<input type="checkbox" class="checkBoxer" id="invert">
</div>
</div>
<div class="ranges">
<div class="range">
<p class="p1 small">RADIUS</p>
<input type="range" min="0" max="20" value="6" step="0.01" class="ranger" id="radius">
</div>
<div class="range">
<p class="p1 small">POWER</p>
<input type="range" min="0.04" max="0.7" value="0.18" step="0.001" class="ranger" id="outPower">
</div>
<div class="range">
<p class="p1 small">DAMPING</p>
<input type="range" min="0.04" max="0.7" value="0.14" step="0.001" class="ranger" id="inPower">
</div>
</div>
</div>
</div>
<div class="frontPage">
<div class="bound">
</div>
</div>
<svg width="20%" viewbox="1 1 28 28" class="svger" style="display: none;">
<path fill="#fff" stroke="#fff" stroke-width="1.5"
d="M15 3
Q16.5 6.8 21 18
A6.8 6.8 0 1 1 9 18
Q13.5 6.8 15 3z" />
</svg>
<a href="https://3dpk.co.uk">
<div class="logo">
<img src="https://raw.githubusercontent.com/forerunrun/extends/main/3dpkLogoV.png">
</div>
</a>
<script src="https://cdn.jsdelivr.net/gh/forerunrun/extends@main/howler.js"></script>
@font-face {font-family: "h4"; src: url("https://cdn.jsdelivr.net/gh/forerunrun/extends@main/Michroma-Regular.ttf") format("truetype");}
html, body{
position: fixed;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
background-color: #050505;
overflow-x: hidden;
overflow-y: hidden;
font-family: 'h2';
}
* {
scrollbar-width: thin;
scrollbar-color: /*bar-->>*/ #adadad /*track-->>*/ #333333;
padding: 0;
margin: 0;
}
*::-webkit-scrollbar {
width: 8px;
}
*::-webkit-scrollbar-track {
background: rgba(220, 220, 220, 0.274);
-webkit-backdrop-filter:invert(100%);
backdrop-filter:invert(100%);
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
*::-webkit-scrollbar-thumb {
background-color: #adadad;
border-radius: 20px;
}
.container{
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100vw;
pointer-events: all;
z-index: -1;
}
p{
margin: 0px 0px 0px 0px;
padding: 0px 0px 0px 0px;
line-height: 1.1;
width: -webkit-fit-content;
width: fit-content;
height: -webkit-fit-content;
height: fit-content;
width: -moz-fit-content;
height: -moz-fit-content;
display: inline-block;
font-size: unset;
}
.p1{
margin: 0px 0px 0px 0px;
padding: 0px 0px 0px 0px;
line-height: 1.1;
width: -webkit-fit-content;
width: fit-content;
height: -webkit-fit-content;
height: fit-content;
width: -moz-fit-content;
height: -moz-fit-content;
display: inline-block;
font-size: unset;
}
.p1{
font-family: "h4";
font-size: 13px;
}
.root{
/* --col1: rgb(0 0 0);
--col2: rgba(9 9 9);
--col3: rgba(25 25 25);
transition: --col1 20.6s, --col2 20.6s, --col3 20.6s; */
--rotate1: 0deg
}
@property --col1 {
syntax: '<color>';
initial-value: rgb(0 0 0);
inherits: false;
}
@property --col2 {
syntax: '<color>';
initial-value: rgba(9 9 9);
inherits: false;
}
@property --col3 {
syntax: '<color>';
initial-value: rgba(25 25 25);
inherits: false;
}
body{
background: linear-gradient(0deg, rgb(25 25 25) 0%, rgb(9 9 9) 60%, rgb(0 0 0) 100%);
background: linear-gradient(0deg, var(--col1) 0%, var(--col2) 60%, var(--col3) 100%);
transition: --col1 0.4s, --col2 0.4s, --col3 0.4s;
color: gainsboro;
}
.glow{
/* background: linear-gradient(180deg, rgb(224 224 224) 0%, rgb(187 187 187) 60%, rgb(131 131 131) 100%); */
/* transition: background 2.6s; */
--col1: rgb(131 131 131) ;
--col2: rgb(187 187 187);
--col3: rgb(224 224 224);
transition: --col1 0.1s, --col2 0.1s, --col3 0.1s;
}
.backPage{
width: 100%;
height: 100%;
z-index: -1;
position: fixed;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
}
.big{
position: absolute;
font-size: clamp(50px, 20.5vw, 130px);
z-index: -1;
opacity: 0.7;
left: -5vw;
top: -64px;
-webkit-text-stroke: 6px #dbdbdb;
}
.small{
padding: 0px 0px 3px 0px;
font-size: 12px;
}
.frontPage{
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
}
.bound{
margin: -12vh 0px 0px 0px;
width: 90%;
height: 66vh;
border: solid 2px gainsboro;
max-width: 595px;
opacity: 0.05;
/* backdrop-filter: blur(63px); */
}
.boundA{
position: relative;
margin: -12vh 0px 0px 0px;
width: 90%;
height: 66vh;
/* border: solid 2px gainsboro; */
max-width: 595px;
/* opacity: 0.05; */
/* backdrop-filter: blur(63px); */
color: #dbdbdb;
display: flex;
justify-content: center;
}
.logo{
position: fixed;
right: 0;
bottom: 0;
width: 40px;
margin: 8px 12px;
opacity: 0.5;
cursor: pointer;
transition: opacity 0.6s;
}
.logo:hover{
opacity: 0.9;
}
.logo img{
width: 100%;
}
.panelPage{
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
pointer-events: none;
}
.uiPanel{
min-width: clamp(300px, 50vw, 333px);
display: flex;
flex-direction: column;
padding: 20px;
margin: 10px;
opacity: 0.4;
}
.ranges{
display: flex;
flex-direction: column;
margin: 7px 0px 7px 0px;
}
.range{
display: flex;
flex-direction: column;
}
.ranger {
outline: 0;
border: 0;
width: 333px;
max-width: 100%;
transition: box-shadow 0.2s ease-in-out;
padding: 4px;
pointer-events: all;
}
.checkBoxes{
}
.checkBox{
display: flex;
justify-content: space-between;
}
.checkBoxer{
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
outline: none;
font-size: 22px;
cursor: pointer;
width: 30px;
height: 30px;
background: #dddddd17;
/* border-radius:5px; */
border: 1px solid #555;
position: relative;
/* margin:0px 10px 0px 5px; */
pointer-events: all;
}
.checkBoxer:checked {
background: gainsboro;
}
.checkBoxer:checked:after {
/* content: "âœâ€ÂÂÂ"; */
position: absolute;
font-size: 80%;
left: 0.14em;
top: -0.20em;
}
@media screen and (-webkit-min-device-pixel-ratio: 0) {
.ranger {
overflow: hidden;
/* height: 40px; */
-webkit-appearance: none;
background-color: #dddddd17;
margin: 0px 0px 5px 0px;
border: 1px solid #555;
}
.ranger::-webkit-slider-runnable-track {
height: 30px;
-webkit-appearance: none;
color: #444;
transition: box-shadow 0.2s ease-in-out;
}
.ranger::-webkit-slider-thumb {
width: 30px;
-webkit-appearance: none;
height: 30px;
cursor: pointer;
background: #fff;
box-shadow: -340px 0 0 320px #ededed00, inset 0 0 0 40px #1597ff00;
border-radius: 0%;
transition: box-shadow 0.2s ease-in-out;
position: relative;
}
.ranger:active::-webkit-slider-thumb {
background: #fff;
box-shadow: -340px 0 0 320px #1597ff00, inset 0 0 0 3px #f5c9b500;
}
}
.ranger::-moz-range-progress {}
.ranger::-moz-range-track {
background-color: #9a905d;
}
.ranger::-ms-fill-lower {}
.ranger::-ms-fill-upper {
background-color: #9a905d;
}
@media (max-width: 590px) {
.panelPage{
justify-content: center;
align-items: flex-end;
display: flex;
}
.uiPanel{
background-color: #0e0e0e;
opacity: 0.8;
}
}
import * as THREE from 'three'
import { OrbitControls } from 'addons/controls/OrbitControls.js'
import { GLTFLoader } from 'addons/loaders/GLTFLoader.js';
import { RGBELoader } from 'addons/loaders/RGBELoader.js';
import { DRACOLoader } from 'addons/loaders/DRACOLoader.js';
import { Reflector } from 'addons/objects/Reflector.js';
import { MeshSurfaceSampler } from 'addons/math/MeshSurfaceSampler.js';
import { EffectComposer } from 'addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'addons/postprocessing/RenderPass.js';
import { ShaderPass } from 'addons/postprocessing/ShaderPass.js';
import { BloomPass } from 'addons/postprocessing/BloomPass.js';
import { FilmPass } from 'addons/postprocessing/FilmPass.js';
import { UnrealBloomPass } from "https://cdn.jsdelivr.net/gh/forerunrun/extends@main/UnrealBloomPassTrr.js";
import { RGBShiftShader } from 'addons/shaders/RGBShiftShader.js'
import { BadTVShader } from 'https://cdn.jsdelivr.net/gh/forerunrun/extends@main/badTV.js'
import { FilmShader } from 'addons/shaders/FilmShader.js'
import { TWEEN } from 'addons/libs/tween.module.min.js'
import {IsMobile} from 'https://cdn.jsdelivr.net/gh/forerunrun/extends@main/isMobile.js'
const isMobile = IsMobile()
const container = document.querySelector('#container');
const sizes = {
width: container.offsetWidth,
height: container.offsetHeight
}
let camPosZ = 35
let fov = 50
if (isMobile){
const desiredWidth = 20;
const aspectRatio = sizes.width / sizes.height;
const hFOV = 2 * Math.atan(desiredWidth / (2 * camPosZ)) * (180 / Math.PI);
fov = 2 * Math.atan(Math.tan(hFOV / 2 * (Math.PI / 180)) / aspectRatio) * (180 / Math.PI);
}
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(fov, sizes.width / sizes.height, 6, 1500)
const renderer = new THREE.WebGLRenderer( { antialias: true, gammaOutput: true, alpha: true } );
const pmremGenerator = new THREE.PMREMGenerator( renderer );
const controls = new OrbitControls(camera, renderer.domElement)
const composer = new EffectComposer( renderer );
const clock = new THREE.Clock()
const bloomPass = new UnrealBloomPass( new THREE.Vector2( sizes.width, sizes.height ));
const raycaster = new THREE.Raycaster()
const mouse = new THREE.Vector2()
let manager
let envMap
let intersects;
let particlesMesh;
let rainMesh
const clickable = []
let starTexture
let angelObj
let angelMesh
let sampler
let vineMesh
let floorMesh
let groundOcolor = new THREE.Color()
let pointSize = 0.15
const count = 50
const _position = new THREE.Vector3();
const pointsGeo = new THREE.BufferGeometry();
let pointsMesh
const positions = new Float32Array(count * 3);
let MAGNET_RADIUS = 6;
let RESTORATION_SPEED = 0.14;
let power = 0.18
let positionArray
let originalPositions
let positionArray1
let originalPositions1
var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
var dir = vector.sub( camera.position ).normalize();
var distance = - camera.position.z / dir.z;
var pos = new THREE.Vector3(2000,2000,2000);
let mouseDown
const pGeo = new THREE.PlaneGeometry(1,1,10,10)
const pMat = new THREE.MeshStandardMaterial({color:0xffffff})
const plane = new THREE.Mesh(pGeo, pMat)
const plane1 = plane.clone()
const plane2 = plane.clone()
let bottom
let glitchBool = false
let randA
let randB
let randC
let sin
let time = 0
let timeF = 0
let child3D
// let child3DC
let flickBool = false
let randA1
let randB1
let randC1
let groundMirror
let rgbPass
let badPass
let filmPass
let isFlickering = false;
let flickerCount = 0;
let flickerInterval;
let flickerTimeout;
let stopGlitch = false
const particlesData = [];
let positions1, colors;
let particles;
let pointCloud;
let particlePositions;
let linesMesh;
const group = new THREE.Group()
const sp = new THREE.Vector3()
const maxParticleCount = 1000;
let particleCount = 500;
const r = 70;
const effectController = {
showDots: false,
showLines: true,
minDistance: 13,
limitConnections: true,
maxConnections: 6,
particleCount: 50,
maxSpeed:0.01
};
const velocities = [];
const particlesCnt = 2000;
const eezing = TWEEN.Easing.Linear.None
let vinesDrawRange
let vinesGlow
let vinesDim
let floorGlow
let vinesDrawRangeReturn
let tweening = false
let sphere
let originalColor = new THREE.Color(0xffffff)
let lightMapLerp = new THREE.Color(0xffffff)
lightMapLerp.r = 10
lightMapLerp.g = 100
lightMapLerp.b = 10
let darkMapLerp = new THREE.Color(0x000000)
const svger = document.querySelector(".svger");
const mainGroup = new THREE.Group()
let loop = null
var vA = new THREE.Vector3(0,0, 0.5);
vA.unproject( camera );
var dirA = vA.sub( camera.position ).normalize();
var distanceA = - camera.position.z / dirA.z;
var posA = new THREE.Vector3(0,0, 0.5);
const invert = document.querySelector('#invert')
invert.addEventListener('change', function(){
console.log(this.checked);
})
const radius = document.querySelector('#radius')
radius.value = MAGNET_RADIUS
radius.addEventListener('input', function(){
MAGNET_RADIUS = this.value
})
const outPower = document.querySelector('#outPower')
outPower.value = power
outPower.addEventListener('input', function(){
power = this.value
})
const inPower = document.querySelector('#inPower')
inPower.value = RESTORATION_SPEED
inPower.addEventListener('input', function(){
RESTORATION_SPEED = this.value
})
loadAssets()
function loadAssets () {
manager = new THREE.LoadingManager();
manager.onStart = function ( url, itemsLoaded, itemsTotal ) {
};
manager.onProgress = function ( url, itemsLoaded, itemsTotal ) {
};
manager.onLoad = function ( ) {
init()
};
manager.onError = function ( url ) {
console.log( 'There was an error loading ' + url );
};
const textureLoader = new THREE.TextureLoader( manager );
const gltfLoader = new GLTFLoader( manager );
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath( 'https://raw.githubusercontent.com/forerunrun/extends/main/draco/' );
gltfLoader.setDRACOLoader( dracoLoader );
const rgbeLoader = new RGBELoader( manager )
gltfLoader.load( 'https://raw.githubusercontent.com/forerunrun/extends/main/angel_drc_02.glb', function ( gltf ) {
gltf.scene.updateMatrix()
gltf.scene.updateMatrixWorld()
gltf.scene.traverse(c=>{
if(c.isMesh){
if(c.name === 'angel'){
angelMesh = c
console.log(c.material);
c.material.side = THREE.DoubleSide
angelMesh.material.envMapIntensity=0.1
angelMesh.material.color.copy(darkMapLerp)
originalColor.copy(angelMesh.material.emissive)
originalColor.r += 0.04
originalColor.g += 0.03
originalColor.b += 0.02
angelMesh.material.emissive.copy(originalColor)
clickable.push(angelMesh)
angelMesh.material.emissiveIntensity = 6.0
bottom = c.geometry.boundingBox.min.y + c.position.y
const box = new THREE.BoxHelper( angelMesh, 0xffffff );
console.log(angelMesh);
}
if(c.name === 'ground'){
console.log(c);
c.material.color.r = 0.01
c.material.color.g = 0.01
c.material.color.b = 0.01
c.material.metalness = 0.6
c.material.roughness = 0.4
c.scale.set(0.8,0.8,0.8)
groundOcolor.copy(c.material.color)
floorMesh = c
}
if(c.name === 'vines'){
c.material.tonemapped = false
c.material.side = THREE.DoubleSide
vineMesh = c
vineMesh.geometry.index.array.reverse()
vineMesh.geometry.computeVertexNormals();
vineMesh.geometry.computeBoundingSphere();
vineMesh.geometry.computeBoundingBox();
vineMesh.geometry.drawRange.count = 0
console.log(vineMesh);
}
}
})
angelObj = gltf.scene
});
textureLoader.load('https://cdn.jsdelivr.net/gh/forerunrun/extends@main/star.png', s => {
s.magFilter = THREE.LinearFilter;
s.minFilter = THREE.LinearFilter;
starTexture = s
})
// rgbeLoader.load( './src/images/spaceS.hdr', function ( texture ) {
rgbeLoader.load( 'https://cdn.jsdelivr.net/gh/forerunrun/extends@main/studio.hdr', function ( texture ) {
envMap = pmremGenerator.fromEquirectangular( texture ).texture;
texture.dispose();
})
}
const init = () => {
scene.environment = envMap
camera.position.set(camPosZ,7,0)
scene.add(camera)
const light = new THREE.AmbientLight(0xffffff, 1, 50)
light.position.set(0, 0, 0)
scene.add(light)
const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.3 );
directionalLight.position.set(0, 1, 100)
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.gammaOutput = true
renderer.setClearColor( 0x0036ff, 0);
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio( window.devicePixelRatio );
renderer.toneMapping = THREE.LinearToneMapping
renderer.toneMappingExposure = 0.6;
container.appendChild(renderer.domElement)
controls.enableDamping = true
controls.enablePan = false
controls.enableZoom = false
controls.autoRotate = true
controls.autoRotateSpeed = 0.5
controls.maxPolarAngle = 90 * Math.PI / 180
controls.rotateSpeed = 0.5
if(isMobile){
window.addEventListener( 'touchstart', onTouchStart, false );
window.addEventListener( 'touchmove', onTouchMove, false );
window.addEventListener( 'touchend', onTouchEnd, false );
}
else{
window.addEventListener( 'mousemove', onMouseMove, false );
window.addEventListener( 'click', onMouseClick, false );
window.addEventListener( 'mousedown', onMouseDown, false );
window.addEventListener( 'mouseup', onMouseUp, false );
window.addEventListener( 'wheel', onMouseWheel, false );
}
mainGroup.add(angelObj)
mainGroup.add(floorMesh)
scene.add(mainGroup)
setupRain()
createMirror()
createParticles()
setupInitialPoints()
postEffects()
startFlicker();
plane1.scale.set(22,22,1)
plane1.position.set(4,7.5,-4)
plane1.visible = false
plane2.scale.set(22,22,1)
plane2.position.set(-4,7.5,-1)
plane2.visible = false
plane.scale.x = sizes.width / 10
plane.scale.y = sizes.height / 10
mainGroup.add(plane)
plane.visible = false
plane.name = 'planer'
clickable.push(plane)
playSound('https://cdn.jsdelivr.net/gh/forerunrun/extends@main/rain.wav',false, false, true)
animate()
}
const setupRain = () => {
const rainGeometry = new THREE.BufferGeometry()
const posArray = new Float32Array(particlesCnt * 3);
for(let i =0; i < particlesCnt * 3; i++) {
posArray[i] = (Math.random() - 0.5) * 80
}
rainGeometry.setAttribute('position', new THREE.BufferAttribute(posArray, 3))
const glowColor = new THREE.Color(0xffffff)
glowColor.r = 10
glowColor.g = 10
glowColor.b = 10
const svgString = new XMLSerializer().serializeToString(svger);
const svgUrl = 'data:image/svg+xml;base64,' + btoa(svgString);
const myImage = new Image();
myImage.onload = ()=>{
const canvas = document.createElement("canvas");
canvas.width = 1024;
canvas.height = 80;
const ctx = canvas.getContext("2d");
ctx.drawImage(
myImage,
canvas.width / 2,
0
);
let rainDropTexture = new THREE.CanvasTexture(canvas)
rainDropTexture.encoding = THREE.sRGBEncoding
// rainDropTexture.minFilter = THREE.LinearFilter
rainDropTexture.magFilter = THREE.LinearFilter
const rainMaterial = new THREE.PointsMaterial({
size: 0.8,
map: rainDropTexture,
transparent: true,
depthWrite:false,
toneMapped: false,
color: glowColor
})
rainMesh = new THREE.Points(rainGeometry, rainMaterial)
rainMesh.renderOrder = 22
mainGroup.add(rainMesh)
}
myImage.src = svgUrl;
}
const postEffects = () => {
const renderModel = new RenderPass( scene, camera );
const params = {
str: 1.1,
thr: 0.85,
rad: 0.01
};
badPass = new ShaderPass(BadTVShader);
badPass.uniforms['distortion'].value = 0;
badPass.uniforms['distortion2'].value = 0;
badPass.uniforms['speed'].value = 0.05;
badPass.uniforms['rollSpeed'].value = 0;
rgbPass = new ShaderPass(RGBShiftShader);
rgbPass.uniforms['angle'].value = -20 * Math.PI / 180;
rgbPass.uniforms['amount'].value = 0.00009;
filmPass = new ShaderPass(FilmShader);
filmPass.uniforms['sCount'].value = 1300;
filmPass.uniforms['sIntensity'].value = 0.2;
filmPass.uniforms['nIntensity'].value = 0.3;
filmPass.uniforms['grayscale'].value = 0;
bloomPass.threshold = params.thr;
bloomPass.strength = params.str;
bloomPass.radius = params.rad;
composer.setSize(sizes.width, sizes.height )
composer.setPixelRatio( window.devicePixelRatio );
composer.addPass( renderModel );
composer.addPass( bloomPass );
composer.addPass( badPass );
composer.addPass( rgbPass );
}
const createMirror = () => {
const geometry = new THREE.CircleGeometry( 15, 64 );
groundMirror = new Reflector( geometry, {
clipBias: 0.003,
textureWidth: window.innerWidth * window.devicePixelRatio,
textureHeight: window.innerHeight * window.devicePixelRatio,
color: 0x777777
} );
groundMirror.position.y = bottom + 0.17;
floorMesh.position.y = bottom + 0.05
groundMirror.rotateX( - Math.PI / 2 );
const roughMat = new THREE.MeshStandardMaterial({
color:0x000000,
transparent:true,
opacity:0.4,
// roughness:0.5,
// metalness:0.1
})
const groundRough = new THREE.Mesh(geometry, roughMat)
groundRough.rotateX( - Math.PI / 2 );
groundRough.position.y = bottom + 0.3;
mainGroup.add( groundRough );
mainGroup.add( groundMirror );
}
const setupInitialPoints = () => {
sampler = new MeshSurfaceSampler( angelMesh )
.build();
pointsGeo.copy(angelMesh.geometry)
const material = new THREE.LineBasicMaterial({
color: 0xed0bcc,
size: pointSize,
toneMapped: false,
// sizeAttenuation:true,
transparent:true,
opacity: 0.5
// alphaTest:0.5,
// map: starTexture
});
material.color.r = 0.7 / 1.5
material.color.g = 0.8 / 1.5
material.color.b = 0.9 / 1.5
pointsMesh = new THREE.Line(pointsGeo, material);
pointsMesh.visible = false
positionArray = angelMesh.geometry.attributes.position.array;
originalPositions = new Float32Array(positionArray.length);
originalPositions.set(positionArray);
positionArray1 = pointsGeo.attributes.position.array;
originalPositions1 = new Float32Array(positionArray1.length);
originalPositions1.set(positionArray1);
scene.add(pointsMesh)
pointsMesh.position.copy(angelMesh.position)
}
const createParticles = () => {
const particlesGeometry = new THREE.BufferGeometry()
const posArray = new Float32Array(particlesCnt * 3);
for(let i =0; i < particlesCnt * 3; i++) {
posArray[i] = (Math.random() - 0.5) * 180
}
particlesGeometry.setAttribute('position', new THREE.BufferAttribute(posArray, 3))
const particleMaterial = new THREE.PointsMaterial({
size: 1,
map: starTexture,
transparent: true,
// depthWrite:false
// depthTest:0.1,
opacity:0.55
})
particlesMesh = new THREE.Points(particlesGeometry, particleMaterial)
particlesMesh.renderOrder = 10
for (let i = 0; i < particlesCnt; i++) {
velocities.push(new THREE.Vector3(
Math.random() - 0.5,
Math.random() - 0.5,
Math.random() - 0.5
).normalize());
}
scene.add(particlesMesh)
}
const animateParticles = () => {
const posy = particlesMesh.geometry.attributes.position;
const elapsedTime = clock.getElapsedTime() ;
for (let i = 0; i < particlesCnt; i++) {
const deviation = new THREE.Vector3(
Math.random() - 0.5,
Math.random() - 0.5,
Math.random() - 0.5
).multiplyScalar(0.05);
velocities[i].add(deviation).clampLength(0, 0.2);
const velocity = velocities[i].clone().multiplyScalar(0.1);
const x = posy.getX(i) + velocity.x;
const y = posy.getY(i) + velocity.y;
const z = posy.getZ(i) + velocity.z;
posy.setXYZ(i, x, y, z);
const maxBounds = 50;
if (x < -maxBounds || x > maxBounds) velocities[i].x *= -1;
if (y < -maxBounds || y > maxBounds) velocities[i].y *= -1;
if (z < -maxBounds || z > maxBounds) velocities[i].z *= -1;
}
particlesMesh.geometry.attributes.position.needsUpdate = true;
}
function onMouseMove( e ) {
mouse.x = ( e.clientX / sizes.width ) * 2 - 1;
mouse.y = - ( e.clientY / sizes.height ) * 2 + 1;
if(mouseDown){
castRay(true, false, false, false)
}
}
function onMouseDown(e){
mouse.x = ( e.clientX / sizes.width ) * 2 - 1;
mouse.y = - ( e.clientY / sizes.height ) * 2 + 1;
mouseDown = true
castRay(false, true, false, false)
}
function onMouseUp(){
mouseDown = false
angelMesh.visible = true
pointsMesh.visible = false
controls.enabled = true
if(stopGlitch){
clearInterval(flickerInterval);
flickerCount = 0;
clearTimeout(flickerTimeout)
flickerTimeout = setTimeout(startFlicker, (Math.random() * 2000) + 800);
stopGlitch = false
}
}
function onTouchStart( e ) {
mouseDown = true
mouse.x = (e.targetTouches[0].pageX / sizes.width ) * 2 - 1;
mouse.y = -((e.targetTouches[0].pageY - pageYOffset) / sizes.height ) * 2 + 1;
castRay(false, false, false, true)
}
function onTouchMove( e ) {
mouse.x = (e.targetTouches[0].pageX / sizes.width ) * 2 - 1;
mouse.y = -((e.targetTouches[0].pageY - pageYOffset) / sizes.height ) * 2 + 1;
if(mouseDown){
castRay(false, false, true, false)
}
}
function onTouchEnd( e ) {
mouseDown = false
mouse.x = (e.changedTouches[0].pageX / sizes.width ) * 2 - 1;
mouse.y = -((e.changedTouches[0].pageY - pageYOffset) / sizes.height ) * 2 + 1;
controls.enabled = true
if(stopGlitch){
clearInterval(flickerInterval);
flickerCount = 0;
clearTimeout(flickerTimeout)
flickerTimeout = setTimeout(startFlicker, (Math.random() * 2000) + 800);
stopGlitch = false
}
}
function castRay(mouseM, mouseD, touchM, touchD){
raycaster.setFromCamera( mouse, camera );
intersects = raycaster.intersectObjects( clickable, true );
if(mouseM || touchM){
if(loop){
loop.frequency((mouse.y + 1.01) * 400);
}
if ( intersects.length > 0 ) {
if(mouseDown){
intersects.forEach((ints, idx)=>{
if(ints.object.name ==='angel'){
plane.position.copy(intersects[idx].point)
}
})
pos.copy(intersects[0].point)
}
}
}
if(mouseD || touchD){
if ( intersects.length > 1 && intersects.some(obj => obj.object.name === 'angel')) {
stopGlitch = true
controls.enabled = false
}
if ( intersects.length > 0 ) {
pos.copy(intersects[0].point)
}
else{
}
}
}
function glitch(){
randA = (Math.random() * 2000) + 1500
randB = (Math.random() * 1000) + 500
randC = (Math.random() * 2000) + 1500
setTimeout(function(){
glitchBool = true
setTimeout(function(){
glitchBool = false
setTimeout(function(){
glitch()
}, randC)
}, randB)
}, randA)
}
let rando = (Math.random() * 60) + 10
let rando2 = (Math.random() * 3000) + 800
let randomNum = Math.floor(Math.random() * 4) + 2;
if (randomNum % 2 !== 0) {
randomNum++;
}
function playSound(file, zap, clap, rain, loopy){
if(zap){
var sound = new Howl({
src: [file],
volume: (Math.random() * 0.25) + 0.08,
onend: function() {
this.unload();
}
});
sound.addFilter({
filterType: 'bandpass',
frequency: (Math.random() * (333 / 2)) + 111 / 2,
Q: 2.0
});
sound.rate((Math.random() * 0.8) + 0.5);
}
if (clap){
var sound = new Howl({
src: [file],
volume: ( Math.random()* 0.1) + 0.2,
onend: function() {
this.unload();
}
});
sound.addFilter({
filterType: 'bandpass',
frequency: (Math.random() * (333 / 2)) + 111 / 2,
Q: 2.0
});
sound.rate((Math.random() * 1) + 0.5);
}
if (rain){
var sound = new Howl({
src: [file],
volume:0.2,
autoplay: true,
loop: true,
});
}
if (loopy){
loop = new Howl({
src: [file],
volume:0.1,
autoplay: true,
loop: true,
// onend: function() {
// this.unload();
// }
});
loop.addFilter({
filterType: 'lowpass',
frequency: 111,
Q: 2.0
});
}
if(sound){
sound.play()
}
}
const setupSounds = () => {
const files = [
'https://cdn.jsdelivr.net/gh/forerunrun/extends@main/zap1.wav',
'https://cdn.jsdelivr.net/gh/forerunrun/extends@main/clap.wav',
'https://cdn.jsdelivr.net/gh/forerunrun/extends@main/rain.wav'
]
var sounds = [];
for (var i = 0; i < files.length; i++) {
sounds.push(new Pizzicato.Sound({
source: 'file',
options: { path: files[i] }
}));
}
if (zap) {
sounds.push(new Pizzicato.Sound({
source: 'file',
options: { path: zap }
}));
}
if (clap) {
sounds.push(new Pizzicato.Sound({
source: 'file',
options: { path: clap }
}));
}
if (rain) {
sounds.push(new Pizzicato.Sound({
source: 'file',
options: { path: rain }
}));
}
sounds.forEach(function(sound) {
sound.play();
});
}
function flickerOn() {
pointsMesh.visible = false
angelMesh.visible = true
isFlickering = true;
pointsMesh.geometry.attributes.position.array.forEach((_, index) => {
if (index % 3 === 0 && positionArray1[index + 1] > bottom - 1.2) {
pointsMesh.geometry.attributes.position.array[index] = originalPositions1[index];
pointsMesh.geometry.attributes.position.array[index + 1] = originalPositions1[index + 1];
pointsMesh.geometry.attributes.position.array[index + 2] = originalPositions1[index + 2];
}
});
pointsMesh.geometry.attributes.position.needsUpdate = true;
rgbPass.uniforms['amount'].value = 0;
badPass.uniforms['distortion'].value = 0;
tweenings(vineMesh.geometry.drawRange, 300, false, true)
}
function flickerOff() {
pointsMesh.visible = true
angelMesh.visible = false
pointsMesh.geometry.attributes.position.array.forEach((_, index) => {
if (index % 3 === 0 && positionArray1[index + 1] > bottom - 1.2) {
pointsMesh.geometry.attributes.position.array[index] += (Math.random() * 1) - 0.5
pointsMesh.geometry.attributes.position.array[index + 1] += (Math.random() * 0.2) - 0.1
pointsMesh.geometry.attributes.position.array[index + 2] += (Math.random() * 0.8) - 0.4
}
});
pointsMesh.geometry.attributes.position.needsUpdate = true;
isFlickering = false;
tweenings(vineMesh.geometry.drawRange, 200, true, false)
}
function randomFlicker() {
if(!stopGlitch ){
rando = (Math.random() * 60) + 10
rando2 = (Math.random() * 2500) + 800
if (flickerCount < randomNum) {
clearInterval(flickerInterval);
flickerInterval = setInterval(randomFlicker, rando);
if (isFlickering) {
flickerOff();
} else{
flickerOn();
flickerCount++;
};
if(flickerCount < randomNum - 1){
playSound('https://cdn.jsdelivr.net/gh/forerunrun/extends@main/zap1.wav', true, false)
}
if(flickerCount === randomNum ){
playSound('https://cdn.jsdelivr.net/gh/forerunrun/extends@main/clap.wav', false, true)
}
} else {
clearInterval(flickerInterval);
flickerCount = 0;
randomNum = Math.floor(Math.random() * 5) + 3;
if (randomNum % 2 !== 0) {
randomNum++;
}
flickerTimeout = setTimeout(startFlicker, rando2);
}
}
else{
clearInterval(flickerInterval);
flickerCount = 0;
clearTimeout(flickerTimeout)
if (!isFlickering) {
flickerOn();
}
}
}
function startFlicker() {
flickerInterval = setInterval(randomFlicker, rando);
}
function animatePoints(){
particlesMesh.rotation.y += 0.0002
if ( mouseDown ){
angelMesh.geometry.attributes.position.array.forEach((_, index) => {
if (index % 3 === 0 && positionArray[index + 1] > bottom - 1.2) {
const deltaX = positionArray[index] - pos.x;
const deltaY = positionArray[index + 1] - pos.y;
const deltaZ = positionArray[index + 2] - pos.z;
const distance = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2) + Math.pow(deltaZ, 2));
const strength = Math.max(0, 1 - distance / MAGNET_RADIUS);
if(invert.checked){
positionArray[index] += deltaX * strength * power;
positionArray[index + 1] += deltaY * strength * power;
positionArray[index + 2] += deltaZ * strength * power;
}else{
positionArray[index] -= deltaX * strength * power;
positionArray[index + 1] -= deltaY * strength * power;
positionArray[index + 2] -= deltaZ * strength * power;
}
}
});
};
angelMesh.geometry.attributes.position.array.forEach((_, index) => {
if (index % 3 === 0) {
const deltaX = originalPositions[index] - positionArray[index];
const deltaY = originalPositions[index + 1] - positionArray[index + 1];
const deltaZ = originalPositions[index + 2] - positionArray[index + 2];
const distance = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2) + Math.pow(deltaZ, 2));
// const distance = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
if (distance > MAGNET_RADIUS) {
const strength = Math.max(0, 1 - (distance - MAGNET_RADIUS) / MAGNET_RADIUS);
positionArray[index] += deltaX * strength * RESTORATION_SPEED;
positionArray[index + 1] += deltaY * strength * RESTORATION_SPEED;
positionArray[index + 2] += deltaZ * strength * RESTORATION_SPEED;
} else if (distance < MAGNET_RADIUS) {
const strength = Math.max(0, 1 - distance / MAGNET_RADIUS);
positionArray[index] += deltaX * strength * RESTORATION_SPEED;
positionArray[index + 1] += deltaY * strength * RESTORATION_SPEED;
positionArray[index + 2] += deltaZ * strength * RESTORATION_SPEED;
} else {
positionArray[index] = originalPositions[index];
positionArray[index + 1] = originalPositions[index + 1];
positionArray[index + 2] = originalPositions[index + 2];
}
}
});
angelMesh.geometry.attributes.position.needsUpdate = true;
}
window.addEventListener('resize', () => {
sizes.width = container.offsetWidth
sizes.height = container.offsetHeight
plane.scale.x = sizes.width / 10
plane.scale.y = sizes.height / 10
camera.aspect = sizes.width / sizes.height
camera.updateProjectionMatrix()
renderer.setSize(sizes.width, sizes.height)
composer.setSize(sizes.width, sizes.height )
groundMirror.getRenderTarget().setSize(
sizes.width * window.devicePixelRatio,
sizes.height * window.devicePixelRatio
);
})
const animatePlanes = () => {
plane.lookAt(camera.position)
}
function tweenings(vdr, aniTime, on, off){
TWEEN.removeAll(vdr)
console.log();
if(on){
document.body.classList.add('glow')
vdr.start = Math.floor(Math.random() * vineMesh.geometry.index.count / 60) * 30
vinesDrawRange = new TWEEN.Tween(vdr)
vinesDrawRange.to({
count: vineMesh.geometry.index.count
},
aniTime * 1.2)
vinesDrawRange.easing(eezing)
vinesDrawRange.onUpdate(()=>{
})
vinesDrawRange.onComplete(()=>{
tweening = false
})
vinesGlow = new TWEEN.Tween(angelMesh.material.color)
vinesGlow.to({
r: 1.4,
g: 1.5,
b: 1.9,
},
aniTime * 0.8)
vinesDim = new TWEEN.Tween(angelMesh.material.emissive)
vinesDim.to({
r: 0,
g: 0,
b: 0,
},
aniTime * 0.8)
floorGlow = new TWEEN.Tween(floorMesh.material.color)
floorGlow.to({
r: 100,
g: 100,
b: 100,
},
aniTime * 0.8)
// floorMesh
vinesDrawRange.start()
vinesGlow.start()
vinesDim.start()
floorGlow.start()
}
if(off){
document.body.classList.remove('glow')
vinesDrawRange = new TWEEN.Tween(vdr)
vinesDrawRange.to({
count: 0
},
aniTime * 2.2)
vinesDrawRange.easing(eezing)
vinesDrawRange.onUpdate(()=>{
})
vinesDrawRange.onComplete(()=>{
tweening = false
})
vinesGlow = new TWEEN.Tween(angelMesh.material.color)
vinesGlow.to({
r: 0,
g: 0,
b: 0,
},
aniTime * 2.8)
vinesDim = new TWEEN.Tween(angelMesh.material.emissive)
vinesDim.to({
r: originalColor.r,
g: originalColor.g,
b: originalColor.b,
},
aniTime * 0.2)
floorGlow = new TWEEN.Tween(floorMesh.material.color)
floorGlow.to({
r: groundOcolor.r,
g: groundOcolor.g,
b: groundOcolor.b,
},
aniTime * 0.01)
vinesDrawRange.start()
vinesGlow.start()
vinesDim.start()
floorGlow.start()
}
}
const animateVines = (t) => {
vineMesh.geometry.drawRange.count = -(Math.sin(t / 1000) * vineMesh.geometry.index.count /2) + vineMesh.geometry.index.count/2
}
const animateRain = () => {
for( let rm = 0; rm < rainMesh.geometry.attributes.position.array.length; rm+=3){
rainMesh.geometry.attributes.position.array[rm + 1] -= 0.8
if(rainMesh.geometry.attributes.position.array[rm + 1] < -40){
rainMesh.geometry.attributes.position.array[rm ] = (Math.random() - 0.5) * 80
rainMesh.geometry.attributes.position.array[rm + 1] = 40
rainMesh.geometry.attributes.position.array[rm + 2] = (Math.random() - 0.5) * 80
}
}
rainMesh.geometry.attributes.position.needsUpdate = true
}
function animate(t) {
timeF += 0.1
if(!isFlickering){
time += Math.random()
sin = Math.sin(time) * 0.008
badPass.uniforms['time'].value = time;
rgbPass.uniforms['amount'].value = sin ;
badPass.uniforms['distortion'].value = (Math.random() * 5);
}
filmPass.uniforms['time'].value = timeF;
if(rainMesh){
animateRain()
}
TWEEN.update()
animatePlanes()
animateParticles();
animatePoints()
controls.update()
render();
requestAnimationFrame( animate );
}
function render() {
composer.render()
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.