Avoid Overlapping Shapes


Author
Message
Alyson
Alyson
New Member (48 reputation)New Member (48 reputation)New Member (48 reputation)New Member (48 reputation)New Member (48 reputation)New Member (48 reputation)New Member (48 reputation)New Member (48 reputation)New Member (48 reputation)
Group: Forum Members
Posts: 4, Visits: 124
Hello,

I am trying to make a program where a random number of randomly sized circles appear on the screen in random positions. However, I do not want the circles to overlap. I know how to avoid having the circles have the same hposition and vposition, but I cannot figure out how to avoid overlap since the sizes are randomized (so even though no two circles can be at (20%, 20%), they could overlap since the second circle could be at (21%, 21%)). Is it possible to do this, and if so, how? Here is the code (I haven't written the code for all circles yet but they will be exactly the same as dots 1 and 2):

<defaults>
/ inputdevice = keyboard
/ fontstyle = ("Verdana", -13, false, false, false, false, 5, 0)
/ screencolor = grey
</defaults>

********STIMULI***********

<item text>
/1 = "+"
</item>

<text fixation>
/items = text
/select = 1
/hposition = 50%
/vposition = 50%
/fontstyle = ("Arial", 40, false, false, false, false, 5, 0)
/ txbgcolor = transparent
</text>

<shape dot1>
/shape = circle
/height = list.dot_size.nextvalue
/color = counter.dot_color1.selectedvalue
/hposition = list.h_position.nextvalue
/vposition = list.v_position.nextvalue
</shape>

<shape dot2>
/shape = circle
/height = list.dot_size.nextvalue
/color = counter.dot_color2.selectedvalue
/hposition = list.h_position.nextvalue
/vposition = list.v_position.nextvalue
</shape>

<list h_position>
/items = (10-90)
/ selectionrate = always
/selectionmode = random
</list>

<list v_position>
/items = (10-90)
/selectionrate = always
/selectionmode = random
</list>

<list dot_size>
/items = (20-40)
/selectionrate = always
/selectionmode = random
</list>

<counter dot_color1>
/items = (blue, yellow)
/select = noreplace
/ selectionrate = trial
</counter>

<counter dot_color2>
/items = (blue, yellow)
/select = noreplace
/not = (counter.dot_color1)
/selectionrate = trial
</counter>

********EXPERIMENT***********

<expt intro>
/blocks = [
    1=intro
]
</expt>

<block intro>
/trials = [
    1=intro
]
</block>

<trial intro>
/ stimulusframes = [1=dot1, dot2, fixation]
/validresponse = (203, 205)
</trial>
Dave
Dave
Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)
Group: Administrators
Posts: 12K, Visits: 98K
Alyson - 10/12/2021
Hello,

I am trying to make a program where a random number of randomly sized circles appear on the screen in random positions. However, I do not want the circles to overlap. I know how to avoid having the circles have the same hposition and vposition, but I cannot figure out how to avoid overlap since the sizes are randomized (so even though no two circles can be at (20%, 20%), they could overlap since the second circle could be at (21%, 21%)). Is it possible to do this, and if so, how? Here is the code (I haven't written the code for all circles yet but they will be exactly the same as dots 1 and 2):

<defaults>
/ inputdevice = keyboard
/ fontstyle = ("Verdana", -13, false, false, false, false, 5, 0)
/ screencolor = grey
</defaults>

********STIMULI***********

<item text>
/1 = "+"
</item>

<text fixation>
/items = text
/select = 1
/hposition = 50%
/vposition = 50%
/fontstyle = ("Arial", 40, false, false, false, false, 5, 0)
/ txbgcolor = transparent
</text>

<shape dot1>
/shape = circle
/height = list.dot_size.nextvalue
/color = counter.dot_color1.selectedvalue
/hposition = list.h_position.nextvalue
/vposition = list.v_position.nextvalue
</shape>

<shape dot2>
/shape = circle
/height = list.dot_size.nextvalue
/color = counter.dot_color2.selectedvalue
/hposition = list.h_position.nextvalue
/vposition = list.v_position.nextvalue
</shape>

<list h_position>
/items = (10-90)
/ selectionrate = always
/selectionmode = random
</list>

<list v_position>
/items = (10-90)
/selectionrate = always
/selectionmode = random
</list>

<list dot_size>
/items = (20-40)
/selectionrate = always
/selectionmode = random
</list>

<counter dot_color1>
/items = (blue, yellow)
/select = noreplace
/ selectionrate = trial
</counter>

<counter dot_color2>
/items = (blue, yellow)
/select = noreplace
/not = (counter.dot_color1)
/selectionrate = trial
</counter>

********EXPERIMENT***********

<expt intro>
/blocks = [
    1=intro
]
</expt>

<block intro>
/trials = [
    1=intro
]
</block>

<trial intro>
/ stimulusframes = [1=dot1, dot2, fixation]
/validresponse = (203, 205)
</trial>

It may be possible to do if the total number of circles is relatively small. The answer to the how question then is: math. I.e. select a size and position for the 1st circle, calculate the screen area it covers, select a size and position for the 2nd circle, calculate the screen area it covers, and if it overlaps with that of the 1st, select a new size and/or position for the 2nd circle (or shif its position) until there's no more overlap. For the 3rd circle then, you'd have to perform checks against both the 1st and 2nd circle. If the total amount of circles is large, this quickly becomes infeasible.

Calculating the distance between two circles A and B's centers, you have three cases:
- If the distance is exactly equal to the radius of circle A + radius of circle B, then the two circles touch each other.
- If the distance is smaller than the sum of the two radii, then the two circles intersect.
- If the distance is larger than the sum of the two radii, then the two circles do not touch or intersect.
Edited 3 Years Ago by Dave
Dave
Dave
Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)
Group: Administrators
Posts: 12K, Visits: 98K
Dave - 10/12/2021
Alyson - 10/12/2021
Hello,

I am trying to make a program where a random number of randomly sized circles appear on the screen in random positions. However, I do not want the circles to overlap. I know how to avoid having the circles have the same hposition and vposition, but I cannot figure out how to avoid overlap since the sizes are randomized (so even though no two circles can be at (20%, 20%), they could overlap since the second circle could be at (21%, 21%)). Is it possible to do this, and if so, how? Here is the code (I haven't written the code for all circles yet but they will be exactly the same as dots 1 and 2):

<defaults>
/ inputdevice = keyboard
/ fontstyle = ("Verdana", -13, false, false, false, false, 5, 0)
/ screencolor = grey
</defaults>

********STIMULI***********

<item text>
/1 = "+"
</item>

<text fixation>
/items = text
/select = 1
/hposition = 50%
/vposition = 50%
/fontstyle = ("Arial", 40, false, false, false, false, 5, 0)
/ txbgcolor = transparent
</text>

<shape dot1>
/shape = circle
/height = list.dot_size.nextvalue
/color = counter.dot_color1.selectedvalue
/hposition = list.h_position.nextvalue
/vposition = list.v_position.nextvalue
</shape>

<shape dot2>
/shape = circle
/height = list.dot_size.nextvalue
/color = counter.dot_color2.selectedvalue
/hposition = list.h_position.nextvalue
/vposition = list.v_position.nextvalue
</shape>

<list h_position>
/items = (10-90)
/ selectionrate = always
/selectionmode = random
</list>

<list v_position>
/items = (10-90)
/selectionrate = always
/selectionmode = random
</list>

<list dot_size>
/items = (20-40)
/selectionrate = always
/selectionmode = random
</list>

<counter dot_color1>
/items = (blue, yellow)
/select = noreplace
/ selectionrate = trial
</counter>

<counter dot_color2>
/items = (blue, yellow)
/select = noreplace
/not = (counter.dot_color1)
/selectionrate = trial
</counter>

********EXPERIMENT***********

<expt intro>
/blocks = [
    1=intro
]
</expt>

<block intro>
/trials = [
    1=intro
]
</block>

<trial intro>
/ stimulusframes = [1=dot1, dot2, fixation]
/validresponse = (203, 205)
</trial>

It may be possible to do if the total number of circles is relatively small. The answer to the how question then is: math. I.e. select a size and position for the 1st circle, calculate the screen area it covers, select a size and position for the 2nd circle, calculate the screen area it covers, and if it overlaps with that of the 1st, select a new size and/or position for the 2nd circle (or shif its position) until there's no more overlap. For the 3rd circle then, you'd have to perform checks against both the 1st and 2nd circle. If the total amount of circles is large, this quickly becomes infeasible.

Calculating the distance between two circles A and B's centers, you have three cases:
- If the distance is exactly equal to the radius of circle A + radius of circle B, then the two circles touch each other.
- If the distance is smaller than the sum of the two radii, then the two circles intersect.
- If the distance is larger than the sum of the two radii, then the two circles do not touch or intersect.

Here's a quick illustration:
<defaults>
/ inputdevice = keyboard
/ fontstyle = ("Verdana", -13, false, false, false, false, 5, 0)
/ screencolor = grey
</defaults>

********STIMULI***********

<item text>
/1 = "+"
</item>

<text fixation>
/items = text
/select = 1
/hposition = 50%
/vposition = 50%
/fontstyle = ("Arial", 40, false, false, false, false, 5, 0)
/ txbgcolor = transparent
</text>

<shape dot1>
/shape = circle
/height = values.dot1r*2
/color = counter.dot_color1.selectedvalue
/hposition = values.dot1x
/vposition = values.dot1y
</shape>

<shape dot2>
/shape = circle
/height = values.dot2r*2
/color = counter.dot_color2.selectedvalue
/hposition = values.dot2x
/vposition = values.dot2y
</shape>

<list h_position>
/items = (30-70) // smaller range for illustration purposes: increases likelihood of dots intersecting or touching
//items = (10-90)
/ selectionrate = always
/selectionmode = random
</list>

<list v_position>
/items = (30-70) // smaller range for illustration purposes: increases likelihood of dots intersecting or touching
//items = (10-90)
/selectionrate = always
/selectionmode = random
</list>

<list dot_size>
/items = (20-40)
/selectionrate = always
/selectionmode = random
</list>

<counter dot_color1>
/items = (blue, yellow)
/select = noreplace
/ selectionrate = trial
</counter>

<counter dot_color2>
/items = (blue, yellow)
/select = noreplace
/not = (counter.dot_color1)
/selectionrate = trial
</counter>

********EXPERIMENT***********

<expt intro>
/blocks = [
  1=intro
]
</expt>

<block intro>
/trials = [
  1-20=intro;
]
</block>

<trial intro>
/ ontrialbegin = [
    values.dot1r = list.dot_size.nextvalue / 2; //radius 1
    values.dot1x = list.h_position.nextvalue; // x1
    values.dot1y = list.v_position.nextvalue; // y1
    
    values.dot2r = list.dot_size.nextvalue / 2; // radius 2
    values.dot2x = list.h_position.nextvalue; // x2
    values.dot2y = list.v_position.nextvalue; // y2
    
    // circles aren't supposed to touch or intersect:
    // while euclidean distance between dot 1 and 2 is less than (dots intersect)
    // or equal to (dots touch) sum of their radii, pick a new size and position for dot 2.
    if (parameters.disallow_intersection) {
        while (sqrt((values.dot1x - values.dot2x) * (values.dot1x - values.dot2x) + (values.dot1y - values.dot2y) * (values.dot1y - values.dot2y)) <= (values.dot1r + values.dot2r)) {
            values.dot2r = list.dot_size.nextvalue /2;
            values.dot2x = list.h_position.nextvalue;
            values.dot2y = list.v_position.nextvalue;
        };
    };
]
/ stimulusframes = [1=dot1, dot2, fixation]
/validresponse = (203, 205)
</trial>

<values>
/ dot1r = 0
/ dot1x = 0
/ dot1y = 0

/ dot2r = 0
/ dot2x = 0
/ dot2y = 0
</values>

<parameters>
/ disallow_intersection = true //set this to false if you want to see what happens without the intersection check
</parameters>



Edited 3 Years Ago by Dave
Alyson
Alyson
New Member (48 reputation)New Member (48 reputation)New Member (48 reputation)New Member (48 reputation)New Member (48 reputation)New Member (48 reputation)New Member (48 reputation)New Member (48 reputation)New Member (48 reputation)
Group: Forum Members
Posts: 4, Visits: 124
Thank you so much, this was very helpful!
Dave
Dave
Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)Supreme Being (1M reputation)
Group: Administrators
Posts: 12K, Visits: 98K
Alyson - 10/12/2021
Thank you so much, this was very helpful!

