ฉันจะตรวจสอบได้อย่างมีประสิทธิภาพว่าจุดอยู่ภายในสี่เหลี่ยมหมุนอย่างไร


11

ส่วนหนึ่งเพื่อประโยชน์ในการเพิ่มประสิทธิภาพส่วนหนึ่งเพื่อจุดประสงค์ในการเรียนรู้ฉันจะกล้าถาม: ฉันจะตรวจสอบได้อย่างมีประสิทธิภาพมากที่สุดว่าจุด 2D นั้นPอยู่ในสี่เหลี่ยมหมุนรอบสองมิติXYZWโดยใช้ C # หรือ C ++ อย่างไร

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

bool PointInTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P)
{
 // Compute vectors        
 Vector2 v0 = C - A;
 Vector2 v1 = B - A;
 Vector2 v2 = P - A;

 // Compute dot products
 float dot00 = Vector2.Dot(v0, v0);
 float dot01 = Vector2.Dot(v0, v1);
 float dot02 = Vector2.Dot(v0, v2);
 float dot11 = Vector2.Dot(v1, v1);
 float dot12 = Vector2.Dot(v1, v2);

 // Compute barycentric coordinates
 float invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
 float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
 float v = (dot00 * dot12 - dot01 * dot02) * invDenom;

 // Check if point is in triangle
 if(u >= 0 && v >= 0 && (u + v) < 1)
    { return true; } else { return false; }
}


bool PointInRectangle(Vector2 X, Vector2 Y, Vector2 Z, Vector2 W, Vector2 P)
{
 if(PointInTriangle(X,Y,Z,P)) return true;
 if(PointInTriangle(X,Z,W,P)) return true;
 return false;
}

อย่างไรก็ตามฉันมีความรู้สึกว่าอาจจะสะอาดกว่าและเร็วกว่าเดิม โดยเฉพาะเพื่อลดจำนวนการดำเนินการทางคณิตศาสตร์


คุณมีหลายจุดหรือคุณมีหลายรูปสี่เหลี่ยม? นั่นเป็นคำถามแรกที่คุณควรถามตัวเองก่อนที่จะพยายามปรับให้เหมาะสมกับงานเล็ก ๆ
sam hocevar

จุดดี. ฉันจะมีคะแนนที่สูงมาก แต่ต้องตรวจสอบสี่เหลี่ยมเพิ่มเติม
Louis15

คำถามที่เกี่ยวข้องเกี่ยวกับการหาระยะห่างของจุดที่จะสี่เหลี่ยมผืนผ้าหมุน นี่เป็นกรณีที่เลวลง (ตรวจสอบเฉพาะเมื่อระยะทางเป็น 0) แน่นอนว่าจะมีการเพิ่มประสิทธิภาพที่ใช้กับที่นี่ซึ่งไม่ได้มี
Anko

คุณได้พิจารณาหมุนจุดเข้าไปในกรอบอ้างอิงของสี่เหลี่ยมผืนผ้าหรือไม่?
Richard Tingle

@RichardTingle ที่จริงฉันไม่ได้อยู่ที่จุดเริ่มต้น หลังจากนั้นฉันก็ทำเพราะฉันคิดว่าเกี่ยวข้องกับหนึ่งในคำตอบที่ให้ไว้ด้านล่าง แต่เพื่อให้ชัดเจนขึ้น: ในสิ่งที่คุณกำลังแนะนำหลังจากหมุนจุดไปยังกรอบอ้างอิงของรูปสี่เหลี่ยมผืนผ้าแล้วหนึ่งควรตรวจสอบการรวมเพียงแค่การเปรียบเทียบตรรกะระหว่าง max.x, min.x ฯลฯ
Louis15

คำตอบ:


2

การเพิ่มประสิทธิภาพที่ง่ายและตรงไปตรงมาคือการเปลี่ยนเงื่อนไขสุดท้ายในPointInTriangle:

bool PointInRectangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P) {
  ...
  if(u >= 0 && v >= 0 && u <= 1 && v <= 1)
      { return true; } else { return false; }
  }
}

รหัสนั้นสวยมากPointInRectangleแล้วสภาพ(u + v) < 1อยู่ที่นั่นเพื่อตรวจสอบว่ามันไม่ได้อยู่ในรูปสามเหลี่ยม "สอง" ของสี่เหลี่ยมผืนผ้า

อีกวิธีหนึ่งคุณสามารถทำisLeft(ตัวอย่างรหัสแรกในหน้าอธิบายอย่างมาก) ทดสอบสี่ครั้งและตรวจสอบว่าพวกเขาทั้งหมดส่งคืนผลลัพธ์ที่มีเครื่องหมายเดียวกัน (ซึ่งขึ้นอยู่กับว่าจุดที่ได้รับตามเข็มนาฬิกาหรือทวนเข็มนาฬิกา) จุดที่จะอยู่ภายใน มันใช้ได้กับรูปหลายเหลี่ยมนูนอื่น ๆ เช่นกัน

float isLeft( Point P0, Point P1, Point P2 )
{
    return ( (P1.x - P0.x) * (P2.y - P0.y) - (P2.x - P0.x) * (P1.y - P0.y) );
}
bool PointInRectangle(Vector2 X, Vector2 Y, Vector2 Z, Vector2 W, Vector2 P)
{
    return (isLeft(X, Y, P) > 0 && isLeft(Y, Z, P) > 0 && isLeft(Z, W, P) > 0 && isLeft(W, X, p) > 0);
}

ดีเยี่ยม ฉันไม่รู้ว่าฉันชอบคำแนะนำของคุณมากขึ้นซึ่งเร็วกว่าและสง่างามกว่าของฉันมากหรือถ้าฉันชอบมากกว่านั้นคุณสังเกตว่ารหัส PointInTri ของฉันอาจกลายเป็น PointInRec ได้อย่างง่ายดาย! ขอบคุณ
Louis15

+1 สำหรับisLeftวิธีการ มันไม่จำเป็นต้องฟังก์ชั่นหนุน (ตามVector2.Dotไม่) ซึ่งความเร็วสิ่งขึ้นมาก
Anko

Btw ไม่สามารถปรับรหัสได้ (ไม่ทดสอบไม่มีวิธีในคอมพิวเตอร์เครื่องนี้) โดยรวม isLeft โดยตรงในฟังก์ชั่นหลักและแทนที่ตัวดำเนินการ "&&" โดย "||" รางผกผันตรรกะ? public static bool PointInRectangle(Vector2 P, Vector2 X, Vector2 Y, Vector2 Z, Vector2 W) { return !(( (Y.x - X.x) * (P.y - X.y) - (P.x - X.x) * (Y.y - X.y) ) < 0 || ( (Z.x - Y.x) * (P.y - Y.y) - (P.x - Y.x) * (Z.y - Y.y) ) < 0 || ( (W.x - Z.x) * (P.y - Z.y) - (P.x - Z.x) * (W.y - Z.y) ) < 0 || ( (X.x - W.x) * (P.y - W.y) - (P.x - W.x) * (X.y - W.y) ) < 0 ); }
Louis15

1
@ Louis15 ฉันไม่คิดว่าคุณจะต้อง - ทั้ง && และ || จะหยุดดำเนินการคำสั่งเพิ่มเติมหากพบว่ามีค่าเป็นลบ / เป็นบวก (หรือมีเหตุผลอื่นหรือไม่) การประกาศisLeftเป็นอินไลน์คอมไพเลอร์จะทำสิ่งที่คล้ายกันสำหรับคุณ (และอาจดีกว่าที่คุณทำได้เพราะวิศวกรที่เขียนคอมไพเลอร์รู้ CPU ดีที่สุดการเลือกตัวเลือกใดที่เร็วที่สุด) ทำให้โค้ดของคุณอ่านง่ายขึ้น
wondra

8

แก้ไข:ความคิดเห็น OPs ได้รับการสงสัยเกี่ยวกับประสิทธิภาพของการตรวจสอบขอบเขตวงกลมเชิงลบที่แนะนำเพื่อปรับปรุงอัลกอริทึมเพื่อทำการตรวจสอบว่าจุด 2D โดยพลการอยู่ภายในสี่เหลี่ยมหมุนและ / หรือเคลื่อนย้าย การเล่นเกมเอนจิ้น 2D ของฉัน (OpenGL / C ++) ฉันเสริมคำตอบของฉันโดยการให้เกณฑ์มาตรฐานประสิทธิภาพของอัลกอริทึมของฉันกับ OPs อัลกอริทึมตรวจสอบจุดในสี่เหลี่ยมผืนผ้า (และรูปแบบต่างๆ)

