view mrjunejune/src/public/dog.js @ 126:e7899c93da77

Remove playground.
author June Park <parkjune1995@gmail.com>
date Thu, 08 Jan 2026 18:03:34 -0800
parents bcc76a156aea
children
line wrap: on
line source

// -- 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();