MySafeCode's picture
Upload folder using huggingface_hub
cc627f9 verified
// Global variables
let scene, camera, renderer, controls;
let model, cuttingPlaneMesh;
let rotationSlider, autoRotateCheckbox, cuttingPlaneSlider;
let sliceCanvas, sliceCtx;
let isRecording = false;
let mediaRecorder;
let recordedChunks = [];
let animationId;
let sliceData = [];
// Initialize Three.js scene
function init3DScene() {
const container = document.getElementById('canvas3d');
// Scene setup
scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
// Camera setup
camera = new THREE.PerspectiveCamera(
75,
container.clientWidth / container.clientHeight,
0.1,
1000
);
camera.position.set(0, 0, 100);
// Renderer setup
renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.localClippingEnabled = true;
container.appendChild(renderer.domElement);
// Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// Lighting
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 10, 10);
scene.add(directionalLight);
// Create 3D model (composite sphere with inner structures)
createCompositeModel();
// Create cutting plane visualization
createCuttingPlane();
// Event listeners
setupEventListeners();
// Initialize slice canvas
initSliceCanvas();
// Start render loop
animate();
}
// Create a composite 3D model
function createCompositeModel() {
// Main outer shell
const outerGeometry = new THREE.SphereGeometry(30, 32, 32);
const outerMaterial = new THREE.MeshPhongMaterial({
color: 0x4a90e2,
transparent: true,
opacity: 0.7,
side: THREE.DoubleSide
});
model = new THREE.Mesh(outerGeometry, outerMaterial);
model.name = 'compositeModel';
scene.add(model);
// Inner structures
const innerGeometry1 = new THREE.SphereGeometry(15, 16, 16);
const innerMaterial1 = new THREE.MeshPhongMaterial({
color: 0x7ed321,
transparent: true,
opacity: 0.8
});
const innerSphere1 = new THREE.Mesh(innerGeometry1, innerMaterial1);
innerSphere1.position.set(5, 5, -5);
model.add(innerSphere1);
const innerGeometry2 = new THREE.BoxGeometry(15, 15, 15);
const innerMaterial2 = new THREE.MeshPhongMaterial({
color: 0xf5a623,
transparent: true,
opacity: 0.8
});
const innerBox = new THREE.Mesh(innerGeometry2, innerMaterial2);
innerBox.position.set(-8, -3, 8);
innerBox.rotation.set(0.5, 0.3, 0.7);
model.add(innerBox);
// Additional small structures for complexity
for (let i = 0; i < 5; i++) {
const smallGeometry = new THREE.SphereGeometry(3 + Math.random() * 5, 8, 8);
const smallMaterial = new THREE.MeshPhongMaterial({
color: new THREE.Color().setHSL(0.5 + Math.random() * 0.3, 0.8, 0.6),
transparent: true,
opacity: 0.9
});
const smallSphere = new THREE.Mesh(smallGeometry, smallMaterial);
smallSphere.position.set(
(Math.random() - 0.5) * 40,
(Math.random() - 0.5) * 40,
(Math.random() - 0.5) * 40
);
model.add(smallSphere);
}
// Update slice data for all z positions
updateSliceData();
}
// Generate slice data for 2D view
function updateSliceData() {
sliceData = [];
const resolution = parseInt(document.getElementById('sliceResolution').value);
const totalSlices = 100;
for (let z = 0; z < totalSlices; z++) {
const zPos = (z - totalSlices / 2) / (totalSlices / 100);
const sliceArray = [];
for (let y = 0; y < resolution; y++) {
const row = [];
for (let x = 0; x < resolution; x++) {
// Convert canvas coordinates to 3D space
const xPos = (x - resolution / 2) / (resolution / 100);
const yPos = (y - resolution / 2) / (resolution / 100);
// Calculate density based on distance from center for outer sphere
const distanceFromCenter = Math.sqrt(xPos * xPos + yPos * yPos + zPos * zPos);
let density = 0;
// Outer sphere (main shell)
if (distanceFromCenter <= 30) {
density = 0.5 + (1 - distanceFromCenter / 30) * 0.5;
// Inner sphere 1
const dist1 = Math.sqrt(
(xPos - 5) * (xPos - 5) +
(yPos - 5) * (yPos - 5) +
(zPos + 5) * (zPos + 5)
);
if (dist1 <= 15) {
density = Math.max(density, 0.8 + (1 - dist1 / 15) * 0.2);
}
// Inner box (approximated)
const boxDistX = Math.abs(xPos + 8);
const boxDistY = Math.abs(yPos + 3);
const boxDistZ = Math.abs(zPos - 8);
if (boxDistX <= 12 && boxDistY <= 12 && boxDistZ <= 12) {
density = Math.max(density, 0.7 + (1 - boxDistX / 12) * 0.3 * 0.3);
density = Math.max(density, 0.7 + (1 - boxDistY / 12) * 0.3 * 0.3);
density = Math.max(density, 0.7 + (1 - boxDistZ / 12) * 0.3 * 0.3);
}
// Small random structures
for (let i = 0; i < 5; i++) {
const posX = (Math.sin(i * 1.5) - 0.5) * 40;
const posY = (Math.cos(i * 1.3) - 0.5) * 40;
const posZ = (Math.sin(i) - 0.5) * 40;
const smallDist = Math.sqrt(
(xPos - posX) * (xPos - posX) +
(yPos - posY) * (yPos - posY) +
(zPos - posZ) * (zPos - posZ)
);
const radius = 8 + i * 2;
if (smallDist <= radius) {
density = Math.max(density, 0.6 + (1 - smallDist / radius) * 0.4);
}
}
}
row.push(density);
}
sliceArray.push(row);
}
sliceData.push(sliceArray);
}
}
// Initialize slice canvas
function initSliceCanvas() {
sliceCanvas = document.getElementById('sliceCanvas');
sliceCtx = sliceCanvas.getContext('2d');
const resolution = parseInt(document.getElementById('sliceResolution').value);
sliceCanvas.width = resolution;
sliceCanvas.height = resolution;
}
// Create cutting plane visualization
function createCuttingPlane() {
const planeGeometry = new THREE.PlaneGeometry(100, 100);
const planeMaterial = new THREE.MeshBasicMaterial({
color: 0xff0000,
transparent: true,
opacity: 0.3,
side: THREE.DoubleSide
});
cuttingPlaneMesh = new THREE.Mesh(planeGeometry, planeMaterial);
cuttingPlaneMesh.rotation.x = -Math.PI / 2;
cuttingPlaneMesh.visible = true;
scene.add(cuttingPlaneMesh);
}
// Update 2D slice view
function updateSliceView(zPosition) {
if (!sliceCtx || sliceData.length === 0) return;
const resolution = parseInt(document.getElementById('sliceResolution').value);
const sliceIndex = Math.floor((zPosition + 50) / 100 * (sliceData.length - 1));
if (sliceIndex < 0 || sliceIndex >= sliceData.length) return;
const slice = sliceData[sliceIndex];
const imageData = sliceCtx.createImageData(resolution, resolution);
const data = imageData.data;
for (let y = 0; y < resolution; y++) {
for (let x = 0; x < resolution; x++) {
const density = slice[y][x];
const index = (y * resolution + x) * 4;
if (density > 0) {
// Color based on density
const hue = 0.6 - density * 0.3; // Blue to green to yellow
const rgb = hslToRgb(hue, 0.8, 0.5 + density * 0.3);
data[index] = rgb[0]; // R
data[index + 1] = rgb[1]; // G
data[index + 2] = rgb[2]; // B
data[index + 3] = Math.floor(density * 255); // A
} else {
data[index] = 0;
data[index + 1] = 0;
data[index + 2] = 0;
data[index + 3] = 0;
}
}
}
sliceCtx.putImageData(imageData, 0, 0);
// Update slice info
document.getElementById('sliceInfo').textContent = `Z = ${zPosition.toFixed(1)}`;
}
// Convert HSL to RGB
function hslToRgb(h, s, l) {
const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs((h * 6) % 2 - 1));
const m = l - c / 2;
let r, g, b;
if (h < 1/6) {
r = c; g = x; b = 0;
} else if (h < 2/6) {
r = x; g = c; b = 0;
} else if (h < 3/6) {
r = 0; g = c; b = x;
} else if (h < 4/6) {
r = 0; g = x; b = c;
} else if (h < 5/6) {
r = x; g = 0; b = c;
} else {
r = c; g = 0; b = x;
}
return [
Math.floor((r + m) * 255),
Math.floor((g + m) * 255),
Math.floor((b + m) * 255)
];
}
// Setup event listeners
function setupEventListeners() {
rotationSlider = document.getElementById('rotationSlider');
autoRotateCheckbox = document.getElementById('autoRotate');
cuttingPlaneSlider = document.getElementById('cuttingPlane');
// Rotation slider
rotationSlider.addEventListener('input', (e) => {
const value = e.target.value;
document.getElementById('rotationValue').textContent = value + '°';
if (model) {
model.rotation.y = (value * Math.PI) / 180;
}
});
// Auto rotate checkbox
autoRotateCheckbox.addEventListener('change', (e) => {
controls.autoRotate = e.target.checked;
});
// Cutting plane slider
cuttingPlaneSlider.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
document.getElementById('planeValue').textContent = value.toFixed(1);
updateCuttingPlane(value);
updateSliceView(value);
});
// Speed slider
const speedSlider = document.getElementById('scanSpeed');
speedSlider.addEventListener('input', (e) => {
document.getElementById('speedValue').textContent = e.target.value + 'x';
});
// Resolution selector
document.getElementById('sliceResolution').addEventListener('change', () => {
updateSliceData();
initSliceCanvas();
updateSliceView(parseFloat(cuttingPlaneSlider.value));
});
// Export button
document.getElementById('exportBtn').addEventListener('click', exportScan);
}
// Update cutting plane position
function updateCuttingPlane(zPosition) {
if (cuttingPlaneMesh) {
cuttingPlaneMesh.position.z = zPosition;
}
}
// Animation loop
function animate() {
animationId = requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
// Export scan as video
async function exportScan() {
if (isRecording) return;
const exportBtn = document.getElementById('exportBtn');
const progressBar = document.getElementById('exportProgress');
const progressFill = progressBar.querySelector('.progress-fill');
const progressText = progressBar.querySelector('.progress-text');
exportBtn.disabled = true;
progressBar.classList.remove('hidden');
isRecording = true;
// Setup MediaRecorder
const stream = sliceCanvas.captureStream(30);
const options = {
mimeType: 'video/webm;codecs=vp8,opus',
videoBitsPerSecond: 2500000
};
try {
mediaRecorder = new MediaRecorder(stream, options);
recordedChunks = [];
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
recordedChunks.push(event.data);
}
};
mediaRecorder.onstop = () => {
const blob = new Blob(recordedChunks, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `3d-scan-${Date.now()}.webm`;
a.click();
URL.revokeObjectURL(url);
// Reset UI
exportBtn.disabled = false;
progressBar.classList.add('hidden');
isRecording = false;
// Reset cutting plane
cuttingPlaneSlider.value = 0;
updateCuttingPlane(0);
updateSliceView(0);
};
// Start recording
mediaRecorder.start();
// Animate scan from top to bottom
const scanSpeed = parseFloat(document.getElementById('scanSpeed').value);
const totalSteps = 100;
const stepDelay = 1000 / scanSpeed / totalSteps;
for (let i = 0; i <= totalSteps; i++) {
const zPosition = 50 - (i * 100 / totalSteps);
cuttingPlaneSlider.value = zPosition;
updateCuttingPlane(zPosition);
updateSliceView(zPosition);
// Update progress
const progress = (i / totalSteps) * 100;
progressFill.style.width = progress + '%';
progressText.textContent = Math.floor(progress) + '%';
await new Promise(resolve => setTimeout(resolve, stepDelay));
}
// Stop recording
mediaRecorder.stop();
} catch (err) {
console.error('Recording failed:', err);
alert('Video recording failed. Please check browser permissions.');
exportBtn.disabled = false;
progressBar.classList.add('hidden');
isRecording = false;
}
}
// Initialize on load
window.addEventListener('load', init3DScene);
// Handle window resize
window.addEventListener('resize', () => {
const container = document.getElementById('canvas3d');
if (camera && renderer) {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
}
});