Here's a slightly improved version; it's actually bettter and more precise to use the underlying pixel grid as our cartesian coordinate system and perform calculations on that basis.

The below essentially converts the percentage values pulled from the size and position lists to their pixel equivalents on the given display. It would strongly recommend following this approach when scaling up to more circles, as this should adapt better to varying display aspect ratios you will encounter in the wild.

<defaults>
/ inputdevice = keyboard
/ fontstyle = ("Verdana", -13, false, false, false, false, 5, 0)
/ screencolor = grey
</defaults>

********STIMULI***********

<item text>
/1 = "+"
</item>

<text fixation>
/items = text
/select = 1
/hposition = 50%
/vposition = 50%
/fontstyle = ("Arial", 40, false, false, false, false, 5, 0)
/ txbgcolor = transparent
/ erase = false
</text>

<shape dot1>
/shape = circle
/height = 2px*values.dot1r // work in pixel grid
/color = counter.dot_color1.selectedvalue
/hposition = 1px*values.dot1x // work in pixel grid
/vposition = 1px*values.dot1y // work in pixel grid
/ erase = false
</shape>

<shape dot2>
/shape = circle
/height = 2px*values.dot2r // work in pixel grid
/color = counter.dot_color2.selectedvalue
/hposition = 1px*values.dot2x // work in pixel grid
/vposition = 1px*values.dot2y // work in pixel grid
/ erase = false
</shape>

