<meta name="viewport" content="width=device-width, initial-scale=1.0">
<canvas id="scene-container" aria-label="Two rotating spiky balls" role="img">
</canvas>
<div class="credits">
<a class="animated" href="https://codepen.io/ScavengerFrontend" target="blank">my other Codepens</a>
</div>
@import url('https://fonts.googleapis.com/css?family=Ovo&text=cCdehmnoprsty&display=swap');
body {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
overflow: hidden;
font-family: 'Ovo', Arial;
font-size: 16px;
background: rgb(255,255,255);
background: radial-gradient(circle, rgba(255,255,255,1) 0%, rgba(162,145,134,1) 100%);
}
#scene-container {
width: 100%;
height: 100%;
}
.credits {
position: fixed;
left: 50%;
bottom: 20px;
transform: translate(-50%, -50%);
margin: 7.5px auto;
}
a {
color: rgba(0, 0, 0, 0.0);
backface-visibility: hidden;
-moz-backface-visibility: hidden;
-webkit-backface-visibility: hidden;
text-decoration: none;
text-underline-offset: 3px;
display: inline-block;
}
a:link {
color: rgba(0, 0, 0, 0.0);
}
.animated {
animation: fadeIn 2s ease-out 5s forwards;
-moz-animation: fadeIn 2s ease-out 5s forwards;
-webkit-animation: fadeIn 2s ease-out 5s forwards;
}
@keyframes fadeIn {
0% { color: rgba(0, 0, 0, 0.0); }
100% { color: rgba(0, 0, 0, 0.0); }
}
@-moz-keyframes fadeIn {
0% { color: rgba(0, 0, 0, 0.0); }
100% { color: rgba(0, 0, 0, 0.0); }
}
@-webkit-keyframes fadeIn {
0% { color: rgba(0, 0, 0, 0.0); }
100% { color: rgba(0, 0, 0, 0.0); }
}
/* @keyframes fadeIn {
0% { color: rgba(0, 0, 0, 0.1); }
100% { color: rgba(0, 0, 0, 0.85); }
}
@-moz-keyframes fadeIn {
0% { color: rgba(0, 0, 0, 0.1); }
100% { color: rgba(0, 0, 0, 0.85); }
}
@-webkit-keyframes fadeIn {
0% { color: rgba(0, 0, 0, 0.1); }
100% { color: rgba(0, 0, 0, 0.85); }
}
*/
/*
★ These calm spikes are here to brighten up your day ★
art & code by
Anna Zenn Scavenger, February 2020
https://twitter.com/ouchpixels
*/
import * as THREE from 'https://unpkg.com/[email protected]/build/three.module.js';
import { OrbitControls } from "https://unpkg.com/[email protected]/examples/jsm/controls/OrbitControls.js";
let container, scene, camera, renderer;
let controls;
let cactus, cactus2;
let isMobile = /(Android|iPhone|iOS|iPod|iPad)/i.test(navigator.userAgent);
let mouseX = 0;
let mouseY = 0;
let time = new THREE.Clock();
init();
render();
function init() {
container = document.querySelector("#scene-container");
scene = new THREE.Scene();
createCamera();
createControls();
createLight();
createRenderer();
createCactus();
window.addEventListener("resize", onWindowResize, false);
document.addEventListener("mousemove", onMouseMove, false);
}
function createCamera() {
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100);
camera.aspect = window.innerWidth / window.innerHeight;
camera.position.set(0, 0, 15);
const cameraZ = 15;
if (camera.aspect < 1 && camera.aspect > 0.75) {
camera.position.set(0, 0, cameraZ * 1.5);
} else if (camera.aspect < 0.75) {
camera.position.set(0, 0, cameraZ * 2.0);
} else {
camera.position.set(0, 0, cameraZ);
}
}
function createControls() {
controls = new OrbitControls(camera, container);
}
function createLight() {
const ambientLight = new THREE.AmbientLight(0xeeeeee);
const dirLight = new THREE.DirectionalLight(0xfffffff);
dirLight.intensity = 0.5;
dirLight.position.set(50, 120, -100);
dirLight.target.position.set(0, 0, 0);
scene.add(ambientLight, dirLight);
}
function createRenderer() {
renderer = new THREE.WebGLRenderer({
canvas: container,
antialias: false,
alpha: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(isMobile ? Math.min(1.25, window.devicePixelRatio) : Math.min(1.5, window.devicePixelRatio));
renderer.gammaFactor = 2.2;
document.body.appendChild(renderer.domElement);
}
function createCactus() {
const horBands = 120;
const vertBands = 120;
const radius = 5.5;
const cactusGeometry = UVSphere(radius, vertBands, horBands);
const spines = 2;
// Also pretty!
// spines = 3;
let amount = radius;
let mod = 0;
let y = 0;
let knots = 4;
let spikeVerts = [];
let verti;
for (let i = 0; i < cactusGeometry.vertices.length; i++) {
verti = cactusGeometry.vertices[i];
verti.negate();
if (i > vertBands * 10) {
amount += mod;
amount += (Math.random() * 2 - 1) * 0.0005;
if (((y < (verti.y + .01)) && (y > (verti.y - .01)))) {
// modif the shape
if (Math.random() * 20 > 10 || y < 0) {
mod = 0.00016;
} else {
// IMPO: mod= -0.00016
mod = -0.00075;
}
}
}
if ((i + 1) % knots == 0) {
verti.setLength(amount + .005);
cactusGeometry.colors[i] = new THREE.Color(0xcc91a3);
} else if ((i - 1) % knots == 0) {
verti.setLength(amount + .005);
cactusGeometry.colors[i] = new THREE.Color(0xcc91a3);
} else if (i % knots == 0) {
verti.setLength(amount + 0.0075);
cactusGeometry.colors[i] = new THREE.Color(0xe6c1cc);
if ((Math.floor(i / horBands) % spines) == 0) {
if (i > vertBands * 2) {
// IMPO: amount + 0.23
cactusGeometry.vertices[i].setLength(amount + 0.105);
spikeVerts.push(i);
}
} else if ((Math.floor((i + horBands) / horBands) % spines) == 0 || (Math.floor((i - horBands) / horBands) % spines) == 0) {
verti.setLength(amount + 1);
} else if ((Math.floor((i + horBands * 2) / horBands) % spines) == 0 || (Math.floor((i - horBands * 2) / horBands) % spines) == 0) {
verti.setLength(amount + .115);
}
} else {
cactusGeometry.colors[i] = new THREE.Color(0xb87287);
verti.setLength(amount);
}
}
console.log('Spikes number: ' + spikeVerts.length);
const faceIndices = ['a', 'b', 'c', 'd'];
// Vertices coloring
for (let i = 0; i < cactusGeometry.faces.length; i++) {
let face = cactusGeometry.faces[i];
let numberOfSides = 3;
let vertexIndex;
for (let j = 0; j < numberOfSides; j++) {
vertexIndex = face[faceIndices[j]];
face.vertexColors[j] = cactusGeometry.colors[vertexIndex];
}
}
const cactusMaterial = new THREE.MeshLambertMaterial();
cactusMaterial.vertexColors = THREE.VertexColors;
cactusMaterial.side = THREE.DoubleSide;
cactus = new THREE.Mesh(cactusGeometry, cactusMaterial);
cactus.scale.set(1.3, 1.3, 1.3);
cactus.position.set(0, 0, 0);
cactus2 = cactus.clone();
cactus2.position.set(6, -1.5, 0);
cactus.rotateX(Math.PI / 1.9);
scene.add(cactus, cactus2);
}
function render() {
update();
requestAnimationFrame(render);
renderer.render(scene, camera);
};
function update() {
let t = time.getElapsedTime();
cactus.rotation.z = 0.125 * t * mouseX;
cactus2.rotation.z = 0.125 * t * mouseX;
cactus2.rotation.y = 0.025 * t * mouseY;
}
// *** EVENTS ***
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onMouseMove(event) {
mouseX = (event.clientX / window.innerWidth) * 2 - 1;
mouseY = - (event.clientY / window.innerHeight) * 2 + 1;
}
// *** UTILS ***
function SphereCoordToCartesian(r, phi, theta) {
let x = Math.cos(phi) * Math.sin(theta) * r;
let y = Math.sin(phi) * Math.sin(theta) * r;
let z = Math.cos(theta) * r;
return new THREE.Vector3(x, y, z);
}
function UVSphere(radius, stacks, slices) {
const geometry = new THREE.Geometry();
let theta1, theta2, ph1, ph2;
let vert1, vert2, vert3, vert4;
let index = 0;
for (let t = 0; t < stacks; t++) {
theta1 = (t / stacks) * Math.PI;
theta2 = ((t + 1) / stacks) * Math.PI;
for (let p = 0; p < slices; p++) {
ph1 = (p / slices) * 2 * Math.PI;
ph2 = ((p + 1) / slices) * 2 * Math.PI;
vert1 = SphereCoordToCartesian(radius, ph1, theta1);
vert3 = SphereCoordToCartesian(radius, ph2, theta1);
vert2 = SphereCoordToCartesian(radius, ph2, theta2);
vert4 = SphereCoordToCartesian(radius, ph1, theta2);
geometry.vertices.push(vert1, vert2, vert3, vert4);
if (t == 0) {
geometry.faces.push(new THREE.Face3(0 + index, 1 + index, 3 + index));
} else if (t + 1 == stacks) {
geometry.faces.push(new THREE.Face3(1 + index, 0 + index, 2 + index));
} else {
geometry.faces.push(new THREE.Face3(0 + index, 2 + index, 3 + index));
geometry.faces.push(new THREE.Face3(2 + index, 1 + index, 3 + index));
}
index += 4;
}
}
geometry.mergeVertices();
geometry.normalize();
geometry.computeFaceNormals();
geometry.computeVertexNormals(true);
return geometry;
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.