ฉันแนะนำให้ออกจากอัลกอริทึมในสถานที่ (เพราะมันเกือบจะดีที่สุด) แต่ลดความซับซ้อนผ่านตรรกะเกมเพียง: (1) ใช้วงกลม pre-processing รอบสี่เหลี่ยมต้นฉบับ (2) ทำการตรวจสอบระยะทางและหากจุดอยู่ภายในวงกลมที่กำหนด (3) ใช้ OPs หรืออัลกอริทึมตรงไปตรงมาใด ๆ (ฉันแนะนำอัลกอริทึม isLeft ตามที่ระบุไว้ในคำตอบอื่น) ตรรกะที่อยู่เบื้องหลังข้อเสนอแนะของฉันคือการตรวจสอบว่าจุดหนึ่งในวงกลมนั้นมีประสิทธิภาพมากกว่าการตรวจสอบขอบเขตของสี่เหลี่ยมผืนผ้าที่หมุนหรือรูปหลายเหลี่ยมอื่น ๆ หรือไม่

สถานการณ์เริ่มต้นของฉันสำหรับการทดสอบเกณฑ์มาตรฐานคือการใช้จุดปรากฏและจุดที่หายไปจำนวนมาก (ซึ่งมีการเปลี่ยนแปลงตำแหน่งในทุกวงเกม) ในพื้นที่ที่ จำกัด ซึ่งจะเต็มไปด้วยสี่เหลี่ยมหมุน / เคลื่อนไหวประมาณ 20 จุด ฉันเผยแพร่วิดีโอ ( ลิงก์ youtube ) เพื่อประกอบการอธิบาย สังเกตพารามิเตอร์: จำนวนจุดที่ปรากฏตัวเลขหรือสี่เหลี่ยมแบบสุ่ม ฉันจะเปรียบเทียบกับพารามิเตอร์ต่อไปนี้:

OFF : อัลกอริธึมที่ตรงไปตรงมาซึ่งจัดทำโดย OP โดยไม่มีการตรวจสอบเชิงลบขอบเขตวงกลม

ON : การใช้วงกลมที่ผ่านการประมวลผล (ขอบเขต) รอบ ๆ สี่เหลี่ยมเป็นการตรวจสอบการแยกครั้งแรก

ON + Stack : การสร้างขอบเขตวงกลมในขณะใช้งานภายในลูปบนสแต็ก

ON + Square Distance : การใช้ระยะทางสแควร์เป็นการเพิ่มประสิทธิภาพเพิ่มเติมเพื่อหลีกเลี่ยงการใช้อัลกอริทึมรากที่มีราคาแพงกว่า (Pieter Geerkens)

นี่คือบทสรุปของการแสดงที่หลากหลายของอัลกอริทึมที่แตกต่างกันโดยแสดงเวลาที่ใช้ในการวนซ้ำผ่านลูป

ป้อนคำอธิบายรูปภาพที่นี่

แกน x แสดงความซับซ้อนที่เพิ่มขึ้นโดยการเพิ่มจุดมากขึ้น (และทำให้วงวนช้าลง) (ตัวอย่างเช่นที่ 1,000 จุดสุ่มปรากฏขึ้นตรวจสอบในพื้นที่ confided กับ 20 สี่เหลี่ยมวนรอบวนซ้ำและเรียกอัลกอริทึม 20000 ครั้ง.) แกน y แสดงเวลาที่ใช้ (ms) เพื่อเสร็จสิ้นการวนรอบทั้งหมดโดยใช้ความละเอียดสูง จับเวลาประสิทธิภาพ มากกว่า 20 ms จะเป็นปัญหาสำหรับเกมที่ดีเพราะมันจะไม่ใช้ประโยชน์จาก fps สูงในการสอดแทรกแอนิเมชั่นที่ราบรื่นและเกมอาจปรากฏขึ้น 'รุนแรง' ในบางครั้ง

ผลที่ 1 : อัลกอริทึมที่ถูกผูกไว้ล่วงหน้าแบบวงกลมที่ประมวลผลล่วงหน้าพร้อมกับการตรวจสอบเชิงลบอย่างรวดเร็วภายในลูปปรับปรุงประสิทธิภาพโดย 1900% เมื่อเทียบกับอัลกอริทึมปกติ (5% ของเวลาลูปดั้งเดิมโดยไม่มีการตรวจสอบ) ผลลัพธ์เก็บสัดส่วนโดยประมาณกับจำนวนการวนซ้ำภายในลูปดังนั้นจึงไม่สำคัญว่าเราจะตรวจสอบ 10 หรือ 10,000 จุดที่ปรากฏแบบสุ่ม ดังนั้นในภาพประกอบนี้เราสามารถเพิ่มจำนวนของวัตถุได้อย่างปลอดภัยถึง 10k โดยไม่รู้สึกว่าประสิทธิภาพลดลง

ผลที่ 2 : ได้รับการแนะนำโดยความคิดเห็นก่อนหน้าว่าอัลกอริทึมอาจเร็วกว่า แต่มีหน่วยความจำมาก อย่างไรก็ตามโปรดทราบว่าการจัดเก็บทศนิยมสำหรับขนาดวงกลมที่ประมวลผลล่วงหน้าใช้เวลาเพียง 4 ไบต์ สิ่งนี้ไม่น่าเป็นปัญหาจริงเว้นแต่ว่า OP มีแผนที่จะเรียกใช้วัตถุพร้อมกันมากกว่า 100,000 รายการ ทางเลือกและวิธีที่มีประสิทธิภาพของหน่วยความจำคือการคำนวณขนาดสูงสุดของวงกลมบนสแต็กภายในลูปและปล่อยให้มันออกไปนอกขอบเขตกับการวนซ้ำทุกครั้งและทำให้ไม่มีการใช้งานหน่วยความจำในทางปฏิบัติ ผลลัพธ์แสดงให้เห็นว่าวิธีนี้ช้ากว่าการใช้ขนาดวงกลมที่ผ่านการประมวลผลล่วงหน้า แต่ก็ยังคงแสดงให้เห็นถึงการปรับปรุงประสิทธิภาพอย่างมากประมาณ 1150% (เช่น 8% ของเวลาการประมวลผลดั้งเดิม)

ผลที่ 3 : ฉันปรับปรุงอัลกอริทึม 1 ให้ดีขึ้นโดยใช้ระยะทางยกกำลังสองแทนระยะทางจริงและทำการคำนวณรากที่สองที่มีราคาแพง สิ่งนี้จะเพิ่มประสิทธิภาพให้กับคุณเพียงเล็กน้อย (2400%) (หมายเหตุ: ฉันยังลองตารางแฮชสำหรับอาร์เรย์ที่ประมวลผลล่วงหน้าสำหรับการประมาณรากที่สองด้วยผลลัพธ์ที่คล้ายกัน แต่แย่กว่าเล็กน้อย)

ผลลัพธ์ 4 : ฉันตรวจสอบการเคลื่อนย้าย / การชนของสี่เหลี่ยมเพิ่มเติม อย่างไรก็ตามสิ่งนี้ไม่เปลี่ยนผลลัพธ์พื้นฐาน (ตามที่คาดไว้) เนื่องจากการตรวจสอบแบบลอจิคัลยังคงเหมือนเดิม

ผลที่ 5 : ฉันเปลี่ยนจำนวนของรูปสี่เหลี่ยมและพบว่าอัลกอริทึมจะมีประสิทธิภาพยิ่งขึ้นเมื่อมีพื้นที่น้อยลง (ไม่แสดงในตัวอย่าง) ผลที่ได้ก็คาดหวังเช่นกันเนื่องจากความน่าจะเป็นลดลงสำหรับจุดที่จะปรากฏในพื้นที่เล็ก ๆ ระหว่างวงกลมและขอบเขตของวัตถุ ในอีกทางหนึ่งฉันพยายามเพิ่มจำนวนสี่เหลี่ยมเกิน 100 ในพื้นที่แคบ ๆ เดียวกันและปรับขนาดตามแบบไดนามิก ณ รันไทม์ภายในลูป (sin (iterator)) สิ่งนี้ยังคงทำงานได้ดีมากโดยเพิ่มประสิทธิภาพขึ้น 570% (หรือ 15% ของเวลาลูปดั้งเดิม)

ผลลัพธ์ 6 : ฉันทดสอบอัลกอริธึมทางเลือกที่แนะนำที่นี่และค้นหาประสิทธิภาพที่แตกต่างกันเล็กน้อย แต่ไม่สำคัญ (2%) อัลกอริทึม IsLeft ที่น่าสนใจและง่ายกว่านั้นทำงานได้ดีมากด้วยการเพิ่มประสิทธิภาพ 17% (85% ของเวลาการคำนวณดั้งเดิม) แต่ไม่มีประสิทธิภาพใกล้เคียงกับอัลกอริทึมการตรวจสอบเชิงลบอย่างรวดเร็ว

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

