【问题标题】:Finding points on a rectangle at a given angle以给定角度在矩形上查找点
【发布时间】:2010-10-31 02:28:36
【问题描述】:

我正在尝试以给定的角度 (Theta) 在矩形对象中绘制渐变,其中渐变的末端与矩形的周边相接触。

我认为使用 tangent 会起作用,但我无法解决问题。有没有我只是想念的简单算法?

最终结果

所以,这将是 (angle, RectX1, RectX2, RectY1, RectY2) 的函数。我希望它以 [x1, x2, y1, y2] 的形式返回,以便渐变将绘制在正方形上。 在我的问题中,如果原点为 0,则 x2 = -x1 和 y2 = -y1。但它并不总是在原点上。

【问题讨论】:

  • 图片和问题有什么关系?只有线的一端(在这种情况下我假设线是斜边)接触边界。这条线会一直通过(或如图所示)原点吗?
  • @aaronasterling,这是我对我想要实现的目标的理解。我需要 X 和 Y。三角形会根据角度而变化。

标签: python math graphics geometry


【解决方案1】:

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

您需要考虑四个地区:

地区 从 到 哪里 ==================================================== =================== 1 -arctan(b/a) +arctan(b/a) 右绿三角 2 +arctan(b/a) π-arctan(b/a) 上黄色三角形 3 π-arctan(b/a) π+arctan(b/a) 左绿色三角形 4 π+arctan(b/a) -arctan(b/a) 下黄色三角形

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

所以 Z0 是区域 1 和 3 的交点表达式
Z1 是区域 2 和 4 的交点的表达式

所需的行从 (X0,Y0) 传递到 Z0 或 Z1,具体取决于区域。所以记住 Tan(φ)=Sin(φ)/Cos(φ)

区域中的行开始结束 ==================================================== ===================== 1 和 3 (X0,Y0) (X0 + a/2 , (a/2 * Tan(φ))+ Y0 2 和 4 (X0,Y0) (X0 + b/(2* Tan(φ)) , b/2 + Y0)

请注意每个象限中 Tan(φ) 的符号,并且始终从正 x 轴逆时针测量角度。

HTH!

【讨论】:

  • 我不明白您的答案或另一个答案中的两个角度 φ 和 θ 代表什么 - 这个问题不是只指定一个角度吗?与区域 3 相比,区域 1 中的相交点/端点的 x 坐标是否应该不同(与区域 2 中的相交点与 4 相比,是否应该有不同的 y 坐标)?
  • @VictorVanHee:角度是与中心点的角度。他使用 2 个字母表示它们是不同的可能代码路径,但在公式中,它只是“theta”(或您使用的任何变量。)
  • @belisarius:你能解释一下接近结尾的句子“请注意每个象限中Tan(φ)的符号”吗?我已经 so 接近了,但我很确定我剩下的错误与不了解如何根据 tan(φ) 的符号更改公式有关。谢谢!
  • @Olie:区域 2 中的结束位置的 X 值低于 X0(毕竟它在它的左边),使得 x(end)=X0 - a/2(注意减号而不是加号)是正确的在区域 2 中计算 X 的方法。同理,在区域 4 中,Y 值由Y(end)=Y0 - b/2 计算。
【解决方案2】:

好的,哇!,我终于拿到了这个。

注意:我是根据贝利撒留的绝妙回答来做这个的。如果你喜欢这个,请也喜欢他的。我所做的只是把他说的话变成了代码。

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

【讨论】:

  • 很棒的代码!我刚刚用Java实现了这个。我必须在计算中交换区域 2 和 4,并且我必须在区域 1 和 3 中使用正 yFactor,但我认为这是因为在 Cocoa/Objective-C 中,原点位于左下角。太棒了!干得好!
  • 您是使用 theta 作为弧度还是“东度数”(顺时针范围为 0 到 180,逆时针范围为 0 到 -180)?
【解决方案3】:

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

【讨论】:

  • 非常感谢你,我刚开始写,然后发现有人已经这样做了:)
  • 它节省了我很多时间。谢谢,伙计。
【解决方案4】:

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

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

如果 θ

【讨论】:

    【解决方案5】:

    Find the CGPoint on a UIView rectangle intersected by a straight line at a given angle from the center point 对这个问题有一个很好的(更程序化的 iOS / Objective-C)答案,包括以下步骤:

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

    【讨论】:

      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

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

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

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

        【讨论】:

          【解决方案8】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-10-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-12-03
            • 1970-01-01
            相关资源
            最近更新 更多