<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()
}
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.