programmierhub/public/js/codescreen.js
Stefan Franke 502131f07c VR ProgrammierHub — begehbarer 3D-Lernraum für hybride Programmier-Lehre
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>
2026-07-30 13:35:11 +00:00

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(); }
}