Expanding and contracting circles with variable speed and momentum

Can any of you think of a way to create circles on touch that are capable of expanding until collision, at which point they then contract. I've been throwing around the idea of using transitions and tweening, but since these circles will constantly be expanding and contracting, transitions are clumsy. I also intend to add the ability to make the speed variable through a slider or something, but am unfamiliar with a way to transfer moment by means of the physics engine. By this i mean if a fast moving circle collides with a slower moving circle, the fast one should slow down and the slow one should speed up.

Sorry about the blind dump guys. I haven't come across much that inspired me in the API, so I thought I'd reach out to whomever may see this. Thanks boat loads! If your curious, i'm trying to add functionality to my app, Tonalverse. I hope that better completes the picture. Thanks again!

The only real problem I see is that the collision model that you create for the circle will NOT scale with the object. It stays the size you created it at, unfortunate, but true.

The only way to have accurate collisions would be to destroy and recreate the object on an enterFrame event...not exactly optimal.

as mike4 pointed out, physics won't be able help you here.

You can easily achieve this using transition though.
Proof of concept below, but I think it will need some optimization for large number of circles. It works alright for 20ish circles. Also needs bit of tweaking to avoid overlapping circles which occurs sometimes (I am not sure why yet although it does create some interesting effects).

I am quite happy with the result of this code though.. felt like good coding exercise. :)

EDIT: Original code, circles looked like polygons when they expanded too much so changed code a bit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
local abs = math.abs
local rand = math.random
 
local r = 100
local maxScale = 1
local minScale = 0.1
local transferFactor = 0.20
 
