<div class="headerContainer">
<div id="container">
<button onClick="init()">New Mesh</button>
<button class="lights-button" onClick="toggleLight()">Toggle
Global Lights</button>
<div class="slidecontainer">
<span>Point Variance</span>
<input type="range" min="1" max="10" value="5" id="sliderRange">
</div>
</div>
</div>
body {
margin: 0;
background-color: #fff;
font-family: "Oswald", sans-serif;
}
button {
position: absolute;
left: 10px;
top: 40px;
padding: 0.3rem;
font-family: "Oswald", sans-serif;
font-size: 1rem;
}
.lights-button {
top: 80px;
}
.headerContainer {
height: 100vh;
min-height: 700px;
width: 100%;
overflow: hidden;
position: relative;
}
.invitationOverlay {
position: absolute;
padding: 24px;
//background-color: rgba(255, 255, 255, 0.5);
left: 65%;
bottom: 30%;
color: #fff;
}
h1 {
font-size: 85px;
line-height: 87px;
font-weight: 800;
margin: 0 0 20px 0;
text-shadow: 0 0 5px rgb(0 0 0 / 50%);
}
h2 {
font-size: 50px;
line-height: 52px;
font-weight: 400;
margin: 0 0 40px 0;
text-shadow: 0 0 5px rgb(0 0 0 / 50%);
}
p {
font-size: 2rem;
color: rgba(255, 255, 255, 0.7);
margin: 0;
}
.statsBarOuter {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
display: flex;
background: rgba(12, 22, 45, 0.3);
}
.statsBarInner {
padding: 20px;
display: flex;
justify-content: space-evenly;
width: 100%;
}
.statItem {
text-shadow: 0 0 5px rgb(0 0 0 / 50%);
}
.light {
font-weight: 300;
color: #a675b3;
font-size: 1.5rem;
}
.button {
display: inline;
}
.noselect {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.button {
box-shadow: inset 0 1px 1px rgb(111 55 125 / 80%),
inset 0 -1px 0px rgb(63 59 113 / 20%), 0 9px 16px 0 rgb(0 0 0 / 30%),
0 4px 3px 0 rgb(0 0 0 / 30%), 0 0 0 1px #150a1e;
background-image: linear-gradient(#3b2751, #271739);
text-shadow: 0 0 21px rgb(223 206 228 / 50%), 0 -1px 0 #311d47;
animation: bounceInDown 900ms 200ms ease-in-out both;
width: 150px;
height: 40px;
text-decoration: none;
outline-width: 0px;
z-index: 990;
color: #a675b3;
text-align: center;
line-height: 1;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: none;
padding: 10px 40px;
font-size: 1rem;
}
.button.active,
.button:active {
box-shadow: 0 9px 16px 0 rgba(0, 0, 0, 0.1), 0 0 0 1px #170c22,
0 2px 1px 0 rgba(121, 65, 135, 0.5), inset 0 0 4px 3px rgba(15, 8, 22, 0.2);
background-image: linear-gradient(#1f132e, #311d47);
text-shadow: 0 0 21px rgba(223, 206, 228, 0.5),
0 0 10px rgba(223, 206, 228, 0.4), 0 0 2px #2a153c;
color: #e4e3ce;
}
.blue {
box-shadow: inset 0 1px 1px rgb(95 96 151 / 80%),
inset 0 -1px 0px rgb(63 59 113 / 20%), 0 9px 16px 0 rgb(0 0 0 / 30%),
0 4px 3px 0 rgb(0 0 0 / 30%), 0 0 0 1px #5f5f97;
background-image: linear-gradient(#272951, #171c39);
text-shadow: 0 0 21px rgb(223 206 228 / 50%), 0 -1px 0 #1d2047;
color: #9293b7;
}
.blue.active,
.blue:active {
box-shadow: 0 9px 16px 0 rgba(0, 0, 0, 0.1), 0 0 0 1px #0c0d22,
0 2px 1px 0 rgba(160, 160, 195, 0.5), inset 0 0 4px 3px rgba(15, 8, 22, 0.2);
background-image: linear-gradient(#13132e, #1e1d47);
text-shadow: 0 0 21px rgba(223, 206, 228, 0.5),
0 0 10px rgba(223, 206, 228, 0.4), 0 0 2px #16153c;
color: #e4e3ce;
}
.slidecontainer {
position: absolute;
left: 10px;
top: 10px;
color: #fff;
}
let container, stats;
let camera, scene, renderer;
let windowHalfX = window.innerWidth / 2;
let windowHalfY = window.innerHeight / 2;
var mouseIsDown = true;
//Mouse variables for mouse light positioning
var mouse = {
x: 0,
y: 0
};
var mouseLightPos = {
x: 0,
y: 0
};
const mouseLight = new THREE.PointLight(0xcccccc, 2, 30);
var mouseLightHeight = 20;
init();
animate();
var headerContainer = document.querySelector(".headerContainer");
var light;
function init() {
var headerCanvas = document.getElementById("headerCanvas");
if (headerCanvas != null) {
headerCanvas.remove();
}
// SETUP
// ======================
headerContainer = document.querySelector(".headerContainer");
camera = new THREE.PerspectiveCamera(
5,
headerContainer.offsetWidth / headerContainer.offsetHeight,
1,
10000
);
camera.position.z = 1800 - camera.aspect * 200;
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
// LIGHTS
// ======================
light = new THREE.DirectionalLight(0xcccccc);
light.position.set(0, 0, 1);
light.intensity = 0;
scene.add(light);
mouseLight.position.set(mouseLightPos.x, mouseLightPos.y, mouseLightHeight);
scene.add(mouseLight);
// point light helper
const sphereSize = 1;
const pointLightHelper = new THREE.PointLightHelper(mouseLight, sphereSize);
//scene.add(pointLightHelper);
// TEMPORARY CANVAS
// this is the canvas for mapping the gradient
// ======================
const tempCanvas = document.createElement("canvas");
var ctx = tempCanvas.getContext("2d");
const width = headerContainer.offsetWidth;
const height = headerContainer.offsetWidth;
const halfWidth = Math.floor(width * 0.5);
const halfHeight = Math.floor(height * 0.5);
const gradientRange = Math.sqrt(width ** 2 + height ** 2);
// Size the canvas to match viewport
tempCanvas.width = width;
tempCanvas.height = height;
// Create a gradient for the fill
var grd = ctx.createRadialGradient(
THREE.Math.randFloat(-halfWidth / 2, halfWidth / 2) + halfWidth,
height,
THREE.Math.randFloat(5, 100),
halfWidth,
height,
gradientRange
);
var firstColorR = Math.floor(Math.random() * 255);
var firstColorG = Math.floor(Math.random() * 255);
var firstColorB = Math.floor(Math.random() * 255);
var numOfGradientBands = Math.floor(THREE.Math.randFloat(2, 4));
grd.addColorStop(
0.25,
"rgb(" + firstColorR + "," + firstColorG + ", " + firstColorB
);
if (numOfGradientBands == 3) {
grd.addColorStop(
0.35,
"rgb(" +
Math.floor(Math.random() * 255) +
"," +
Math.floor(Math.random() * 255) +
", " +
Math.floor(Math.random() * 255)
);
}
grd.addColorStop(
0.45,
"rgb(" +
Math.floor(Math.random() * 255) +
"," +
Math.floor(Math.random() * 255) +
", " +
Math.floor(Math.random() * 255)
);
// Render gradient across whole fill covering canvas
ctx.fillStyle = grd;
ctx.fillRect(0, 0, width, height);
var pixels = [];
// Map the pixel data (RGB) to an array
for (var a = 1; a < 33; a++) {
for (var b = 1; b < 33; b++) {
var pixel = ctx.getImageData(
(tempCanvas.width / 32) * a,
(tempCanvas.height / 32) * b,
1,
1
).data;
pixels.push(pixel);
}
}
// * For debugging, render the gradient canvas to see the gradient
//document.body.appendChild(tempCanvas);
// GENERATE LOW-POLY MESH
// ======================
var pointsCount = 1000;
var points3d = [];
const visibleHeightAtZDepth = (depth, camera) => {
// compensate for cameras not positioned at z=0
const cameraOffset = camera.position.z;
if (depth < cameraOffset) depth -= cameraOffset;
else depth += cameraOffset;
// vertical fov in radians
const vFOV = (camera.fov * Math.PI) / 180;
// Math.abs to ensure the result is always positive
return 2 * Math.tan(vFOV / 2) * Math.abs(depth);
};
const visibleWidthAtZDepth = (depth, camera) => {
const height = visibleHeightAtZDepth(depth, camera);
return height * camera.aspect;
};
var visibleWidth = visibleWidthAtZDepth(camera.position.z, camera);
if (visibleWidth < 800) {
visibleWidth = 500;
}
var variance;
var varianceSlider = document.getElementById("sliderRange");
if (varianceSlider) {
variance = varianceSlider.value;
} else {
variance = THREE.Math.randFloat(1, 10);
}
// generate 1024 verticies (32 * 32)
for (let i = 1; i < 33; i++) {
for (let j = 1; j < 33; j++) {
// width/height of screen / 32 segents * index / 5 (used to scale the mesh. Larger values = smaller mesh)
let x = (visibleWidth / 32) * i + THREE.Math.randFloatSpread(variance);
let z = (visibleWidth / 32) * j + THREE.Math.randFloatSpread(variance);
let y = THREE.Math.randFloatSpread(4);
points3d.push(new THREE.Vector3(x, y, z));
}
}
var geometry1 = new THREE.BufferGeometry().setFromPoints(points3d);
// DELAUNAY / APPLY FACES TO MESH
// ======================
// triangulate x, z
var indexDelaunay = Delaunator.from(
points3d.map((v) => {
return [v.x, v.z];
})
);
// delaunay index => three.js index
var meshIndex = [];
for (let i = 0; i < indexDelaunay.triangles.length; i++) {
meshIndex.push(indexDelaunay.triangles[i]);
}
// add three.js index to the existing geometry
geometry1.setIndex(meshIndex);
geometry1.computeVertexNormals();
// get the geometry points using attributes.position.count
const count = geometry1.attributes.position.count;
// assign a color attribute to geometry points
geometry1.setAttribute(
"color",
new THREE.BufferAttribute(new Float32Array(count * 3), 3)
);
const color = new THREE.Color();
const positions1 = geometry1.attributes.position;
const colors1 = geometry1.attributes.color;
// Generate color
color1 =
"rgb(" +
Math.floor(Math.random() * 255) +
"," +
Math.floor(Math.random() * 255) +
"," +
Math.floor(Math.random() * 255);
for (let i = 0; i < count; i++) {
color.setRGB(pixels[i][0] / 255, pixels[i][1] / 255, pixels[i][2] / 255);
colors1.setXYZ(i, color.r, color.g, color.b);
}
var mesh = new THREE.Mesh(
geometry1,
new THREE.MeshPhongMaterial({
color: 0xffffff,
opacity: 1,
vertexColors: true,
flatShading: true,
shininess: 30
})
);
mesh.rotation.x = Math.PI / 2;
mesh.position.y = 200;
mesh.position.x -= visibleWidth / 2.5;
mesh.scale.set(0.8, 0.8, 0.8);
scene.add(mesh);
const wireframeGeometry = new THREE.WireframeGeometry(geometry1);
const wireframeMaterial = new THREE.MeshBasicMaterial({ color: 0x111111 });
const wireframe = new THREE.LineSegments(
wireframeGeometry,
wireframeMaterial
);
mesh.add(wireframe);
wireframe.position.y = 2;
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(headerContainer.offsetWidth, headerContainer.offsetHeight);
var canvas = renderer.domElement;
canvas.id = "headerCanvas";
headerContainer.appendChild(canvas);
// * DEBUG enable orbit controls
//var controls = new THREE.OrbitControls(camera, canvas);
document.addEventListener("mousemove", onDocumentMouseMove);
document.addEventListener("mousedown", onDocumentMouseDown);
document.addEventListener("mouseup", onDocumentMouseUp);
window.addEventListener("resize", onWindowResize);
}
function onWindowResize() {
camera.aspect = headerContainer.offsetWidth / headerContainer.offsetHeight;
camera.updateProjectionMatrix();
renderer.setSize(headerContainer.offsetWidth, headerContainer.offsetHeight);
}
var distX;
var distY;
var pos;
console.log(mouse.x);
function onDocumentMouseMove(event) {
// Update the mouse variable
//event.preventDefault();
mouse.x = (event.clientX / headerContainer.offsetWidth) * 2 - 1;
mouse.y = -(event.clientY / headerContainer.offsetHeight) * 2 + 1;
// Make the light follow the mouse
var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
vector.unproject(camera);
var dir = vector.sub(camera.position).normalize();
var distance = -camera.position.z / dir.z;
pos = camera.position.clone().add(dir.multiplyScalar(distance));
//1. find distance X , distance Y
distX = pos.x - mouseLight.position.x;
distY = pos.y - mouseLight.position.y;
//Easing motion
//Progressive reduction of distance
mouseLight.position.x += distX / 10;
mouseLight.position.y += distY / 10;
mouseLight.position.z = mouseLightHeight;
}
function GetMousePosition() {}
function onDocumentMouseDown(event) {
mouseIsDown = true;
}
function onDocumentMouseUp(event) {
mouseIsDown = false;
}
//
function animate() {
if (mouseIsDown & (mouseLight.distance < 150)) {
mouseLight.distance += 2;
} else if (mouseLight.distance > 30) {
mouseLight.distance -= 2;
}
if (pos) {
if (mouseLight.position.x != pos.x) {
distX = pos.x - mouseLight.position.x;
mouseLight.position.x += distX / 50;
}
if (mouseLight.position.y != pos.y) {
distY = pos.y - mouseLight.position.y;
mouseLight.position.y += distY / 50;
}
}
requestAnimationFrame(animate);
render();
}
function render() {
camera.lookAt(scene.position);
renderer.render(scene, camera);
}
function toggleLight() {
if (light.intensity == 0) {
light.intensity = 1;
} else light.intensity = 0;
}
This Pen doesn't use any external CSS resources.