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! [import]uid: 13050 topic_id: 10625 reply_id: 310625[/import]

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. [import]uid: 5317 topic_id: 10625 reply_id: 38660[/import]

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. :slight_smile:
EDIT: Original code, circles looked like polygons when they expanded too much so changed code a bit
[lua]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)[/lua]
[import]uid: 48521 topic_id: 10625 reply_id: 38900[/import]

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! [import]uid: 13050 topic_id: 10625 reply_id: 38914[/import]

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 :stuck_out_tongue: Thanks! Thats what I get for not reading all the way through! [import]uid: 13050 topic_id: 10625 reply_id: 39056[/import]

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! [import]uid: 13050 topic_id: 10625 reply_id: 39072[/import]

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
[lua] if ( (circles[i].xScale + circles[j].xScale)*r - distanceBetween(circles[i],circles[j])) < 1 then[/lua]

and if the condition make the circle contract as soon as and whenever they over lap… [import]uid: 48521 topic_id: 10625 reply_id: 39276[/import]

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. [import]uid: 13050 topic_id: 10625 reply_id: 39293[/import]

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…
[lua]if ( (circles[i].xScale + circles[j].xScale)*r - distanceBetween(circles[i],circles[j])) >= 1 then[/lua] [import]uid: 48521 topic_id: 10625 reply_id: 39294[/import]

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. [import]uid: 13050 topic_id: 10625 reply_id: 39296[/import]

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… [import]uid: 48521 topic_id: 10625 reply_id: 39298[/import]

great. now i’m on board. certainly going to give that a shot now. Thanks again. [import]uid: 13050 topic_id: 10625 reply_id: 39300[/import]

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!

[import]uid: 13050 topic_id: 10625 reply_id: 40383[/import]