local circles = {}
 
 
local function addCircle(e)
        local x = e.x
        local y = e.y
        
        local circle = display.newCircle(x,y,r)
        circle.speed = rand(3)*0.1
        circle.t = nil
        circle.direction = "expand"
        circle:setFillColor(rand(255),rand(255),rand(255))
        circle:scale(minScale,minScale)
        
        function circle:change(direction)
                if self.t ~= nil then
                        transition.cancel(self.t)
                        self.t = nil
                end
                if direction == "expand" then
                        self.t = transition.to(self, {time =  1000 * (maxScale-self.xScale)/self.speed, xScale = maxScale, yScale = maxScale, onComplete=self.change})
                        self.direction = "expand"
                elseif direction == "shrink" then
                        self.t = transition.to(self, {time = 1000*(self.xScale-minScale)/self.speed, xScale = minScale, yScale = minScale, onComplete=self.change})
                        self.direction = "shrink"
                else
                        if self.direction == "shrink" then
                                self.t = transition.to(self, {time =  1000 * (maxScale-self.xScale)/self.speed, xScale = maxScale, yScale = maxScale, onComplete=self.change})
                                self.direction = "expand"
                        elseif self.direction == "expand" then
                                self.t = transition.to(self, {time = 1000*(self.xScale-minScale)/self.speed, xScale = minScale, yScale = minScale, onComplete=self.change, onComplete=self.change})
                                self.direction = "shrink"
                        end
                end
                self:setFillColor(rand(255),rand(255),rand(255))
        end
        
        function circle:tap(e)
                print("circle tapped")
                return true
        end
        circle:addEventListener("tap",circle)
        table.insert(circles , circle )
        circle:change("expand")
        print("no. of circles : " , #circles)
        
        return true
end
 
 
 
Runtime:addEventListener("tap",addCircle)
 
local function distanceBetween( point1, point2 )
 
        local xfactor = point2.x-point1.x ; local yfactor = point2.y-point1.y
        local distanceBetween = math.sqrt((xfactor*xfactor) + (yfactor*yfactor))
        return distanceBetween
end
 
local timers = {}
 
local function gameLoop(e)
        for i = 1, #circles do
                for j = i+1, #circles do 
                        --print("dist", i, j, abs( (circles[i].xScale + circles[j].xScale)*r - distanceBetween(circles[i],circles[j])))
                        if timers[tostring(i)..","..tostring(j)] == nil then 
                                
                                if abs( (circles[i].xScale + circles[j].xScale)*r - distanceBetween(circles[i],circles[j])) < 1 then
                                        
                                        local speeddif = circles[i].speed - circles[j].speed 
                                        circles[i].speed = circles[i].speed - speeddif * transferFactor
                                        circles[j].speed = circles[j].speed + speeddif * transferFactor
                                        circles[i]:change()
                                        circles[j]:change()
                                        timers[tostring(i)..","..tostring(j)] = e.time
                                end
                        elseif  e.time - timers[tostring(i)..","..tostring(j)] > 50 then
                                        
                                if abs( (circles[i].xScale + circles[j].xScale)*r - distanceBetween(circles[i],circles[j])) < 1 then
                                
                                        local speeddif = circles[i].speed - circles[j].speed 
                                        circles[i].speed = circles[i].speed - speeddif * transferFactor
                                        circles[j].speed = circles[j].speed + speeddif * transferFactor
                                        circles[i]:change()
                                        circles[j]:change()
                                        timers[tostring(i)..","..tostring(j)] = e.time
                                end
                        end             
                end
        end
end
 
Runtime:addEventListener("enterFrame",gameLoop)local abs = math.abs
local rand = math.random
 
local r = 100
local maxScale = 1
local minScale = 0.1
local transferFactor = 0.20
 
local circles = {}
 
 
local function addCircle(e)
        local x = e.x
        local y = e.y
        
        local circle = display.newCircle(x,y,r)
        circle.speed = rand(3)*0.1
        circle.t = nil
        circle.direction = "expand"
        circle:setFillColor(rand(255),rand(255),rand(255))
        circle:scale(minScale,minScale)
        
        function circle:change(direction)
                if self.t ~= nil then
                        transition.cancel(self.t)
                        self.t = nil
                end
                if direction == "expand" then
                        self.t = transition.to(self, {time =  1000 * (maxScale-self.xScale)/self.speed, xScale = maxScale, yScale = maxScale, onComplete=self.change})
                        self.direction = "expand"
                elseif direction == "shrink" then
                        self.t = transition.to(self, {time = 1000*(self.xScale-minScale)/self.speed, xScale = minScale, yScale = minScale, onComplete=self.change})
                        self.direction = "shrink"
                else
                        if self.direction == "shrink" then
                                self.t = transition.to(self, {time =  1000 * (maxScale-self.xScale)/self.speed, xScale = maxScale, yScale = maxScale, onComplete=self.change})
                                self.direction = "expand"
                        elseif self.direction == "expand" then
                                self.t = transition.to(self, {time = 1000*(self.xScale-minScale)/self.speed, xScale = minScale, yScale = minScale, onComplete=self.change, onComplete=self.change})
                                self.direction = "shrink"
                        end
                end
                self:setFillColor(rand(255),rand(255),rand(255))
        end
        
        function circle:tap(e)
                print("circle tapped")
                return true
        end
        circle:addEventListener("tap",circle)
        table.insert(circles , circle )
        circle:change("expand")
        print("no. of circles : " , #circles)
        
        return true
end
 
 
 
Runtime:addEventListener("tap",addCircle)
 
local function distanceBetween( point1, point2 )
 
        local xfactor = point2.x-point1.x ; local yfactor = point2.y-point1.y
        local distanceBetween = math.sqrt((xfactor*xfactor) + (yfactor*yfactor))
        return distanceBetween
end
 
local timers = {}
 
local function gameLoop(e)
        for i = 1, #circles do
                for j = i+1, #circles do 
                        --print("dist", i, j, abs( (circles[i].xScale + circles[j].xScale)*r - distanceBetween(circles[i],circles[j])))
                        if timers[tostring(i)..","..tostring(j)] == nil then 
                                
                                if abs( (circles[i].xScale + circles[j].xScale)*r - distanceBetween(circles[i],circles[j])) < 1 then
                                        
                                        local speeddif = circles[i].speed - circles[j].speed 
                                        circles[i].speed = circles[i].speed - speeddif * transferFactor
                                        circles[j].speed = circles[j].speed + speeddif * transferFactor
                                        circles[i]:change()
                                        circles[j]:change()
                                        timers[tostring(i)..","..tostring(j)] = e.time
                                end
                        elseif  e.time - timers[tostring(i)..","..tostring(j)] > 50 then
                                        
                                if abs( (circles[i].xScale + circles[j].xScale)*r - distanceBetween(circles[i],circles[j])) < 1 then
                                
                                        local speeddif = circles[i].speed - circles[j].speed 
                                        circles[i].speed = circles[i].speed - speeddif * transferFactor
                                        circles[j].speed = circles[j].speed + speeddif * transferFactor
                                        circles[i]:change()
                                        circles[j]:change()
                                        timers[tostring(i)..","..tostring(j)] = e.time
                                end
                        end             
                end
        end
end
 
Runtime:addEventListener("enterFrame",gameLoop)

Thank you so much for your well detailed response! I won't be able to run your code till tomorrow morning, but reading through your provided solution leaves me quite excited. Thank you again! I will report back soon!

Is it my understanding that with each tap, two circles are created? Thank you again for your substantial effort!

EDIT: The code is simply duplicated in the above post :P Thanks! Thats what I get for not reading all the way through!

After fairly rigorous testing, it looks as if an iPhone 4 can only handle 12-14 circles. I'm currently working to create some preventative measure for when the circles almost endless engage one another before expanding past their bounds. My suspicion is that this problem arises in the calculation of circle proximity when two circles are within a few pixels of each other. Thanks again to chinmay.patil for the excellent start!

Glad you liked it.. I don't think it's most optimized solution but I still liked coding it.

Ya.. avoiding the "collision" handler triggering was the only challenge. I implemented timer check for this. so for given circle pair, you can adjust the time after one collision to check for next collision.

this helped me avoid jamming fo circle if they are changing very slowly.. but it introduces new problem that makes them expand beyond bounds of each other sometimes.

Maybe you can change/add to collision condition a bit to following

1
 if ( (circles[i].xScale + circles[j].xScale)*r - distanceBetween(circles[i],circles[j])) < 1 then

In a sense, the timmer is insuring that two circles are not constantly engaging and can contract. When a third is added one of the contracting circles and the newly added circle hit one another, the first two circles again should engage, but the timmer condition has not yet been met. Interesting. I'm not certain how the condition you provided above will prevent this from happening. Maybe a flag is necessary resetting the timmer if either circle is engaged by other circles? hm.

The new condition checks whether the sum of radii of 2 circles is more than distance between center, and if it is so, then the circle should always contract. Right?

btw. there was error in my condition it should be other way round..

1
if ( (circles[i].xScale + circles[j].xScale)*r - distanceBetween(circles[i],circles[j])) >= 1 then

is this an alteration to the existing conditions? or are you adding this condition as a third condition in the gameLoop function? I apologize for my misunderstanding.

Naah mate.. don't worry with apologies.. It's always difficult to figure out what's going on in other coder's code without proper comments.

I would say try it as alterations first and within condition instead of change() call shrink()..

What our previous code did was check if the two circles are just touching each other and ignored if they have started overlapping.

The new condition will force shrinking if there is any overlapping of 1 or more pixels..

great. now i'm on board. certainly going to give that a shot now. Thanks again.

Hello my friend, I've forwarded the problems I am having to this thread:

http://developer.anscamobile.com/forum/2011/06/10/expanding-and-contracting-circles-collision-issues#comment-40382

I could use the extra eye, especially given your familiarity with you code. Thanks!

views:2111 update:2011/9/20 13:12:00
corona forums © 2003-2011