随着时间的推移在圆圈内的反射

Reflection Inside Circle Over Time

这是我正在考虑的一款游戏。

我有一个点,它被标记为从任意点、任意方向和特定时间在 (2D) 圆内移动。当点相交时,该点将从圆的内壁反弹。

对于此示例,假设圆的直径为 100 公里,圆心位于 (0,0),10 小时前该点位于位置 (20,30),航向为 40 度,速度为50 公里/小时。

确定该点当前位置及其行进方向的最佳方法是什么?

我将在 PHP 中实现这一点,并将点和圆数据存储在 MySQL 中。由于它是一个网页,因此不会经常 运行 主机进程来保持最新状态,并且需要在页面加载时刷新数据。

我当然不是在寻找任何人为我编写代码,但我希望有人可以帮助我以一种更有效的方式来解决这个问题。

您的点对象将沿着几何学中所谓的 chords 移动。

当物体撞击圆边界时,它将从该点的圆切线反射,并沿着具有相同长度的下一个弦移动。下一次命中将与上一次命中的角度相同(切线位于该命中点),因此它将继续。在恒定速度下,命中之间的时间将是恒定时间。

给定开始时间和当前时间,可以计算出已经完成的和弦数,以及当前和弦完成了多少。当您知道上一个和下一个命中位置时,从中计算位置很容易。由于这些命中位置沿圆边界的距离相等,这就是将极坐标(角度和1弧度的距离)转换为笛卡尔坐标的问题。

我将使用 JavaScript 代码对此进行演示。将其包装在 PHP:

中并不费力

var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var radius = canvas.height / 2 - 5;
var pixel = 1/radius;
// transform the canvas so that 0,0 is in the center of the canvas,
// and a unit circle would cover most of the height of the canvas: 
context.setTransform(radius, 0, 0, radius, radius+2, radius+2);

// draw unit circle
context.beginPath();
context.arc(0, 0, 1, 0, 2 * Math.PI, false);
context.lineWidth = pixel;
context.strokeStyle = 'black';
context.stroke();

function drawPoint(point) {
    // use a different color every 30 seconds:
    context.fillStyle = Date.now() % 60000 > 30000 ? 'red' : 'blue';
    context.fillRect(point.x-2*pixel, point.y-2*pixel, 4*pixel, 4*pixel);
}
function polarToCartesian(rad, dist) {
    return {
        x: Math.cos(rad) * dist,
        y: Math.sin(rad) * dist
    }
}
function pointBetween(a, b, fractionTravelled) {
    return {
        x: a.x + (b.x-a.x)*fractionTravelled,
        y: a.y + (b.y-a.y)*fractionTravelled
    }
}

// 4 parameters are needed:
var startRadians = 0;            // distance along circle boundary from (0,1)
var hitAngle = Math.PI/2.931;    // PI/2 would be head-on impact along diagonal
var speed = 0.4;                 // radians per second
var startTime = Date.now()/1000; // seconds
//

// Calculate some derived values which remain constant:
// - theta as used on https://en.wikipedia.org/wiki/Chord_%28geometry%29
// - chordSize formula comes from that wiki article. 
var theta = 2 * hitAngle;
var chordSize = 2 * Math.sin(theta/2); // in radians

function drawCurrentPosition() {
    // Note that this calculation does not look at the previous result,
    // but uses the original parameters and time passed to calculate
    // the objects current position.
    var elapsedTime = Date.now()/1000 - startTime;   // in secs
    var distanceTravelled = speed * elapsedTime;     // in radians
    var chordsTravelled = distanceTravelled / chordSize; // in number of chords
    var chordsTravelledComplete = Math.floor(chordsTravelled);
    var fractionOnChord = chordsTravelled - chordsTravelledComplete; // 0<=f<1
    var lastHitRadians = startRadians + chordsTravelledComplete * theta; // rad
    var nextHitRadians = lastHitRadians + theta;
    var lastHitPos = polarToCartesian(lastHitRadians, 1); // (x,y)
    var nextHitPos = polarToCartesian(nextHitRadians, 1);
    var currentPos = pointBetween(lastHitPos, nextHitPos, fractionOnChord);
    drawPoint(currentPos);
}

// Demo: keep drawing the object's position every 0.1 second:
setInterval(drawCurrentPosition, 100);
<canvas id="myCanvas" width="200" height="200"></canvas>

附录:PHP代码

下面是一些可用于 PHP 的代码。它使用与上述 JavaScript 代码相同的计算,但不保留 运行。相反,它首先检查会话范围内是否有已启动的游戏,如果没有,它会启动 "clock"。在每次请求(重新加载页面)时,都会计算新位置并将其作为 X,Y 对打印在页面上。

坐标是基于单位圆(半径 1)归一化的。游戏参数是硬编码的,但您可以轻松地通过 POST/GET 参数传递它们:

session_start(); // needed to persist game data for this user session

function getNewGame($startRadians, $hitAngle, $speed) {
    $game = array();
    $game["startTime"] = microtime(true);
    $game["startRadians"] = $startRadians;
    $game["theta"] = 2 * $hitAngle;
    $game["chordSize"] = 2 * sin($hitAngle);
    $game["speed"] = $speed;
    return (object) $game;
}

function polarToCartesian($rad, $dist) {
    return (object) array(
        "x" => cos($rad) * $dist,
        "y" => sin($rad) * $dist
    );
}

function pointBetween($a, $b, $fractionTravelled) {
    return (object) array(
        "x" => $a->x + ($b->x-$a->x)*$fractionTravelled,
        "y" => $a->y + ($b->y-$a->y)*$fractionTravelled
    );
}

function getCurrentPosition($game) {
    // Note that this calculation does not look at the previous result,
    // but uses the original parameters and time passed to calculate
    // the objects current position.
    $elapsedTime = microtime(true) - $game->startTime;   // in secs
    $distanceTravelled = $game->speed * $elapsedTime;     // in radians
    $chordsTravelled = $distanceTravelled / $game->chordSize; //number of chords
    $chordsTravelledComplete = floor($chordsTravelled);
    $fractionOnChord = $chordsTravelled - $chordsTravelledComplete; // 0<=f<1
    $lastHitRadians = $game->startRadians
          + $chordsTravelledComplete * $game->theta; // in radians on circle
    $nextHitRadians = $lastHitRadians + $game->theta;
    $lastHitPos = polarToCartesian($lastHitRadians, 1); // (x,y)
    $nextHitPos = polarToCartesian($nextHitRadians, 1);
    $currentPos = pointBetween($lastHitPos, $nextHitPos, $fractionOnChord);
    return $currentPos;
}

// check if this is the first time the user loads this page:
if (!isset($_SESSION["game"])) {
    // start game with some game parameters:
    $_SESSION["game"] = getNewGame(0, pi()/2.931, 0.4);
}

// calculate the position based on game info and current time:
$pos = getCurrentPosition($_SESSION["game"]);
// print the result:
echo "Current position: {$pos->x}, {$pos->y}<br>";