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

发布于 2024-09-29 18:37:05 字数 337 浏览 2 评论 0原文

我试图在一个矩形对象中绘制一个具有给定角度(Theta)的渐变,其中渐变的末端接触矩形的周边。

Graph

我认为使用切线可以,但我无法解决问题。有没有我缺少的简单算法?

最终结果

因此,这将是 (angle, 矩形 X1, 矩形 X2, 矩形 Y1, 矩形 Y2) 的函数。我希望它以 [x1, x2, y1, y2] 的形式返回,以便渐变将绘制在正方形上。 在我的问题中,如果原点为 0,则 x2 = -x1 且 y2 = -y1。但它并不总是在原点。

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.

Graph

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). I want it returned in the form of [x1, x2, y1, y2], so that the gradient will draw across the square.
In my problem, if the origin is 0, then x2 = -x1 and y2 = -y1. But it's not always going to be on the origin.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

梦里泪两行 2024-10-06 18:37:05

我们将 ab 称为矩形边,将 (x0,y0) 称为矩形中心的坐标。

您需要考虑四个区域:

alt text

    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

通过一点三角函数,我们可以获得您想要的交叉点的坐标在每个地区。

alt text

因此 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 轴逆时针方向测量。

哈!

Let's call a and b your rectangle sides, and (x0,y0) the coordinates of your rectangle center.

You have four regions to consider:

alt text

    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.

alt text

So Z0 is the expression for the intersection point for regions 1 and 3
And Z1 is the expression for the intersection point for regions 2 and 4

The desired lines pass from (X0,Y0) to Z0 or Z1 depending the region. So remembering that 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.

HTH!

知你几分 2024-10-06 18:37:05

好吧,哇!,我终于得到了这个。

注意:我是根据贝利撒留的精彩答案得出这个结论的。如果你喜欢这个,请也喜欢他的。我所做的就是把他说的话变成代码。

这是它在 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

Ok, whew!, I finally got this one.

NOTE: I based this off of belisarius's awesome answer. 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. 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
哭了丶谁疼 2024-10-06 18:37:05

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

Javascript version:

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

厌味 2024-10-06 18:37:05

根据您的图片,我假设矩形以 (0,0) 为中心,右上角为 (w,h)。然后,连接 (0,0) 到 (w,h) 的线与 X 轴形成角度 φ,其中 tan(φ) = h/w。

假设θ>0。 φ,我们正在寻找您所绘制的线与矩形顶边相交的点 (x,y)。那么 y/x = tan(θ)。我们知道 y=h,因此求解 x,我们得到 x = h/tan(θ)。

如果 θ < φ,直线与矩形右边缘相交于 (x,y)。这次,我们知道 x=w,因此 y = tan(θ)*w。

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). Then the line connecting (0,0) to (w,h) forms an angle φ with the X axis, where 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. Then y/x = tan(θ). We know that y=h so, solving for x, we get x = h/tan(θ).

If θ < φ, the line intersects with the right edge of the rectangle at (x,y). This time, we know that x=w, so y = tan(θ)*w.

忘你却要生生世世 2024-10-06 18:37:05

这个问题有一个很好的(更具编程性的 iOS / Objective-C)答案 查找 UIView 矩形上与中心点以给定角度的直线相交的 CGPoint 涉及以下步骤:

  1. 假设角度更大大于或等于 0 且小于 2*π,从 0(东)开始逆时针旋转。
  2. 获取与矩形右边缘交点的 y 坐标 [tan(角度)*宽度/2]。
  3. 检查该y坐标是否在矩形框内(绝对值小于等于高度的一半)。
  4. 如果 y 交点位于矩形内,则如果角度小于 π/2 或大于 3π/2,则选择右边缘(宽度/2,-y 坐标)。否则选择左边缘(-width/2,y 坐标)。
  5. 如果右边缘交点的 y 坐标超出范围,则计算与下边缘交点的 x 坐标 [高度的一半/tan(角度)]。
  6. 接下来确定您想要顶部边缘还是底部边缘。如果角度小于 π,我们需要底边(x,-高度的一半)。否则,我们需要顶部边缘(-x 坐标,高度的一半)。
  7. 然后(如果框架的中心不是 0,0),将该点偏移框架的实际中心。

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:

  1. Assume that the angle is greater than or equal to 0 and less than 2*π, going counterclockwise from 0 (East).
  2. Get the y coordinate of the intersection with the right edge of the rectangle [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).
  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). Otherwise choose the left edge (-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)].
  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). Otherwise, we want the top edge (-x coord, half the height).
  7. Then (if the center of the frame is not 0,0), offset the point by the actual center of the frame.
时光倒影 2024-10-06 18:37:05

Unity C#(从 Winter 的 Java 代码转换而来)

    public Vector2 DetermineRectangleEdge(float aDegrees, float aWidth, float aHeight) {

        if (aDegrees < -90)
            aDegrees += 360f;

        float ANGLE = Mathf.Deg2Rad * aDegrees;
        float diag = Mathf.Atan2(aHeight, aWidth);
        float tangent = Mathf.Tan(ANGLE);

        Vector2 OUT = Vector2.zero;

        if (ANGLE > -diag && ANGLE <= diag)
        {
            OUT.x = aWidth / 2f;
            OUT.y = aWidth / 2f * tangent;
            _ObjectRectTransform.sizeDelta = _VerticalSize;
        }
        else if(ANGLE > diag && ANGLE <= Mathf.PI - diag)
        {
            OUT.x = aHeight / 2f / tangent;
            OUT.y = aHeight / 2f;
            _ObjectRectTransform.sizeDelta = _HorizontalSize;
        }
        else if(ANGLE > Mathf.PI - diag && ANGLE <= Mathf.PI + diag)
        {
            OUT.x = -aWidth / 2f;
            OUT.y = -aWidth / 2f * tangent;
            _ObjectRectTransform.sizeDelta = _VerticalSize;
        }
        else
        {
            OUT.x = -aHeight / 2f / tangent;
            OUT.y = -aHeight / 2f;
            _ObjectRectTransform.sizeDelta = _HorizontalSize;
        }

        return OUT;
        
    }  

Unity C# (Converted from Winter's Java code)

    public Vector2 DetermineRectangleEdge(float aDegrees, float aWidth, float aHeight) {

        if (aDegrees < -90)
            aDegrees += 360f;

        float ANGLE = Mathf.Deg2Rad * aDegrees;
        float diag = Mathf.Atan2(aHeight, aWidth);
        float tangent = Mathf.Tan(ANGLE);

        Vector2 OUT = Vector2.zero;

        if (ANGLE > -diag && ANGLE <= diag)
        {
            OUT.x = aWidth / 2f;
            OUT.y = aWidth / 2f * tangent;
            _ObjectRectTransform.sizeDelta = _VerticalSize;
        }
        else if(ANGLE > diag && ANGLE <= Mathf.PI - diag)
        {
            OUT.x = aHeight / 2f / tangent;
            OUT.y = aHeight / 2f;
            _ObjectRectTransform.sizeDelta = _HorizontalSize;
        }
        else if(ANGLE > Mathf.PI - diag && ANGLE <= Mathf.PI + diag)
        {
            OUT.x = -aWidth / 2f;
            OUT.y = -aWidth / 2f * tangent;
            _ObjectRectTransform.sizeDelta = _VerticalSize;
        }
        else
        {
            OUT.x = -aHeight / 2f / tangent;
            OUT.y = -aHeight / 2f;
            _ObjectRectTransform.sizeDelta = _HorizontalSize;
        }

        return OUT;
        
    }  
眼趣 2024-10-06 18:37:05

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

For 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;
}
滴情不沾 2024-10-06 18:37:05

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

注意:这是基于 Olie 的代码。基于贝利撒留的答案。如果这对您有帮助,请给这些人点赞。

变化:使用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);
    }
}

Unreal Engine 4 (UE4) C++ Version.

Note: This is based off of 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.

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);
    }
}
霊感 2024-10-06 18:37:05

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()

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()
平定天下 2024-10-06 18:37:05
const toDegrees = (angle) => angle * (180 / Math.PI);
const toRadians = (angle) => angle * (Math.PI / 180);

 class Theta {
  degrees;
  radians;
  constructor({ degrees, radians }) {
    if (degrees === undefined && radians === undefined)
      throw new Error("degrees or radians must be provided");
    if (degrees !== undefined) {
      this.degrees = degrees;
      this.radians = toRadians(degrees);
    }
    if (radians !== undefined) {
      this.radians = radians;
      this.degrees = toDegrees(radians);
    }
  }

  add(deg) {
    return new Theta({ degrees: this.degrees + deg });
  }

  sub(deg) {
    return new Theta({ degrees: this.degrees - deg });
  }

  static Degrees(deg) {
    return new Theta({ degrees: deg });
  }

  static Radians(rad) {
    return new Theta({ radians: rad });
  }
}
/**
 @link https://zh.wikipedia.org/wiki/%E4%B8%89%E8%A7%92%E5%87%BD%E6%95%B0#%E4%BB%A5%E7%9B%B4%E8%A7%92%E5%9D%90%E6%A0%87%E7%B3%BB%E4%BE%86%E5%AE%9A%E4%B9%89
 *
 * */
function getRX(theta, x) {
  return Math.abs(x / Math.cos(theta.radians));
}
function getRY(theta, y) {
  return Math.abs(y / Math.sin(theta.radians));
}

 function cartesian2Polar(x, y) {
  const r = Math.sqrt(x * x + y * y);
  const theta = Theta.Radians(Math.atan(y / x));
  return { r, theta };
}

 function polar2Cartesian(r, theta) {
  const radians = theta.radians;
  const x = r * Math.cos(radians);
  const y = r * Math.sin(radians);
  return { x, y };
}

 function getCrossPoint(rect, theta) {
  const x = rect.width / 2;
  const y = rect.height / 2;

  const r1 = getRX(theta, x);
  const r2 = getRY(theta, y);
  const r = Math.min(r1, r2);
  const point = polar2Cartesian(r, theta);

  // move back to real coordinate
  point.x += rect.width / 2 + rect.x;
  point.y += rect.height / 2 + rect.y;


  return {
    point,
    radius: r,
  };
}

 function getCross(rect, theta) {
  const head = getCrossPoint(rect, theta).point;
  const tail = getCrossPoint(rect, Theta.Degrees(theta.degrees + 180)).point;

  return {
    head,
    tail,
  };
}



function main() {

    const width = 300 
    const height = 300 


  function drawCanvas() {
    const canvas = document.getElementById('canvas');
    canvas.width = width
    canvas.height = height
    const ctx = canvas.getContext('2d');
    const offset = -270
    const rect = {
        x: 10,
        y: 50,
        width: 280,
        height: 200,
    }
    const toRad = (deg) => toRadians(deg + offset)


    return {
        draw(angle) {
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            ctx.fillText(`radians: ${toRad(angle, 3)}`, 10, 10)
            ctx.fillText(`degrees: ${angle}`, 10, 30)
            ctx.strokeRect(rect.x, rect.y, rect.width, rect.height)


            ctx.save()
            const theta = Theta.Degrees(angle + offset)
            const {head, tail} = getCross(rect, theta)

            ctx.moveTo(head.x, head.y)
            ctx.lineTo(tail.x, tail.y)
            ctx.stroke()
            ctx.restore()

            ctx.save()
            ctx.translate(head.x, head.y)
            const ro = Theta.Degrees(135).add(theta.degrees)
            console.log(ro.degrees)
            ctx.rotate(ro.radians)
            ctx.beginPath()
            ctx.moveTo(-5, -5)
            ctx.lineTo(5, -5)
            ctx.lineTo(-5, 5)
            ctx.closePath()
            ctx.fill()
            ctx.stroke()

            ctx.beginPath();

            ctx.restore()
        }
    }
}
    
    
 const canvas = drawCanvas()
 canvas.draw(0)

 document.getElementById('degrees').oninput = e => {
        const value = Number(e.target.value)
        console.log('degrees', value)
        document.querySelector('[for="degrees"]').innerText = `degrees ${value}`
            canvas.draw(value)
        
    }

}

main()
        [for="degrees"] {
            display: inline-block;
            width: 130px;
        }
<div>
  <label for="degrees">degrees 0</label>
  <input type="range" value="0" min="0" max="360" id="degrees"/>
</div>
<canvas id="canvas" width="300" height="300"></canvas>

延长矩形的四个边。
从中心发出的射线必然与两条边相交,一个交点在矩形上,一个在延长线上。而且,矩形上的点总是比延长线上的点更靠近中心。 矩形两个交叉点

我们的线索是光线的角度和矩形的大小。通过使用与 x 的角度和与 y 的角度,我们计算从中心到两点的距离。我们取较短的距离为R。此时,我们就有了射线的角度和长度,它们恰好形成了极坐标。将此极坐标转换为笛卡尔坐标即可得到交点的精确坐标。

您可以在 codepen 中查看 LinearGradient 示例