ฉันขอแนะนำให้พยายามอ้างอิงมาตรฐานอัลกอริธึมที่ซับซ้อนในระหว่างขั้นตอนการออกแบบเกมเนื่องจากการดูรหัสธรรมดาอาจไม่เปิดเผยความจริงเกี่ยวกับประสิทธิภาพของเวลาทำงานจริง อัลกอริทึมที่แนะนำอาจไม่จำเป็นแม้แต่ที่นี่ตัวอย่างเช่นหากใครต้องการทดสอบว่าเคอร์เซอร์ของเมาส์อยู่ภายในสี่เหลี่ยมหรือไม่หรือเมื่อวัตถุส่วนใหญ่สัมผัสแล้ว หากการตรวจสอบคะแนนส่วนใหญ่อยู่ในกรอบสี่เหลี่ยมอัลกอริทึมจะมีประสิทธิภาพน้อยลง (อย่างไรก็ตามจากนั้นก็เป็นไปได้ที่จะสร้างขอบเขต 'วงใน' เป็นตัวตรวจสอบเชิงลบที่สอง) การตรวจสอบขอบเขตวงกลม / ทรงกลมมีประโยชน์มากสำหรับการตรวจจับการชนที่เหมาะสมของวัตถุจำนวนมากที่มีช่องว่างระหว่างพวกเขา .

Rec Points  Iter    OFF     ON     ON_Stack     ON_SqrDist  Ileft Algorithm (Wondra)
            (ms)    (ms)    (ms)    (ms)        (ms)        (ms)
20  10      200     0.29    0.02    0.04        0.02        0.17
20  100     2000    2.23    0.10    0.20        0.09        1.69
20  1000    20000   24.48   1.25    1.99        1.05        16.95
20  10000   200000  243.85  12.54   19.61       10.85       160.58

แม้ว่าฉันจะชอบวิธีการที่ผิดปกติและชอบการอ้างอิงของดาวินชี แต่ฉันไม่คิดว่าการจัดการกับแวดวงรัศมีที่โดดเดี่ยวจะมีประสิทธิภาพ นอกจากนี้การแก้ปัญหานั้นก็สมเหตุสมผลถ้าสี่เหลี่ยมทั้งหมดได้รับการแก้ไขและรู้จักล่วงหน้า
Louis15

ตำแหน่งของสี่เหลี่ยมไม่จำเป็นต้องได้รับการแก้ไข ใช้พิกัดสัมพัทธ์ ลองคิดดูเช่นนี้ รัศมีนั้นยังคงเดิมไม่ว่าจะหมุน
Majte

นี่คือคำตอบที่ดี ยังดีกว่าเพราะฉันไม่ได้คิด คุณอาจต้องการที่จะทราบว่ามันเพียงพอที่จะใช้ระยะทางยกกำลังสองแทนระยะทางจริงทำให้ไม่จำเป็นต้องคำนวณรากที่สอง
Pieter Geerkens

