ฉันจะอ่านระหว่างค่าที่วนซ้ำ (เช่นสีหรือการหมุน) ได้อย่างไร


25

ตัวอย่างภาพเคลื่อนไหวร่วม

ดูการสาธิต

ฉันพยายามที่จะทำให้รอยต่อหมุนได้อย่างราบรื่นรอบจุดศูนย์กลางของผืนผ้าใบไปยังมุมของตัวชี้เมาส์ สิ่งที่ฉันได้ผล แต่ฉันต้องการให้มันเคลื่อนไหวได้ในระยะทางที่สั้นที่สุดเท่าที่จะเป็นไปได้เพื่อให้ได้มุมของเมาส์ ปัญหาเกิดขึ้นเมื่อค่าวนรอบที่เส้นแนวนอน ( 3.14และ-3.14) วางเมาส์เหนือบริเวณนั้นเพื่อดูว่าทิศทางเปลี่ยนไปอย่างไรและมันจะย้อนกลับไปได้ไกล

รหัสที่เกี่ยวข้อง

// ease the current angle to the target angle
joint.angle += ( joint.targetAngle - joint.angle ) * 0.1;

// get angle from joint to mouse
var dx = e.clientX - joint.x,
    dy = e.clientY - joint.y;  
joint.targetAngle = Math.atan2( dy, dx );

ฉันจะทำให้มันหมุนระยะทางที่สั้นที่สุดแม้ "ข้ามช่องว่าง" ได้อย่างไร?


ใช้โมดูโล่ : D en.wikipedia.org/wiki/Modular_arithmetic
— Vaughan Hilts

1
@VaughanHilts ไม่แน่ใจว่าฉันจะใช้สถานการณ์ของฉันได้อย่างไร คุณสามารถอธิบายเพิ่มเติมได้หรือไม่
— jackrugile

คำตอบ:


11

มันไม่ได้เป็นวิธีที่ดีที่สุดเสมอไปและอาจมีค่าใช้จ่ายในการคำนวณที่สูงกว่า (แม้ว่าท้ายที่สุดแล้วขึ้นอยู่กับว่าคุณเก็บข้อมูลของคุณอย่างไร) แต่ฉันจะทำให้อาร์กิวเมนต์ที่การอ่านค่า 2D ทำงานได้ดีในกรณีส่วนใหญ่ แทนที่จะ lerping มุมต้องการคุณสามารถ lerp ที่ต้องการปกติเวกเตอร์ทิศทาง

ข้อดีอย่างหนึ่งของวิธีนี้เหนือวิธี "เลือกเส้นทางที่สั้นที่สุดไปยังมุม" คือการทำงานเมื่อคุณต้องการแทรกระหว่างค่ามากกว่าสองค่า

เมื่อ lerping ค่าสีคุณสามารถแทนที่hueด้วย[cos(hue), sin(hue)]เวกเตอร์

ในกรณีของคุณ lerping ทิศทางร่วมร่วม:

// get normalised direction from joint to mouse
var dx = e.clientX - joint.x,
    dy = e.clientY - joint.y;
var len = Math.sqrt(dx * dx + dy * dy);
dx /= len ? len : 1.0; dy /= len ? len : 1.0;
// get current direction
var dirx = cos(joint.angle),
    diry = sin(joint.angle);
// ease the current direction to the target direction
dirx += (dx - dirx) * 0.1;
diry += (dy - diry) * 0.1;

joint.angle = Math.atan2(diry, dirx);

รหัสสามารถสั้นลงได้ถ้าคุณสามารถใช้คลาสเวกเตอร์ 2D ตัวอย่างเช่น

// get normalised direction from joint to mouse
var desired_dir = normalize(vec2(e.clientX, e.clientY) - joint);
// get current direction
var current_dir = vec2(cos(joint.angle), sin(joint.angle));
// ease the current direction to the target direction
current_dir += (desired_dir - current_dir) * 0.1;

joint.angle = Math.atan2(current_dir.y, current_dir.x);

ขอบคุณนี่ใช้งานได้ดีที่นี่: codepen.io/jackrugile/pen/45c356f06f08ebea0e58daa4d06d204fฉันเข้าใจว่าส่วนใหญ่ของคุณกำลังทำอะไรอยู่ ไม่แน่ใจว่าเกิดอะไรขึ้นที่นั่นdx /= len...
— jackrugile

1
หารเวกเตอร์โดยความยาวของมันจะเรียกว่าการฟื้นฟู มันช่วยให้มั่นใจได้ว่ามีความยาว 1 len ? len : 1.0ส่วนเพียงแค่หลีกเลี่ยงการหารด้วยศูนย์ในกรณีที่หายากที่วางเมาส์ตรงตำแหน่งร่วมกัน มันสามารถเขียนได้: if (len != 0) dx /= len;.
— sam hocevar

-1 คำตอบนี้อยู่ไกลจากที่ดีที่สุดในกรณีส่วนใหญ่ เกิดอะไรขึ้นถ้าคุณกำลังแก้ไขระหว่าง0°และ180°? ในรูปแบบเวกเตอร์: และ[1, 0] [-1, 0]เวกเตอร์ interpolating จะให้คุณทั้งสอง0°, 180°หรือหารด้วย 0 t=0.5ข้อผิดพลาดในกรณีของ
— Gustavo Maciel

@GustavoMaciel นั่นไม่ใช่“ กรณีส่วนใหญ่” นั่นเป็นกรณีมุมที่เฉพาะเจาะจงมากที่ไม่เคยเกิดขึ้นในทางปฏิบัติ นอกจากนี้ยังไม่มีการหารศูนย์ตรวจสอบรหัส
— sam hocevar

@GustavoMaciel มีการตรวจสอบรหัสอีกครั้งจริง ๆ แล้วมันปลอดภัยมากและทำงานได้อย่างที่ควรจะเป็นแม้ในกรณีมุมคุณ
— sam hocevar

11

เคล็ดลับคือการจำไว้ว่ามุม (อย่างน้อยในอวกาศยูคลิด) นั้นเป็นระยะ 2 * pi หากความแตกต่างระหว่างมุมปัจจุบันและมุมเป้าหมายมีขนาดใหญ่เกินไป (เช่นเคอร์เซอร์ข้ามเขตแดน) เพียงแค่ปรับมุมปัจจุบันโดยการเพิ่มหรือลบ 2 * pi ตามลำดับ

ในกรณีนี้คุณสามารถลองทำสิ่งต่อไปนี้: (ฉันไม่เคยตั้งโปรแกรมใน Javascript มาก่อนดังนั้นโปรดยกโทษให้สไตล์การเข้ารหัสของฉัน)

  var dtheta = joint.targetAngle - joint.angle;
  if (dtheta > Math.PI) joint.angle += 2*Math.PI;
  else if (dtheta < -Math.PI) joint.angle -= 2*Math.PI;
  joint.angle += ( joint.targetAngle - joint.angle ) * joint.easing;

แก้ไข : ในการดำเนินการนี้การย้ายเคอร์เซอร์เร็วเกินไปรอบจุดศูนย์กลางของรอยต่อทำให้กระตุก dthetaนี่คือพฤติกรรมที่ตั้งใจไว้ตั้งแต่ความเร็วเชิงมุมของการร่วมทุนอยู่เสมอสัดส่วนกับ หากพฤติกรรมนี้ไม่พึงประสงค์สามารถแก้ไขปัญหาได้ง่าย ๆ โดยวางฝาครอบไว้ที่การเร่งความเร็วเชิงมุมของข้อต่อ

ในการทำเช่นนี้เราจะต้องติดตามความเร็วของข้อต่อและกำหนดอัตราเร่งสูงสุด:

  joint = {
    // snip
    velocity: 0,
    maxAccel: 0.01
  },

จากนั้นเพื่อความสะดวกของเราเราจะแนะนำฟังก์ชั่นการตัด:

function clip(x, min, max) {
  return x < min ? min : x > max ? max : x
}

ตอนนี้รหัสการเคลื่อนไหวของเรามีลักษณะเช่นนี้ อันดับแรกเราคำนวณdthetaเหมือนก่อนปรับjoint.angleตามความจำเป็น:

  var dtheta = joint.targetAngle - joint.angle;
  if (dtheta > Math.PI) joint.angle += 2*Math.PI;
  else if (dtheta < -Math.PI) joint.angle -= 2*Math.PI;

จากนั้นแทนที่จะเคลื่อนที่ข้อต่อทันทีเราคำนวณความเร็วเป้าหมายและใช้clipบังคับภายในช่วงที่เรายอมรับได้

  var targetVel = ( joint.targetAngle - joint.angle ) * joint.easing;
  joint.velocity = clip(targetVel,
                        joint.velocity - joint.maxAccel,
                        joint.velocity + joint.maxAccel);
  joint.angle += joint.velocity;

สิ่งนี้ทำให้เกิดการเคลื่อนไหวที่ราบรื่นแม้ในขณะเปลี่ยนทิศทางขณะทำการคำนวณในมิติเดียวเท่านั้น นอกจากนี้ยังช่วยให้ความเร็วและความเร่งของข้อต่อสามารถปรับได้อย่างอิสระ ดูตัวอย่างได้ที่นี่: http://codepen.io/anon/pen/HGnDF/


วิธีนี้เข้ามาใกล้จริงๆ แต่ถ้าฉันหนูเร็วเกินไปมันก็เริ่มกระโดดผิดทางเล็กน้อย การสาธิตที่นี่แจ้งให้เราทราบหากฉันไม่ได้ใช้งานอย่างถูกต้อง: codepen.io/jackrugile/pen/db40aee91e1c0b693346e6cec4546e98
— jackrugile

1
ฉันไม่แน่ใจว่าคุณหมายถึงอะไรโดยการกระโดดผิดทางเล็กน้อย สำหรับฉันข้อต่อจะเคลื่อนที่ไปในทิศทางที่ถูกต้องเสมอ แน่นอนว่าถ้าคุณเลื่อนเมาส์ไปรอบ ๆ ศูนย์เร็วเกินไปคุณก็จะวิ่งออกมาที่ข้อต่อและกระตุกในขณะที่มันสลับจากการเคลื่อนที่ในทิศทางเดียวไปอีกทิศทางหนึ่ง dthetaนี่คือพฤติกรรมที่ตั้งใจไว้ตั้งแต่ความเร็วในการหมุนอยู่เสมอสัดส่วนกับ คุณตั้งใจจะให้ข้อต่อมีแรงผลักดันบ้างไหม?
— David Zhang

ดูการแก้ไขล่าสุดของฉันสำหรับวิธีการกำจัดการเคลื่อนไหวกระตุกที่สร้างขึ้นโดยเอาชนะข้อต่อ
— David Zhang

เฮ้ลองลิงค์ตัวอย่างของคุณ แต่ดูเหมือนว่ามันจะชี้ไปที่ลิงก์ดั้งเดิมของฉัน คุณบันทึกใหม่หรือไม่ หากไม่เป็นไรฉันควรจะสามารถนำสิ่งนี้ไปใช้ในคืนนี้และดูว่ามันทำงานอย่างไร ฉันคิดว่าสิ่งที่คุณอธิบายในการแก้ไขเป็นสิ่งที่ฉันกำลังมองหา จะกลับมาหาคุณขอบคุณ!
— jackrugile

1
โอ้ใช่คุณพูดถูก ขอโทษที่ฉันทำผิด ฉันจะแก้ไขมัน
— David Zhang

3

ฉันรักคำตอบอื่น ๆ ที่ได้รับ ทางเทคนิคมาก!

ถ้าคุณต้องการฉันมีวิธีง่าย ๆ ในการทำสิ่งนี้ เราจะสมมุติมุมสำหรับตัวอย่างเหล่านี้ แนวคิดสามารถคาดการณ์กับค่าชนิดอื่นเช่นสี

double MAX_ANGLE = 360.0;
double startAngle = 300.0;
double endAngle = 15.0;
double distanceForward = 0.0;  // Clockwise
double distanceBackward = 0.0; // Counter-Clockwise

// Calculate both distances, forward and backward:
distanceForward = endAngle - startAngle;    // -285.00
distanceBackward = startAngle - endAngle;   // +285.00

// Which direction is shortest?
// Forward? (normalized to 75)
if (NormalizeAngle(distanceForward) < NormalizeAngle(distanceBackward)) {
    // Adjust for 360/0 degree wrap
    if (endAngle < startAngle) endAngle += MAX_ANGLE; // Will be above 360
}

// Backward? (normalized to 285)
else {
    // Adjust for 360/0 degree wrap
    if (endAngle > startAngle) endAngle -= MAX_ANGLE; // Will be below 0
}

// Now, Lerp between startAngle and endAngle. 

// EndAngle can be above 360 if wrapping clockwise past 0, or
// EndAngle can be below 0 if wrapping counter-clockwise before 0.
// Normalize each returned Lerp value to bring angle in range of 0 to 360 if required.  Most engines will automatically do this for you.


double NormalizeAngle(double angle) {
    while (angle < 0) 
        angle += MAX_ANGLE;
    while (angle >= MAX_ANGLE) 
        angle -= MAX_ANGLE;
    return angle;
}

ฉันเพิ่งสร้างสิ่งนี้ในเบราว์เซอร์และไม่เคยทดสอบ ฉันหวังว่าฉันจะใช้ตรรกะในการลองครั้งแรก

[แก้ไข] 2017/06/02 - ชี้แจงตรรกะเล็กน้อย

เริ่มต้นด้วยการคำนวณ distanceForward และ distanceBackwards และอนุญาตให้ผลลัพธ์ขยายออกไปนอกช่วง (0-360)

การทำให้เป็นปกติของมุมทำให้ค่าเหล่านั้นกลับสู่ช่วง (0-360) ในการทำเช่นนี้คุณต้องเพิ่ม 360 จนกว่าค่าจะสูงกว่าศูนย์และลบ 360 ในขณะที่ค่านั้นสูงกว่า 360 มุมเริ่มต้น / สิ้นสุดที่ได้จะเท่ากับ (-285 เท่ากับ 75)

