Newer
Older
2025-Tsuji / system / demo / start.html
<!DOCTYPE html>
<html>
<head>
  <title>メニュー画面</title>
  <style>
    body {
      margin: 0;
      overflow: hidden;
      background-color: #111;
      color: white;
      font-family: sans-serif;
    }
    #container {
      position: relative;
      width: 640px;
      height: 480px;
      margin: 20px auto;
      border: 4px solid #fff;
    }
    #video, #canvas {
      position: absolute;
      top: 0;
      left: 0;
    }
    #canvas {
      transform: scaleX(-1);
    }
    .zone {
      position: absolute;
      display: flex;
      align-items: center;
      justify-content: center;
      color: white;
      font-size: 16px;
      font-weight: bold;
      pointer-events: none;
    }
    #zoneRight {
      top: 180px;
      right: 0;
      width: 160px;
      height: 120px;
      background-color: rgba(255, 255, 0, 0.4);
      border-left: 2px dashed white;
    }
    #zoneLeft {
      top: 180px;
      left: 0;
      width: 160px;
      height: 120px;
      background-color: rgba(100, 255, 100, 0.4);
      border-right: 2px dashed white;
    }
    #zoneTop {
      top: 0;
      left: 240px;
      width: 160px;
      height: 80px;
      background-color: rgba(100, 100, 255, 0.4);
      border-bottom: 2px dashed white;
    }
    #status {
      position: absolute;
      bottom: 10px;
      left: 50%;
      transform: translateX(-50%);
      font-size: 20px;
      text-shadow: 2px 2px 4px black;
    }
  </style>
</head>
<body>
  <div id="container">
    <video id="video" width="640" height="480" autoplay playsinline></video>
    <canvas id="canvas" width="640" height="480"></canvas>
    <div id="zoneRight" class="zone">▶ 水やりスタート</div>
    <div id="zoneLeft" class="zone">◀ 左のページ</div>
    <div id="zoneTop" class="zone">▲ 上のページ</div>
    <div id="status">右手首をゾーンに入れてください</div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
  <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/posenet"></script>
  <script>
    (async function() {
      const video = document.getElementById('video');
      const canvas = document.getElementById('canvas');
      const ctx = canvas.getContext('2d');
      const status = document.getElementById('status');
      const zoneRight = document.getElementById('zoneRight');
      const zoneLeft = document.getElementById('zoneLeft');
      const zoneTop = document.getElementById('zoneTop');

      const urlRight = 'https://www.yatex.org/gitbucket/HiroseLabo./2025-Tsuji/pages/system/demo/demo.html';
      const urlLeft = 'left.html'; // 左方向のページURL
      const urlTop = 'top.html';   // 上方向のページURL

      let enterTimeRight = null;
      let enterTimeLeft = null;
      let enterTimeTop = null;
      const waitTime = 2000;

      const stream = await navigator.mediaDevices.getUserMedia({
        video: { width: 640, height: 480, facingMode: 'user' }
      });
      video.srcObject = stream;

      const net = await posenet.load();

      function isHandInZone(x, y, zone) {
        const flippedX = canvas.width - x;
        const rect = zone.getBoundingClientRect();
        const canvasRect = canvas.getBoundingClientRect();
        const zoneLeft = rect.left - canvasRect.left;
        const zoneRight = rect.right - canvasRect.left;
        const zoneTop = rect.top - canvasRect.top;
        const zoneBottom = rect.bottom - canvasRect.top;
        return flippedX >= zoneLeft && flippedX <= zoneRight &&
               y >= zoneTop && y <= zoneBottom;
      }

      function drawWrist(x, y) {
        ctx.beginPath();
        ctx.arc(x, y, 10, 0, Math.PI * 2);
        ctx.fillStyle = 'rgba(0,255,255,0.7)';
        ctx.fill();
      }

      function process() {
        net.estimateSinglePose(video, { flipHorizontal: false }).then(pose => {
          ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
          const wrist = pose.keypoints.find(k => k.part === 'rightWrist');

          if (wrist && wrist.score > 0.4) {
            drawWrist(wrist.position.x, wrist.position.y);

            const inRight = isHandInZone(wrist.position.x, wrist.position.y, zoneRight);
            const inLeft = isHandInZone(wrist.position.x, wrist.position.y, zoneLeft);
            const inTop = isHandInZone(wrist.position.x, wrist.position.y, zoneTop);

            // 右
            if (inRight) {
              if (!enterTimeRight) enterTimeRight = Date.now();
              if (Date.now() - enterTimeRight >= waitTime) {
                status.textContent = '水やりゲームへ移動します...';
                window.location.href = urlRight;
                return;
              } else {
                const elapsed = ((Date.now() - enterTimeRight) / 1000).toFixed(1);
                status.textContent = `右ゾーン ${elapsed}s`;
              }
            } else {
              enterTimeRight = null;
            }

            // 左
            if (inLeft) {
              if (!enterTimeLeft) enterTimeLeft = Date.now();
              if (Date.now() - enterTimeLeft >= waitTime) {
                status.textContent = '左のページへ移動します...';
                window.location.href = urlLeft;
                return;
              } else {
                const elapsed = ((Date.now() - enterTimeLeft) / 1000).toFixed(1);
                status.textContent = `左ゾーン ${elapsed}s`;
              }
            } else {
              enterTimeLeft = null;
            }

            // 上
            if (inTop) {
              if (!enterTimeTop) enterTimeTop = Date.now();
              if (Date.now() - enterTimeTop >= waitTime) {
                status.textContent = '上のページへ移動します...';
                window.location.href = urlTop;
                return;
              } else {
                const elapsed = ((Date.now() - enterTimeTop) / 1000).toFixed(1);
                status.textContent = `上ゾーン ${elapsed}s`;
              }
            } else {
              enterTimeTop = null;
            }

            if (!inRight && !inLeft && !inTop) {
              status.textContent = '右手首をゾーンに入れてください';
            }

          } else {
            status.textContent = '右手首を検出できません';
            enterTimeRight = enterTimeLeft = enterTimeTop = null;
          }

          requestAnimationFrame(process);
        });
      }

      video.onloadeddata = () => {
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        process();
      };
    })();
  </script>
</body>
</html>