อัลกอริทึมที่น่าสนใจสำหรับการทดสอบในเชิงบวก / เชิงลบอย่างรวดเร็ว! ปัญหาอาจเป็นหน่วยความจำเพิ่มเติมเพื่อบันทึกวงกลมที่ถูกประมวลผลล่วงหน้า (และความกว้าง) ซึ่งอาจเป็นวิธีแก้ปัญหาที่ดี แต่ยังทราบว่ามีการใช้งาน จำกัด - ส่วนใหญ่สำหรับกรณีที่หน่วยความจำไม่สำคัญมาก (สี่เหลี่ยมขนาดคงที่ และมีเวลาในการประมวลผลล่วงหน้า
wondra

แก้ไข + เพิ่มการทดสอบเกณฑ์มาตรฐานแล้ว
Majte

2

การกำหนดสี่เหลี่ยมที่มี 4 จุดทำให้สามารถสร้างสี่เหลี่ยมคางหมูได้ อย่างไรก็ตามหากคุณจะกำหนดมันด้วย x, y, ความกว้างความสูงและการหมุนรอบ ๆ ตรงกลางคุณก็สามารถหมุนจุดที่คุณกำลังตรวจสอบได้โดยการหมุนผกผันของสี่เหลี่ยมผืนผ้าของคุณ (รอบต้นกำเนิดเดียวกัน) แล้วตรวจสอบว่า ในสี่เหลี่ยมดั้งเดิม


อืมขอบคุณสำหรับคำแนะนำ แต่การหมุนและรับการหมุนผกผันนั้นดูเหมือนจะไม่มีประสิทธิภาพ ในความเป็นจริงมันจะมีประสิทธิภาพแทบจะไม่เป็นวิธีแก้ปัญหาของฉัน - ไม่พูดถึง wondra's
Louis15

คุณสามารถทราบได้ว่าการหมุนจุด 3D ด้วยเมทริกซ์คือการคูณ 6 ครั้งและการเพิ่ม 3 ครั้งและการเรียกใช้ฟังก์ชันหนึ่งครั้ง วิธีการแก้ปัญหาของ @Wondra นั้นมีความเทียบเท่าที่ดีที่สุด แต่มีเจตนาที่ชัดเจนน้อยกว่า และอ่อนไหวมากขึ้นต่อข้อผิดพลาดในการบำรุงรักษาผ่านการละเมิด DRY
Pieter Geerkens

@Pieter Geerkens การอ้างสิทธิ์การตัดกันโซลูชันใดของฉันละเมิด DRY (และ DRY เป็นหนึ่งในหลักการการเขียนโปรแกรมที่สำคัญอย่างไรไม่เคยได้ยินมาก่อนจนถึงตอนนี้) และที่สำคัญที่สุดโซลูชั่นเหล่านั้นมีข้อผิดพลาดอะไร? พร้อมที่จะเรียนรู้เสมอ
wondra

@wondra: DRY = อย่าทำซ้ำตัวเอง ข้อมูลโค้ดของคุณแนะนำการเข้ารหัสรายละเอียดของเมทริกซ์ด้วยการคูณเวกเตอร์ทุกที่ที่ฟังก์ชันปรากฏในรหัสแทนที่จะเรียกใช้วิธีเมทริกซ์
Pieter Geerkens

@PieterGeerkens ของหลักสูตรแนะนำเพียงส่วนหนึ่งของมัน - 1) คุณไม่ได้เมทริกซ์อย่างชัดเจน (การจัดสรรเมทริกซ์ใหม่สำหรับแต่ละแบบสอบถามจะตีประสิทธิภาพการทำงานอย่างหนัก) 2) ฉันใช้เฉพาะกรณีของการคูณที่เพิ่มประสิทธิภาพสำหรับกรณีนี้ลดลงทั่วไป หนึ่ง. เป็นการดำเนินการระดับต่ำและควรยังคงถูกห่อหุ้มเพื่อป้องกันพฤติกรรมที่ไม่คาดคิด
wondra

1

ฉันไม่มีเวลาในการเปรียบเทียบสิ่งนี้ แต่ข้อเสนอแนะของฉันคือการเก็บเมทริกซ์การแปลงที่แปลงสี่เหลี่ยมผืนผ้าเป็นสี่เหลี่ยมจตุรัสแกนใน x- และ y- ช่วงจาก 0 ถึง 1 ในคำอื่น ๆ เก็บเมทริกซ์ที่ แปลงมุมหนึ่งของสี่เหลี่ยมเป็น (0,0) และอีกมุมหนึ่งเป็น (1,1)

แน่นอนว่าจะมีราคาแพงกว่าถ้าสี่เหลี่ยมถูกย้ายบ่อยครั้งและมีการตรวจสอบการชนค่อนข้างบ่อย แต่ถ้ามีการตรวจสอบมากกว่าการอัพเดทสี่เหลี่ยมอย่างน้อยก็เร็วกว่าวิธีทดสอบแบบสามเหลี่ยมสองรูปแบบเดิมอย่างน้อย เนื่องจากผลิตภัณฑ์หกจุดจะถูกแทนที่ด้วยการคูณเมทริกซ์เดียว

แต่เช่นเคยความเร็วของอัลกอริทึมนี้ขึ้นอยู่กับชนิดของการตรวจสอบที่คุณคาดว่าจะทำ หากจุดส่วนใหญ่ไม่ได้อยู่ใกล้กับสี่เหลี่ยมผืนผ้าที่ดำเนินการตรวจสอบระยะทางง่ายๆ (เช่น (point.x - firstCorner.x)> aLargeDistance) อาจส่งผลให้มีการเร่งความเร็วขนาดใหญ่ในขณะที่มันอาจทำให้ช้าลงหากเกือบทั้งหมด จุดที่อยู่ภายในสี่เหลี่ยม

แก้ไข: นี่คือสิ่งที่ชั้นสี่เหลี่ยมผืนผ้าของฉันจะมีลักษณะ:

class Rectangle
{
public:
    Matrix3x3 _transform;

    Rectangle()
    {}

