blob: eac0803ef0744d31d3f26035a345559c268a2654 [file] [log] [blame]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BVH Visualization</title>
</head>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
height: 100vh;
}
#container {
width: 100%;
height: 50%;
border: 2px solid #ccc;
background-color: #fff;
padding: 10px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.node circle {
fill: #999;
stroke: steelblue;
stroke-width: 1.5px;
cursor: pointer;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
.tooltip {
position: absolute;
text-align: left;
width: 300px;
height: auto;
padding: 10px;
font: 12px sans-serif;
background: lightsteelblue;
border: 1px solid #333;
border-radius: 8px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5);
overflow-y: auto;
max-height: 400px;
visibility: hidden;
}
.tooltip h3 {
margin: 0;
font-size: 14px;
font-weight: bold;
}
.tooltip p {
margin: 5px 0;
font-size: 12px;
word-wrap: break-word;
}
.tooltip .indent {
margin-left: 20px;
}
#header {
margin-bottom: 20px;
padding: 10px;
background-color: #e0e0e0;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
#header h2 {
margin-top: 0;
color: #333;
}
#header p {
margin: 5px 0;
font-size: 14px;
line-height: 1.5;
}
#threeContainer {
width: 75%;
height: 100%;
}
#toggleControls {
width: 25%;
height: 100%;
overflow-y: auto;
margin-left: 20px;
border: 2px solid #ccc;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
padding: 10px;
}
.toggle-container {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.toggle-container label {
font-size: 14px;
margin-left: 10px;
}
#mainContainer {
display: flex;
width: 100%;
height: 50%;
justify-content: space-between;
margin-bottom: 20px;
}
</style>
<body>
<div id="header"></div>
<div id="container">
<div class="tooltip"></div>
<svg id="canvas"></svg>
</div>
<br></br>
<br></br>
<br></br>
<div id="mainContainer">
<div id="threeContainer"></div>
<div id="toggleControls"></div>
</div>
<br></br>
<br></br>
<br></br>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.130.1/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.130.1/examples/js/controls/OrbitControls.js"></script>
<script>
function formatProperties(properties, depth = 0, maxDepth = 2, index = '') {
if (depth > maxDepth) {
return '<p><strong>...</strong></p>';
}
let html = '';
for (const [key, value] of Object.entries(properties)) {
const currentIndex = index ? `${index}.${key}` : key;
if (typeof value === 'object' && value !== null) {
html += `<p><strong>${currentIndex}:</strong><div class="indent">${formatProperties(value, depth + 1, maxDepth, currentIndex)}</div></p>`;
} else {
let displayValue = value;
if (typeof value === 'string' && value.length > 100) {
displayValue = value.substring(0, 100) + '...';
}
html += `<p><strong>${currentIndex}:</strong> ${displayValue}</p>`;
}
}
return html;
}
d3.json('bvh_dump.json').then(function(treeData) {
document.getElementById('header').innerHTML = `<h2>Header Information</h2>${formatProperties(treeData.header)}`;
var width = 1400,
height = 400; // Adjusted for half the height
var svg = d3.select("#canvas")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function (event) {
g.attr("transform", event.transform);
}))
.append("g");
var g = svg.append("g");
var root = d3.hierarchy(treeData.nodes.find(n => n.id === 0), function(d) {
return treeData.relationships[d.id].map(id => treeData.nodes.find(n => n.id === id));
});
var treeLayout = d3.tree().size([height, width - 250]);
treeLayout(root);
var link = g.selectAll(".link")
.data(root.links())
.enter().append("path")
.attr("class", "link")
.attr("d", d3.linkHorizontal()
.x(d => d.y)
.y(d => d.x));
var node = g.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("class", "node")
.attr("transform", d => `translate(${d.y},${d.x})`);
node.append("circle")
.attr("r", 10)
.on("click", function(event, d) {
const propertiesHtml = formatProperties(d.data.properties);
d3.select(".tooltip")
.html(`<h3>ID: ${d.data.id}</h3>${propertiesHtml}`)
.style("visibility", "visible")
.style("left", (event.pageX + 20) + "px")
.style("top", (event.pageY - 20) + "px");
d3.selectAll("circle").attr("r", 10);
d3.select(this).attr("r", 15);
});
node.append("text")
.attr("dy", 3)
.attr("x", d => d.children ? -12 : 12)
.style("text-anchor", d => d.children ? "end" : "start")
.text(d => d.data.type);
d3.select("body").on("click", function(event) {
if (!event.target.closest(".node")) {
d3.select(".tooltip").style("visibility", "hidden");
d3.selectAll("circle").attr("r", 10);
}
});
initThreeJs(treeData);
});
function initThreeJs(treeData) {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth * 0.7 / 800, 0.1, 1000); // Adjusted for the new layout
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth * 0.7, 800); // Adjusted for half the height
document.getElementById('threeContainer').appendChild(renderer.domElement);
// Add orbit controls for zoom and rotate
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
controls.screenSpacePanning = false;
controls.maxPolarAngle = Math.PI / 2;
// Add grid helper and axis helper for reference
const gridHelper = new THREE.GridHelper(10, 10);
scene.add(gridHelper);
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
const geometries = [];
function createBox(coord, color, id) {
const geometry = new THREE.BoxGeometry(
coord.x_upper - coord.x_lower,
coord.y_upper - coord.y_lower,
coord.z_upper - coord.z_lower
);
const edges = new THREE.EdgesGeometry(geometry);
const material = new THREE.LineBasicMaterial({ color: color });
const box = new THREE.LineSegments(edges, material);
box.position.set(
(coord.x_upper + coord.x_lower) / 2,
(coord.y_upper + coord.y_lower) / 2,
(coord.z_upper + coord.z_lower) / 2
);
scene.add(box);
geometries.push(box);
addToggleControl(box, id);
}
function createTriangle(vertices, leafColor, id) {
const geometry = new THREE.BufferGeometry();
const verticesArray = new Float32Array(vertices.flat());
geometry.setAttribute('position', new THREE.BufferAttribute(verticesArray, 3));
const material = new THREE.MeshBasicMaterial({ color: leafColor, side: THREE.DoubleSide, wireframe: true });
const triangle = new THREE.Mesh(geometry, material);
scene.add(triangle);
geometries.push(triangle);
addToggleControl(triangle, id);
}
function createInstance(instanceProperties, color, id) {
const geometry = new THREE.SphereGeometry(0.1, 32, 32); // Create a sphere geometry
const material = new THREE.MeshBasicMaterial({ color: color, wireframe: false }); // Create a material with wireframe
const sphere = new THREE.Mesh(geometry, material); // Create a mesh
const { obj2world_p } = instanceProperties.part0;
sphere.position.set(obj2world_p[0], obj2world_p[1], obj2world_p[2]);
scene.add(sphere);
geometries.push(sphere);
addToggleControl(sphere, id);
}
function addToggleControl(geometry, id) {
const toggleContainer = document.createElement('div');
toggleContainer.className = 'toggle-container';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = true;
checkbox.addEventListener('change', () => {
geometry.visible = checkbox.checked;
});
const labelElement = document.createElement('label');
labelElement.textContent = `${id}`;
toggleContainer.appendChild(checkbox);
toggleContainer.appendChild(labelElement);
document.getElementById('toggleControls').appendChild(toggleContainer);
}
function handleNode(node, parentType) {
const leafColor = 0xffa500;
const internalColor = 0x00ff00;
if (node.type === 'AnvInternalNode') {
// Check if the internal node is a fatLeaf
const isFatProceduralLeaf = node.properties.node_type.nodeType === 0x3;
const isFatInstanceLeaf = node.properties.node_type.nodeType === 0x1;
node.properties.child_data.forEach((child, index) => {
if (child.blockIncr !== 1 && child.blockIncr !== 2) {
return;
}
const childIsProcedural = child.startPrim === 0x3 || isFatProceduralLeaf;
const childIsInstance = child.startPrim === 0x1 || isFatInstanceLeaf;
const color = (childIsProcedural || childIsInstance) ? leafColor : internalColor;
let label = node.id + "'s child box";
label += (childIsProcedural) ? " also a procedural leaf" : "";
label += (childIsInstance) ? " also a instance leaf" : "";
createBox(node.properties.actual_coords[index], color, label);
});
} else {
switch (node.type) {
case 'AnvQuadLeafNode':
createTriangle(node.properties.v, leafColor, `Triangle. NodeID=${node.id}`);
break;
case 'AnvInstanceLeaf':
// Skip. Already drawn by parents
break;
case 'AnvAabbLeafNode':
// Skip. Already drawn by parents
break;
}
}
}
// Draw AABB from header
const headerAABB = treeData.header.aabb;
createBox({
x_lower: headerAABB.min_x,
x_upper: headerAABB.max_x,
y_lower: headerAABB.min_y,
y_upper: headerAABB.max_y,
z_lower: headerAABB.min_z,
z_upper: headerAABB.max_z
}, 0xff00ff, 'Root AABB');
// Draw nodes
treeData.nodes.forEach(node => {
handleNode(node, node.properties.node_type);
});
camera.position.z = 5;
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
}
</script>
</body>
</html>