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