    void setCorners(Vector2 p_a, Vector2 p_b, Vector2 p_c)
    {
        // create a matrix from the two edges of the rectangle
        Vector2 edgeX = p_b - p_a;
        Vector2 edgeY = p_c - p_a;

        // and then create the inverse of that matrix because we want to 
        // transform points from world coordinates into "rectangle coordinates".
        float scaling = 1/(edgeX._x*edgeY._y - edgeY._x*edgeX._y);

        _transform._columns[0]._x = scaling * edgeY._y;
        _transform._columns[0]._y = - scaling * edgeX._y;
        _transform._columns[1]._x = - scaling * edgeY._x;
        _transform._columns[1]._y = scaling * edgeX._x;

        // the third column is the translation, which also has to be transformed into "rectangle space"
        _transform._columns[2]._x = -p_a._x * _transform._columns[0]._x - p_a._y * _transform._columns[1]._x;
        _transform._columns[2]._y = -p_a._x * _transform._columns[0]._y - p_a._y * _transform._columns[1]._y;
    }

    bool isInside(Vector2 p_point)
    {
        Vector2 test = _transform.transform(p_point);
        return  (test._x>=0)
                && (test._x<=1)
                && (test._y>=0)
                && (test._y<=1);
    }
};

นี่คือรายการเต็มรูปแบบของมาตรฐานของฉัน:

#include <cstdlib>
#include <math.h>
#include <iostream>

#include <sys/time.h>

using namespace std;

class Vector2
{
public:
    float _x;
    float _y;

    Vector2()
    :_x(0)
    ,_y(0)
    {}

    Vector2(float p_x, float p_y)
        : _x (p_x)
        , _y (p_y)
        {}

    Vector2 operator-(const Vector2& p_other) const
    {
        return Vector2(_x-p_other._x, _y-p_other._y);
    }

    Vector2 operator+(const Vector2& p_other) const
    {
        return Vector2(_x+p_other._x, _y+p_other._y);
    }

    Vector2 operator*(float p_factor) const
    {
        return Vector2(_x*p_factor, _y*p_factor);
    }

    static float Dot(Vector2 p_a, Vector2 p_b)
    {
        return (p_a._x*p_b._x + p_a._y*p_b._y);
    }
};

bool PointInTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P)
{
 // Compute vectors        
 Vector2 v0 = C - A;
 Vector2 v1 = B - A;
 Vector2 v2 = P - A;

 // Compute dot products
 float dot00 = Vector2::Dot(v0, v0);
 float dot01 = Vector2::Dot(v0, v1);
 float dot02 = Vector2::Dot(v0, v2);
 float dot11 = Vector2::Dot(v1, v1);
 float dot12 = Vector2::Dot(v1, v2);

 // Compute barycentric coordinates
 float invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
 float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
 float v = (dot00 * dot12 - dot01 * dot02) * invDenom;

 // Check if point is in triangle
 if(u >= 0 && v >= 0 && (u + v) < 1)
    { return true; } else { return false; }
}


bool PointInRectangle(Vector2 X, Vector2 Y, Vector2 Z, Vector2 W, Vector2 P)
{
 if(PointInTriangle(X,Y,Z,P)) return true;
 if(PointInTriangle(X,Z,W,P)) return true;
 return false;
}

class Matrix3x3
{
public:
    Vector2 _columns[3];

    Vector2 transform(Vector2 p_in)
    {
        return _columns[0] * p_in._x + _columns[1] * p_in._y + _columns[2];
    }
};

class Rectangle
{
public:
    Matrix3x3 _transform;

    Rectangle()
    {}

    void setCorners(Vector2 p_a, Vector2 p_b, Vector2 p_c)
    {
        // create a matrix from the two edges of the rectangle
        Vector2 edgeX = p_b - p_a;
        Vector2 edgeY = p_c - p_a;

        // and then create the inverse of that matrix because we want to 
        // transform points from world coordinates into "rectangle coordinates".
        float scaling = 1/(edgeX._x*edgeY._y - edgeY._x*edgeX._y);

        _transform._columns[0]._x = scaling * edgeY._y;
        _transform._columns[0]._y = - scaling * edgeX._y;
        _transform._columns[1]._x = - scaling * edgeY._x;
        _transform._columns[1]._y = scaling * edgeX._x;

        // the third column is the translation, which also has to be transformed into "rectangle space"
        _transform._columns[2]._x = -p_a._x * _transform._columns[0]._x - p_a._y * _transform._columns[1]._x;
        _transform._columns[2]._y = -p_a._x * _transform._columns[0]._y - p_a._y * _transform._columns[1]._y;
    }

    bool isInside(Vector2 p_point)
    {
        Vector2 test = _transform.transform(p_point);
        return  (test._x>=0)
                && (test._x<=1)
                && (test._y>=0)
                && (test._y<=1);
    }
};

void runTest(float& outA, float& outB)
{
    Rectangle r;
    r.setCorners(Vector2(0,0.5), Vector2(0.5,1), Vector2(0.5,0));

    int numTests = 10000;

    Vector2 points[numTests];

    Vector2 cornerA[numTests];
    Vector2 cornerB[numTests];
    Vector2 cornerC[numTests];
    Vector2 cornerD[numTests];

    bool results[numTests];
    bool resultsB[numTests];

    for (int i=0; i<numTests; ++i)
    {
        points[i]._x = rand() / ((float)RAND_MAX);
        points[i]._y = rand() / ((float)RAND_MAX);

        cornerA[i]._x = rand() / ((float)RAND_MAX);
        cornerA[i]._y = rand() / ((float)RAND_MAX);

        Vector2 edgeA;
        edgeA._x = rand() / ((float)RAND_MAX);
        edgeA._y = rand() / ((float)RAND_MAX);

        Vector2 edgeB;
        edgeB._x = rand() / ((float)RAND_MAX);
        edgeB._y = rand() / ((float)RAND_MAX);

        cornerB[i] = cornerA[i] + edgeA;
        cornerC[i] = cornerA[i] + edgeB;
        cornerD[i] = cornerA[i] + edgeA + edgeB;
    }

    struct timeval start, end;

    gettimeofday(&start, NULL);
    for (int i=0; i<numTests; ++i)
    {
        r.setCorners(cornerA[i], cornerB[i], cornerC[i]);
        results[i] = r.isInside(points[i]);
    }
    gettimeofday(&end, NULL);
    float elapsed = (end.tv_sec - start.tv_sec)*1000;
    elapsed += (end.tv_usec - start.tv_usec)*0.001;
    outA += elapsed;

    gettimeofday(&start, NULL);
    for (int i=0; i<numTests; ++i)
    {
        resultsB[i] = PointInRectangle(cornerA[i], cornerB[i], cornerC[i], cornerD[i], points[i]);
    }
    gettimeofday(&end, NULL);
    elapsed = (end.tv_sec - start.tv_sec)*1000;
    elapsed += (end.tv_usec - start.tv_usec)*0.001;
    outB += elapsed;
}

/*
 * 
 */
int main(int argc, char** argv) 
{
    float a = 0;
    float b = 0;

    for (int i=0; i<5000; i++)
    {
        runTest(a, b);
    }

    std::cout << "Result: " << a << " / " << b << std::endl;

    return 0;
}

รหัสไม่สวย แต่ฉันไม่เห็นข้อบกพร่องที่สำคัญทันที เมื่อใช้รหัสนั้นฉันจะได้ผลลัพธ์ที่ระบุว่าโซลูชันของฉันเร็วเป็นสองเท่าหากสี่เหลี่ยมถูกย้ายระหว่างการตรวจสอบแต่ละครั้ง หากไม่ย้ายรหัสของฉันน่าจะเร็วกว่าห้าเท่า

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


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

ใช่นั่นจะช่วยได้ถ้าคุณคาดหวังว่าคะแนนส่วนใหญ่จะอยู่ใกล้กับกึ่งกลางของรูปสามเหลี่ยม ฉันจินตนาการถึงสถานการณ์เช่นลู่วิ่งรูปสี่เหลี่ยมผืนผ้าที่คุณเช่นกำหนดเส้นทางรูปสี่เหลี่ยมผืนผ้าโดยใช้สี่เหลี่ยมด้านนอกที่ตัวละครต้องอยู่ด้านในและสี่เหลี่ยมผืนผ้าด้านในที่เล็กกว่าที่จะต้องไม่อยู่ ในกรณีนั้นการตรวจสอบทุกครั้งจะอยู่ใกล้กับขอบของสี่เหลี่ยมและการตรวจสอบวงกลมเหล่านั้นอาจทำให้ประสิทธิภาพแย่ลงเท่านั้น จริงอยู่นี่เป็นตัวอย่างที่สร้างขึ้น แต่ฉันบอกว่ามันเป็นสิ่งที่อาจเกิดขึ้นได้จริง
Lars Kokemohr

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