Public
Edited
Jun 27, 2023
Insert cell
Insert cell
renderer = {
let container, stats;
const particlesData = [];
let positions, colors;
let particles;
let pointCloud;
let particlePositions;
let linesMesh;
const maxParticleCount = 2000;
let particleCount = MAX_POINTS;
const r = 800;
const rHalf = r / 2;

const effectController = {
showDots: true,
showLines: false,
minDistance: 150,
limitConnections: true,
maxConnections: 10,
particleCount: 10
};

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);
renderer.outputEncoding = THREE.sRGBEncoding;

const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.minDistance = 1000;
controls.maxDistance = 300000;
controls.enablePan = false;

const scene = new THREE.Scene();
scene.background = new THREE.Color("#ffffff");
// scene.background = new THREE.Color("#000000");

const group = new THREE.Group();
scene.add(group);

const helper = new THREE.BoxHelper(
new THREE.Mesh(new THREE.BoxGeometry(r, r, r))
);
helper.material.color.setHex(0x999999);
// helper.material.blending = THREE.AdditiveBlending;
helper.material.transparent = true;
group.add(helper);

const segments = maxParticleCount * maxParticleCount;
positions = new Float32Array(segments * 3);
colors = new Float32Array(segments * 3);

const pMaterial = new THREE.PointsMaterial({
color: 0xff0000,
size: 2,
// blending: THREE.AdditiveBlending,
transparent: true,
sizeAttenuation: false
});

particles = new THREE.BufferGeometry();
particlePositions = new Float32Array(maxParticleCount * 3);

for (let i = 0; i < maxParticleCount; i++) {
const x = Math.random() * r - r / 2;
const y = Math.random() * r - r / 2;
const z = Math.random() * r - r / 2;

particlePositions[i * 3] = x;
particlePositions[i * 3 + 1] = y;
particlePositions[i * 3 + 2] = z;

// add it to the geometry
particlesData.push({
velocity: new THREE.Vector3(
-1 + Math.random() * 2,
-1 + Math.random() * 2,
-1 + Math.random() * 2
),
numConnections: 0
});
}

particles.setDrawRange(0, particleCount);
particles.setAttribute(
"position",
new THREE.BufferAttribute(particlePositions, 3).setUsage(
THREE.DynamicDrawUsage
)
);
// create the particle system
pointCloud = new THREE.Points(particles, pMaterial);
group.add(pointCloud);

const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
"position",
new THREE.BufferAttribute(positions, 3).setUsage(THREE.DynamicDrawUsage)
);
geometry.setAttribute(
"color",
new THREE.BufferAttribute(colors, 3).setUsage(THREE.DynamicDrawUsage)
);
geometry.computeBoundingSphere();
geometry.setDrawRange(0, 0);

const material = new THREE.LineBasicMaterial({
color: 0x999999,
// opacity: 0.7,
// vertexColors: true,
blending: THREE.MultiplyBlending, //AdditiveBlending,
transparent: true
});

linesMesh = new THREE.LineSegments(geometry, material);
group.add(linesMesh);

function animate() {
let vertexpos = 0;
let colorpos = 0;
let numConnected = 0;

for (let i = 0; i < particleCount; i++) particlesData[i].numConnections = 0;

for (let i = 0; i < particleCount; i++) {
// get the particle
const particleData = particlesData[i];

particlePositions[i * 3] += particleData.velocity.x;
particlePositions[i * 3 + 1] += particleData.velocity.y;
particlePositions[i * 3 + 2] += particleData.velocity.z;

if (
particlePositions[i * 3 + 1] < -rHalf ||
particlePositions[i * 3 + 1] > rHalf
)
particleData.velocity.y = -particleData.velocity.y;

if (particlePositions[i * 3] < -rHalf || particlePositions[i * 3] > rHalf)
particleData.velocity.x = -particleData.velocity.x;

if (
particlePositions[i * 3 + 2] < -rHalf ||
particlePositions[i * 3 + 2] > rHalf
)
particleData.velocity.z = -particleData.velocity.z;

if (
effectController.limitConnections &&
particleData.numConnections >= effectController.maxConnections
)
continue;

// Check collision
for (let j = i + 1; j < particleCount; j++) {
const particleDataB = particlesData[j];
if (
effectController.limitConnections &&
particleDataB.numConnections >= effectController.maxConnections
)
continue;

const dx = particlePositions[i * 3] - particlePositions[j * 3];
const dy = particlePositions[i * 3 + 1] - particlePositions[j * 3 + 1];
const dz = particlePositions[i * 3 + 2] - particlePositions[j * 3 + 2];
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);

if (dist < effectController.minDistance) {
particleData.numConnections++;
particleDataB.numConnections++;

const alpha = 1.0 - dist / effectController.minDistance;

positions[vertexpos++] = particlePositions[i * 3];
positions[vertexpos++] = particlePositions[i * 3 + 1];
positions[vertexpos++] = particlePositions[i * 3 + 2];

positions[vertexpos++] = particlePositions[j * 3];
positions[vertexpos++] = particlePositions[j * 3 + 1];
positions[vertexpos++] = particlePositions[j * 3 + 2];

colors[colorpos++] = alpha;
colors[colorpos++] = alpha;
colors[colorpos++] = alpha;

colors[colorpos++] = alpha;
colors[colorpos++] = alpha;
colors[colorpos++] = alpha;

numConnected++;
}
}
}

linesMesh.geometry.setDrawRange(0, numConnected * 2);
linesMesh.geometry.attributes.position.needsUpdate = true;
linesMesh.geometry.attributes.color.needsUpdate = true;

pointCloud.geometry.attributes.position.needsUpdate = true;

requestAnimationFrame(animate);

// stats.update();
// render();
}
animate();

while (true) {
renderer.render(scene, camera);
group.rotation.y += 0.001;

yield renderer.domElement;
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more