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.

Claude – Kugeln.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>12 × K₁₂ — 3D</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.2);
font: 12px monospace;
letter-spacing: 0.1em;
pointer-events: none;
transition: opacity 2s;
}
</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>
(() => {
// === Config ===
const NC = 12;
const NP = 12;
const BIG_R = 5.0;
const SMALL_R = 1.1;
// === Setup ===
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 6, 14);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(0x000000);
document.body.appendChild(renderer.domElement);
// === Distribute NC points on sphere (Fibonacci) ===
function fibSphere(n, radius) {
const pts = [];
const goldenAngle = 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 = goldenAngle * i;
pts.push(new THREE.Vector3(
r * Math.cos(theta) * radius,
y * radius,
r * Math.sin(theta) * radius
));
}
return pts;
}
// Cluster centers on big sphere
const centers = fibSphere(NC, BIG_R);
// Hues
const hues = [];
for (let i = 0; i < NC; i++) hues.push((i * 30) % 360);
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));
}
// === Build geometry ===
const clusters = [];
for (let ci = 0; ci < NC; ci++) {
const center = centers[ci];
const h = hues[ci];
// Local points on small sphere around center
const localPts = fibSphere(NP, SMALL_R);
const worldPts = localPts.map(p => p.clone().add(center));
clusters.push({ center, points: worldPts, hue: h });
}
// === Create line materials & geometries ===
const macroGroup = new THREE.Group();
const intraGroup = new THREE.Group();
const dotsGroup = new THREE.Group();
// --- Macro lines (cluster center to center) ---
for (let i = 0; i < NC; i++) {
for (let j = i + 1; j < NC; j++) {
const mixH = (hues[i] + hues[j]) / 2;
const color = hslToRGB(mixH, 45, 55);
const geom = new THREE.BufferGeometry().setFromPoints([
clusters[i].center, clusters[j].center
]);
const mat = new THREE.LineBasicMaterial({
color: color,
transparent: true,
opacity: 0.2,
linewidth: 1
});
macroGroup.add(new THREE.Line(geom, mat));
}
}
// --- Intra-cluster lines ---
for (let ci = 0; ci < NC; ci++) {
const pts = clusters[ci].points;
const color = hslToRGB(hues[ci], 65, 60);
// Batch all intra lines into one geometry for performance
const positions = [];
for (let i = 0; i < NP; i++) {
for (let j = i + 1; j < NP; j++) {
positions.push(pts[i].x, pts[i].y, pts[i].z);
positions.push(pts[j].x, pts[j].y, pts[j].z);
}
}
const geom = new THREE.BufferGeometry();
geom.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
const mat = new THREE.LineBasicMaterial({
color: color,
transparent: true,
opacity: 0.45
});
intraGroup.add(new THREE.LineSegments(geom, mat));
}
// --- Dots as small spheres ---
// Center dots (bigger)
for (let ci = 0; ci < NC; ci++) {
const color = hslToRGB(hues[ci], 60, 80);
const geo = new THREE.SphereGeometry(0.1, 12, 12);
const mat = new THREE.MeshBasicMaterial({ color });
const mesh = new THREE.Mesh(geo, mat);
mesh.position.copy(clusters[ci].center);
dotsGroup.add(mesh);
}
// Small point dots
for (let ci = 0; ci < NC; ci++) {
const color = hslToRGB(hues[ci], 70, 85);
const geo = new THREE.SphereGeometry(0.05, 8, 8);
const mat = new THREE.MeshBasicMaterial({ color });
for (let i = 0; i < NP; i++) {
const mesh = new THREE.Mesh(geo, mat);
mesh.position.copy(clusters[ci].points[i]);
dotsGroup.add(mesh);
}
}
// --- Glow sprites for cluster centers ---
const glowCanvas = document.createElement('canvas');
glowCanvas.width = 64;
glowCanvas.height = 64;
const gctx = glowCanvas.getContext('2d');
const grad = gctx.createRadialGradient(32, 32, 0, 32, 32, 32);
grad.addColorStop(0, 'rgba(255,255,255,0.6)');
grad.addColorStop(0.3, 'rgba(255,255,255,0.15)');
grad.addColorStop(1, 'rgba(255,255,255,0)');
gctx.fillStyle = grad;
gctx.fillRect(0, 0, 64, 64);
const glowTex = new THREE.CanvasTexture(glowCanvas);
for (let ci = 0; ci < NC; ci++) {
const color = hslToRGB(hues[ci], 70, 65);
const spriteMat = new THREE.SpriteMaterial({
map: glowTex,
color: color,
transparent: true,
opacity: 0.4,
blending: THREE.AdditiveBlending,
depthWrite: false
});
const sprite = new THREE.Sprite(spriteMat);
sprite.position.copy(clusters[ci].center);
sprite.scale.set(1.8, 1.8, 1);
dotsGroup.add(sprite);
}
scene.add(macroGroup);
scene.add(intraGroup);
scene.add(dotsGroup);
// === Simple orbit controls (no import needed) ===
let isDragging = false;
let prevMouse = { x: 0, y: 0 };
let rotY = 0, rotX = 0.3;
let targetRotY = 0, targetRotX = 0.3;
let autoRotate = true;
let dist = 14, targetDist = 14;
renderer.domElement.addEventListener('mousedown', e => {
isDragging = true;
prevMouse = { x: e.clientX, y: e.clientY };
autoRotate = false;
document.getElementById('hint').style.opacity = '0';
});
renderer.domElement.addEventListener('mousemove', e => {
if (!isDragging) return;
const dx = e.clientX - prevMouse.x;
const dy = e.clientY - prevMouse.y;
targetRotY += dx * 0.005;
targetRotX += dy * 0.005;
targetRotX = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, targetRotX));
prevMouse = { x: e.clientX, y: e.clientY };
});
renderer.domElement.addEventListener('mouseup', () => isDragging = false);
renderer.domElement.addEventListener('mouseleave', () => isDragging = false);
// Touch
renderer.domElement.addEventListener('touchstart', e => {
isDragging = true;
prevMouse = { x: e.touches[0].clientX, y: e.touches[0].clientY };
autoRotate = false;
document.getElementById('hint').style.opacity = '0';
});
renderer.domElement.addEventListener('touchmove', e => {
if (!isDragging) return;
e.preventDefault();
const dx = e.touches[0].clientX - prevMouse.x;
const dy = e.touches[0].clientY - prevMouse.y;
targetRotY += dx * 0.005;
targetRotX += dy * 0.005;
targetRotX = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, targetRotX));
prevMouse = { x: e.touches[0].clientX, y: e.touches[0].clientY };
}, { passive: false });
renderer.domElement.addEventListener('touchend', () => isDragging = false);
// Zoom
renderer.domElement.addEventListener('wheel', e => {
targetDist += e.deltaY * 0.01;
targetDist = Math.max(5, Math.min(30, targetDist));
document.getElementById('hint').style.opacity = '0';
});
// === Animate ===
function animate() {
requestAnimationFrame(animate);
if (autoRotate) targetRotY += 0.003;
// Smooth interpolation
rotY += (targetRotY - rotY) * 0.08;
rotX += (targetRotX - rotX) * 0.08;
dist += (targetDist - dist) * 0.08;
camera.position.set(
dist * Math.cos(rotX) * Math.sin(rotY),
dist * Math.sin(rotX),
dist * Math.cos(rotX) * Math.cos(rotY)
);
camera.lookAt(0, 0, 0);
renderer.render(scene, camera);
}
animate();
// Resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
})();
</script>
</body>
</html>
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>