HEX
Server: LiteSpeed
System: Linux houston.panomity.com 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64
User: nudepix (1011)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: /home/panomity.de/vr.panomity.com/plugins/splat3d/viewer.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>3D-Modus</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;background:#0a0f1c;font-family:sans-serif}
#loading{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;color:#0ff;font-size:15px;text-align:center}
canvas{display:block;touch-action:none;cursor:grab}
canvas:active{cursor:grabbing}
</style>
</head>
<body>
<div id="loading">Lade 3D-Szene…</div>
<script type="importmap">
{ "imports": { "three": "https://unpkg.com/three@0.160.0/build/three.module.js" } }
</script>
<script type="module">
import * as THREE from 'three';

const params = new URLSearchParams(location.search);
const pano = params.get('pano');
const equirectUrl = params.get('equirect') || ('../../panos/p' + pano + '.tiles/equirect.jpg');
const depthUrl = params.get('depth') || ('../../panos/p' + pano + '.tiles/dmap.png');
const startYaw = parseFloat(params.get('yaw') || '0') * Math.PI / 180;

// Tiefen-Kalibrierung (Disparität → Distanz), wie beim Splat-Ansatz.
const DEPTH_SCALE = 1.6, NEAR_DISP = 0.05, FAR_CAP = 8.0;

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.01, 100);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(2, devicePixelRatio));
document.body.appendChild(renderer.domElement);

addEventListener('resize', () => {
    camera.aspect = innerWidth / innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(innerWidth, innerHeight);
});

function loadImage(url) {
    return new Promise((res, rej) => {
        const img = new Image();
        img.crossOrigin = 'anonymous';
        img.onload = () => res(img);
        img.onerror = rej;
        img.src = url;
    });
}

// Equirect auf max. 4096 begrenzen (GPU-Kompatibilität) und als Textur nutzen.
function makeTexture(img) {
    let w = img.naturalWidth, h = img.naturalHeight;
    const MAX = 4096;
    if (w > MAX) { h = Math.round(h * MAX / w); w = MAX; }
    const cv = document.createElement('canvas');
    cv.width = w; cv.height = h;
    cv.getContext('2d').drawImage(img, 0, 0, w, h);
    const tex = new THREE.CanvasTexture(cv);
    tex.colorSpace = THREE.SRGBColorSpace;
    tex.minFilter = THREE.LinearMipmapLinearFilter;
    tex.generateMipmaps = true;
    tex.anisotropy = renderer.capabilities.getMaxAnisotropy();
    return tex;
}

// Tiefenkarte in ein Raster lesen.
function readDepth(img, gw, gh) {
    const cv = document.createElement('canvas');
    cv.width = gw; cv.height = gh;
    const cx = cv.getContext('2d', { willReadFrequently: true });
    cx.drawImage(img, 0, 0, gw, gh);
    const px = cx.getImageData(0, 0, gw, gh).data;
    const disp = new Float32Array(gw * gh);
    for (let i = 0; i < gw * gh; i++) disp[i] = px[i * 4] / 255;
    return disp;
}