const toDegrees = (angle) => angle * (180 / Math.PI);
const toRadians = (angle) => angle * (Math.PI / 180);

 class Theta {
  degrees;
  radians;
  constructor({ degrees, radians }) {
    if (degrees === undefined && radians === undefined)
      throw new Error("degrees or radians must be provided");
    if (degrees !== undefined) {
      this.degrees = degrees;
      this.radians = toRadians(degrees);
    }
    if (radians !== undefined) {
      this.radians = radians;
      this.degrees = toDegrees(radians);
    }
  }

  add(deg) {
    return new Theta({ degrees: this.degrees + deg });
  }

  sub(deg) {
    return new Theta({ degrees: this.degrees - deg });
  }

  static Degrees(deg) {
    return new Theta({ degrees: deg });
  }

  static Radians(rad) {
    return new Theta({ radians: rad });
  }
}
/**
 @link https://zh.wikipedia.org/wiki/%E4%B8%89%E8%A7%92%E5%87%BD%E6%95%B0#%E4%BB%A5%E7%9B%B4%E8%A7%92%E5%9D%90%E6%A0%87%E7%B3%BB%E4%BE%86%E5%AE%9A%E4%B9%89
 *
 * */
function getRX(theta, x) {
  return Math.abs(x / Math.cos(theta.radians));
}
function getRY(theta, y) {
  return Math.abs(y / Math.sin(theta.radians));
}

 function cartesian2Polar(x, y) {
  const r = Math.sqrt(x * x + y * y);
  const theta = Theta.Radians(Math.atan(y / x));
  return { r, theta };
}

 function polar2Cartesian(r, theta) {
  const radians = theta.radians;
  const x = r * Math.cos(radians);
  const y = r * Math.sin(radians);
  return { x, y };
}

 function getCrossPoint(rect, theta) {
  const x = rect.width / 2;
  const y = rect.height / 2;

  const r1 = getRX(theta, x);
  const r2 = getRY(theta, y);
  const r = Math.min(r1, r2);
  const point = polar2Cartesian(r, theta);

  // move back to real coordinate
  point.x += rect.width / 2 + rect.x;
  point.y += rect.height / 2 + rect.y;


  return {
    point,
    radius: r,
  };
}

 function getCross(rect, theta) {
  const head = getCrossPoint(rect, theta).point;
  const tail = getCrossPoint(rect, Theta.Degrees(theta.degrees + 180)).point;

  return {
    head,
    tail,
  };
}



function main() {

    const width = 300 
    const height = 300 


  function drawCanvas() {
    const canvas = document.getElementById('canvas');
    canvas.width = width
    canvas.height = height
    const ctx = canvas.getContext('2d');
    const offset = -270
    const rect = {
        x: 10,
        y: 50,
        width: 280,
        height: 200,
    }
    const toRad = (deg) => toRadians(deg + offset)


    return {
        draw(angle) {
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            ctx.fillText(`radians: ${toRad(angle, 3)}`, 10, 10)
            ctx.fillText(`degrees: ${angle}`, 10, 30)
            ctx.strokeRect(rect.x, rect.y, rect.width, rect.height)


            ctx.save()
            const theta = Theta.Degrees(angle + offset)
            const {head, tail} = getCross(rect, theta)

            ctx.moveTo(head.x, head.y)
            ctx.lineTo(tail.x, tail.y)
            ctx.stroke()
            ctx.restore()

            ctx.save()
            ctx.translate(head.x, head.y)
            const ro = Theta.Degrees(135).add(theta.degrees)
            console.log(ro.degrees)
            ctx.rotate(ro.radians)
            ctx.beginPath()
            ctx.moveTo(-5, -5)
            ctx.lineTo(5, -5)
            ctx.lineTo(-5, 5)
            ctx.closePath()
            ctx.fill()
            ctx.stroke()

            ctx.beginPath();

            ctx.restore()
        }
    }
}
    
    
 const canvas = drawCanvas()
 canvas.draw(0)

 document.getElementById('degrees').oninput = e => {
        const value = Number(e.target.value)
        console.log('degrees', value)
        document.querySelector('[for="degrees"]').innerText = `degrees ${value}`
            canvas.draw(value)
        
    }

}

main()
        [for="degrees"] {
            display: inline-block;
            width: 130px;
        }
<div>
  <label for="degrees">degrees 0</label>
  <input type="range" value="0" min="0" max="360" id="degrees"/>
</div>
<canvas id="canvas" width="300" height="300"></canvas>

extending the four sides of a rectangle.
A ray emanating from the center will inevitably intersect with two sides, one intersection point on the rectangle and one on the extended line. Moreover, the point on the rectangle is always closer to the center than the one on the extended line. rectangle with two cross point

Our clues are the ray's angle and the rectangle's size. By using the angle with x and the angle with y, we calculate the distances from the center to the two points. We take the shorter distance as R. At this point, we have the ray's angle and its length, which exactly form a polar coordinate. Converting this polar coordinate to a Cartesian coordinate gives us the exact coordinates of the intersection point.

You can check it in codepen for linearGradient example

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文