ตกลงฉันได้ทุกอย่างทำงานมันใช้เวลาตลอดไปดังนั้นฉันจะโพสต์โซลูชั่นรายละเอียดของฉันที่นี่
หมายเหตุ: ตัวอย่างโค้ดทั้งหมดอยู่ใน JavaScript
ดังนั้นขอแบ่งปัญหาออกเป็นส่วนพื้นฐาน:
คุณต้องคำนวณความยาวของและจุดระหว่าง0..1
บนเส้นโค้งเบซิเยร์
ตอนนี้คุณต้องปรับสเกลของคุณT
เพื่อเร่งความเร็วเรือจากความเร็วหนึ่งไปอีกความเร็วหนึ่ง
ทำให้ Bezier ถูกต้อง
การค้นหารหัสบางอย่างสำหรับการวาดเส้นโค้ง Bezier นั้นง่ายมีหลายวิธีที่แตกต่างกัน แต่หนึ่งในนั้นคือDeCasteljau Algorithmแต่คุณสามารถใช้สมการสำหรับเส้นโค้งเบซิเยร์ลูกบาศก์ได้:
// Part of a class, a, b, c, d are the four control points of the curve
x: function (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.a.x
+ 3 * ((1 - t) * (1 - t)) * t * this.b.x
+ 3 * (1 - t) * (t * t) * this.c.x
+ (t * t * t) * this.d.x;
},
y: function (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.a.y
+ 3 * ((1 - t) * (1 - t)) * t * this.b.y
+ 3 * (1 - t) * (t * t) * this.c.y
+ (t * t * t) * this.d.y;
}
ด้วยวิธีนี้ได้ในขณะนี้สามารถวาดเส้นโค้งเบซิเยร์โดยการเรียกx
และy
ด้วยt
ซึ่งมีตั้งแต่0 to 1
ลองมาดู:
เอ่อ ... นั่นไม่ได้เป็นการกระจายจุดอย่างสม่ำเสมอจริงๆเหรอ?
เนื่องจากลักษณะของเส้นโค้งเบซิเยร์จุดที่0...1
แตกต่างกันarc lenghts
ดังนั้นส่วนที่อยู่ใกล้จุดเริ่มต้นและจุดสิ้นสุดจึงยาวกว่าจุดที่อยู่ใกล้กับกึ่งกลางของเส้นโค้ง
การแมป T อย่างสม่ำเสมอบนการกำหนดพารามิเตอร์ความยาวส่วนโค้งของ AKA
แล้วจะทำอย่างไรดี? ในแง่ง่ายเราจำเป็นต้องมีฟังก์ชั่นในการแมปของเราT
ลงบนส่วนt
โค้งเพื่อให้T 0.25
ผลลัพธ์ของเราอยู่t
ที่25%
ความยาวของส่วนโค้ง
เราจะทำอย่างนั้นได้อย่างไร? เรา Google ... แต่ปรากฎว่าคำนั้นไม่ใช่googleableและในบางจุดคุณจะพบกับPDFนี้ ซึ่งแน่นอนว่าเป็นการอ่านที่ยอดเยี่ยม แต่ในกรณีที่คุณลืมคณิตศาสตร์ทั้งหมดที่คุณเรียนรู้ในโรงเรียน (หรือคุณไม่ชอบสัญลักษณ์ทางคณิตศาสตร์เหล่านั้น) มันไร้ประโยชน์เลยทีเดียว
เกิดอะไรขึ้น ไปกันเถอะและ Google อีกแล้ว (อ่าน: 6 ชั่วโมง) และในที่สุดคุณก็พบบทความที่ดีเกี่ยวกับหัวข้อ (รวมถึงรูปภาพที่สวยงาม! ^ _ ^ "):
http://www.planetclegg.com/projects/WarpingTextToSplines.html
ทำรหัสจริง
ในกรณีที่คุณไม่สามารถต่อต้านการดาวน์โหลดไฟล์ PDF แม้ว่าคุณจะสูญเสียความรู้ทางคณิตศาสตร์ไปแล้วเมื่อนานมาแล้ว (และคุณสามารถข้ามลิงค์บทความที่ยอดเยี่ยม ) ตอนนี้คุณอาจคิดว่า: "พระเจ้าสิ่งนี้จะต้องใช้เวลา โค้ดหลายร้อยบรรทัดและ CPU จำนวนมาก "
ไม่มันจะไม่ เพราะเราทำสิ่งที่โปรแกรมเมอร์ทุกคนทำเมื่อมันมาถึงสิ่งคณิตศาสตร์:
เราเพียงแค่โกง
การตั้งค่าพารามิเตอร์ความยาวส่วนโค้ง, วิธีขี้เกียจ
เอาหน้าเราไม่ต้องการความแม่นยำไม่รู้จบในเกมของเราใช่ไหม? ดังนั้นหากคุณไม่ได้ทำงานที่ Nasa และวางแผนที่จะส่งคนไปยังดาวอังคารคุณจะไม่ต้องการ0.000001 pixel
โซลูชันที่สมบูรณ์แบบ
ดังนั้นทำอย่างไรเราแผนที่T
บนt
? ง่ายและประกอบด้วย 3 ขั้นตอนเท่านั้น:
คำนวณN
คะแนนบนเส้นโค้งโดยใช้t
และเก็บarc-length
(หรือที่รู้จักความยาวของเส้นโค้ง) ที่ตำแหน่งนั้นลงในอาร์เรย์
ในการแมปT
ลงบนt
ก่อนอื่นให้คูณT
ด้วยความยาวทั้งหมดของเส้นโค้งเพื่อหาu
และจากนั้นค้นหาอาร์เรย์ของความยาวเพื่อหาดัชนีที่มีค่ามากที่สุดที่เล็กกว่าu
ถ้าเรามีการตีที่แน่นอนกลับค่าอาร์เรย์ที่ดัชนีที่หารด้วยN
หากไม่ได้สอดแทรกบิตระหว่างจุดที่เราพบและหน้าหนึ่งแบ่งสิ่งอีกครั้งโดยN
และผลตอบแทน
นั่นคือทั้งหมด! ดังนั้นตอนนี้ลองมาดูรหัสที่สมบูรณ์:
function Bezier(a, b, c, d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.len = 100;
this.arcLengths = new Array(this.len + 1);
this.arcLengths[0] = 0;
var ox = this.x(0), oy = this.y(0), clen = 0;
for(var i = 1; i <= this.len; i += 1) {
var x = this.x(i * 0.05), y = this.y(i * 0.05);
var dx = ox - x, dy = oy - y;
clen += Math.sqrt(dx * dx + dy * dy);
this.arcLengths[i] = clen;
ox = x, oy = y;
}
this.length = clen;
}
นี่ต้นโค้งใหม่ของเราและคำนวณarg-lenghts
ก็ยังเก็บสุดท้ายของความยาวเป็นtotal length
โค้งปัจจัยที่สำคัญที่นี่เป็นซึ่งเป็นของเราthis.len
N
ยิ่งการแมปที่แม่นยำยิ่งขึ้นสำหรับเส้นโค้งของขนาดในภาพด้านบนนั้น100 points
น่าจะเพียงพอหากคุณต้องการการประมาณความยาวที่ดีบางอย่างที่คล้ายกัน25
จะทำงานได้โดยที่เหลือเพียง 1 พิกเซลในของเรา ตัวอย่างเช่น แต่แล้วคุณจะมีการทำแผนที่ที่แม่นยำน้อยลงซึ่งจะส่งผลให้เกิดการกระจายไม่ได้ดังนั้นแม้เมื่อแมปไปT
t
Bezier.prototype = {
map: function(u) {
var targetLength = u * this.arcLengths[this.len];
var low = 0, high = this.len, index = 0;
while (low < high) {
index = low + (((high - low) / 2) | 0);
if (this.arcLengths[index] < targetLength) {
low = index + 1;
} else {
high = index;
}
}
if (this.arcLengths[index] > targetLength) {
index--;
}
var lengthBefore = this.arcLengths[index];
if (lengthBefore === targetLength) {
return index / this.len;
} else {
return (index + (targetLength - lengthBefore) / (this.arcLengths[index + 1] - lengthBefore)) / this.len;
}
},
mx: function (u) {
return this.x(this.map(u));
},
my: function (u) {
return this.y(this.map(u));
},
รหัสการแมปจริงก่อนอื่นเราทำการเรียบง่ายbinary search
กับความยาวที่เก็บไว้ของเราเพื่อค้นหาความยาวที่ใหญ่ที่สุดที่เล็กกว่านั้นtargetLength
จากนั้นเราแค่ส่งคืนหรือทำการแก้ไขและกลับมา
x: function (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.a.x
+ 3 * ((1 - t) * (1 - t)) * t * this.b.x
+ 3 * (1 - t) * (t * t) * this.c.x
+ (t * t * t) * this.d.x;
},
y: function (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.a.y
+ 3 * ((1 - t) * (1 - t)) * t * this.b.y
+ 3 * (1 - t) * (t * t) * this.c.y
+ (t * t * t) * this.d.y;
}
};
สิ่งนี้คำนวณt
บนเส้นโค้งอีกครั้ง
เวลาสำหรับผลลัพธ์
เมื่อใช้งานmx
แล้วmy
คุณจะได้T
เส้นโค้งที่เท่ากัน:)
มันไม่ยากใช่ไหม อีกครั้งปรากฎว่าวิธีง่ายๆ (แม้ว่าจะไม่ใช่วิธีที่สมบูรณ์แบบ) จะเพียงพอสำหรับเกม
ในกรณีที่คุณต้องการเห็นรหัสที่สมบูรณ์มี Gist ให้ใช้งาน:
https://gist.github.com/670236
ในที่สุดเร่งเรือ
ดังนั้นสิ่งที่เหลืออยู่ในตอนนี้คือการเร่งเรือไปตามเส้นทางของพวกเขาโดยการทำแผนที่ตำแหน่งT
ที่เราใช้เพื่อค้นหาt
บนโค้งของเรา
ก่อนอื่นเราต้องใช้สมการการเคลื่อนที่สองแบบคือut + 1/2at²
และ(v - u) / t
ในรหัสจริงที่มีลักษณะเช่นนี้:
startSpeed = getStartingSpeedInPixels() // Note: pixels
endSpeed = getFinalSpeedInPixels() // Note: pixels
acceleration = (endSpeed - startSpeed) // since we scale to 0...1 we can leave out the division by 1 here
position = 0.5 * acceleration * t * t + startSpeed * t;
จากนั้นเราลดขนาดลง0...1
โดยทำ:
maxPosition = 0.5 * acceleration + startSpeed;
newT = 1 / maxPosition * position;
และคุณไปแล้วตอนนี้เรือกำลังเคลื่อนไหวอย่างราบรื่นไปตามเส้นทาง
ในกรณีที่มันไม่ทำงาน ...
เมื่อคุณอ่านสิ่งนี้ทุกอย่างทำงานได้ดีและน่าสนใจ แต่เริ่มแรกฉันมีปัญหากับส่วนเร่งความเร็วเมื่ออธิบายปัญหาให้กับใครบางคนในห้องสนทนา gamedev ฉันพบข้อผิดพลาดสุดท้ายในความคิดของฉัน
ในกรณีที่คุณยังไม่ลืมเกี่ยวกับภาพในคำถามเดิมฉันพูดถึงที่s
นั่นปรากฎว่าs
มีความเร็วเป็นองศาแต่เรือเคลื่อนที่ไปตามเส้นทางเป็นพิกเซลและฉันลืมความจริงนั้น ดังนั้นสิ่งที่ฉันต้องทำในกรณีนี้คือการแปลงการกระจัดเป็นองศาเป็นการกระจัดเป็นพิกเซลปรากฎว่ามันค่อนข้างง่าย:
function rotationToMovement(planetSize, rotationSpeed) {
var r = shipAngle * Math.PI / 180;
var rr = (shipAngle + rotationSpeed) * Math.PI / 180;
var orbit = planetSize + shipOrbit;
var dx = Math.cos(r) * orbit - Math.cos(rr) * orbit;
var dy = Math.sin(r) * orbit - Math.sin(rr) * orbit;
return Math.sqrt(dx * dx + dy * dy);
};
และนั่นคือทั้งหมด! ขอบคุณที่อ่าน ;)