matterjs实现沙漏动画效果代码

代码语言:html

所属分类:动画

代码描述:matterjs实现沙漏动画效果代码,显示沙漏通过的粮食量、所用时间和平均每秒通过量。

代码标签: matter js 沙漏 动画

下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开

<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
 
    <style>
        canvas {
            border: 1px solid black;
        }
    </style>

</head>

<body translate="no">
  <div style="display: flex; align-items: start; scale:0.6; margin-top:-20vh">
	<canvas id="hourglassCanvas" width="400" height="500"></canvas>
	<div id="dataDisplay" style="margin-left: 20px; font-family: Arial; font-size: 26px;">
		<p id="grainsPassed">Number of Grains Passed: 0</p>
		<p id="currentTime">Current Time: 00:00.000</p>
	</div>
</div>
<script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/matter.0.19.0.js"></script>
  
      <script >
let canvas, ctx;
let grains = [];
const numGrains = 2000;
let grainsPassedCenter = 0;
let timerStarted = false;
let timerEnded = false;
let startTime;
let endTime;
let lastGrainPassedTime = null;

const glassThickness = 6;

window.onload = function () {
  initializeCanvas();
  initializeGrains();
  startTimer();
  animate();
};

function initializeCanvas() {
  canvas = document.getElementById("hourglassCanvas");
  ctx = canvas.getContext("2d");
  canvas.width = 800;
  canvas.height = 1000;
}

function initializeGrains() {
  grains = [];
  grainsPassedCenter = 0;
  const startY = canvas.height / 2 - neckHeight / 2 - glassThickness; // Just above the center
  const endY = 120 + glassThickness;
  const spreadX = hourglassWidth - 2 * glassThickness;

  const cellSize = 4;
  const spatialHash = new Map();

  for (let i = 0; i < numGrains; i++) {
    let x, y;
    let placed = false;
    let attempts = 0;
    const maxAttempts = 1000;

    while (!placed && attempts < maxAttempts) {
      x = canvas.width / 2 + (Math.random() - 0.5) * spreadX;
      y = startY - Math.random() * (startY - endY); // Fill upwards
      attempts++;

      const cellX = Math.floor(x / cellSize);
      const cellY = Math.floor(y / cellSize);
      const cellKey = `${cellX},${cellY}`;

      const nearbyKeys = [
      cellKey,
      `${cellX - 1},${cellY}`,
      `${cellX + 1},${cellY}`,
      `${cellX},${cellY - 1}`,
      `${cellX},${cellY + 1}`,
      `${cellX - 1},${cellY - 1}`,
      `${cellX + 1},${cellY - 1}`,
      `${cellX - 1},${cellY + 1}`,
      `${cellX + 1},${cellY + 1}`];


      let overlaps = false;
      for (const key of nearbyKeys) {
        if (spatialHash.has(key)) {
          for (const otherGrain of spatialHash.get(key)) {
            const dx = x - otherGrain.x;
            const dy = y - otherGrain.y;
            if (dx * dx + dy * dy < 16) {
              // 4^2, square of 2 * collisionRadius
              overlaps = true;
              break;
            }
          }
          if (overlaps) break;
        }
      }

      if (!overlaps && isInsideHourglass(x, y, 2)) {
        // Check if inside hourglass
        placed = true;
        const grain = {
          x: x,
          y: y,
          radius: 3,
          collisionRadius: 3,
          velocity: { x: 0, y: 0 },
          acceleration: 0,
          passedCenter: false,
          isDone: false };

        grains.push(grain);

        if (!spatialHash.has(cellKey)) {
          spatialHash.set(cellKey, []);
        }
        spatialHash.get(cellKey).push(grain);
      }
    }

    // If we couldn't place the grain after max attempts, skip it
    if (!placed) {
      console.warn(`Couldn't place grain ${i}`);
    }
  }

  console.log(`Placed ${grains.length} out of ${numGrains} grains`);
}

function startTimer() {
  timerStarted = true;
  startTime = Date.now();
}

// Hourglass parameters
const hourglassWidth = 500;
const hourglassHeight = 800;
const neckWidth = 26;
const neckHeight = 40;
const curvature = 200;

function drawHourglass() {
  const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
  gradient.addColorStop(0, "rgba(0, 255, 255, 0.7)"); // Cyan
  gradient.addColorStop(0.3, "rgba(255, 255, 255, 0.9)"); // White
  gradient.addColorStop(0.5, "rgba(0, 255, 255, 0.7)"); // Cyan
  gradient.addColorStop(0.7, "rgba(255, 255, 255, 0.9)"); // White
  gradient.addColorStop(1, "rgba(0, 255, 255, 0.7)"); // Cyan

  // Draw the main outline
  ctx.beginPath();

  // Top half
  ctx.moveTo(canvas.width / 2 - hourglassWidth / 2, 100); // Doubled from 50
  ctx.lineTo(canvas.width / 2 + hourglassWidth / 2, 100); // Doubled from 50
  ctx.quadraticCurveTo(
  canvas.width / 2 + hourglassWidth / 2,
  canvas.height / 2 - curvature,
  canvas.width / 2 + neckWidth / 2,
  canvas.height / 2 - neckHeight / 2);


  // Straight center part
  ctx.lineTo(
  canvas.width / 2 + neckWidth / 2,
  canvas.height / 2 + neckHeight / 2);


  // Bottom half
  ctx.quadraticCurveTo(
  canvas.width / 2 + hourglassWidth / 2,
  canvas.height / 2 + curvature,
  canvas.width / 2 + hourglassWidth / 2,
  canvas.height - 100 // Doubled from 50
  );
  ctx.lineTo(canvas.width / 2 - hourglassWidth / 2, canvas.height - 100);
  ctx.quadraticCurveTo(
  canvas.width / 2 - hourglassWidth / 2,
  canvas.height / 2 + curvature,
  canvas.width / 2 - neckWidth / 2,
  canvas.height / 2 + neckHeight / 2);


  // Straight center part
  ctx.lineTo(
  canvas.width / 2 - neckWidth / 2,
  canvas.height / 2 - neckHeight / 2);


  ctx.quadraticCurveTo(
  canvas.width / 2 - hourglassWidth / 2,
  canvas.height / 2 - curvature,
  canvas.width / 2 - hourglassWidth / 2,
  100);


  ctx.closePath();

  ctx.strokeStyle = gradient;
  ctx.lineWidth = 6;
  ctx.stroke();

  //drawCollisionBoundary();
}

function drawCollisionBoundary() {
  ctx.beginPath();
  for (
  let y = 100 + glassThickness;
  y <= canvas.height - 100 - glassThickness;
  y++)
  {
    // Doubled from 50
    const width = getHourglassWidthAtY(y);
    const x1 = canvas.width / 2 - width / 2;
    const x2 = canvas.width / 2 + width / 2;
    ctx.moveTo(x1, y);
    ctx.lineTo(x2, y);
  }
  ctx.strokeStyle = "rgba(255, 0, 0, 0.3)";
  ctx.lineWidth = 1;
  ctx.stroke();
}

function drawSandGrains() {
  grains.forEach(grain => {
    if (!grain.isSettled) {
      ctx.fillStyle = "rgb(255, 0, 99)";
    } else {
      ctx.fillStyle = "rgb(255,255,255)";
    }
    ctx.fillRect(
    grain.x - grain.radius,
    grain.y - grain.radius,
    grain.radius * 2,
    grain.radius * 2);

  });
}

function getHourglassWidthAtY(y) {
  const halfHeight = (canvas.height - 200) / 2;
  const centerY = canvas.height / 2;

  if (y < centerY - neckHeight / 2) {
    // Top half
    const t = (y - 100) / (halfHeight - neckHeight / 2);
    return hourglassWidth * (1 - t * t) + neckWidth * t * t - 2 * glassThickness;
  } else if (y > centerY + neckHeight / 2) {
    // Bottom half
    const t = (canvas.height - 100 - y) / (halfHeight - neckHeight / 2);
    return hourglassWidth * (1 - t * t) + neckWidth * t * t - 2 * glassThickness;
  } else {
    // Neck
    return neckWidth - 2 * glassThickness;
  }
}

function isInsideHourglass(x, y, radius) {
  const hourglassCenter = canvas.width / 2;
  const hourglassWidthAtY = getHourglassWidthAtY(y);
  return (
    x >= hourglassCenter - hourglassWidthAtY / 2 + radius &&
    x <= hourglassCenter + hourglassWidthAtY / 2 - radius &&
    y >= 100 + glassThickness + radius &&
    y <= canvas.height - 100 - glassThickness - radius);

}

function getHourglassNormalAt(x, y) {
  const dy = 1;
  const widthAtY = getHourglassWidthAtY(y);
  const widthAtYPlusDy = getHourglassWidthAtY(y + dy);
  const dx = (widthAtYPlusDy - widthAtY) / 2;
  const length = Math.sqrt(dx * dx + dy * dy);
  return { x: dy / length, y: -dx / length };
}

let grainsReachedBottom = 0;

function updateSandGrains() {
  const spatialHash = new Map();
  const cellSize = 4;
  const settlingThreshold = 0.05; // Velocity threshold for considering a grain settled
  const settlingFriction = 0.95; // Higher friction for settled grains
  const settlingYThreshold = canvas.height * 0.8; // 80% down the hourglass

  grains.forEach(grain => {
    let canSettle = grain.y > settlingYThreshold;

    // Apply small random forces in the neck area
    if (
    grain.y > canvas.height / 2 - neckHeight / 2 &&
    grain.y < canvas.height / 2 + neckHeight / 2)
    {
      grain.velocity.x += (Math.random() - 0.5) * 0.01;
      grain.velocity.y += Math.random() * 0.01;
      grain.isSettled = false; // Grains in the neck are ne.........完整代码请登录后点击上方下载按钮下载查看

网友评论0