首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >根据前一点的直线距离确定Bézier曲线上的下一个点。

根据前一点的直线距离确定Bézier曲线上的下一个点。
EN

Stack Overflow用户
提问于 2021-09-30 09:31:51
回答 1查看 219关注 0票数 0

我需要在Bézier曲线上的球之间保持相同的距离。现在我使用曲线上可能的位置的简单迭代,在每次迭代中增加一些步数,直到找到所需的距离,大于或相等的位置。它可以工作,但可以是资源密集型的,当有大量的球.https://codesandbox.io/s/bezier-with-balls-mz37y?file=/src/index.js

代码语言:javascript
复制
class TestScene extends Phaser.Scene {
  create() {
    // prettier-ignore
    const curve = new Phaser.Curves.QuadraticBezier(
      [
        55, 310,
        40, 0,
        250, 310
      ]
    );

    const graphicsLayer = this.add.graphics({
      lineStyle: {
        width: 2,
        color: 0x0000ff
      }
    });

    curve.draw(graphicsLayer);

    const balls = this.initBalls(6);

    this.curve = curve;
    this.balls = balls;
  }

  initBalls(quantity) {
    const balls = [];

    for (let i = 0; i < quantity; i++) {
      const ball = this.add.circle(0, 0, 12, 0x00ff00);
      ball.t = 0;

      balls.push(ball);
    }

    return balls;
  }

  calcNextBallT(previous) {
    const ballDiameter = previous.radius * 2;
    const curve = this.curve;
    const curveLen = curve.getLength();
    const previousPos = new Phaser.Math.Vector2(previous);
    const previousT = previous.t;

    const startingT = (1 / curveLen) * ballDiameter + previousT;

    const step = 1 / 1000;

    let nextT = startingT;
    let nextPos;
    let currentDistance = 0;
    while (nextT >= 0 && nextT <= 1) {
      nextPos = curve.getPointAt(nextT);
      currentDistance = previousPos.distance(nextPos);
      if (currentDistance >= ballDiameter) {
        break;
      }
      nextT += step;
    }

    return nextT;
  }

  update() {
    const {
      curve,
      balls
    } = this;
    if (!curve || !balls) {
      return;
    }

    const speed = 1;
    const curveLen = curve.getLength();
    const step = (1 / curveLen) * speed;

    balls.forEach((ball, index, array) => {
      let nextT = ball.t;

      if (index === 0) {
        nextT += step;
        if (nextT > 1) {
          nextT = 0;
        }
      } else {
        const previous = array[index - 1];
        nextT = this.calcNextBallT(previous);
      }

      ball.t = nextT;
      ball.copyPosition(curve.getPointAt(nextT));
    });
  }
}

const game = new Phaser.Game({
  width: 320,
  height: 320,
  powerPreference: 'low-power',
  audio: {
    noAudio: true
  },
  scene: [TestScene]
});
代码语言:javascript
复制
<script src="https://unpkg.com/phaser@3.55.2/dist/phaser.min.js"></script>

我想也许有一个数学或算法的解决方案。就像找到一个更大的圆与曲线的交点一样,但据我所知,这是不可能的。

是否有更好的方法来确定Bézier曲线上的下一个点,根据从前一点到的直线距离?

更新1:一种使用二进制搜索的解决方案,如@MBo所建议的那样,具有良好的稳定性,平均迭代次数为5-10次,即使曲线和球大小不同。https://codesandbox.io/s/bezier-with-balls-binary-search-s4foq?file=/src/index.js

代码语言:javascript
复制
class TestScene extends Phaser.Scene {
  init() {
    this.input.on('drag', (pointer, gameObject, dragX, dragY) => {
      gameObject.setPosition(dragX, dragY);
    });

    this.iters = [];
  }

  create() {
    // prettier-ignore
    const p0 = this.add.circle(55, 310, 4, 0xffff00),
          p1 = this.add.circle(40, 5, 4, 0xffff00),
          p2 = this.add.circle(250, 310, 4, 0xffff00);

    const curve = new Phaser.Curves.QuadraticBezier(p0, p1, p2);

    const graphicsLayer = this.add.graphics({
      lineStyle: {
        width: 2,
        color: 0x0000ff
      }
    });
    curve.draw(graphicsLayer);

    const curveDragHandler = () => {
      curve.updateArcLengths();
      graphicsLayer.clear();
      curve.draw(graphicsLayer);
    };

    [p0, p1, p2].forEach((p) => {
      p.setDepth(10).setInteractive();
      this.input.setDraggable(p);
      p.on('drag', curveDragHandler);
    });

    const balls = this.initBalls(3, 50);

    const text = this.add.text(0, 0, '', {
      color: 'yellow'
    });

    this.curve = curve;
    this.balls = balls;
    this.text = text;
  }

  initBalls(quantity, diameter) {
    const radius = diameter / 2;
    const balls = [];

    for (let i = 0; i < quantity; i++) {
      const ball = this.add.circle(0, 0, radius, 0x00ff00);
      ball.t = 0;

      balls.push(ball);
    }

    return balls;
  }

  calcNextBallT(previous) {
    const ballDiameter = previous.radius * 2;
    const curve = this.curve;
    const previousPos = new Phaser.Math.Vector2(previous);
    const previousT = previous.t;

    let nextT = 1;
    let lowT = previousT;
    let highT = 1;
    let iter = 1;
    const skip = previousPos.distance(curve.getEndPoint()) <= ballDiameter;
    while (lowT <= highT && !skip) {
      nextT = lowT + (highT - lowT) / 2;
      const nextPos = curve.getPointAt(nextT);
      const currentDistance = previousPos.distance(nextPos);

      if (fuzzySame(currentDistance, ballDiameter)) {
        break;
      }

      if (currentDistance > ballDiameter) {
        highT = nextT;
      } else {
        lowT = nextT;
      }

      iter++;
    }

    if (!skip) {
      this.iters.push(iter);
    }

    return nextT;
  }

  update() {
    const {
      curve,
      balls
    } = this;
    if (!curve || !balls) {
      return;
    }

    const speed = 1;
    const curveLen = curve.getLength();
    const step = (1 / curveLen) * speed;

    balls.forEach((ball, index, array) => {
      let nextT = ball.t;

      if (index === 0) {
        nextT += step;
        if (nextT > 1) {
          const average = findAverage(this.iters).toFixed(2);
          const maximum = findMaximum(this.iters);
          this.text.setText(`Average: ${average}\nMaximum: ${maximum}`);
          this.iters = [];
          nextT = 0;
        }
      } else {
        const previous = array[index - 1];
        nextT = this.calcNextBallT(previous);
      }

      ball.t = nextT;
      ball.copyPosition(curve.getPointAt(nextT));
    });
  }
}

const fuzzySame = (num1, num2) => {
  const tolerance = 0.2;
  return num1 >= num2 - tolerance && num1 <= num2 + tolerance;
};

const findAverage = (array) => {
  const len = array.length;
  let total = 0;
  array.forEach((num) => (total += num));
  return total / len;
};

const findMaximum = (array) => {
  return [...array].sort((a, b) => b - a)[0];
};

const game = new Phaser.Game({
  width: 320,
  height: 320,
  powerPreference: 'low-power',
  audio: {
    noAudio: true
  },
  scene: [TestScene]
});
代码语言:javascript
复制
<script src="https://unpkg.com/phaser@3.55.2/dist/phaser.min.js"></script>

EN

回答 1

Stack Overflow用户

发布于 2021-09-30 12:17:04

与直接迭代不同,在t范围内使用二进制搜索。

数学解需要解6度方程(对于三次曲线)没有封闭公式,所以需要使用数值方法(再次迭代)。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69389762

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档