Tingchenliang's picture
Upload folder using huggingface_hub
dd4c5da verified
class ASCII3DViewer {
constructor() {
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
this.renderer = new THREE.WebGLRenderer();
this.asciiCanvas = document.getElementById('ascii-canvas');
this.context = this.asciiCanvas.getContext('2d');
this.currentModel = null;
this.autoRotate = true;
this.resolution = 50;
this.charSet = '@%#*+=-:. ';
this.colorMode = 'mono';
this.init();
this.setupEventListeners();
this.loadModel('cube');
this.animate();
}
init() {
this.camera.position.z = 5;
this.scene.background = new THREE.Color(0x000000);
// Add some ambient light
const ambientLight = new THREE.AmbientLight(0x404040);
this.scene.add(ambientLight);
// Add directional light
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.position.set(1, 1, 1);
this.scene.add(directionalLight);
this.resize();
window.addEventListener('resize', () => this.resize());
}
resize() {
const container = this.asciiCanvas.parentElement;
const width = container.clientWidth;
const height = container.clientHeight;
this.asciiCanvas.width = width;
this.asciiCanvas.height = height;
this.camera.aspect = width / height;
this.camera.updateProjectionMatrix();
}
loadModel(modelType) {
// Remove current model if exists
if (this.currentModel) {
this.scene.remove(this.currentModel);
}
let geometry;
switch(modelType) {
case 'sphere':
geometry = new THREE.SphereGeometry(1, 32, 32);
break;
case 'torus':
geometry = new THREE.TorusGeometry(1, 0.4, 16, 100);
break;
case 'teapot':
// Simple teapot approximation
geometry = new THREE.CylinderGeometry(0.5, 0.8, 1, 8);
const lid = new THREE.CylinderGeometry(0.3, 0.5, 0.3, 8);
lid.translate(0, 0.8, 0);
geometry = THREE.BufferGeometryUtils.mergeBufferGeometries([geometry, lid]);
break;
case 'cube':
default:
geometry = new THREE.BoxGeometry(1, 1, 1);
break;
}
const material = new THREE.MeshPhongMaterial({
color: 0xffffff,
flatShading: true
});
this.currentModel = new THREE.Mesh(geometry, material);
this.scene.add(this.currentModel);
document.getElementById('loading').style.display = 'none';
}
renderToASCII() {
const width = this.asciiCanvas.width;
const height = this.asciiCanvas.height;
// Temporary render to get pixel data
this.renderer.setSize(width, height);
this.renderer.render(this.scene, this.camera);
// Create offscreen buffer
const buffer = document.createElement('canvas');
buffer.width = width;
buffer.height = height;
const bufferCtx = buffer.getContext('2d');
bufferCtx.drawImage(this.renderer.domElement, 0, 0, width, height);
const imageData = bufferCtx.getImageData(0, 0, width, height);
const pixels = imageData.data;
// Clear canvas
this.context.fillStyle = '#000';
this.context.fillRect(0, 0, width, height);
// Calculate step based on resolution
const step = Math.max(1, Math.floor(100 / this.resolution));
const chars = this.charSet;
const charCount = chars.length;
// Render ASCII art
this.context.font = `${step}px 'Courier New', monospace`;
this.context.textAlign = 'center';
for (let y = 0; y < height; y += step * 2) {
for (let x = 0; x < width; x += step) {
const index = (y * width + x) * 4;
const r = pixels[index];
const g = pixels[index + 1];
const b = pixels[index + 2];
if (r + g + b > 10) { // Skip very dark pixels
const brightness = (r + g + b) / 3 / 255;
const charIndex = Math.floor(brightness * (charCount - 1));
const char = chars[charIndex];
// Set color based on mode
if (this.colorMode === 'color') {
this.context.fillStyle = `rgb(${r},${g},${b})`;
} else if (this.colorMode === 'grayscale') {
const gray = (r + g + b) / 3;
this.context.fillStyle = `rgb(${gray},${gray},${gray})`;
} else {
this.context.fillStyle = '#fff';
}
this.context.fillText(char, x, y);
}
}
}
}
animate() {
requestAnimationFrame(() => this.animate());
if (this.autoRotate && this.currentModel) {
this.currentModel.rotation.x += 0.01;
this.currentModel.rotation.y += 0.01;
}
this.renderToASCII();
}
setupEventListeners() {
// Model selection
document.getElementById('model-select').addEventListener('change', (e) => {
document.getElementById('loading').style.display = 'block';
setTimeout(() => this.loadModel(e.target.value), 100);
});
// Resolution slider
const resolutionSlider = document.getElementById('resolution');
const resolutionValue = document.getElementById('resolution-value');
resolutionSlider.addEventListener('input', (e) => {
this.resolution = parseInt(e.target.value);
resolutionValue.textContent = this.resolution;
});
// Character set
document.getElementById('charset').addEventListener('change', (e) => {
const sets = {
'default': '@%#*+=-:. ',
'dense': '█▓▒░ ',
'simple': '#*+-. ',
'numbers': '9876543210'
};
this.charSet = sets[e.target.value];
});
// Color mode
document.getElementById('color-mode').addEventListener('change', (e) => {
this.colorMode = e.target.value;
});
// Auto rotation
document.getElementById('auto-rotate').addEventListener('change', (e) => {
this.autoRotate = e.target.checked;
});
// Reset camera
document.getElementById('reset-camera').addEventListener('click', () => {
if (this.currentModel) {
this.currentModel.rotation.set(0, 0, 0);
}
});
// Mouse controls for manual rotation
let isDragging = false;
let previousMousePosition = {x: 0, y: 0};
this.asciiCanvas.addEventListener('mousedown', (e) => {
isDragging = true;
});
this.asciiCanvas.addEventListener('mouseup', () => {
isDragging = false;
});
this.asciiCanvas.addEventListener('mousemove', (e) => {
if (!isDragging || !this.currentModel) return;
const deltaMove = {
x: e.offsetX - previousMousePosition.x,
y: e.offsetY - previousMousePosition.y
};
this.currentModel.rotation.y += deltaMove.x * 0.01;
this.currentModel.rotation.x += deltaMove.y * 0.01;
previousMousePosition = {
x: e.offsetX,
y: e.offsetY
};
});
this.asciiCanvas.addEventListener('mouseleave', () => {
isDragging = false;
});
}
}
// Initialize the viewer when the page loads
window.addEventListener('load', () => {
new ASCII3DViewer();
});
// Polyfill for BufferGeometryUtils if needed
if (!THREE.BufferGeometryUtils) {
console.warn('THREE.BufferGeometryUtils not available. Some models may not work correctly.');
THREE.BufferGeometryUtils = {
mergeBufferGeometries: (geometries) => geometries[0]
};
}