ถัดไปคุณจะพบมุมที่เล็กที่สุดที่ปรับให้เป็นมาตรฐานของระยะทางไปข้างหน้าหรือระยะทางด้านหลัง distanceForward ในตัวอย่างจะกลายเป็น 75 ซึ่งน้อยกว่าค่าปกติของ distanceBackward (300)

หาก distanceForward มีขนาดเล็กที่สุดและ endAngle <startAngle ให้ขยาย endAngle เกิน 360 โดยเพิ่ม 360 (ตัวอย่างจะกลายเป็น 375 ในตัวอย่าง)

หาก distanceBackward มีขนาดเล็กที่สุดและสิ้นสุดแองเกิล> startAngle ให้ขยาย endAngle เป็นต่ำกว่า 0 โดยการลบ 360

ตอนนี้คุณจะอ่านจาก startAngle (300) ถึง endAngle ใหม่ (375) เครื่องยนต์ควรปรับค่าที่สูงกว่า 360 โดยอัตโนมัติด้วยการลบ 360 สำหรับคุณ มิฉะนั้นคุณจะต้องอ่านค่าจาก 300 ถึง 360 จากนั้นอ่านค่าจาก 0 ถึง 15 หากเครื่องยนต์ไม่ปรับมาตรฐานให้เป็นค่าปกติ


แม้ว่าatan2ความคิดพื้นฐานนั้นจะตรงไปตรงมา แต่แนวคิดนี้สามารถประหยัดพื้นที่และประสิทธิภาพเล็กน้อย (ไม่จำเป็นต้องเก็บ x และ y หรือคำนวณบาป cos และ atan2 บ่อยเกินไป) ฉันคิดว่ามันคุ้มค่าที่จะขยายตัวสักหน่อยว่าทำไมการแก้ปัญหานี้จึงถูกต้อง (เช่นการเลือกเส้นทางที่สั้นที่สุดในวงกลมหรือทรงกลมเช่นเดียวกับ SLERP ที่ใช้สำหรับคำถาม คำถามและคำตอบนี้ควรอยู่ในวิกิชุมชนเพราะนี่เป็นปัญหาที่พบบ่อยมากที่นักเขียนโปรแกรมเกมส่วนใหญ่เผชิญ
— teodron

คำตอบที่ดี ฉันชอบคำตอบอื่น ๆ ที่ได้รับจาก @David Zhang อย่างไรก็ตามฉันมักจะได้รับความกระวนกระวายใจแปลก ๆ โดยใช้วิธีการแก้ไขของเขาเมื่อฉันหมุนอย่างรวดเร็ว แต่คำตอบของคุณลงตัวในเกมของฉัน ฉันสนใจในทฤษฎีคณิตศาสตร์ที่อยู่เบื้องหลังคำตอบของคุณ แม้ว่ามันจะดูเรียบง่าย แต่ก็ไม่ชัดเจนว่าทำไมเราควรเปรียบเทียบระยะมุมปกติของทิศทางต่าง ๆ
— newguy

ฉันดีใจที่รหัสของฉันทำงาน ดังที่ได้กล่าวมานี่เป็นเพียงแค่พิมพ์ลงในเบราว์เซอร์ไม่ได้ทดสอบในโครงการจริง ฉันจำการตอบคำถามนี้ไม่ได้ด้วยซ้ำ (สามปีก่อน)! แต่เมื่อมองดูมันดูเหมือนว่าฉันเพิ่งขยายช่วง (0-360) เพื่อให้ค่าทดสอบเกินกว่า / ก่อนช่วงนี้เพื่อเปรียบเทียบความแตกต่างขององศาทั้งหมดและใช้ความแตกต่างที่สั้นที่สุด การทำให้เป็นมาตรฐานจะทำให้ค่าเหล่านั้นอยู่ในช่วง (0-360) อีกครั้ง ดังนั้น DistanceForward จึงกลายเป็น 75 (-285 + 360) ซึ่งเล็กกว่าระยะทาง Backward (285) ดังนั้นจึงเป็นระยะทางที่สั้นที่สุด
— Doug.McFarlane

เนื่องจาก distanceForward เป็นระยะทางที่สั้นที่สุดส่วนแรกของประโยค IF จะถูกใช้ เนื่องจาก endAngle (15) น้อยกว่า startAngle (300) เราเพิ่ม 360 เพื่อรับ 375 ดังนั้นคุณจะอ่านจาก startAngle (300) ไปที่ endAngle ใหม่ (375) เครื่องยนต์ควรปรับค่าที่สูงกว่า 360 โดยอัตโนมัติด้วยการลบ 360 สำหรับคุณ
— Doug.McFarlane

ฉันแก้ไขคำตอบเพื่อเพิ่มความชัดเจนมากขึ้น
— Doug.McFarlane
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.