File size: 9,172 Bytes
130bee5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
<!DOCTYPE html>
<html>
<head>
    <title>TMF_005 | High-Res Tree Explorer</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/OBJLoader.js"></script>
    <style>
        body { margin: 0; background: #050505; color: #ccc; font-family: 'Segoe UI', sans-serif; overflow: hidden; display: flex; }
        #sidebar { width: 350px; height: 100vh; background: #111; border-right: 1px solid #333; display: flex; flex-direction: column; z-index: 10; }
        #header { padding: 15px; border-bottom: 1px solid #333; font-size: 14px; font-weight: bold; color: #00ffcc; text-align: center; }
        #tree { flex-grow: 1; overflow-y: auto; padding: 10px; font-size: 11px; }
        .group { margin-bottom: 8px; }
        .group-header { cursor: pointer; background: #1a1a1a; padding: 6px 10px; border-radius: 4px; display: flex; justify-content: space-between; font-weight: bold; color: #fff; border: 1px solid #222; }
        .group-content { padding-left: 5px; margin-top: 5px; border-left: 1px solid #222; }
        .hidden { display: none; }
        .item { display: flex; align-items: center; padding: 4px; border-radius: 3px; border-bottom: 1px solid #1a1a1a; }
        .item:hover { background: #1a1a1a; }
        .item input[type="checkbox"] { margin-right: 8px; }
        .item input[type="range"] { width: 40px; margin-left: auto; height: 10px; }
        .color-dot { width: 10px; height: 10px; border-radius: 2px; margin-right: 8px; flex-shrink: 0; }
        .label-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 180px; }
        #container { flex-grow: 1; position: relative; }
        #loading { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #00ffcc; font-weight: bold; }
        .controls { padding: 10px; display: flex; gap: 5px; border-bottom: 1px solid #333; justify-content: center; }
        button { background: #333; color: white; border: none; padding: 5px 10px; cursor: pointer; border-radius: 3px; font-size: 10px; }
        button:hover { background: #444; }
        ::-webkit-scrollbar { width: 6px; }
        ::-webkit-scrollbar-thumb { background: #333; border-radius: 10px; }
    </style>
</head>
<body>

<div id="sidebar">
    <div id="header">TMF_005 | ANATOMICAL ATLAS</div>
    <div class="controls">
        <button onclick="toggleGlobal(false)">Hide All</button>
        <button onclick="toggleGlobal(true)">Show All</button>
    </div>
    <div id="tree"></div>
</div>

<div id="container"><div id="loading">CLEANING MESH SURFACES...</div></div>

<script>
    // --- FULL ANATOMICAL LOOKUP TABLE ---
    const lut_base = {
        2: "White Matter", 3: "Gray Matter", 4: "Lateral Ventricle", 5: "Inf Lat Vent", 7: "Cerebellum WM", 8: "Cerebellum CX",
        10: "Thalamus", 11: "Caudate", 12: "Putamen", 13: "Pallidum", 14: "3rd Ventricle", 15: "4th Ventricle",
        16: "Brain Stem", 17: "Hippocampus", 18: "Amygdala", 24: "CSF", 26: "Accumbens", 28: "Ventral DC (Hypothalamus)", 31: "Choroid Plexus",
        1002: "Caudal Ant Cingulate", 1003: "Caudal Mid Frontal", 1005: "Cuneus", 1006: "Entorhinal", 1007: "Fusiform", 1008: "Inferior Parietal",
        1009: "Inferior Temporal", 1010: "Ismthmus Cingulate", 1011: "Lateral Occipital", 1012: "Lateral Orbitofrontal", 1013: "Lingual",
        1014: "Medial Orbitofrontal", 1015: "Middle Temporal", 1016: "Parahippocampal", 1017: "Paracentral", 1018: "Pars Nasalis", 
        1019: "Pars Orbitalis", 1020: "Pars Triangularis", 1021: "Pericalcarine", 1022: "Postcentral", 1023: "Posterior Cingulate",
        1024: "Precentral", 1025: "Precuneus", 1026: "Rostral Ant Cingulate", 1027: "Rostral Mid Frontal", 1028: "Superior Frontal",
        1029: "Superior Parietal", 1030: "Superior Temporal", 1031: "Supramarginal", 1034: "Transverse Temporal", 1035: "Insula"
    };

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, (window.innerWidth - 350) / window.innerHeight, 0.1, 2000);
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth - 350, window.innerHeight);
    document.getElementById('container').appendChild(renderer.domElement);

    const controls = new THREE.OrbitControls(camera, renderer.domElement);
    scene.add(new THREE.AmbientLight(0xffffff, 0.6));
    const light = new THREE.PointLight(0xffffff, 1);
    camera.add(light); scene.add(camera);

    const meshes = {};
    const groups = {
        "Subcortical (Deep)": [],
        "Cortex (Left)": [],
        "Cortex (Right)": [],
        "Ventricles & CSF": []
    };

    function getColor(name) {
        let hash = 0;
        for (let i = 0; i < name.length; i++) { hash = name.charCodeAt(i) + ((hash << 5) - hash); }
        return "#" + (hash & 0x00FFFFFF).toString(16).padStart(6, '0');
    }

    const loader = new THREE.OBJLoader();
    loader.load('highres_smooth_brain.obj', (object) => {
        document.getElementById('loading').style.display = 'none';

        object.traverse((child) => {
            if (child.isMesh) {
                const id = parseInt(child.name.replace('Segment_', ''));
                let base_id = id > 2000 ? id - 1000 : id; // Normalize right cortex to left ID for name lookup
                let name = (id >= 2000 ? "R " : id >= 1000 ? "L " : "") + (lut_base[base_id] || lut_base[id] || "Part " + id);
                
                const color = getColor(child.name);
                child.material = new THREE.MeshStandardMaterial({ 
                    color: color, 
                    side: THREE.DoubleSide,
                    transparent: true,
                    opacity: 1.0
                });

                const isDefault = [17, 53, 28, 60].includes(id); // Hypothalamus/Hippocampus
                child.visible = isDefault;
                meshes[child.name] = child;

                const item = { id, name, color, meshName: child.name, visible: isDefault };
                if (id >= 2000) groups["Cortex (Right)"].push(item);
                else if (id >= 1000) groups["Cortex (Left)"].push(item);
                else if ([4, 5, 14, 15, 24, 31, 43, 44, 63].includes(id)) groups["Ventricles & CSF"].push(item);
                else groups["Subcortical (Deep)"].push(item);
            }
        });

        const tree = document.getElementById('tree');
        for (let gName in groups) {
            const gDiv = document.createElement('div');
            gDiv.className = 'group';
            // Categories are collapsed by default
            gDiv.innerHTML = `<div class="group-header" onclick="this.nextSibling.classList.toggle('hidden')">
                ${gName} <span>▾</span></div><div class="group-content hidden"></div>`;
            
            const content = gDiv.querySelector('.group-content');
            groups[gName].sort((a, b) => a.id - b.id).forEach(item => {
                const iDiv = document.createElement('div');
                iDiv.className = 'item';
                iDiv.innerHTML = `
                    <input type="checkbox" id="chk-${item.meshName}" ${item.visible ? 'checked' : ''}>
                    <div class="color-dot" style="background:${item.color}"></div>
                    <span class="label-text">${item.name}</span>
                    <input type="range" min="0" max="100" value="100" title="Opacity" id="alpha-${item.meshName}">
                `;
                
                // Visibility Click
                iDiv.querySelector('input[type="checkbox"]').onchange = (e) => {
                    meshes[item.meshName].visible = e.target.checked;
                };

                // Opacity Slider
                iDiv.querySelector('input[type="range"]').oninput = (e) => {
                    meshes[item.meshName].material.opacity = e.target.value / 100;
                    if (e.target.value < 100) meshes[item.meshName].visible = true; // Auto-show if sliding
                };

                content.appendChild(iDiv);
            });
            tree.appendChild(gDiv);
        }

        scene.add(object);
        const box = new THREE.Box3().setFromObject(object);
        const center = box.getCenter(new THREE.Vector3());
        camera.position.set(center.x, center.y + 200, center.z + 280);
        controls.target.copy(center);
        controls.update();
    });

    function toggleGlobal(show) {
        for (let id in meshes) {
            meshes[id].visible = show;
            const chk = document.getElementById(`chk-${id}`);
            if (chk) chk.checked = show;
        }
    }

    function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); }
    animate();

    window.addEventListener('resize', () => {
        renderer.setSize(window.innerWidth - 350, window.innerHeight);
        camera.aspect = (window.innerWidth - 350) / window.innerHeight;
        camera.updateProjectionMatrix();
    });
</script>
</body>
</html>