Heute ist eine weitere Person der SuS dazu gekommen. Die Forscher:innen-AG hat offenbar auch Zuwachs bekommen. Wir haben ein Auto-Loop-Batch-Script angeschaut.
Notizen: https://fobizz.com/de/
Nächste Mal normal.

ziehen zum drehen · scrollen zum zoomen
Claude – Kugeln.
Rotierende Kugeln:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>32 × K₃₂ — Kreisel</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #000; overflow: hidden; }
canvas { display: block; }
#hint {
position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
color: rgba(255,255,255,0.18); font: 11px monospace;
letter-spacing: 0.1em; pointer-events: none; transition: opacity 3s;
}
</style>
</head>
<body>
<div id="hint">ziehen zum drehen · scrollen zum zoomen</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
(() => {
const NC = 32;
const NP = 32;
const BIG_R = 8.0;
const SMALL_R = 1.1;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 300);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setClearColor(0x000000);
document.body.appendChild(renderer.domElement);
function fibSphere(n, radius) {
const pts = [];
const golden = Math.PI * (3 - Math.sqrt(5));
for (let i = 0; i < n; i++) {
const y = 1 - (i / (n - 1)) * 2;
const r = Math.sqrt(1 - y * y);
const theta = golden * i;
pts.push(new THREE.Vector3(r * Math.cos(theta) * radius, y * radius, r * Math.sin(theta) * radius));
}
return pts;
}
function hslToRGB(h, s, l) {
s /= 100; l /= 100;
const k = n => (n + h / 30) % 12;
const a = s * Math.min(l, 1 - l);
const f = n => l - a * Math.max(-1, Math.min(k(n) - 3, 9 - k(n), 1));
return new THREE.Color(f(0), f(4), f(8));
}
// Gyro for meta sphere: very slow
function createMetaGyro() {
return {
getAxis(t) {
const tilt = 0.25 + 0.15 * Math.sin(t * 0.004) + 0.08 * Math.sin(t * 0.0025);
const prec = t * 0.002 + 0.2 * Math.sin(t * 0.0013);
return new THREE.Vector3(
Math.sin(tilt) * Math.cos(prec),
Math.cos(tilt),
Math.sin(tilt) * Math.sin(prec)
).normalize();
},
getSpeed(t) {
return 0.00025 * (1.0 + 0.3 * Math.sin(t * 0.006));
}
};
}
// Gyro for small spheres: clearly visible rotation, each unique
function createClusterGyro(seed) {
const s = seed;
// Each small sphere gets a distinct base speed
const base = 0.0015 + (s % 13) * 0.0002;
return {
getAxis(t) {
const tilt = 0.3 + 0.2 * Math.sin(t * (0.006 + s * 0.001) + s * 1.7)
+ 0.1 * Math.sin(t * (0.004 + s * 0.0007) + s * 0.9);
const prec = t * (0.004 + s * 0.0005) + 0.3 * Math.sin(t * (0.003 + s * 0.0008) + s * 2.3);
return new THREE.Vector3(
Math.sin(tilt) * Math.cos(prec),
Math.cos(tilt),
Math.sin(tilt) * Math.sin(prec)
).normalize();
},
getSpeed(t) {
return base * (1.0 + 0.25 * Math.sin(t * (0.008 + s * 0.001) + s * 0.7));
}
};
}
const centers = fibSphere(NC, BIG_R);
const hues = [];
for (let i = 0; i < NC; i++) hues.push((i * 360 / NC) % 360);
const gc = document.createElement('canvas');
gc.width = 128; gc.height = 128;
const gx = gc.getContext('2d');
const gr = gx.createRadialGradient(64, 64, 0, 64, 64, 64);
gr.addColorStop(0, 'rgba(255,255,255,0.9)');
gr.addColorStop(0.15, 'rgba(255,255,255,0.3)');
gr.addColorStop(0.4, 'rgba(255,255,255,0.05)');
gr.addColorStop(1, 'rgba(255,255,255,0)');
gx.fillStyle = gr;
gx.fillRect(0, 0, 128, 128);
const glowTex = new THREE.CanvasTexture(gc);
// === Build clusters — each is a pivot group with a spinning inner group ===
// pivot sits at center position (doesn't rotate)
// inner group sits at (0,0,0) inside pivot and rotates
const clusterPivots = [];
const clusterInners = [];
const clusterGyros = [];
for (let ci = 0; ci < NC; ci++) {
const pivot = new THREE.Group();
pivot.position.copy(centers[ci]);
const inner = new THREE.Group();
pivot.add(inner);
const h = hues[ci];
const localPts = fibSphere(NP, SMALL_R);
// Shell on inner (rotates with it)
const shellGeo = new THREE.SphereGeometry(SMALL_R, 24, 18);
const color = hslToRGB(h, 45, 32);
inner.add(new THREE.Mesh(shellGeo, new THREE.MeshBasicMaterial({
color, transparent: true, opacity: 0.05, side: THREE.DoubleSide, depthWrite: false
})));
inner.add(new THREE.Mesh(shellGeo, new THREE.MeshBasicMaterial({
color, wireframe: true, transparent: true, opacity: 0.025
})));
// Intra lines
const pos = [];
for (let i = 0; i < NP; i++)
for (let j = i + 1; j < NP; j++) {
pos.push(localPts[i].x, localPts[i].y, localPts[i].z);
pos.push(localPts[j].x, localPts[j].y, localPts[j].z);
}
const lg = new THREE.BufferGeometry();
lg.setAttribute('position', new THREE.Float32BufferAttribute(pos, 3));
inner.add(new THREE.LineSegments(lg, new THREE.LineBasicMaterial({
color: hslToRGB(h, 55, 52), transparent: true, opacity: 0.25
})));
// Dots
const dotGeo = new THREE.SphereGeometry(0.035, 4, 4);
const dotMat = new THREE.MeshBasicMaterial({ color: hslToRGB(h, 60, 82) });
for (let i = 0; i < NP; i++) {
const m = new THREE.Mesh(dotGeo, dotMat);
m.position.copy(localPts[i]);
inner.add(m);
}
for (let i = 0; i < NP; i += 2) {
const s = new THREE.Sprite(new THREE.SpriteMaterial({
map: glowTex, color: hslToRGB(h, 60, 58),
transparent: true, opacity: 0.12,
blending: THREE.AdditiveBlending, depthWrite: false
}));
s.position.copy(localPts[i]);
s.scale.set(0.4, 0.4, 1);
inner.add(s);
}
// Center dot (on pivot, doesn't rotate — stays at center)
pivot.add(new THREE.Mesh(
new THREE.SphereGeometry(0.14, 10, 10),
new THREE.MeshBasicMaterial({ color: hslToRGB(h, 55, 80) })
));
const cg = new THREE.Sprite(new THREE.SpriteMaterial({
map: glowTex, color: hslToRGB(h, 60, 55),
transparent: true, opacity: 0.5,
blending: THREE.AdditiveBlending, depthWrite: false
}));
cg.scale.set(1.8, 1.8, 1);
pivot.add(cg);
clusterPivots.push(pivot);
clusterInners.push(inner);
clusterGyros.push(createClusterGyro(ci + 1));
}
// === Meta group ===
const metaGroup = new THREE.Group();
const metaGeo = new THREE.SphereGeometry(BIG_R, 48, 36);
metaGroup.add(new THREE.Mesh(metaGeo, new THREE.MeshBasicMaterial({
color: new THREE.Color(0.15, 0.18, 0.25),
transparent: true, opacity: 0.04, side: THREE.DoubleSide, depthWrite: false
})));
metaGroup.add(new THREE.Mesh(metaGeo, new THREE.MeshBasicMaterial({
color: new THREE.Color(0.3, 0.35, 0.45),
wireframe: true, transparent: true, opacity: 0.03, depthWrite: false
})));
for (let lat = -2; lat <= 2; lat++) {
const y = (lat / 3) * BIG_R;
const ringR = Math.sqrt(BIG_R * BIG_R - y * y);
const curve = new THREE.EllipseCurve(0, 0, ringR, ringR, 0, Math.PI * 2, false, 0);
const pts2d = curve.getPoints(80);
const pts3d = pts2d.map(p => new THREE.Vector3(p.x, y, p.y));
metaGroup.add(new THREE.LineLoop(
new THREE.BufferGeometry().setFromPoints(pts3d),
new THREE.LineBasicMaterial({ color: new THREE.Color(0.4, 0.45, 0.55), transparent: true, opacity: 0.06 })
));
}
function makeTube(a, b, radius, color, opacity) {
const dir = new THREE.Vector3().subVectors(b, a);
const len = dir.length();
const mid = new THREE.Vector3().addVectors(a, b).multiplyScalar(0.5);
const geo = new THREE.CylinderGeometry(radius, radius, len, 4, 1);
const mat = new THREE.MeshBasicMaterial({ color, transparent: true, opacity, depthWrite: false });
const mesh = new THREE.Mesh(geo, mat);
mesh.position.copy(mid);
mesh.lookAt(b);
mesh.rotateX(Math.PI / 2);
return mesh;
}
for (let i = 0; i < NC; i++) {
for (let j = i + 1; j < NC; j++) {
const mixH = (hues[i] + hues[j]) / 2;
metaGroup.add(makeTube(centers[i], centers[j], 0.06, hslToRGB(mixH, 45, 45), 0.07));
metaGroup.add(makeTube(centers[i], centers[j], 0.025, hslToRGB(mixH, 50, 55), 0.3));
}
}
clusterPivots.forEach(p => metaGroup.add(p));
// Meta center
const metaCenterMat = new THREE.MeshBasicMaterial({
color: new THREE.Color(1, 1, 1), transparent: true, opacity: 0.6
});
metaGroup.add(new THREE.Mesh(new THREE.SphereGeometry(1.0, 24, 24), metaCenterMat));
const metaGlowMat = new THREE.SpriteMaterial({
map: glowTex, color: new THREE.Color(1, 1, 1),
transparent: true, opacity: 0.6,
blending: THREE.AdditiveBlending, depthWrite: false
});
const metaGlowSprite = new THREE.Sprite(metaGlowMat);
metaGlowSprite.scale.set(14, 14, 1);
metaGroup.add(metaGlowSprite);
const metaGlow2Mat = new THREE.SpriteMaterial({
map: glowTex, color: new THREE.Color(1, 1, 1),
transparent: true, opacity: 0.2,
blending: THREE.AdditiveBlending, depthWrite: false
});
const metaGlow2Sprite = new THREE.Sprite(metaGlow2Mat);
metaGlow2Sprite.scale.set(22, 22, 1);
metaGroup.add(metaGlow2Sprite);
scene.add(metaGroup);
const metaGyro = createMetaGyro();
// === Camera ===
let isDrag = false, prevM = { x: 0, y: 0 };
let camRotY = 0, camRotX = 0.3, tCamRotY = 0, tCamRotX = 0.3;
let dist = 26, tDist = 26;
const el = renderer.domElement;
el.addEventListener('mousedown', e => { isDrag = true; prevM = { x: e.clientX, y: e.clientY }; fadeHint(); });
el.addEventListener('mousemove', e => {
if (!isDrag) return;
tCamRotY += (e.clientX - prevM.x) * 0.004;
tCamRotX += (e.clientY - prevM.y) * 0.004;
tCamRotX = Math.max(-1.5, Math.min(1.5, tCamRotX));
prevM = { x: e.clientX, y: e.clientY };
});
el.addEventListener('mouseup', () => isDrag = false);
el.addEventListener('mouseleave', () => isDrag = false);
el.addEventListener('touchstart', e => { isDrag = true; prevM = { x: e.touches[0].clientX, y: e.touches[0].clientY }; fadeHint(); });
el.addEventListener('touchmove', e => {
if (!isDrag) return; e.preventDefault();
tCamRotY += (e.touches[0].clientX - prevM.x) * 0.004;
tCamRotX += (e.touches[0].clientY - prevM.y) * 0.004;
tCamRotX = Math.max(-1.5, Math.min(1.5, tCamRotX));
prevM = { x: e.touches[0].clientX, y: e.touches[0].clientY };
}, { passive: false });
el.addEventListener('touchend', () => isDrag = false);
el.addEventListener('wheel', e => { tDist += e.deltaY * 0.02; tDist = Math.max(10, Math.min(60, tDist)); fadeHint(); });
function fadeHint() { document.getElementById('hint').style.opacity = '0'; }
let time = 0;
const quat = new THREE.Quaternion();
function animate() {
requestAnimationFrame(animate);
time += 0.008;
camRotY += (tCamRotY - camRotY) * 0.06;
camRotX += (tCamRotX - camRotX) * 0.06;
dist += (tDist - dist) * 0.06;
camera.position.set(
dist * Math.cos(camRotX) * Math.sin(camRotY),
dist * Math.sin(camRotX),
dist * Math.cos(camRotX) * Math.cos(camRotY)
);
camera.lookAt(0, 0, 0);
// Meta: very slow gyroscopic rotation
const metaAxis = metaGyro.getAxis(time);
quat.setFromAxisAngle(metaAxis, metaGyro.getSpeed(time));
metaGroup.quaternion.multiply(quat);
// Each small sphere: independent visible rotation
for (let ci = 0; ci < NC; ci++) {
const axis = clusterGyros[ci].getAxis(time);
const speed = clusterGyros[ci].getSpeed(time);
quat.setFromAxisAngle(axis, speed);
clusterInners[ci].quaternion.multiply(quat);
}
// Iridescent center (slow)
const h = (time * 3) % 360;
metaCenterMat.color.copy(hslToRGB(h, 50, 70));
metaGlowMat.color.copy(hslToRGB(h, 60, 50));
metaGlow2Mat.color.copy(hslToRGB((h + 60) % 360, 40, 40));
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
})();
</script>
</body>
</html>