วิธีการคำนวณมุมจากสามจุด? [ปิด]


120

บอกว่าคุณมีสิ่งนี้:

P1 = (x=2, y=50)
P2 = (x=9, y=40)
P3 = (x=5, y=20)

สมมติว่าP1เป็นจุดศูนย์กลางของวงกลม มันมักจะเหมือนกัน ฉันต้องการมุมที่ถูกสร้างขึ้นโดยP2และP3หรือในคำอื่น ๆ P1มุมที่อยู่ถัดไป มุมด้านในให้แม่นยำ มันจะเป็นมุมแหลมเสมอดังนั้นน้อยกว่า -90 องศา

ฉันคิดว่า: ผู้ชายนั่นคือคณิตศาสตร์เรขาคณิตง่ายๆ แต่ตอนนี้ฉันมองหาสูตรมาประมาณ 6 ชั่วโมงแล้วและพบเพียงคนที่พูดถึงสิ่งที่ซับซ้อนของ NASA เช่น arccos และผลิตภัณฑ์สเกลาร์เวกเตอร์ หัวของฉันรู้สึกเหมือนอยู่ในตู้เย็น

กูรูคณิตศาสตร์บางคนที่คิดว่านี่เป็นปัญหาง่ายๆ? ฉันไม่คิดว่าภาษาการเขียนโปรแกรมมีความสำคัญที่นี่ แต่สำหรับผู้ที่คิดว่ามันทำได้: java และ objective-c ฉันต้องการมันสำหรับทั้งสอง แต่ยังไม่ได้ติดแท็กสำหรับสิ่งเหล่านี้

คำตอบ:


87

หากคุณหมายถึงมุมที่ P1 คือจุดยอดของการใช้กฎของโคไซน์ควรใช้งานได้:

ARccOS((P 12 2 + P 13 2 - P 23 2 ) / (2 * P 12 * P 13 ))

โดยที่ P 12คือความยาวของส่วนจาก P1 ถึง P2 คำนวณโดย

sqrt ((P1 x - P2 x ) 2 + (P1 y - P2 y ) 2 )



@Rafa Firenze cos ^ -1 เป็นสัญกรณ์ทั่วไปสำหรับ acos แต่ acos มีความคลุมเครือน้อยกว่า en.wikipedia.org/wiki/Inverse_trigonometric_functions
geon

ฉันจะออกจากการแก้ไขเนื่องจากมันไม่ได้เสียหายอะไรเลย แต่การมีองศาคณิตศาสตร์ / CS / EE cos ^ -1 เป็นสัญกรณ์ที่ใช้บ่อยที่สุด
Lance Roberts

1
มีเพียงไม่กี่ภาษาเท่านั้นที่ใช้เครื่องหมาย "พลังของ" ดังนั้นหากคุณไม่ต้องการเรียกมันว่าอาร์โคสโปรดพิมพ์cos⁻¹ (หากคุณใช้ระบบปฏิบัติการเชิงพาณิชย์ที่ทำให้พิมพ์เลขชี้กำลังได้ยากฉันคาดว่าจะมีแอปพลิเคชันคีย์แคปที่คุณสามารถซื้อได้หรืออาจเป็นปลั๊กอินของเบราว์เซอร์ที่คุณสามารถติดตั้งได้หรือคุณสามารถค้นหาเว็บและคัดลอกและวางได้)
Michael Scheper

1
@MichaelScheper ฉันใช้เครื่องหมายคาเร็ตในความคิดเห็นเท่านั้นที่ html มีข้อ จำกัด แน่นอนฉันจะใช้สัญกรณ์ย่อย / ตัวยกในคำตอบจริงใด ๆ
Lance Roberts

47

มันจะง่ายมากถ้าคุณคิดว่ามันเป็นเวกเตอร์สองตัวตัวหนึ่งจากจุด P1 ถึง P2 และอีกหนึ่งจาก P1 ถึง P3

ดังนั้น:
a = (p1.x - p2.x, p1.y - p2.y)
b = (p1.x - p3.x, p1.y - p3.y)

จากนั้นคุณสามารถพลิกกลับสูตรผลิตภัณฑ์จุด:
dot product
เพื่อให้ได้มุม:
มุมระหว่างสองเวกเตอร์

จำไว้ว่าdot productหมายถึง: a1 * b1 + a2 * b2 (แค่ 2 มิติที่นี่ ... )


1
ขนาด Ah ของเวกเตอร์
Daniel Little

ตรวจสอบโซลูชัน atan2
Luc Boissaye

25

วิธีที่ดีที่สุดในการจัดการกับการคำนวณมุมคือการใช้atan2(y, x)จุดที่กำหนดx, yจะส่งกลับมุมจากจุดนั้นและX+แกนตามจุดกำเนิด

ระบุว่าการคำนวณคือ

double result = atan2(P3.y - P1.y, P3.x - P1.x) -
                atan2(P2.y - P1.y, P2.x - P1.x);

เช่นคุณโดยทั่วไปแปลโดยสองจุด-P1(ในคำอื่น ๆ ที่คุณแปลทุกอย่างเพื่อที่P1จะสิ้นสุดลงในแหล่งกำเนิด) และจากนั้นคุณพิจารณาความแตกต่างของมุมที่สมบูรณ์ของและP3P2

ข้อดีของatan2คือการแสดงวงกลมเต็ม (คุณสามารถรับตัวเลขใดก็ได้ระหว่าง-πถึงπ) โดยที่acosคุณต้องจัดการหลาย ๆ กรณีขึ้นอยู่กับสัญญาณเพื่อคำนวณผลลัพธ์ที่ถูกต้อง

จุดเอกพจน์เดียวสำหรับatan2คือ(0, 0)... หมายความว่าทั้งสองP2และP3ต้องแตกต่างจากP1ในกรณีนั้นไม่สมเหตุสมผลที่จะพูดถึงมุม


ขอบคุณสำหรับคำตอบ. นั่นคือสิ่งที่ฉันกำลังมองหา วิธีแก้ปัญหาง่ายๆและคุณสามารถหามุมทวนเข็มนาฬิกาได้อย่างง่ายดายถ้าฉันเพิ่ม 2pi เมื่อค่าเป็นลบ
Mario

@marcpt: atan2เป็นสิ่งที่จำเป็นสำหรับปัญหานี้ แต่ดูเหมือนว่าคนส่วนใหญ่จะอ่านคำถามนี้ไม่ได้หรือไม่เข้าใจว่าทำไมacosวิธีแก้ปัญหาที่อ้างอิงถึงไม่ดี โชคดีพอสำหรับฉันฉันออกจากเฟส"คนผิดทางอินเทอร์เน็ต" ( xkcd.com/386 ) เมื่อหลายปีก่อนและฉันจะไม่เริ่มต่อสู้เพื่อปกป้องสิ่งที่เห็นได้ชัด :-)
6502

ขอบคุณที่ชี้ให้เห็น แต่คุณจัดการ 3D ด้วยวิธีนี้ได้ไหม
nicoco

1
@nicoco: ในสามมิติคุณกำหนดมุมได้อย่างไร? โดยเฉพาะอย่างยิ่งมุมสามารถเป็นลบหรือมากกว่า pi (180 องศา)? เวกเตอร์ที่ไม่ขนานกันสองตัวใน 3 มิติกำหนดระนาบ แต่สามารถ "มองเห็น" ได้จากสองด้าน: มองจากด้านหนึ่ง A จะปรากฏ "ทางซ้าย" ของ B และจากอีกด้านหนึ่งจะปรากฏ "ทางขวา" .. .
6502

@ 6505 ขอบคุณสำหรับคำตอบฉันโพสต์ก่อนที่จะคิดปัญหาของฉันผ่าน ตอนนี้ฉันคิดออกแล้ว
nicoco

19

ขอยกตัวอย่างใน JavaScript ฉันได้ต่อสู้กับสิ่งนั้นมามาก:

/**
 * Calculates the angle (in radians) between two vectors pointing outward from one center
 *
 * @param p0 first point
 * @param p1 second point
 * @param c center point
 */
function find_angle(p0,p1,c) {
    var p0c = Math.sqrt(Math.pow(c.x-p0.x,2)+
                        Math.pow(c.y-p0.y,2)); // p0->c (b)   
    var p1c = Math.sqrt(Math.pow(c.x-p1.x,2)+
                        Math.pow(c.y-p1.y,2)); // p1->c (a)
    var p0p1 = Math.sqrt(Math.pow(p1.x-p0.x,2)+
                         Math.pow(p1.y-p0.y,2)); // p0->p1 (c)
    return Math.acos((p1c*p1c+p0c*p0c-p0p1*p0p1)/(2*p1c*p0c));
}

โบนัส: ตัวอย่างด้วยผ้าใบ HTML5


5
คุณสามารถทำให้มีประสิทธิภาพมากขึ้นโดยการทำน้อยลงsqrtและกำลังสอง ดูคำตอบของฉันที่นี่ (เขียนด้วย Ruby) หรือในการสาธิตที่อัปเดตนี้(JavaScript)
Phrogz

คุณสามารถใช้ atan2 สำหรับวิธีแก้ปัญหาที่ง่ายกว่า
Luc Boissaye

15

โดยพื้นฐานแล้วสิ่งที่คุณมีคือเวกเตอร์สองตัวเวกเตอร์หนึ่งจาก P1 ถึง P2 และอีกตัวหนึ่งจาก P1 ถึง P3 ดังนั้นสิ่งที่คุณต้องมีคือสูตรคำนวณมุมระหว่างเวกเตอร์สองตัว

ได้ดูที่นี่สำหรับคำอธิบายที่ดีและสูตร

ข้อความแสดงแทน


12

หากคุณคิดว่า P1 เป็นศูนย์กลางของวงกลมคุณกำลังคิดว่าซับซ้อนเกินไป คุณมีสามเหลี่ยมง่ายดังนั้นปัญหาของคุณคือ solveable กับกฎของโคไซน์ ไม่จำเป็นต้องมีการเปลี่ยนแปลงพิกัดเชิงขั้วหรืออย่างใดอย่างหนึ่ง บอกว่าระยะทางคือ P1-P2 = A, P2-P3 = B และ P3-P1 = C:

มุม = arccos ((B ^ 2-A ^ 2-C ^ 2) / 2AC)

สิ่งที่คุณต้องทำคือคำนวณความยาวของระยะทาง A, B และ C ซึ่งสามารถหาได้ง่ายจากพิกัด x และ y ของจุดของคุณและ ทฤษฎีบทของพีทาโกรัส

ความยาว = sqrt ((X2-X1) ^ 2 + (Y2-Y1) ^ 2)


ฉันสับสนเล็กน้อยว่าจะใช้สิ่งนี้ได้อย่างไรในขณะที่คุณปฏิบัติต่อ P1 etc เป็นค่าส่วนบุคคลแทนที่จะเป็น (x, y)
Dominic

@ โดมินิกโทเบียส: P1-P2 = Aไม่ควรอ่านสัญกรณ์เป็น "ในการคำนวณ A ให้ลบ P2 ออกจาก P1" แต่เป็น "ฉันกำหนด A เป็นระยะทางจาก P1 ถึง P2" ซึ่งสามารถคำนวณได้โดยใช้สมการที่สอง ฉันแค่ต้องการกำหนดชวเลขสำหรับระยะทางเพื่อให้อ่านสมการได้ง่ายขึ้น
Treb

8

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

 @Override
 public boolean onTouchEvent(MotionEvent e) {
    float x = e.getX();
    float y = e.getY();
    switch (e.getAction()) {
    case MotionEvent.ACTION_MOVE:
       //find an approximate angle between them.

       float dx = x-cx;
       float dy = y-cy;
       double a=Math.atan2(dy,dx);

       float dpx= mPreviousX-cx;
       float dpy= mPreviousY-cy;
       double b=Math.atan2(dpy, dpx);

       double diff  = a-b;
       this.bearing -= Math.toDegrees(diff);
       this.invalidate();
    }
    mPreviousX = x;
    mPreviousY = y;
    return true;
 }

7

โซลูชันทางเรขาคณิตที่เรียบง่ายมากพร้อมคำอธิบาย

ไม่กี่วันที่ผ่านมาประสบปัญหาเดียวกันและต้องนั่งอ่านหนังสือคณิตศาสตร์ ฉันแก้ปัญหาโดยการรวมและลดความซับซ้อนของสูตรพื้นฐานบางอย่าง


ลองพิจารณารูปนี้ -

มุม

เราอยากรู้ϴดังนั้นเราต้องหาαและβก่อน ตอนนี้สำหรับเส้นตรง -

y = m * x + c

Let- A = (ขวาน Ay) , B = (BX โดย)และO = (วัว Oy) ดังนั้นสำหรับสายOA -

oy = m1 * ox + c   ⇒ c = oy - m1 * ox   ...(eqn-1)

ay = m1 * ax + c   ⇒ ay = m1 * ax + oy - m1 * ox   [from eqn-1]
                   ⇒ ay = m1 * ax + oy - m1 * ox
                   ⇒ m1 = (ay - oy) / (ax - ox)
                   ⇒ tan α = (ay - oy) / (ax - ox)   [m = slope = tan ϴ]   ...(eqn-2)

ในทำนองเดียวกันสำหรับบรรทัดOB -

tan β = (by - oy) / (bx - ox)   ...(eqn-3)

ϴ = β - αตอนนี้เราต้อง ในตรีโกณมิติเรามีสูตร -

tan (β-α) = (tan β + tan α) / (1 - tan β * tan α)   ...(eqn-4)

หลังจากแทนที่ค่าของtan α(จาก eqn-2) และtan b(จาก eqn-3) ใน eqn-4 และใช้การทำให้เข้าใจง่ายเราจะได้รับ -

tan (β-α) = ( (ax-ox)*(by-oy)+(ay-oy)*(bx-ox) ) / ( (ax-ox)*(bx-ox)-(ay-oy)*(by-oy) )

ดังนั้น,

ϴ = β-α = tan^(-1) ( ((ax-ox)*(by-oy)+(ay-oy)*(bx-ox)) / ((ax-ox)*(bx-ox)-(ay-oy)*(by-oy)) )

อย่างนั้นแหละ!


ตอนนี้ใช้รูปต่อไปนี้ -

มุม

วิธี C # หรือ Java นี้จะคำนวณมุม ( ϴ ) -

    private double calculateAngle(double P1X, double P1Y, double P2X, double P2Y,
            double P3X, double P3Y){

        double numerator = P2Y*(P1X-P3X) + P1Y*(P3X-P2X) + P3Y*(P2X-P1X);
        double denominator = (P2X-P1X)*(P1X-P3X) + (P2Y-P1Y)*(P1Y-P3Y);
        double ratio = numerator/denominator;

        double angleRad = Math.Atan(ratio);
        double angleDeg = (angleRad*180)/Math.PI;

        if(angleDeg<0){
            angleDeg = 180+angleDeg;
        }

        return angleDeg;
    }

วิธีนี้ใช้กับสามเหลี่ยมด้านเท่าได้อย่างไร?
Vikrant

1
ตอนนี้คำตอบของคุณใช้ได้ดี มันเป็นปัญหาตรรกะบางอย่างในรหัสของฉันเมื่อสัปดาห์ก่อน
Vikrant

6

ใน Objective-C คุณสามารถทำได้โดย

float xpoint = (((atan2((newPoint.x - oldPoint.x) , (newPoint.y - oldPoint.y)))*180)/M_PI);

หรืออ่านเพิ่มเติมได้ที่นี่


7
เอ่อไม่ มีสามจุดจุดศูนย์กลางไม่ได้อยู่ที่ (0,0) และสิ่งนี้ให้มุมของสามเหลี่ยมมุมฉากไม่ใช่มุมของปลายยอด และชื่อ "xpoint" สำหรับมุมคืออะไร?
Jim Balter

4

คุณพูดถึงมุมที่มีลายเซ็น (-90) ในหลาย ๆ มุมการใช้งานอาจมีสัญญาณ (บวกและลบดูhttp://en.wikipedia.org/wiki/Angle ) หากจุดเป็น (พูด) P2 (1,0), P1 (0,0), P3 (0,1) ดังนั้นมุม P3-P1-P2 จะเป็นบวกตามอัตภาพ (PI / 2) ในขณะที่มุม P2-P1- P3 เป็นลบ การใช้ความยาวของด้านข้างจะไม่แยกความแตกต่างระหว่าง + และ - ดังนั้นหากมีความสำคัญคุณจะต้องใช้เวกเตอร์หรือฟังก์ชันเช่น Math.atan2 (a, b)

มุมยังสามารถขยายได้เกิน 2 * PI และแม้ว่าสิ่งนี้จะไม่เกี่ยวข้องกับคำถามปัจจุบัน แต่ก็สำคัญพอที่ฉันจะต้องเขียนคลาสมุมของตัวเอง (เพื่อให้แน่ใจว่าองศาและเรเดียนไม่ปะปนกัน) คำถามที่ว่า angle1 น้อยกว่า angle2 นั้นขึ้นอยู่กับการกำหนดมุมอย่างไร การตัดสินใจว่าเส้น (-1,0) (0,0) (1,0) จะแสดงเป็น Math.PI หรือ -Math.PI เป็นสิ่งสำคัญเช่นกัน


4

โปรแกรมสาธิตมุมของฉัน

เมื่อเร็ว ๆ นี้ฉันก็มีปัญหาเดียวกันเช่นกัน ... ใน Delphi มันคล้ายกับ Objective-C มาก

procedure TForm1.FormPaint(Sender: TObject);
var ARect: TRect;
    AWidth, AHeight: Integer;
    ABasePoint: TPoint;
    AAngle: Extended;
begin
  FCenter := Point(Width div 2, Height div 2);
  AWidth := Width div 4;
  AHeight := Height div 4;
  ABasePoint := Point(FCenter.X+AWidth, FCenter.Y);
  ARect := Rect(Point(FCenter.X - AWidth, FCenter.Y - AHeight),
    Point(FCenter.X + AWidth, FCenter.Y + AHeight));
  AAngle := ArcTan2(ClickPoint.Y-Center.Y, ClickPoint.X-Center.X) * 180 / pi;
  AngleLabel.Caption := Format('Angle is %5.2f', [AAngle]);
  Canvas.Ellipse(ARect);
  Canvas.MoveTo(FCenter.X, FCenter.Y);
  Canvas.LineTo(FClickPoint.X, FClickPoint.Y);
  Canvas.MoveTo(FCenter.X, FCenter.Y);
  Canvas.LineTo(ABasePoint.X, ABasePoint.Y);
end;

2

นี่คือวิธี C # ในการกลับมุม (0-360) ทวนเข็มนาฬิกาจากแนวนอนสำหรับจุดบนวงกลม

    public static double GetAngle(Point centre, Point point1)
    {
        // Thanks to Dave Hill
        // Turn into a vector (from the origin)
        double x = point1.X - centre.X;
        double y = point1.Y - centre.Y;
        // Dot product u dot v = mag u * mag v * cos theta
        // Therefore theta = cos -1 ((u dot v) / (mag u * mag v))
        // Horizontal v = (1, 0)
        // therefore theta = cos -1 (u.x / mag u)
        // nb, there are 2 possible angles and if u.y is positive then angle is in first quadrant, negative then second quadrant
        double magnitude = Math.Sqrt(x * x + y * y);
        double angle = 0;
        if(magnitude > 0)
            angle = Math.Acos(x / magnitude);

        angle = angle * 180 / Math.PI;
        if (y < 0)
            angle = 360 - angle;

        return angle;
    }

ไชโยพอล


2

function p(x, y) {return {x,y}}

function normaliseToInteriorAngle(angle) {
	if (angle < 0) {
		angle += (2*Math.PI)
	}
	if (angle > Math.PI) {
		angle = 2*Math.PI - angle
	}
	return angle
}

function angle(p1, center, p2) {
	const transformedP1 = p(p1.x - center.x, p1.y - center.y)
	const transformedP2 = p(p2.x - center.x, p2.y - center.y)

	const angleToP1 = Math.atan2(transformedP1.y, transformedP1.x)
	const angleToP2 = Math.atan2(transformedP2.y, transformedP2.x)

	return normaliseToInteriorAngle(angleToP2 - angleToP1)
}

function toDegrees(radians) {
	return 360 * radians / (2 * Math.PI)
}

console.log(toDegrees(angle(p(-10, 0), p(0, 0), p(0, -10))))


0

มีคำตอบง่ายๆสำหรับเรื่องนี้โดยใช้คณิตศาสตร์ระดับมัธยมศึกษาตอนปลาย ..

สมมติว่าคุณมี 3 คะแนน

เพื่อหามุมจากจุด A ถึง B

angle = atan2(A.x - B.x, B.y - A.y)

เพื่อให้ได้มุมจากจุด B ถึง C

angle2 = atan2(B.x - C.x, C.y - B.y)

Answer = 180 + angle2 - angle
If (answer < 0){
    return answer + 360
}else{
    return answer
}

ฉันเพิ่งใช้รหัสนี้ในโครงการล่าสุดที่ฉันสร้างขึ้นเปลี่ยน B เป็น P1 .. คุณอาจลบ "180 +" ได้เช่นกันหากต้องการ


-1

คำตอบอื่น ๆ ดูเหมือนจะครอบคลุมทุกอย่างที่จำเป็นดังนั้นฉันต้องการเพิ่มสิ่งนี้หากคุณใช้ JMonkeyEngine:

Vector3f.angleBetween(otherVector)

นั่นคือสิ่งที่ฉันมาที่นี่เพื่อมองหา :)


-2
      Atan2        output in degrees
       PI/2              +90
         |                | 
         |                |    
   PI ---.--- 0   +180 ---.--- 0       
         |                |
         |                |
       -PI/2             +270

public static double CalculateAngleFromHorizontal(double startX, double startY, double endX, double endY)
{
    var atan = Math.Atan2(endY - startY, endX - startX); // Angle in radians
    var angleDegrees = atan * (180 / Math.PI);  // Angle in degrees (can be +/-)
    if (angleDegrees < 0.0)
    {
        angleDegrees = 360.0 + angleDegrees;
    }
    return angleDegrees;
}

// Angle from point2 to point 3 counter clockwise
public static double CalculateAngle0To360(double centerX, double centerY, double x2, double y2, double x3, double y3)
{
    var angle2 = CalculateAngleFromHorizontal(centerX, centerY, x2, y2);
    var angle3 = CalculateAngleFromHorizontal(centerX, centerY, x3, y3);
    return (360.0 + angle3 - angle2)%360;
}

// Smaller angle from point2 to point 3
public static double CalculateAngle0To180(double centerX, double centerY, double x2, double y2, double x3, double y3)
{
    var angle = CalculateAngle0To360(centerX, centerY, x2, y2, x3, y3);
    if (angle > 180.0)
    {
        angle = 360 - angle;
    }
    return angle;
}

}

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