Scan to open on your phone
Point a camera at the QR code - it opens this exact paste, no app needed.
https://codepastes.com/zzgsq2w7s738vex7unbenanntPlain Text
Here is an even more interactive version built directly into an embedded, playable simulation right here in the chat!
You can use your mouse to:
* **Click and Drag** to orbit around the black hole.
* **Scroll** to zoom in and out.
* Use the control panel on the top right to adjust the accretion disk speed, brightness, gravitational lens glow, and plasma jets in real time.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Embedded 3D Black Hole Sandbox</title>
<style>
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #000;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
#canvas-container {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
#info {
position: absolute;
top: 20px;
left: 20px;
color: #ffffff;
z-index: 100;
pointer-events: none;
text-shadow: 0 0 5px rgba(0,0,0,0.8);
}
h1 { margin: 0 0 5px 0; font-size: 18px; }
p { margin: 0; font-size: 11px; opacity: 0.7; }
</style>
</head>
<body>
<div id="info">
<h1>Black Hole Playground</h1>
<p>Drag to rotate | Scroll to zoom | Adjust sliders on right</p>
</div>
<div id="canvas-container"></div>
<!-- Three.js, OrbitControls, and lil-gui CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lil-gui@0.18"></script>
<script>
const container = document.getElementById('canvas-container');
// --- Configuration & Parameters Object ---
const params = {
diskSpeed: 0.2,
diskOpacity: 0.75,
haloGlow: 1.0,
jetIntensity: 0.6,
eventHorizonSize: 4.0,
autoRotate: false
};
// --- 1. Scene & Setup ---
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, container.clientWidth / container.clientHeight, 0.1, 1000);
camera.position.set(0, 15, 35);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
container.appendChild(renderer.domElement);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.maxDistance = 100;
controls.minDistance = 5;
// --- 2. Starfield Background ---
const starGeometry = new THREE.BufferGeometry();
const starCount = 4000;
const starPositions = new Float32Array(starCount * 3);
for (let i = 0; i < starCount * 3; i += 3) {
starPositions[i] = (Math.random() - 0.5) * 400;
starPositions[i + 1] = (Math.random() - 0.5) * 400;
starPositions[i + 2] = (Math.random() - 0.5) * 400;
}
starGeometry.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
const starMaterial = new THREE.PointsMaterial({ color: 0xffffff, size: 0.6, transparent: true, opacity: 0.8 });
const starField = new THREE.Points(starGeometry, starMaterial);
scene.add(starField);
// --- 3. Event Horizon (Black Sphere) ---
const eventHorizonGeo = new THREE.SphereGeometry(params.eventHorizonSize, 64, 64);
const eventHorizonMat = new THREE.MeshBasicMaterial({ color: 0x000000 });
const eventHorizon = new THREE.Mesh(eventHorizonGeo, eventHorizonMat);
scene.add(eventHorizon);
// --- 4. Gravitational Lensing Halo ---
const haloGeo = new THREE.SphereGeometry(params.eventHorizonSize * 1.15, 64, 64);
const haloMat = new THREE.ShaderMaterial({
vertexShader: `
varying vec3 vNormal;
void main() {
vNormal = normalize(normalMatrix * normal);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
varying vec3 vNormal;
uniform float uGlow;
void main() {
float intensity = pow(0.6 - dot(vNormal, vec3(0, 0, 1.0)), 3.0) * uGlow;
gl_FragColor = vec4(1.0, 0.4, 0.1, 1.0) * intensity;
}
`,
uniforms: {
uGlow: { value: params.haloGlow }
},
blending: THREE.AdditiveBlending,
side: THREE.BackSide,
transparent: true
});
const halo = new THREE.Mesh(haloGeo, haloMat);
scene.add(halo);
// --- 5. Accretion Disk ---
const particleCount = 14000;
const diskGeometry = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);
const innerRadius = params.eventHorizonSize * 1.3;
const outerRadius = 22;
const colorInner = new THREE.Color(0xfff0bb);
const colorMid = new THREE.Color(0xff6600);
const colorOuter = new THREE.Color(0xaa0000);
for (let i = 0; i < particleCount; i++) {
const radius = innerRadius + Math.pow(Math.random(), 1.5) * (outerRadius - innerRadius);
const angle = Math.random() * Math.PI * 2;
const height = (Math.random() - 0.5) * (radius * 0.08);
positions[i * 3] = Math.cos(angle) * radius;
positions[i * 3 + 1] = height;
positions[i * 3 + 2] = Math.sin(angle) * radius;
const t = (radius - innerRadius) / (outerRadius - innerRadius);
let particleColor = new THREE.Color();
if (t < 0.3) {
particleColor.lerpColors(colorInner, colorMid, t / 0.3);
} else {
particleColor.lerpColors(colorMid, colorOuter, (t - 0.3) / 0.7);
}
colors[i * 3] = particleColor.r;
colors[i * 3 + 1] = particleColor.g;
colors[i * 3 + 2] = particleColor.b;
}
diskGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
diskGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const diskMaterial = new THREE.PointsMaterial({
size: 0.35,
vertexColors: true,
blending: THREE.AdditiveBlending,
transparent: true,
opacity: params.diskOpacity
});
const accretionDisk = new THREE.Points(diskGeometry, diskMaterial);
scene.add(accretionDisk);
// --- 6. Relativistic Jets ---
const jetCount = 800;
function createJetData() {
const pos = new Float32Array(jetCount * 3);
const col = new Float32Array(jetCount * 3);
for (let i = 0; i < jetCount; i++) {
col[i * 3] = 0.4;
col[i * 3 + 1] = 0.7;
col[i * 3 + 2] = 1.0;
}
return { pos, col };
}
const topJetData = createJetData();
const bottomJetData = createJetData();
const topJetGeo = new THREE.BufferGeometry();
topJetGeo.setAttribute('position', new THREE.BufferAttribute(topJetData.pos, 3));
topJetGeo.setAttribute('color', new THREE.BufferAttribute(topJetData.col, 3));
const bottomJetGeo = new THREE.BufferGeometry();
bottomJetGeo.setAttribute('position', new THREE.BufferAttribute(bottomJetData.pos, 3));
bottomJetGeo.setAttribute('color', new THREE.BufferAttribute(bottomJetData.col, 3));
const jetMat = new THREE.PointsMaterial({
size: 0.25,
vertexColors: true,
blending: THREE.AdditiveBlending,
transparent: true,
opacity: params.jetIntensity
});
const topJet = new THREE.Points(topJetGeo, jetMat);
const bottomJet = new THREE.Points(bottomJetGeo, jetMat);
scene.add(topJet);
scene.add(bottomJet);
function updateJets() {
const tPos = topJetGeo.attributes.position.array;
const bPos = bottomJetGeo.attributes.position.array;
for (let i = 0; i < jetCount; i++) {
let h1 = tPos[i * 3 + 1] + 0.4;
if (h1 > 30) h1 = params.eventHorizonSize;
let spread1 = h1 * 0.08;
let angle1 = i + performance.now() * 0.002;
tPos[i * 3] = Math.cos(angle1) * spread1;
tPos[i * 3 + 1] = h1;
tPos[i * 3 + 2] = Math.sin(angle1) * spread1;
let h2 = bPos[i * 3 + 1] - 0.4;
if (h2 < -30) h2 = -params.eventHorizonSize;
let spread2 = Math.abs(h2) * 0.08;
let angle2 = i - performance.now() * 0.002;
bPos[i * 3] = Math.cos(angle2) * spread2;
bPos[i * 3 + 1] = h2;
bPos[i * 3 + 2] = Math.sin(angle2) * spread2;
}
topJetGeo.attributes.position.needsUpdate = true;
bottomJetGeo.attributes.position.needsUpdate = true;
}
// --- 7. Setup GUI Controls ---
const gui = new GUI();
gui.domElement.style.position = 'absolute';
gui.domElement.style.top = '10px';
gui.domElement.style.right = '10px';
container.appendChild(gui.domElement);
gui.add(params, 'diskSpeed', 0, 1.0, 0.01).name('Disk Rotation');
gui.add(params, 'diskOpacity', 0.1, 1.0, 0.05).name('Disk Brightness').onChange(v => diskMaterial.opacity = v);
gui.add(params, 'haloGlow', 0.0, 3.0, 0.1).name('Lensing Glow').onChange(v => haloMat.uniforms.uGlow.value = v);
gui.add(params, 'jetIntensity', 0.0, 1.0, 0.05).name('Jet Intensity').onChange(v => jetMat.opacity = v);
gui.add(params, 'autoRotate').name('Auto Orbit Cam');
// --- 8. Animation Loop ---
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const time = clock.getElapsedTime();
accretionDisk.rotation.y = time * params.diskSpeed;
updateJets();
topJet.rotation.y = -time * 0.5;
bottomJet.rotation.y = -time * 0.5;
if (params.autoRotate) {
camera.position.x = Math.sin(time * 0.2) * 35;
camera.position.z = Math.cos(time * 0.2) * 35;
camera.lookAt(0, 0, 0);
}
controls.update();
renderer.render(scene, camera);
}
animate();
// --- 9. Window Resize Handling ---
window.addEventListener('resize', () => {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
});
</script>
</body>
</html>
```
11,502 chars · codepastes.com