簡體   English   中英

在給定角度的矩形上查找點

[英]Finding points on a rectangle at a given angle

我試圖用給定的角度 (Theta) 在矩形對象中繪制漸變,其中漸變的末端接觸矩形的周長。

圖

我認為使用切線會起作用,但我無法解決問題。 有沒有一個簡單的算法我只是想念?

最終結果

因此,這將是 (angle, RectX1, RectX2, RectY1, RectY2) 的函數。 我希望它以 [x1, x2, y1, y2] 的形式返回,以便漸變將繪制在正方形上。 在我的問題中,如果原點為 0,則 x2 = -x1 且 y2 = -y1。 但它並不總是在原點上。

讓我們將ab稱為矩形邊,以及(x0,y0)矩形中心的坐標。

您需要考慮四個區域:

替代文字

Region    from               to                 Where
    ====================================================================
       1      -arctan(b/a)       +arctan(b/a)       Right green triangle
       2      +arctan(b/a)        π-arctan(b/a)     Upper yellow triangle
       3       π-arctan(b/a)      π+arctan(b/a)     Left green triangle
       4       π+arctan(b/a)     -arctan(b/a)       Lower yellow triangle

用一點三角函數,我們可以得到每個區域中你想要的交點的坐標。

替代文字

所以Z0是區域 1 和 3 的交點的表達式
Z1是區域 2 和區域 4 交點的表達式

所需的線從 (X0,Y0) 到 Z0 或 Z1,具體取決於區域。 所以記住 Tan(φ)=Sin(φ)/Cos(φ)

Lines in regions      Start                   End
    ======================================================================
       1 and 3           (X0,Y0)      (X0 + a/2 , (a/2 * Tan(φ))+ Y0
       2 and 4           (X0,Y0)      (X0 + b/(2* Tan(φ)) , b/2 + Y0)

請注意每個象限中 Tan(φ) 的符號,並且角度始終從正 x 軸反時針方向測量。

哼!

好的,嗬! ,我終於得到了這個。

注意:我基於 belisarius 的精彩回答。 如果你喜歡這個,請也喜歡他的。 我所做的只是將他所說的變成了代碼。

這是它在 Objective-C 中的樣子。 它應該足夠簡單,可以轉換為您喜歡的任何語言。

+ (CGPoint) edgeOfView: (UIView*) view atAngle: (float) theta
{
    // Move theta to range -M_PI .. M_PI
    const double twoPI = M_PI * 2.;
    while (theta < -M_PI)
    {
        theta += twoPI;
    }

    while (theta > M_PI)
    {
        theta -= twoPI;
    }

    // find edge ofview
    // Ref: http://stackoverflow.com/questions/4061576/finding-points-on-a-rectangle-at-a-given-angle
    float aa = view.bounds.size.width;                                          // "a" in the diagram
    float bb = view.bounds.size.height;                                         // "b"

    // Find our region (diagram)
    float rectAtan = atan2f(bb, aa);
    float tanTheta = tan(theta);

    int region;
    if ((theta > -rectAtan)
    &&  (theta <= rectAtan) )
    {
        region = 1;
    }
    else if ((theta >  rectAtan)
    &&       (theta <= (M_PI - rectAtan)) )
    {
        region = 2;
    }
    else if ((theta >   (M_PI - rectAtan))
    ||       (theta <= -(M_PI - rectAtan)) )
    {
        region = 3;
    }
    else
    {
        region = 4;
    }

    CGPoint edgePoint = view.center;
    float xFactor = 1;
    float yFactor = 1;

    switch (region)
    {
        case 1: yFactor = -1;       break;
        case 2: yFactor = -1;       break;
        case 3: xFactor = -1;       break;
        case 4: xFactor = -1;       break;
    }

    if ((region == 1)
    ||  (region == 3) )
    {
        edgePoint.x += xFactor * (aa / 2.);                                     // "Z0"
        edgePoint.y += yFactor * (aa / 2.) * tanTheta;
    }
    else                                                                        // region 2 or 4
    {
        edgePoint.x += xFactor * (bb / (2. * tanTheta));                        // "Z1"
        edgePoint.y += yFactor * (bb /  2.);
    }

    return edgePoint;
}

此外,這是我創建的一個小測試視圖,以驗證它是否有效。 創建此視圖並將其放置在某個位置,它將使另一個小視圖繞過邊緣。

@interface DebugEdgeView()
{
    int degrees;
    UIView *dotView;
    NSTimer *timer;
}

@end

@implementation DebugEdgeView

- (void) dealloc
{
    [timer invalidate];
}


- (id) initWithFrame: (CGRect) frame
{
    self = [super initWithFrame: frame];
    if (self)
    {
        self.backgroundColor = [[UIColor magentaColor] colorWithAlphaComponent: 0.25];
        degrees = 0;
        self.clipsToBounds = NO;

        // create subview dot
        CGRect dotRect = CGRectMake(frame.size.width / 2., frame.size.height / 2., 20, 20);
        dotView = [[DotView alloc] initWithFrame: dotRect];
        dotView.backgroundColor = [UIColor magentaColor];
        [self addSubview: dotView];

        // move it around our edges
        timer = [NSTimer scheduledTimerWithTimeInterval: (5. / 360.)
                                                 target: self
                                               selector: @selector(timerFired:)
                                               userInfo: nil
                                                repeats: YES];
    }

    return self;
}


- (void) timerFired: (NSTimer*) timer
{
    float radians = ++degrees * M_PI / 180.;
    if (degrees > 360)
    {
        degrees -= 360;
    }

    dispatch_async(dispatch_get_main_queue(), ^{
        CGPoint edgePoint = [MFUtils edgeOfView: self atAngle: radians];
        edgePoint.x += (self.bounds.size.width  / 2.) - self.center.x;
        edgePoint.y += (self.bounds.size.height / 2.) - self.center.y;
        dotView.center = edgePoint;
    });
}

@end

Javascript 版本:

 function edgeOfView(rect, deg) { var twoPI = Math.PI*2; var theta = deg * Math.PI / 180; while (theta < -Math.PI) { theta += twoPI; } while (theta > Math.PI) { theta -= twoPI; } var rectAtan = Math.atan2(rect.height, rect.width); var tanTheta = Math.tan(theta); var region; if ((theta > -rectAtan) && (theta <= rectAtan)) { region = 1; } else if ((theta > rectAtan) && (theta <= (Math.PI - rectAtan))) { region = 2; } else if ((theta > (Math.PI - rectAtan)) || (theta <= -(Math.PI - rectAtan))) { region = 3; } else { region = 4; } var edgePoint = {x: rect.width/2, y: rect.height/2}; var xFactor = 1; var yFactor = 1; switch (region) { case 1: yFactor = -1; break; case 2: yFactor = -1; break; case 3: xFactor = -1; break; case 4: xFactor = -1; break; } if ((region === 1) || (region === 3)) { edgePoint.x += xFactor * (rect.width / 2.); // "Z0" edgePoint.y += yFactor * (rect.width / 2.) * tanTheta; } else { edgePoint.x += xFactor * (rect.height / (2. * tanTheta)); // "Z1" edgePoint.y += yFactor * (rect.height / 2.); } return edgePoint; };

按照您的圖片,我將假設矩形以 (0,0) 為中心,並且右上角是 (w,h)。 然后連接 (0,0) 到 (w,h) 的線與 X 軸形成一個角度 φ,其中 tan(φ) = h/w。

假設 θ > φ,我們正在尋找您繪制的線與矩形頂部邊緣相交的點 (x,y)。 然后 y/x = tan(θ)。 我們知道 y=h 所以,求解 x,我們得到 x = h/tan(θ)。

如果 θ < φ,則該線在 (x,y) 處與矩形的右邊緣相交。 這一次,我們知道 x=w,所以 y = tan(θ)*w。

Find the CGPoint on a UIView rectangle by a直線與中心點以給定角度相交,對此問題有一個很好的(更具編程性的iOS / Objective-C)答案,包括以下步驟:

  1. 假設角度大於或等於 0 且小於 2*π,從 0(東)逆時針方向。
  2. 獲取與矩形右邊緣交點的 y 坐標 [tan(angle)*width/2]。
  3. 檢查這個y坐標是否在矩形框內(絕對值小於等於高度的一半)。
  4. 如果 y 交點在矩形內,則如果角度小於 π/2 或大於 3π/2,則選擇右邊緣(寬度/2,-y 坐標)。 否則選擇左邊緣 (-width/2, y coord)。
  5. 如果右邊緣交點的 y 坐標越界,則計算與底邊緣交點的 x 坐標 [高度的一半/tan(角度)]。
  6. 接下來確定您想要頂部邊緣還是底部邊緣。 如果角度小於 π,我們需要底邊 (x, -half of height)。 否則,我們需要頂部邊緣(-x 坐標,高度的一半)。
  7. 然后(如果幀的中心不是 0,0),將點偏移幀的實際中心。

對於 Java,LibGDX。 我讓角度是兩倍以提高精度。

public static Vector2 projectToRectEdge(double angle, float width, float height, Vector2 out)
{
    return projectToRectEdgeRad(Math.toRadians(angle), width, height, out);
}

public static Vector2 projectToRectEdgeRad(double angle, float width, float height, Vector2 out)
{
    float theta = negMod((float)angle + MathUtils.PI, MathUtils.PI2) - MathUtils.PI;

    float diag = MathUtils.atan2(height, width);
    float tangent = (float)Math.tan(angle);

    if (theta > -diag && theta <= diag)
    {
        out.x = width / 2f;
        out.y = width / 2f * tangent;
    }
    else if(theta > diag && theta <= MathUtils.PI - diag)
    {
        out.x = height / 2f / tangent;
        out.y = height / 2f;
    }
    else if(theta > MathUtils.PI - diag && theta <= MathUtils.PI + diag)
    {
        out.x = -width / 2f;
        out.y = -width / 2f * tangent;
    }
    else
    {
        out.x = -height / 2f / tangent;
        out.y = -height / 2f;
    }

    return out;
}

虛幻引擎 4 (UE4) C++ 版本。

注意:這是基於 Olie's Code 的 基於貝利撒留的回答 如果這對您有幫助,請給這些人點贊。

變化:使用 UE4 語法和函數,Angle 被否定。

標題

UFUNCTION(BlueprintCallable, meta = (DisplayName = "Project To Rectangle Edge (Radians)"), Category = "Math|Geometry")
static void ProjectToRectangleEdgeRadians(FVector2D Extents, float Angle, FVector2D & EdgeLocation);

代碼

void UFunctionLibrary::ProjectToRectangleEdgeRadians(FVector2D Extents, float Angle, FVector2D & EdgeLocation)
{
    // Move theta to range -M_PI .. M_PI. Also negate the angle to work as expected.
    float theta = FMath::UnwindRadians(-Angle);

    // Ref: http://stackoverflow.com/questions/4061576/finding-points-on-a-rectangle-at-a-given-angle
    float a = Extents.X; // "a" in the diagram | Width
    float b = Extents.Y; // "b"                | Height

    // Find our region (diagram)
    float rectAtan = FMath::Atan2(b, a);
    float tanTheta = FMath::Tan(theta);

    int region;
    if ((theta > -rectAtan) && (theta <= rectAtan))
    {
        region = 1;
    }
    else if ((theta > rectAtan) && (theta <= (PI - rectAtan)))
    {
        region = 2;
    }
    else if ((theta > (PI - rectAtan)) || (theta <= -(PI - rectAtan)))
    {
        region = 3;
    }
    else
    {
        region = 4;
    }

    float xFactor = 1.f;
    float yFactor = 1.f;

    switch (region)
    {
        case 1: yFactor = -1; break;
        case 2: yFactor = -1; break;
        case 3: xFactor = -1; break;
        case 4: xFactor = -1; break;
    }

    EdgeLocation = FVector2D(0.f, 0.f); // This rese is nessesary, UE might re-use otherwise. 

    if (region == 1 || region == 3)
    {
        EdgeLocation.X += xFactor * (a / 2.f);              // "Z0"
        EdgeLocation.Y += yFactor * (a / 2.f) * tanTheta;
    }
    else // region 2 or 4
    {
        EdgeLocation.X += xFactor * (b / (2.f * tanTheta)); // "Z1"
        EdgeLocation.Y += yFactor * (b / 2.f);
    }
}

蟒蛇

import math
import matplotlib.pyplot as plt

twoPI = math.pi * 2.0
PI = math.pi

def get_points(width, height, theta):
    theta %= twoPI

    aa = width
    bb = height

    rectAtan = math.atan2(bb,aa)
    tanTheta = math.tan(theta)

    xFactor = 1
    yFactor = 1
    
    # determine regions
    if theta > twoPI-rectAtan or theta <= rectAtan:
        region = 1
    elif theta > rectAtan and theta <= PI-rectAtan:
        region = 2

    elif theta > PI - rectAtan and theta <= PI + rectAtan:
        region = 3
        xFactor = -1
        yFactor = -1
    elif theta > PI + rectAtan and theta < twoPI - rectAtan:
        region = 4
        xFactor = -1
        yFactor = -1
    else:
        print(f"region assign failed : {theta}")
        raise
    
    # print(region, xFactor, yFactor)
    edgePoint = [0,0]
    ## calculate points
    if (region == 1) or (region == 3):
        edgePoint[0] += xFactor * (aa / 2.)
        edgePoint[1] += yFactor * (aa / 2.) * tanTheta
    else:
        edgePoint[0] += xFactor * (bb / (2. * tanTheta))
        edgePoint[1] += yFactor * (bb /  2.)

    return region, edgePoint

l_x = []
l_y = []
theta = 0
for _ in range(10000):
    r, (x, y) = get_points(600,300, theta)
    l_x.append(x)
    l_y.append(y)
    theta += (0.01 / PI)

    if _ % 100 == 0:
        print(r, x,y)

plt.plot(l_x, l_y)
plt.show()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM