3d-heart / index.html
bruinjoe917's picture
Add 2 files
648f43a verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Heart Visualization</title>
<style>
body {
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f5f5;
color: #333;
overflow: hidden;
}
#container {
position: absolute;
width: 100%;
height: 100%;
display: flex;
}
#viewer {
flex: 1;
}
#controls {
width: 300px;
background-color: white;
padding: 20px;
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
overflow-y: auto;
z-index: 100;
}
h1 {
color: #e74c3c;
text-align: center;
margin-bottom: 20px;
font-size: 1.5em;
}
.control-group {
margin-bottom: 20px;
border-bottom: 1px solid #eee;
padding-bottom: 15px;
}
.control-group h2 {
font-size: 1.2em;
color: #333;
margin-bottom: 10px;
display: flex;
align-items: center;
}
.color-indicator {
display: inline-block;
width: 20px;
height: 20px;
border-radius: 50%;
margin-right: 10px;
border: 1px solid #ddd;
}
.toggle {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.toggle input {
margin-right: 10px;
}
.toggle label {
display: flex;
align-items: center;
cursor: pointer;
}
.info-text {
font-size: 0.85em;
color: #666;
margin-top: 5px;
margin-left: 30px;
}
#loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(255, 255, 255, 0.9);
padding: 20px;
border-radius: 5px;
text-align: center;
z-index: 1000;
}
.progress-bar {
width: 100%;
background-color: #e0e0e0;
border-radius: 5px;
margin-top: 10px;
}
.progress {
height: 10px;
background-color: #e74c3c;
border-radius: 5px;
width: 0%;
transition: width 0.3s;
}
.reset-btn {
background-color: #e74c3c;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
width: 100%;
margin-top: 15px;
transition: background-color 0.3s;
}
.reset-btn:hover {
background-color: #c0392b;
}
.legend {
display: flex;
flex-wrap: wrap;
margin-top: 20px;
}
.legend-item {
display: flex;
align-items: center;
margin: 5px 15px 5px 0;
font-size: 0.8em;
}
.legend-color {
width: 15px;
height: 15px;
border-radius: 50%;
margin-right: 5px;
border: 1px solid #ddd;
}
@media (max-width: 768px) {
#container {
flex-direction: column;
}
#controls {
width: 100%;
height: 300px;
}
}
</style>
</head>
<body>
<div id="container">
<div id="viewer"></div>
<div id="controls">
<h1>Interactive 3D Heart Anatomy</h1>
<div class="control-group">
<h2><span class="color-indicator" style="background-color: #ff1744;"></span>Left Ventricle</h2>
<div class="toggle">
<input type="checkbox" id="leftVentricleToggle" checked>
<label for="leftVentricleToggle">Show Left Ventricle</label>
</div>
<p class="info-text">Pumps oxygenated blood to the body through the aorta.</p>
</div>
<div class="control-group">
<h2><span class="color-indicator" style="background-color: #3d5afe;"></span>Right Ventricle</h2>
<div class="toggle">
<input type="checkbox" id="rightVentricleToggle" checked>
<label for="rightVentricleToggle">Show Right Ventricle</label>
</div>
<p class="info-text">Pumps deoxygenated blood to the lungs.</p>
</div>
<div class="control-group">
<h2><span class="color-indicator" style="background-color: #ff5252;"></span>Left Atrium</h2>
<div class="toggle">
<input type="checkbox" id="leftAtriumToggle" checked>
<label for="leftAtriumToggle">Show Left Atrium</label>
</div>
<p class="info-text">Receives oxygenated blood from the lungs.</p>
</div>
<div class="control-group">
<h2><span class="color-indicator" style="background-color: #448aff;"></span>Right Atrium</h2>
<div class="toggle">
<input type="checkbox" id="rightAtriumToggle" checked>
<label for="rightAtriumToggle">Show Right Atrium</label>
</div>
<p class="info-text">Receives deoxygenated blood from the body.</p>
</div>
<div class="control-group">
<h2><span class="color-indicator" style="background-color: #d32f2f;"></span>Aorta</h2>
<div class="toggle">
<input type="checkbox" id="aortaToggle" checked>
<label for="aortaToggle">Show Aorta</label>
</div>
<p class="info-text">Main artery carrying oxygenated blood from the left ventricle.</p>
</div>
<div class="control-group">
<h2><span class="color-indicator" style="background-color: #1e88e5;"></span>Pulmonary Arteries</h2>
<div class="toggle">
<input type="checkbox" id="pulmonaryArteriesToggle" checked>
<label for="pulmonaryArteriesToggle">Show Pulmonary Arteries</label>
</div>
<p class="info-text">Carry deoxygenated blood from the right ventricle to the lungs.</p>
</div>
<div class="control-group">
<h2><span class="color-indicator" style="background-color: #ff7043;"></span>Pulmonary Veins</h2>
<div class="toggle">
<input type="checkbox" id="pulmonaryVeinsToggle" checked>
<label for="pulmonaryVeinsToggle">Show Pulmonary Veins</label>
</div>
<p class="info-text">Carry oxygenated blood from the lungs to the left atrium.</p>
</div>
<div class="control-group">
<h2><span class="color-indicator" style="background-color: #43a047;"></span>Cardiac Fat</h2>
<div class="toggle">
<input type="checkbox" id="fatToggle" checked>
<label for="fatToggle">Show Cardiac Fat</label>
</div>
<p class="info-text">Fatty deposits surrounding the heart.</p>
</div>
<button class="reset-btn" id="resetView">Reset View</button>
<div class="legend">
<div class="legend-item">
<div class="legend-color" style="background-color: #ff1744;"></div>
Left Ventricle
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #3d5afe;"></div>
Right Ventricle
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #ff5252;"></div>
Left Atrium
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #448aff;"></div>
Right Atrium
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #d32f2f;"></div>
Aorta
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #1e88e5;"></div>
Pulmonary Arteries
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #ff7043;"></div>
Pulmonary Veins
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #43a047;"></div>
Cardiac Fat
</div>
</div>
</div>
</div>
<div id="loading">
<h2>Loading Heart Model</h2>
<p>Please wait while we prepare your 3D heart visualization...</p>
<div class="progress-bar">
<div class="progress" id="progress"></div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/GLTFLoader.min.js"></script>
<script>
// Initialize Three.js scene
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf5f5f5);
// Set up camera
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / (window.innerHeight - 300), 0.1, 1000);
camera.position.z = 5;
// Set up renderer with WebGL
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth - 300, window.innerHeight);
document.getElementById('viewer').appendChild(renderer.domElement);
// Add orbit controls for interaction
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
// Add lights to the scene
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
// Create a group to hold all heart parts
const heartGroup = new THREE.Group();
scene.add(heartGroup);
// Heart parts with their materials (colors)
const heartParts = {
leftVentricle: {
object: null,
color: 0xff1744,
visible: true
},
rightVentricle: {
object: null,
color: 0x3d5afe,
visible: true
},
leftAtrium: {
object: null,
color: 0xff5252,
visible: true
},
rightAtrium: {
object: null,
color: 0x448aff,
visible: true
},
aorta: {
object: null,
color: 0xd32f2f,
visible: true
},
pulmonaryArteries: {
object: null,
color: 0x1e88e5,
visible: true
},
pulmonaryVeins: {
object: null,
color: 0xff7043,
visible: true
},
fat: {
object: null,
color: 0x43a047,
visible: true
}
};
// Function to create simplified heart parts (in a real app, you would load 3D models)
function createHeartParts() {
// Left Ventricle
const leftVentricleGeometry = new THREE.SphereGeometry(1, 32, 32);
leftVentricleGeometry.scale(1, 1.3, 0.7);
const leftVentricleMaterial = new THREE.MeshPhongMaterial({
color: heartParts.leftVentricle.color,
shininess: 50,
transparent: true,
opacity: 0.9
});
heartParts.leftVentricle.object = new THREE.Mesh(leftVentricleGeometry, leftVentricleMaterial);
heartParts.leftVentricle.object.position.set(-0.7, 0, 0);
heartGroup.add(heartParts.leftVentricle.object);
updateLoadingProgress(12.5);
// Right Ventricle
const rightVentricleGeometry = new THREE.SphereGeometry(0.9, 32, 32);
rightVentricleGeometry.scale(1, 1.2, 0.6);
const rightVentricleMaterial = new THREE.MeshPhongMaterial({
color: heartParts.rightVentricle.color,
shininess: 50,
transparent: true,
opacity: 0.9
});
heartParts.rightVentricle.object = new THREE.Mesh(rightVentricleGeometry, rightVentricleMaterial);
heartParts.rightVentricle.object.position.set(0.7, 0, 0);
heartGroup.add(heartParts.rightVentricle.object);
updateLoadingProgress(25);
// Left Atrium
const leftAtriumGeometry = new THREE.SphereGeometry(0.7, 32, 32);
leftAtriumGeometry.scale(1, 0.8, 0.7);
const leftAtriumMaterial = new THREE.MeshPhongMaterial({
color: heartParts.leftAtrium.color,
shininess: 50,
transparent: true,
opacity: 0.9
});
heartParts.leftAtrium.object = new THREE.Mesh(leftAtriumGeometry, leftAtriumMaterial);
heartParts.leftAtrium.object.position.set(-0.8, 1.2, 0);
heartGroup.add(heartParts.leftAtrium.object);
updateLoadingProgress(37.5);
// Right Atrium
const rightAtriumGeometry = new THREE.SphereGeometry(0.7, 32, 32);
rightAtriumGeometry.scale(1, 0.8, 0.7);
const rightAtriumMaterial = new THREE.MeshPhongMaterial({
color: heartParts.rightAtrium.color,
shininess: 50,
transparent: true,
opacity: 0.9
});
heartParts.rightAtrium.object = new THREE.Mesh(rightAtriumGeometry, rightAtriumMaterial);
heartParts.rightAtrium.object.position.set(0.8, 1.2, 0);
heartGroup.add(heartParts.rightAtrium.object);
updateLoadingProgress(50);
// Aorta (a tube-like structure)
const aortaGeometry = new THREE.CylinderGeometry(0.4, 0.35, 1.5, 32);
const aortaMaterial = new THREE.MeshPhongMaterial({
color: heartParts.aorta.color,
shininess: 50,
transparent: true,
opacity: 0.9
});
heartParts.aorta.object = new THREE.Mesh(aortaGeometry, aortaMaterial);
heartParts.aorta.object.position.set(-0.6, 0.8, 0);
heartParts.aorta.object.rotation.x = -Math.PI / 5;
heartGroup.add(heartParts.aorta.object);
updateLoadingProgress(62.5);
// Pulmonary Arteries (two tubes coming from the right ventricle)
const pulmonaryArteriesGeometry = new THREE.CylinderGeometry(0.3, 0.25, 1.2, 32);
pulmonaryArteriesGeometry.translate(0, 0.6, 0);
const pulmonaryArteriesMaterial = new THREE.MeshPhongMaterial({
color: heartParts.pulmonaryArteries.color,
shininess: 50,
transparent: true,
opacity: 0.9
});
heartParts.pulmonaryArteries.object = new THREE.Group();
const artery1 = new THREE.Mesh(pulmonaryArteriesGeometry, pulmonaryArteriesMaterial);
artery1.position.set(0.4, 0.5, 0);
artery1.rotation.x = -Math.PI / 4;
artery1.rotation.z = Math.PI / 6;
const artery2 = new THREE.Mesh(pulmonaryArteriesGeometry, pulmonaryArteriesMaterial);
artery2.position.set(0.7, 0.3, 0.2);
artery2.rotation.x = -Math.PI / 4;
artery2.rotation.z = -Math.PI / 6;
heartParts.pulmonaryArteries.object.add(artery1);
heartParts.pulmonaryArteries.object.add(artery2);
heartGroup.add(heartParts.pulmonaryArteries.object);
updateLoadingProgress(75);
// Pulmonary Veins (four tubes entering the left atrium)
const pulmonaryVeinsGeometry = new THREE.CylinderGeometry(0.2, 0.2, 0.8, 16);
const pulmonaryVeinsMaterial = new THREE.MeshPhongMaterial({
color: heartParts.pulmonaryVeins.color,
shininess: 50,
transparent: true,
opacity: 0.9
});
heartParts.pulmonaryVeins.object = new THREE.Group();
for (let i = 0; i < 4; i++) {
const vein = new THREE.Mesh(pulmonaryVeinsGeometry, pulmonaryVeinsMaterial);
vein.position.set(-0.8 - i * 0.15, 1.6 - i * 0.1, 0.15 - i * 0.1);
vein.rotation.x = -Math.PI / 4;
heartParts.pulmonaryVeins.object.add(vein);
}
heartGroup.add(heartParts.pulmonaryVeins.object);
updateLoadingProgress(87.5);
// Cardiac Fat (adipose tissue around the heart)
const fatGeometry = new THREE.SphereGeometry(1.5, 32, 32);
fatGeometry.scale(1, 0.8, 0.9);
const fatMaterial = new THREE.MeshPhongMaterial({
color: heartParts.fat.color,
shininess: 30,
transparent: true,
opacity: 0.7,
wireframe: false
});
heartParts.fat.object = new THREE.Mesh(fatGeometry, fatMaterial);
heartParts.fat.object.position.set(0, 0, 0);
heartGroup.add(heartParts.fat.object);
updateLoadingProgress(100);
}
// Set up UI event listeners
function setupEventListeners() {
// Toggle controls for each heart part
document.getElementById('leftVentricleToggle').addEventListener('change', function(e) {
heartParts.leftVentricle.visible = e.target.checked;
heartParts.leftVentricle.object.visible = e.target.checked;
});
document.getElementById('rightVentricleToggle').addEventListener('change', function(e) {
heartParts.rightVentricle.visible = e.target.checked;
heartParts.rightVentricle.object.visible = e.target.checked;
});
document.getElementById('leftAtriumToggle').addEventListener('change', function(e) {
heartParts.leftAtrium.visible = e.target.checked;
heartParts.leftAtrium.object.visible = e.target.checked;
});
document.getElementById('rightAtriumToggle').addEventListener('change', function(e) {
heartParts.rightAtrium.visible = e.target.checked;
heartParts.rightAtrium.object.visible = e.target.checked;
});
document.getElementById('aortaToggle').addEventListener('change', function(e) {
heartParts.aorta.visible = e.target.checked;
heartParts.aorta.object.visible = e.target.checked;
});
document.getElementById('pulmonaryArteriesToggle').addEventListener('change', function(e) {
heartParts.pulmonaryArteries.visible = e.target.checked;
heartParts.pulmonaryArteries.object.visible = e.target.checked;
});
document.getElementById('pulmonaryVeinsToggle').addEventListener('change', function(e) {
heartParts.pulmonaryVeins.visible = e.target.checked;
heartParts.pulmonaryVeins.object.visible = e.target.checked;
});
document.getElementById('fatToggle').addEventListener('change', function(e) {
heartParts.fat.visible = e.target.checked;
heartParts.fat.object.visible = e.target.checked;
});
// Reset view button
document.getElementById('resetView').addEventListener('click', function() {
controls.reset();
camera.position.z = 5;
});
}
// Loading progress simulation
function updateLoadingProgress(percent) {
document.getElementById('progress').style.width = percent + '%';
if (percent === 100) {
setTimeout(() => {
document.getElementById('loading').style.display = 'none';
}, 500);
}
}
// Handle window resize
function onWindowResize() {
const width = window.innerWidth - 300;
const height = window.innerHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
window.addEventListener('resize', onWindowResize);
// Animation loop
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
// Initialize everything
function init() {
createHeartParts();
setupEventListeners();
animate();
}
init();
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body>
</html>