Finding points on a rectangle at a given angle

后端 未结 7 1993
没有蜡笔的小新
没有蜡笔的小新 2020-12-14 02:43

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.

相关标签:
7条回答
  • 2020-12-14 03:06

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

    0 讨论(0)
  • 2020-12-14 03:10

    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!

    0 讨论(0)
  • 2020-12-14 03:16

    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
    
    0 讨论(0)
  • 2020-12-14 03:19

    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);
        }
    }
    
    0 讨论(0)
  • 2020-12-14 03:20

    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.
    0 讨论(0)
  • 2020-12-14 03:21

    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;
    }
    
    0 讨论(0)
提交回复
热议问题