test face

by mcoirad

HTML

<div id="controls"></div>
<div id="stage"></div>

CSS

body {
  font-family: system-ui, sans-serif;
  display: grid;
  grid-template-columns: 260px 1fr;
  gap: 20px;
  padding: 20px;
}

label {
  display: block;
  margin-bottom: 12px;
}

input {
  width: 100%;
}

svg {
  width: 500px;
  height: 500px;
  border: 1px solid #ccc;
  background: #fafafa;
}

JavaScript

const params = {
  yaw: 0,
  pitch: 0,
  faceWidth: 170,
  faceHeight: 230,
  eyeSpacing: 46,
  eyeSize: 18,
  eyeTilt: 0,
  noseLength: 48,
  mouthWidth: 70,
  smile: 0
};

const sliders = {
  yaw: [-0.8, 0.8, 0.01],
  pitch: [-0.5, 0.5, 0.01],
  faceWidth: [120, 220, 1],
  faceHeight: [170, 290, 1],
  eyeSpacing: [25, 70, 1],
  eyeSize: [8, 30, 1],
  eyeTilt: [-0.6, 0.6, 0.01],
  noseLength: [20, 80, 1],
  mouthWidth: [30, 110, 1],
  smile: [-35, 35, 1]
};

const controls = document.getElementById("controls");
const stage = document.getElementById("stage");

for (const key in sliders) {
  const [min, max, step] = sliders[key];

  const label = document.createElement("label");
  label.innerHTML = `
    ${key}: <span id="${key}-value">${params[key]}</span>
    <input type="range" min="${min}" max="${max}" step="${step}" value="${params[key]}" id="${key}">
  `;

  controls.appendChild(label);

  label.querySelector("input").addEventListener("input", e => {
    params[key] = Number(e.target.value);
    document.getElementById(`${key}-value`).textContent = params[key];
    render();
  });
}

function rotateAndProject(point) {
  let [x, y, z] = point;

  const cy = Math.cos(params.yaw);
  const sy = Math.sin(params.yaw);
  const cp = Math.cos(params.pitch);
  const sp = Math.sin(params.pitch);

  // yaw
  let x1 = x * cy - z * sy;
  let z1 = x * sy + z * cy;

  // pitch
  let y1 = y * cp - z1 * sp;
  let z2 = y * sp + z1 * cp;

  const perspective = 500 / (500 + z2);

  return {
    x: 250 + x1 * perspective,
    y: 250 + y1 * perspective,
    scale: perspective,
    depth: z2
  };
}

function P(x, y, z = 0) {
  return rotateAndProject([x, y, z]);
}

function ellipse(p, rx, ry, extra = "") {
  return `
    <ellipse
      cx="${p.x}"
      cy="${p.y}"
      rx="${rx * p.scale}"
      ry="${ry * p.scale}"
      ${extra}
    />
  `;
}

function path(points, extra = "") {
  const d...