• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>BVH Visualization</title>
7</head>
8    <style>
9        body {
10            font-family: Arial, sans-serif;
11            background-color: #f0f0f0;
12            margin: 0;
13            padding: 20px;
14            display: flex;
15            flex-direction: column;
16            align-items: center;
17            height: 100vh;
18        }
19
20        #container {
21            width: 100%;
22            height: 50%;
23            border: 2px solid #ccc;
24            background-color: #fff;
25            padding: 10px;
26            border-radius: 8px;
27            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
28        }
29
30        .node circle {
31            fill: #999;
32            stroke: steelblue;
33            stroke-width: 1.5px;
34            cursor: pointer;
35        }
36
37        .node text {
38            font: 12px sans-serif;
39        }
40
41        .link {
42            fill: none;
43            stroke: #555;
44            stroke-opacity: 0.4;
45            stroke-width: 1.5px;
46        }
47
48        .tooltip {
49            position: absolute;
50            text-align: left;
51            width: 300px;
52            height: auto;
53            padding: 10px;
54            font: 12px sans-serif;
55            background: lightsteelblue;
56            border: 1px solid #333;
57            border-radius: 8px;
58            box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5);
59            overflow-y: auto;
60            max-height: 400px;
61            visibility: hidden;
62        }
63
64        .tooltip h3 {
65            margin: 0;
66            font-size: 14px;
67            font-weight: bold;
68        }
69
70        .tooltip p {
71            margin: 5px 0;
72            font-size: 12px;
73            word-wrap: break-word;
74        }
75
76        .tooltip .indent {
77            margin-left: 20px;
78        }
79
80        #header {
81            margin-bottom: 20px;
82            padding: 10px;
83            background-color: #e0e0e0;
84            border-radius: 8px;
85            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
86        }
87
88        #header h2 {
89            margin-top: 0;
90            color: #333;
91        }
92
93        #header p {
94            margin: 5px 0;
95            font-size: 14px;
96            line-height: 1.5;
97        }
98
99        #threeContainer {
100            width: 75%;
101            height: 100%;
102        }
103
104        #toggleControls {
105            width: 25%;
106            height: 100%;
107            overflow-y: auto;
108            margin-left: 20px;
109            border: 2px solid #ccc;
110            border-radius: 8px;
111            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
112            padding: 10px;
113        }
114
115        .toggle-container {
116            display: flex;
117            align-items: center;
118            margin-bottom: 10px;
119        }
120
121        .toggle-container label {
122            font-size: 14px;
123            margin-left: 10px;
124        }
125
126        #mainContainer {
127            display: flex;
128            width: 100%;
129            height: 50%;
130            justify-content: space-between;
131            margin-bottom: 20px;
132        }
133    </style>
134<body>
135    <div id="header"></div>
136    <div id="container">
137        <div class="tooltip"></div>
138        <svg id="canvas"></svg>
139    </div>
140    <br></br>
141    <br></br>
142    <br></br>
143    <div id="mainContainer">
144        <div id="threeContainer"></div>
145        <div id="toggleControls"></div>
146    </div>
147    <br></br>
148    <br></br>
149    <br></br>
150    <script src="https://d3js.org/d3.v6.min.js"></script>
151    <script src="https://cdn.jsdelivr.net/npm/three@0.130.1/build/three.min.js"></script>
152    <script src="https://cdn.jsdelivr.net/npm/three@0.130.1/examples/js/controls/OrbitControls.js"></script>
153    <script>
154        function formatProperties(properties, depth = 0, maxDepth = 2, index = '') {
155            if (depth > maxDepth) {
156                return '<p><strong>...</strong></p>';
157            }
158
159            let html = '';
160            for (const [key, value] of Object.entries(properties)) {
161                const currentIndex = index ? `${index}.${key}` : key;
162                if (typeof value === 'object' && value !== null) {
163                    html += `<p><strong>${currentIndex}:</strong><div class="indent">${formatProperties(value, depth + 1, maxDepth, currentIndex)}</div></p>`;
164                } else {
165                    let displayValue = value;
166                    if (typeof value === 'string' && value.length > 100) {
167                        displayValue = value.substring(0, 100) + '...';
168                    }
169                    html += `<p><strong>${currentIndex}:</strong> ${displayValue}</p>`;
170                }
171            }
172            return html;
173        }
174
175        d3.json('bvh_dump.json').then(function(treeData) {
176            document.getElementById('header').innerHTML = `<h2>Header Information</h2>${formatProperties(treeData.header)}`;
177
178            var width = 1400,
179                height = 400; // Adjusted for half the height
180
181            var svg = d3.select("#canvas")
182                .attr("width", width)
183                .attr("height", height)
184                .call(d3.zoom().on("zoom", function (event) {
185                    g.attr("transform", event.transform);
186                }))
187                .append("g");
188
189            var g = svg.append("g");
190
191            var root = d3.hierarchy(treeData.nodes.find(n => n.id === 0), function(d) {
192                return treeData.relationships[d.id].map(id => treeData.nodes.find(n => n.id === id));
193            });
194
195            var treeLayout = d3.tree().size([height, width - 250]);
196
197            treeLayout(root);
198
199            var link = g.selectAll(".link")
200                .data(root.links())
201                .enter().append("path")
202                .attr("class", "link")
203                .attr("d", d3.linkHorizontal()
204                    .x(d => d.y)
205                    .y(d => d.x));
206
207            var node = g.selectAll(".node")
208                .data(root.descendants())
209                .enter().append("g")
210                .attr("class", "node")
211                .attr("transform", d => `translate(${d.y},${d.x})`);
212
213            node.append("circle")
214                .attr("r", 10)
215                .on("click", function(event, d) {
216                    const propertiesHtml = formatProperties(d.data.properties);
217
218                    d3.select(".tooltip")
219                        .html(`<h3>ID: ${d.data.id}</h3>${propertiesHtml}`)
220                        .style("visibility", "visible")
221                        .style("left", (event.pageX + 20) + "px")
222                        .style("top", (event.pageY - 20) + "px");
223                    d3.selectAll("circle").attr("r", 10);
224                    d3.select(this).attr("r", 15);
225                });
226
227            node.append("text")
228                .attr("dy", 3)
229                .attr("x", d => d.children ? -12 : 12)
230                .style("text-anchor", d => d.children ? "end" : "start")
231                .text(d => d.data.type);
232
233            d3.select("body").on("click", function(event) {
234                if (!event.target.closest(".node")) {
235                    d3.select(".tooltip").style("visibility", "hidden");
236                    d3.selectAll("circle").attr("r", 10);
237                }
238            });
239
240            initThreeJs(treeData);
241        });
242
243        function initThreeJs(treeData) {
244            const scene = new THREE.Scene();
245            const camera = new THREE.PerspectiveCamera(75, window.innerWidth * 0.7 / 800, 0.1, 1000); // Adjusted for the new layout
246            const renderer = new THREE.WebGLRenderer({ antialias: true });
247            renderer.setSize(window.innerWidth * 0.7, 800); // Adjusted for half the height
248            document.getElementById('threeContainer').appendChild(renderer.domElement);
249
250            // Add orbit controls for zoom and rotate
251            const controls = new THREE.OrbitControls(camera, renderer.domElement);
252            controls.enableDamping = true;
253            controls.dampingFactor = 0.25;
254            controls.screenSpacePanning = false;
255            controls.maxPolarAngle = Math.PI / 2;
256
257            // Add grid helper and axis helper for reference
258            const gridHelper = new THREE.GridHelper(10, 10);
259            scene.add(gridHelper);
260
261            const axesHelper = new THREE.AxesHelper(5);
262            scene.add(axesHelper);
263
264            const geometries = [];
265
266            function createBox(coord, color, id) {
267                const geometry = new THREE.BoxGeometry(
268                    coord.x_upper - coord.x_lower,
269                    coord.y_upper - coord.y_lower,
270                    coord.z_upper - coord.z_lower
271                );
272                const edges = new THREE.EdgesGeometry(geometry);
273                const material = new THREE.LineBasicMaterial({ color: color });
274                const box = new THREE.LineSegments(edges, material);
275                box.position.set(
276                    (coord.x_upper + coord.x_lower) / 2,
277                    (coord.y_upper + coord.y_lower) / 2,
278                    (coord.z_upper + coord.z_lower) / 2
279                );
280
281                scene.add(box);
282                geometries.push(box);
283
284                addToggleControl(box, id);
285            }
286
287            function createTriangle(vertices, leafColor, id) {
288                const geometry = new THREE.BufferGeometry();
289                const verticesArray = new Float32Array(vertices.flat());
290                geometry.setAttribute('position', new THREE.BufferAttribute(verticesArray, 3));
291                const material = new THREE.MeshBasicMaterial({ color: leafColor, side: THREE.DoubleSide, wireframe: true });
292                const triangle = new THREE.Mesh(geometry, material);
293                scene.add(triangle);
294                geometries.push(triangle);
295
296                addToggleControl(triangle, id);
297            }
298
299            function createInstance(instanceProperties, color, id) {
300                const geometry = new THREE.SphereGeometry(0.1, 32, 32); // Create a sphere geometry
301                const material = new THREE.MeshBasicMaterial({ color: color, wireframe: false }); // Create a material with wireframe
302                const sphere = new THREE.Mesh(geometry, material); // Create a mesh
303
304                const { obj2world_p } = instanceProperties.part0;
305                sphere.position.set(obj2world_p[0], obj2world_p[1], obj2world_p[2]);
306
307                scene.add(sphere);
308                geometries.push(sphere);
309
310                addToggleControl(sphere, id);
311            }
312            function addToggleControl(geometry, id) {
313                const toggleContainer = document.createElement('div');
314                toggleContainer.className = 'toggle-container';
315
316                const checkbox = document.createElement('input');
317                checkbox.type = 'checkbox';
318                checkbox.checked = true;
319                checkbox.addEventListener('change', () => {
320                    geometry.visible = checkbox.checked;
321                });
322
323                const labelElement = document.createElement('label');
324                labelElement.textContent = `${id}`;
325
326                toggleContainer.appendChild(checkbox);
327                toggleContainer.appendChild(labelElement);
328                document.getElementById('toggleControls').appendChild(toggleContainer);
329            }
330
331            function handleNode(node, parentType) {
332                const leafColor = 0xffa500;
333                const internalColor = 0x00ff00;
334                if (node.type === 'AnvInternalNode') {
335                    // Check if the internal node is a fatLeaf
336                    const isFatProceduralLeaf = node.properties.node_type.nodeType === 0x3;
337                    const isFatInstanceLeaf = node.properties.node_type.nodeType === 0x1;
338                    node.properties.child_data.forEach((child, index) => {
339                        if (child.blockIncr !== 1 && child.blockIncr !== 2) {
340                            return;
341                        }
342                        const childIsProcedural = child.startPrim === 0x3 || isFatProceduralLeaf;
343                        const childIsInstance = child.startPrim === 0x1 || isFatInstanceLeaf;
344                        const color = (childIsProcedural || childIsInstance) ? leafColor : internalColor;
345                        let label = node.id + "'s child box";
346                        label += (childIsProcedural) ? " also a procedural leaf" : "";
347                        label += (childIsInstance) ? " also a instance leaf" : "";
348                        createBox(node.properties.actual_coords[index], color, label);
349                    });
350                } else {
351                    switch (node.type) {
352                        case 'AnvQuadLeafNode':
353                            createTriangle(node.properties.v, leafColor, `Triangle. NodeID=${node.id}`);
354                            break;
355                        case 'AnvInstanceLeaf':
356                            // Skip. Already drawn by parents
357                            break;
358                        case 'AnvAabbLeafNode':
359                            // Skip. Already drawn by parents
360                            break;
361                    }
362                }
363            }
364
365            // Draw AABB from header
366            const headerAABB = treeData.header.aabb;
367            createBox({
368                x_lower: headerAABB.min_x,
369                x_upper: headerAABB.max_x,
370                y_lower: headerAABB.min_y,
371                y_upper: headerAABB.max_y,
372                z_lower: headerAABB.min_z,
373                z_upper: headerAABB.max_z
374            }, 0xff00ff, 'Root AABB');
375
376            // Draw nodes
377            treeData.nodes.forEach(node => {
378                handleNode(node, node.properties.node_type);
379            });
380
381            camera.position.z = 5;
382
383            function animate() {
384                requestAnimationFrame(animate);
385                controls.update();
386                renderer.render(scene, camera);
387            }
388            animate();
389        }
390
391    </script>
392</body>
393</html>
394
395