<list h_position>
/items = (30-70) // smaller range for illustration purposes: increases likelihood of dots intersecting or touching
//items = (10-90)
/selectionrate = always
/selectionmode = random
</list>

<list v_position>
/items = (30-70) // smaller range for illustration purposes: increases likelihood of dots intersecting or touching
//items = (10-90)
/selectionrate = always
/selectionmode = random
</list>

<list dot_size>
/items = (20-40)
/selectionrate = always
/selectionmode = random
</list>

<counter dot_color1>
/items = (blue, yellow)
/select = noreplace
/ selectionrate = trial
</counter>

<counter dot_color2>
/items = (blue, yellow)
/select = noreplace
/not = (counter.dot_color1)
/selectionrate = trial
</counter>

********EXPERIMENT***********

<expt intro>
/blocks = [
1=intro
]
</expt>

<block intro>
/trials = [
1-20=intro;
]
</block>

<trial intro>
/ ontrialbegin = [
    values.dot1r = 1px*list.dot_size.nextvalue/100*display.canvasheight/2; //radius 1; converted from percentage to corresponding pixel value
    values.dot1x = 1px*list.h_position.nextvalue/100*display.canvaswidth; // x1; converted from percentage to corresponding pixel value
    values.dot1y = 1px*list.v_position.nextvalue/100*display.canvasheight; // y1; converted from percentage to corresponding pixel value
    
    values.dot2r = 1px*list.dot_size.nextvalue/100*display.canvasheight/2; //radius 2; converted from percentage to corresponding pixel value
    values.dot2x = 1px*list.h_position.nextvalue/100*display.canvaswidth; // x2; converted from percentage to corresponding pixel value
    values.dot2y = 1px*list.v_position.nextvalue/100*display.canvasheight; // y2; converted from percentage to corresponding pixel value
    
    // circles aren't supposed to touch or intersect:
    // while euclidean distance between dot 1 and 2 is less than (dots intersect)
    // or equal to (dots touch) sum of their radii, pick a new size and position for dot 2.
    if (parameters.disallow_intersection) {
        while (sqrt((values.dot1x - values.dot2x) * (values.dot1x - values.dot2x) + (values.dot1y - values.dot2y) * (values.dot1y - values.dot2y)) <= (values.dot1r + values.dot2r)) {
            values.dot2r = 1px*list.dot_size.nextvalue/100*display.canvasheight/2; //radius 2; converted from percentage to corresponding pixel value
            values.dot2x = 1px*list.h_position.nextvalue/100*display.canvaswidth; // x2; converted from percentage to corresponding pixel value
            values.dot2y = 1px*list.v_position.nextvalue/100*display.canvasheight; // y2; converted from percentage to corresponding pixel value
        };
    };
]
/ stimulusframes = [1=clearscreen, dot1, dot2, fixation]
/validresponse = (203, 205)
</trial>

<values>
/ dot1r = 0
/ dot1x = 0
/ dot1y = 0

/ dot2r = 0
/ dot2x = 0
/ dot2y = 0
</values>

<parameters>
/ disallow_intersection = false //set this to false if you want to see what happens without the intersection check
</parameters>

GO

Merge Selected

Merge into selected topic...



Merge into merge target...



Merge into a specific topic ID...




Reading This Topic

Explore
Messages
Mentions
Search