<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<div id="container" role="img" aria-label="Ghost flying around a tree">
</div>
<div id="loader" >
<div id="loader-content" class="border-gradient border-gradient-purple">
<p>Loading 3D scene... ✨</p>
</div>
</div>
<button id="btn-freeze">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 99.799 42.739"><defs><filter height="1.248" y="-.124" width="1.092" x="-.046" id="a" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="1.807"/></filter></defs><g
id="freeze-text-blur" transform="translate(-1.18 -1.623)" filter="url(#a)" fill="none" stroke="#b0b0b0"><path d="M25.636 10.552c-1.843 7.337-4.62 16.543-8.126 22.679M8.816 19.624c9.974-.372 30.667-3.216 33.64-14.363M15.053 24.726c5.363 1.364 13.934-2.697 16.442-4.535M31.022 27.845c1.609-1.694 3.847-2.611 5.576-4.158 6.37-5.7 1.953-7.319-1.418-1.701-3.897 6.496 7.813-.468 9.45-2.268M44.63 19.718c-4.05.891-13.429 16.197-2.74 9.072"/><path d="M42.078 26.805c2.818-.666 4.975-1.844 7.37-3.213 10.356-5.917 6.352-12.377-3.118-.756-8.242 10.116 2.227 8.924 8.316 2.835"/><path d="M52.19 25.766c2.445-1.012 4.868-2.107 7.18-3.402 9.618-5.386 3.297-8.388-2.55.189-5.712 8.377 3.361 8.05 8.598 3.685"/><path d="M64.284 23.498c1.292-.693 2.596-1.363 3.875-2.079 1.033-.578 1.86-1.403 2.645-2.268 4.807-5.287-4.933-.681-2.551 1.701 2.696 2.696 11.116-6.284 3.969 1.512-.322.35-5.82 5.654-6.142 5.008-.408-.816 3.932-2.844 4.724-2.646.867.217-.713 2.941 1.04 2.552 1.237-.275 2.219-1.27 3.118-2.08"/><path d="M73.828 23.498c3.162-.749 5.092-1.59 7.654-3.213 9.24-5.851-4.396-5.169-5.102 3.307-.416 4.986 10.12 2.11 12-.189M20.061 11.97c-5.567-2.034-10.471.077-14.552 4.157-2.638 2.639-2.336 5.286 1.7 5.67 1.577.15 6.195-1.06 4.726-.472-4.195 1.677-.003 1.448 2.173.756"/><path d="M79.309 28.128c2.9.636 5.15.384 7.843-.472 5.975-1.902 9.454-9.107 5.575-6.52-3.217 2.144 3.86 1.413 5.197.661M18.41 26.488c-3.197 8.188-9.445 15.54-11.16 5.856"/><g><path d="M95.263 5.625c-2.024 3.49-7.167 6.601-13.293 5.438M90.832 4.215c-.295 3.694-2.873 8.353-5.304 10.54M82.172 6.43c3.077 3.028 9.31 5.886 12.554 5.707"/></g><g><path d="M28.935 28.72c-1.436 3.613-5.137 8.71-8.66 10.472M22.557 29.793c2.541 3.398 4.076 6.935 2.685 9.4M19.267 35.164c5.351.644 10.22-1.22 13.293-4.565"/></g></g><g id="freeze-text-top" fill="none" stroke="#b0b0b0"><g stroke-width=".5"><path d="M24.457 8.93c-1.843 7.336-4.62 16.543-8.126 22.678M7.637 18c9.974-.371 30.667-3.215 33.64-14.362M13.874 23.104c5.363 1.363 13.934-2.697 16.442-4.536M29.843 26.222c1.608-1.694 3.847-2.611 5.575-4.158 6.37-5.7 1.954-7.319-1.417-1.7-3.898 6.495 7.813-.468 9.45-2.269M43.45 18.095c-4.049.892-13.428 16.197-2.74 9.072"/><path d="M40.899 25.182c2.818-.665 4.975-1.843 7.37-3.212 10.356-5.918 6.352-12.378-3.118-.756-8.242 10.115 2.227 8.923 8.316 2.834"/><path d="M51.01 24.143c2.446-1.012 4.87-2.107 7.181-3.402 9.618-5.385 3.297-8.388-2.55.19-5.713 8.376 3.361 8.048 8.598 3.684"/><path d="M63.105 21.875c1.292-.693 2.596-1.363 3.874-2.079 1.033-.578 1.86-1.403 2.646-2.268 4.807-5.287-4.933-.681-2.551 1.701 2.696 2.696 11.115-6.284 3.969 1.512-.322.351-5.82 5.655-6.142 5.008-.409-.816 3.932-2.843 4.724-2.645.867.216-.713 2.94 1.04 2.55 1.236-.274 2.218-1.268 3.118-2.078"/><path d="M72.649 21.875c3.162-.749 5.092-1.59 7.654-3.213 9.24-5.851-4.396-5.168-5.103 3.308-.415 4.985 10.12 2.11 12.001-.19M18.882 10.347c-5.568-2.034-10.472.077-14.552 4.158-2.638 2.638-2.336 5.285 1.7 5.67 1.576.15 6.195-1.061 4.725-.473-4.194 1.677-.002 1.448 2.174.756"/><path d="M78.13 26.505c2.9.636 5.15.384 7.843-.472 5.975-1.901 9.454-9.106 5.575-6.52-3.217 2.144 3.86 1.414 5.197.661"/></g><path d="M17.23 24.865c-3.196 8.189-9.444 15.54-11.159 5.857" stroke-width=".5"/><g stroke-width=".3" stroke="#cb02ea"><path d="M94.084 4.002c-2.024 3.49-7.167 6.601-13.293 5.438M89.653 2.593c-.295 3.693-2.873 8.352-5.304 10.54M80.993 4.808c3.077 3.028 9.31 5.885 12.554 5.706"/></g><g stroke-width=".3" stroke="#cb02ea"><path d="M27.755 27.096c-1.435 3.614-5.136 8.712-8.66 10.473M21.378 28.17c2.541 3.399 4.076 6.935 2.685 9.4M18.088 33.541c5.351.644 10.22-1.219 13.293-4.565"/></g></g></svg>
</button>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.158.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.158.0/examples/jsm/"
}
}
</script>
<!-- Matcap VS -->
<script type="x-shader/x-vertex" id="matcap-vs">
varying vec2 vN;
void main() {
vec3 e = normalize( vec3( modelViewMatrix * vec4( position, 1.0 ) ) );
vec3 n = normalize( normalMatrix * normal );
vec3 r = reflect( e, n );
float m = 2. * sqrt( pow( r.x, 2. ) + pow( r.y, 2. ) + pow( r.z + 1., 2. ) );
vN = r.xy / m + .5;
gl_Position = projectionMatrix * modelViewMatrix * vec4( vec3(position), 1. );
}
</script>
<!-- Matcap FS -->
<script type="x-shader/x-fragment" id="matcap-fs">
uniform sampler2D tMatCap;
varying vec2 vN;
void main() {
vec3 base = texture2D( tMatCap, vN ).rgb;
gl_FragColor = vec4( base, 1.0 );
}
</script>
<!-- Flaky VS -->
<script type="x-shader/x-vertex" id="flaky-vs">
// Simplex 4D Noise
// by Ian McEwan, Ashima Arts
//
vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
float permute(float x){return floor(mod(((x*34.0)+1.0)*x, 289.0));}
vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}
float taylorInvSqrt(float r){return 1.79284291400159 - 0.85373472095314 * r;}
vec4 grad4(float j, vec4 ip){
const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0);
vec4 p,s;
p.xyz = floor( fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0;
p.w = 1.5 - dot(abs(p.xyz), ones.xyz);
s = vec4(lessThan(p, vec4(0.0)));
p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www;
return p;
}
float snoise(vec4 v){
const vec2 C = vec2( 0.138196601125010504, // (5 - sqrt(5))/20 G4
0.309016994374947451); // (sqrt(5) - 1)/4 F4
// First corner
vec4 i = floor(v + dot(v, C.yyyy) );
vec4 x0 = v - i + dot(i, C.xxxx);
// Other corners
// Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI)
vec4 i0;
vec3 isX = step( x0.yzw, x0.xxx );
vec3 isYZ = step( x0.zww, x0.yyz );
// i0.x = dot( isX, vec3( 1.0 ) );
i0.x = isX.x + isX.y + isX.z;
i0.yzw = 1.0 - isX;
// i0.y += dot( isYZ.xy, vec2( 1.0 ) );
i0.y += isYZ.x + isYZ.y;
i0.zw += 1.0 - isYZ.xy;
i0.z += isYZ.z;
i0.w += 1.0 - isYZ.z;
// i0 now contains the unique values 0,1,2,3 in each channel
vec4 i3 = clamp( i0, 0.0, 1.0 );
vec4 i2 = clamp( i0-1.0, 0.0, 1.0 );
vec4 i1 = clamp( i0-2.0, 0.0, 1.0 );
// x0 = x0 - 0.0 + 0.0 * C
vec4 x1 = x0 - i1 + 1.0 * C.xxxx;
vec4 x2 = x0 - i2 + 2.0 * C.xxxx;
vec4 x3 = x0 - i3 + 3.0 * C.xxxx;
vec4 x4 = x0 - 1.0 + 4.0 * C.xxxx;
// Permutations
i = mod(i, 289.0);
float j0 = permute( permute( permute( permute(i.w) + i.z) + i.y) + i.x);
vec4 j1 = permute( permute( permute( permute (
i.w + vec4(i1.w, i2.w, i3.w, 1.0 ))
+ i.z + vec4(i1.z, i2.z, i3.z, 1.0 ))
+ i.y + vec4(i1.y, i2.y, i3.y, 1.0 ))
+ i.x + vec4(i1.x, i2.x, i3.x, 1.0 ));
// Gradients
// ( 7*7*6 points uniformly over a cube, mapped onto a 4-octahedron.)
// 7*7*6 = 294, which is close to the ring size 17*17 = 289.
vec4 ip = vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ;
vec4 p0 = grad4(j0, ip);
vec4 p1 = grad4(j1.x, ip);
vec4 p2 = grad4(j1.y, ip);
vec4 p3 = grad4(j1.z, ip);
vec4 p4 = grad4(j1.w, ip);
// Normalise gradients
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
p4 *= taylorInvSqrt(dot(p4,p4));
// Mix contributions from the five corners
vec3 m0 = max(0.6 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), 0.0);
vec2 m1 = max(0.6 - vec2(dot(x3,x3), dot(x4,x4) ), 0.0);
m0 = m0 * m0;
m1 = m1 * m1;
return 49.0 * ( dot(m0*m0, vec3( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 )))
+ dot(m1*m1, vec2( dot( p3, x3 ), dot( p4, x4 ) ) ) ) ;
}
#define NUM_OCTAVES 3
float fbm(vec4 x) {
float v = 0.0;
float a = 0.5;
vec4 shift = vec4(100.);
for (int i = 0; i < NUM_OCTAVES; ++i) {
v += a * snoise(x);
x = x * 2.0 + shift;
a *= 0.5;
}
return v;
}
float map(float value, float min1, float max1, float min2, float max2) {
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
}
uniform float uTime;
uniform float uEdgeWidth;
uniform float uFrequency;
varying float vNoise;
varying float vEdge;
varying vec3 vNormal;
varying vec3 vViewPosition;
varying vec2 vUv;
void main() {
vUv = uv;
vNoise = fbm(vec4(position*uFrequency, 0.));
vNoise = map(vNoise,-0.6,0.6,0.,1. - uEdgeWidth);
vec4 modelViewPosition = modelViewMatrix * vec4( position, 1.0 );
vViewPosition = -modelViewPosition.xyz;
gl_Position = projectionMatrix * modelViewPosition;
vNormal = normalMatrix * normal;
}
</script>
<!-- Flaky FS before main -->
<script type="x-shader/x-fragment" id="flaky-fs-beforeMain">
uniform float uThreshold;
uniform float uEdgeWidth;
uniform vec3 uEdgeColor;
uniform vec3 uColor;
varying float vNoise;
</script>
<!-- Flaky FS main -->
<script type="x-shader/x-fragment" id="flaky-fs">
float alpha = 1.0;
if( vNoise > uThreshold) {
alpha = 0.0;
}
vec3 materialColor = uColor / 255.0;
vec3 edgeColor = uEdgeColor / 255.0;
vec3 color = materialColor;
if( vNoise + uEdgeWidth > uThreshold ) {
color = edgeColor;
}
vec4 diffuseColor = vec4( color, alpha );
</script>
<!-- Glow VS -->
<script type="x-shader/x-vertex" id="glow-vs">
varying vec3 vVertexWorldPosition;
varying vec3 vVertexNormal;
varying vec4 vFragColor;
void main() {
vVertexNormal = normalize(normalMatrix * normal);
vVertexWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<!-- Glow FS -->
<script type="x-shader/x-fragment" id="glow-fs">
uniform vec3 glowColor;
uniform float coeficient;
uniform float power;
varying vec3 vVertexNormal;
varying vec3 vVertexWorldPosition;
varying vec4 vFragColor;
void main() {
vec3 worldCameraToVertex = vVertexWorldPosition - cameraPosition;
vec3 viewCameraToVertex = (viewMatrix * vec4(worldCameraToVertex, 0.0)).xyz;
viewCameraToVertex = normalize(viewCameraToVertex);
float intensity = pow(coeficient + dot(vVertexNormal, viewCameraToVertex), power);
gl_FragColor = vec4(glowColor, intensity);
}
</script>
<!-- Radial gradient VS -->
<script type="x-shader/x-vertex" id="radialGradient-vs">
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1.0);
}
</script>
<!-- Radial gradient FS -->
<script type="x-shader/x-fragment" id="radialGradient-fs">
varying vec2 vUv;
uniform vec3 color1;
uniform vec3 color2;
uniform float ratio;
void main() {
vec2 uv = (vUv - 0.5) * vec2(ratio, 1.0);
gl_FragColor = vec4(mix(color1, color2, length(uv)), 1.0);
}
</script>
body {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
overflow: hidden;
background: rgb(82,81,79);
background: radial-gradient(circle, rgba(82,81,79,1) 0%, rgba(36,35,33,1) 32%, rgba(0,0,0,1) 100%);
color: #adadad;
font-family: Arial;
font-size: 16px;
-ms-touch-action: none;
touch-action: none;
}
#container {
position: absolute;
opacity: 0.25;
}
#loader {
width: inherit;
height: inherit;
margin: 0 auto;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
}
#loader-content {
background: none;
}
button {
position: absolute;
background: none;
border: none;
outline: none;
transition: transform 0.2s ease-in-out;
opacity: 1.0;
}
#btn-freeze {
position: absolute;
width: 145px;
height: auto;
cursor: pointer;
top: 0;
right: 0;
transform: rotate(3deg);
padding: 1.25em 1.0em;
opacity: 0.85;
}
#btn-freeze:hover {
animation: rotateMe2 0.35s ease-in-out forwards;
}
#btn-credits {
position: absolute;
width: 100px;
height: auto;
background: none;
bottom: 0;
right: 0;
opacity: 0.75;
padding: 0.5em 0.25em;
transform: rotate(-20deg);
display: none;
opacity: 0.7;
text-decoration: none;
}
a:active, a:focus {
text-decoration: none;
outline: 0;
border: none;
-moz-outline-style: none;
}
#btn-credits:hover {
animation: rotateMe 0.25s ease-in-out forwards;
}
@keyframes fadeOut {
0% { opacity: 1; }
100% { opacity: 0; }
}
@-moz-keyframes fadeOut {
0% { opacity: 1; }
100% { opacity: 0; }
}
@keyframes fadeIn {
0% { opacity: 0.25; }
100% { opacity: 1; }
}
@-moz-keyframes fadeIn {
0% { opacity: 0.25; }
100% { opacity: 1; }
}
@keyframes rotateMe {
0% { transform: rotate(-20deg);
opacity: 0.7;}
100% { transform: rotate(0deg);
opacity: 1;}
}
@-moz-keyframes rotateMe {
0% { transform: rotate(-20deg);
opacity: 0.7;}
100% { transform: rotate(0deg); opacity: 1.0; }
}
@keyframes rotateMe2 {
0% {
transform: rotate(3deg) scale(1.0);
opacity: 0.7;
}
100% {
transform: rotate(15deg) scale(1.05);
opacity: 1.0;
}
}
@-moz-keyframes rotateMe2 {
0% {
transform: rotate(3deg);
transform: scale(1.0);
opacity: 0.7;
}
100% {
transform: rotate(15deg);
transform: scale(1.1);
opacity: 1.0;
}
}
@media ( max-width: 800px ) {
#btn-freeze {
width: 120px;
position: fixed;
top: 0;
left: inherit;
right: 0;
padding: 1em;
transform: rotate(15deg);
}
#btn-freeze:hover {
animation: none;
}
#btn-credits {
display: none;
}
}
/*
UPDATED VERSION: NOVEMBER 2023, threejs v0.158.0
It's disco time!
And this ghost wants to party all night long 👻
But you can freeze him anytime you wish to!
★ Scene designed & coded by Anna Scavenger in May 2020 ★
https://twitter.com/ouchpixels
License: You can remix, adapt, and build upon my code non-commercially.
Assets credits, special thanks to:
Cubemap by Emil Persson aka Humus (@_Humus_)
Simplex noise by Stefan Gustavson (@stegu)
Tree gtlf model - by Helindu, modified by me in Blender:
https://sketchfab.com/3d-models/tree-without-leaves-1-336d3bc197ce4618ab325e7a6dfa0e7a
*/
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { Flow } from 'three/addons/modifiers/CurveModifier.js';
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
// ASSETS
let texChromeURL = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/911157/matcap_chromeEye_128.jpg";
let texSteelURL = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/911157/matcap_steel_256.jpg";
let ghostURL = "https://assets.codepen.io/911157/ghost1.glb";
let treeURL = "https://assets.codepen.io/911157/tree_ghost_opt.glb";
let scene, camera, renderer;
// Through these points flies the ghost
// Also points for placing the tori
let controlPoints = [
new THREE.Vector3(420, 120, 0), // letter D
new THREE.Vector3(-70, 200, 750), // additional point
new THREE.Vector3(-440, 100, 150), // letter G
new THREE.Vector3(-250, 440, -395), // letter O
new THREE.Vector3(-20, 600, -590), // additional point
new THREE.Vector3(200, 500, -380) // letter O2
];
let materials;
let geometries;
// FLOW + GLOW
let ghost;
let flow;
let glowMeshes = [];
let currentGlowMesh;
// GLOBAL MESHES
let discoBall, discoBallN, ballCover, ballCover2, discoBallG, discoBallT;
let letterHLeft, letterHRight, cylinderT;
let lettersNightGroup;
let hookG, hookD, starD, starG;
let pointLight, pointLight2;
// 'FREEZING' THE SCENE EFFECT
let quaternion = new THREE.Quaternion();
let isSceneFrozen = false;
let rotSpeed = 0.25;
const btnFreeze = document.querySelector('#btn-freeze');
const freezeTextBlur = document.querySelector('#freeze-text-blur');
const freezeTextTop = document.querySelector('#freeze-text-top');
// 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 mouseX = 0;
let mouseY = 0;
let params = {
terrainWidth: 2000,
terrainLength: isLandscape ? 3000 : (2500 / windowRatio),
terrainPosY: -250,
treeMainPos: new THREE.Vector3(0, -250, 0),
treesBackgroundPos: new THREE.Vector3(0, -250, 0),
numTrees: 7,
terrainLettersPosZ: isLandscape ? 850 : 950,
ghostColor: new THREE.Color(0xffffff),
glowOffColor: new THREE.Color(0x8D8D8D),
glowOnColor: new THREE.Color(0xca00e5),
pointLightColor: new THREE.Color(0x0fc4b8), // dark green
play: false,
}
init();
function init() {
geometries = initGeometries();
materials = initMaterials();
// CAMERA, LIGHTS, RENDERER
initBasicSetup();
// MESHES
initTerrain();
initTerrainLetters();
initAirLetters();
loadTree();
loadGhost();
render();
window.addEventListener("resize", onWindowResize, false);
document.addEventListener("mousemove", onMouseMove, false);
btnFreeze.addEventListener("click", freezeScene, false);
}
function initBasicSetup() {
initScene();
initCamera();
initLights();
initRenderer();
}
function initScene() {
scene = new THREE.Scene();
const fogNear = isLandscape ? 1200 : 2200;
const fogFar = isLandscape ? 4000 : 5500;
scene.fog = new THREE.Fog(0x998a7d, fogNear, fogFar);
}
function initCamera() {
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 5000);
const cameraY = isLandscape ? 170 : 400;
const cameraZ = isLandscape ? 1850 : (1800 / windowRatio);
camera.position.set(0, cameraY, cameraZ);
camera.lookAt(0, 0, 0);
scene.add(camera);
}
function initLights() {
const backLight = new THREE.DirectionalLight(0xff00ff, 1.75 * Math.PI);
backLight.position.set(200, 0, -55);
backLight.target.position.set(300, 50, -100);
const fillLight = new THREE.DirectionalLight(0xffffff, 0.65 * Math.PI);
fillLight.position.set(80, 10, 200);
fillLight.target.position.set(50, 50, -50);
let posZ = isLandscape ? 750 : 850;
pointLight = new THREE.PointLight(params.pointLightColor, 8.5 * Math.PI, 200, 2);
pointLight.position.set(-300, 0, posZ);
pointLight2 = new THREE.PointLight(params.pointLightColor, 8.5 * Math.PI, 200, 2);
pointLight2.position.set(300, 0, posZ);
scene.add( fillLight, backLight, pointLight, pointLight2 );
}
function initRenderer() {
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(window.devicePixelRatio > 1.5 ? Math.min(window.devicePixelRatio, 1.4) : Math.min(window.devicePixelRatio, 1.25));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.gammaFactor = 2.2;
container.appendChild(renderer.domElement);
}
function initGeometries() {
let torus = new THREE.TorusGeometry(140, 40, 16, 46);
let halfTorus = new THREE.TorusGeometry(70, 25, 10, 20, Math.PI);
let cylinderThin = new THREE.CylinderGeometry(10, 10, 190, 8);
let cylinderThick = new THREE.CylinderGeometry(30, 30, 150, 22, 1);
let cylinderThick2 = new THREE.CylinderGeometry(30, 30, 150, 22, 22);
let sphere = new THREE.SphereGeometry(50, 24, 12);
let halfSphere = new THREE.SphereGeometry(50, 28, 8, 0, Math.PI);
let halfSphere2 = new THREE.SphereGeometry(55, 28, 8, 0, Math.PI);
let cone = new THREE.ConeGeometry(40, 110, 20);
// HOOK 1 - MERGED GEOMS
let halfTorusThin = new THREE.TorusGeometry( 85, 10, 12, 50, Math.PI);
halfTorusThin.rotateX(Math.PI);
let radius = halfTorusThin.parameters.radius;
let cylinderHeight = cylinderThin.parameters.height;
halfTorusThin.translate(- radius, - cylinderHeight / 2, 0);
let hook = mergeGeometries([halfTorusThin, cylinderThin], true);
// HOOK 2 - MERGED GEOMS
let cylinderD = new THREE.CylinderGeometry(10, 10, 350, 10);
let halfTorusD = new THREE.TorusGeometry(70, 10, 12, 50, Math.PI / 2);
halfTorusD.rotateX(Math.PI);
halfTorusD.rotateZ( - Math.PI / 2);
let radiusD = halfTorusD.parameters.radius;
let heightCylinderD = cylinderD.parameters.height;
halfTorusD.translate( radiusD, - heightCylinderD / 2, 0 );
let hook2 = mergeGeometries([cylinderD, halfTorusD], true);
return {
torus,
halfTorus,
cylinderThin,
cylinderThick,
cylinderThick2,
sphere,
halfSphere,
halfSphere2,
cone,
hook,
hook2
}
}
function initMaterials() {
const grey = new THREE.MeshBasicMaterial({color: 0x222222});
const blackBasic = new THREE.MeshBasicMaterial({color: 0x000000});
const brownSimple = isMobile ? (new THREE.MeshBasicMaterial({color: 0x594a3e})) : (new THREE.MeshLambertMaterial({color: 0x5e4732}));
const brown = new THREE.MeshLambertMaterial({color: 0x5e4732});
const purpleFlat = new THREE.MeshLambertMaterial({color: 0xff00ff});
const purple = new THREE.MeshStandardMaterial({
color: 0xff00ff,
roughness: 0.4,
metalness: 0.9
});
purple.color.convertSRGBToLinear();
const greyFlat = new THREE.MeshPhongMaterial({
color: 0x200066,
emissive: 0x54944,
specular: 0x1d683f,
shininess: 80,
flatShading: true
});
greyFlat.color.convertSRGBToLinear();
// MATCAPS
const textureLoader = new THREE.TextureLoader();
textureLoader.setCrossOrigin('');
const textureChrome = textureLoader.load(texChromeURL);
const textureSteel = textureLoader.load(texSteelURL);
const matcapChrome = new THREE.ShaderMaterial({
uniforms: {
tMatCap: { type: "t", value: textureChrome }
},
vertexShader: document.querySelector("#matcap-vs").textContent,
fragmentShader: document.querySelector("#matcap-fs").textContent,
side: THREE.DoubleSide
});
const matcapSteel = new THREE.ShaderMaterial({
uniforms: {
tMatCap: { type: "t", value: textureSteel }
},
vertexShader: document.querySelector("#matcap-vs").textContent,
fragmentShader: document.querySelector("#matcap-fs").textContent,
side: THREE.DoubleSide
});
// DISCO REFLECTIONS
const loader = new THREE.CubeTextureLoader();
loader.setCrossOrigin('');
loader.setPath('https://s3-us-west-2.amazonaws.com/s.cdpn.io/911157/');
const textureCube = loader.load([
'pisa1_px.jpg', 'pisa1_nx.jpg',
'pisa1_py.jpg', 'pisa1_ny.jpg',
'pisa1_pz.jpg', 'pisa1_nz.jpg'
]);
const disco = new THREE.MeshPhongMaterial({
envMap: textureCube,
color: 0xff00ff,
emissive: 0xaa00aa,
specular: 0x1d683f,
flatShading: true
});
// FLAKY MATERIAL
let phongShader = THREE.ShaderLib.standard;
let fragmentShader = phongShader.fragmentShader;
fragmentShader = document.querySelector("#flaky-fs-beforeMain").textContent +
fragmentShader.replace(
"vec4 diffuseColor = vec4( diffuse, opacity );",
document.querySelector("#flaky-fs").textContent
);
const flaky = new THREE.ShaderMaterial({
uniforms: THREE.UniformsUtils.merge([phongShader.uniforms, {
uThreshold: { value: 0.5 },
uEdgeWidth: { value: 0.0 },
uEdgeColor: { value: [255, 109, 203] },
uColor: { value: [255, 0, 255] },
uFrequency: { value: 0.95 }
}]),
vertexShader: document.querySelector("#flaky-vs").textContent,
fragmentShader: fragmentShader,
side: THREE.DoubleSide,
lights: true,
transparent: true
});
// GLOW MATERIAL
let glowOffColor = params.glowOffColor;
const glow = new THREE.ShaderMaterial({
uniforms: {
coeficient: { type: "f", value: 1.1 },
power: { type: "f", value: 1.4 },
glowColor: { type: "c", value: glowOffColor },
},
vertexShader: document.querySelector("#glow-vs").textContent,
fragmentShader: document.querySelector("#glow-fs").textContent,
transparent: true,
});
return {
blackBasic,
grey, greyFlat,
purple, purpleFlat,
brown, brownSimple,
matcapChrome, matcapSteel,
disco,
flaky,
glow
}
}
function initTerrain() {
let terrainWidth = params.terrainWidth;
let terrainLength = params.terrainLength;
let terrainY = params.terrainPosY;
let terrainGeom = new THREE.PlaneGeometry(terrainWidth, terrainLength, 30, 30);
terrainGeom.rotateX(-0.5 * Math.PI);
let positionAttribute = terrainGeom.attributes.position;
for (let i = 0; i < positionAttribute.count; i++) {
let x = positionAttribute.getX( i );
let y = positionAttribute.getY( i );
let z = positionAttribute.getZ( i );
y += Math.random() * 40;
positionAttribute.setXYZ(i, x, y, z);
}
const terrain = new THREE.Mesh(terrainGeom, materials.brown);
terrain.position.y = terrainY;
scene.add(terrain);
// SPRINKLE TERRAIN WITH POINTS
const positionArray = terrainGeom.attributes.position.array;
const positionAttributeCount = terrainGeom.attributes.position.count;
const pointsGeom = new THREE.BufferGeometry();
pointsGeom.setAttribute('position', new THREE.Float32BufferAttribute(positionArray, 3));
const pointsMat = new THREE.PointsMaterial({ color: 0xff00ff, size: 6 });
const terrainPoints = new THREE.Points(pointsGeom, pointsMat);
terrainPoints.position.y = terrainY;
scene.add(terrainPoints);
// SPRINKLE TERRAIN WITH LITTLE BOXES
const boxBufferGeom = new THREE.BoxGeometry(2.85, 2.85, 2.85);
const instancedBoxGeom = new THREE.InstancedBufferGeometry().copy(boxBufferGeom);
let numInstances = positionAttributeCount;
const terrainBoxes = new THREE.InstancedMesh(instancedBoxGeom, materials.purpleFlat, numInstances);
const dummy = new THREE.Object3D();
for (let i = 0; i < numInstances; i++) {
dummy.position.set(
noise.simplex3(i * 0.2, i * 0.5, 0.5) * 900,
Math.random() * (-220 - (-240)) + (-240),
Math.random() * (1300 - (-850)) + (-850),
);
dummy.rotation.set(
Math.random() * (Math.PI * 1.95 - (- Math.PI * 1.95)) + (-Math.PI * 1.95),
Math.random() * (Math.PI * 1.95 - (- Math.PI * 1.95)) + (-Math.PI * 1.95),
0.0
);
dummy.updateMatrix();
terrainBoxes.setMatrixAt(i, dummy.matrix);
}
scene.add(terrainBoxes);
}
function initTerrainLetters() {
let posZ = params.terrainLettersPosZ;
// letter N
const cylinderN = new THREE.Mesh(geometries.cylinderThick, materials.purple);
cylinderN.scale.y = 0.85;
cylinderN.position.set(-360, -150, posZ);
const cylinderN2 = new THREE.Mesh(geometries.cylinderThick, materials.purple);
cylinderN2.position.set(-230, -170, posZ);
const halfSphereN = new THREE.Mesh(geometries.halfSphere, materials.matcapSteel);
halfSphereN.position.set(-200.6, -97, posZ);
halfSphereN.scale.set(0.593, 0.593, 0.593);
halfSphereN.rotation.x = - Math.PI / 2;
scene.add(halfSphereN);
discoBallN = new THREE.Mesh(geometries.sphere, materials.disco);
discoBallN.position.set(-360, -50, posZ);
discoBallN.scale.set(0.5, 0.5, 0.5);
scene.add(discoBallN);
const cylinderThinnerN = new THREE.Mesh(geometries.cylinderThin, materials.matcapSteel);
cylinderThinnerN.position.set(-360, -150, posZ);
cylinderThinnerN.scale.set(0.4, 0.9, 0.4);
scene.add(cylinderThinnerN);
const cylinderThinN = new THREE.Mesh(geometries.cylinderThin, materials.matcapSteel);
cylinderThinN.position.set(-290, -170, posZ);
cylinderThinN.scale.set(1.2, 0.8, 1.2);
cylinderThinN.rotation.z = Math.PI / 4.5;
const discN = new THREE.Mesh( geometries.cylinderThick, materials.matcapSteel );
discN.position.set(-360, -223, posZ);
discN.scale.set(1.28, 0.125, 1.28);
const discN2 = discN.clone();
discN.position.set(-230, -240, posZ);
const letterN = new THREE.Group();
letterN.add(cylinderN, cylinderN2, cylinderThinN, discN, discN2);
scene.add(letterN);
// letter I
let letterIPosX = -110;
discoBall = new THREE.Mesh(geometries.sphere, materials.disco);
discoBall.position.set(letterIPosX, -70, posZ);
ballCover = new THREE.Mesh(geometries.halfSphere, materials.purple);
ballCover.material.side = THREE.DoubleSide;
let halfRadius = ballCover.geometry.parameters.radius;
ballCover.scale.set(1.15, 1.15, 1.15);
ballCover.position.set(letterIPosX, - 74 - halfRadius, posZ);
ballCover.rotation.set(Math.PI / 2, - Math.PI / 2, 0);
ballCover.geometry.applyMatrix4(new THREE.Matrix4().makeTranslation(- halfRadius, 0, 0));
ballCover.updateMatrix();
ballCover2 = ballCover.clone();
ballCover2.position.copy(ballCover.position);
ballCover2.rotation.z = Math.PI;
ballCover2.rotation.y = Math.PI / 2;
const cone = new THREE.Mesh(geometries.cone, materials.matcapSteel);
cone.position.set(letterIPosX, -180, posZ);
const letterI = new THREE.Group();
letterI.add(cone, discoBall, ballCover, ballCover2);
scene.add(letterI);
// letter G
const torusG = new THREE.Mesh(geometries.halfTorus, materials.purple);
torusG.position.set(50, -160, posZ);
torusG.rotation.z = Math.PI / 2;
const hookG = new THREE.Mesh(geometries.hook, materials.matcapSteel);
hookG.scale.set(0.3, 0.25, 0.8);
hookG.position.set(70, -185, posZ);
discoBallG = discoBallN.clone();
discoBallG.position.set(50, -160, posZ);
const letterG = new THREE.Group();
letterG.add(torusG, hookG, discoBallG);
scene.add(letterG);
// letter H
letterHLeft = new THREE.Group();
letterHRight = new THREE.Group();
const cylinderH = new THREE.Mesh(geometries.cylinderThick2, materials.flaky);
// cylinderH.position.set(140, -145, 850);
cylinderH.position.set(140, -160, posZ);
cylinderH.scale.set(1.0, 0.9, 1.0);
const cylinderH2 = new THREE.Mesh(geometries.cylinderThick, materials.matcapSteel);
cylinderH2.scale.set(0.98, 0.88, 0.98);
cylinderH2.position.copy(cylinderH.position);
const discH = new THREE.Mesh(geometries.cylinderThick, materials.matcapSteel);
discH.position.set(140, -87, posZ);
discH.scale.set(1.275, 0.08, 1.275);
const discH2 = discN.clone();
discH2.position.set(140, -225, posZ);
letterHLeft.add(cylinderH, cylinderH2, discH, discH2);
letterHRight = letterHLeft.clone();
letterHRight.position.set(120, 0, 0);
const horizontalH = new THREE.Mesh(geometries.cylinderThin, materials.matcapSteel);
horizontalH.scale.set(1.2, 0.5, 1.2);
horizontalH.rotation.z = Math.PI / 2;
horizontalH.position.set(200, -155, posZ);
const letterH = new THREE.Group();
letterH.add(letterHLeft, letterHRight, horizontalH);
scene.add(letterH);
// letter T
cylinderT = new THREE.Mesh(geometries.cylinderThin, materials.matcapSteel);
cylinderT.scale.set(0.55, 0.725, 0.55);
cylinderT.position.set(370, -40, posZ);
cylinderT.rotation.z = Math.PI / 2;
const cylinderT2 = cylinderN.clone();
cylinderT2.position.set(370, -170, posZ);
cylinderT2.scale.set(0.9, 0.75, 0.9);
discoBallT = discoBallN.clone();
discoBallT.position.set(370, -70, posZ);
const letterT = new THREE.Group();
letterT.add(cylinderT, cylinderT2, discoBallT);
scene.add(letterT);
lettersNightGroup = new THREE.Group();
}
function loadTree() {
const loader = new GLTFLoader();
loader.load(treeURL,
function (gltf) {
let tree = gltf.scene.children[0].children[0].children[0].children[0].children[0];
tree.geometry.computeVertexNormals();
let treeGeom = tree.geometry;
treeGeom.scale(55, 55, 55);
treeGeom.rotateX(-0.5 * Math.PI);
tree.position.set(50, -150, -1050);
tree.material = new THREE.MeshBasicMaterial({color: 0x000000, fog: false});
initTrees(treeGeom);
scene.add(tree);
},
function (xhr) { console.log((xhr.loaded / xhr.total * 100 ) + '% loaded'); },
function (error) { console.log( 'An error happened' ); }
);
}
function initTrees(tGeom) {
let treeMesh = new THREE.Mesh(tGeom, materials.matcapSteel);
let treesCoords = [
-350, -200, -1400,
-200, -270, -1300,
250, -250, -1300
];
for (let i = 0; i < (treesCoords.length / 3); i++) {
let t = treeMesh.clone();
let x = treesCoords[3 * i];
let y = treesCoords[3 * i + 1];
let z = treesCoords[3 * i + 2];
t.position.set(x, y, z);
t.rotation.set(0, i * 0.2 * Math.PI, 0);
scene.add(t);
}
}
function loadGhost() {
const loader = new GLTFLoader();
loader.load(ghostURL,
function (gltf) {
ghost = gltf.scene.children[2].parent.children[2];
ghost.geometry.computeVertexNormals();
initFlow();
removeLoadingScreen();
},
function (xhr) { console.log((xhr.loaded / xhr.total * 100 ) + '% loaded'); },
function (error) { console.log( 'An error happened' ); }
);
}
function initFlow() {
let ghostColor = params.ghostColor || 0xff3da4;
let purple = new THREE.MeshStandardMaterial({
color: ghostColor,
roughness: 0.95,
metalness: 0.05,
side: THREE.DoubleSide
});
purple.color.convertSRGBToLinear();
let curvePoints = controlPoints;
let curve = new THREE.CatmullRomCurve3(curvePoints);
curve.curveType = "centripetal";
curve.closed = true;
let ghostScale = 0.375;
const mesh = ghost;
mesh.geometry.scale(ghostScale, ghostScale, ghostScale);
mesh.geometry.rotateY(-Math.PI / 2);
mesh.geometry.rotateX(Math.PI / 2);
let objectToCurve = new THREE.Mesh(mesh.geometry, purple);
flow = new Flow(objectToCurve);
flow.updateCurve(0, curve);
scene.add(flow.object3D);
}
// "GOOD" LETTERS IN THE AIR
function initAirLetters() {
// 4 TORI - AIR LETTERS
let letterPositions = [controlPoints[0], controlPoints[2], controlPoints[3], controlPoints[5]];
let torus = new THREE.Mesh(geometries.torus, materials.glow);
for (let i = 0; i < letterPositions.length; i++) {
let t = torus.clone();
// IMPORTANT! When cloning a ShaderMaterial, the attributes and vertex/fragment programs are copied by reference.
// The uniforms are copied by value, which is what we need. / turning on and off glow for each mesh /
t.material = torus.material.clone();
t.material.isGlowing = false;
t.position.copy(letterPositions[i]);
glowMeshes.push(t);
scene.add(t);
}
// letter G
hookG = new THREE.Mesh(geometries.hook, materials.matcapChrome);
hookG.position.set(-310, -40, 150);
hookG.matrixAutoUpdate = false;
hookG.updateMatrix();
scene.add( hookG );
let starGeom = generateStarGeometry();
starG = new THREE.Mesh(starGeom, materials.matcapSteel);
starG.position.set(-480, -124, 150);
starG.rotation.z = Math.PI / 5;
starG.scale.set(2.4, 2.4, 2.4);
scene.add(starG);
// letter D
hookD = new THREE.Mesh(geometries.hook2, materials.matcapChrome);
hookD.position.set(550, 210, 0);
hookD.matrixAutoUpdate = false;
hookD.updateMatrix();
scene.add(hookD);
starD = starG.clone();
starD.position.set(550, 398, 0);
starD.rotation.z = Math.PI / 5;
scene.add(starD);
}
function generateStarGeometry() {
let numPoints = 7;
let minLength = 4;
let maxLength = 15;
let extrudeSettings = {
steps: 2,
depth: 2.5,
bevelEnabled: true,
bevelThickness: 0,
bevelSize: 2,
bevelOffset: 0,
bevelSegments: 1
};
let points = [];
for (let i = 0; i < numPoints * 2; i++) {
let l = i % 2 == 1 ? minLength : maxLength;
let step = i / numPoints * Math.PI;
points.push(new THREE.Vector2(Math.cos(step) * l, Math.sin(step) * l));
}
let starShape = new THREE.Shape(points);
let starGeom = new THREE.ExtrudeGeometry(starShape, extrudeSettings);
return starGeom;
}
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
if (flow && isSceneFrozen) {
// STOP THE FLYING GHOST AND ROTATE THE SCENE
flow.moveAlongCurve(0.000);
rotateScene();
} else if (flow && !isSceneFrozen ) {
animateTerrainLetters();
updatePointLights();
flow.moveAlongCurve(0.005);
let offset = flow.uniforms.pathOffset.value.toFixed(3);
let progress = (offset - Math.floor(offset)).toFixed(3);
if (progress == 0.25) {
currentGlowMesh = glowMeshes[1];
updateMaterial(currentGlowMesh);
} else if (progress == 0.55) {
currentGlowMesh = glowMeshes[2];
updateMaterial(currentGlowMesh);
} else if (progress == 0.7) {
currentGlowMesh = glowMeshes[3];
updateMaterial(currentGlowMesh);
} else if (progress == 0.92) {
currentGlowMesh = glowMeshes[0];
updateMaterial(currentGlowMesh);
} else {
return;
}
}
}
function rotateScene() {
camera.lookAt(scene.position);
let angle = 0.001 * mouseX;
camera.position.applyQuaternion(quaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), angle));
}
function updateMaterial(mesh) {
let glowColor = mesh.material.uniforms.glowColor.value;
let coeff = mesh.material.uniforms.coeficient;
mesh.material.isGlowing = !mesh.material.isGlowing;
mesh.material.isGlowing ? glowColor.set(params.glowOnColor) : glowColor.set(params.glowOffColor);
mesh.material.isGlowing ? coeff.value = 1.4 : coeff.value = 1.0;
if (currentGlowMesh == glowMeshes[1]) {
mesh.material.isGlowing ? (hookG.material = materials.purple) : (hookG.material = materials.matcapChrome);
} else if (currentGlowMesh == glowMeshes[0]) {
mesh.material.isGlowing ? (hookD.material = materials.purple) : (hookD.material = materials.matcapChrome);
}
}
function updatePointLights() {
pointLight.intensity = 3.25 + Math.abs(mouseX) * 4.75;
pointLight2.intensity = 3.25 + Math.abs(mouseX) * 4.75;
}
function animateTerrainLetters() {
starG.rotation.y = mouseX * Math.PI;
starD.rotation.y = - mouseX * Math.PI;
// letter N
discoBallN.rotation.y = - (mouseX * mouseX) * Math.PI * 0.5;
// letter I
discoBall.rotation.y = mouseX * Math.PI * 0.25;
discoBall.material.emissive.r = 0.8 - Math.abs(mouseX * 0.78);
ballCover.rotation.y = - Math.PI / 2 + Math.PI / 1.85 * Math.abs(mouseX * mouseX);
ballCover.scale.set(1.15 - 0.6 * Math.abs(mouseX*mouseX), 1.15 - 0.85 * Math.abs(mouseX * mouseX), 1.15 - 0.75 * Math.abs(mouseX * mouseX));
ballCover2.rotation.y = Math.PI / 2 - Math.PI / 1.85 * Math.abs(mouseX * mouseX);
ballCover2.scale.set(1.15 - 0.6 * Math.abs(mouseX*mouseX), 1.15 - 0.85 * Math.abs(mouseX*mouseX), 1.15 - 0.75 * Math.abs(mouseX*mouseX));
// letter G
discoBallG.rotation.y = -mouseX * Math.PI;
discoBallG.scale.set(0.5, 0.2 + 0.3 * Math.abs(mouseX), 0.5);
// letter H
letterHLeft.children[0].rotation.y = (mouseX) * Math.PI;
letterHRight.children[0].rotation.y = - (mouseX) * Math.PI;
// letter T
discoBallT.rotation.y = -mouseX * Math.PI * 0.5;
cylinderT.rotation.y = -mouseX * Math.PI * 0.25;
}
// *** EVENTS ***
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onMouseMove(e) {
e.preventDefault();
mouseX = (event.clientX / window.innerWidth) * 2 - 1;
mouseY = -(event.clientY / window.innerHeight) * 2 + 1;
}
function freezeScene() {
params.play = false;
if (!isSceneFrozen) {
params.play = false;
isSceneFrozen = !isSceneFrozen;
freezeTextTop.style.stroke = "#63639f";
freezeTextBlur.style.stroke = "#5959ff";
} else {
params.play = true;
isSceneFrozen = !isSceneFrozen;
freezeTextBlur.style.stroke = "#b0b0b0";
freezeTextTop.style.stroke = "#b0b0b0";
}
}
function removeLoadingScreen() {
const loaderDiv = document.querySelector("#loader");
const sceneDiv = document.querySelector("#container");
loaderDiv.style.animation = "fadeOut 0.375s ease-in-out forwards";
sceneDiv.style.animation = "fadeIn 0.75s ease-in-out forwards";
loaderDiv.style.display = "none";
}
This Pen doesn't use any external CSS resources.