// Durchgehendes, texturiertes Tiefen-Mesh aus Equirect + Tiefenkarte.
function buildMesh(tex, disp, gw, gh) {
    const cols = gw + 1, rows = gh + 1;
    const positions = new Float32Array(cols * rows * 3);
    const uvs = new Float32Array(cols * rows * 2);
    const distAt = (i, j) => {
        const ii = Math.min(gw - 1, i), jj = Math.min(gh - 1, j);
        const d = disp[jj * gw + ii];
        return Math.min(DEPTH_SCALE / (d + NEAR_DISP), FAR_CAP);
    };
    let p = 0, u = 0;
    for (let j = 0; j < rows; j++) {
        const lat = (0.5 - j / gh) * Math.PI;
        const cl = Math.cos(lat), sl = Math.sin(lat);
        for (let i = 0; i < cols; i++) {
            const lon = (i / gw - 0.5) * 2 * Math.PI;
            const dist = distAt(i, j);
            positions[p++] = cl * Math.sin(lon) * dist;
            positions[p++] = -sl * dist;
            positions[p++] = cl * Math.cos(lon) * dist;
            uvs[u++] = i / gw;
            // three.js-Texturursprung liegt unten: Bildzeile invertieren,
            // sonst landet der Boden in der Zenit-Richtung (Bild steht kopf).
            uvs[u++] = 1 - j / gh;
        }
    }
    // Dreiecke; an großen Tiefensprüngen (Türkanten) auslassen, um
    // "Gummiband"-Verzerrungen zu vermeiden.
    const indices = [];
    const EDGE = 0.14;
    for (let j = 0; j < gh; j++) {
        for (let i = 0; i < gw; i++) {
            const a = j * cols + i, b = a + 1, c = a + cols, d = c + 1;
            const da = disp[Math.min(gh - 1, j) * gw + Math.min(gw - 1, i)];
            const db = disp[Math.min(gh - 1, j) * gw + Math.min(gw - 1, i + 1)];
            const dc = disp[Math.min(gh - 1, j + 1) * gw + Math.min(gw - 1, i)];
            if (Math.max(Math.abs(da - db), Math.abs(da - dc), Math.abs(db - dc)) > EDGE) continue;
            indices.push(a, c, b, b, c, d);
        }
    }
    const geo = new THREE.BufferGeometry();
    geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
    geo.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
    geo.setIndex(indices);
    geo.computeVertexNormals();
    const mat = new THREE.MeshBasicMaterial({ map: tex, side: THREE.DoubleSide });
    return new THREE.Mesh(geo, mat);
}

// Blickzustand (Kamera nahe Aufnahmepunkt, in-place drehen + kleine Parallaxe)
let yaw = startYaw, pitch = 0;
const pos = new THREE.Vector3(0, 0, 0);
const keys = {};

Promise.all([loadImage(equirectUrl), loadImage(depthUrl)]).then(([eq, dm]) => {
    const gw = 640, gh = 320;
    const tex = makeTexture(eq);
    const disp = readDepth(dm, gw, gh);
    scene.add(buildMesh(tex, disp, gw, gh));
    const l = document.getElementById('loading'); if (l) l.remove();
    window.__splatReady = true;
    loop(performance.now());
}).catch(e => {
    const l = document.getElementById('loading'); if (l) l.textContent = '3D-Szene konnte nicht geladen werden';
    window.__splatError = String(e);
});

let dragging = false, lastX = 0, lastY = 0;
addEventListener('pointerdown', e => { dragging = true; lastX = e.clientX; lastY = e.clientY; });
addEventListener('pointermove', e => {
    if (!dragging) return;
    yaw   -= (e.clientX - lastX) * 0.0032;
    pitch -= (e.clientY - lastY) * 0.0032;
    pitch = Math.max(-1.3, Math.min(1.3, pitch));
    lastX = e.clientX; lastY = e.clientY;
});
addEventListener('pointerup', () => dragging = false);
addEventListener('pointercancel', () => dragging = false);
addEventListener('wheel', e => { pos.addScaledVector(dirVec(), -e.deltaY * 0.0006); clampPos(); }, { passive: true });
addEventListener('keydown', e => { keys[e.key.toLowerCase()] = true; });
addEventListener('keyup', e => { keys[e.key.toLowerCase()] = false; });

function dirVec() {
    return new THREE.Vector3(
        Math.cos(pitch) * Math.sin(yaw),
        -Math.sin(pitch),
        Math.cos(pitch) * Math.cos(yaw));
}
function clampPos() { const MAX = 0.5; if (pos.length() > MAX) pos.setLength(MAX); }

let last = 0;
function loop(now) {
    requestAnimationFrame(loop);
    const dt = Math.min(0.05, (now - last) / 1000); last = now;
    const fwd = dirVec();
    const right = new THREE.Vector3().crossVectors(fwd, new THREE.Vector3(0, -1, 0)).normalize();
    const mv = new THREE.Vector3();
    if (keys['w'] || keys['arrowup']) mv.add(fwd);
    if (keys['s'] || keys['arrowdown']) mv.sub(fwd);
    if (keys['d'] || keys['arrowright']) mv.add(right);
    if (keys['a'] || keys['arrowleft']) mv.sub(right);
    if (mv.lengthSq() > 0) { mv.setLength(0.35 * dt); pos.add(mv); clampPos(); }
    camera.position.copy(pos);
    camera.up.set(0, -1, 0);
    camera.lookAt(pos.x + fwd.x, pos.y + fwd.y, pos.z + fwd.z);
    renderer.render(scene, camera);
}
</script>
</body>
</html>