Erste lauffähige Fassung: 3D-Raum, Echtzeit-Multiplayer (WebSocket), Python-IDE via Pyodide im Browser, eigener Arbeitsplatz mit Live-Code-Monitor. Modularer, self-hostbarer Aufbau (Three.js + Node/Express + ws), ohne CDN- Abhängigkeit zur Laufzeit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
58 lines
2.3 KiB
JavaScript
58 lines
2.3 KiB
JavaScript
// codescreen.js — zeigt Code als Live-Textur auf einer Bildschirmfläche in der 3D-Welt.
|
|
// Dient (a) dem eigenen Monitor und (b) später den Monitoren anderer ("über die Schulter schauen").
|
|
// Bewusst als Textur (nicht CSS3D): echte 3D-Geometrie -> korrekte Verdeckung, scharf, performant.
|
|
import * as THREE from 'three';
|
|
|
|
const W = 1024, H = 640; // Textur-Auflösung
|
|
const PAD = 22, LINE = 27; // Innenabstand / Zeilenhöhe in px
|
|
const FONT_PX = 22;
|
|
const MAX_COLS = 54, MAX_LINES = 20;
|
|
|
|
export class CodeScreen {
|
|
/** @param {number} width - Breite der Fläche in Metern (Höhe folgt dem Seitenverhältnis). */
|
|
constructor(width = 2.0) {
|
|
this.canvas = document.createElement('canvas');
|
|
this.canvas.width = W; this.canvas.height = H;
|
|
this.ctx = this.canvas.getContext('2d');
|
|
this.tex = new THREE.CanvasTexture(this.canvas);
|
|
this.tex.colorSpace = THREE.SRGBColorSpace;
|
|
|
|
// MeshBasicMaterial = unbeleuchtet -> Bildschirm leuchtet gleichmäßig, immer lesbar.
|
|
const geo = new THREE.PlaneGeometry(width, width * H / W);
|
|
this.mesh = new THREE.Mesh(geo, new THREE.MeshBasicMaterial({ map: this.tex }));
|
|
|
|
this._title = 'Monitor';
|
|
this.setCode('');
|
|
}
|
|
|
|
setTitle(t) { this._title = t; }
|
|
|
|
/** Zeichnet den Code neu auf die Textur (nur bei Änderung aufrufen, nicht pro Frame). */
|
|
setCode(text) {
|
|
const g = this.ctx;
|
|
g.fillStyle = '#0b1020'; g.fillRect(0, 0, W, H);
|
|
g.strokeStyle = 'rgba(137,180,250,.45)'; g.lineWidth = 3; g.strokeRect(4, 4, W - 8, H - 8);
|
|
|
|
// Kopfzeile
|
|
g.fillStyle = '#11172a'; g.fillRect(6, 6, W - 12, 40);
|
|
g.fillStyle = '#89b4fa'; g.font = `bold 22px ui-monospace, monospace`;
|
|
g.fillText('💻 ' + this._title, PAD, 34);
|
|
|
|
// Code
|
|
g.fillStyle = '#dfe4f5'; g.font = `${FONT_PX}px ui-monospace, "JetBrains Mono", monospace`;
|
|
const lines = String(text).replace(/\t/g, ' ').split('\n');
|
|
const shown = lines.slice(0, MAX_LINES);
|
|
let y = 46 + LINE;
|
|
for (const ln of shown) {
|
|
g.fillText(ln.slice(0, MAX_COLS), PAD, y);
|
|
y += LINE;
|
|
}
|
|
if (lines.length > MAX_LINES) {
|
|
g.fillStyle = '#6c7086';
|
|
g.fillText(`… +${lines.length - MAX_LINES} Zeilen`, PAD, y);
|
|
}
|
|
this.tex.needsUpdate = true;
|
|
}
|
|
|
|
dispose() { this.tex.dispose(); this.mesh.geometry.dispose(); this.mesh.material.dispose(); }
|
|
}
|