Mercurial
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 83:49b611c808e7 | 84:bcc76a156aea |
|---|---|
| 1 // -- Dog -- // | |
| 2 const SCREEN_WIDTH = 800; | |
| 3 const SCREEN_HEIGHT = 600; | |
| 4 const PIXEL_SIZE = 3; | |
| 5 const FRAME = 60; | |
| 6 | |
| 7 const dog = document.getElementById("epi3D"); | |
| 8 dog.width = SCREEN_WIDTH; | |
| 9 dog.height = SCREEN_HEIGHT; | |
| 10 const ctx = dog.getContext("2d"); | |
| 11 | |
| 12 function drawBackground() { | |
| 13 ctx.fillStyle = "black"; | |
| 14 ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); | |
| 15 } | |
| 16 | |
| 17 function drawPixel({x, y}) { | |
| 18 ctx.fillStyle = "blue"; | |
| 19 ctx.fillRect(x * SCREEN_WIDTH, y * SCREEN_HEIGHT, PIXEL_SIZE, PIXEL_SIZE); | |
| 20 } | |
| 21 | |
| 22 function normalize({x, y}) { | |
| 23 return { | |
| 24 x: ((x + 1) / 2), | |
| 25 y: (1 - ((y + 1) / 2)), | |
| 26 } | |
| 27 } | |
| 28 | |
| 29 function threeDtotwoD({x, y, z}) { | |
| 30 return { | |
| 31 x: x/z, | |
| 32 y: y/z | |
| 33 } | |
| 34 } | |
| 35 | |
| 36 function drawLine(point1, point2) { | |
| 37 ctx.beginPath(); | |
| 38 ctx.moveTo(point1.x * SCREEN_WIDTH, point1.y * SCREEN_HEIGHT); | |
| 39 ctx.lineTo(point2.x * SCREEN_WIDTH, point2.y * SCREEN_HEIGHT); | |
| 40 ctx.lineWidth = 3; | |
| 41 ctx.strokeStyle = "red"; | |
| 42 ctx.stroke(); | |
| 43 } | |
| 44 | |
| 45 let points = []; | |
| 46 let vertices = []; | |
| 47 | |
| 48 async function loadXYZModel(filepath) { | |
| 49 try { | |
| 50 const response = await fetch(filepath); | |
| 51 const xyzContent = await response.text(); | |
| 52 // return parseXYZFile(xyzContent); | |
| 53 return parseSmartSTL(xyzContent); | |
| 54 } catch (error) { | |
| 55 console.error('Error loading XYZ file:', error); | |
| 56 return null; | |
| 57 } | |
| 58 } | |
| 59 | |
| 60 function downsamplePoints(points, gridSize = 0.2) { | |
| 61 const grid = new Map(); | |
| 62 | |
| 63 for (const point of points) { | |
| 64 // Create grid cell key | |
| 65 const cellX = Math.floor(point.x / gridSize); | |
| 66 const cellY = Math.floor(point.y / gridSize); | |
| 67 const cellZ = Math.floor(point.z / gridSize); | |
| 68 const key = `${cellX},${cellY},${cellZ}`; | |
| 69 | |
| 70 // Keep first point in each cell (or you could average them) | |
| 71 if (!grid.has(key)) { | |
| 72 grid.set(key, point); | |
| 73 } | |
| 74 } | |
| 75 | |
| 76 return Array.from(grid.values()); | |
| 77 } | |
| 78 | |
| 79 async function initModel() { | |
| 80 const model = await loadXYZModel('/public/dog.xyz'); | |
| 81 | |
| 82 if (model) { | |
| 83 const normalizedPoints = normalizePoints(model.points, 1.0); | |
| 84 points = downsamplePoints(normalizedPoints, 0.1); // grid method | |
| 85 | |
| 86 vertices = model.vertices; | |
| 87 for (let i = 0; i < points.length - 1; i++) { | |
| 88 vertices.push([i, i + 1]); | |
| 89 } | |
| 90 | |
| 91 // Start the animation after loading | |
| 92 drawAnimation(); | |
| 93 } else { | |
| 94 console.error('Failed to load model'); | |
| 95 } | |
| 96 } | |
| 97 | |
| 98 function parseSmartSTL(stlString) { | |
| 99 const lines = stlString.split('\n'); | |
| 100 const points = []; | |
| 101 const vortexs = []; | |
| 102 | |
| 103 let currentNormal = ""; | |
| 104 let tempVertices = []; | |
| 105 | |
| 106 lines.forEach((line) => { | |
| 107 const trimmed = line.trim(); | |
| 108 | |
| 109 // 1. Detect the "Angle" (Normal) | |
| 110 if (trimmed.startsWith('facet normal')) { | |
| 111 const normal = trimmed.replace('facet normal ', ''); | |
| 112 // Only care if the normal is different from the last one (it's a new angle) | |
| 113 // Or just keep them all for now and we'll filter by position | |
| 114 currentNormal = normal; | |
| 115 tempVertices = []; | |
| 116 } | |
| 117 | |
| 118 // 2. Grab the vertices | |
| 119 if (trimmed.startsWith('vertex')) { | |
| 120 const parts = trimmed.split(/\s+/); | |
| 121 tempVertices.push({ | |
| 122 x: parseFloat(parts[1]), | |
| 123 y: parseFloat(parts[2]), | |
| 124 z: parseFloat(parts[3]) | |
| 125 }); | |
| 126 } | |
| 127 | |
| 128 // 3. When the triangle ends, connect them | |
| 129 if (trimmed.startsWith('endloop')) { | |
| 130 const startIndex = points.length; | |
| 131 points.push(...tempVertices); | |
| 132 | |
| 133 // Create a triangle connection (vortex) | |
| 134 vortexs.push([startIndex, startIndex + 1, startIndex + 2]); | |
| 135 } | |
| 136 }); | |
| 137 | |
| 138 return { points, vortexs }; | |
| 139 } | |
| 140 | |
| 141 // Usage: | |
| 142 // const { points, vortexs } = parseSmartSTL(yourStlString); | |
| 143 | |
| 144 initModel(); | |
| 145 | |
| 146 function parseXYZFile(xyzFileContent) { | |
| 147 const lines = xyzFileContent.trim().split('\n').filter(line => line.trim()); | |
| 148 | |
| 149 const points = lines.map(line => { | |
| 150 const coords = line.trim().split(/\s+/).map(Number); | |
| 151 return { | |
| 152 x: coords[0], | |
| 153 y: coords[1], | |
| 154 z: coords[2] | |
| 155 }; | |
| 156 }); | |
| 157 | |
| 158 return { points }; | |
| 159 } | |
| 160 | |
| 161 function normalizePoints(points, scale = 1.0) { | |
| 162 const xs = points.map(p => p.x); | |
| 163 const ys = points.map(p => p.y); | |
| 164 const zs = points.map(p => p.z); | |
| 165 | |
| 166 const minX = Math.min(...xs), maxX = Math.max(...xs); | |
| 167 const minY = Math.min(...ys), maxY = Math.max(...ys); | |
| 168 const minZ = Math.min(...zs), maxZ = Math.max(...zs); | |
| 169 | |
| 170 const centerX = (minX + maxX) / 2; | |
| 171 const centerY = (minY + maxY) / 2; | |
| 172 const centerZ = (minZ + maxZ) / 2; | |
| 173 | |
| 174 const rangeX = maxX - minX; | |
| 175 const rangeY = maxY - minY; | |
| 176 const rangeZ = maxZ - minZ; | |
| 177 const maxRange = Math.max(rangeX, rangeY, rangeZ); | |
| 178 | |
| 179 return points.map(p => ({ | |
| 180 x: ((p.x - centerX) / maxRange) * scale, | |
| 181 y: ((p.y - centerY) / maxRange) * scale, | |
| 182 z: ((p.z - centerZ) / maxRange) * scale | |
| 183 })); | |
| 184 } | |
| 185 | |
| 186 function rotate_xz({x, y, z}, angle) { | |
| 187 return { | |
| 188 x: x * Math.cos(angle) - z * Math.sin(angle), | |
| 189 y: y, | |
| 190 z: x * Math.sin(angle) + z * Math.cos(angle), | |
| 191 } | |
| 192 } | |
| 193 | |
| 194 function move_point(point, { x, y, z}) { | |
| 195 return { | |
| 196 ...point, | |
| 197 x: point.x + x, | |
| 198 y: 1 - (point.y + y), | |
| 199 z: point.z + z | |
| 200 } | |
| 201 } | |
| 202 | |
| 203 function move_z(point, dz) { | |
| 204 return {...point, z: point.z + dz} | |
| 205 } | |
| 206 | |
| 207 let dz = 0; | |
| 208 let dt = 1/FRAME; | |
| 209 let angle = 0; | |
| 210 | |
| 211 function drawAnimation() { | |
| 212 drawBackground(); | |
| 213 // dz += 1 * dt; | |
| 214 angle += 1 * Math.PI * dt; | |
| 215 | |
| 216 for (const point of points) { | |
| 217 drawPixel( | |
| 218 threeDtotwoD( | |
| 219 move_point(rotate_xz(point, angle), { x: 0.7, y: 0.5, z: 1.5}) | |
| 220 ) | |
| 221 ); | |
| 222 } | |
| 223 | |
| 224 for (const vertex of vertices) { | |
| 225 for (let i = 0; i < vertex.length - 1; i++) { | |
| 226 const point1 = normalize( | |
| 227 threeDtotwoD( | |
| 228 move_point(rotate_xz(points[vertex[i]], angle), { x: 0.7, y: 0.5, z: 1.5}) | |
| 229 ) | |
| 230 ); | |
| 231 const point2 = normalize( | |
| 232 threeDtotwoD( | |
| 233 move_point(rotate_xz(points[vertex[i + 1]], angle), { x: 0.7, y: 0.5, z: 1.5}) | |
| 234 ) | |
| 235 ); | |
| 236 drawLine(point1, point2); | |
| 237 } | |
| 238 } | |
| 239 | |
| 240 setTimeout(() => drawAnimation(), 1000/60); | |
| 241 } | |
| 242 drawAnimation(); |