Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save kazzohikaru/e46d7cc711b2c4a2451ff2fe38f7480f to your computer and use it in GitHub Desktop.

Select an option

Save kazzohikaru/e46d7cc711b2c4a2451ff2fe38f7480f to your computer and use it in GitHub Desktop.
Born of Data – A Three.js Particle Lifeform

Born of Data – A Three.js Particle Lifeform

Built with Three.js and glsl shaders, this interactive particle animation creates glowing formations that shift between a sphere, vortex, and nebula. Users can toggle trails, glow, and color from a cybernetic control panel.

A Pen by Techartist on CodePen.

License.

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neural Nexus</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600;700;900&display=swap');
:root {
--primary-color: #00ffff;
--accent-color: #88ffff;
--secondary-color: #0088ff;
--primary-rgb: 0, 255, 255;
--accent-rgb: 136, 255, 255;
--secondary-rgb: 0, 136, 255;
--gradient-start: #00ff88;
--gradient-middle: #00ffff;
--gradient-end: #0088ff;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background: #000;
font-family: 'Orbitron', monospace;
color: var(--primary-color);
}
#container {
position: fixed;
width: 100%;
height: 100%;
background: linear-gradient(135deg,
#000506 0%,
#001133 25%,
#000814 50%,
#001a0a 75%,
#000208 100%
);
}
.grid-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0.05;
background-image:
linear-gradient(rgba(var(--primary-rgb), 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(var(--primary-rgb), 0.1) 1px, transparent 1px);
background-size: 60px 60px;
animation: gridShift 15s linear infinite;
}
@keyframes gridShift {
0% { transform: translate(0, 0); }
100% { transform: translate(60px, 60px); }
}
.scan-line {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 8px;
background: linear-gradient(90deg,
transparent 0%,
var(--primary-color) 40%,
var(--accent-color) 50%,
var(--primary-color) 60%,
transparent 100%);
box-shadow:
0 0 40px var(--primary-color),
0 0 80px rgba(var(--primary-rgb), 0.6),
0 0 120px rgba(var(--primary-rgb), 0.3);
animation: scanMove 2.5s ease-in-out infinite;
}
@keyframes scanMove {
0%, 100% { top: 0%; opacity: 0; }
10%, 90% { opacity: 1; }
50% { top: 100%; }
}
.tech-pattern {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0.03;
background-image:
radial-gradient(circle at 20% 20%, rgba(var(--primary-rgb), 0.15) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(var(--secondary-rgb), 0.15) 0%, transparent 50%),
radial-gradient(circle at 50% 50%, rgba(var(--accent-rgb), 0.08) 0%, transparent 70%);
animation: techPulse 6s ease-in-out infinite;
}
@keyframes techPulse {
0%, 100% { opacity: 0.03; transform: scale(1); }
50% { opacity: 0.08; transform: scale(1.15); }
}
.circuit-lines {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0.1;
background-image:
linear-gradient(45deg, transparent 40%, rgba(var(--primary-rgb), 0.15) 41%, rgba(var(--primary-rgb), 0.15) 42%, transparent 43%),
linear-gradient(-45deg, transparent 40%, rgba(var(--secondary-rgb), 0.15) 41%, rgba(var(--secondary-rgb), 0.15) 42%, transparent 43%);
background-size: 180px 180px;
animation: circuitFlow 12s linear infinite;
}
@keyframes circuitFlow {
0% { background-position: 0px 0px, 0px 0px; }
100% { background-position: 180px 180px, -180px 180px; }
}
#hud {
position: fixed;
top: 10px;
left: 10px;
font-size: 12px;
color: var(--primary-color);
text-transform: uppercase;
letter-spacing: 1px;
z-index: 100;
background: rgba(0, 0, 0, 0.6);
padding: 10px 15px;
border: 1px solid rgba(var(--primary-rgb), 0.2);
border-radius: 15px;
font-weight: 500;
backdrop-filter: blur(20px);
box-shadow:
0 0 15px rgba(var(--primary-rgb), 0.2),
inset 0 1px 0 rgba(var(--primary-rgb), 0.15),
inset 0 0 15px rgba(var(--primary-rgb), 0.1);
transition: all 0.3s ease;
}
#hud::before {
content: '';
position: absolute;
top: -1px;
left: -1px;
right: -1px;
bottom: -1px;
background: linear-gradient(45deg, var(--primary-color), transparent, var(--primary-color));
z-index: -1;
border-radius: 15px;
animation: borderGlow 2.5s ease-in-out infinite;
opacity: 0.3;
}
@keyframes borderGlow {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.7; }
}
.hud-line {
margin: 2px 0;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
}
.hud-line::after {
content: '';
position: absolute;
right: -8px;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 3px;
background: var(--primary-color);
border-radius: 50%;
animation: hudPulse 1.8s ease-in-out infinite;
}
@keyframes hudPulse {
0%, 100% { opacity: 0.3; box-shadow: 0 0 4px var(--primary-color); }
50% { opacity: 1; box-shadow: 0 0 12px var(--primary-color); }
}
.hud-value {
color: var(--accent-color);
font-weight: 500;
margin-left: 8px;
text-shadow: 0 0 8px rgba(var(--accent-rgb), 0.4);
}
#status-indicator {
width: 6px;
height: 6px;
background: var(--primary-color);
border-radius: 50%;
box-shadow: 0 0 8px var(--primary-color);
animation: statusPulse 1.8s ease-in-out infinite;
}
@keyframes statusPulse {
0%, 100% { opacity: 1; transform: scale(1); box-shadow: 0 0 8px var(--primary-color); }
50% { opacity: 0.5; transform: scale(1.2); box-shadow: 0 0 15px var(--primary-color); }
}
#data-stream {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 40px;
font-weight: 700;
color: var(--primary-color);
text-transform: uppercase;
letter-spacing: 6px;
opacity: 0 !important;
transition: opacity 1.2s ease;
z-index: 100;
pointer-events: none;
text-shadow:
0 0 15px var(--primary-color),
0 0 30px var(--primary-color),
0 0 45px var(--primary-color);
}
#control-panel {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
flex-wrap: nowrap;
justify-content: center;
gap: 15px;
z-index: 100;
width: auto;
padding: 0 15px;
}
.control-section {
display: flex;
align-items: center;
gap: 15px;
background: rgba(0, 0, 0, 0.6);
padding: 10px 20px;
border: 1px solid rgba(var(--primary-rgb), 0.2);
border-radius: 15px;
backdrop-filter: blur(20px);
position: relative;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1px;
overflow: hidden;
box-shadow: inset 0 0 15px rgba(var(--primary-rgb), 0.1);
}
.control-section::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 1px;
background: linear-gradient(90deg, transparent, var(--primary-color), transparent);
animation: controlSweep 3.5s linear infinite;
}
.control-section::after {
content: '';
position: absolute;
bottom: 0;
right: -100%;
width: 100%;
height: 1px;
background: linear-gradient(90deg, transparent, var(--primary-color), transparent);
animation: controlSweep 3.5s linear infinite reverse;
}
@keyframes controlSweep {
0% { left: -100%; }
100% { left: 100%; }
}
.cyber-switch {
position: relative;
width: 50px;
height: 25px;
background: #001133;
border: 1px solid var(--primary-color);
cursor: pointer;
transition: all 0.3s ease;
overflow: hidden;
border-radius: 12px;
}
.cyber-switch::before {
content: '';
position: absolute;
top: 1px;
left: 1px;
width: 23px;
height: 23px;
background: var(--primary-color);
transition: all 0.3s ease;
box-shadow: 0 0 8px var(--primary-color);
border-radius: 50%;
}
.cyber-switch::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(45deg, transparent 30%, rgba(var(--primary-rgb), 0.1) 50%, transparent 70%);
animation: switchShimmer 2.5s ease-in-out infinite;
}
@keyframes switchShimmer {
0%, 100% { transform: translateX(-100%); }
50% { transform: translateX(100%); }
}
.cyber-switch.active::before {
transform: translateX(25px);
background: var(--accent-color);
box-shadow: 0 0 12px var(--accent-color);
}
.cyber-switch.active {
background: #002233;
box-shadow: inset 0 0 8px var(--primary-color);
}
.switch-label {
color: var(--primary-color);
font-weight: 400;
user-select: none;
cursor: pointer;
text-shadow: 0 0 4px rgba(var(--primary-rgb), 0.3);
transition: all 0.3s ease;
}
.switch-label:hover {
color: var(--accent-color);
text-shadow: 0 0 8px rgba(var(--accent-rgb), 0.4);
}
#execute-btn, #controls-toggle {
background: rgba(0, 0, 0, 0.6);
border: 1px solid rgba(var(--primary-rgb), 0.2);
color: var(--primary-color);
font-family: 'Orbitron', monospace;
font-size: 12px;
font-weight: 500;
padding: 8px 18px;
cursor: pointer;
text-transform: uppercase;
letter-spacing: 1.5px;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
backdrop-filter: blur(20px);
border-radius: 20px;
box-shadow: inset 0 0 15px rgba(var(--primary-rgb), 0.1);
}
#execute-btn::before, #controls-toggle::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(var(--primary-rgb), 0.15), transparent);
transition: left 0.5s ease;
}
#execute-btn::after, #controls-toggle::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
linear-gradient(45deg, transparent 48%, rgba(var(--primary-rgb), 0.1) 49%, rgba(var(--primary-rgb), 0.1) 51%, transparent 52%),
linear-gradient(-45deg, transparent 48%, rgba(var(--primary-rgb), 0.1) 49%, rgba(var(--primary-rgb), 0.1) 51%, transparent 52%);
background-size: 20px 20px;
animation: buttonPattern 1.8s linear infinite;
opacity: 0;
transition: opacity 0.3s ease;
}
@keyframes buttonPattern {
0% { background-position: 0px 0px, 0px 0px; }
100% { background-position: 20px 20px, -20px 20px; }
}
#execute-btn:hover, #controls-toggle:hover {
background: rgba(var(--primary-rgb), 0.1);
box-shadow:
0 0 15px rgba(var(--primary-rgb), 0.4),
inset 0 0 15px rgba(var(--primary-rgb), 0.1);
transform: scale(1.03);
}
#execute-btn:hover::before, #controls-toggle:hover::before {
left: 100%;
}
#execute-btn:hover::after, #controls-toggle:hover::after {
opacity: 1;
}
#execute-btn:active, #controls-toggle:active {
transform: scale(0.98);
}
.cyber-icon {
width: 14px;
height: 14px;
stroke: var(--primary-color);
stroke-width: 2;
fill: none;
filter: drop-shadow(0 0 4px var(--primary-color));
transition: all 0.3s ease;
}
#execute-btn:hover .cyber-icon, #controls-toggle:hover .cyber-icon {
filter: drop-shadow(0 0 8px var(--primary-color));
transform: scale(1.05);
}
.performance-bars {
position: fixed;
top: 10px;
right: 10px;
display: flex;
flex-direction: column;
gap: 5px;
z-index: 100;
}
.perf-bar {
width: 100px;
height: 3px;
background: rgba(0, 0, 0, 0.6);
border: 1px solid var(--primary-color);
position: relative;
overflow: hidden;
}
.perf-bar::before {
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
background: linear-gradient(90deg, var(--gradient-start), var(--gradient-middle), var(--gradient-end));
transition: width 0.3s ease;
animation: perfPulse 1.8s ease-in-out infinite;
}
@keyframes perfPulse {
0%, 100% { opacity: 0.7; }
50% { opacity: 1; }
}
.perf-bar.cpu::before { width: 70%; }
.perf-bar.gpu::before { width: 85%; }
.perf-bar.memory::before { width: 55%; }
.perf-label {
font-size: 9px;
color: var(--primary-color);
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 1px;
}
#color-picker {
width: 30px;
height: 20px;
border: none;
background: transparent;
cursor: pointer;
padding: 0;
}
#controls-toggle {
display: none;
}
@media (max-width: 850px) {
#control-panel {
flex-direction: column;
gap: 12px;
bottom: 15px;
}
}
@media (max-width: 768px) {
#controls-toggle {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
font-size: 11px;
}
.control-section {
display: none;
}
#control-panel.expanded .control-section {
display: flex;
flex-wrap: wrap;
gap: 12px 8px;
align-items: center;
padding: 12px 15px;
font-size: 10px;
width: auto;
max-width: 300px;
}
#data-stream {
font-size: 32px;
letter-spacing: 4px;
}
#hud {
font-size: 11px;
padding: 10px 12px;
}
.performance-bars {
top: 10px;
right: 10px;
}
.perf-bar {
width: 80px;
height: 3px;
}
}
@media (max-width: 480px) {
#execute-btn {
padding: 10px 20px;
font-size: 11px;
}
#data-stream {
font-size: 24px;
letter-spacing: 2px;
}
.performance-bars {
display: none;
}
}
</style>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/"
}
}
</script>
<div id="container"></div>
<div class="grid-overlay"></div>
<div class="tech-pattern"></div>
<div class="circuit-lines"></div>
<div class="scan-line"></div>
<div id="hud">
<div class="hud-line">
<span>SYSTEM:</span>
<span class="hud-value">ONLINE</span>
<div id="status-indicator"></div>
</div>
<div class="hud-line">
<span>FPS:</span>
<span class="hud-value" id="fps-display">60</span>
</div>
<div class="hud-line">
<span>NODES:</span>
<span class="hud-value" id="node-display">28000</span>
</div>
<div class="hud-line">
<span>MODE:</span>
<span class="hud-value" id="mode-display">HARMONIC SPHERE</span>
</div>
<div class="hud-line">
<span>TRAILS:</span>
<span class="hud-value" id="trail-display">ACTIVE</span>
</div>
<div class="hud-line">
<span>GLOW:</span>
<span class="hud-value" id="glow-display">ENABLED</span>
</div>
</div>
<div class="performance-bars">
<div class="perf-label">CPU LOAD</div>
<div class="perf-bar cpu"></div>
<div class="perf-label">GPU LOAD</div>
<div class="perf-bar gpu"></div>
<div class="perf-label">MEMORY</div>
<div class="perf-bar memory"></div>
</div>
<div id="data-stream">HARMONIC SPHERE</div>
<div id="control-panel">
<button id="controls-toggle">
<svg class="cyber-icon" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round">
<line x1="4" y1="21" x2="4" y2="14"></line>
<line x1="4" y1="10" x2="4" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12" y2="3"></line>
<line x1="20" y1="21" x2="20" y2="16"></line>
<line x1="20" y1="12" x2="20" y2="3"></line>
<line x1="1" y1="14" x2="7" y2="14"></line>
<line x1="9" y1="8" x2="15" y2="8"></line>
<line x1="17" y1="16" x2="23" y2="16"></line>
</svg>
CONTROLS
</button>
<div class="control-section">
<div class="cyber-switch active" id="flow-switch"></div>
<span class="switch-label">DATA_FLOW</span>
<div class="cyber-switch active" id="trails-switch"></div>
<span class="switch-label">TRAILS</span>
<div class="cyber-switch active" id="glow-switch"></div>
<span class="switch-label">GLOW_EFFECT</span>
<span class="switch-label">COLOR</span>
<input type="color" id="color-picker" value="#00ffff">
</div>
<button id="execute-btn">
<svg class="cyber-icon" viewBox="0 0 24 24">
<polygon points="5,3 19,12 5,21"/>
</svg>
EXECUTE_NEXT
</button>
</div>
<script type="module">
import * as THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
let scene, camera, renderer, dataNodes, trailSystem, backgroundNodes;
let composer, controls;
let time = 0;
let currentVisualization = 0;
let isTransforming = false;
let transformProgress = 0;
let frameCount = 0;
let lastTime = performance.now();
let fps = 60;
let dataFlow = true;
let showTrails = true;
let glowEffect = true;
const nodeCount = 28000;
const trailCount = 10000;
const transformSpeed = 0.018;
const visualizationNames = [
"HARMONIC SPHERE",
"TOROIDAL VORTEX",
"NEBULA CORE"
];
let baseColors = ['#00ffff', '#ffff00', '#ff00ff'];
let colorSchemes = baseColors.map(generateScheme);
function generateScheme(baseHex) {
const base = new THREE.Color(baseHex);
const hsl = base.getHSL({h:0, s:0, l:0});
const scheme = [];
for (let i = 0; i < 10; i++) {
const h = (hsl.h + (i - 5) * 0.03) % 1;
const s = Math.min(1, Math.max(0, hsl.s + (i - 5) * 0.02));
const l = Math.min(1, Math.max(0, hsl.l + (i - 5) * 0.03));
const c = new THREE.Color().setHSL(h, s, l);
scheme.push(c);
}
return scheme;
}
function updateCSSColors(baseHex) {
const base = new THREE.Color(baseHex);
const hsl = base.getHSL({h:0, s:0, l:0});
const accentHsl = {h: hsl.h, s: hsl.s, l: Math.min(1, hsl.l + 0.4)};
const accent = new THREE.Color().setHSL(accentHsl.h, accentHsl.s, accentHsl.l).getHexString();
const secondaryHsl = {h: (hsl.h + 0.08) % 1, s: hsl.s * 0.8, l: hsl.l};
const secondary = new THREE.Color().setHSL(secondaryHsl.h, secondaryHsl.s, secondaryHsl.l).getHexString();
const startColor = new THREE.Color().setHSL(hsl.h - 0.05, hsl.s, hsl.l);
const startHex = startColor.getHexString();
const endColor = new THREE.Color().setHSL(hsl.h + 0.1, hsl.s * 0.8, hsl.l);
const endHex = endColor.getHexString();
document.documentElement.style.setProperty('--primary-color', baseHex);
document.documentElement.style.setProperty('--accent-color', `#${accent}`);
document.documentElement.style.setProperty('--secondary-color', `#${secondary}`);
document.documentElement.style.setProperty('--gradient-start', `#${startHex}`);
document.documentElement.style.setProperty('--gradient-middle', baseHex);
document.documentElement.style.setProperty('--gradient-end', `#${endHex}`);
const primaryRgb = `${Math.round(base.r * 255)}, ${Math.round(base.g * 255)}, ${Math.round(base.b * 255)}`;
document.documentElement.style.setProperty('--primary-rgb', primaryRgb);
const accentC = new THREE.Color(`#${accent}`);
const accentRgb = `${Math.round(accentC.r * 255)}, ${Math.round(accentC.g * 255)}, ${Math.round(accentC.b * 255)}`;
document.documentElement.style.setProperty('--accent-rgb', accentRgb);
const secondaryC = new THREE.Color(`#${secondary}`);
const secondaryRgb = `${Math.round(secondaryC.r * 255)}, ${Math.round(secondaryC.g * 255)}, ${Math.round(secondaryC.b * 255)}`;
document.documentElement.style.setProperty('--secondary-rgb', secondaryRgb);
}
window.onload = initializeSystem;
function generateHarmonicSphere(i, count) {
const t = i / count;
const phi = Math.acos(1 - 2 * t);
const theta = Math.PI * (1 + Math.sqrt(5)) * i;
const radius = 80;
const perturbation = 0.2 * (Math.sin(5 * phi) * Math.cos(3 * theta) + Math.cos(4 * phi) * Math.sin(2 * theta));
const r = radius * (1 + perturbation);
const x = r * Math.sin(phi) * Math.cos(theta);
const y = r * Math.sin(phi) * Math.sin(theta);
const z = r * Math.cos(phi);
return new THREE.Vector3(x, y, z);
}
function generateToroidalVortex(i, count) {
const t = i / count;
const theta = t * Math.PI * 2;
const phi = theta * 10 + Math.PI * (1 + Math.sqrt(5)) * t * 10;
const major = 70;
const minor = 25 + Math.sin(theta * 3) * 5;
const x = (major + minor * Math.cos(phi)) * Math.cos(theta);
const y = (major + minor * Math.cos(phi)) * Math.sin(theta);
const z = minor * Math.sin(phi) + Math.cos(theta * 4) * 10;
return new THREE.Vector3(x, y, z);
}
function generateNebulaCore(i, count) {
const coreRatio = 0.25;
const coreCount = Math.floor(count * coreRatio);
if (i < coreCount) {
const t = i / coreCount;
const phi = Math.acos(1 - 2 * t);
const theta = Math.PI * (1 + Math.sqrt(5)) * i + Math.sin(t * 10) * 0.5;
const radius = 25 * Math.pow(Math.random(), 1.5);
const x = radius * Math.sin(phi) * Math.cos(theta);
const y = radius * Math.sin(phi) * Math.sin(theta);
const z = radius * Math.cos(phi) + Math.sin(theta * 2) * 2;
return new THREE.Vector3(x, y, z);
} else {
const ringParticles = count - coreCount;
const ringIndex = i - coreCount;
const numRings = 8;
const ringNum = Math.floor(ringIndex / (ringParticles / numRings));
const nodeInRing = ringIndex % Math.floor(ringParticles / numRings);
const nodeInRingT = nodeInRing / Math.floor(ringParticles / numRings);
const angle = nodeInRingT * Math.PI * 2 + Math.sin(ringNum * 2) * 0.3;
const baseRadius = 35 + ringNum * 10;
const ringRadius = baseRadius + Math.sin(angle * 4) * 5;
const tiltAngle = (ringNum % 2 === 0 ? 1 : -1) * (Math.PI / 6 + ringNum * Math.PI / 15);
const axisAngle = ringNum * Math.PI / 4;
const axis = new THREE.Vector3(Math.sin(axisAngle), Math.cos(axisAngle), Math.sin(axisAngle * 2)).normalize();
const rotationMatrix = new THREE.Matrix4().makeRotationAxis(axis, tiltAngle);
const pos = new THREE.Vector3(
Math.cos(angle) * ringRadius,
0,
Math.sin(angle) * ringRadius
);
pos.applyMatrix4(rotationMatrix);
return pos;
}
}
const visualizations = [
generateHarmonicSphere,
generateToroidalVortex,
generateNebulaCore
];
function createTrailSystem() {
const trailGeometry = new THREE.BufferGeometry();
const trailPositions = new Float32Array(trailCount * 3);
const trailColors = new Float32Array(trailCount * 3);
const trailSizes = new Float32Array(trailCount);
const trailOpacities = new Float32Array(trailCount);
for (let i = 0; i < trailCount; i++) {
trailPositions[i * 3] = (Math.random() - 0.5) * 120;
trailPositions[i * 3 + 1] = (Math.random() - 0.5) * 120;
trailPositions[i * 3 + 2] = (Math.random() - 0.5) * 120;
const colorScheme = colorSchemes[currentVisualization];
const color = colorScheme[Math.floor(Math.random() * colorScheme.length)];
trailColors[i * 3] = color.r;
trailColors[i * 3 + 1] = color.g;
trailColors[i * 3 + 2] = color.b;
trailSizes[i] = Math.random() * 2 + 0.8;
trailOpacities[i] = Math.random() * 0.6 + 0.3;
}
trailGeometry.setAttribute('position', new THREE.BufferAttribute(trailPositions, 3));
trailGeometry.setAttribute('color', new THREE.BufferAttribute(trailColors, 3));
trailGeometry.setAttribute('size', new THREE.BufferAttribute(trailSizes, 1));
trailGeometry.setAttribute('opacity', new THREE.BufferAttribute(trailOpacities, 1));
const trailMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
textureMap: { value: createTrailTexture() }
},
vertexShader: `
attribute vec3 color;
attribute float size;
attribute float opacity;
varying vec3 vColor;
varying float vOpacity;
uniform float time;
void main() {
vColor = color;
vOpacity = opacity * (0.5 + 0.5 * sin(time + position.x * 0.1));
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_PointSize = size * (300.0 / -mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
uniform sampler2D textureMap;
varying vec3 vColor;
varying float vOpacity;
void main() {
vec4 tex = texture2D(textureMap, gl_PointCoord);
gl_FragColor = vec4(vColor * tex.rgb, tex.a * vOpacity);
}
`,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false
});
trailSystem = new THREE.Points(trailGeometry, trailMaterial);
scene.add(trailSystem);
}
function createTrailTexture() {
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
const context = canvas.getContext('2d');
const gradient = context.createRadialGradient(32, 32, 0, 32, 32, 32);
gradient.addColorStop(0, 'rgba(255, 255, 255, 1.0)');
gradient.addColorStop(0.4, 'rgba(255, 255, 255, 0.5)');
gradient.addColorStop(0.8, 'rgba(255, 255, 255, 0.2)');
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
context.fillStyle = gradient;
context.fillRect(0, 0, 64, 64);
const texture = new THREE.CanvasTexture(canvas);
texture.needsUpdate = true;
return texture;
}
function createBackgroundParticles() {
const bgGeometry = new THREE.BufferGeometry();
const bgCount = 3000;
const bgPositions = new Float32Array(bgCount * 3);
const bgColors = new Float32Array(bgCount * 3);
const bgSizes = new Float32Array(bgCount);
for (let i = 0; i < bgCount; i++) {
const radius = 250 + Math.random() * 350;
const phi = Math.random() * Math.PI * 2;
const theta = Math.random() * Math.PI;
bgPositions[i * 3] = radius * Math.sin(theta) * Math.cos(phi);
bgPositions[i * 3 + 1] = radius * Math.sin(theta) * Math.sin(phi);
bgPositions[i * 3 + 2] = radius * Math.cos(theta);
const intensity = Math.random() * 0.4 + 0.2;
bgColors[i * 3] = intensity * 0.3;
bgColors[i * 3 + 1] = intensity * 0.4;
bgColors[i * 3 + 2] = intensity * 0.6;
bgSizes[i] = Math.random() * 3 + 1;
}
bgGeometry.setAttribute('position', new THREE.BufferAttribute(bgPositions, 3));
bgGeometry.setAttribute('color', new THREE.BufferAttribute(bgColors, 3));
bgGeometry.setAttribute('size', new THREE.BufferAttribute(bgSizes, 1));
const bgMaterial = new THREE.PointsMaterial({
size: 1.5,
vertexColors: true,
transparent: true,
opacity: 0.5,
blending: THREE.AdditiveBlending,
depthWrite: false
});
backgroundNodes = new THREE.Points(bgGeometry, bgMaterial);
scene.add(backgroundNodes);
}
function initializeSystem() {
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x000814, 0.0005);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2500);
camera.position.set(0, 0, 155);
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: "high-performance"
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
document.getElementById('container').appendChild(renderer.domElement);
setupCameraControls();
setupPostProcessing();
createDataVisualization();
createTrailSystem();
createBackgroundParticles();
setupEventHandlers();
updateCSSColors(baseColors[currentVisualization]);
document.getElementById('color-picker').value = baseColors[currentVisualization];
displayVisualizationName(visualizationNames[currentVisualization]);
executeMainLoop();
}
function setupCameraControls() {
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.1;
controls.rotateSpeed = 0.6;
controls.zoomSpeed = 0.9;
controls.minDistance = 30;
controls.maxDistance = 350;
controls.enablePan = false;
}
function setupPostProcessing() {
composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
0.5,
0.6,
0.8
);
composer.addPass(bloomPass);
const distortionShader = {
uniforms: {
tDiffuse: { value: null },
time: { value: 0.0 },
intensity: { value: 0.02 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float time;
uniform float intensity;
varying vec2 vUv;
void main() {
vec2 uv = vUv;
uv.x += sin(uv.y * 10.0 + time) * intensity;
uv.y += cos(uv.x * 10.0 + time) * intensity;
gl_FragColor = texture2D(tDiffuse, uv);
}
`
};
const distortionPass = new ShaderPass(distortionShader);
composer.addPass(distortionPass);
composer.addPass(new OutputPass());
}
function createDataVisualization() {
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(nodeCount * 3);
const colors = new Float32Array(nodeCount * 3);
const sizes = new Float32Array(nodeCount);
const initialVisualization = visualizations[currentVisualization];
for (let i = 0; i < nodeCount; i++) {
const pos = initialVisualization(i, nodeCount);
positions[i * 3] = pos.x;
positions[i * 3 + 1] = pos.y;
positions[i * 3 + 2] = pos.z;
assignParticleProperties(i, colors, sizes, currentVisualization);
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
geometry.userData.currentColors = new Float32Array(colors);
const material = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
textureMap: { value: createEnhancedParticleTexture() },
glowIntensity: { value: 1.0 }
},
vertexShader: `
attribute vec3 color;
attribute float size;
varying vec3 vColor;
uniform float time;
uniform float glowIntensity;
void main() {
vColor = color * (1.0 + 0.2 * sin(time * 2.0 + position.y * 0.05)) * glowIntensity;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_PointSize = size * (350.0 / -mvPosition.z) * (1.0 + 0.1 * sin(time + position.x));
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
uniform sampler2D textureMap;
uniform float time;
varying vec3 vColor;
void main() {
vec2 uv = gl_PointCoord - vec2(0.5);
float r = length(uv) * 2.0;
vec4 tex = texture2D(textureMap, gl_PointCoord);
float alpha = tex.a * (1.0 - smoothstep(0.8, 1.0, r));
gl_FragColor = vec4(vColor, alpha);
}
`,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false
});
dataNodes = new THREE.Points(geometry, material);
scene.add(dataNodes);
}
function assignParticleProperties(i, colors, sizes, vizIndex) {
const colorScheme = colorSchemes[vizIndex];
let color;
let brightness = 1.0;
if (sizes) {
if (vizIndex === 0) {
const channelIndex = Math.floor((i / nodeCount) * 12);
color = colorScheme[channelIndex % colorScheme.length];
brightness = 0.8 + Math.random() * 0.7;
sizes[i] = 1.0 + Math.random() * 2.5;
} else if (vizIndex === 1) {
const layerIndex = Math.floor(i / (nodeCount / 10));
color = colorScheme[layerIndex % colorScheme.length];
brightness = 0.9 + Math.random() * 0.6;
sizes[i] = 1.0 + Math.random() * 2.5;
} else {
const coreRatio = 0.25;
const coreCount = Math.floor(nodeCount * coreRatio);
if (i < coreCount) {
color = colorScheme[i % 4];
brightness = 1.2 + Math.random() * 0.6;
sizes[i] = 2.0 + Math.random() * 2.5;
} else {
color = colorScheme[4 + (i % (colorScheme.length - 4))];
brightness = 0.8 + Math.random() * 0.6;
sizes[i] = 1.0 + Math.random() * 2.0;
}
}
} else {
const channelIndex = Math.floor((i / nodeCount) * 12);
color = colorScheme[channelIndex % colorScheme.length];
brightness = 1.0;
}
colors[i * 3] = color.r * brightness;
colors[i * 3 + 1] = color.g * brightness;
colors[i * 3 + 2] = color.b * brightness;
}
function createEnhancedParticleTexture() {
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const context = canvas.getContext('2d');
const centerX = 128, centerY = 128;
const outerGradient = context.createRadialGradient(centerX, centerY, 0, centerX, centerY, 128);
outerGradient.addColorStop(0, 'rgba(255, 255, 255, 1.0)');
outerGradient.addColorStop(0.3, 'rgba(255, 255, 255, 0.7)');
outerGradient.addColorStop(0.6, 'rgba(200, 255, 255, 0.4)');
outerGradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
context.fillStyle = outerGradient;
context.fillRect(0, 0, 256, 256);
const coreGradient = context.createRadialGradient(centerX, centerY, 0, centerX, centerY, 20);
coreGradient.addColorStop(0, 'rgba(255, 255, 255, 1.0)');
coreGradient.addColorStop(1, 'rgba(200, 255, 255, 0.3)');
context.fillStyle = coreGradient;
context.beginPath();
context.arc(centerX, centerY, 20, 0, Math.PI * 2);
context.fill();
const texture = new THREE.CanvasTexture(canvas);
texture.needsUpdate = true;
return texture;
}
function setupEventHandlers() {
window.addEventListener('resize', onSystemResize);
const flowSwitch = document.getElementById('flow-switch');
if (flowSwitch) {
flowSwitch.addEventListener('click', () => {
dataFlow = !dataFlow;
flowSwitch.classList.toggle('active', dataFlow);
});
}
const trailsSwitch = document.getElementById('trails-switch');
if (trailsSwitch) {
trailsSwitch.addEventListener('click', () => {
showTrails = !showTrails;
trailsSwitch.classList.toggle('active', showTrails);
if (trailSystem) {
trailSystem.visible = showTrails;
document.getElementById('trail-display').textContent = showTrails ? 'ACTIVE' : 'INACTIVE';
}
});
}
const glowSwitch = document.getElementById('glow-switch');
if (glowSwitch) {
glowSwitch.addEventListener('click', () => {
glowEffect = !glowEffect;
glowSwitch.classList.toggle('active', glowEffect);
if (dataNodes) {
dataNodes.material.uniforms.glowIntensity.value = glowEffect ? 1.0 : 0.5;
}
document.getElementById('glow-display').textContent = glowEffect ? 'ENABLED' : 'DISABLED';
});
}
const colorPicker = document.getElementById('color-picker');
if (colorPicker) {
colorPicker.addEventListener('input', (e) => {
baseColors[currentVisualization] = e.target.value;
colorSchemes[currentVisualization] = generateScheme(e.target.value);
const colors = dataNodes.geometry.attributes.color.array;
for (let i = 0; i < nodeCount; i++) {
assignParticleProperties(i, colors, null, currentVisualization);
}
dataNodes.geometry.attributes.color.needsUpdate = true;
const trailColors = trailSystem.geometry.attributes.color.array;
const scheme = colorSchemes[currentVisualization];
for (let i = 0; i < trailCount; i++) {
const c = scheme[Math.floor(Math.random() * scheme.length)];
trailColors[i * 3] = c.r;
trailColors[i * 3 + 1] = c.g;
trailColors[i * 3 + 2] = c.b;
}
trailSystem.geometry.attributes.color.needsUpdate = true;
updateCSSColors(e.target.value);
});
}
const executeBtn = document.getElementById('execute-btn');
if (executeBtn) {
executeBtn.addEventListener('click', executeTransformation);
executeBtn.addEventListener('touchend', (e) => {
e.preventDefault();
executeTransformation();
});
}
const controlsToggle = document.getElementById('controls-toggle');
const controlPanel = document.getElementById('control-panel');
if (controlsToggle && controlPanel) {
controlsToggle.addEventListener('click', () => {
controlPanel.classList.toggle('expanded');
});
}
}
function executeTransformation() {
if (isTransforming) return;
const nextVisualization = (currentVisualization + 1) % visualizations.length;
transformToVisualization(nextVisualization);
displayVisualizationName(visualizationNames[nextVisualization]);
}
function transformToVisualization(newVisualization) {
document.getElementById('color-picker').value = baseColors[newVisualization];
updateCSSColors(baseColors[newVisualization]);
isTransforming = true;
transformProgress = 0;
const positions = dataNodes.geometry.attributes.position.array;
const colors = dataNodes.geometry.attributes.color.array;
const sizes = dataNodes.geometry.attributes.size.array;
const currentPositions = new Float32Array(positions);
const currentColors = new Float32Array(dataNodes.geometry.userData.currentColors);
const currentSizes = new Float32Array(sizes);
const visualizationFunction = visualizations[newVisualization];
const newPositions = new Float32Array(positions.length);
const newColors = new Float32Array(colors.length);
const newSizes = new Float32Array(sizes.length);
for (let i = 0; i < nodeCount; i++) {
const pos = visualizationFunction(i, nodeCount);
newPositions[i * 3] = pos.x;
newPositions[i * 3 + 1] = pos.y;
newPositions[i * 3 + 2] = pos.z;
assignParticleProperties(i, newColors, newSizes, newVisualization);
}
dataNodes.userData.fromPositions = currentPositions;
dataNodes.userData.toPositions = newPositions;
dataNodes.userData.fromColors = currentColors;
dataNodes.userData.toColors = newColors;
dataNodes.userData.fromSizes = currentSizes;
dataNodes.userData.toSizes = newSizes;
dataNodes.userData.targetVisualization = newVisualization;
if (trailSystem) {
const trailColors = trailSystem.geometry.attributes.color.array;
const newColorScheme = colorSchemes[newVisualization];
for (let i = 0; i < trailCount; i++) {
const color = newColorScheme[Math.floor(Math.random() * newColorScheme.length)];
trailColors[i * 3] = color.r;
trailColors[i * 3 + 1] = color.g;
trailColors[i * 3 + 2] = color.b;
}
trailSystem.geometry.attributes.color.needsUpdate = true;
}
}
function displayVisualizationName(name) {
const modeElement = document.getElementById('mode-display');
modeElement.textContent = name;
}
function updateSystemStatus() {
frameCount++;
const currentTime = performance.now();
if (currentTime - lastTime >= 1000) {
fps = Math.round((frameCount * 1000) / (currentTime - lastTime));
document.getElementById('fps-display').textContent = fps;
document.getElementById('node-display').textContent = nodeCount.toLocaleString();
frameCount = 0;
lastTime = currentTime;
}
}
function animateTrailSystem() {
if (!trailSystem || !showTrails) return;
const positions = trailSystem.geometry.attributes.position.array;
const opacities = trailSystem.geometry.attributes.opacity.array;
for (let i = 0; i < trailCount; i++) {
const ix = i * 3, iy = ix + 1, iz = ix + 2;
switch(currentVisualization) {
case 0:
positions[iy] += 0.35;
if (positions[iy] > 70) positions[iy] = -70;
positions[ix] += Math.sin(time * 2.2 + i * 0.12) * 0.12;
positions[iz] += Math.cos(time * 2.0 + i * 0.12) * 0.12;
break;
case 1:
const distance = Math.sqrt(positions[ix] * positions[ix] + positions[iz] * positions[iz]);
if (distance > 0.1) {
const expansion = 1 + Math.sin(time * 3.5 + i * 0.06) * 0.006;
positions[ix] *= expansion;
positions[iz] *= expansion;
}
positions[iy] += Math.sin(time * 2.8 + i * 0.04) * 0.25;
break;
case 2:
const orbitSpeed = 0.012 + (i % 6) * 0.006;
const x = positions[ix];
const z = positions[iz];
positions[ix] = x * Math.cos(orbitSpeed) - z * Math.sin(orbitSpeed);
positions[iz] = x * Math.sin(orbitSpeed) + z * Math.cos(orbitSpeed);
positions[iy] += Math.sin(time * 1.5 + i * 0.08) * 0.15;
break;
}
opacities[i] = 0.4 + Math.sin(time * 3.5 + i * 0.12) * 0.35;
}
trailSystem.geometry.attributes.position.needsUpdate = true;
trailSystem.geometry.attributes.opacity.needsUpdate = true;
trailSystem.material.uniforms.time.value = time;
}
function animateDataFlow() {
if (!dataNodes || isTransforming || !dataFlow) return;
const positions = dataNodes.geometry.attributes.position.array;
switch(currentVisualization) {
case 0:
for (let i = 0; i < nodeCount; i++) {
const ix = i * 3, iy = i * 3 + 1, iz = i * 3 + 2;
const x = positions[ix];
const z = positions[iz];
positions[ix] = x * Math.cos(0.005) - z * Math.sin(0.005);
positions[iz] = x * Math.sin(0.005) + z * Math.cos(0.005);
}
dataNodes.geometry.attributes.position.needsUpdate = true;
break;
case 1:
for (let i = 0; i < nodeCount; i++) {
const ix = i * 3, iy = i * 3 + 1, iz = i * 3 + 2;
const x = positions[ix];
const y = positions[iy];
positions[ix] = x * Math.cos(0.004) - y * Math.sin(0.004);
positions[iy] = x * Math.sin(0.004) + y * Math.cos(0.004);
}
dataNodes.geometry.attributes.position.needsUpdate = true;
break;
case 2:
const coreRatio = 0.25;
const coreCount = Math.floor(nodeCount * coreRatio);
for (let i = 0; i < nodeCount; i++) {
const ix = i * 3, iy = i * 3 + 1, iz = i * 3 + 2;
let x = positions[ix], y = positions[iy], z = positions[iz];
let rotationSpeed;
if (i < coreCount) {
rotationSpeed = 0.0015;
} else {
const ringParticles = nodeCount - coreCount;
const ringIndex = i - coreCount;
const numRings = 8;
const ringNum = Math.floor(ringIndex / (ringParticles / numRings));
rotationSpeed = 0.0025 + ringNum * 0.002;
}
positions[ix] = x * Math.cos(rotationSpeed) - z * Math.sin(rotationSpeed);
positions[iz] = x * Math.sin(rotationSpeed) + z * Math.cos(rotationSpeed);
}
dataNodes.geometry.attributes.position.needsUpdate = true;
break;
}
}
function onSystemResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
composer.setSize(window.innerWidth, window.innerHeight);
}
function executeMainLoop() {
requestAnimationFrame(executeMainLoop);
time += 0.012;
updateSystemStatus();
controls.update();
if (backgroundNodes) {
backgroundNodes.rotation.y += 0.0004;
backgroundNodes.rotation.x += 0.00015;
}
if (isTransforming) {
transformProgress += transformSpeed;
if (transformProgress >= 1.0) {
const positions = dataNodes.geometry.attributes.position.array;
const colors = dataNodes.geometry.attributes.color.array;
const sizes = dataNodes.geometry.attributes.size.array;
positions.set(dataNodes.userData.toPositions);
colors.set(dataNodes.userData.toColors);
sizes.set(dataNodes.userData.toSizes);
dataNodes.geometry.attributes.position.needsUpdate = true;
dataNodes.geometry.attributes.color.needsUpdate = true;
dataNodes.geometry.attributes.size.needsUpdate = true;
dataNodes.geometry.userData.currentColors = new Float32Array(dataNodes.userData.toColors);
currentVisualization = dataNodes.userData.targetVisualization;
isTransforming = false;
transformProgress = 0;
} else {
const positions = dataNodes.geometry.attributes.position.array;
const colors = dataNodes.geometry.attributes.color.array;
const sizes = dataNodes.geometry.attributes.size.array;
const t = transformProgress;
const ease = t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
for (let i = 0; i < positions.length; i++) {
positions[i] = dataNodes.userData.fromPositions[i] * (1 - ease) +
dataNodes.userData.toPositions[i] * ease;
colors[i] = dataNodes.userData.fromColors[i] * (1 - ease) +
dataNodes.userData.toColors[i] * ease;
}
for (let i = 0; i < sizes.length; i++) {
sizes[i] = dataNodes.userData.fromSizes[i] * (1 - ease) +
dataNodes.userData.toSizes[i] * ease;
}
dataNodes.geometry.attributes.position.needsUpdate = true;
dataNodes.geometry.attributes.color.needsUpdate = true;
dataNodes.geometry.attributes.size.needsUpdate = true;
}
} else {
animateDataFlow();
}
animateTrailSystem();
if (dataNodes) {
dataNodes.material.uniforms.time.value = time;
}
composer.passes[2].uniforms.time.value = time; // Update distortion pass time
composer.render();
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment