ベジェ曲線を等しいセグメントに分割する
時々ゲーム開発において、カーブした軌道に沿って均一なオブジェクトを均等に分配する必要があります.私は、これのためにどんなコード解決も見つけることができませんでした.
このチュートリアルではgoプログラミング言語とPixel ゲームライブラリ.使いましょうBezier curves ここで記述されたアルゴリズムはどんな種類の曲がった軌道のためにも適用できるけれども、彼らはどんな軌道をつくって、それを手動で編集するのに十分柔軟であるので.ベジエ曲線を使用して作成することができますgonum パッケージ.インポートセクションです.
最初に新しい曲線を作成する必要があります.立方体のベジエ曲線を4つの制御点で使用します.
また、後で必要とする定数もあります.
それは多くのベジエ曲線ポイントをできるだけ取得する方が良いです.これを行うには、ステップを使用します
カーブのポイントを描くために、新しいウィンドウと
では、私たちが得たものを見ましょう.
見ることができるように、曲線の任意の2つの隣接点間の距離は常に同じではありません.さらに、グラフは特に曲線の最初と最後の制御点に近いほど密になりません.
実は、それは私たちが見たいものではありません.我々は、カーブに沿って等しく散乱されるすべての点を必要とします.これに到達するためには,曲線を等分割するアルゴリズムを導入しなければならない.
では、新しい関数を定義しましょう
曲線点を線で接続します.
Aを変換する
行の合計長を計算します.
単一のセグメントの長さであるステップを計算します.
さて,多角形鎖をセグメント化するタスクに還元した.より多くの曲線点が得られると、より正確には、元の曲線のセグメント化になります.
最初にいくつかの初期設定を行います.
では、カーブを10等分していきましょう.
まあ、それはずっと良いです.お読みありがとうございます.ここではsource code .
依存
このチュートリアルではgoプログラミング言語とPixel ゲームライブラリ.使いましょうBezier curves ここで記述されたアルゴリズムはどんな種類の曲がった軌道のためにも適用できるけれども、彼らはどんな軌道をつくって、それを手動で編集するのに十分柔軟であるので.ベジエ曲線を使用して作成することができますgonum パッケージ.インポートセクションです.
import (
"fmt"
"time"
"github.com/faiface/pixel"
"github.com/faiface/pixel/imdraw"
"github.com/faiface/pixel/pixelgl"
colors "golang.org/x/image/colornames"
"gonum.org/v1/plot/plotter"
"gonum.org/v1/plot/tools/bezier"
"gonum.org/v1/plot/vg"
)
ご覧の通り、私も使用しますcolornames
パッケージ定義済みの色.曲線の作成
最初に新しい曲線を作成する必要があります.立方体のベジエ曲線を4つの制御点で使用します.
controlPoints := []vg.Point{
{X: 0.45, Y: 0.328},
{X: 1.403, Y: 0.12},
{X: 0.62, Y: 1.255},
{X: 1.521, Y: 0.593},
}
ご覧の通り、数字はかなり小さいです.心配しないでください、曲線が縮小され、画面上で良い見て翻訳されます.また、後で必要とする定数もあります.
const (
screenWidth = 1280
screenHeight = 720
offsetX float64 = 400
offsetY float64 = 300
scaleX float64 = 300
scaleY float64 = 300
numberOfSegments = 10
epsilon float64 = 0.001
dt float64 = 0.5
)
コントロールポイントから新しい曲線を作成するには、次の手順に従います.// Form the curve.
curve := bezier.New(controlPoints...)
今はカーブのポイントを計算する時間です.ベジエ曲線の単一点を得るには、パラメータt、0を使用する必要があります≤ t ≤ 1 .メソッド(c Curve) Point(t float64) vg.Point
パラメータに対応する曲線のポイントを返します.たとえば、T = 0.5の場合、このメソッドは曲線の中点を返します.それは多くのベジエ曲線ポイントをできるだけ取得する方が良いです.これを行うには、ステップを使用します
dt
. より少ないステップ、あなたが得るより多くのポイント.points := make(plotter.XYs, 0)
for t := 0.0; t < 100.0; t += dt {
point := curve.Point(t / 100.0)
points = append(points, plotter.XY{
X: float64(point.X)*scaleX + offsetX,
Y: float64(point.Y)*scaleY + offsetY})
}
曲線を描く
カーブのポイントを描くために、新しいウィンドウと
IMDraw
対象:cfg := pixelgl.WindowConfig{
Title: "Bezier curve",
Bounds: pixel.R(0, 0, screenWidth, screenHeight),
}
win, err := pixelgl.NewWindow(cfg)
handleError(err)
imd := imdraw.New(nil)
また、FPSカウンタを設定したいです.fps := 0
perSecond := time.Tick(time.Second)
今、私たちは、アプリケーションのメインループを入力し、曲線を各フレームを描画する準備が整いましたfor !win.Closed() {
win.Clear(colors.White)
imd.Clear()
// Draw the curve and other things.
imd.Color = colors.Red
for _, point := range points {
imd.Push(gonumToPixel(point))
imd.Circle(1, 1)
}
imd.Draw(win)
win.Update()
// Show FPS in the window title.
fps++
select {
case <-perSecond:
win.SetTitle(fmt.Sprintf("%s | FPS: %d", cfg.Title, fps))
fps = 0
default:
}
}
ところで、上記のすべては、中に置かれなければなりませんrun()
関数main()
:func main() {
pixelgl.Run(run)
}
メインGoroutineが別のスレッドに割り当てられないことを確認する必要があります.では、私たちが得たものを見ましょう.
見ることができるように、曲線の任意の2つの隣接点間の距離は常に同じではありません.さらに、グラフは特に曲線の最初と最後の制御点に近いほど密になりません.
実は、それは私たちが見たいものではありません.我々は、カーブに沿って等しく散乱されるすべての点を必要とします.これに到達するためには,曲線を等分割するアルゴリズムを導入しなければならない.
では、新しい関数を定義しましょう
getSegmentPoints(points plotter.XYs, numberOfSegments int) []pixel.Vec
:曲線点を線で接続します.
// Create lines out of bezier
// curve points.
lines := []pixel.Line{}
for i := 0; i < len(points)-1; i++ {
line := pixel.L(gonumToPixel(points[i]),
gonumToPixel(points[i+1]))
lines = append(lines, line)
}
注意:関数gonumToPixel(xy plotter.XY) pixel.Vec
Aを変換する
gonum
ベクトルにpixel
ベクトル.行の合計長を計算します.
// Compute the length
// of the bezier curve
// interpolated with lines.
length := 0.0
for _, line := range lines {
length += line.Len()
}
単一のセグメントの長さであるステップを計算します.
step := length / float64(numberOfSegments)
さて,多角形鎖をセグメント化するタスクに還元した.より多くの曲線点が得られると、より正確には、元の曲線のセグメント化になります.
最初にいくつかの初期設定を行います.
segmentPoints := []pixel.Vec{}
lastLine := 0
lastPoint := lines[0].A
segmentPoints = append(segmentPoints, lastPoint)
So lastPoint
は最後に形成されたセグメントの最後のポイントである.lastLine
は最後の点を含む行のインデックスです.ループへ for i := 0; i < numberOfSegments; i++ {
subsegments := []pixel.Line{}
startLine := pixel.L(lastPoint, lines[lastLine].B)
subsegments = append(subsegments, startLine)
localLength := startLine.Len()
for step-localLength > epsilon {
line := lines[lastLine+1]
subsegments = append(subsegments, line)
localLength += line.Len()
lastLine++
}
line := lines[lastLine]
if localLength > step {
difference := localLength - step
t := difference / line.Len()
lastPoint = pixel.V(t*line.A.X+(1-t)*line.B.X,
t*line.A.Y+(1-t)*line.B.Y)
} else {
lastPoint = line.B
lastLine++
}
segmentPoints = append(segmentPoints, lastPoint)
}
このループでは、その長さがセグメントの長さを超えるまで、我々は線を拾い上げます.それが起こるとき、我々は線形補間を使っている最後の線に位置するセグメンテーションポイントを計算します.行の最後と一致する場合は、最後の行カウンタをインクリメントし、次の行で新しい反復を開始します.まあ、それはずっと良いです.お読みありがとうございます.ここではsource code .
Reference
この問題について(ベジェ曲線を等しいセグメントに分割する), 我々は、より多くの情報をここで見つけました https://dev.to/zergon321/dividing-a-bezier-curve-into-equal-segments-2hh8テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol