简体   繁体   English

在给定角度的矩形上查找点

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

I'm trying to draw a gradient in a rectangle object, with a given angle (Theta), where the ends of the gradient are touching the perimeter of the rectangle.我试图用给定的角度 (Theta) 在矩形对象中绘制渐变,其中渐变的末端接触矩形的周长。

图

I thought that using tangent would work, but I'm having trouble getting the kinks out.我认为使用切线会起作用,但我无法解决问题。 Is there an easy algorithm that I am just missing?有没有一个简单的算法我只是想念?

End Result最终结果

So, this is going to be a function of (angle, RectX1, RectX2, RectY1, RectY2).因此,这将是 (angle, RectX1, RectX2, RectY1, RectY2) 的函数。 I want it returned in the form of [x1, x2, y1, y2], so that the gradient will draw across the square.我希望它以 [x1, x2, y1, y2] 的形式返回,以便渐变将绘制在正方形上。 In my problem, if the origin is 0, then x2 = -x1 and y2 = -y1.在我的问题中,如果原点为 0,则 x2 = -x1 且 y2 = -y1。 But it's not always going to be on the origin.但它并不总是在原点上。

Let's call a and b your rectangle sides, and (x0,y0) the coordinates of your rectangle center.让我们将ab称为矩形边,以及(x0,y0)矩形中心的坐标。

You have four regions to consider:您需要考虑四个区域:

替代文字

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

With a little of trigonometry-fu, we can get the coordinates for your desired intersection in each region.用一点三角函数,我们可以得到每个区域中你想要的交点的坐标。

替代文字

So Z0 is the expression for the intersection point for regions 1 and 3所以Z0是区域 1 和 3 的交点的表达式
And Z1 is the expression for the intersection point for regions 2 and 4 Z1是区域 2 和区域 4 交点的表达式

The desired lines pass from (X0,Y0) to Z0 or Z1 depending the region.所需的线从 (X0,Y0) 到 Z0 或 Z1,具体取决于区域。 So remembering that Tan(φ)=Sin(φ)/Cos(φ)所以记住 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)

Just be aware of the signs of Tan(φ) in each quadrant, and that the angle is always measured from THE POSITIVE x axis ANTICLOCKWISE.请注意每个象限中 Tan(φ) 的符号,并且角度始终从正 x 轴反时针方向测量。

HTH!哼!

Ok, whew!好的,嗬! , I finally got this one. ,我终于得到了这个。

NOTE: I based this off of belisarius's awesome answer.注意:我基于 belisarius 的精彩回答。 If you like this, please like his, too.如果你喜欢这个,请也喜欢他的。 All I did was turn what he said into code.我所做的只是将他所说的变成了代码。

Here's what it looks like in Objective-C.这是它在 Objective-C 中的样子。 It should be simple enough to convert to whatever your favorite language is.它应该足够简单,可以转换为您喜欢的任何语言。

+ (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;
}

In addition, here's a little test-view I created to verify that it works.此外,这是我创建的一个小测试视图,以验证它是否有效。 Create this view and put it somewhere, it will make another little view scoot around the edge.创建此视图并将其放置在某个位置,它将使另一个小视图绕过边缘。

@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 version: 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; };

Following your picture, I'm going to assume that the rectangle is centered at (0,0), and that the upper right corner is (w,h).按照您的图片,我将假设矩形以 (0,0) 为中心,并且右上角是 (w,h)。 Then the line connecting (0,0) to (w,h) forms an angle φ with the X axis, where tan(φ) = h/w.然后连接 (0,0) 到 (w,h) 的线与 X 轴形成一个角度 φ,其中 tan(φ) = h/w。

Assuming that θ > φ, we are looking for the point (x,y) where the line that you have drawn intersects the top edge of the rectangle.假设 θ > φ,我们正在寻找您绘制的线与矩形顶部边缘相交的点 (x,y)。 Then y/x = tan(θ).然后 y/x = tan(θ)。 We know that y=h so, solving for x, we get x = h/tan(θ).我们知道 y=h 所以,求解 x,我们得到 x = h/tan(θ)。

If θ < φ, the line intersects with the right edge of the rectangle at (x,y).如果 θ < φ,则该线在 (x,y) 处与矩形的右边缘相交。 This time, we know that x=w, so y = tan(θ)*w.这一次,我们知道 x=w,所以 y = tan(θ)*w。

There's a good (more programmatic iOS / Objective-C) answer to this question at Find the CGPoint on a UIView rectangle intersected by a straight line at a given angle from the center point involving the following steps:Find the CGPoint on a UIView rectangle by a直线与中心点以给定角度相交,对此问题有一个很好的(更具编程性的iOS / Objective-C)答案,包括以下步骤:

  1. Assume that the angle is greater than or equal to 0 and less than 2*π, going counterclockwise from 0 (East).假设角度大于或等于 0 且小于 2*π,从 0(东)逆时针方向。
  2. Get the y coordinate of the intersection with the right edge of the rectangle [tan(angle)*width/2].获取与矩形右边缘交点的 y 坐标 [tan(angle)*width/2]。
  3. Check whether this y coordinate is in the rectangle frame (absolute value less than or equal to half the height).检查这个y坐标是否在矩形框内(绝对值小于等于高度的一半)。
  4. If the y intersection is in the rectangle, then if the angle is less than π/2 or greater than 3π/2 choose the right edge (width/2, -y coord).如果 y 交点在矩形内,则如果角度小于 π/2 或大于 3π/2,则选择右边缘(宽度/2,-y 坐标)。 Otherwise choose the left edge (-width/2, y coord).否则选择左边缘 (-width/2, y coord)。
  5. If the y coordinate of the right edge intersection was out-of-bounds, calculate the x coordinate of the intersection with the bottom edge [half the height/tan(angle)].如果右边缘交点的 y 坐标越界,则计算与底边缘交点的 x 坐标 [高度的一半/tan(角度)]。
  6. Next determine whether you want the top edge or the bottom edge.接下来确定您想要顶部边缘还是底部边缘。 If the angle is less than π, we want the bottom edge (x, -half the height).如果角度小于 π,我们需要底边 (x, -half of height)。 Otherwise, we want the top edge (-x coord, half the height).否则,我们需要顶部边缘(-x 坐标,高度的一半)。
  7. Then (if the center of the frame is not 0,0), offset the point by the actual center of the frame.然后(如果帧的中心不是 0,0),将点偏移帧的实际中心。

For Java, LibGDX.对于 Java,LibGDX。 I've let the angle be a double to increase precision.我让角度是两倍以提高精度。

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;
}

Unreal Engine 4 (UE4) C++ Version.虚幻引擎 4 (UE4) C++ 版本。

Note: This is based off of Olie's Code .注意:这是基于 Olie's Code 的 Based on Belisarius's Answer .基于贝利撒留的回答 Give those guys upvotes if this helps you.如果这对您有帮助,请给这些人点赞。

Changes: Uses UE4 syntax and functions, and Angle is negated.变化:使用 UE4 语法和函数,Angle 被否定。

Header标题

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

Code代码

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);
    }
}

PYTHON蟒蛇

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