Mercurial
diff mrjunejune/src/public/dog.js @ 84:bcc76a156aea
Updated to be called src instead of pages.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Thu, 01 Jan 2026 13:01:10 -0800 |
| parents | mrjunejune/pages/public/dog.js@1ded13720541 |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/src/public/dog.js Thu Jan 01 13:01:10 2026 -0800 @@ -0,0 +1,242 @@ +// -- Dog -- // +const SCREEN_WIDTH = 800; +const SCREEN_HEIGHT = 600; +const PIXEL_SIZE = 3; +const FRAME = 60; + +const dog = document.getElementById("epi3D"); +dog.width = SCREEN_WIDTH; +dog.height = SCREEN_HEIGHT; +const ctx = dog.getContext("2d"); + +function drawBackground() { + ctx.fillStyle = "black"; + ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); +} + +function drawPixel({x, y}) { + ctx.fillStyle = "blue"; + ctx.fillRect(x * SCREEN_WIDTH, y * SCREEN_HEIGHT, PIXEL_SIZE, PIXEL_SIZE); +} + +function normalize({x, y}) { + return { + x: ((x + 1) / 2), + y: (1 - ((y + 1) / 2)), + } +} + +function threeDtotwoD({x, y, z}) { + return { + x: x/z, + y: y/z + } +} + +function drawLine(point1, point2) { + ctx.beginPath(); + ctx.moveTo(point1.x * SCREEN_WIDTH, point1.y * SCREEN_HEIGHT); + ctx.lineTo(point2.x * SCREEN_WIDTH, point2.y * SCREEN_HEIGHT); + ctx.lineWidth = 3; + ctx.strokeStyle = "red"; + ctx.stroke(); +} + +let points = []; +let vertices = []; + +async function loadXYZModel(filepath) { + try { + const response = await fetch(filepath); + const xyzContent = await response.text(); + // return parseXYZFile(xyzContent); + return parseSmartSTL(xyzContent); + } catch (error) { + console.error('Error loading XYZ file:', error); + return null; + } +} + +function downsamplePoints(points, gridSize = 0.2) { + const grid = new Map(); + + for (const point of points) { + // Create grid cell key + const cellX = Math.floor(point.x / gridSize); + const cellY = Math.floor(point.y / gridSize); + const cellZ = Math.floor(point.z / gridSize); + const key = `${cellX},${cellY},${cellZ}`; + + // Keep first point in each cell (or you could average them) + if (!grid.has(key)) { + grid.set(key, point); + } + } + + return Array.from(grid.values()); +} + +async function initModel() { + const model = await loadXYZModel('/public/dog.xyz'); + + if (model) { + const normalizedPoints = normalizePoints(model.points, 1.0); + points = downsamplePoints(normalizedPoints, 0.1); // grid method + + vertices = model.vertices; + for (let i = 0; i < points.length - 1; i++) { + vertices.push([i, i + 1]); + } + + // Start the animation after loading + drawAnimation(); + } else { + console.error('Failed to load model'); + } +} + +function parseSmartSTL(stlString) { + const lines = stlString.split('\n'); + const points = []; + const vortexs = []; + + let currentNormal = ""; + let tempVertices = []; + + lines.forEach((line) => { + const trimmed = line.trim(); + + // 1. Detect the "Angle" (Normal) + if (trimmed.startsWith('facet normal')) { + const normal = trimmed.replace('facet normal ', ''); + // Only care if the normal is different from the last one (it's a new angle) + // Or just keep them all for now and we'll filter by position + currentNormal = normal; + tempVertices = []; + } + + // 2. Grab the vertices + if (trimmed.startsWith('vertex')) { + const parts = trimmed.split(/\s+/); + tempVertices.push({ + x: parseFloat(parts[1]), + y: parseFloat(parts[2]), + z: parseFloat(parts[3]) + }); + } + + // 3. When the triangle ends, connect them + if (trimmed.startsWith('endloop')) { + const startIndex = points.length; + points.push(...tempVertices); + + // Create a triangle connection (vortex) + vortexs.push([startIndex, startIndex + 1, startIndex + 2]); + } + }); + + return { points, vortexs }; +} + +// Usage: +// const { points, vortexs } = parseSmartSTL(yourStlString); + +initModel(); + +function parseXYZFile(xyzFileContent) { + const lines = xyzFileContent.trim().split('\n').filter(line => line.trim()); + + const points = lines.map(line => { + const coords = line.trim().split(/\s+/).map(Number); + return { + x: coords[0], + y: coords[1], + z: coords[2] + }; + }); + + return { points }; +} + +function normalizePoints(points, scale = 1.0) { + const xs = points.map(p => p.x); + const ys = points.map(p => p.y); + const zs = points.map(p => p.z); + + const minX = Math.min(...xs), maxX = Math.max(...xs); + const minY = Math.min(...ys), maxY = Math.max(...ys); + const minZ = Math.min(...zs), maxZ = Math.max(...zs); + + const centerX = (minX + maxX) / 2; + const centerY = (minY + maxY) / 2; + const centerZ = (minZ + maxZ) / 2; + + const rangeX = maxX - minX; + const rangeY = maxY - minY; + const rangeZ = maxZ - minZ; + const maxRange = Math.max(rangeX, rangeY, rangeZ); + + return points.map(p => ({ + x: ((p.x - centerX) / maxRange) * scale, + y: ((p.y - centerY) / maxRange) * scale, + z: ((p.z - centerZ) / maxRange) * scale + })); +} + +function rotate_xz({x, y, z}, angle) { + return { + x: x * Math.cos(angle) - z * Math.sin(angle), + y: y, + z: x * Math.sin(angle) + z * Math.cos(angle), + } +} + +function move_point(point, { x, y, z}) { + return { + ...point, + x: point.x + x, + y: 1 - (point.y + y), + z: point.z + z + } +} + +function move_z(point, dz) { + return {...point, z: point.z + dz} +} + +let dz = 0; +let dt = 1/FRAME; +let angle = 0; + +function drawAnimation() { + drawBackground(); + // dz += 1 * dt; + angle += 1 * Math.PI * dt; + + for (const point of points) { + drawPixel( + threeDtotwoD( + move_point(rotate_xz(point, angle), { x: 0.7, y: 0.5, z: 1.5}) + ) + ); + } + + for (const vertex of vertices) { + for (let i = 0; i < vertex.length - 1; i++) { + const point1 = normalize( + threeDtotwoD( + move_point(rotate_xz(points[vertex[i]], angle), { x: 0.7, y: 0.5, z: 1.5}) + ) + ); + const point2 = normalize( + threeDtotwoD( + move_point(rotate_xz(points[vertex[i + 1]], angle), { x: 0.7, y: 0.5, z: 1.5}) + ) + ); + drawLine(point1, point2); + } + } + + setTimeout(() => drawAnimation(), 1000/60); +} +drawAnimation();