Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clickable arc issue#778 #1561

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
219 changes: 219 additions & 0 deletions shoes-core/lib/shoes/arc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class Shoes
# the arc itself!
# @note The angle passed in is measured in Radians starting at 90 degrees or the 3 o'clock position.
class Arc < Common::ArtElement
include Math
# angle is the gradient angle used across all art elements
# angle1/2 are the angles of the arc itself!
style_with :angle1, :angle2, :art_styles, :center, :common_styles, :dimensions, :radius, :wedge
STYLES = { wedge: false, fill: Shoes::COLORS[:black] }.freeze

Expand Down Expand Up @@ -55,5 +58,221 @@ def center_point=(point)
self.top = point.y - (height * 0.5).to_i
end
end

def radius_x
@radius_x ||= width.to_f / 2
end

def radius_y
@radius_y ||= height.to_f / 2
end

def middle_x
@middle_x ||= left + radius_x
end

def middle_y
@middle_y ||= top + radius_y
end

def oval_axis_fraction(axis, input, difference = 0)
(((input - send("middle_#{axis}"))**2).to_f / ((send("radius_#{axis}") - difference)**2))
end

def inner_oval_axis_fraction(axis, input)
oval_axis_fraction(axis, input, inner_oval_difference)
end

def inside_oval?(x, y)
# https://math.stackexchange.com/questions/76457/check-if-a-point-is-within-an-ellipse
# (x - h)**2 (y - k)**2
# ------- + ------- <= 1
# Rx ** 2 Ry ** 2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love love love the reference!

(oval_axis_fraction(:x, x) + oval_axis_fraction(:y, y)) <= 1
end

def inside_inner_oval?(x, y)
x_side = inner_oval_axis_fraction(:x, x)
y_side = inner_oval_axis_fraction(:y, y)
x_side + y_side <= 1
end

def normalize_angle(input_angle)
# This fixes angles > 6.283185 and < 0
input_angle % (Math::PI * 2)
end

def adjust_angle(input_angle)
# Must pad angle, since angles in standard formulas start at different location than in shoes
adjusted_angle = input_angle + 1.5708

# Must make angle 0..6.28318
normalize_angle(adjusted_angle)
end

def y_adjust_negative?(input_angle)
((input_angle >= 0) && (input_angle <= 1.5708)) || ((input_angle >= 4.71239) && (input_angle <= 6.28319))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could probably benefit from some constants for these values

end

def x_adjust_positive?(input_angle)
(input_angle >= 0) && (input_angle <= 3.14159)
end

def y_result_adjustment(input_angle, y_result)
if y_adjust_negative?(input_angle)
middle_y - y_result
else
middle_y + y_result
end
end

def x_result_adjustment(input_angle, x_result)
if x_adjust_positive?(input_angle)
middle_x + x_result
else
middle_x - x_result
end
end

def generate_coordinates(input_angle, x_result, y_result)
x_result = x_result_adjustment(input_angle, x_result).round(3)
y_result = y_result_adjustment(input_angle, y_result).round(3)

{
x_value: x_result,
y_value: y_result
}
end

def tangent_squared(input_angle)
tan(input_angle)**2
end

def angle_base_coords(given_angle)
# https://math.stackexchange.com/questions/22064/calculating-a-point-that-lies-on-an-ellipse-given-an-angle
# The above link was used in creating this method...but this implementation varies due to nature of coordinates in shoes
modded_angle = adjust_angle(given_angle)
top_of_equation = (radius_x * radius_y)

x_result = top_of_equation / (((radius_y**2) + ((radius_x**2) / tangent_squared(modded_angle)))**0.5)
y_result = top_of_equation / (((radius_x**2) + ((radius_y**2) * tangent_squared(modded_angle)))**0.5)

generate_coordinates(modded_angle, x_result, y_result)
end

def angle1_coordinates
@angle1_coordinates ||= angle_base_coords(angle1)
end

def angle2_coordinates
@angle2_coordinates ||= angle_base_coords(angle2)
end

def angle1_x
angle1_coordinates[:x_value]
end

def angle1_y
angle1_coordinates[:y_value]
end

def angle2_x
angle2_coordinates[:x_value]
end

def angle2_y
angle2_coordinates[:y_value]
end

def slope_of_angles
# slope = (y2 - y1) / (x2 - x1)
(angle2_y - angle1_y) / (angle2_x - angle1_x)
end

def b_value_for_line
# SINCE y = mx + b
# THEN b = y - mx
mx_value = (angle1_x * slope_of_angles)

if mx_value == Float::INFINITY
angle1_y
else
angle1_y - mx_value
end
end

def vertical_check(x_input)
# The above/below are
if angle1_x == x_input
:on
elsif angle1_x < x_input
:above
else
:below
end
end

def normal_above_below_check(mx_value, y_input)
right_side_of_equation = mx_value + b_value_for_line

if right_side_of_equation == y_input
# If input y is same...input is on the line
:on
elsif right_side_of_equation > y_input
# If input y is more, point is above the line
:above
else
# If input y is less, point is below the line
:below
end
end

def above_below_on(x_input, y_input)
mx_value = (x_input * slope_of_angles)

if mx_value.abs > 1_000_000.00
# If line is straight up and down..compare with x value to an x coordinate
vertical_check(x_input)
else
# If standard slope...find what the y value would be given the input x
normal_above_below_check(mx_value, y_input)
end
end

def angle1_x_smaller_check(x, y)
above_below_on(x, y) == :above && angle1_x < angle2_x
end

def angle2_x_smaller_check(x, y)
above_below_on(x, y) == :below && angle1_x > angle2_x
end

def on_shaded_part?(x, y)
angle1_x_smaller_check(x, y) || angle2_x_smaller_check(x, y)
end

def standard_arc_bounds_check?(x, y)
# Check if it is in the oval, and then if on correct side of the line between points
inside_oval?(x, y) && on_shaded_part?(x, y)
end

def inner_oval_difference
@inner_oval_difference ||= calculate_inner_oval_difference
end

def calculate_inner_oval_difference
[style[:strokewidth].to_f, 3.0].max
end

def in_bounds?(x, y)
bounds_check = standard_arc_bounds_check?(x, y)

if bounds_check && !style[:fill]
# If no fill, then it is just the edge and needs a further check
bounds_check = !inside_inner_oval?(x, y)
end

bounds_check
end
end
end
2 changes: 1 addition & 1 deletion shoes-core/lib/shoes/oval.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def create_dimensions(left, top, width, height)
@dimensions = AbsoluteDimensions.new left, top, width, height, @style
end

# Check out http://math.stackexchange.com/questions/60070/checking-whether-a-point-lies-on-a-wide-line-segment
# Check out https://math.stackexchange.com/questions/76457/check-if-a-point-is-within-an-ellipse
# for explanations how the algorithm works
def in_bounds?(x, y)
radius_x = width.to_f / 2
Expand Down
Loading