From 1a3a3db1ef11d32adb9974ddd03c0107072ea470 Mon Sep 17 00:00:00 2001 From: Eric Stensland Date: Sat, 10 Feb 2018 20:43:29 -0600 Subject: [PATCH 01/11] Issue #778 oval in bounds, fixed wrong link in comment --- shoes-core/lib/shoes/oval.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shoes-core/lib/shoes/oval.rb b/shoes-core/lib/shoes/oval.rb index f83a5721d..c3bab9aec 100644 --- a/shoes-core/lib/shoes/oval.rb +++ b/shoes-core/lib/shoes/oval.rb @@ -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 From c7d6b43e9fc9ecf1805f9611fd8ce0dec5158e21 Mon Sep 17 00:00:00 2001 From: Eric Stensland Date: Sat, 10 Feb 2018 20:57:49 -0600 Subject: [PATCH 02/11] Issue #778 arc in bounds, Logic for both in the oval and above/below the line Co-authored-by: Matthew Feickert --- shoes-core/lib/shoes/arc.rb | 148 ++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/shoes-core/lib/shoes/arc.rb b/shoes-core/lib/shoes/arc.rb index 704336601..ac8f690be 100644 --- a/shoes-core/lib/shoes/arc.rb +++ b/shoes-core/lib/shoes/arc.rb @@ -22,5 +22,153 @@ def create_dimensions(left, top, width, height, angle1, angle2) def wedge? wedge end + + def radius_x + @radius_x ||= width.to_f / 2 + end + + def radius_y + @radius_y ||= height.to_f / 2 + end + + def middle_y + @middle_y ||= left + radius_x + end + + def middle_x + @middle_x ||= top + radius_y + end + + def oval_in_bounds?(x, y) + x_side = (((x - middle_x)**2).to_f / radius_x**2) + y_side = (((y - middle_y)**2).to_f / radius_y**2) + + x_side + y_side <= 1 + end + + def angle_from_coordinates_base_equation(axis, input_angle) + top_of_angle_coordinate_equation = (radius_x * radius_y) + + if axis == :y + top_of_angle_coordinate_equation / ( ( (radius_x ** 2) + ((radius_y ** 2) * (Math::tan(modded_angle) ** 2) ) ) ** 0.5 ) + else + top_of_angle_coordinate_equation / ( ( (radius_y ** 2) + ((radius_x ** 2) / (Math::tan(modded_angle) ** 2) ) ) ** 0.5 ) + end + 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 the implementation varies due to nature of shoes + + # Must pad angle, since angles in here start at different location on the ellipse + modded_angle = given_angle + 1.5708 + + x_check = angle_from_coordinates_base_equation(:x, modded_angle) + y_check = angle_from_coordinates_base_equation(:y, modded_angle) + + if ((0 <= modded_angle) && (modded_angle <= 1.5708)) || ((4.71239 <= modded_angle) && (modded_angle <= 6.28319)) + y_check = middle_x - y_check + else + y_check = middle_x + y_check + end + + if (0 <= modded_angle) && (modded_angle <= 3.14159) + x_check = middle_y + x_check + else + x_check = middle_y - x_check + end + + {x_value: x_check.round(3), y_value: y_check.round(3)} + 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 in_bounds?(x, y) + if oval_in_bounds?(x, y) + if above_below_on(x,y) == :below && angle1 < angle2 + true + elsif above_below_on(x,y) == :above && angle1 > angle2 + true + end + end + end end end From 8033fa63254a61f63ef4c3b2989a21175cd2bb7d Mon Sep 17 00:00:00 2001 From: Eric Stensland Date: Sun, 11 Feb 2018 13:43:11 -0600 Subject: [PATCH 03/11] Issue #778 refactoring odd names --- shoes-core/lib/shoes/arc.rb | 41 +++++++++++++++---------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/shoes-core/lib/shoes/arc.rb b/shoes-core/lib/shoes/arc.rb index ac8f690be..3ebd5488f 100644 --- a/shoes-core/lib/shoes/arc.rb +++ b/shoes-core/lib/shoes/arc.rb @@ -31,12 +31,12 @@ def radius_y @radius_y ||= height.to_f / 2 end - def middle_y - @middle_y ||= left + radius_x + def middle_x + @middle_x ||= left + radius_x end - def middle_x - @middle_x ||= top + radius_y + def middle_y + @middle_y ||= top + radius_y end def oval_in_bounds?(x, y) @@ -46,16 +46,6 @@ def oval_in_bounds?(x, y) x_side + y_side <= 1 end - def angle_from_coordinates_base_equation(axis, input_angle) - top_of_angle_coordinate_equation = (radius_x * radius_y) - - if axis == :y - top_of_angle_coordinate_equation / ( ( (radius_x ** 2) + ((radius_y ** 2) * (Math::tan(modded_angle) ** 2) ) ) ** 0.5 ) - else - top_of_angle_coordinate_equation / ( ( (radius_y ** 2) + ((radius_x ** 2) / (Math::tan(modded_angle) ** 2) ) ) ** 0.5 ) - end - 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 the implementation varies due to nature of shoes @@ -63,19 +53,22 @@ def angle_base_coords(given_angle) # Must pad angle, since angles in here start at different location on the ellipse modded_angle = given_angle + 1.5708 - x_check = angle_from_coordinates_base_equation(:x, modded_angle) - y_check = angle_from_coordinates_base_equation(:y, modded_angle) + top_bit = (radius_x * radius_y) + + x_check = top_bit / ( ( (radius_y ** 2) + ((radius_x**2) / (Math::tan(modded_angle)**2) ) ) ** 0.5 ) + + y_check = top_bit / ( ( (radius_x ** 2) + ((radius_y**2) * (Math::tan(modded_angle)**2) ) ) ** 0.5 ) if ((0 <= modded_angle) && (modded_angle <= 1.5708)) || ((4.71239 <= modded_angle) && (modded_angle <= 6.28319)) - y_check = middle_x - y_check + y_check = middle_y - y_check else - y_check = middle_x + y_check + y_check = middle_y + y_check end if (0 <= modded_angle) && (modded_angle <= 3.14159) - x_check = middle_y + x_check + x_check = middle_x + x_check else - x_check = middle_y - x_check + x_check = middle_x - x_check end {x_value: x_check.round(3), y_value: y_check.round(3)} @@ -164,10 +157,10 @@ def above_below_on(x_input, y_input) def in_bounds?(x, y) if oval_in_bounds?(x, y) if above_below_on(x,y) == :below && angle1 < angle2 - true - elsif above_below_on(x,y) == :above && angle1 > angle2 - true - end + true + elsif above_below_on(x,y) == :above && angle1 > angle2 + true + end end end end From 7e7e6406eaf0733885869486ec9620e12b5d2e7c Mon Sep 17 00:00:00 2001 From: Eric Stensland Date: Sun, 11 Feb 2018 13:57:46 -0600 Subject: [PATCH 04/11] Issue #778 complex methods breakdown --- shoes-core/lib/shoes/arc.rb | 55 ++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/shoes-core/lib/shoes/arc.rb b/shoes-core/lib/shoes/arc.rb index 3ebd5488f..7afe49cbb 100644 --- a/shoes-core/lib/shoes/arc.rb +++ b/shoes-core/lib/shoes/arc.rb @@ -46,32 +46,55 @@ def oval_in_bounds?(x, y) x_side + y_side <= 1 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 the implementation varies due to nature of shoes - + def adjust_angle(input_angle) # Must pad angle, since angles in here start at different location on the ellipse - modded_angle = given_angle + 1.5708 - - top_bit = (radius_x * radius_y) + input_angle + 1.5708 + end - x_check = top_bit / ( ( (radius_y ** 2) + ((radius_x**2) / (Math::tan(modded_angle)**2) ) ) ** 0.5 ) + def y_adjust_negative?(input_angle) + ((0 <= input_angle) && (input_angle <= 1.5708)) || ((4.71239 <= input_angle) && (input_angle <= 6.28319)) + end - y_check = top_bit / ( ( (radius_x ** 2) + ((radius_y**2) * (Math::tan(modded_angle)**2) ) ) ** 0.5 ) + def x_adjust_positive?(input_angle) + (0 <= input_angle) && (input_angle <= 3.14159) + end - if ((0 <= modded_angle) && (modded_angle <= 1.5708)) || ((4.71239 <= modded_angle) && (modded_angle <= 6.28319)) - y_check = middle_y - y_check + def y_result_adjustment(input_angle, y_result) + if y_adjust_negative?(input_angle) + middle_y - y_result else - y_check = middle_y + y_check + middle_y + y_result end + end - if (0 <= modded_angle) && (modded_angle <= 3.14159) - x_check = middle_x + x_check + def x_result_adjustment(input_angle, x_result) + if x_adjust_positive?(input_angle) + middle_x + x_result else - x_check = middle_x - x_check + 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 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 the implementation varies due to nature of 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) / (Math::tan(modded_angle)**2) ) ) ** 0.5 ) + y_result = top_of_equation / ( ( (radius_x ** 2) + ((radius_y**2) * (Math::tan(modded_angle)**2) ) ) ** 0.5 ) - {x_value: x_check.round(3), y_value: y_check.round(3)} + generate_coordinates(modded_angle, x_result, y_result) end def angle1_coordinates From cc00ec88e3c32cfb4989891890477ba95d3aefbb Mon Sep 17 00:00:00 2001 From: Eric Stensland Date: Mon, 12 Feb 2018 18:44:51 -0600 Subject: [PATCH 05/11] Issue#778 appeasing rubocop --- shoes-core/lib/shoes/arc.rb | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/shoes-core/lib/shoes/arc.rb b/shoes-core/lib/shoes/arc.rb index 7afe49cbb..b60800e7c 100644 --- a/shoes-core/lib/shoes/arc.rb +++ b/shoes-core/lib/shoes/arc.rb @@ -52,11 +52,11 @@ def adjust_angle(input_angle) end def y_adjust_negative?(input_angle) - ((0 <= input_angle) && (input_angle <= 1.5708)) || ((4.71239 <= input_angle) && (input_angle <= 6.28319)) + ((input_angle >= 0) && (input_angle <= 1.5708)) || ((input_angle >= 4.71239) && (input_angle <= 6.28319)) end def x_adjust_positive?(input_angle) - (0 <= input_angle) && (input_angle <= 3.14159) + (input_angle >= 0) && (input_angle <= 3.14159) end def y_result_adjustment(input_angle, y_result) @@ -91,14 +91,14 @@ def angle_base_coords(given_angle) modded_angle = adjust_angle(given_angle) top_of_equation = (radius_x * radius_y) - x_result = top_of_equation / ( ( (radius_y ** 2) + ((radius_x**2) / (Math::tan(modded_angle)**2) ) ) ** 0.5 ) - y_result = top_of_equation / ( ( (radius_x ** 2) + ((radius_y**2) * (Math::tan(modded_angle)**2) ) ) ** 0.5 ) + x_result = top_of_equation / (((radius_y**2) + ((radius_x**2) / (tan(modded_angle)**2)))**0.5) + y_result = top_of_equation / (((radius_x**2) + ((radius_y**2) * (tan(modded_angle)**2)))**0.5) generate_coordinates(modded_angle, x_result, y_result) end def angle1_coordinates - @angle1_coordinates ||= angle_base_coords(angle1) + @angle1_coordinates ||= angle_base_coords(angle1) end def angle2_coordinates @@ -164,7 +164,6 @@ def normal_above_below_check(mx_value, y_input) end end - def above_below_on(x_input, y_input) mx_value = (x_input * slope_of_angles) @@ -179,11 +178,11 @@ def above_below_on(x_input, y_input) def in_bounds?(x, y) if oval_in_bounds?(x, y) - if above_below_on(x,y) == :below && angle1 < angle2 - true - elsif above_below_on(x,y) == :above && angle1 > angle2 - true - end + if above_below_on(x, y) == :below && angle1 < angle2 + true + elsif above_below_on(x, y) == :above && angle1 > angle2 + true + end end end end From 6521120a1e08f8194f6aeb50b6ea3559f8528a52 Mon Sep 17 00:00:00 2001 From: Eric Stensland Date: Sat, 24 Mar 2018 13:41:37 -0500 Subject: [PATCH 06/11] issue #778 ugly code but working state in need of refactor --- shoes-core/lib/shoes/arc.rb | 82 ++++++++- shoes-core/spec/shoes/arc_spec.rb | 278 ++++++++++++++++++++++++++++++ 2 files changed, 353 insertions(+), 7 deletions(-) diff --git a/shoes-core/lib/shoes/arc.rb b/shoes-core/lib/shoes/arc.rb index b60800e7c..92afd96ea 100644 --- a/shoes-core/lib/shoes/arc.rb +++ b/shoes-core/lib/shoes/arc.rb @@ -2,6 +2,7 @@ class Shoes 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 @@ -39,16 +40,43 @@ def middle_y @middle_y ||= top + radius_y end - def oval_in_bounds?(x, y) + def inside_oval?(x, y) x_side = (((x - middle_x)**2).to_f / radius_x**2) y_side = (((y - middle_y)**2).to_f / radius_y**2) + x_side + y_side <= 1 + end + def inside_inner_oval?(dist, x, y) + dd = dist / 2 + x_side = (((x - middle_x)**2).to_f / ((radius_x - dd))**2) + y_side = (((y - middle_y)**2).to_f / ((radius_y - dd))**2) x_side + y_side <= 1 end def adjust_angle(input_angle) # Must pad angle, since angles in here start at different location on the ellipse - input_angle + 1.5708 + + l = if false#input_angle < 0 + (Math::PI * 2) - input_angle + else + input_angle + 1.5708 + end + + if l < 0 + + while l < (Math::PI * -2) + l = l + (Math::PI * 2) + end + + l = (Math::PI * 2) + l + end + + while l > (Math::PI * 2) + l = l - (Math::PI * 2) + end + + + l end def y_adjust_negative?(input_angle) @@ -176,14 +204,54 @@ def above_below_on(x_input, y_input) end end + def angle1_smaller_check(x,y) + above_below_on(x, y) == :below && angle1_coordinates[:x_value] > angle2_coordinates[:x_value] + end + + def angle2_smaller_check(x,y) + above_below_on(x, y) == :above && angle1_coordinates[:x_value] < angle2_coordinates[:x_value] + end + + def on_shaded_part?(x, y) + angle1_smaller_check(x,y) || angle2_smaller_check(x,y) + end + + def get_y_value(x) + xie_fraction1 = ((x - middle_x) ** 2) / (radius_x ** 2) + xie_fraction2 = ((x + middle_x) ** 2) / (radius_x ** 2) + + y_rad_bit = radius_y ** 2 + + left_side1 = (1 - xie_fraction1) * y_rad_bit + left_side2 = (1 - xie_fraction2) * y_rad_bit + + final_left1 = left_side1 ** 0.5 + final_left2 = left_side2 ** 0.5 + + [final_left1 + middle_y, middle_y - final_left1, final_left2 + middle_y, middle_y - final_left2] + end + def in_bounds?(x, y) - if oval_in_bounds?(x, y) - if above_below_on(x, y) == :below && angle1 < angle2 - true - elsif above_below_on(x, y) == :above && angle1 > angle2 - true + @x = x + @y = y + + close_y_val = get_y_value(x) + check = close_y_val.detect do |attempt| + (y - attempt).abs <= (style[:strokewidth]) + 1 + end + + check = inside_oval?(x, y) && on_shaded_part?(x, y) + + if check && !style[:fill] + difference = style[:strokewidth] * 2 + + if difference < 4 + difference = 4 end + check = !inside_inner_oval?(difference, x, y) end + + check end end end diff --git a/shoes-core/spec/shoes/arc_spec.rb b/shoes-core/spec/shoes/arc_spec.rb index ed9716173..fda432e48 100644 --- a/shoes-core/spec/shoes/arc_spec.rb +++ b/shoes-core/spec/shoes/arc_spec.rb @@ -209,4 +209,282 @@ expect { dsl.arc 10, 20, 30, 40, Shoes::PI, Shoes::TWO_PI, 666, left: -1 }.to raise_error(ArgumentError) end end + + describe 'in_bounds methods' do + subject(:arc) { Shoes::Arc.new(app, parent, left, top, width, height, start_angle, end_angle) } + + describe '#radius_x' do + it 'must return half the width' do + expect(arc.radius_x).to eq(width.to_f / 2) + end + end + + describe '#radius_y' do + it 'must return half the height' do + expect(arc.radius_y).to eq(height.to_f / 2) + end + end + + describe '#middle_x' do + it 'must return the left plus radius_x' do + expect(arc.middle_x).to eq(113.0) + end + end + + describe '#middle_y' do + it 'must return the top plus radius_y' do + expect(arc.middle_y).to eq(194.0) + end + end + + describe '#inside_oval?' do + context 'when point is inside' do + it 'must return true' do + expect(arc.inside_oval??(arc.middle_x, arc.middle_y)).to eq(true) + end + end + + context 'when point is outside' do + it 'must return false' do + expect(arc.inside_oval??(arc.top, arc.left)).to eq(false) + end + end + end + + describe '#adjust_angle' do + it 'must rotate the angle 1.5708 radians (90 degrees)' do + expect(arc.adjust_angle(1.5708)).to eq(3.1416) + end + end + + describe '#y_adjust_negative?' do + context 'when angle is between 0 and 1.5708' do + it 'must return true' do + expect(arc.y_adjust_negative?(1)).to eq(true) + end + end + + context 'when angle is between 1.5708 and 4.71239' do + it 'must return false' do + expect(arc.y_adjust_negative?(2)).to eq(false) + end + end + + context 'when angle is between 4.71239 and 6.28319' do + it 'must return true' do + expect(arc.y_adjust_negative?(5)).to eq(true) + end + end + end + + describe '#x_adjust_positive?' do + context 'when angle is between 0 and 3.14159' do + it 'must return true' do + expect(arc.y_adjust_negative?(1)).to eq(true) + end + end + + context 'when angle is above 3.14159' do + it 'must return false' do + expect(arc.y_adjust_negative?(2)).to eq(false) + end + end + end + + describe '#y_result_adjustment' do + context 'when #y_adjust_negative? is false' do + before(:each){allow(arc).to receive(:y_adjust_negative?){false}} + + it 'should add y_result to middle_y' do + expect(arc.y_result_adjustment(1,1)).to eq(195.0) + end + end + + context 'when #y_adjust_negative? is true' do + before(:each){allow(arc).to receive(:y_adjust_negative?){true}} + + it 'should subtract y_result from middle_y' do + expect(arc.y_result_adjustment(1,1)).to eq(193.0) + end + end + + end + + describe '#x_result_adjustment' do + context 'when #x_adjust_positive? is false' do + before(:each){allow(arc).to receive(:x_adjust_positive?){false}} + + it 'should subtract y_result from middle_y' do + expect(arc.x_result_adjustment(1,1)).to eq(112.0) + end + end + + context 'when #x_adjust_positive? is true' do + before(:each){allow(arc).to receive(:x_adjust_positive?){true}} + + it 'should add y_result from middle_y' do + expect(arc.x_result_adjustment(1,1)).to eq(114.0) + end + end + end + + describe '#generate_coordinates' do + it 'must output a hash of the adjusted x and y coordinates' do + allow(arc).to receive(:x_result_adjustment){5.0035} + allow(arc).to receive(:y_result_adjustment){7.0034} + expect(arc.generate_coordinates(0,50,50)).to eq({x_value: 5.004, y_value: 7.003}) + end + end + + describe '#angle_base_coords' do + it 'must find the coordinates for a given angle' do + expect(arc.angle_base_coords(1.5708)).to eq({:x_value=>112.999, :y_value=>344.0}) + end + end + + describe '#angle1_coordinates' do + it 'must call angle_base_coords' do + allow(arc).to receive(:angle_base_coords){|e| e} + expect(arc.angle1_coordinates).to eq(arc.angle1) + end + end + + describe '#angle2_coordinates' do + it 'must call angle_base_coords' do + allow(arc).to receive(:angle_base_coords){|e| e} + expect(arc.angle2_coordinates).to eq(arc.angle2) + end + end + + describe '#angle1_x' do + it 'must grab :x_value from @angle1_coordinates' do + allow(arc).to receive(:angle1_coordinates){{x_value: 55}} + expect(arc.angle1_x).to eq(55) + end + end + + describe '#angle1_y' do + it 'must grab :y_value from @angle1_coordinates' do + allow(arc).to receive(:angle1_coordinates){{y_value: 55}} + expect(arc.angle1_y).to eq(55) + end + end + + describe '#angle2_x' do + it 'must grab :x_value from @angle2_coordinates' do + allow(arc).to receive(:angle2_coordinates){{x_value: 55}} + expect(arc.angle2_x).to eq(55) + end + end + + describe '#angle2_y' do + it 'must grab :y_value from @angle2_coordinates' do + allow(arc).to receive(:angle2_coordinates){{y_value: 55}} + expect(arc.angle2_y).to eq(55) + end + end + + describe '#slope_of_angles' do + it 'must calculate the slope value from the angles' do + expect(arc.slope_of_angles).to eq(-0.0) + end + end + + describe '#b_value_for_line' do + it 'must calculate the b value of the line equation from its angles' do + expect(arc.b_value_for_line).to eq(194.0 ) + end + end + + describe '#vertical_check' do + context 'if angle_x is same as input' do + it 'must return :on' do + allow(arc).to receive(:angle1_x){55} + expect(arc.vertical_check(55)).to eq(:on) + end + end + + context 'if angle_x is less than input' do + it 'must return :above' do + allow(arc).to receive(:angle1_x){1} + expect(arc.vertical_check(55)).to eq(:above) + end + end + + context 'if angle_x is more than input' do + it 'must return :below' do + allow(arc).to receive(:angle1_x){100} + expect(arc.vertical_check(55)).to eq(:below) + end + end + end + + describe '#normal_above_below_check' do + before(:each){allow(arc).to receive(:b_value_for_line){0}} + + context 'if right_side_of_equation is same as y_input' do + it 'must return :on' do + expect(arc.normal_above_below_check(10, 10)).to eq(:on) + end + end + + context 'if right_side_of_equation is less than y_input' do + it 'must return :below' do + expect(arc.normal_above_below_check(8, 10)).to eq(:below) + end + end + + context 'if right_side_of_equation is more than y_input' do + it 'must return :above' do + expect(arc.normal_above_below_check(12, 10)).to eq(:above) + end + end + end + + describe '#above_below_on' do + before(:each){allow(arc).to receive(:vertical_check){|x| x}} + before(:each){allow(arc).to receive(:normal_above_below_check){|x,y| [x,y]}} + + context 'when mx_value over a million' do + context 'when #slope_of_angles is positive' do + before(:each){allow(arc).to receive(:slope_of_angles){1}} + + it 'must call and return #vertical_check' do + expect(arc.above_below_on(6_000_000, 100)).to eq(6_000_000) + end + end + + context 'when #slope_of_angles is negative' do + before(:each){allow(arc).to receive(:slope_of_angles){-1}} + + it 'must call and return #vertical_check' do + expect(arc.above_below_on(-6_000_000, 100)).to eq(-6_000_000) + end + end + end + + context 'when mx_value under a million' do + it 'must call #normal_above_below_check' do + allow(arc).to receive(:slope_of_angles){1} + expect(arc.above_below_on(60, 100)).to eq([60, 100]) + end + end + end + + describe '#in_bounds?' do + context 'when #inside_oval?? is false' do + it 'must return nil' do + allow(arc).to receive(:inside_oval??){false} + expect(arc.in_bounds?(1,1)).to eq(nil) + end + end + + context 'when #inside_oval?? is true' do + it 'must return nil' do + allow(arc).to receive(:inside_oval??){true} + expect(arc.in_bounds?(1,1)).to eq(nil) + end + end + end + end end From 1f79647cb8db36e112129dbea348220698fe48cf Mon Sep 17 00:00:00 2001 From: Eric Stensland Date: Sat, 24 Mar 2018 20:50:54 -0500 Subject: [PATCH 07/11] refactoring code for issue #778 --- shoes-core/lib/shoes/arc.rb | 96 +++++++++++++++---------------------- 1 file changed, 39 insertions(+), 57 deletions(-) diff --git a/shoes-core/lib/shoes/arc.rb b/shoes-core/lib/shoes/arc.rb index 92afd96ea..184e5f9f0 100644 --- a/shoes-core/lib/shoes/arc.rb +++ b/shoes-core/lib/shoes/arc.rb @@ -40,43 +40,36 @@ def middle_y @middle_y ||= top + radius_y end - def inside_oval?(x, y) - x_side = (((x - middle_x)**2).to_f / radius_x**2) - y_side = (((y - middle_y)**2).to_f / radius_y**2) - x_side + y_side <= 1 + def oval_axis_fraction(axis, input, difference = 0) + # x_side = (((x - middle_x)**2).to_f / radius_x**2) + (((input - send("middle_#{axis}")) ** 2).to_f / ((send("radius_#{axis}") - difference) ** 2)) end - def inside_inner_oval?(dist, x, y) - dd = dist / 2 - x_side = (((x - middle_x)**2).to_f / ((radius_x - dd))**2) - y_side = (((y - middle_y)**2).to_f / ((radius_y - dd))**2) - x_side + y_side <= 1 + def inner_oval_axis_fraction(axis, input) + oval_axis_fraction(axis, input, inner_oval_difference) end - def adjust_angle(input_angle) - # Must pad angle, since angles in here start at different location on the ellipse - - l = if false#input_angle < 0 - (Math::PI * 2) - input_angle - else - input_angle + 1.5708 - end - - if l < 0 - - while l < (Math::PI * -2) - l = l + (Math::PI * 2) - end + def inside_oval?(x, y) + (oval_axis_fraction(:x, x) + oval_axis_fraction(:y, y)) <= 1 + end - l = (Math::PI * 2) + l - 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 - while l > (Math::PI * 2) - l = l - (Math::PI * 2) - 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 - l + # Must make angle 0..6.28318 + adjusted_angle = normalize_angle(adjusted_angle) end def y_adjust_negative?(input_angle) @@ -199,59 +192,48 @@ def above_below_on(x_input, y_input) # 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. + # 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_smaller_check(x,y) - above_below_on(x, y) == :below && angle1_coordinates[:x_value] > angle2_coordinates[:x_value] + above_below_on(x, y) == :below && angle1_x > angle2_x end def angle2_smaller_check(x,y) - above_below_on(x, y) == :above && angle1_coordinates[:x_value] < angle2_coordinates[:x_value] + above_below_on(x, y) == :above && angle1_x < angle2_x end def on_shaded_part?(x, y) angle1_smaller_check(x,y) || angle2_smaller_check(x,y) end - def get_y_value(x) - xie_fraction1 = ((x - middle_x) ** 2) / (radius_x ** 2) - xie_fraction2 = ((x + middle_x) ** 2) / (radius_x ** 2) + def standard_arc_bounds_check(x,y) + inside_oval?(x, y) && on_shaded_part?(x, y) + end - y_rad_bit = radius_y ** 2 + def inner_oval_difference + @inner_oval_difference ||= calculate_inner_oval_difference + end - left_side1 = (1 - xie_fraction1) * y_rad_bit - left_side2 = (1 - xie_fraction2) * y_rad_bit + def calculate_inner_oval_difference + difference = style[:strokewidth].to_i * 2 - final_left1 = left_side1 ** 0.5 - final_left2 = left_side2 ** 0.5 + difference = 4 if difference < 4 - [final_left1 + middle_y, middle_y - final_left1, final_left2 + middle_y, middle_y - final_left2] + difference.to_f / 2 end def in_bounds?(x, y) - @x = x - @y = y - - close_y_val = get_y_value(x) - check = close_y_val.detect do |attempt| - (y - attempt).abs <= (style[:strokewidth]) + 1 - end - - check = inside_oval?(x, y) && on_shaded_part?(x, y) + bounds_check = standard_arc_bounds_check(x,y) - if check && !style[:fill] - difference = style[:strokewidth] * 2 + if bounds_check && !style[:fill] - if difference < 4 - difference = 4 - end - check = !inside_inner_oval?(difference, x, y) + bounds_check = !inside_inner_oval?(x, y) end - check + bounds_check end end end From 5e31e1545d7f0453a3afee8f9344366c7b700905 Mon Sep 17 00:00:00 2001 From: Eric Stensland Date: Sun, 25 Mar 2018 21:54:15 -0500 Subject: [PATCH 08/11] fixed specs and rubocop --- shoes-core/lib/shoes/arc.rb | 38 ++++++++------- shoes-core/spec/shoes/arc_spec.rb | 81 +++++++++++++++---------------- 2 files changed, 61 insertions(+), 58 deletions(-) diff --git a/shoes-core/lib/shoes/arc.rb b/shoes-core/lib/shoes/arc.rb index 184e5f9f0..f4bc53207 100644 --- a/shoes-core/lib/shoes/arc.rb +++ b/shoes-core/lib/shoes/arc.rb @@ -41,8 +41,7 @@ def middle_y end def oval_axis_fraction(axis, input, difference = 0) - # x_side = (((x - middle_x)**2).to_f / radius_x**2) - (((input - send("middle_#{axis}")) ** 2).to_f / ((send("radius_#{axis}") - difference) ** 2)) + (((input - send("middle_#{axis}"))**2).to_f / ((send("radius_#{axis}") - difference)**2)) end def inner_oval_axis_fraction(axis, input) @@ -50,6 +49,10 @@ def inner_oval_axis_fraction(axis, input) 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 (oval_axis_fraction(:x, x) + oval_axis_fraction(:y, y)) <= 1 end @@ -69,7 +72,7 @@ def adjust_angle(input_angle) adjusted_angle = input_angle + 1.5708 # Must make angle 0..6.28318 - adjusted_angle = normalize_angle(adjusted_angle) + normalize_angle(adjusted_angle) end def y_adjust_negative?(input_angle) @@ -106,14 +109,18 @@ def generate_coordinates(input_angle, x_result, 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 the implementation varies due to nature of shoes + # 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) / (tan(modded_angle)**2)))**0.5) - y_result = top_of_equation / (((radius_x**2) + ((radius_y**2) * (tan(modded_angle)**2)))**0.5) + 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 @@ -197,19 +204,20 @@ def above_below_on(x_input, y_input) end end - def angle1_smaller_check(x,y) + def angle2_x_smaller_check(x, y) above_below_on(x, y) == :below && angle1_x > angle2_x end - def angle2_smaller_check(x,y) + def angle1_x_smaller_check(x, y) above_below_on(x, y) == :above && angle1_x < angle2_x end def on_shaded_part?(x, y) - angle1_smaller_check(x,y) || angle2_smaller_check(x,y) + angle1_x_smaller_check(x, y) || angle2_x_smaller_check(x, y) end - def standard_arc_bounds_check(x,y) + 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 @@ -218,18 +226,14 @@ def inner_oval_difference end def calculate_inner_oval_difference - difference = style[:strokewidth].to_i * 2 - - difference = 4 if difference < 4 - - difference.to_f / 2 + [style[:strokewidth].to_f, 3.0].max end def in_bounds?(x, y) - bounds_check = standard_arc_bounds_check(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 diff --git a/shoes-core/spec/shoes/arc_spec.rb b/shoes-core/spec/shoes/arc_spec.rb index fda432e48..beff0a505 100644 --- a/shoes-core/spec/shoes/arc_spec.rb +++ b/shoes-core/spec/shoes/arc_spec.rb @@ -211,7 +211,7 @@ end describe 'in_bounds methods' do - subject(:arc) { Shoes::Arc.new(app, parent, left, top, width, height, start_angle, end_angle) } + subject(:arc) { Shoes::Arc.new(app, parent, left, top, width, height, 0, Shoes::HALF_PI) } describe '#radius_x' do it 'must return half the width' do @@ -240,13 +240,13 @@ describe '#inside_oval?' do context 'when point is inside' do it 'must return true' do - expect(arc.inside_oval??(arc.middle_x, arc.middle_y)).to eq(true) + expect(arc.inside_oval?(arc.middle_x, arc.middle_y)).to eq(true) end end context 'when point is outside' do it 'must return false' do - expect(arc.inside_oval??(arc.top, arc.left)).to eq(false) + expect(arc.inside_oval?(arc.top, arc.left)).to eq(false) end end end @@ -293,134 +293,133 @@ describe '#y_result_adjustment' do context 'when #y_adjust_negative? is false' do - before(:each){allow(arc).to receive(:y_adjust_negative?){false}} + before(:each) { allow(arc).to receive(:y_adjust_negative?) { false } } it 'should add y_result to middle_y' do - expect(arc.y_result_adjustment(1,1)).to eq(195.0) + expect(arc.y_result_adjustment(1, 1)).to eq(195.0) end end context 'when #y_adjust_negative? is true' do - before(:each){allow(arc).to receive(:y_adjust_negative?){true}} + before(:each) { allow(arc).to receive(:y_adjust_negative?) { true } } it 'should subtract y_result from middle_y' do - expect(arc.y_result_adjustment(1,1)).to eq(193.0) + expect(arc.y_result_adjustment(1, 1)).to eq(193.0) end end - end describe '#x_result_adjustment' do context 'when #x_adjust_positive? is false' do - before(:each){allow(arc).to receive(:x_adjust_positive?){false}} + before(:each) { allow(arc).to receive(:x_adjust_positive?) { false } } it 'should subtract y_result from middle_y' do - expect(arc.x_result_adjustment(1,1)).to eq(112.0) + expect(arc.x_result_adjustment(1, 1)).to eq(112.0) end end context 'when #x_adjust_positive? is true' do - before(:each){allow(arc).to receive(:x_adjust_positive?){true}} + before(:each) { allow(arc).to receive(:x_adjust_positive?) { true } } it 'should add y_result from middle_y' do - expect(arc.x_result_adjustment(1,1)).to eq(114.0) + expect(arc.x_result_adjustment(1, 1)).to eq(114.0) end end end describe '#generate_coordinates' do it 'must output a hash of the adjusted x and y coordinates' do - allow(arc).to receive(:x_result_adjustment){5.0035} - allow(arc).to receive(:y_result_adjustment){7.0034} - expect(arc.generate_coordinates(0,50,50)).to eq({x_value: 5.004, y_value: 7.003}) + allow(arc).to receive(:x_result_adjustment) { 5.0035 } + allow(arc).to receive(:y_result_adjustment) { 7.0034 } + expect(arc.generate_coordinates(0, 50, 50)).to eq(x_value: 5.004, y_value: 7.003) end end describe '#angle_base_coords' do it 'must find the coordinates for a given angle' do - expect(arc.angle_base_coords(1.5708)).to eq({:x_value=>112.999, :y_value=>344.0}) + expect(arc.angle_base_coords(1.5708)).to eq(x_value: 112.999, y_value: 195.5) end end describe '#angle1_coordinates' do it 'must call angle_base_coords' do - allow(arc).to receive(:angle_base_coords){|e| e} + allow(arc).to receive(:angle_base_coords) { |e| e } expect(arc.angle1_coordinates).to eq(arc.angle1) end end describe '#angle2_coordinates' do it 'must call angle_base_coords' do - allow(arc).to receive(:angle_base_coords){|e| e} + allow(arc).to receive(:angle_base_coords) { |e| e } expect(arc.angle2_coordinates).to eq(arc.angle2) end end describe '#angle1_x' do it 'must grab :x_value from @angle1_coordinates' do - allow(arc).to receive(:angle1_coordinates){{x_value: 55}} + allow(arc).to receive(:angle1_coordinates) { {x_value: 55 } } expect(arc.angle1_x).to eq(55) end end describe '#angle1_y' do it 'must grab :y_value from @angle1_coordinates' do - allow(arc).to receive(:angle1_coordinates){{y_value: 55}} + allow(arc).to receive(:angle1_coordinates) { {y_value: 55 } } expect(arc.angle1_y).to eq(55) end end describe '#angle2_x' do it 'must grab :x_value from @angle2_coordinates' do - allow(arc).to receive(:angle2_coordinates){{x_value: 55}} + allow(arc).to receive(:angle2_coordinates) { {x_value: 55 } } expect(arc.angle2_x).to eq(55) end end describe '#angle2_y' do it 'must grab :y_value from @angle2_coordinates' do - allow(arc).to receive(:angle2_coordinates){{y_value: 55}} + allow(arc).to receive(:angle2_coordinates) { {y_value: 55 } } expect(arc.angle2_y).to eq(55) end end describe '#slope_of_angles' do it 'must calculate the slope value from the angles' do - expect(arc.slope_of_angles).to eq(-0.0) + expect(arc.slope_of_angles).to eq(-2.245508982035907) end end describe '#b_value_for_line' do it 'must calculate the b value of the line equation from its angles' do - expect(arc.b_value_for_line).to eq(194.0 ) + expect(arc.b_value_for_line).to eq(449.2402694610754) end end describe '#vertical_check' do context 'if angle_x is same as input' do it 'must return :on' do - allow(arc).to receive(:angle1_x){55} + allow(arc).to receive(:angle1_x) { 55 } expect(arc.vertical_check(55)).to eq(:on) end end context 'if angle_x is less than input' do it 'must return :above' do - allow(arc).to receive(:angle1_x){1} + allow(arc).to receive(:angle1_x) { 1 } expect(arc.vertical_check(55)).to eq(:above) end end context 'if angle_x is more than input' do it 'must return :below' do - allow(arc).to receive(:angle1_x){100} + allow(arc).to receive(:angle1_x) { 100 } expect(arc.vertical_check(55)).to eq(:below) end end end describe '#normal_above_below_check' do - before(:each){allow(arc).to receive(:b_value_for_line){0}} + before(:each) { allow(arc).to receive(:b_value_for_line) { 0 } } context 'if right_side_of_equation is same as y_input' do it 'must return :on' do @@ -442,12 +441,12 @@ end describe '#above_below_on' do - before(:each){allow(arc).to receive(:vertical_check){|x| x}} - before(:each){allow(arc).to receive(:normal_above_below_check){|x,y| [x,y]}} + before(:each) { allow(arc).to receive(:vertical_check) { |x| x } } + before(:each) { allow(arc).to receive(:normal_above_below_check) { |x, y| [x, y] } } context 'when mx_value over a million' do context 'when #slope_of_angles is positive' do - before(:each){allow(arc).to receive(:slope_of_angles){1}} + before(:each) { allow(arc).to receive(:slope_of_angles) { 1 } } it 'must call and return #vertical_check' do expect(arc.above_below_on(6_000_000, 100)).to eq(6_000_000) @@ -455,7 +454,7 @@ end context 'when #slope_of_angles is negative' do - before(:each){allow(arc).to receive(:slope_of_angles){-1}} + before(:each) { allow(arc).to receive(:slope_of_angles) { -1 } } it 'must call and return #vertical_check' do expect(arc.above_below_on(-6_000_000, 100)).to eq(-6_000_000) @@ -465,24 +464,24 @@ context 'when mx_value under a million' do it 'must call #normal_above_below_check' do - allow(arc).to receive(:slope_of_angles){1} + allow(arc).to receive(:slope_of_angles) { 1 } expect(arc.above_below_on(60, 100)).to eq([60, 100]) end end end describe '#in_bounds?' do - context 'when #inside_oval?? is false' do - it 'must return nil' do - allow(arc).to receive(:inside_oval??){false} - expect(arc.in_bounds?(1,1)).to eq(nil) + context 'when #inside_oval? is false' do + it 'must return false' do + allow(arc).to receive(:inside_oval?) { false } + expect(arc.in_bounds?(1, 1)).to eq(false) end end - context 'when #inside_oval?? is true' do - it 'must return nil' do - allow(arc).to receive(:inside_oval??){true} - expect(arc.in_bounds?(1,1)).to eq(nil) + context 'when #inside_oval? is true' do + it 'must return false' do + allow(arc).to receive(:inside_oval?) { true } + expect(arc.in_bounds?(1, 1)).to eq(false) end end end From c5410efc8e432243044aefefe54aadc5aa754609 Mon Sep 17 00:00:00 2001 From: Eric Stensland Date: Sun, 8 Apr 2018 07:04:18 -0500 Subject: [PATCH 09/11] Issue 778 arc within bounds specs and code refactoring --- shoes-core/lib/shoes/arc.rb | 12 +- shoes-core/spec/shoes/arc_spec.rb | 207 +++++++++++++++++++++++++++++- 2 files changed, 208 insertions(+), 11 deletions(-) diff --git a/shoes-core/lib/shoes/arc.rb b/shoes-core/lib/shoes/arc.rb index f4bc53207..3d994793b 100644 --- a/shoes-core/lib/shoes/arc.rb +++ b/shoes-core/lib/shoes/arc.rb @@ -204,19 +204,19 @@ def above_below_on(x_input, y_input) end end - def angle2_x_smaller_check(x, y) - above_below_on(x, y) == :below && angle1_x > angle2_x - 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) + 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 @@ -230,7 +230,7 @@ def calculate_inner_oval_difference end def in_bounds?(x, y) - bounds_check = standard_arc_bounds_check(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 diff --git a/shoes-core/spec/shoes/arc_spec.rb b/shoes-core/spec/shoes/arc_spec.rb index beff0a505..246d7f3bb 100644 --- a/shoes-core/spec/shoes/arc_spec.rb +++ b/shoes-core/spec/shoes/arc_spec.rb @@ -45,6 +45,7 @@ subject(:arc) { Shoes::Arc.new(app, parent, left, top, width, height, start_angle, end_angle, wedge: true) } its(:wedge) { should eq(true) } + its(:wedge?) { should eq(true) } end describe "dsl" do @@ -390,8 +391,19 @@ end describe '#b_value_for_line' do - it 'must calculate the b value of the line equation from its angles' do - expect(arc.b_value_for_line).to eq(449.2402694610754) + context 'when #mx_value is not infinity' do + it 'must calculate the b value of the line equation from its angles' do + expect(arc.b_value_for_line).to eq(449.2402694610754) + end + end + + context 'when #mx_value is infinity' do + it 'must return angle1_y' do + allow(arc).to receive(:angle1_x) { Float::INFINITY } + allow(arc).to receive(:slope_of_angles) { Float::INFINITY } + allow(arc).to receive(:angle1_y) { 55 } + expect(arc.b_value_for_line).to eq(55) + end end end @@ -470,20 +482,205 @@ end end - describe '#in_bounds?' do + describe '#standard_arc_bounds_check?' do context 'when #inside_oval? is false' do it 'must return false' do allow(arc).to receive(:inside_oval?) { false } - expect(arc.in_bounds?(1, 1)).to eq(false) + expect(arc.standard_arc_bounds_check?(1, 1)).to eq(false) end end context 'when #inside_oval? is true' do + before(:each) { allow(arc).to receive(:inside_oval?) { true } } + context 'when #on_shaded_part? is false' do + it 'must return false' do + allow(arc).to receive(:on_shaded_part?) { false } + expect(arc.standard_arc_bounds_check?(1, 1)).to eq(false) + end + end + + context 'when #on_shaded_part? is true' do + it 'must return true' do + allow(arc).to receive(:on_shaded_part?) { true } + expect(arc.standard_arc_bounds_check?(1, 1)).to eq(true) + end + end + end + end + + describe '#on_shaded_part?' do + context 'when #angle2_x_smaller_check is true' do + it 'must return true' do + allow(arc).to receive(:angle1_x_smaller_check) { false } + allow(arc).to receive(:angle2_x_smaller_check) { true } + expect(arc.on_shaded_part?(1, 1)).to eq(true) + end + end + + context 'when #angle2_x_smaller_check is true' do + it 'must return true' do + allow(arc).to receive(:angle1_x_smaller_check) { true } + allow(arc).to receive(:angle2_x_smaller_check) { false } + expect(arc.on_shaded_part?(1, 1)).to eq(true) + end + end + + context 'when both #angle2_x_smaller_check and #angle1_x_smaller_check are false' do it 'must return false' do - allow(arc).to receive(:inside_oval?) { true } + allow(arc).to receive(:angle1_x_smaller_check) { false } + allow(arc).to receive(:angle2_x_smaller_check) { false } + expect(arc.on_shaded_part?(1, 1)).to eq(false) + end + end + end + + describe '#angle1_x_smaller_check' do + context 'when #above_below_on is :below' do + it 'must return false' do + allow(arc).to receive(:above_below_on) { :below } + allow(arc).to receive(:angle1_x) { 1 } + allow(arc).to receive(:angle2_x) { 2 } + expect(arc.angle1_x_smaller_check(1, 1)).to eq(false) + end + end + + context 'when #angle1_x is greater than #angle2_x' do + it 'must return false' do + allow(arc).to receive(:above_below_on) { :above } + allow(arc).to receive(:angle1_x) { 2 } + allow(arc).to receive(:angle2_x) { 1 } + expect(arc.angle1_x_smaller_check(1, 1)).to eq(false) + end + end + + context 'when #above_below_on is :above and #angle1_x is less than #angle2_x' do + it 'must return false' do + allow(arc).to receive(:above_below_on) { :above } + allow(arc).to receive(:angle1_x) { 1 } + allow(arc).to receive(:angle2_x) { 2 } + expect(arc.angle1_x_smaller_check(1, 1)).to eq(true) + end + end + end + + describe '#angle2_x_smaller_check' do + context 'when #above_below_on is :above' do + it 'must return false' do + allow(arc).to receive(:above_below_on) { :above } + allow(arc).to receive(:angle1_x) { 2 } + allow(arc).to receive(:angle2_x) { 1 } + expect(arc.angle2_x_smaller_check(1, 1)).to eq(false) + end + end + + context 'when #angle1_x is less than #angle2_x' do + it 'must return false' do + allow(arc).to receive(:above_below_on) { :below } + allow(arc).to receive(:angle1_x) { 1 } + allow(arc).to receive(:angle2_x) { 2 } + expect(arc.angle2_x_smaller_check(1, 1)).to eq(false) + end + end + + context 'when #above_below_on is :below and #angle1_x is greater than #angle2_x' do + it 'must return false' do + allow(arc).to receive(:above_below_on) { :below } + allow(arc).to receive(:angle1_x) { 2 } + allow(arc).to receive(:angle2_x) { 1 } + expect(arc.angle2_x_smaller_check(1, 1)).to eq(true) + end + end + end + + + describe '#inner_oval_difference' do + it 'must call #calculate_inner_oval_difference only once' do + allow(arc).to receive(:calculate_inner_oval_difference) { 1 } + arc.should_receive(:calculate_inner_oval_difference).once + arc.inner_oval_difference + arc.inner_oval_difference + end + end + + describe '#in_bounds?' do + context 'when #standard_arc_bounds_check? is false' do + it 'must return false' do + allow(arc).to receive(:standard_arc_bounds_check?) { false } expect(arc.in_bounds?(1, 1)).to eq(false) end end + + context 'when #standard_arc_bounds_check? is true' do + before(:each) { allow(arc).to receive(:standard_arc_bounds_check?) { true } } + context 'when #style[:fill] is present' do + it 'must return false' do + allow(arc).to receive(:style) { {fill: 1} } + expect(arc.in_bounds?(1, 1)).to eq(true) + end + end + + context 'when #style[:fill] is nil' do + before(:each) { allow(arc).to receive(:style) { {} } } + context 'when #inside_inner_oval? is false' do + it 'must return true' do + allow(arc).to receive(:inside_inner_oval?) { false } + expect(arc.in_bounds?(1, 1)).to eq(true) + end + end + + context 'when #inside_inner_oval? is true' do + it 'must return false' do + allow(arc).to receive(:inside_inner_oval?) { true } + expect(arc.in_bounds?(1, 1)).to eq(false) + end + end + end + end + end + + describe '#calculate_inner_oval_difference' do + context 'when style[:strokewidth] is under 3.0' do + it 'must return 3' do + arc.instance_variable_set(:@style, strokewidth: 2) + expect(arc.calculate_inner_oval_difference).to eq(3.0) + end + end + + context 'when style[:strokewidth] is more than 3.0' do + it 'must return style[:strokewidth]' do + arc.instance_variable_set(:@style, strokewidth: 5) + expect(arc.calculate_inner_oval_difference).to eq(5.0) + end + end + end + + describe '#inner_oval_axis_fraction' do + it 'must call #oval_axis_fraction with the input inner_oval_difference' do + allow(arc).to receive(:inner_oval_difference) { :test_input } + expect(arc).to receive(:oval_axis_fraction).with(:a, :b, :test_input) + arc.inner_oval_axis_fraction(:a, :b) + end + end + + describe '#inside_inner_oval?' do + before(:each) { allow(arc).to receive(:inner_oval_axis_fraction) { |_, a| a } } + context 'when sum is less than one' do + it 'must return true' do + expect(arc.inside_inner_oval?(0.25, 0.25)).to eq(true) + end + end + + context 'when sum is greater than one' do + it 'must return false' do + expect(arc.inside_inner_oval?(1.25, 0.25)).to eq(false) + end + end + + context 'when sum is one' do + it 'must return true' do + expect(arc.inside_inner_oval?(0.75, 0.25)).to eq(true) + end + end end end end From d12f71f61533f994f0300095ec5e10e24e1af9c2 Mon Sep 17 00:00:00 2001 From: Eric Stensland Date: Mon, 9 Apr 2018 18:57:20 -0500 Subject: [PATCH 10/11] fixed errors and added specs to catch it --- shoes-core/lib/shoes/arc.rb | 6 +- shoes-core/spec/shoes/arc_spec.rb | 146 +++++++++++++++++++++++++----- 2 files changed, 126 insertions(+), 26 deletions(-) diff --git a/shoes-core/lib/shoes/arc.rb b/shoes-core/lib/shoes/arc.rb index 1220a50e8..8e3337056 100644 --- a/shoes-core/lib/shoes/arc.rb +++ b/shoes-core/lib/shoes/arc.rb @@ -33,7 +33,7 @@ def wedge? wedge end - # Access the center point of the arc. + # Access the center point of the arc. # # @return [Shoes::Point] A point at the center of the arc. # @example @@ -154,8 +154,8 @@ def angle_base_coords(given_angle) 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) + 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 diff --git a/shoes-core/spec/shoes/arc_spec.rb b/shoes-core/spec/shoes/arc_spec.rb index d0f46d68f..0e9b674d6 100644 --- a/shoes-core/spec/shoes/arc_spec.rb +++ b/shoes-core/spec/shoes/arc_spec.rb @@ -364,7 +364,7 @@ describe '#angle_base_coords' do it 'must find the coordinates for a given angle' do - expect(arc.angle_base_coords(1.5708)).to eq(x_value: 112.999, y_value: 195.5) + expect(arc.angle_base_coords(1.5708)).to eq(x_value: 112.999, y_value: 344.0) end end @@ -412,14 +412,14 @@ describe '#slope_of_angles' do it 'must calculate the slope value from the angles' do - expect(arc.slope_of_angles).to eq(-2.245508982035907) + expect(arc.slope_of_angles).to eq(-1.4999850001499984) end end describe '#b_value_for_line' do context 'when #mx_value is not infinity' do it 'must calculate the b value of the line equation from its angles' do - expect(arc.b_value_for_line).to eq(449.2402694610754) + expect(arc.b_value_for_line).to eq(513.4968050319496) end end @@ -618,46 +618,146 @@ end end - describe '#inner_oval_difference' do it 'must call #calculate_inner_oval_difference only once' do allow(arc).to receive(:calculate_inner_oval_difference) { 1 } - arc.should_receive(:calculate_inner_oval_difference).once + expect(arc).to receive(:calculate_inner_oval_difference).once arc.inner_oval_difference arc.inner_oval_difference end end describe '#in_bounds?' do - context 'when #standard_arc_bounds_check? is false' do - it 'must return false' do - allow(arc).to receive(:standard_arc_bounds_check?) { false } - expect(arc.in_bounds?(1, 1)).to eq(false) + context 'without stubbing methods' do + context 'with fill' do + context 'with standard angles' do + let(:test_arc) { Shoes::Arc.new(app, parent, 30, 30, 360, 360, 0.0, 4.71238898038469) } + context 'when point is inside the arc' do + it 'must return true' do + expect(test_arc.in_bounds?(245.0, 158.0)).to eq(true) + end + end + + context 'when point is outside the arc' do + it 'must return false' do + expect(test_arc.in_bounds?(215.0, 31.0)).to eq(false) + end + end + end + + context 'with negative angles' do + let(:test_arc) { Shoes::Arc.new(app, parent, 30, 30, 360, 360, -7.330382858376184, -5.759586531581287) } + context 'when point is inside the arc' do + it 'must return true' do + expect(test_arc.in_bounds?(350.0, 170.0)).to eq(true) + end + end + + context 'when point is outside the arc' do + it 'must return false' do + expect(test_arc.in_bounds?(385.0, 305.0)).to eq(false) + end + end + end + + context 'with an angle over 2 PI' do + let(:test_arc) { Shoes::Arc.new(app, parent, 210, 210, 200, 200, 6.8067840827778845, 5.235987755982989) } + context 'when point is inside the arc' do + it 'must return true' do + expect(test_arc.in_bounds?(295.0, 239.0)).to eq(true) + end + end + + context 'when point is outside the arc' do + it 'must return false' do + expect(test_arc.in_bounds?(385.0, 305.0)).to eq(false) + end + end + end + end + + context 'with fill' do + context 'with standard angles' do + let(:test_arc) { Shoes::Arc.new(app, parent, 30, 30, 360, 360, 0.0, 4.71238898038469) } + before(:each) { allow(test_arc).to receive(:style) { {fill: nil} } } + context 'when point is inside the arc' do + it 'must return true' do + expect(test_arc.in_bounds?(210.5, 30.5)).to eq(true) + end + end + + context 'when point is outside the arc' do + it 'must return false' do + expect(test_arc.in_bounds?(245.0, 158.0)).to eq(false) + end + end + end + + context 'with negative angles' do + let(:test_arc) { Shoes::Arc.new(app, parent, 30, 30, 360, 360, -7.330382858376184, -5.759586531581287) } + before(:each) { allow(test_arc).to receive(:style) { {fill: nil} } } + context 'when point is inside the arc' do + it 'must return true' do + expect(test_arc.in_bounds?(301, 55)).to eq(true) + end + end + + context 'when point is outside the arc' do + it 'must return false' do + expect(test_arc.in_bounds?(350.0, 170.0)).to eq(false) + end + end + end + + context 'with an angle over 2 PI' do + let(:test_arc) { Shoes::Arc.new(app, parent, 210, 210, 200, 200, 6.8067840827778845, 5.235987755982989) } + before(:each) { allow(test_arc).to receive(:style) { {fill: nil} } } + context 'when point is inside the arc' do + it 'must return true' do + expect(test_arc.in_bounds?(210.0, 310.0)).to eq(true) + end + end + + context 'when point is outside the arc' do + it 'must return false' do + expect(test_arc.in_bounds?(295.0, 239.0)).to eq(false) + end + end + end end end - context 'when #standard_arc_bounds_check? is true' do - before(:each) { allow(arc).to receive(:standard_arc_bounds_check?) { true } } - context 'when #style[:fill] is present' do + context 'with stubging methods' do + context 'when #standard_arc_bounds_check? is false' do it 'must return false' do - allow(arc).to receive(:style) { {fill: 1} } - expect(arc.in_bounds?(1, 1)).to eq(true) + allow(arc).to receive(:standard_arc_bounds_check?) { false } + expect(arc.in_bounds?(1, 1)).to eq(false) end end - context 'when #style[:fill] is nil' do - before(:each) { allow(arc).to receive(:style) { {} } } - context 'when #inside_inner_oval? is false' do - it 'must return true' do - allow(arc).to receive(:inside_inner_oval?) { false } + context 'when #standard_arc_bounds_check? is true' do + before(:each) { allow(arc).to receive(:standard_arc_bounds_check?) { true } } + context 'when #style[:fill] is present' do + it 'must return false' do + allow(arc).to receive(:style) { {fill: 1} } expect(arc.in_bounds?(1, 1)).to eq(true) end end - context 'when #inside_inner_oval? is true' do - it 'must return false' do - allow(arc).to receive(:inside_inner_oval?) { true } - expect(arc.in_bounds?(1, 1)).to eq(false) + context 'when #style[:fill] is nil' do + before(:each) { allow(arc).to receive(:style) { {} } } + context 'when #inside_inner_oval? is false' do + it 'must return true' do + allow(arc).to receive(:inside_inner_oval?) { false } + expect(arc.in_bounds?(1, 1)).to eq(true) + end + end + + context 'when #inside_inner_oval? is true' do + it 'must return false' do + allow(arc).to receive(:inside_inner_oval?) { true } + expect(arc.in_bounds?(1, 1)).to eq(false) + end end end end From d486a7ff429ea32de002694a04331a4d5eac359d Mon Sep 17 00:00:00 2001 From: Eric Stensland Date: Sat, 5 May 2018 21:50:06 -0500 Subject: [PATCH 11/11] added comments and cleaned up ranges --- shoes-core/lib/shoes/arc.rb | 54 +++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/shoes-core/lib/shoes/arc.rb b/shoes-core/lib/shoes/arc.rb index 8e3337056..e61fd9d90 100644 --- a/shoes-core/lib/shoes/arc.rb +++ b/shoes-core/lib/shoes/arc.rb @@ -84,14 +84,21 @@ def inner_oval_axis_fraction(axis, input) end def inside_oval?(x, y) - # https://math.stackexchange.com/questions/76457/check-if-a-point-is-within-an-ellipse + # The following equation (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 + # + # The above equation determines if a pair of coordinates are located in an oval. + # Rx = Radius X; Ry = Radius Y; h,k is the origin; x,y are the input coordinate (oval_axis_fraction(:x, x) + oval_axis_fraction(:y, y)) <= 1 end def inside_inner_oval?(x, y) + # For use when style[:fill] is nil, meaning it is just the edge of the arc + # This uses shared code with #inside_oval? to check if a point is also inside a second smaller oval. + # The smaller oval signifies the no_fill area of the arc x_side = inner_oval_axis_fraction(:x, x) y_side = inner_oval_axis_fraction(:y, y) x_side + y_side <= 1 @@ -111,14 +118,17 @@ def adjust_angle(input_angle) end def y_adjust_negative?(input_angle) - ((input_angle >= 0) && (input_angle <= 1.5708)) || ((input_angle >= 4.71239) && (input_angle <= 6.28319)) + in_first_quater = (0..Shoes::HALF_PI).cover?(input_angle) + in_last_quarter = ((Shoes::HALF_PI * 3)..Shoes::TWO_PI).cover?(input_angle) + in_first_quater || in_last_quarter end def x_adjust_positive?(input_angle) - (input_angle >= 0) && (input_angle <= 3.14159) + (0..Math::PI).cover?(input_angle) end def y_result_adjustment(input_angle, y_result) + # This checks which way from center the angle is if y_adjust_negative?(input_angle) middle_y - y_result else @@ -127,6 +137,7 @@ def y_result_adjustment(input_angle, y_result) end def x_result_adjustment(input_angle, x_result) + # This checks which way from center the angle is if x_adjust_positive?(input_angle) middle_x + x_result else @@ -135,6 +146,7 @@ def x_result_adjustment(input_angle, x_result) end def generate_coordinates(input_angle, x_result, y_result) + # This turns results on distance from center into actual coordinates x_result = x_result_adjustment(input_angle, x_result).round(3) y_result = y_result_adjustment(input_angle, y_result).round(3) @@ -149,17 +161,22 @@ def tangent_squared(input_angle) 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 + # First the angle must be rotated to work with shoes, also be normalized into the 0..2PI range modded_angle = adjust_angle(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 the below formula to find the point values from the center + 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) + # With distance from center for x and y found, we turn that into coordinates: generate_coordinates(modded_angle, x_result, y_result) end + # The following angle coordinate methods are to simply clean up code def angle1_coordinates @angle1_coordinates ||= angle_base_coords(angle1) end @@ -194,6 +211,7 @@ def b_value_for_line # THEN b = y - mx mx_value = (angle1_x * slope_of_angles) + # If value of mx is Infinity, the line is horizontal, just return angle1_y if mx_value == Float::INFINITY angle1_y else @@ -202,7 +220,7 @@ def b_value_for_line end def vertical_check(x_input) - # The above/below are + # Since line is vertical, we can just compare x values and map to "above" or "below" if angle1_x == x_input :on elsif angle1_x < x_input @@ -213,41 +231,48 @@ def vertical_check(x_input) end def normal_above_below_check(mx_value, y_input) - right_side_of_equation = mx_value + b_value_for_line + # SINCE y = mx + b + # We solve for where the y value is for the line between the arc's angles given the slope * input x + y_value_arc_line = mx_value + b_value_for_line - if right_side_of_equation == y_input - # If input y is same...input is on the line + if y_value_arc_line == y_input + # If input y is same as y on the arc line ...input is on the line :on - elsif right_side_of_equation > y_input - # If input y is more, point is above the line + elsif y_value_arc_line > y_input + # If input y is more than y on the arc line, point is above the line :above else - # If input y is less, point is below the line + # If input y is less than y on the arc line, point is below the line :below end end def above_below_on(x_input, y_input) + # SINCE y = mx + b + # We get mx_value of the line between arc points at the input of x 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 + # 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 + # If standard slope...find what the y value between arc points would be given the input x normal_above_below_check(mx_value, y_input) end end def angle1_x_smaller_check(x, y) + # if angle 1 is smaller, point should be above above_below_on(x, y) == :above && angle1_x < angle2_x end def angle2_x_smaller_check(x, y) + # if angle 2 is smaller, point should be below above_below_on(x, y) == :below && angle1_x > angle2_x end def on_shaded_part?(x, y) + # This checks if point is on the correct side of the oval, based on the lines between the arc points angle1_x_smaller_check(x, y) || angle2_x_smaller_check(x, y) end @@ -257,6 +282,7 @@ def standard_arc_bounds_check?(x, y) end def inner_oval_difference + # this calculates how much smaller the inner oval must be to test a no_fill oval @inner_oval_difference ||= calculate_inner_oval_difference end