Experiment using the physics engine to model the individual bristles in a brush. Also picks up some of the ‘paint’ already on the paper.
-- Physical Brush
-- Herwig Van Marck
function setup()
print("Physical Brush experiment")
canvas=Canvas()
iparameter("size",1,100,50)
iparameter("r",0,255,225)
iparameter("g",0,255,225)
iparameter("b",0,255,225)
iparameter("a",0,255,50)
end
function touched(touch)
if (touch.state==BEGAN) then
br=Brush(touch.x,touch.y,size,50,color(r,g,b,a))
elseif (touch.state==MOVING) then
br:move(touch.x,touch.y)
elseif (touch.state==ENDED) then
br:free()
br=nil
end
end
function draw()
background(0, 0, 0, 255)
strokeWidth(5)
if (br) then
canvas:paint(br)
end
canvas:draw()
if (br) then
br:draw()
end
end
Bristle = class()
function Bristle:init(x,y,clr)
-- you can accept and set parameters here
self.body=physics.body(CIRCLE,4)
self.body.x=x
self.body.y=y
self.body.interpolate=true
self.body.sleepingAllowed=false
self.body.gravityScale=0.1
self.body.restitution=0.0
self.body.density=100
self.x=x
self.y=y
self.color=clr
end
function Bristle:free()
self.body:destroy()
self.body=nil
end
function Bristle:draw()
-- Codea does not automatically call this method
if (self.body) then
pushStyle()
fill(255, 0, 7, 185)
stroke(0, 0, 0, 255)
strokeWidth(1)
ellipseMode(PROJECT)
ellipse(self.body.position.x,self.body.position.y,self.body.radius*2)
popStyle()
end
end
function Bristle:paint(x,y,img)
-- Codea does not automatically call this method
if (self.body) then
pushStyle()
noFill()
stroke(self.color)
strokeWidth(15)
--lineCapMode(SQUARE)
noSmooth()
local rl,gl,bl,al=img:get(self.body.position.x,self.body.position.y)
line(self.x,self.y,self.body.position.x,self.body.position.y)
self.x=self.body.position.x
self.y=self.body.position.y
local f=(255-al)/255.0
self.color=color(self.color.r*0.95+r*0.05*f+rl*0.05*(1-f),
self.color.g*0.95+g*0.05*f+gl*0.05*(1-f),
self.color.b*0.95+b*0.05*f+bl*0.05*(1-f),
self.color.a)
popStyle()
end
end
function Bristle:contact(x,y)
if (self.body) then
return self.body:testPoint(vec2(x,y))
else
return false
end
end
Brush = class()
function Brush:init(x,y,size,nr,clr)
self.x = x
self.y = y
self.size = size
self.color=clr or color(255, 255, 255, 255)
self.nr=0
self.bristles={}
local i=nr
local try=15
while (i>0) do
local r=math.random(0,self.size - 5)
local ang=math.random(0,99)*0.02*math.pi
local xb=r*math.cos(ang)+self.x
local yb=r*math.sin(ang)+self.y
local flg=true
for j,bristle in pairs(self.bristles) do
if bristle:contact(xb,yb)==true then
flg=false
try = try - 1
if try==0 then
try=15
i = i - 1
end
break
end
end
if flg then
local col=color(clamp(0,255,clr.r + math.random(-5,5)),
clamp(0,255,clr.g + math.random(-5,5)),
clamp(0,255,clr.b + math.random(-5,5)),
clamp(0,255,clr.a + math.random(-5,5)))
table.insert(self.bristles,Bristle(xb,yb,col))
self.nr = self.nr + 1
i = i - 1
end
end
print(self.nr.." bristles")
end
function clamp(min,max,val)
if val<min then return min end
if val>max then return max end
return val
end
function Brush:free()
for i,bristle in pairs(self.bristles) do
bristle:free()
end
end
function Brush:move(x,y)
self.x = x
self.y = y
end
function Brush:draw()
-- Codea does not automatically call this method
pushStyle()
fill(255, 255, 255, 40)
stroke(0, 0, 0, 255)
strokeWidth(1)
ellipseMode(CENTER)
ellipse(self.x,self.y,self.size*2)
popStyle()
pushStyle()
stroke(0, 95, 255, 141)
strokeWidth(2)
local gain=2.0
local damp=0.9
for i,bristle in pairs(self.bristles) do
local worldAnchor = bristle.body:getWorldPoint(vec2(0,0))
local touchPoint = vec2(self.x,self.y)
local diff = touchPoint - worldAnchor
local vel = bristle.body:getLinearVelocityFromWorldPoint(worldAnchor)
bristle.body:applyForce( (1/1) * diff * gain - vel * damp, worldAnchor)
--line(self.x,self.y,worldAnchor.x,worldAnchor.y)
--bristle:draw()
end
popStyle()
end
function Brush:paint(img)
pushStyle()
fill(self.color)
stroke(0, 0, 0, 0)
ellipseMode(CENTER)
--ellipse(self.x,self.y,self.size)
popStyle()
for i,bristle in pairs(self.bristles) do
bristle:paint(self.x,self.y,img)
end
end
Canvas = class()
function Canvas:init()
self.image = image(WIDTH,HEIGHT)
end
function Canvas:draw()
pushStyle()
spriteMode(CORNER)
sprite(self.image,0,0)
popStyle()
end
function Canvas:paint(brush)
setContext(self.image)
brush:paint(self.image)
setContext()
end