Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@dermotbalson
Last active December 25, 2015 23:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dermotbalson/7057101 to your computer and use it in GitHub Desktop.
Save dermotbalson/7057101 to your computer and use it in GitHub Desktop.
smoke
-- smoke
function setup()
parameter.integer("FPS",0,60,60)
parameter.integer("Particles",0,500,0)
s=Smoke(vec2(WIDTH/2,50))
end
function draw()
FPS=FPS*.9+.1/DeltaTime
background(167, 188, 204, 255)
s:draw()
Particles=#s.p
end
Smoke=class()
function Smoke:init(pos)
self.pos=pos
--particle settings --
self.p={}
self.size=vec2(200,300)
self.expandTime=vec2(70,80) --particles expand to full size over this time (min, max range)
self.fadeTime=vec2(10,20) --particles fade in this number of secs (min, max range)
--texture image--
local w=512 --size of image used as texture
self.img=Smoke.createImage(w)
--create base particle that will just sit at the source and mask the new particles
self.base=SmokeParticle(self.pos,self.img,vec2(15,15),vec2(5,5),vec2(0,0))
--set time to first new particle
self.timer=0
self.wind=math.random()*0.3-0.15 --horizontal wind speed
self.windShift=math.random(10,20) --time to next wind shift
end
function Smoke:draw(wind)
--create new particle when timer gets to 0
self.timer=self.timer-DeltaTime
if self.timer<0 then
local p=SmokeParticle(self.pos,self.img,self.size,self.expandTime,self.fadeTime)
table.insert(self.p,p)
self.timer=2+math.random()*2
end
--reset wind shift when timer reaches 0
self.windShift=self.windShift-DeltaTime
if self.windShift<0 then
self.wind=math.random()*0.3-0.15 --horizontal wind speed
self.windShift=math.random()*10+20
end
self.base:draw(vec2(0,0)) --draw base particle in unchanged position
--draw other particles
local velocity=vec2(self.wind,0.5) --y value is vertical drift speed
for i,p in pairs(self.p) do
if p:draw(velocity)==false then table.remove(self.p,i) end --remove particle if faded completely
end
end
--creates noise image with gray colours based on intensity values
function Smoke.createImage(size)
local min,max=100,255 --gray range used
local m=hmap.create(size,size,0.3)
local z1,z2=0,0
for i=0,m.w do
for j=0,m.h do
z1=math.min(z1,m[i][j])
z2=math.max(z2,m[i][j])
end
end
local img=image(m.w,m.h)
local f=(max-min)/(z2-z1)
for i=1,m.w do
for j=1,m.h do
local intensity=min+f*(m[i][j]-z1)
img:set(i,j,color(intensity))
end
end
return img
end
SmokeParticle=class()
function SmokeParticle:init(pos,img,vecSize,vecExpand,vecFade)
self.pos=vec2(pos.x,pos.y) --current x,y position
self.source=vec2(pos.x,pos.y) --starting position
--particle settings --
local size=math.random(vecSize.x,vecSize.y)
self.expandTime=math.random(vecExpand.x,vecExpand.y)
self.fadeTime=math.random(vecFade.x,vecFade.y)
--create mesh --
self.m=mesh()
self.m.texture=img
local w=img.width
local x1,y1,x2,y2=-size/2,-size/2,size/2,size/2
local tx1,ty1=math.random()*(1-size/w),math.random()*(1-size/w)
local tx2,ty2=tx1+size/w,ty1+size/w
v,t={},{}
v[1]=vec2(x1,y1) t[1]=vec2(tx1,ty1)
v[2]=vec2(x2,y1) t[2]=vec2(tx2,ty1)
v[3]=vec2(x2,y2) t[3]=vec2(tx2,ty2)
v[4]=vec2(x1,y2) t[4]=vec2(tx1,ty2)
v[5]=vec2(x1,y1) t[5]=vec2(tx1,ty1)
v[6]=vec2(x2,y2) t[6]=vec2(tx2,ty2)
self.m.vertices=v
self.m.texCoords=t
self.m:setColors(color(255))
self.m.shader=shader(smokeShader.vertexShader,smokeShader.fragmentShader)
self.m.shader.size=size/w
self.m.shader.fade=1 --0=invisible, 1=full alpha
self.m.shader.centre=vec2((tx1+tx2)/2,(ty1+ty2)/2)
self.timer=0
end
function SmokeParticle:draw(velocity)
self.timer=self.timer+DeltaTime
--calculate how big it has got
self.m.shader.frac=math.max(0.04,math.min(1,self.timer/(self.expandTime+self.fadeTime)))
--if in the fading stage, calculate fade and return false if fade=0
if self.timer>self.expandTime and self.fadeTime>0 then
self.m.shader.fade=1-(self.timer-self.expandTime)/self.fadeTime
if self.m.shader.fade<0 then return false end
end
pushMatrix()
--wind has little effect on smoke near the ground, phase it in over 100 pixels
self.pos.x=self.pos.x+velocity.x*math.min(1,(self.pos.y-self.source.y)/100)
--make smoke drift upwards slowly at first, then increasing
self.pos.y=self.pos.y+velocity.y*math.min(1,self.timer/50)
if self.pos.y>HEIGHT then return false end
translate(self.pos.x,self.pos.y)
self.m:draw()
popMatrix()
return true
end
smokeShader = {
vertexShader = [[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
void main()
{
vColor = color;
vTexCoord = texCoord;
gl_Position = modelViewProjection * position;
}
]],
fragmentShader = [[
precision highp float;
uniform lowp sampler2D texture;
uniform float size;
uniform float frac;
uniform float fade;
uniform vec2 centre;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
void main()
{
lowp vec4 col = texture2D( texture, vTexCoord );
float f = length( centre - vTexCoord ) / size * 2.0 / frac;
if (f >= 1.0 ) discard; //col.a = 0.0;
else col.a = 0.75*( 1.0 - f ) * fade;
gl_FragColor = col;
}
]]}
-- Heightmap module
-- Copyright (C) 2011 Marc Lepage
hmap={}
hmap.H=0
hmap.f=0
-- Create a heightmap using the specified height function (or default)
-- map[x][y] where x from 0 to map.w and y from 0 to map.h
--f = height function, can be left blank
--h = coarseness 0-1
function hmap.create(width, height, h, f)
hmap.f = f and hmap.f or hmap.defaultf
hmap.H=h or .6
-- make heightmap
local map = hmap.diamondsquare(hmap.pot(math.max(width, height)), hmap.f)
-- clip heightmap to desired size
for x = 0, map.w do for y = height+1, map.h do map[x][y] = nil end end
for x = width+1, map.w do map[x] = nil end
map.w, map.h = width, height
return map
end
-- Find power of two sufficient for size
function hmap.pot(size)
local pot = 2
while true do
if size <= pot then return pot end
pot = 2*pot
end
end
-- Create a table with 0 to n zero values
function hmap.tcreate(n)
local t = {}
for i = 0, n do t[i] = 0 end
return t
end
-- Square step
-- Sets map[x][y] from square of radius d using height function f
function hmap.square(map, x, y, d, f)
local sum, num = 0, 0
if 0 <= x-d then
if 0 <= y-d then sum, num = sum + map[x-d][y-d], num + 1 end
if y+d <= map.h then sum, num = sum + map[x-d][y+d], num + 1 end
end
if x+d <= map.w then
if 0 <= y-d then sum, num = sum + map[x+d][y-d], num + 1 end
if y+d <= map.h then sum, num = sum + map[x+d][y+d], num + 1 end
end
map[x][y] = f(map, x, y, d, sum/num)
end
-- Diamond step
-- Sets map[x][y] from diamond of radius d using height function f
function hmap.diamond(map, x, y, d, f)
local sum, num = 0, 0
if 0 <= x-d then sum, num = sum + map[x-d][y], num + 1 end
if x+d <= map.w then sum, num = sum + map[x+d][y], num + 1 end
if 0 <= y-d then sum, num = sum + map[x][y-d], num + 1 end
if y+d <= map.h then sum, num = sum + map[x][y+d], num + 1 end
map[x][y] = f(map, x, y, d, sum/num)
end
-- Diamond square algorithm generates cloud/plasma fractal heightmap
-- http://en.wikipedia.org/wiki/Diamond-square_algorithm
-- Size must be power of two
-- Height function f must look like f(map, x, y, d, h) and return h'
function hmap.diamondsquare(size, f)
-- create map
local map = { w = size, h = size }
for c = 0, size do map[c] = hmap.tcreate(size) end
-- seed four corners
local d = size
local dd=2^-hmap.H
hmap.D=dd
local dx=(math.random()-0.5)*2
map[0][0] = dx --f(map, 0, 0, d, 0)
map[0][d] = dx --f(map, 0, d, d, 0)
map[d][0] = dx --f(map, d, 0, d, 0)
map[d][d] = dx --f(map, d, d, d, 0)
d = d/2 hmap.D=hmap.D*dd
-- perform square and diamond steps
while 1 <= d do
for x = d, map.w-1, 2*d do
for y = d, map.h-1, 2*d do
hmap.square(map, x, y, d, f)
end
end
for x = d, map.w-1, 2*d do
for y = 0, map.h, 2*d do
hmap.diamond(map, x, y, d, f)
end
end
for x = 0, map.w, 2*d do
for y = d, map.h-1, 2*d do
hmap.diamond(map, x, y, d, f)
end
end
d = d/2 hmap.D=hmap.D*dd
end
return map
end
-- Default height function
-- d is depth (from size to 1 by powers of two)
-- h is mean height at map[x][y] (from square/diamond of radius d)
-- returns h' which is used to set map[x][y]
function hmap.defaultf(map, x, y, d, h)
return h + (math.random()-0.5)*hmap.D
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment